Uma biblioteca somente para o cabeçalho para renderizar texto em puro SDL2 com stb_truetype. Esses armazenam em cache os glifos à medida que são desenhados, permitindo a renderização rápida de texto. Ele também fornece algumas maneiras fáceis de renderizar uma string para textura para uma renderização de texto ainda mais rápida.
NOVO (2022)! Pode prender em ambientes multithreads com producerConsumerFrontend.h !
Novo (2021)! Também pode renderizar no BGFX com bgfxFrontend.h !

Exemplo de texto em http://www.columbia.edu/~fdc/utf8/index.html

Liam Twigger - @snapperthetwig
fc.drawText(x, y, string)fc.renderTextToTexture(string, &widthOut, &heightOut)Em um Intel i7-8750H:
Exemplo de imagem leva 0,7ms para renderizar (~ 1400 fps) se estiver usando textura ( renderTextToTexture / renderTextToObject ). A velocidade será o máximo em que o SDL2 permite que você gire uma textura.
Exemplo de imagem leva ~ 5ms para renderizar (~ 200 fps) se renderizar diretamente ( drawText )
Para o texto que dura mais de um quadro, você deve cache -o com renderTextToTexture ou renderTextToObject .
Texto formatado:
Frontend não-SDL:
Esta é uma biblioteca apenas de cabeçalho - nenhum sistema de construção é necessário
Em qualquer cabeçalho:
# include " path/to/sdlStbFont/sdlStbFont.h "Em um .cpp:
# define SDL_STB_FONT_IMPL
# include " path/to/sdlStbFont/sdlStbFont.h " Esta biblioteca tem uma dependência de stb_truetype. Incluirá automaticamente isso. Se você não deseja que ele seja incluído automaticamente, use #define STB_TRUETYPE_INCLUDE_HANDLED e manuseie a inclusão de 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.Você também pode obter a largura e a altura de uma corda 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
A primeira fonte carregada deve estar com "LoadFont".
Observe que todas as fontes precisam ser carregadas antes que quaisquer funções de desenho sejam chamadas. Se um glifo não for encontrado na primeira fonte, a segunda fonte será pesquisada, 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 Dica: drawText() Retorna a coordenada X da extremidade de uma string desenhada ou objeto de texto formatado.
Para embalagem de linhas, use a função fc.breakString(stringIn, arrayOut, width) . Isso converte uma string ou um objeto de texto formatado em um vetor de strings ou objetos de texto formatados, cada um dos quais é menor que a largura. Isso tentará quebrar as linhas de novo e o espaço em branco primeiro, depois dividirá as palavras se forem mais longas que a largura especificada. Isso é UTF8 seguro.
Gerenciamento 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 behaviourGerenciamento automático:
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 Primeira maneira:
// 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 maneira (mesmo efeito, mas mais limpo)
// 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(); Dica: prt.draw() retorna a coordenada X da extremidade do objeto.
Use SDL_SetTextureColorMod com uma textura em cache. Ou use sdl_stb_prerendered_text::drawWithColor .
Use getCaretPos(text, relativeMouseX, relativeMouseY)
Observe que isso atualmente suporta apenas uma pesquisa Carret em strings sem linhas de novo - se você tentar isso com uma string multilina, poderá retornar um valor incorreto.
A largura da guia é tratada pela variável fc.tabWidth . Os caracteres após uma guia se alinharão ao próximo local da guia.
Você pode definir fc.tabWidthInSpaces = X antes de ligar para fc.loadFont(...) para definir automaticamente fc.tabWidth como um múltiplo da largura do espaço. Por padrão, as fontes são definidas para 8 espaços em largura. Os valores mais baixos são melhores para fontes monoespaciais, os valores mais altos são melhores para fontes não monoespaciais.
Esta biblioteca consiste em duas partes - um back -end de manuseio de fontes (classes denominadas sttfont_* ) e um front -end de renderização SDL ( sdl_stb_* ).
Para fazer com que sua própria fronteira de renderização estenda as classes sttfont_* relevantes. Consulte a implementação do SDL para obter detalhes. Suas ~ 200 linhas de código, tudo o que você precisa fazer é retirar as coisas específicas do SDL e colocar em seu renderizador. No seu aplicativo, inclua sttFont.h em vez de sdlStbFont.h
Você pode usar a 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 glypsSe você é uma renderização de software no SDL , isso não é necessário e apenas desacelerará a inicialização. Se você estiver usando um frontend personalizado que use atlas de textura (como o BGFX), isso é recomendado.
Use producerConsumerExample.h . Consulte producerConsumerExample.cpp para um exemplo trabalhado.
A idéia é que você instancie um objeto producer_consumer_font_cache que é compartilhado entre os threads do seu produtor e do consumidor. Este objeto tem um membro que aponta para o front -end real usado pelo consumidor:
INITALIZAÇÃO:
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 frontendsProduzindo:
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 consumerConsumindo:
// <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,5Limpar:
// 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: você pode enviar um ponteiro bruto junto com seu texto.
mPcCahce.pushUserdata(void*); // producer
void* foo = mPcCahce.getUserdata(); // consumer
Se você não fizer isso, um std::mutex é usado para passar o estado de produtor para consumidor. Existe apenas um slot no buffer transitório. Você deve ter algum mecanismo para parar, por exemplo, o produtor funcionando mais rápido que o consumidor.
Se você deseja usar uma fila, recomendo o Moodycamel :: ReaderWriterQueue. Você pode habilitá -lo com producer_consumer_font_cache por:
#define SSF_CONCURRENT_QUEUE moodycamel::ReaderWriterQueue
Pesquisa para mudanças:
if (mPcCache.receiveFromProducer()) { // wraps moodycamel::ReaderWriterQueue::try_dequeue()
// have dequeued!
}
Limpeza (descarregue a fila)
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
}

