僅使用STB_TRUETYPE在純SDL2中渲染文本的僅標題庫。這是繪製的,即允許快速文本渲染的字形。它還提供了幾種簡單的方法來渲染一個字符串以使紋理以使更快的文本渲染。
新(2022)!可以使用producerConsumerFrontend.h的多線程環境中的prerender!
新(2021)!也可以使用bgfxFrontend.h在BGFX中渲染!

示例文本來自http://www.columbia.edu/~fdc/utf8/index.html

liam twigger- @snapperthetwig
fc.drawText(x, y, string)fc.renderTextToTexture(string, &widthOut, &heightOut)在英特爾I7-8750H上:
如果使用紋理( renderTextToTexture / renderTextToObject ),則示例圖像需要0.7ms渲染(〜1400 fps)。速度將是SDL2使您可以在紋理上翻轉紋理的最大值。
如果直接呈現( drawText ),則示例圖像需要約5ms渲染(〜200 fps)(〜200 fps)
對於持續多個幀的文本,您應該使用renderTextToTexture或renderTextToObject緩存。
格式化文字:
非SDL前端:
這是一個僅標題庫 - 無需構建系統
在任何標題中:
# 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查找 - 如果您使用多行字符串嘗試此操作,則可能會返回不正確的值。
TAB寬度由可變fc.tabWidth處理。選項卡之後的字符將與下一個標籤位置對齊。
您可以在調用fc.loadFont(...)之前將fc.tabWidthInSpaces = X ,將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啟用它:
#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等)來完成的所有操作)
為了使BOLD/斜體變體工作,您必須加載字體的粗體/斜體變體。對於粗體+斜體,您必須加載字體的粗體+斜體變體!該庫自動生成了下劃線和罷工。
加載變體後,加載基本字體後,使用addFormatFont或addFormatFontManaged 。如果您關心這些字體在BOLD/ITALIC中可用,則也應該為後備字體(用於多語言支持)進行此操作。
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);如果您請求粗體或斜體字符串,並且沒有大膽或斜體變體可用,則將使用常規變體。如果您請求一個粗體+斜體字符串,並且只有一個加載(但不是組合),則將使用最後加載的變體 - 因此,如果您請求Bold+Italic且已加載粗體,然後將斜體(但不是Bold Italic)加載,則呈現斜體。
圖書館將繪製下劃線並罷工本身,您無需提供這些變體。
包括bgfxFrontend.h ,並創建一個bgfx_stb_font_cache的實例。
請參閱bgfxExample.cpp查看如何使用此前端。
一些註釋:
sttfont_uint32_t_range::populateRangesLatin()並在渲染前填充地圖集可能是有益的。如果一個地圖集充滿了字形,則前端將創建一個新的地圖集頁。確保創建/編輯.lzz文件,而不是生成的.h文件。提供了從.lzz源創建標頭文件的工具 +腳本。
感謝SDL和STB項目的貢獻!
公共領域
STB_TRUETYPE是公共領域
Noto字體為(C)Google,並根據SIL Open Font許可證(版本1.1)發布。請參閱https://www.google.com/get/noto/