summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c150
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
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. 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
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY 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
21SOFTWARE, 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
26int gap_Text; /* cf. gap_UI in metrics.h */ 49int gap_Text; /* cf. gap_UI in metrics.h */
27int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ 50int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */
51int enableKerning_Text = iTrue; /* looking up kern pairs is slow */
28 52
29struct Impl_Glyph { 53struct 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
692static void freeBitmap_(void *ptr) {
693 stbtt_FreeBitmap(ptr, NULL);
694}
695
696iString *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
663iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) 799iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text)