Una biblioteca de solo encabezado para representar texto en SDL2 puro con stb_truetype. Esto almacena glifos a medida que se dibujan permitiendo la representación de texto rápido. También proporciona un par de formas fáciles de representar una cadena a la textura para una representación de texto aún más rápida.
¡Nuevo (2022)! ¿Puede Prerender en entornos multiproceso con producerConsumerFrontend.h !
¡Nuevo (2021)! También puede renderizar en BGFX con bgfxFrontend.h !

Texto de muestra de http://www.columbia.edu/~fdc/utf8/index.html

Liam Twigger - @snapperthetwig
fc.drawText(x, y, string)fc.renderTextToTexture(string, &widthOut, &heightOut)En un Intel i7-8750h:
La imagen de ejemplo toma 0.7ms en renderizar (~ 1400 fps) si se usa textura ( renderTextToTexture / renderTextToObject ). La velocidad será la máxima que SDL2 le permite voltear una textura.
La imagen de ejemplo toma ~ 5ms en renderizar (~ 200 fps) si se reproduce directamente ( drawText )
Para el texto que dura más de un cuadro, debe almacenarlo en caché con renderTextToTexture o renderTextToObject .
Texto formateado:
Frontends no SDL:
Esta es una biblioteca de solo encabezado: no se requiere un sistema de compilación
En cualquier encabezado:
# include " path/to/sdlStbFont/sdlStbFont.h "En uno .cpp:
# define SDL_STB_FONT_IMPL
# include " path/to/sdlStbFont/sdlStbFont.h " Esta biblioteca tiene una dependencia de stb_truetype. Se incluirá automáticamente esto. Si no desea que lo incluya automáticamente, use #define STB_TRUETYPE_INCLUDE_HANDLED y maneje el incluido de stb_truetype.h usted mismo.
# 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.También puede obtener el ancho y la altura de una cadena renderizada:
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 primera fuente cargada debe estar con "LoadFont".
Tenga en cuenta que todas las fuentes deben cargarse antes de llamar a cualquier función de dibujo. Si no se encuentra un glifo en la primera fuente, se buscará la segunda fuente, 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 Consejo: drawText() Devuelve la coordenada x del extremo de una cadena dibujada o objeto de texto formateado.
Para envoltura de línea, use la función fc.breakString(stringIn, arrayOut, width) . Esto convierte una cadena o un objeto de texto formateado en un vector de cadenas o objetos de texto formateados, cada uno de los cuales es inferior al ancho. Esto intentará romperse primero en nuevas líneas y espacios en blanco, entonces dividirá las palabras si son más largos que el ancho especificado. Esto es seguro UTF8.
Gestión manual:
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 behaviourGestión automática:
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 Primera forma:
// 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); Segunda vía (mismo efecto, pero más limpio)
// 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(); Consejo: prt.draw() Devuelve la coordenada x del final del objeto.
Use SDL_SetTextureColorMod con una textura en caché. O use sdl_stb_prerendered_text::drawWithColor .
Use getCaretPos(text, relativeMouseX, relativeMouseY)
Tenga en cuenta que esto solo es compatible con la búsqueda correcta en cadenas sin nuevas líneas: si intenta esto con una cadena multilínea, entonces puede devolver un valor incorrecto.
El ancho de la pestaña se maneja por la variable fc.tabWidth . Los caracteres después de una pestaña se alinearán con la siguiente ubicación de pestaña.
Puede establecer fc.tabWidthInSpaces = X antes de llamar fc.loadFont(...) para establecer automáticamente fc.tabWidth en algún múltiplo del ancho del espacio. Por defecto, las fuentes se establecen en 8 espacios en ancho. Los valores más bajos son mejores para las fuentes monoespaciales, los valores más altos son mejores para las fuentes no monoespaciales.
Esta biblioteca consta de dos partes: un backend de manejo de fuentes (clases llamadas sttfont_* ) y un frontend de representación SDL ( sdl_stb_* ).
Para que su propia representación frontend extienda las clases Relevent sttfont_* . Consulte la implementación SDL para más detalles. Sus ~ 200 líneas de código, todo lo que tiene que hacer es eliminar las cosas específicas de SDL y poner en su renderizador. En su aplicación, incluya sttFont.h en lugar de sdlStbFont.h
Puede usar 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 está en software en SDL , esto no es necesario , y simplemente ralentizará la inicio. Si está utilizando un frontend personalizado que usa atlas de textura (como BGFX), esto se recomienda esto.
Use producerConsumerExample.h . Consulte producerConsumerExample.cpp para un ejemplo trabajado.
La idea es que instanciese un objeto producer_consumer_font_cache que se comparte entre su productor y los hilos de consumo. Este objeto tiene un miembro que apunta al interfaz real utilizado por el consumidor:
Initalización:
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 frontendsProductor:
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 consumerConsumidor:
// <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,5Limpieza:
// 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: puede enviar un puntero RAW junto con su texto.
mPcCahce.pushUserdata(void*); // producer
void* foo = mPcCahce.getUserdata(); // consumer
Si no hace esto, se usa un std::mutex para pasar el estado de productor a consumidor. Solo hay una ranura en el búfer transitorio. Debe tener algo de mecanismos para detener, por ejemplo, el productor que funciona más rápido que el consumidor.
Si desea usar una cola, le recomiendo MoodyCamel :: ReaderWriterqueue. Puede habilitarlo con producer_consumer_font_cache por:
#define SSF_CONCURRENT_QUEUE moodycamel::ReaderWriterQueue
Encuestas para cambios:
if (mPcCache.receiveFromProducer()) { // wraps moodycamel::ReaderWriterQueue::try_dequeue()
// have dequeued!
}
Limpieza (enjuague la cola)
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
}

