Eine nur Header-Bibliothek zur Renderung von Text in reinem SDL2 mit STB_Truetype. Dieser schneidet die Glyphen aus, während sie gezeichnet werden, sodass eine schnelle Textrepublikation ermöglicht. Es bietet auch ein paar einfache Möglichkeiten, eine Zeichenfolge für eine noch schnellere Textrevision auf Textur zu rendern.
Neu (2022)! Kann in Multithread -Umgebungen mit producerConsumerFrontend.h !
Neu (2021)! Kann auch in BGFX mit bgfxFrontend.h !

Beispieltext von http://www.columbia.edu/~fdc/utf8/index.html

Liam Tmigger - @snapperthetwig
fc.drawText(x, y, string)fc.renderTextToTexture(string, &widthOut, &heightOut)Auf einem Intel i7-8750h:
Das Beispielbild braucht 0,7 ms zum Rendern (~ 1400 fps), wenn Textur verwendet wird ( renderTextToTexture / renderTextToObject ). Die Geschwindigkeit ist das Maximum, dass Sie mit SDL2 eine Textur umdrehen können.
Das Beispielbild braucht ~ 5 ms zum Rendern (~ 200 fps), wenn es direkt rendert ( drawText )
Für Text, der mehr als einen Frame dauert, sollten Sie ihn entweder mit renderTextToTexture oder renderTextToObject zwischenspeichern.
Formatierter Text:
Nicht-SDL-Frontenden:
Dies ist nur eine Bibliothek mit Header - kein Build -System erforderlich
In jedem Kopfball:
# include " path/to/sdlStbFont/sdlStbFont.h "In einem .cpp:
# define SDL_STB_FONT_IMPL
# include " path/to/sdlStbFont/sdlStbFont.h " Diese Bibliothek hat eine Abhängigkeit von STB_Truetype. Es wird dies automatisch einschließen. Wenn Sie es nicht automatisch enthalten möchten, verwenden Sie #define STB_TRUETYPE_INCLUDE_HANDLED und verarbeiten Sie die Einschließung von stb_trueType.h selbst.
# 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.Sie können auch die Breite und Höhe einer gerenderten Zeichenfolge erhalten:
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
Die erste Schriftart muss mit "Loadfont" sein.
Beachten Sie, dass alle Schriftarten geladen werden müssen, bevor alle Zeichnungsfunktionen aufgerufen werden. Wenn in der ersten Schrift keine Glyphe nicht gefunden wird, wird die zweite Schriftart usw. durchsucht.
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 Tipp: drawText() gibt die X -Koordinate des Endes einer gezogenen Zeichenfolge oder eines formatierten Textobjekts zurück.
Verwenden Sie für die Linienverpackung die Funktion fc.breakString(stringIn, arrayOut, width) . Dies wandelt eine Zeichenfolge oder ein formatiertes Textobjekt in einen Vektor aus Zeichenfolgen oder formatierten Textobjekten um, von denen jeweils weniger als die Breite ist. Dies wird versuchen, zuerst Neulinien und Whitespace zu brechen, dann wird es Wörter aufteilt, wenn die länger als die angegebene Breite sind. Dies ist UTF8 sicher.
Handliches Management:
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 behaviourAutomatisches Management:
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 Erster Weg:
// 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); Zweiter Weg (gleicher Effekt, aber sauberer)
// 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(); Tipp: prt.draw() gibt die X -Koordinate des Endes des Objekts zurück.
Verwenden Sie SDL_SetTextureColorMod mit einer zwischengespeicherten Textur. Oder verwenden Sie sdl_stb_prerendered_text::drawWithColor .
Verwenden Sie getCaretPos(text, relativeMouseX, relativeMouseY)
Beachten Sie, dass dies derzeit nur Carret Lookup in Saiten ohne Neulinge unterstützt. Wenn Sie dies mit einer Multiline -Zeichenfolge versuchen, kann dies einen falschen Wert zurückgeben.
Die Registerkartenbreite wird von der variablen fc.tabWidth behandelt. Zeichen nach einer Registerkarte werden sich auf den nächsten Registerkarte ausrichten.
Sie können fc.tabWidthInSpaces = X festlegen, bevor Sie fc.loadFont(...) so aufrufen, dass fc.tabWidth automatisch auf ein Vielfaches der Space -Breite festgelegt wird. Standardmäßig werden Schriftarten auf 8 Räume in der Breite eingestellt. Niedrigere Werte sind für Monospace-Schriftarten besser, höhere Werte sind für nicht-monospace-Schriftarten besser.
Diese Bibliothek besteht aus zwei Teilen - einem Backend (Klassen namens sttfont_* ) und einem SDL -Rendering -Frontend ( sdl_stb_* ).
Um Ihr eigenes Rendering -Frontend zu machen, erweitern Sie die relevanten sttfont_* -Klasse. Weitere Informationen finden Sie in der SDL -Implementierung. Seine ~ 200 Codezeilen, alles, was Sie tun müssen, ist, die SDL -spezifischen Sachen herauszunehmen und Ihr rendererspezifisches Zeug einzugeben. In Ihrer Bewerbung sttFont.h anstelle von sdlStbFont.h einschließen
Sie können die pregenGlyphs function verwenden:
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 glypsWenn Sie Software -Rendering in SDL sind, wird dies nicht benötigt und wird nur das Start verlangsamt. Wenn Sie ein benutzerdefiniertes Frontend verwenden, das Texturatlasen (z. B. BGFX) verwendet, wird dies empfohlen.
Verwenden Sie producerConsumerExample.h . Siehe producerConsumerExample.cpp für ein bearbeitetes Beispiel.
Die Idee ist, dass Sie ein producer_consumer_font_cache -Objekt instanziieren, das zwischen Ihrem Produzenten- und Verbraucher -Thread geteilt wird. Dieses Objekt hat ein Mitglied, das auf die vom Verbraucher verwendete Frontend verweist:
Initalisierung:
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 frontendsProduzieren:
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 consumerKonsumieren:
// <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,5Reinigung:
// 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: Sie können zusammen mit Ihrem Text einen Rohzeiger einreichen.
mPcCahce.pushUserdata(void*); // producer
void* foo = mPcCahce.getUserdata(); // consumer
Wenn Sie dies nicht tun, wird ein std::mutex verwendet, um den Zustand vom Produzenten an den Verbraucher weiterzugeben. Es gibt nur einen Schlitz im Übergangspuffer. Sie müssen über einige Mechanisims stehen, z. B. dem Hersteller, der schneller als der Verbraucher läuft.
Wenn Sie eine Warteschlange verwenden möchten, empfehle ich MoodyCamel :: ReaderWriterqueue. Sie können es mit producer_consumer_font_cache aktivieren, indem Sie:
#define SSF_CONCURRENT_QUEUE moodycamel::ReaderWriterQueue
Umfragen für Änderungen:
if (mPcCache.receiveFromProducer()) { // wraps moodycamel::ReaderWriterQueue::try_dequeue()
// have dequeued!
}
Reinigung (die Warteschlange spülen)
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
}

