Библиотека только для заголовка для рендеринга текста в Pure SDL2 с STB_TRUETYPE. Это кэширует глифы, когда они нарисованы, позволяя быстро рендеринг текста. Он также предоставляет несколько простых способов отобрать строку для текстуры для даже более быстрого текстового рендеринга.
Новый (2022)! Может ли прерандер в многопоточной среде с producerConsumerFrontend.h .
Новый (2021)! Может также рендеринг в BGFX с bgfxFrontend.h !

Пример текста от http://www.columbia.edu/~fdc/utf8/index.html

Liam Twigger - @SnapperTheTwig
fc.drawText(x, y, string)fc.renderTextToTexture(string, &widthOut, &heightOut)На Intel I7-8750H:
Пример изображения занимает 0,7 мс для рендеринга (~ 1400 кадров в секунду) при использовании текстуры ( renderTextToTexture / renderTextToObject ). Скорость будет максимальной, которую SDL2 позволяет вам перевернуть текстуру.
Пример изображения занимает ~ 5 мс для рендеринга (~ 200 кадров в секунду), если рендеринг напрямую ( drawText )
Для текста, который длится более одного кадра, вы должны кэшировать его с помощью renderTextToTexture или renderTextToObject .
Форматированный текст:
Не-SDL Frontends:
Это библиотека только заголовка - система сборки не требуется
В любом заголовке:
# include " path/to/sdlStbFont/sdlStbFont.h "В одном .cpp:
# define SDL_STB_FONT_IMPL
# include " path/to/sdlStbFont/sdlStbFont.h " Эта библиотека зависит от STB_TRUETYPE. Это автоматически включает это. Если вы не хотите, чтобы это автоматически включало, используйте #define STB_TRUETYPE_INCLUDE_HANDLED и обрабатывайте включение STB_TRUETYPE.H.
# define SDL_STB_FONT_IMPL
# include " sdlStbFont.h "
...
// Load font
char * ttfFontFromMemory = loadFontFileSomehow( " path/to/file.ttf " );
sdl_stb_font_cache fc;
fc.faceSize = 24 ; // Must be set before loadFont()!
fc.tabWidthInSpaces = 8 ; // Optional
fc.loadFont(ttfFontFromMemory);
...
// Setup renderer
SDL_Renderer * mSdlRenderer = SDL_CreateRenderer( mWindow , SDL_RENDERER_SOFTWARE, 0 );
fc.bindRenderer( mSdlRenderer ); // Must bind a renderer before generating any glyphs
...
// Main loop
SDL_SetRenderDrawColor ( mSdlRenderer , 125 , 125 , 125 , 255 );
SDL_RenderClear ( mSdlRenderer );
fc.drawText( 5 , 5 , " Hello world! " );
SDL_RenderPresent ( mSdlRenderer );
// fc will clean itself up when it falls out of scope
delete[] ttfFontFromMemory; // You must manually remove the char buff when done
// if you want auto management use fc.loadFontManaged(), which will transfer
// ownership of ttfFontFromMemory to fc and be freed when done.Вы также можете получить ширину и высоту отображаемой струны:
fc.drawText(5, 5, widthOut, heightOut, "Hello world!");
// Draws "hello world" and sets widthOut and heightOut to the size of the string
class sdl_stb_font_cache {
...
public:
int ascent;
int descent;
int lineGap;
int baseline;
int rowSize;
int tabWidth; // May be changed later
float scale;
float underlineThickness;
float strikethroughThickness;
float underlinePosition;
float strikethroughPosition;
// Must be set before loadFont() is called. Cannot be changed after
int faceSize; // Default is 20. All the parameters are calcualted based on this
int tabWidthInSpaces; // Default is 8, set this before loadFont(). Max 128. Sets tabWidth when font is loaded
...
}
fc.loadFont(ttfFontFromMemory);
fc.addFont(someSecondFontFromMemory);
fc.addFont(someThirdFontFromMemory);
...
etc
Первая загрузка шрифта должна быть с «LoadFont».
Обратите внимание, что все шрифты должны быть загружены до того, как будут вызваны любые функции чертежа. Если глиф не найден в первом шрифте, то второй шрифт будет искать и т. Д.
int w, h;
fc.getTextSize(w, h, " Text " ); // Get width and height at same time
// Also
h = fc.getTextHeight( " Text " ); // Faster, if only height is needed
w = fc.getTextWidth( " Text " ); // Internally just a wrapper for getTextSize
int nRows = fc.getTextRows( " Text n More text " ); // UTF8 safe, returns the number of rows of text - here it's 2
int numNewlines = fc.getNumNewLines Совет: drawText() Возвращает x координату конца нарисованной строки или форматированного текстового объекта.
Для обертывания линии используйте функцию fc.breakString(stringIn, arrayOut, width) . Это преобразует строку или форматированный текстовый объект в вектор строк или форматированного текста, каждый из которых меньше ширины. Сначала это попытается сломать новые линии и пробелы, тогда он будет разделять слова, если они длиннее указанной ширины. Это UTF8 безопасно.
Ручное управление:
char * mFontData = loadFontFromFileSomehow( " path/to/file.ttf " );
fc.loadFont( mFontData );
// You will have to free mFontData when you are done with it
// fc does not copy mFontData internally
// using fc after free'ing mFontData will result in undefined behaviourАвтоматическое управление:
filep * file = openFile( " path/to/file " );
sttfont_memory mFontData ;
mFontData .alloc(file_size);
fread (file, & mFontData .data);
fc.loadFontManaged( mFontData );
// fc now owns mFontData's contents and will free it when fc is destroyed
// You can safely let mFontData fall out of scope
// Also addFontManaged is avaliable for adding fallback fonts Первый путь:
// creating
int RTw, RTh;
SDL_Texture * RT;
RT = fc.renderTextToTexture ( " Text " , &RTw, &RTh);
// Rendering
SDL_Rect r;
r.x = 5 ;
r.y = 5 ;
r.w = RTw;
r.h = RTh;
SDL_RenderCopy ( mSdlRenderer , RT , NULL , &r); Второй путь (тот же эффект, но чище)
// creating
sdl_stb_prerendered_text prt;
prt. mRenderer = your__SDL_Render__instance;
fc.renderTextToObject(&prt, " Text " );
// Rendering
prt.draw(x, y);
// Rendering in colour & alpha
prt.drawWithColor(x, y, 255 , 185 , 85 , 255 );
// Cleanup
prt.freeTexture(); Совет: prt.draw() возвращает x координату конца объекта.
Используйте SDL_SetTextureColorMod с кэшированной текстурой. Или используйте sdl_stb_prerendered_text::drawWithColor .
Используйте getCaretPos(text, relativeMouseX, relativeMouseY)
Обратите внимание, что это только в настоящее время поддерживает Carret поиск в струнах без новичков - если вы попытаетесь это с помощью многослойной строки, то это может вернуть неверное значение.
Ширина вкладок обрабатывается переменной fc.tabWidth . Символы после вкладки будут соответствовать следующему местоположению вкладки.
Вы можете установить fc.tabWidthInSpaces = X перед вызовом fc.loadFont(...) , чтобы автоматически установить fc.tabWidth на некоторую кратную ширину пространства. По умолчанию шрифты установлены на 8 мест в ширине. Более низкие значения лучше для моноспешений, более высокие значения лучше для немоскожных шрифтов.
Эта библиотека состоит из двух частей - бэкэнд обработки шрифта (классы с именем sttfont_* ) и рендеринг SDL ( sdl_stb_* ).
Чтобы сделать свой собственный рендеринг фронтенда, расширить соответствующие классы sttfont_* . Смотрите реализацию SDL для деталей. Его ~ 200 строк кода, все, что вам нужно сделать, это убрать конкретные вещи SDL и поместить в свой карендере. В ваше приложение включите sttFont.h вместо sdlStbFont.h
Вы можете использовать pregenGlyphs function :
std::vector<sttfont_uint32_t_range> codepoint_ranges;
sttfont_uint32_t_range customRange;
customRange.start = 1337 ; customRange.end = 1444 ; // end is inclusive
sttfont_uint32_t_range::populateRangesLatin (codepoint_ranges); // add the Latin character set
sttfont_uint32_t_range::populateRangesCyrillic (codepoint_ranges); // add the Cyrillic character set
fc.pregenGlyphs(codepoint_ranges, 0 ); // generatess the glypsЕсли вы занимаетесь рендерингом программного обеспечения в SDL , это не нужно , и просто замедлит запуск. Если вы используете пользовательский фронт, который использует атлазы текстур (например, BGFX), это рекомендуется.
Используйте producerConsumerExample.h . См. producerConsumerExample.cpp для проработанного примера.
Идея состоит в том, что вы создаете создание объекта producer_consumer_font_cache , который используется между вашим производителем и потребительским потоком. У этого объекта есть член, который указывает на фактический фронт, используемый потребителем:
Инитализация:
producer_consumer_font_cache mPcCache ;
sdl_stb_font_cache mSdlFontCache ;
mPcCache .consumer_font_cache = & mSdlFontCache ;
sttfont_memory m;
m.data = &fontData[ 0 ];
m.size = fontData.size();
m.ownsData = false ;
mPcCache .faceSize = 24 ;
mPcCache .loadFontManagedBoth(m); // Loads the font into both frontendsПроизводство:
pcfc_prerendered_text prt;
mPcCache .renderTextToObject(&prt, " Prerendered text from Producer Thread! " ); // prt.handle now holds a handle
pcfc_handle h = mPcCache .pushText( 5 , 5 , " Hello World! " ); // use this instead of "drawText"
mPcCache .submitToConsumer(); // sends to consumerПотребление:
// <somehow send prt.handle and h to consumer thread>
// Suggestion: use a concurrentqueue (https://github.com/cameron314/concurrentqueue)
// and/or some kind of command buffer (https://github.com/SnapperTT/nanovg_command_buffer)
mPcCache .receiveFromProducer();
mPcCache .dispatchPrerenderJobs<sdl_stb_prerendered_text>(); // takes the prerended text and creates sdl_stb_prerended_text objects (invokes mPcCache.consumer_font_cache->renderTextToObject)
mPcCache .dispatchSinglePrerendered(prt.handle, 5 , 5 ); // actually draws the prerendered text
mPcCache .dispatchSingleText(h); // Renders "hello world" at 5,5Очистка:
// Cleanup - just let mPcCache fall out of scope
mPcCache .freeStoredPrerenderedText( true ); // deletes all prerendered text objects stored. true == also calls prt->freeTexture() for all prerendered text
// this is manual destruction as destroying a large number of objects can be expensive, esp. when you want to exit quickly
// Don't forget to delete mPcCache.consumer_font_cache if it was heap allocated
delete mPcCache .consumer_font_cache;UserData: Вы можете отправить необработанное указатель вместе с текстом.
mPcCahce.pushUserdata(void*); // producer
void* foo = mPcCahce.getUserdata(); // consumer
Если вы не делаете этого, std::mutex используется для передачи состояния от производителя к потребителю. В временном буфере есть только один слот. У вас должен быть какой -то механизм, чтобы остановиться, например, производитель, работающий быстрее, чем потребитель.
Если вы хотите использовать очередь, я рекомендую MoodyCamel :: ReaderWriterQueue. Вы можете включить его с помощью producer_consumer_font_cache by:
#define SSF_CONCURRENT_QUEUE moodycamel::ReaderWriterQueue
Опрос для изменений:
if (mPcCache.receiveFromProducer()) { // wraps moodycamel::ReaderWriterQueue::try_dequeue()
// have dequeued!
}
Очистка (промыть очередь)
while (mPcCache.receiveFromProducer()) {
// pulls stuff from the queue until its empty
delete mPcCache.getUserdata(); // if we're using heap allocated userdata here is how to clear it
}

