From 17d7cc86578eb0a9e97e3a51bad0e10a9da1482e Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 21 Aug 2020 13:21:11 +0300 Subject: Experimenting with text art Convert glyph bitmaps to Unicode block characters --- src/ui/text.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 2 deletions(-) (limited to 'src/ui/text.c') 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 @@ #include #include #include +#include #include #include #include @@ -287,8 +288,8 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { /* Rasterize the glyph using stbtt. */ { surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); if (hoff == 0) { - int adv, lsb; - stbtt_GetCodepointHMetrics(&d->font, ch, &adv, &lsb); + int adv; + stbtt_GetCodepointHMetrics(&d->font, ch, &adv, NULL); glyph->advance = d->scale * adv; } stbtt_GetCodepointBitmapBoxSubpixel(&d->font, @@ -658,6 +659,111 @@ SDL_Texture *glyphCache_Text(void) { return text_.cache; } +static void freeBitmap_(void *ptr) { + stbtt_FreeBitmap(ptr, NULL); +} + +iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlockMode mode, + const iString *text) { + iBeginCollect(); + stbtt_fontinfo font; + iZap(font); + stbtt_InitFont(&font, constData_Block(fontData), 0); + int ascent; + stbtt_GetFontVMetrics(&font, &ascent, NULL, NULL); + iDeclareType(CharBuf); + struct Impl_CharBuf { + uint8_t *pixels; + iInt2 size; + int dy; + int advance; + }; + iArray * chars = collectNew_Array(sizeof(iCharBuf)); + int pxRatio = (mode == quadrants_TextBlockMode ? 2 : 1); + int pxHeight = height * pxRatio; + const float scale = stbtt_ScaleForPixelHeight(&font, pxHeight); + const float xScale = scale * 2; /* character aspect ratio */ + const int baseline = ascent * scale; + int width = 0; + size_t strRemain = length_String(text); + iConstForEach(String, i, text) { + if (!strRemain) break; + if (i.value == variationSelectorEmoji_Char) { + strRemain--; + continue; + } + iCharBuf buf; + buf.pixels = stbtt_GetCodepointBitmap( + &font, xScale, scale, i.value, &buf.size.x, &buf.size.y, 0, &buf.dy); + stbtt_GetCodepointHMetrics(&font, i.value, &buf.advance, NULL); + buf.advance *= xScale; + if (!isSpace_Char(i.value)) { + if (mode == quadrants_TextBlockMode) { + buf.advance = (buf.size.x - 1) / 2 * 2 + 2; + } + else { + buf.advance = buf.size.x + 1; + } + } + pushBack_Array(chars, &buf); + collect_Garbage(buf.pixels, freeBitmap_); + width += buf.advance; + strRemain--; + } + const size_t len = (mode == quadrants_TextBlockMode ? height * ((width + 1) / 2 + 1) + : (height * (width + 1))); + iChar *outBuf = iCollectMem(malloc(sizeof(iChar) * len)); + for (size_t i = 0; i < len; ++i) { + outBuf[i] = 0x20; + } + iChar *outPos = outBuf; + for (int y = 0; y < pxHeight; y += pxRatio) { + const iCharBuf *ch = constData_Array(chars); + int lx = 0; + for (int x = 0; x < width; x += pxRatio, lx += pxRatio) { + if (lx >= ch->advance) { + ch++; + lx = 0; + } + const int ly = y - baseline - ch->dy; + if (mode == quadrants_TextBlockMode) { + #define checkPixel_(offx, offy) \ + (lx + offx < ch->size.x && ly + offy < ch->size.y && ly + offy >= 0 ? \ + ch->pixels[(lx + offx) + (ly + offy) * ch->size.x] > 155 \ + : iFalse) + const int mask = (checkPixel_(0, 0) ? 1 : 0) | + (checkPixel_(1, 0) ? 2 : 0) | + (checkPixel_(0, 1) ? 4 : 0) | + (checkPixel_(1, 1) ? 8 : 0); + #undef checkPixel_ + static const iChar blocks[16] = { 0x0020, 0x2598, 0x259D, 0x2580, 0x2596, 0x258C, + 0x259E, 0x259B, 0x2597, 0x259A, 0x2590, 0x259C, + 0x2584, 0x2599, 0x259F, 0x2588 }; + *outPos++ = blocks[mask]; + } + else { + static const iChar shades[5] = { 0x0020, 0x2591, 0x2592, 0x2593, 0x2588 }; + *outPos++ = shades[lx < ch->size.x && ly < ch->size.y && ly >= 0 ? + ch->pixels[lx + ly * ch->size.x] * 5 / 256 : 0]; + } + } + *outPos++ = '\n'; + } + /* We could compose the lines separately, but we'd still need to convert them to Strings + individually to trim them. */ + iStringList *lines = split_String(collect_String(newUnicodeN_String(outBuf, len)), "\n"); + while (!isEmpty_StringList(lines) && + isEmpty_String(collect_String(trimmed_String(at_StringList(lines, 0))))) { + popFront_StringList(lines); + } + while (!isEmpty_StringList(lines) && isEmpty_String(collect_String(trimmed_String( + at_StringList(lines, size_StringList(lines) - 1))))) { + popBack_StringList(lines); + } + iEndCollect(); + return joinCStr_StringList(iClob(lines), "\n"); +} + /*-----------------------------------------------------------------------------------------------*/ iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) -- cgit v1.2.3