7#include "GuiTextRenderer.h"
19uint32_t GuiTextRenderer::metricsGeneration = 0;
34 extern const unsigned char tt_driver_class[];
35 extern const unsigned char sfnt_module_class[];
36 extern const unsigned char ft_smooth_renderer_class[];
40 void* ftAlloc(FT_Memory,
long size) {
return std::malloc(size); }
41 void ftFree(FT_Memory,
void* block) { std::free(block); }
42 void* ftRealloc(FT_Memory,
long,
long newSize,
void* block) {
return std::realloc(block, newSize); }
45 FT_MemoryRec_ ftMemory = {
nullptr, ftAlloc, ftFree, ftRealloc };
47 FT_Library createFreeTypeLibrary()
49 FT_Library library =
nullptr;
50 if (FT_New_Library(&ftMemory, &library) != 0)
53 if (FT_Add_Module(library,
reinterpret_cast<const FT_Module_Class*
>(tt_driver_class)) != 0 ||
54 FT_Add_Module(library,
reinterpret_cast<const FT_Module_Class*
>(sfnt_module_class)) != 0 ||
55 FT_Add_Module(library,
reinterpret_cast<const FT_Module_Class*
>(ft_smooth_renderer_class)) != 0)
57 FT_Done_Library(library);
65 inline int16_t ToDesignPixels(
int v,
float uiScale)
67 return uiScale == 1.0f ? (int16_t)v : (int16_t)std::lround(v / uiScale);
72 : currentPixelSize(0), renderer(glyphRenderer), current(nullptr), uiScale(uiScale_)
75 ftLibrary = createFreeTypeLibrary();
76 FT_New_Memory_Face(ftLibrary, (FT_Byte*)fontBuffer, bufferSize, 0, &ftFace);
77 ftKerningEnabled = FT_HAS_KERNING(ftFace);
80GuiTextRenderer::~GuiTextRenderer() {
82 if (ftFace) FT_Done_Face(ftFace);
83 if (ftLibrary) FT_Done_Library(ftLibrary);
86void GuiTextRenderer::unloadFont() {
87 for (
auto& sizePair : fontData) {
88 for (
auto& charPair : sizePair.second.charMap) {
89 if (charPair.second.texture) {
90 renderer->destroyTexture(charPair.second.texture);
91 charPair.second.texture =
nullptr;
101 if (currentPixelSize != pixelSize) {
102 currentPixelSize = pixelSize;
107 FT_Set_Pixel_Sizes(ftFace, 0, (FT_UInt)std::lround(currentPixelSize * uiScale));
113GlyphData* GuiTextRenderer::cacheGlyphData(
wchar_t charCode, int16_t pixelSize) {
114 ftData* data = &fontData[pixelSize];
117 if (!data->metricsReady) {
118 data->metricsReady =
true;
120 data->align.ascender = ToDesignPixels(ftFace->size->metrics.ascender >> 6, uiScale);
121 data->align.descender = ToDesignPixels(ftFace->size->metrics.descender >> 6, uiScale);
126 const bool inFastRange = (uint32_t)charCode < 256;
128 auto it = data->charMap.find(charCode);
129 if (it != data->charMap.end()) {
130 if (inFastRange) data->fast[charCode] = &it->second;
134 FT_UInt gIndex = FT_Get_Char_Index(ftFace, (FT_ULong)charCode);
135 bool loaded = gIndex != 0 && (FT_Load_Glyph(ftFace, gIndex, FT_LOAD_DEFAULT | FT_LOAD_RENDER | FT_LOAD_NO_BITMAP) == 0 ||
136 FT_Load_Glyph(ftFace, gIndex, FT_LOAD_DEFAULT | FT_LOAD_RENDER) == 0);
138 if (ftFace->glyph->format == FT_GLYPH_FORMAT_BITMAP && ftFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
139 FT_Bitmap* glyphBitmap = &ftFace->glyph->bitmap;
141 GlyphData& charData = data->charMap[charCode];
142 charData.
renderOffsetX = ToDesignPixels(ftFace->glyph->bitmap_left, uiScale);
143 charData.
glyphAdvanceX = (uint16_t)ToDesignPixels(ftFace->glyph->advance.x >> 6, uiScale);
144 charData.
glyphAdvanceY = (uint16_t)ToDesignPixels(ftFace->glyph->advance.y >> 6, uiScale);
153 charData.
drawWidth = (uint16_t)ToDesignPixels(glyphBitmap->width, uiScale);
154 charData.
drawHeight = (uint16_t)ToDesignPixels(glyphBitmap->rows, uiScale);
156 charData.
renderOffsetY = ToDesignPixels(ftFace->glyph->bitmap_top, uiScale);
158 charData.
renderOffsetMin = ToDesignPixels((
int)glyphBitmap->rows - ftFace->glyph->bitmap_top, uiScale);
163 renderer->loadTextureData(charData.
texture, glyphBitmap);
166 if (inFastRange) data->fast[charCode] = &charData;
170 if (inFastRange) data->fast[charCode] = &missingGlyph;
174GlyphData* GuiTextRenderer::glyph(
wchar_t charCode) {
175 if (!current) current = &fontData[currentPixelSize];
177 if ((uint32_t)charCode < 256) {
178 GlyphData* g = current->fast[charCode];
179 if (g)
return g == &missingGlyph ? nullptr : g;
181 return cacheGlyphData(charCode, currentPixelSize);
186int16_t GuiTextRenderer::getStyleOffsetWidth(uint16_t width, uint32_t format) {
187 if (format & GUI_TEXT_JUSTIFY_LEFT)
return 0;
188 else if (format & GUI_TEXT_JUSTIFY_CENTER)
return -(width >> 1);
189 else if (format & GUI_TEXT_JUSTIFY_RIGHT)
return -width;
193int16_t GuiTextRenderer::getStyleOffsetHeight(
FontOffset* offset, uint32_t format) {
194 switch (format & GUI_TEXT_ALIGN_MASK) {
195 case GUI_TEXT_ALIGN_TOP:
return offset->
ascender;
196 case GUI_TEXT_ALIGN_BOTTOM:
return offset->
descender;
197 case GUI_TEXT_ALIGN_BASELINE:
return 0;
198 case GUI_TEXT_ALIGN_GLYPH_TOP:
return offset->
max;
199 case GUI_TEXT_ALIGN_GLYPH_MIDDLE:
return (offset->
max + offset->
min + 1) >> 1;
200 case GUI_TEXT_ALIGN_GLYPH_BOTTOM:
return offset->
min;
201 case GUI_TEXT_ALIGN_MIDDLE:
207 int16_t strMax = 0, strMin = 9999;
219 offset->
ascender = ToDesignPixels(ftFace->size->metrics.ascender >> 6, uiScale);
220 offset->
descender = ToDesignPixels(ftFace->size->metrics.descender >> 6, uiScale);
221 offset->
max = strMax;
222 offset->
min = strMin;
228 uint16_t strWidth = 0;
232 for (
int i = 0; text[i]; ++i) {
235 if (ftKerningEnabled && prev) {
236 FT_Get_Kerning(ftFace, prev->
glyphIndex, glyphData->
glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
237 strWidth += ToDesignPixels(pairDelta.x >> 6, uiScale);
247 uint16_t strWidth = 0;
252 for (
int i = 0; text[i]; ++i) {
255 if (ftKerningEnabled && prev) {
256 FT_Get_Kerning(ftFace, prev->
glyphIndex, glyphData->
glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
257 strWidth += ToDesignPixels(pairDelta.x >> 6, uiScale);
262 if ((
int)strWidth <= maxWidth) best = i + 1;
268 uint32_t dynlen = textlen > pos ? textlen - pos : 0;
269 const bool spaced = dynlen + 2 < textlen;
270 if (spaced) dynlen += 2;
274 const uint32_t extra = textlen > dynlen + 1 ? textlen - 1 - dynlen : 0;
275 const uint32_t total = dynlen + extra;
277 lineScratch.resize(total + 1);
278 wchar_t* line = lineScratch.data();
280 for (uint32_t i = pos; i < textlen; ++i) line[n++] = text[i];
281 if (spaced) { line[n++] = L
' '; line[n++] = L
' '; }
282 for (uint32_t i = 0; i < extra; ++i) line[n++] = text[i];
286 widthScratch.resize(total + 1);
287 uint16_t* w = widthScratch.data();
288 uint16_t strWidth = 0;
292 for (uint32_t i = 0; i < total; ++i) {
295 if (ftKerningEnabled && prev) {
296 FT_Get_Kerning(ftFace, prev->
glyphIndex, glyphData->
glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
297 strWidth += ToDesignPixels(pairDelta.x >> 6, uiScale);
306 if ((
int)w[dynlen] > maxWidth) {
309 while (len > 0 && (
int)w[len] > maxWidth) --len;
313 while ((
int)w[len] < maxWidth && len + 1 < textlen) ++len;
315 const uint32_t drop = (int)w[len] > maxWidth ? 2 : 1;
316 len = len > drop ? len - drop : 0;
318 for (uint32_t i = 0; i < len; ++i) out[i] = line[i];
325 return offset.
max - offset.
min;
333 uint16_t x_pos = x, printed = 0;
334 int16_t x_offset = 0, y_offset = 0;
341 if ((renderFlags & GUI_TEXT_JUSTIFY_MASK) && !(renderFlags & GUI_TEXT_JUSTIFY_LEFT)) {
342 x_offset = getStyleOffsetWidth(
getWidth(text), renderFlags);
344 if (renderFlags & GUI_TEXT_ALIGN_MASK) {
345 const uint32_t vertical = renderFlags & GUI_TEXT_ALIGN_MASK;
346 if (vertical == GUI_TEXT_ALIGN_GLYPH_TOP || vertical == GUI_TEXT_ALIGN_GLYPH_MIDDLE || vertical == GUI_TEXT_ALIGN_GLYPH_BOTTOM) {
349 offset.
ascender = ToDesignPixels(ftFace->size->metrics.ascender >> 6, uiScale);
350 offset.
descender = ToDesignPixels(ftFace->size->metrics.descender >> 6, uiScale);
354 y_offset = getStyleOffsetHeight(&offset, renderFlags);
358 renderer->beginText();
359 for (
int i = 0; text[i]; ++i) {
364 if (ftKerningEnabled && prev) {
365 FT_Get_Kerning(ftFace, prev->
glyphIndex, glyphData->
glyphIndex, FT_KERNING_DEFAULT, &pairDelta);
366 x_pos += ToDesignPixels(pairDelta.x >> 6, uiScale);
370 int16_t screenX = x_pos + glyphData->
renderOffsetX + x_offset;
382 if (renderFlags & GUI_TEXT_STYLE_MASK) {
384 drawTextFeature(x + x_offset, y + y_offset,
getWidth(text), &offset, renderFlags, color);
390void GuiTextRenderer::drawTextFeature(int16_t x, int16_t y, uint16_t width,
FontOffset* offsetData, uint32_t format,
const PixelColor& color) {
391 uint16_t featureHeight = currentPixelSize >> 4 > 0 ? currentPixelSize >> 4 : 1;
393 if (format & GUI_TEXT_STYLE_UNDERLINE) {
394 renderer->
drawFeature(x, y + 1, width, featureHeight, color);
396 if (format & GUI_TEXT_STYLE_STRIKE) {
397 renderer->drawFeature(x, y - (offsetData->
max >> 1), width, featureHeight, color);
404 if (!strChar)
return nullptr;
406 const unsigned char* s =
reinterpret_cast<const unsigned char*
>(strChar);
407 wchar_t* strWChar =
new wchar_t[strlen(strChar) + 1];
418 else if (cp >= 0xC2 && cp <= 0xDF) { extra = 1; cp &= 0x1F; minCp = 0x80; }
419 else if (cp >= 0xE0 && cp <= 0xEF) { extra = 2; cp &= 0x0F; minCp = 0x800; }
420 else if (cp >= 0xF0 && cp <= 0xF4) { extra = 3; cp &= 0x07; minCp = 0x10000; }
426 while (n < extra && (s[1 + n] & 0xC0) == 0x80) {
427 v = (v << 6) | (s[1 + n] & 0x3F);
431 bool valid = (n == extra) && v >= minCp && v <= 0x10FFFF && !(v >= 0xD800 && v <= 0xDFFF);
432 if (valid && v > (uint32_t)WCHAR_MAX) valid =
false;
434 strWChar[out++] = (wchar_t)v;
441 strWChar[out++] = (wchar_t)cp;
445 strWChar[out] = L
'\0';
451 uint16_t result =
drawText(x, y, wText, color, renderFlags);
473extern "C" int BrotliDecoderDecompress(
size_t encodedSize,
const uint8_t* encodedBuffer,
size_t* decodedSize, uint8_t* decodedBuffer)
Draws glyph textures and solid rectangles on behalf of GuiTextRenderer.
virtual void drawFeature(int16_t screenX, int16_t screenY, uint16_t width, uint16_t height, const PixelColor &color)=0
Draws a solid-color rectangle used for text decoration (underline/strike-through).
FreeType2-based glyph shaping/caching.
void setPixelSize(int16_t pixelSize)
Selects the pixel size subsequent drawText()/getWidth()/getHeight() calls use.
uint16_t getHeight(const wchar_t *text)
static wchar_t * charToWideChar(const char *p)
uint16_t drawText(int16_t x, int16_t y, const wchar_t *text, PixelColor color=black, uint32_t renderFlags=0)
Draws text at (x, y) using the current pixel size.
int fitCount(const wchar_t *text, int maxWidth)
How many leading characters of text fit in maxWidth (the largest n for which the first n characters m...
void scrollWindow(const wchar_t *text, uint32_t textlen, uint32_t pos, int maxWidth, wchar_t *out)
The part of a horizontally scrolling line that is shown at one scroll position: the text from pos on,...
void getOffset(const wchar_t *text, FontOffset *offset)
uint16_t getWidth(const wchar_t *text)
GuiTextRenderer(const uint8_t *fontBuffer, FT_Long bufferSize, GlyphRenderer *glyphRenderer, float uiScale=1.0f)
Per-pixel-size font metrics used for text-block alignment/positioning.
int16_t max
Height above the baseline of the tallest glyph in the measured string (see GuiTextRenderer::getOffset...
int16_t min
Depth below the baseline of the lowest glyph in the measured string (see GuiTextRenderer::getOffset()...
int16_t descender
Font descender for the current size (negative: distance below the baseline), in design pixels.
int16_t ascender
Font ascender for the current size (distance above the baseline), in design pixels.
Cached per-glyph metrics and rasterized texture, keyed by (pixel size, char code) in GuiTextRenderer:...
int16_t renderOffsetX
Horizontal offset from the pen position to the bitmap's left edge.
uint16_t glyphAdvanceX
Horizontal pen advance after this glyph.
uint16_t textureWidth
Pixel dims of the rasterized bitmap/texture, at uiScale - always >= drawWidth/drawHeight,...
uint16_t drawWidth
Design-pixel dims to actually draw the quad at (textureWidth/Height divided back down by uiScale) - k...
int16_t renderOffsetMax
Height of the glyph bitmap above the baseline (feeds FontOffset::max)
uint32_t glyphIndex
FreeType glyph index.
int16_t renderOffsetY
Height of the bitmap's top edge above the baseline.
int16_t renderOffsetMin
Depth of the glyph bitmap below the baseline (feeds FontOffset::min)
uint16_t glyphAdvanceY
Vertical pen advance after this glyph.
uint16_t drawHeight
Height counterpart of drawWidth.
uint16_t textureHeight
Height counterpart of textureWidth.
void * texture
Abstracted texture pointer.
8-bit-per-channel RGBA color used by the video interfaces.