Primero cree un sttfont_formatted_text . El ejemplo anterior se creó con:
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 " Puede combinar opciones de formato con el operador << . El formato se restablece después de insertar una cadena.
Luego puede renderizar el uso de las mismas funciones utilizadas para cadenas simples ( drawText(x, y, formattedText) , etc.). Eso incluye representar a la textura con renderTextToObject . Todo lo que puede hacer con una cadena simple que debe poder hacer con la misma función de nombre ( getTextSize , getNumRows , getCaretPos , etc.)
Para que las variantes en negrita/en cursiva funcionen, debe cargar una variante en negrita/cursiva de la fuente. ¡Para negrita+cursiva debes cargar una variante en negrita+en cursiva de la fuente! Los subrayados y los strikethethroughs son generados automáticamente por esta biblioteca.
Para cargar una variante, use addFormatFont o addFormatFontManaged después de cargar una fuente base. Esto también debe hacerse para las fuentes de respaldo (para el soporte multilingüe) si le importa que esas fuentes estén disponibles en negrita/cursiva.
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 solicita una cadena en negrita o en cursiva y no se utilizará una variante en negrita o en cursiva, se utilizará la variante regular. Si solicita una cadena en negrita+cursiva y solo hay una cargada (pero no la combinación) se usará la última variante cargada, por lo que si solicita negrita+cursiva y ha cargado en negrita, entonces cursiva (pero no en negrita+cursiva), entonces se representará en cursiva.
La biblioteca dibujará las variantes subrayadas y de Strikethrough, no necesita proporcionarlas.
Incluya bgfxFrontend.h y cree una instancia de bgfx_stb_font_cache .
Vea bgfxExample.cpp para ver cómo usar este frontend.
Algunas notas:
sttfont_uint32_t_range::populateRangesLatin() y similar para llenar el atlas antes de representar. Si un Atlas está lleno de glifos, entonces el frontend creará una nueva página de Atlas. Asegúrese de crear/editar los archivos .lzz , no los archivos .h generados. Se proporcionan las herramientas + scripts para crear los archivos de encabezado de las fuentes .lzz .
¡Gracias a los contribuyentes a los proyectos SDL y STB!
Dominio público
stb_truetype es dominio público
Las fuentes de notas son (c) Google y se lanzan bajo la licencia SIL Open Font, versión 1.1. Ver https://www.google.com/get/noto/