// Text rendering. See include/text.h for what this replaces and why. #include "types.h" #include "defs.h" #include "param.h" #include "text.h" // Included, now that this is the only file that reads it. // // It used to be `extern unsigned char font_data[256][16];` with a note // explaining that include/font.h *defines* the array rather than declaring // it, so a second includer would break the link. The second includer was // gfx.c, which walked the bitmap itself when there was no TrueType face -- // and that was the duplicate glyph path. With it gone, the header can live // with its one user. #include "font.h" // --- UTF-8 ----------------------------------------------------------------- uint32 utf8_next(const char **p) { const unsigned char *s = (const unsigned char *)*p; uint32 c = s[0]; if (c < 0x80) { *p += 1; return c; } int extra; uint32 cp; if ((c & 0xE0) == 0xC0) { extra = 1; cp = c & 0x1F; } else if ((c & 0xF0) == 0xE0) { extra = 2; cp = c & 0x0F; } else if ((c & 0xF8) == 0xF0) { extra = 3; cp = c & 0x07; } else { // A continuation byte or 0xF8..0xFF where a lead byte should be. Consume // exactly one so a malformed string cannot stall the loop -- a decoder that // does not advance on bad input is a hang, not a rendering artefact. *p += 1; return 0xFFFD; } for (int i = 1; i <= extra; i++) { if ((s[i] & 0xC0) != 0x80) { // truncated sequence *p += 1; return 0xFFFD; } cp = (cp << 6) | (s[i] & 0x3F); } *p += extra + 1; // Surrogates are not characters, and an overlong encoding is the classic way // to smuggle a byte past a filter that checks the decoded form. if (cp >= 0xD800 && cp <= 0xDFFF) return 0xFFFD; if ((extra == 1 && cp < 0x80) || (extra == 2 && cp < 0x800) || (extra == 3 && cp < 0x10000)) return 0xFFFD; return cp; } // --- the built-in bitmap font as a provider -------------------------------- // // font_data is 256 CP437 glyphs of 8x16, one bit per pixel. Expanded here into // 8-bit coverage so that every caller works in one representation and an // outline rasteriser can be added without touching any of them. // // The expansion buffer is static and reused per glyph. That is fine because // glyphs are consumed immediately by the blitter and nothing holds one; it // would not be fine the moment a caller kept a struct glyph across calls, which // is why the header says nothing about the lifetime being longer than that. #define BUILTIN_W 8 #define BUILTIN_H 16 static unsigned char builtin_cov[BUILTIN_W * BUILTIN_H]; // Unicode to CP437, for the range where they differ and it matters. // // The built-in font is CP437, not Latin-1 -- a fact that has bitten this // codebase before. Rather than pretend otherwise, map the handful of // codepoints worth having and fall back for the rest. This table exists to be // *deleted* when a real font arrives; it is a bridge, not a design. static uint32 to_cp437(uint32 cp) { if (cp < 0x80) return cp; switch (cp) { case 0x00A0: return 0x20; // no-break space case 0x00B0: return 0xF8; // degree case 0x00B1: return 0xF1; // plus-minus case 0x00B7: return 0xFA; // middle dot case 0x2022: return 0x07; // bullet case 0x2190: return 0x1B; // arrows case 0x2191: return 0x18; case 0x2192: return 0x1A; case 0x2193: return 0x19; case 0x2500: return 0xC4; // box drawing, the part worth keeping case 0x2502: return 0xB3; case 0x250C: return 0xDA; case 0x2510: return 0xBF; case 0x2514: return 0xC0; case 0x2518: return 0xD9; case 0x251C: return 0xC3; case 0x2524: return 0xB4; case 0x252C: return 0xC2; case 0x2534: return 0xC1; case 0x253C: return 0xC5; case 0x2588: return 0xDB; // full block case 0x2591: return 0xB0; // shades case 0x2592: return 0xB1; case 0x2593: return 0xB2; default: return 0; // "no glyph" } } static int builtin_glyph(struct font *f, uint32 cp, struct glyph *out) { uint32 idx = to_cp437(cp); if (idx == 0 && cp != 0) return -1; // caller substitutes for (int y = 0; y < BUILTIN_H; y++) { unsigned char bits = font_data[idx & 0xFF][y]; for (int x = 0; x < BUILTIN_W; x++) builtin_cov[y * BUILTIN_W + x] = (bits & (0x80 >> x)) ? 0xFF : 0x00; } out->cov = builtin_cov; out->w = BUILTIN_W; out->h = BUILTIN_H; out->bearing_x = 0; // The whole cell sits above the baseline, less two rows of descender space. // The value matters only for consistency with whatever font comes next. out->bearing_y = BUILTIN_H - 3; out->advance = BUILTIN_W; return 0; } static int builtin_height(struct font *f) { return BUILTIN_H; } static int builtin_baseline(struct font *f) { return BUILTIN_H - 3; } static const struct font_ops builtin_ops = { .glyph = builtin_glyph, .height = builtin_height, .baseline = builtin_baseline, }; static struct font builtin = { &builtin_ops, 0 }; struct font * font_builtin(void) { return &builtin; } // --- blitting -------------------------------------------------------------- // Blend `color` over the pixel at (x, y) by coverage a. // // Reads the destination, which a bitmap font never needs -- but an // anti-aliased edge is defined as a blend with what is already there, and a // renderer that cannot do it is a renderer that cannot draw a real typeface. static uint32 sur_read(struct surface *s, unsigned char *at) { switch (s->bpp) { case 24: return ((uint32)at[2] << 16) | ((uint32)at[1] << 8) | at[0]; case 16: { uint16 v = *(uint16 *)at; return (uint32)(((v >> 11) & 0x1F) << 19) | (uint32)(((v >> 5) & 0x3F) << 10) | (uint32)((v & 0x1F) << 3); } default: return *(uint32 *)at; } } static void sur_write(struct surface *s, unsigned char *at, uint32 color) { switch (s->bpp) { case 24: at[0] = color & 0xFF; at[1] = (color >> 8) & 0xFF; at[2] = (color >> 16) & 0xFF; return; case 16: *(uint16 *)at = (uint16)((((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F)); return; default: *(uint32 *)at = color; return; } } static void blend_px(struct surface *s, int x, int y, uint32 color, unsigned a) { if (x < 0 || y < 0 || x >= s->width || y >= s->height) return; unsigned char *at = (unsigned char *)s->pixels + (uint64)y * s->stride + (uint64)x * (s->bpp ? s->bpp / 8 : 4); if (a >= 255) { sur_write(s, at, color); return; } uint32 dst = sur_read(s, at); uint32 r = (((color >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255; uint32 g = (((color >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255; uint32 b = (((color ) & 0xFF) * a + ((dst ) & 0xFF) * (255 - a)) / 255; sur_write(s, at, (r << 16) | (g << 8) | b); } struct surface surface_sub(const struct surface *s, int x, int y, int w, int h) { struct surface out = *s; int bytes = s->bpp ? s->bpp / 8 : 4; out.pixels = (unsigned char *)s->pixels + (uint64)y * s->stride + (uint64)x * bytes; out.width = w; out.height = h; return out; } int text_draw_glyph(struct surface *s, struct font *f, int x, int y, uint32 cp, uint32 color) { struct glyph g; if (f->ops->glyph(f, cp, &g) != 0) { // Substitute rather than skip. A missing glyph that draws nothing makes // text silently shorter and the fault impossible to see; a visible box // says exactly what happened and where. if (f->ops->glyph(f, 0x25A1, &g) != 0 && // white square f->ops->glyph(f, '?', &g) != 0) return 0; } int ox = x + g.bearing_x; int oy = y - g.bearing_y; // bearing is measured up from the baseline for (int gy = 0; gy < g.h; gy++) for (int gx = 0; gx < g.w; gx++) { unsigned a = g.cov[gy * g.w + gx]; if (a) blend_px(s, ox + gx, oy + gy, color, a); } return g.advance; } int text_draw(struct surface *s, struct font *f, int x, int y, const char *utf8, uint32 color) { int pen = x; const char *p = utf8; while (*p) { uint32 cp = utf8_next(&p); if (cp == '\n') break; // layout is the caller's business, not ours pen += text_draw_glyph(s, f, pen, y, cp, color); } return pen - x; } int text_measure(struct font *f, const char *utf8) { int w = 0; const char *p = utf8; struct glyph g; while (*p) { uint32 cp = utf8_next(&p); if (cp == '\n') break; if (f->ops->glyph(f, cp, &g) == 0) w += g.advance; else if (f->ops->glyph(f, '?', &g) == 0) w += g.advance; } return w; }