Une bibliothèque d'en-tête uniquement pour rendre du texte en pur SDL2 avec STB_TrUETYPE. Cela cache les glyphes tels qu'ils sont dessinés permettant un rendu de texte rapide. Il fournit également quelques façons faciles de rendre une chaîne à la texture pour un rendu de texte encore plus rapide.
Nouveau (2022)! Peut prétendre dans des environnements multithread avec producerConsumerFrontend.h !
Nouveau (2021)! Peut également rendre dans BGFX avec bgfxFrontend.h !

Exemple de texte de http://www.columbia.edu/~fdc/utf8/index.html

Liam twigger - @snapperthetwig
fc.drawText(x, y, string)fc.renderTextToTexture(string, &widthOut, &heightOut)Sur un Intel i7-8750h:
L'exemple d'image prend 0,7 ms pour rendre (~ 1400 ips) si l'utilisation de la texture ( renderTextToTexture / renderTextToObject ). La vitesse sera le maximum que SDL2 vous permet de retourner une texture.
L'exemple d'image prend ~ 5 ms pour rendre (~ 200 ips) en cas de rendu directement ( drawText )
Pour le texte qui dure plus d'une trame, vous devez le mettre en cache avec renderTextToTexture ou renderTextToObject .
Texte formaté:
Frontend non-SDL:
Ceci est une bibliothèque d'en-tête uniquement - aucun système de construction requis
Dans n'importe quel en-tête:
# include " path/to/sdlStbFont/sdlStbFont.h "Dans un .cpp:
# define SDL_STB_FONT_IMPL
# include " path/to/sdlStbFont/sdlStbFont.h " Cette bibliothèque est dépendante de STB_TrUETYPE. Il inclura automatiquement cela. Si vous ne voulez pas qu'il soit inclus automatiquement, utilisez #define STB_TRUETYPE_INCLUDE_HANDLED , et gérez l'inclusion de STB_TrueType.h vous-même.
# 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.Vous pouvez également obtenir la largeur et la hauteur d'une chaîne rendue:
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
La première police chargée doit être avec "LoadFont".
Notez que toutes les polices doivent être chargées avant que toutes les fonctions de dessin ne soient appelées. Si un glyphe n'est pas trouvé dans la première police, la deuxième police sera recherchée, etc.
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 Astuce: drawText() Renvoie la coordonnée x de l'extrémité d'une chaîne dessinée ou un objet texte formaté.
Pour l'enveloppe de ligne, utilisez la fonction fc.breakString(stringIn, arrayOut, width) . Cela convertit une chaîne ou un objet texte formaté en un vecteur de chaînes ou d'objets texte formatés dont chacun est inférieur à la largeur. Cela essaiera de briser les nouvelles et les espaces blancs, puis il divisera les mots si ils sont plus longs que la largeur spécifiée. Ceci est sûr UTF8.
Gestion manuelle:
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 behaviourGestion automatique:
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 Première façon:
// 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); Deuxième façon (même effet, mais plus propre)
// 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(); Astuce: prt.draw() Renvoie la coordonnée X de la fin de l'objet.
Utilisez SDL_SetTextureColorMod avec une texture en cache. Ou utilisez sdl_stb_prerendered_text::drawWithColor .
Utilisez getCaretPos(text, relativeMouseX, relativeMouseY)
Notez que cela ne prend en charge que la recherche Carret dans les chaînes sans Newlines - si vous essayez cela avec une chaîne multiline, il peut renvoyer une valeur incorrecte.
La largeur d'onglet est gérée par la variable fc.tabWidth . Les caractères après un onglet s'aligneront sur l'emplacement de l'onglet suivant.
Vous pouvez définir fc.tabWidthInSpaces = X avant d'appeler fc.loadFont(...) pour définir automatiquement fc.tabWidth sur un multiple de la largeur d'espace. Par défaut, les polices sont définies sur 8 espaces de largeur. Les valeurs plus faibles sont meilleures pour les polices en monospace, les valeurs plus élevées sont meilleures pour les polices non monospatiales.
Cette bibliothèque se compose de deux parties - un backend de gestion des polices (classes nommées sttfont_* ) et un frontend de rendu SDL ( sdl_stb_* ).
Pour faire votre propre frontend de rendu, étendez les classes pertinentes sttfont_* . Voir l'implémentation SDL pour plus de détails. Ses ~ 200 lignes de code, tout ce que vous avez à faire est de retirer les trucs spécifiques aux SDL et de mettre vos trucs spécifiques à votre rendu. Dans votre application, incluez sttFont.h au lieu de sdlStbFont.h
Vous pouvez utiliser la 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 glypsSi vous êtes un rendu de logiciel dans SDL , cela n'est pas nécessaire et ralentira le démarrage. Si vous utilisez un frontend personnalisé qui utilise des atlas de texture (tels que BGFX), cela est recommandé.
Utilisez producerConsumerExample.h . Voir producerConsumerExample.cpp pour un exemple travaillé.
L'idée est que vous instanciez un objet producer_consumer_font_cache partagé entre vos threads producteur et consommateur. Cet objet a un membre qui pointe vers le frontend réel utilisé par le consommateur:
Initaling:
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 frontendsProduction:
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 consumerConsommant:
// <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,5Nettoyage:
// 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: Vous pouvez soumettre un pointeur brut avec votre texte.
mPcCahce.pushUserdata(void*); // producer
void* foo = mPcCahce.getUserdata(); // consumer
Si vous ne faites pas cela, un std::mutex est utilisé pour passer l'état d'un producteur à l'autre. Il n'y a qu'une seule fente dans le tampon transitoire. Vous devez avoir une mécanisim pour s'arrêter, par exemple, le producteur fonctionnant plus rapidement que le consommateur.
Si vous souhaitez utiliser une file d'attente, je recommande MoodyCamel :: ReaderWriterQueue. Vous pouvez l'activer avec producer_consumer_font_cache par:
#define SSF_CONCURRENT_QUEUE moodycamel::ReaderWriterQueue
Pollage pour les changements:
if (mPcCache.receiveFromProducer()) { // wraps moodycamel::ReaderWriterQueue::try_dequeue()
// have dequeued!
}
Nettoyage (rincer la file d'attente)
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
}