Сначала создайте sttfont_formatted_text . Приведенный выше пример был создан с помощью:
sttfont_formatted_text formattedText;
formattedText << sttfont_format::black << " Plain text "
<< sttfont_format:: bold << " bold text "
<< sttfont_format:: italic << " italic text n "
<< sttfont_format:: underline << sttfont_format::green << " underline text t "
<< sttfont_format::strikethrough << " strikethrough text n "
<< sttfont_format::red << sttfont_format:: bold << " red bold t "
<< sttfont_format:: bold << " not red bold t "
<< sttfont_format::red << " red not bold n "
<< sttfont_format:: bold << sttfont_format:: italic << sttfont_format::colour( 255 , 127 , 50 ) << " custom colour t "
<< sttfont_format:: bold << sttfont_format::strikethrough << sttfont_format::colour( 127 , 255 , 50 ) << " bold strikethrough n "
<< sttfont_format:: bold << sttfont_format:: underline << sttfont_format::colour( 0 , 50 , 200 ) << " bold underline t "
<< sttfont_format:: italic << sttfont_format::strikethrough << sttfont_format::colour( 255 , 255 , 50 ) << " italic strikethrough n "
<< sttfont_format:: italic << sttfont_format:: underline << sttfont_format::colour( 127 , 50 , 255 ) << " italic underline " Вы можете объединить параметры форматирования с оператором << . Форматирование сброшено после вставки строки.
Затем вы можете отображать, используя те же функции, которые использовались для простых строк ( drawText(x, y, formattedText) и т. Д.). Это включает в себя рендеринг в текстуре с помощью renderTextToObject . Все, что вы можете сделать с простой строкой, вы сможете выполнить с той же названной функцией ( getTextSize , getNumRows , getCaretPos и т. Д.)
Для того, чтобы жирные/курсивные варианты работали, вы должны загрузить жирный/курсивый вариант шрифта. Для жирного шрифта. Подчеркивает и удары генерируются автоматически этой библиотекой.
Чтобы загрузить вариант, используйте addFormatFont или addFormatFontManaged после загрузки базового шрифта. Это также должно быть сделано для запасных шрифтов (для многоязычной поддержки), если вы заботитесь о том, чтобы эти шрифты были доступны в жирном шрифте/курсинге.
fc.loadFontManaged(notoSans); // First font - loadFont
fc.addFormatFontManaged(sttfont_format::FORMAT_BOLD, notoSansBold);
fc.addFormatFontManaged(sttfont_format::FORMAT_ITALIC, notoSansItalic);
fc.addFormatFontManaged(sttfont_format::FORMAT_BOLD | sttfont_format::FORMAT_ITALIC, notoSansBoldItalic);
fc.addFontManaged(notoSansArmenian); // Fallback fonts - addFont
fc.addFormatFontManaged(sttfont_format::FORMAT_BOLD, notoSansArmenianBold);
fc.addFormatFontManaged(sttfont_format::FORMAT_ITALIC, notoSansArmenianItalic);
fc.addFormatFontManaged(sttfont_format::FORMAT_BOLD | sttfont_format::FORMAT_ITALIC, notoSansArmenianBoldItalic);Если вы запросите жирную жирную или курсивную строку, а также нет жирного шрифта или курсивного варианта, будет использоваться обычный вариант. Если вы запросите жирную шрифту+курсивную строку, и есть только одна загруженная (но не комбинация), будет использован последний загруженный вариант - поэтому, если вы запросите жирный жирный цвет+курсив и загружен жирным шрифтом, а затем и курсив (но не жирным шрифтом+курсив), то курсив будет отображаться.
Библиотека будет нарисовать подчеркивание и удивлять сами варианты, вам не нужно их предоставлять.
Включите bgfxFrontend.h и создайте экземпляр bgfx_stb_font_cache .
См. bgfxExample.cpp , чтобы увидеть, как использовать этот фронт.
Некоторые примечания:
sttfont_uint32_t_range::populateRangesLatin() и аналогично заполнению атласа перед рендерингом. Если атлас заполнен глифами, то Frontend создаст новую страницу Atlas. Обязательно создайте/отредактируйте файлы .lzz , а не сгенерированные файлы .h . Инструменты + сценарии для создания файлов заголовка из источников .lzz предоставляются.
Благодаря вкладам как в проектах SDL, так и STB!
Общественный достояние
STB_TRUETYPE - это общественное достояние
Нотовые шрифты (C) Google и выпущены по лицензии SIL Open Font, версия 1.1. Смотрите https://www.google.com/get/noto/