diff options
Diffstat (limited to 'src/ui/text.c')
-rw-r--r-- | src/ui/text.c | 150 |
1 files changed, 143 insertions, 7 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 1e702eee..836d540f 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -1,3 +1,25 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
1 | #include "text.h" | 23 | #include "text.h" |
2 | #include "color.h" | 24 | #include "color.h" |
3 | #include "metrics.h" | 25 | #include "metrics.h" |
@@ -11,6 +33,7 @@ | |||
11 | #include <the_Foundation/file.h> | 33 | #include <the_Foundation/file.h> |
12 | #include <the_Foundation/hash.h> | 34 | #include <the_Foundation/hash.h> |
13 | #include <the_Foundation/math.h> | 35 | #include <the_Foundation/math.h> |
36 | #include <the_Foundation/stringlist.h> | ||
14 | #include <the_Foundation/regexp.h> | 37 | #include <the_Foundation/regexp.h> |
15 | #include <the_Foundation/path.h> | 38 | #include <the_Foundation/path.h> |
16 | #include <the_Foundation/vec2.h> | 39 | #include <the_Foundation/vec2.h> |
@@ -25,6 +48,7 @@ iDeclareTypeConstructionArgs(Glyph, iChar ch) | |||
25 | 48 | ||
26 | int gap_Text; /* cf. gap_UI in metrics.h */ | 49 | int gap_Text; /* cf. gap_UI in metrics.h */ |
27 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ | 50 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ |
51 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ | ||
28 | 52 | ||
29 | struct Impl_Glyph { | 53 | struct Impl_Glyph { |
30 | iHashNode node; | 54 | iHashNode node; |
@@ -62,6 +86,7 @@ struct Impl_Font { | |||
62 | int baseline; | 86 | int baseline; |
63 | iHash glyphs; | 87 | iHash glyphs; |
64 | iBool isMonospaced; | 88 | iBool isMonospaced; |
89 | iBool manualKernOnly; | ||
65 | enum iFontId symbolsFont; /* font to use for symbols */ | 90 | enum iFontId symbolsFont; /* font to use for symbols */ |
66 | }; | 91 | }; |
67 | 92 | ||
@@ -120,7 +145,7 @@ static void initFonts_Text_(iText *d) { | |||
120 | int symbolsFont; | 145 | int symbolsFont; |
121 | } fontData[max_FontId] = { | 146 | } fontData[max_FontId] = { |
122 | { &fontSourceSansProRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, | 147 | { &fontSourceSansProRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, |
123 | { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.150f, defaultMediumSymbols_FontId }, | 148 | { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, |
124 | { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId }, | 149 | { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId }, |
125 | { &fontFiraSansRegular_Embedded, textSize, symbols_FontId }, | 150 | { &fontFiraSansRegular_Embedded, textSize, symbols_FontId }, |
126 | { &fontFiraMonoRegular_Embedded, textSize * 0.866f, smallSymbols_FontId }, | 151 | { &fontFiraMonoRegular_Embedded, textSize * 0.866f, smallSymbols_FontId }, |
@@ -133,14 +158,14 @@ static void initFonts_Text_(iText *d) { | |||
133 | { &fontFiraSansBold_Embedded, textSize * 2.000f, hugeSymbols_FontId }, | 158 | { &fontFiraSansBold_Embedded, textSize * 2.000f, hugeSymbols_FontId }, |
134 | { &fontFiraSansLight_Embedded, textSize * 1.666f, largeSymbols_FontId }, | 159 | { &fontFiraSansLight_Embedded, textSize * 1.666f, largeSymbols_FontId }, |
135 | { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId }, | 160 | { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId }, |
136 | { &fontSymbola_Embedded, fontSize_UI * 1.150f, defaultMediumSymbols_FontId }, | 161 | { &fontSymbola_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, |
137 | { &fontSymbola_Embedded, textSize, symbols_FontId }, | 162 | { &fontSymbola_Embedded, textSize, symbols_FontId }, |
138 | { &fontSymbola_Embedded, textSize * 1.333f, mediumSymbols_FontId }, | 163 | { &fontSymbola_Embedded, textSize * 1.333f, mediumSymbols_FontId }, |
139 | { &fontSymbola_Embedded, textSize * 1.666f, largeSymbols_FontId }, | 164 | { &fontSymbola_Embedded, textSize * 1.666f, largeSymbols_FontId }, |
140 | { &fontSymbola_Embedded, textSize * 2.000f, hugeSymbols_FontId }, | 165 | { &fontSymbola_Embedded, textSize * 2.000f, hugeSymbols_FontId }, |
141 | { &fontSymbola_Embedded, textSize * 0.866f, smallSymbols_FontId }, | 166 | { &fontSymbola_Embedded, textSize * 0.866f, smallSymbols_FontId }, |
142 | { &fontNotoEmojiRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, | 167 | { &fontNotoEmojiRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, |
143 | { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.150f, defaultMediumSymbols_FontId }, | 168 | { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, |
144 | { &fontNotoEmojiRegular_Embedded, textSize, symbols_FontId }, | 169 | { &fontNotoEmojiRegular_Embedded, textSize, symbols_FontId }, |
145 | { &fontNotoEmojiRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId }, | 170 | { &fontNotoEmojiRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId }, |
146 | { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, largeSymbols_FontId }, | 171 | { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, largeSymbols_FontId }, |
@@ -153,6 +178,9 @@ static void initFonts_Text_(iText *d) { | |||
153 | if (fontData[i].ttf == &fontFiraMonoRegular_Embedded) { | 178 | if (fontData[i].ttf == &fontFiraMonoRegular_Embedded) { |
154 | font->isMonospaced = iTrue; | 179 | font->isMonospaced = iTrue; |
155 | } | 180 | } |
181 | if (i == default_FontId || i == defaultMedium_FontId) { | ||
182 | font->manualKernOnly = iTrue; | ||
183 | } | ||
156 | } | 184 | } |
157 | gap_Text = iRound(gap_UI * d->contentFontSize); | 185 | gap_Text = iRound(gap_UI * d->contentFontSize); |
158 | } | 186 | } |
@@ -287,8 +315,8 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
287 | /* Rasterize the glyph using stbtt. */ { | 315 | /* Rasterize the glyph using stbtt. */ { |
288 | surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); | 316 | surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); |
289 | if (hoff == 0) { | 317 | if (hoff == 0) { |
290 | int adv, lsb; | 318 | int adv; |
291 | stbtt_GetCodepointHMetrics(&d->font, ch, &adv, &lsb); | 319 | stbtt_GetCodepointHMetrics(&d->font, ch, &adv, NULL); |
292 | glyph->advance = d->scale * adv; | 320 | glyph->advance = d->scale * adv; |
293 | } | 321 | } |
294 | stbtt_GetCodepointBitmapBoxSubpixel(&d->font, | 322 | stbtt_GetCodepointBitmapBoxSubpixel(&d->font, |
@@ -406,6 +434,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
406 | /* ANSI escape. */ | 434 | /* ANSI escape. */ |
407 | chPos++; | 435 | chPos++; |
408 | iRegExpMatch m; | 436 | iRegExpMatch m; |
437 | init_RegExpMatch(&m); | ||
409 | if (match_RegExp(text_.ansiEscape, chPos, text.end - chPos, &m)) { | 438 | if (match_RegExp(text_.ansiEscape, chPos, text.end - chPos, &m)) { |
410 | if (mode == draw_RunMode) { | 439 | if (mode == draw_RunMode) { |
411 | /* Change the color. */ | 440 | /* Change the color. */ |
@@ -431,7 +460,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
431 | if (ch == '\r') { | 460 | if (ch == '\r') { |
432 | const iChar esc = nextChar_(&chPos, text.end); | 461 | const iChar esc = nextChar_(&chPos, text.end); |
433 | if (mode == draw_RunMode) { | 462 | if (mode == draw_RunMode) { |
434 | const iColor clr = get_Color(esc - '0'); | 463 | const iColor clr = get_Color(esc - asciiBase_ColorEscape); |
435 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); | 464 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); |
436 | } | 465 | } |
437 | prevCh = 0; | 466 | prevCh = 0; |
@@ -488,9 +517,11 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
488 | /* Manual kerning for double-slash. */ | 517 | /* Manual kerning for double-slash. */ |
489 | xpos -= glyph->rect[hoff].size.x * 0.5f; | 518 | xpos -= glyph->rect[hoff].size.x * 0.5f; |
490 | } | 519 | } |
491 | else if (next) { | 520 | #if defined (LAGRANGE_ENABLE_KERNING) |
521 | else if (enableKerning_Text && !d->manualKernOnly && next) { | ||
492 | xpos += d->scale * stbtt_GetCodepointKernAdvance(&d->font, ch, next); | 522 | xpos += d->scale * stbtt_GetCodepointKernAdvance(&d->font, ch, next); |
493 | } | 523 | } |
524 | #endif | ||
494 | } | 525 | } |
495 | prevCh = ch; | 526 | prevCh = ch; |
496 | if (--maxLen == 0) { | 527 | if (--maxLen == 0) { |
@@ -658,6 +689,111 @@ SDL_Texture *glyphCache_Text(void) { | |||
658 | return text_.cache; | 689 | return text_.cache; |
659 | } | 690 | } |
660 | 691 | ||
692 | static void freeBitmap_(void *ptr) { | ||
693 | stbtt_FreeBitmap(ptr, NULL); | ||
694 | } | ||
695 | |||
696 | iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlockMode mode, | ||
697 | const iString *text) { | ||
698 | iBeginCollect(); | ||
699 | stbtt_fontinfo font; | ||
700 | iZap(font); | ||
701 | stbtt_InitFont(&font, constData_Block(fontData), 0); | ||
702 | int ascent; | ||
703 | stbtt_GetFontVMetrics(&font, &ascent, NULL, NULL); | ||
704 | iDeclareType(CharBuf); | ||
705 | struct Impl_CharBuf { | ||
706 | uint8_t *pixels; | ||
707 | iInt2 size; | ||
708 | int dy; | ||
709 | int advance; | ||
710 | }; | ||
711 | iArray * chars = collectNew_Array(sizeof(iCharBuf)); | ||
712 | int pxRatio = (mode == quadrants_TextBlockMode ? 2 : 1); | ||
713 | int pxHeight = height * pxRatio; | ||
714 | const float scale = stbtt_ScaleForPixelHeight(&font, pxHeight); | ||
715 | const float xScale = scale * 2; /* character aspect ratio */ | ||
716 | const int baseline = ascent * scale; | ||
717 | int width = 0; | ||
718 | size_t strRemain = length_String(text); | ||
719 | iConstForEach(String, i, text) { | ||
720 | if (!strRemain) break; | ||
721 | if (i.value == variationSelectorEmoji_Char) { | ||
722 | strRemain--; | ||
723 | continue; | ||
724 | } | ||
725 | iCharBuf buf; | ||
726 | buf.pixels = stbtt_GetCodepointBitmap( | ||
727 | &font, xScale, scale, i.value, &buf.size.x, &buf.size.y, 0, &buf.dy); | ||
728 | stbtt_GetCodepointHMetrics(&font, i.value, &buf.advance, NULL); | ||
729 | buf.advance *= xScale; | ||
730 | if (!isSpace_Char(i.value)) { | ||
731 | if (mode == quadrants_TextBlockMode) { | ||
732 | buf.advance = (buf.size.x - 1) / 2 * 2 + 2; | ||
733 | } | ||
734 | else { | ||
735 | buf.advance = buf.size.x + 1; | ||
736 | } | ||
737 | } | ||
738 | pushBack_Array(chars, &buf); | ||
739 | collect_Garbage(buf.pixels, freeBitmap_); | ||
740 | width += buf.advance; | ||
741 | strRemain--; | ||
742 | } | ||
743 | const size_t len = (mode == quadrants_TextBlockMode ? height * ((width + 1) / 2 + 1) | ||
744 | : (height * (width + 1))); | ||
745 | iChar *outBuf = iCollectMem(malloc(sizeof(iChar) * len)); | ||
746 | for (size_t i = 0; i < len; ++i) { | ||
747 | outBuf[i] = 0x20; | ||
748 | } | ||
749 | iChar *outPos = outBuf; | ||
750 | for (int y = 0; y < pxHeight; y += pxRatio) { | ||
751 | const iCharBuf *ch = constData_Array(chars); | ||
752 | int lx = 0; | ||
753 | for (int x = 0; x < width; x += pxRatio, lx += pxRatio) { | ||
754 | if (lx >= ch->advance) { | ||
755 | ch++; | ||
756 | lx = 0; | ||
757 | } | ||
758 | const int ly = y - baseline - ch->dy; | ||
759 | if (mode == quadrants_TextBlockMode) { | ||
760 | #define checkPixel_(offx, offy) \ | ||
761 | (lx + offx < ch->size.x && ly + offy < ch->size.y && ly + offy >= 0 ? \ | ||
762 | ch->pixels[(lx + offx) + (ly + offy) * ch->size.x] > 155 \ | ||
763 | : iFalse) | ||
764 | const int mask = (checkPixel_(0, 0) ? 1 : 0) | | ||
765 | (checkPixel_(1, 0) ? 2 : 0) | | ||
766 | (checkPixel_(0, 1) ? 4 : 0) | | ||
767 | (checkPixel_(1, 1) ? 8 : 0); | ||
768 | #undef checkPixel_ | ||
769 | static const iChar blocks[16] = { 0x0020, 0x2598, 0x259D, 0x2580, 0x2596, 0x258C, | ||
770 | 0x259E, 0x259B, 0x2597, 0x259A, 0x2590, 0x259C, | ||
771 | 0x2584, 0x2599, 0x259F, 0x2588 }; | ||
772 | *outPos++ = blocks[mask]; | ||
773 | } | ||
774 | else { | ||
775 | static const iChar shades[5] = { 0x0020, 0x2591, 0x2592, 0x2593, 0x2588 }; | ||
776 | *outPos++ = shades[lx < ch->size.x && ly < ch->size.y && ly >= 0 ? | ||
777 | ch->pixels[lx + ly * ch->size.x] * 5 / 256 : 0]; | ||
778 | } | ||
779 | } | ||
780 | *outPos++ = '\n'; | ||
781 | } | ||
782 | /* We could compose the lines separately, but we'd still need to convert them to Strings | ||
783 | individually to trim them. */ | ||
784 | iStringList *lines = split_String(collect_String(newUnicodeN_String(outBuf, len)), "\n"); | ||
785 | while (!isEmpty_StringList(lines) && | ||
786 | isEmpty_String(collect_String(trimmed_String(at_StringList(lines, 0))))) { | ||
787 | popFront_StringList(lines); | ||
788 | } | ||
789 | while (!isEmpty_StringList(lines) && isEmpty_String(collect_String(trimmed_String( | ||
790 | at_StringList(lines, size_StringList(lines) - 1))))) { | ||
791 | popBack_StringList(lines); | ||
792 | } | ||
793 | iEndCollect(); | ||
794 | return joinCStr_StringList(iClob(lines), "\n"); | ||
795 | } | ||
796 | |||
661 | /*-----------------------------------------------------------------------------------------------*/ | 797 | /*-----------------------------------------------------------------------------------------------*/ |
662 | 798 | ||
663 | iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) | 799 | iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) |