SchOS/kernel/dev/text.c
Nexoniarz d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
2026-08-09 13:47:01 +02:00

242 lines
7.2 KiB
C

// Text rendering. See include/text.h for what this replaces and why.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "text.h"
// Declared, not included. include/font.h *defines* font_data rather than
// declaring it, so including it here as well as in vga.c gives two definitions
// of the same array and the link fails. The header is generated data; leaving
// it alone and referring to the symbol is less fragile than editing it.
extern unsigned char font_data[256][16];
// --- 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 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;
uint32 *px = (uint32 *)((unsigned char *)s->pixels + (uint64)y * s->stride) + x;
if (a >= 255) {
*px = color;
return;
}
uint32 dst = *px;
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;
*px = (r << 16) | (g << 8) | b;
}
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;
}