Primeiro, crie um sttfont_formatted_text . O exemplo acima foi criado com:
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 " Você pode combinar opções de formatação com o operador << . A formatação é redefinida após a inserção de uma string.
Em seguida, você pode renderizar usando as mesmas funções usadas para strings simples ( drawText(x, y, formattedText) , etc.). Isso inclui renderizar a textura com renderTextToObject . Tudo o que você pode fazer com uma string simples que você deve fazer com a mesma função com nome ( getTextSize , getNumRows , getCaretPos , etc)
Para que as variantes em negrito/itálico funcionem, você deve carregar uma variante em negrito/itálico da fonte. Para negrito+itálico, você deve carregar uma variante em negrito+em itálico da fonte! Os sublinhados e as greves são gerados automaticamente por esta biblioteca.
Para carregar uma variante, use addFormatFont ou addFormatFontManaged após o carregamento de uma fonte base. Isso também deve ser feito para fontes de fallback (para suporte multilíngue) se você se preocupa com as fontes estarem disponíveis em negrito/itálico.
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);Se você solicitar uma corda em negrito ou em itálico e não houver uma variante ousada ou itálica disponível, a variante regular será usada. Se você solicitar uma corda em negrito+em itálico e houver apenas uma carregada (mas não a combinação), a última variante carregada será usada - por isso, se você solicitar negrito+itálico e carregar em negrito, então em itálico (mas não em negrito+itálico), será renderizado em itálico.
A biblioteca atrairá o sublinhamento e atingirá as próprias variantes, você não precisa fornecê -las.
Inclua bgfxFrontend.h e crie uma instância de bgfx_stb_font_cache .
Consulte bgfxExample.cpp para ver como usar este front -end.
Algumas notas:
sttfont_uint32_t_range::populateRangesLatin() e semelhante a preencher o Atlas antes de renderizar. Se um atlas estiver preenchido com glifos, o front -end criará uma nova página do Atlas. Certifique -se de criar/editar os arquivos .lzz , não os arquivos .h gerados. Os scripts do Ferramentas + para criar os arquivos de cabeçalho das fontes .lzz são fornecidas.
Graças aos contribuidores dos projetos SDL e STB!
Domínio público
stb_truetype é domínio público
Noto Fontes são (c) Google e são lançadas sob a licença de fonte SIL Open, versão 1.1. Veja https://www.google.com/get/noto/