diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-12 15:37:20 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-12 15:37:20 +0200 |
commit | ef07b24ad3431496372d3cfd2884b0609b940e27 (patch) | |
tree | e8a23f15dabc3fd5e2d34c87619c698da478f2ef /src/ui | |
parent | 8864cc5aee3b134b0ffc5f53c1593645f4109f8c (diff) |
New monospace font; flexible mono spacing
Iosevka replaces Fira Mono as the monospace font.
Emojis are now allowed to be wider than normal monospace glyphs. However, if whitespace permits, the original monospacing is restored.
IssueID #73
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/text.c | 51 | ||||
-rw-r--r-- | src/ui/text.h | 21 |
2 files changed, 45 insertions, 27 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 4229217c..ec240c75 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -120,10 +120,10 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale, | |||
120 | d->xScale *= floorf(advance) / advance; | 120 | d->xScale *= floorf(advance) / advance; |
121 | } | 121 | } |
122 | } | 122 | } |
123 | d->vertOffset = height * (1.0f - scale) / 2; | 123 | d->vertOffset = height * (1.0f - scale) / 2; |
124 | int ascent; | 124 | int ascent; |
125 | stbtt_GetFontVMetrics(&d->font, &ascent, NULL, NULL); | 125 | stbtt_GetFontVMetrics(&d->font, &ascent, NULL, NULL); |
126 | d->baseline = ceil(ascent * d->yScale); | 126 | d->baseline = /*ceil*/(ascent * d->yScale); |
127 | d->symbolsFont = symbolsFont; | 127 | d->symbolsFont = symbolsFont; |
128 | d->japaneseFont = regularJapanese_FontId; | 128 | d->japaneseFont = regularJapanese_FontId; |
129 | d->koreanFont = regularKorean_FontId; | 129 | d->koreanFont = regularKorean_FontId; |
@@ -176,8 +176,8 @@ static iText text_; | |||
176 | 176 | ||
177 | static void initFonts_Text_(iText *d) { | 177 | static void initFonts_Text_(iText *d) { |
178 | const float textSize = fontSize_UI * d->contentFontSize; | 178 | const float textSize = fontSize_UI * d->contentFontSize; |
179 | const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f; | 179 | const float monoSize = textSize * 0.71f; //fontSize_UI * d->contentFontSize / contentScale_Text_ * 1.0f; //0.866f; |
180 | const float smallMonoSize = monoSize * 0.866f; | 180 | const float smallMonoSize = monoSize * 0.8f; |
181 | const iBlock *regularFont = &fontNunitoRegular_Embedded; | 181 | const iBlock *regularFont = &fontNunitoRegular_Embedded; |
182 | const iBlock *italicFont = &fontNunitoLightItalic_Embedded; | 182 | const iBlock *italicFont = &fontNunitoLightItalic_Embedded; |
183 | const iBlock *h12Font = &fontNunitoExtraBold_Embedded; | 183 | const iBlock *h12Font = &fontNunitoExtraBold_Embedded; |
@@ -226,12 +226,12 @@ static void initFonts_Text_(iText *d) { | |||
226 | { &fontSourceSansProRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, | 226 | { &fontSourceSansProRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, |
227 | { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, | 227 | { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, |
228 | { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.666f, 1.0f, defaultLargeSymbols_FontId }, | 228 | { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.666f, 1.0f, defaultLargeSymbols_FontId }, |
229 | { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, 1.0f, defaultSymbols_FontId }, | 229 | { &fontIosevkaTermExtended_Embedded, fontSize_UI * 0.866f, 1.0f, defaultSymbols_FontId }, |
230 | { &fontSourceSansProRegular_Embedded, textSize, scaling, symbols_FontId }, | 230 | { &fontSourceSansProRegular_Embedded, textSize, scaling, symbols_FontId }, |
231 | /* content fonts */ | 231 | /* content fonts */ |
232 | { regularFont, textSize, scaling, symbols_FontId }, | 232 | { regularFont, textSize, scaling, symbols_FontId }, |
233 | { &fontFiraMonoRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, | 233 | { &fontIosevkaTermExtended_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, |
234 | { &fontFiraMonoRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, | 234 | { &fontIosevkaTermExtended_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, |
235 | { regularFont, textSize * 1.200f, scaling, mediumSymbols_FontId }, | 235 | { regularFont, textSize * 1.200f, scaling, mediumSymbols_FontId }, |
236 | { h3Font, textSize * 1.333f, h123Scaling, bigSymbols_FontId }, | 236 | { h3Font, textSize * 1.333f, h123Scaling, bigSymbols_FontId }, |
237 | { italicFont, textSize, scaling, symbols_FontId }, | 237 | { italicFont, textSize, scaling, symbols_FontId }, |
@@ -239,7 +239,7 @@ static void initFonts_Text_(iText *d) { | |||
239 | { h12Font, textSize * 2.000f, h123Scaling, hugeSymbols_FontId }, | 239 | { h12Font, textSize * 2.000f, h123Scaling, hugeSymbols_FontId }, |
240 | { lightFont, textSize * 1.666f, lightScaling, largeSymbols_FontId }, | 240 | { lightFont, textSize * 1.666f, lightScaling, largeSymbols_FontId }, |
241 | /* monospace content fonts */ | 241 | /* monospace content fonts */ |
242 | { &fontFiraMonoRegular_Embedded, textSize, 0.8f, symbols_FontId }, | 242 | { &fontIosevkaTermExtended_Embedded, textSize, 0.866f, symbols_FontId }, |
243 | /* symbol fonts */ | 243 | /* symbol fonts */ |
244 | { &fontSymbola_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, | 244 | { &fontSymbola_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, |
245 | { &fontSymbola_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, | 245 | { &fontSymbola_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, |
@@ -288,7 +288,7 @@ static void initFonts_Text_(iText *d) { | |||
288 | fontData[i].size, | 288 | fontData[i].size, |
289 | fontData[i].scaling, | 289 | fontData[i].scaling, |
290 | fontData[i].symbolsFont, | 290 | fontData[i].symbolsFont, |
291 | fontData[i].ttf == &fontFiraMonoRegular_Embedded); | 291 | fontData[i].ttf == &fontIosevkaTermExtended_Embedded); |
292 | if (i == default_FontId || i == defaultMedium_FontId) { | 292 | if (i == default_FontId || i == defaultMedium_FontId) { |
293 | font->manualKernOnly = iTrue; | 293 | font->manualKernOnly = iTrue; |
294 | } | 294 | } |
@@ -636,6 +636,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
636 | iRect bounds = zero_Rect(); | 636 | iRect bounds = zero_Rect(); |
637 | const iInt2 orig = pos; | 637 | const iInt2 orig = pos; |
638 | float xpos = pos.x; | 638 | float xpos = pos.x; |
639 | float xposExtend = pos.x; /* allows wide glyphs to use more space; restored by whitespace */ | ||
639 | float xposMax = xpos; | 640 | float xposMax = xpos; |
640 | float monoAdvance = 0; | 641 | float monoAdvance = 0; |
641 | iAssert(xposLimit == 0 || isMeasuring_(mode)); | 642 | iAssert(xposLimit == 0 || isMeasuring_(mode)); |
@@ -668,6 +669,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
668 | } | 669 | } |
669 | } | 670 | } |
670 | iChar ch = nextChar_(&chPos, text.end); | 671 | iChar ch = nextChar_(&chPos, text.end); |
672 | iBool isEmoji = isEmoji_Char(ch); | ||
671 | if (ch == 0x200d) { /* zero-width joiner */ | 673 | if (ch == 0x200d) { /* zero-width joiner */ |
672 | /* We don't have the composited Emojis. */ | 674 | /* We don't have the composited Emojis. */ |
673 | if (isEmoji_Char(prevCh)) { | 675 | if (isEmoji_Char(prevCh)) { |
@@ -676,9 +678,19 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
676 | ch = nextChar_(&chPos, text.end); | 678 | ch = nextChar_(&chPos, text.end); |
677 | } | 679 | } |
678 | } | 680 | } |
681 | #if 0 | ||
682 | iChar nextCh = 0; { | ||
683 | /* TODO: Since we're peeking ahead, should use this on the next loop iteration. */ | ||
684 | const char *ncp = chPos; | ||
685 | nextCh = nextChar_(&ncp, text.end); | ||
686 | } | ||
687 | /* VS15: Peek ahead and treat as Emoji font. */ | ||
688 | if (nextCh == emojiVariationSelector_Char) { | ||
689 | isEmoji = iTrue; | ||
690 | } | ||
691 | #endif | ||
679 | if (isVariationSelector_Char(ch)) { | 692 | if (isVariationSelector_Char(ch)) { |
680 | /* TODO: VS15: Should peek ahead for this and prefer the Emoji font. */ | 693 | ch = nextChar_(&chPos, text.end); /* skip it */ |
681 | ch = nextChar_(&chPos, text.end); /* just ignore */ | ||
682 | } | 694 | } |
683 | /* Special instructions. */ { | 695 | /* Special instructions. */ { |
684 | if (ch == 0xad) { /* soft hyphen */ | 696 | if (ch == 0xad) { /* soft hyphen */ |
@@ -703,7 +715,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
703 | } | 715 | } |
704 | } | 716 | } |
705 | if (ch == '\n') { | 717 | if (ch == '\n') { |
706 | xpos = pos.x; | 718 | xpos = xposExtend = pos.x; |
707 | pos.y += d->height; | 719 | pos.y += d->height; |
708 | prevCh = ch; | 720 | prevCh = ch; |
709 | continue; | 721 | continue; |
@@ -711,6 +723,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
711 | if (ch == '\t') { | 723 | if (ch == '\t') { |
712 | const int tabStopWidth = d->height * 8; | 724 | const int tabStopWidth = d->height * 8; |
713 | xpos = pos.x + ((int) ((xpos - pos.x) / tabStopWidth) + 1) * tabStopWidth; | 725 | xpos = pos.x + ((int) ((xpos - pos.x) / tabStopWidth) + 1) * tabStopWidth; |
726 | xposExtend = iMax(xposExtend, xpos); | ||
714 | prevCh = 0; | 727 | prevCh = 0; |
715 | continue; | 728 | continue; |
716 | } | 729 | } |
@@ -728,7 +741,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
728 | } | 741 | } |
729 | } | 742 | } |
730 | const iGlyph *glyph = glyph_Font_(d, ch); | 743 | const iGlyph *glyph = glyph_Font_(d, ch); |
731 | int x1 = xpos; | 744 | int x1 = iMax(xpos, xposExtend); |
732 | const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; | 745 | const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; |
733 | int x2 = x1 + glyph->rect[hoff].size.x; | 746 | int x2 = x1 + glyph->rect[hoff].size.x; |
734 | /* Out of the allotted space? */ | 747 | /* Out of the allotted space? */ |
@@ -765,11 +778,13 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
765 | bounds.size.x = iMax(bounds.size.x, x2 - orig.x); | 778 | bounds.size.x = iMax(bounds.size.x, x2 - orig.x); |
766 | bounds.size.y = iMax(bounds.size.y, pos.y + glyph->font->height - orig.y); | 779 | bounds.size.y = iMax(bounds.size.y, pos.y + glyph->font->height - orig.y); |
767 | } | 780 | } |
781 | /* Symbols and emojis are NOT monospaced, so must conform when the primary font | ||
782 | is monospaced. Except with Japanese script, that's larger than the normal monospace. */ | ||
768 | const iBool useMonoAdvance = | 783 | const iBool useMonoAdvance = |
769 | monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font)); | 784 | monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font)); |
770 | const float advance = (useMonoAdvance ? monoAdvance : glyph->advance); | 785 | const float advance = (useMonoAdvance ? monoAdvance : glyph->advance); |
771 | if (!isMeasuring_(mode)) { | 786 | if (!isMeasuring_(mode)) { |
772 | if (useMonoAdvance && dst.w > advance && glyph->font != d) { | 787 | if (useMonoAdvance && dst.w > advance && glyph->font != d && !isEmoji) { |
773 | /* Glyphs from a different font may need recentering to look better. */ | 788 | /* Glyphs from a different font may need recentering to look better. */ |
774 | dst.x -= (dst.w - advance) / 2; | 789 | dst.x -= (dst.w - advance) / 2; |
775 | } | 790 | } |
@@ -784,10 +799,12 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
784 | } | 799 | } |
785 | SDL_RenderCopy(text_.render, text_.cache, &src, &dst); | 800 | SDL_RenderCopy(text_.render, text_.cache, &src, &dst); |
786 | } | 801 | } |
787 | /* Symbols and emojis are NOT monospaced, so must conform when the primary font | ||
788 | is monospaced. Except with Japanese script, that's larger than the normal monospace. */ | ||
789 | xpos += advance; | 802 | xpos += advance; |
790 | xposMax = iMax(xposMax, xpos); | 803 | if (!isSpace_Char(ch)) { |
804 | xposExtend += isEmoji ? glyph->advance : advance; | ||
805 | } | ||
806 | xposExtend = iMax(xposExtend, xpos); | ||
807 | xposMax = iMax(xposMax, xposExtend); | ||
791 | if (continueFrom_out && ((mode & noWrapFlag_RunMode) || isWrapBoundary_(prevCh, ch))) { | 808 | if (continueFrom_out && ((mode & noWrapFlag_RunMode) || isWrapBoundary_(prevCh, ch))) { |
792 | lastWordEnd = chPos; | 809 | lastWordEnd = chPos; |
793 | } | 810 | } |
diff --git a/src/ui/text.h b/src/ui/text.h index 6728bb62..5867a84b 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -127,7 +127,8 @@ iLocalDef iBool isDefaultIgnorable_Char(iChar c) { | |||
127 | c == 0xfeff; | 127 | c == 0xfeff; |
128 | } | 128 | } |
129 | iLocalDef iBool isEmoji_Char(iChar c) { | 129 | iLocalDef iBool isEmoji_Char(iChar c) { |
130 | return (c >= 0x1f300 && c < 0x1f700) || (c >= 0x1f900 && c <= 0x1f9ff); | 130 | return (c >= 0x1f300 && c < 0x1f700) || (c >= 0x1f7e0 && c <= 0x1f7eb) || |
131 | (c >= 0x1f900 && c <= 0x1f9ff); | ||
131 | } | 132 | } |
132 | 133 | ||
133 | #define emojiVariationSelector_Char ((iChar) 0xfe0f) | 134 | #define emojiVariationSelector_Char ((iChar) 0xfe0f) |
@@ -141,21 +142,21 @@ enum iTextFont { | |||
141 | 142 | ||
142 | extern int gap_Text; /* affected by content font size */ | 143 | extern int gap_Text; /* affected by content font size */ |
143 | 144 | ||
144 | void init_Text (SDL_Renderer *); | 145 | void init_Text (SDL_Renderer *); |
145 | void deinit_Text (void); | 146 | void deinit_Text (void); |
146 | 147 | ||
147 | void setContentFont_Text (enum iTextFont font); | 148 | void setContentFont_Text (enum iTextFont font); |
148 | void setHeadingFont_Text (enum iTextFont font); | 149 | void setHeadingFont_Text (enum iTextFont font); |
149 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ | 150 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ |
150 | void resetFonts_Text (void); | 151 | void resetFonts_Text (void); |
151 | 152 | ||
152 | int lineHeight_Text (int fontId); | 153 | int lineHeight_Text (int fontId); |
153 | iInt2 measure_Text (int fontId, const char *text); | 154 | iInt2 measure_Text (int fontId, const char *text); |
154 | iInt2 measureRange_Text (int fontId, iRangecc text); | 155 | iInt2 measureRange_Text (int fontId, iRangecc text); |
155 | iRect visualBounds_Text (int fontId, iRangecc text); | 156 | iRect visualBounds_Text (int fontId, iRangecc text); |
156 | iInt2 advance_Text (int fontId, const char *text); | 157 | iInt2 advance_Text (int fontId, const char *text); |
157 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | 158 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ |
158 | iInt2 advanceRange_Text (int fontId, iRangecc text); | 159 | iInt2 advanceRange_Text (int fontId, iRangecc text); |
159 | iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text); | 160 | iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text); |
160 | 161 | ||
161 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); | 162 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); |