Créez d'abord un sttfont_formatted_text . L'exemple ci-dessus a été créé avec:
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 " Vous pouvez combiner des options de formatage avec l'opérateur << . Le formatage est réinitialisé une fois une chaîne insérée.
Ensuite, vous pouvez rendre en utilisant les mêmes fonctions utilisées pour les chaînes simples ( drawText(x, y, formattedText) , etc.). Cela inclut le rendu à la texture avec renderTextToObject . Tout ce que vous pouvez faire avec une chaîne simple que vous devriez pouvoir faire avec la fonction homosexuelle ( getTextSize , getNumRows , getCaretPos , etc.)
Pour que les variantes audacieuses / italiques fonctionnent, vous devez charger une variante audacieuse / italique de la police. Pour le gras + italique, vous devez charger une variante audacieuse + italique de la police! Les soulignements et les strikethroughs sont générés automatiquement par cette bibliothèque.
Pour charger une variante, utilisez addFormatFont ou addFormatFontManaged après le chargement d'une police de base. Cela devrait également être fait pour les polices de secours (pour le support multilingue) si vous vous souciez que ces polices soient disponibles en gras / italique.
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);Si vous demandez une chaîne audacieuse ou italique et qu'il n'y a pas de variante audacieuse ou italique disponible, la variante régulière sera utilisée. Si vous demandez une chaîne en italique en gras + et qu'il n'y a qu'une seule charge (mais pas la combinaison), la dernière variante chargée sera utilisée - donc si vous demandez en gras + italique et que vous avez chargé audacieux alors l'italique (mais pas en gras + italique), alors l'italique sera rendue.
La bibliothèque tracera les variantes de soulignement et de strikethrough elle-même, vous n'avez pas besoin de les fournir.
Inclure bgfxFrontend.h et créez une instance de bgfx_stb_font_cache .
Voir bgfxExample.cpp pour voir comment utiliser ce frontend.
Quelques notes:
sttfont_uint32_t_range::populateRangesLatin() et similaire à remplir l'atlas avant le rendu. Si un atlas est rempli de glyphes, le frontend créera une nouvelle page Atlas. Assurez-vous de créer / modifier les fichiers .lzz , pas les fichiers .h générés. Les outils + scripts pour créer les fichiers d'en-tête à partir des sources .lzz sont fournis.
Grâce aux contributeurs aux projets SDL et STB!
Domaine public
STB_TrueType est un domaine public
Les polices noto sont (c) Google et sont publiées sous la licence SIL Open de la police, version 1.1. Voir https://www.google.com/get/noto/