diff options
Diffstat (limited to 'src/ui/text.c')
-rw-r--r-- | src/ui/text.c | 166 |
1 files changed, 117 insertions, 49 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 889aa2e4..ffe08fca 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | 32 | ||
33 | #include <the_Foundation/array.h> | 33 | #include <the_Foundation/array.h> |
34 | #include <the_Foundation/file.h> | 34 | #include <the_Foundation/file.h> |
35 | #include <the_Foundation/fileinfo.h> | ||
35 | #include <the_Foundation/hash.h> | 36 | #include <the_Foundation/hash.h> |
36 | #include <the_Foundation/math.h> | 37 | #include <the_Foundation/math.h> |
37 | #include <the_Foundation/stringlist.h> | 38 | #include <the_Foundation/stringlist.h> |
@@ -124,10 +125,6 @@ struct Impl_Font { | |||
124 | iBool isMonospaced; | 125 | iBool isMonospaced; |
125 | iBool manualKernOnly; | 126 | iBool manualKernOnly; |
126 | enum iFontSize sizeId; /* used to look up different fonts of matching size */ | 127 | enum iFontSize sizeId; /* used to look up different fonts of matching size */ |
127 | // enum iFontId | ||
128 | // enum iFontId japaneseFont; /* font to use for Japanese glyphs */ | ||
129 | // enum iFontId chineseFont; /* font to use for Simplified Chinese glyphs */ | ||
130 | // enum iFontId koreanFont; /* font to use for Korean glyphs */ | ||
131 | uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ | 128 | uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ |
132 | }; | 129 | }; |
133 | 130 | ||
@@ -155,13 +152,17 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale, | |||
155 | d->xScale *= floorf(advance) / advance; | 152 | d->xScale *= floorf(advance) / advance; |
156 | } | 153 | } |
157 | } | 154 | } |
158 | d->vertOffset = height * (1.0f - scale) / 2; | 155 | d->baseline = ascent * d->yScale; |
159 | d->baseline = ascent * d->yScale; | 156 | d->vertOffset = height * (1.0f - scale) / 2; |
160 | d->sizeId = sizeId; | 157 | /* Custom tweaks. */ |
161 | // d->symbolsFont = symbolsFont; | 158 | if (data == &fontNotoSansSymbolsRegular_Embedded || |
162 | // d->japaneseFont = regularJapanese_FontId; | 159 | data == &fontNotoSansSymbols2Regular_Embedded) { |
163 | // d->chineseFont = regularChinese_FontId; | 160 | d->vertOffset /= 2; |
164 | // d->koreanFont = regularKorean_FontId; | 161 | } |
162 | else if (data == &fontNotoEmojiRegular_Embedded) { | ||
163 | //d->vertOffset -= height / 30; | ||
164 | } | ||
165 | d->sizeId = sizeId; | ||
165 | memset(d->indexTable, 0xff, sizeof(d->indexTable)); | 166 | memset(d->indexTable, 0xff, sizeof(d->indexTable)); |
166 | } | 167 | } |
167 | 168 | ||
@@ -215,7 +216,8 @@ struct Impl_Text { | |||
215 | iRegExp * ansiEscape; | 216 | iRegExp * ansiEscape; |
216 | }; | 217 | }; |
217 | 218 | ||
218 | static iText text_; | 219 | static iText text_; |
220 | static iBlock *userFont_; | ||
219 | 221 | ||
220 | static void initFonts_Text_(iText *d) { | 222 | static void initFonts_Text_(iText *d) { |
221 | const float textSize = fontSize_UI * d->contentFontSize; | 223 | const float textSize = fontSize_UI * d->contentFontSize; |
@@ -321,27 +323,31 @@ static void initFonts_Text_(iText *d) { | |||
321 | { &fontIosevkaTermExtended_Embedded, smallMonoSize, 1.0f, contentMonoSmall_FontSize }, | 323 | { &fontIosevkaTermExtended_Embedded, smallMonoSize, 1.0f, contentMonoSmall_FontSize }, |
322 | { &fontIosevkaTermExtended_Embedded, monoSize, 1.0f, contentMono_FontSize }, | 324 | { &fontIosevkaTermExtended_Embedded, monoSize, 1.0f, contentMono_FontSize }, |
323 | /* extra content fonts */ | 325 | /* extra content fonts */ |
324 | { &fontSourceSans3Regular_Embedded, textSize, scaling, contentRegular_FontSize }, | 326 | { &fontSourceSans3Regular_Embedded, textSize, scaling, contentRegular_FontSize }, |
325 | { &fontIosevkaTermExtended_Embedded, textSize, 0.866f, contentRegular_FontSize }, | 327 | { &fontSourceSans3Regular_Embedded, textSize * 0.80f, scaling, contentRegular_FontSize }, |
326 | /* symbols and scripts */ | 328 | /* symbols and scripts */ |
327 | #define DEFINE_FONT_SET(data) \ | 329 | #define DEFINE_FONT_SET(data, glyphScale) \ |
328 | { &data, uiSize, 1.0f, uiNormal_FontSize }, \ | 330 | { (data), uiSize, glyphScale, uiNormal_FontSize }, \ |
329 | { &data, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, \ | 331 | { (data), uiSize * 1.125f, glyphScale, uiMedium_FontSize }, \ |
330 | { &data, uiSize * 1.333f, 1.0f, uiBig_FontSize }, \ | 332 | { (data), uiSize * 1.333f, glyphScale, uiBig_FontSize }, \ |
331 | { &data, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, \ | 333 | { (data), uiSize * 1.666f, glyphScale, uiLarge_FontSize }, \ |
332 | { &data, textSize, 1.0f, contentRegular_FontSize }, \ | 334 | { (data), textSize, glyphScale, contentRegular_FontSize }, \ |
333 | { &data, textSize * 1.200f, 1.0f, contentMedium_FontSize }, \ | 335 | { (data), textSize * 1.200f, glyphScale, contentMedium_FontSize }, \ |
334 | { &data, textSize * 1.333f, 1.0f, contentBig_FontSize }, \ | 336 | { (data), textSize * 1.333f, glyphScale, contentBig_FontSize }, \ |
335 | { &data, textSize * 1.666f, 1.0f, contentLarge_FontSize }, \ | 337 | { (data), textSize * 1.666f, glyphScale, contentLarge_FontSize }, \ |
336 | { &data, textSize * 2.000f, 1.0f, contentHuge_FontSize }, \ | 338 | { (data), textSize * 2.000f, glyphScale, contentHuge_FontSize }, \ |
337 | { &data, smallMonoSize, 1.0f, contentMonoSmall_FontSize }, \ | 339 | { (data), smallMonoSize, glyphScale, contentMonoSmall_FontSize }, \ |
338 | { &data, monoSize, 1.0f, contentMono_FontSize } | 340 | { (data), monoSize, glyphScale, contentMono_FontSize } |
339 | DEFINE_FONT_SET(fontSymbola_Embedded), | 341 | DEFINE_FONT_SET(userFont_ ? userFont_ : &fontIosevkaTermExtended_Embedded, 1.0f), |
340 | DEFINE_FONT_SET(fontNotoEmojiRegular_Embedded), | 342 | DEFINE_FONT_SET(&fontIosevkaTermExtended_Embedded, 0.866f), |
341 | DEFINE_FONT_SET(fontNotoSansJPRegular_Embedded), | 343 | DEFINE_FONT_SET(&fontNotoSansSymbolsRegular_Embedded, 1.45f), |
342 | DEFINE_FONT_SET(fontNotoSansSCRegular_Embedded), | 344 | DEFINE_FONT_SET(&fontNotoSansSymbols2Regular_Embedded, 1.45f), |
343 | DEFINE_FONT_SET(fontNanumGothicRegular_Embedded), /* TODO: should use Noto Sans here, too */ | 345 | DEFINE_FONT_SET(&fontSmolEmojiRegular_Embedded, 1.0f), |
344 | DEFINE_FONT_SET(fontNotoSansArabicUIRegular_Embedded), | 346 | DEFINE_FONT_SET(&fontNotoEmojiRegular_Embedded, 1.10f), |
347 | DEFINE_FONT_SET(&fontNotoSansJPRegular_Embedded, 1.0f), | ||
348 | DEFINE_FONT_SET(&fontNotoSansSCRegular_Embedded, 1.0f), | ||
349 | DEFINE_FONT_SET(&fontNanumGothicRegular_Embedded, 1.0f), /* TODO: should use Noto Sans here, too */ | ||
350 | DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f), | ||
345 | }; | 351 | }; |
346 | iForIndices(i, fontData) { | 352 | iForIndices(i, fontData) { |
347 | iFont *font = &d->fonts[i]; | 353 | iFont *font = &d->fonts[i]; |
@@ -401,8 +407,28 @@ static void deinitCache_Text_(iText *d) { | |||
401 | SDL_DestroyTexture(d->cache); | 407 | SDL_DestroyTexture(d->cache); |
402 | } | 408 | } |
403 | 409 | ||
410 | void loadUserFonts_Text(void) { | ||
411 | if (userFont_) { | ||
412 | delete_Block(userFont_); | ||
413 | userFont_ = NULL; | ||
414 | } | ||
415 | /* Load the system font. */ | ||
416 | const iPrefs *prefs = prefs_App(); | ||
417 | if (!isEmpty_String(&prefs->symbolFontPath)) { | ||
418 | iFile *f = new_File(&prefs->symbolFontPath); | ||
419 | if (open_File(f, readOnly_FileMode)) { | ||
420 | userFont_ = readAll_File(f); | ||
421 | } | ||
422 | else { | ||
423 | fprintf(stderr, "[Text] failed to open: %s\n", cstr_String(&prefs->symbolFontPath)); | ||
424 | } | ||
425 | iRelease(f); | ||
426 | } | ||
427 | } | ||
428 | |||
404 | void init_Text(SDL_Renderer *render) { | 429 | void init_Text(SDL_Renderer *render) { |
405 | iText *d = &text_; | 430 | iText *d = &text_; |
431 | loadUserFonts_Text(); | ||
406 | d->contentFont = nunito_TextFont; | 432 | d->contentFont = nunito_TextFont; |
407 | d->headingFont = nunito_TextFont; | 433 | d->headingFont = nunito_TextFont; |
408 | d->contentFontSize = contentScale_Text_; | 434 | d->contentFontSize = contentScale_Text_; |
@@ -542,14 +568,36 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
542 | } | 568 | } |
543 | 569 | ||
544 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | 570 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { |
571 | if (isVariationSelector_Char(ch)) { | ||
572 | return d; | ||
573 | } | ||
574 | /* Smol Emoji overrides all other fonts. */ | ||
575 | if (ch != 0x20) { | ||
576 | iFont *smol = font_Text_(smolEmoji_FontId + d->sizeId); | ||
577 | if (smol != d && (*glyphIndex = glyphIndex_Font_(smol, ch)) != 0) { | ||
578 | return smol; | ||
579 | } | ||
580 | } | ||
581 | /* Manual exceptions. */ { | ||
582 | if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) { | ||
583 | d = font_Text_(iosevka_FontId + d->sizeId); | ||
584 | *glyphIndex = glyphIndex_Font_(d, ch); | ||
585 | return d; | ||
586 | } | ||
587 | } | ||
545 | if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { | 588 | if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { |
546 | return d; | 589 | return d; |
547 | } | 590 | } |
548 | /* Not defined in current font, try Noto Emoji (for selected characters). */ | 591 | const int fallbacks[] = { |
549 | if ((ch >= 0x1f300 && ch < 0x1f600) || (ch >= 0x1f680 && ch <= 0x1f6c5)) { | 592 | notoEmoji_FontId, |
550 | iFont *emoji = font_Text_(emoji_FontId + d->sizeId); | 593 | symbols2_FontId, |
551 | if (emoji != d && (*glyphIndex = glyphIndex_Font_(emoji, ch)) != 0) { | 594 | symbols_FontId |
552 | return emoji; | 595 | }; |
596 | /* First fallback is Smol Emoji. */ | ||
597 | iForIndices(i, fallbacks) { | ||
598 | iFont *fallback = font_Text_(fallbacks[i] + d->sizeId); | ||
599 | if (fallback != d && (*glyphIndex = glyphIndex_Font_(fallback, ch)) != 0) { | ||
600 | return fallback; | ||
553 | } | 601 | } |
554 | } | 602 | } |
555 | /* Try Simplified Chinese. */ | 603 | /* Try Simplified Chinese. */ |
@@ -584,17 +632,25 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | |||
584 | /* White up arrow is used for the Shift key on macOS. Symbola's glyph is not a great | 632 | /* White up arrow is used for the Shift key on macOS. Symbola's glyph is not a great |
585 | match to the other text, so use the UI font instead. */ | 633 | match to the other text, so use the UI font instead. */ |
586 | if ((ch == 0x2318 || ch == 0x21e7) && d == font_Text_(regular_FontId)) { | 634 | if ((ch == 0x2318 || ch == 0x21e7) && d == font_Text_(regular_FontId)) { |
587 | *glyphIndex = glyphIndex_Font_(d = font_Text_(defaultContentSized_FontId), ch); | 635 | *glyphIndex = glyphIndex_Font_(d = font_Text_(defaultContentRegular_FontId), ch); |
588 | return d; | 636 | return d; |
589 | } | 637 | } |
590 | #endif | 638 | #endif |
591 | /* Fall back to Symbola for anything else. */ | 639 | /* User's symbols font. */ { |
592 | iFont *font = font_Text_(symbols_FontId + d->sizeId); | 640 | iFont *sys = font_Text_(userSymbols_FontId + d->sizeId); |
593 | *glyphIndex = glyphIndex_Font_(font, ch); | 641 | if (sys != d && (*glyphIndex = glyphIndex_Font_(sys, ch)) != 0) { |
594 | // if (!*glyphIndex) { | 642 | return sys; |
595 | // fprintf(stderr, "failed to find %08x (%lc)\n", ch, ch); fflush(stderr); | 643 | } |
596 | // } | 644 | } |
597 | return font; | 645 | /* Final fallback. */ |
646 | iFont *font = font_Text_(iosevka_FontId + d->sizeId); | ||
647 | if (d != font) { | ||
648 | *glyphIndex = glyphIndex_Font_(font, ch); | ||
649 | } | ||
650 | if (!*glyphIndex) { | ||
651 | fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); | ||
652 | } | ||
653 | return d; | ||
598 | } | 654 | } |
599 | 655 | ||
600 | static iGlyph *glyph_Font_(iFont *d, iChar ch) { | 656 | static iGlyph *glyph_Font_(iFont *d, iChar ch) { |
@@ -1190,7 +1246,7 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) { | |||
1190 | return init_I2(advance, lineHeight_Text(fontId)); | 1246 | return init_I2(advance, lineHeight_Text(fontId)); |
1191 | } | 1247 | } |
1192 | 1248 | ||
1193 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { | 1249 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { |
1194 | iText *d = &text_; | 1250 | iText *d = &text_; |
1195 | iFont *font = font_Text_(fontId); | 1251 | iFont *font = font_Text_(fontId); |
1196 | const iColor clr = get_Color(color & mask_ColorId); | 1252 | const iColor clr = get_Color(color & mask_ColorId); |
@@ -1201,11 +1257,16 @@ static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, i | |||
1201 | (color & fillBackground_ColorId ? fillBackground_RunMode : 0) | | 1257 | (color & fillBackground_ColorId ? fillBackground_RunMode : 0) | |
1202 | runFlagsFromId_(fontId), | 1258 | runFlagsFromId_(fontId), |
1203 | .text = text, | 1259 | .text = text, |
1260 | .maxLen = maxLen, | ||
1204 | .pos = pos, | 1261 | .pos = pos, |
1205 | .xposLayoutBound = xposBound, | 1262 | .xposLayoutBound = xposBound, |
1206 | .color = color & mask_ColorId }); | 1263 | .color = color & mask_ColorId }); |
1207 | } | 1264 | } |
1208 | 1265 | ||
1266 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { | ||
1267 | drawBoundedN_Text_(fontId, pos, xposBound, color, text, 0); | ||
1268 | } | ||
1269 | |||
1209 | static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { | 1270 | static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { |
1210 | drawBounded_Text_(fontId, pos, 0, color, text); | 1271 | drawBounded_Text_(fontId, pos, 0, color, text); |
1211 | } | 1272 | } |
@@ -1248,6 +1309,10 @@ void drawRange_Text(int fontId, iInt2 pos, int color, iRangecc text) { | |||
1248 | draw_Text_(fontId, pos, color, text); | 1309 | draw_Text_(fontId, pos, color, text); |
1249 | } | 1310 | } |
1250 | 1311 | ||
1312 | void drawRangeN_Text(int fontId, iInt2 pos, int color, iRangecc text, size_t maxChars) { | ||
1313 | drawBoundedN_Text_(fontId, pos, 0, color, text, maxChars); | ||
1314 | } | ||
1315 | |||
1251 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | 1316 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { |
1252 | iInt2 size = zero_I2(); | 1317 | iInt2 size = zero_I2(); |
1253 | const char *endp; | 1318 | const char *endp; |
@@ -1285,13 +1350,16 @@ void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, con | |||
1285 | vprintf_Block(&chars, format, args); | 1350 | vprintf_Block(&chars, format, args); |
1286 | va_end(args); | 1351 | va_end(args); |
1287 | } | 1352 | } |
1288 | const iRangecc text = range_Block(&chars); | 1353 | drawCenteredRange_Text(fontId, rect, alignVisual, color, range_Block(&chars)); |
1289 | iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) | 1354 | deinit_Block(&chars); |
1355 | } | ||
1356 | |||
1357 | void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { | ||
1358 | iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) | ||
1290 | : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; | 1359 | : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; |
1291 | textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds)); | 1360 | textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds)); |
1292 | textBounds.pos.x = iMax(textBounds.pos.x, left_Rect(rect)); /* keep left edge visible */ | 1361 | textBounds.pos.x = iMax(textBounds.pos.x, left_Rect(rect)); /* keep left edge visible */ |
1293 | draw_Text_(fontId, textBounds.pos, color, text); | 1362 | draw_Text_(fontId, textBounds.pos, color, text); |
1294 | deinit_Block(&chars); | ||
1295 | } | 1363 | } |
1296 | 1364 | ||
1297 | SDL_Texture *glyphCache_Text(void) { | 1365 | SDL_Texture *glyphCache_Text(void) { |