diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-21 13:21:11 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-21 13:21:11 +0300 |
commit | 17d7cc86578eb0a9e97e3a51bad0e10a9da1482e (patch) | |
tree | 9f95e7fd9f91bea0590959d656f4a2e98f0d86de /src/ui/text.c | |
parent | 648ddbf3d118d28a20027b8c5dd074494c53b90a (diff) |
Experimenting with text art
Convert glyph bitmaps to Unicode block characters
Diffstat (limited to 'src/ui/text.c')
-rw-r--r-- | src/ui/text.c | 110 |
1 files changed, 108 insertions, 2 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 0bf9b294..60e26692 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -11,6 +11,7 @@ | |||
11 | #include <the_Foundation/file.h> | 11 | #include <the_Foundation/file.h> |
12 | #include <the_Foundation/hash.h> | 12 | #include <the_Foundation/hash.h> |
13 | #include <the_Foundation/math.h> | 13 | #include <the_Foundation/math.h> |
14 | #include <the_Foundation/stringlist.h> | ||
14 | #include <the_Foundation/regexp.h> | 15 | #include <the_Foundation/regexp.h> |
15 | #include <the_Foundation/path.h> | 16 | #include <the_Foundation/path.h> |
16 | #include <the_Foundation/vec2.h> | 17 | #include <the_Foundation/vec2.h> |
@@ -287,8 +288,8 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
287 | /* Rasterize the glyph using stbtt. */ { | 288 | /* Rasterize the glyph using stbtt. */ { |
288 | surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); | 289 | surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); |
289 | if (hoff == 0) { | 290 | if (hoff == 0) { |
290 | int adv, lsb; | 291 | int adv; |
291 | stbtt_GetCodepointHMetrics(&d->font, ch, &adv, &lsb); | 292 | stbtt_GetCodepointHMetrics(&d->font, ch, &adv, NULL); |
292 | glyph->advance = d->scale * adv; | 293 | glyph->advance = d->scale * adv; |
293 | } | 294 | } |
294 | stbtt_GetCodepointBitmapBoxSubpixel(&d->font, | 295 | stbtt_GetCodepointBitmapBoxSubpixel(&d->font, |
@@ -658,6 +659,111 @@ SDL_Texture *glyphCache_Text(void) { | |||
658 | return text_.cache; | 659 | return text_.cache; |
659 | } | 660 | } |
660 | 661 | ||
662 | static void freeBitmap_(void *ptr) { | ||
663 | stbtt_FreeBitmap(ptr, NULL); | ||
664 | } | ||
665 | |||
666 | iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlockMode mode, | ||
667 | const iString *text) { | ||
668 | iBeginCollect(); | ||
669 | stbtt_fontinfo font; | ||
670 | iZap(font); | ||
671 | stbtt_InitFont(&font, constData_Block(fontData), 0); | ||
672 | int ascent; | ||
673 | stbtt_GetFontVMetrics(&font, &ascent, NULL, NULL); | ||
674 | iDeclareType(CharBuf); | ||
675 | struct Impl_CharBuf { | ||
676 | uint8_t *pixels; | ||
677 | iInt2 size; | ||
678 | int dy; | ||
679 | int advance; | ||
680 | }; | ||
681 | iArray * chars = collectNew_Array(sizeof(iCharBuf)); | ||
682 | int pxRatio = (mode == quadrants_TextBlockMode ? 2 : 1); | ||
683 | int pxHeight = height * pxRatio; | ||
684 | const float scale = stbtt_ScaleForPixelHeight(&font, pxHeight); | ||
685 | const float xScale = scale * 2; /* character aspect ratio */ | ||
686 | const int baseline = ascent * scale; | ||
687 | int width = 0; | ||
688 | size_t strRemain = length_String(text); | ||
689 | iConstForEach(String, i, text) { | ||
690 | if (!strRemain) break; | ||
691 | if (i.value == variationSelectorEmoji_Char) { | ||
692 | strRemain--; | ||
693 | continue; | ||
694 | } | ||
695 | iCharBuf buf; | ||
696 | buf.pixels = stbtt_GetCodepointBitmap( | ||
697 | &font, xScale, scale, i.value, &buf.size.x, &buf.size.y, 0, &buf.dy); | ||
698 | stbtt_GetCodepointHMetrics(&font, i.value, &buf.advance, NULL); | ||
699 | buf.advance *= xScale; | ||
700 | if (!isSpace_Char(i.value)) { | ||
701 | if (mode == quadrants_TextBlockMode) { | ||
702 | buf.advance = (buf.size.x - 1) / 2 * 2 + 2; | ||
703 | } | ||
704 | else { | ||
705 | buf.advance = buf.size.x + 1; | ||
706 | } | ||
707 | } | ||
708 | pushBack_Array(chars, &buf); | ||
709 | collect_Garbage(buf.pixels, freeBitmap_); | ||
710 | width += buf.advance; | ||
711 | strRemain--; | ||
712 | } | ||
713 | const size_t len = (mode == quadrants_TextBlockMode ? height * ((width + 1) / 2 + 1) | ||
714 | : (height * (width + 1))); | ||
715 | iChar *outBuf = iCollectMem(malloc(sizeof(iChar) * len)); | ||
716 | for (size_t i = 0; i < len; ++i) { | ||
717 | outBuf[i] = 0x20; | ||
718 | } | ||
719 | iChar *outPos = outBuf; | ||
720 | for (int y = 0; y < pxHeight; y += pxRatio) { | ||
721 | const iCharBuf *ch = constData_Array(chars); | ||
722 | int lx = 0; | ||
723 | for (int x = 0; x < width; x += pxRatio, lx += pxRatio) { | ||
724 | if (lx >= ch->advance) { | ||
725 | ch++; | ||
726 | lx = 0; | ||
727 | } | ||
728 | const int ly = y - baseline - ch->dy; | ||
729 | if (mode == quadrants_TextBlockMode) { | ||
730 | #define checkPixel_(offx, offy) \ | ||
731 | (lx + offx < ch->size.x && ly + offy < ch->size.y && ly + offy >= 0 ? \ | ||
732 | ch->pixels[(lx + offx) + (ly + offy) * ch->size.x] > 155 \ | ||
733 | : iFalse) | ||
734 | const int mask = (checkPixel_(0, 0) ? 1 : 0) | | ||
735 | (checkPixel_(1, 0) ? 2 : 0) | | ||
736 | (checkPixel_(0, 1) ? 4 : 0) | | ||
737 | (checkPixel_(1, 1) ? 8 : 0); | ||
738 | #undef checkPixel_ | ||
739 | static const iChar blocks[16] = { 0x0020, 0x2598, 0x259D, 0x2580, 0x2596, 0x258C, | ||
740 | 0x259E, 0x259B, 0x2597, 0x259A, 0x2590, 0x259C, | ||
741 | 0x2584, 0x2599, 0x259F, 0x2588 }; | ||
742 | *outPos++ = blocks[mask]; | ||
743 | } | ||
744 | else { | ||
745 | static const iChar shades[5] = { 0x0020, 0x2591, 0x2592, 0x2593, 0x2588 }; | ||
746 | *outPos++ = shades[lx < ch->size.x && ly < ch->size.y && ly >= 0 ? | ||
747 | ch->pixels[lx + ly * ch->size.x] * 5 / 256 : 0]; | ||
748 | } | ||
749 | } | ||
750 | *outPos++ = '\n'; | ||
751 | } | ||
752 | /* We could compose the lines separately, but we'd still need to convert them to Strings | ||
753 | individually to trim them. */ | ||
754 | iStringList *lines = split_String(collect_String(newUnicodeN_String(outBuf, len)), "\n"); | ||
755 | while (!isEmpty_StringList(lines) && | ||
756 | isEmpty_String(collect_String(trimmed_String(at_StringList(lines, 0))))) { | ||
757 | popFront_StringList(lines); | ||
758 | } | ||
759 | while (!isEmpty_StringList(lines) && isEmpty_String(collect_String(trimmed_String( | ||
760 | at_StringList(lines, size_StringList(lines) - 1))))) { | ||
761 | popBack_StringList(lines); | ||
762 | } | ||
763 | iEndCollect(); | ||
764 | return joinCStr_StringList(iClob(lines), "\n"); | ||
765 | } | ||
766 | |||
661 | /*-----------------------------------------------------------------------------------------------*/ | 767 | /*-----------------------------------------------------------------------------------------------*/ |
662 | 768 | ||
663 | iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) | 769 | iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) |