Erstellen Sie zuerst einen sttfont_formatted_text . Das obige Beispiel wurde erstellt mit:
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 " Sie können Formatierungsoptionen mit dem << Operator kombinieren. Die Formatierung wird zurückgesetzt, nachdem eine Zeichenfolge eingefügt wurde.
Dann können Sie mit den gleichen Funktionen für einfache Zeichenfolgen ( drawText(x, y, formattedText) usw.) rendern. Dies beinhaltet das Rendern von Textur mit renderTextToObject . Alles, was Sie mit einer einfachen Zeichenfolge tun können, sollten Sie mit der gleich benannten Funktion in der Lage sein ( getTextSize , getNumRows , getCaretPos usw.)
Damit mutige/kursive Varianten funktionieren, müssen Sie eine fett-/kursive Variante der Schriftart laden. Für mutige+kursive ITALIC MÜSSEN Sie eine fetthaltige+kursive Variante der Schriftart laden! Untersteuerung und Schläge werden von dieser Bibliothek automatisch generiert.
Zum Laden einer Variante verwenden Sie addFormatFont oder addFormatFontManaged nach dem Laden einer Basisschrift. Dies sollte auch für Fallback -Schriftarten (für mehrsprachige Unterstützung) erfolgen, wenn Sie sich daran interessieren, dass diese Schriftarten fett/kursiv sind.
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);Wenn Sie eine mutige oder kursive Zeichenfolge anfordern und es keine mutige oder kursive Variante gibt, wird die reguläre Variante verwendet. Wenn Sie eine fetthaltige+kursive Zeichenfolge anfordern und nur eine geladene (aber nicht die Kombination) Die zuletzt geladene Variante wird verwendet. Wenn Sie also fett+italisch anfordern und fett geladen sind, dann wird es zu einer kursiven (aber nicht fett+italischen).
Die Bibliothek wird unterstreichen und streift Varianten selbst, Sie müssen diese nicht bereitstellen.
Fügen Sie bgfxFrontend.h ein und erstellen Sie eine Instanz von bgfx_stb_font_cache .
Siehe bgfxExample.cpp um zu sehen, wie diese Frontend verwendet werden.
Einige Notizen:
sttfont_uint32_t_range::populateRangesLatin() aufzurufen und den Atlas vor dem Rendering zu bevölkern. Wenn ein Atlas mit Glyphen gefüllt ist, erstellt der Frontend eine neue Atlas -Seite. Stellen Sie sicher, dass Sie die .lzz -Dateien erstellen/bearbeiten, nicht die generierten .h -Dateien. Die Tools + Skripte zum Erstellen der Header -Dateien aus den .lzz -Quellen werden bereitgestellt.
Vielen Dank an die Beiträge an den SDL- und STB -Projekten!
Öffentlich zugängliche
STB_TruType ist Public Domain
Noto -Schriftarten sind (c) Google und werden unter der SIL Open -Schriftart, Version 1.1, veröffentlicht. Siehe https://www.google.com/get/noto/