// Host harness for kernel/dev/ttf.c. // // A rasteriser is checked by looking at it. This renders a string with the real // font and writes a PGM, which is the smallest image format that needs no // compression -- the point is the glyphs, not the container. // // build/tools/ttftest "text" out.pgm #include #include #include #define TTF_HOST #include "../kernel/dev/ttf.c" int ttf_render(struct ttf *, uint32, struct glyph *, unsigned char *, int, int *, int *); // The console speaks UTF-8, so the harness has to as well -- otherwise the // only glyphs it can ever ask for are ASCII, and ASCII is entirely made of // simple glyphs. Every composite in a Latin font is an accented letter, which // is to say: the byte-at-a-time version could not reach the code path that // mattered. // // text.h declares utf8_next and kernel/dev/text.c defines it, but that file // pulls in the kernel headers and cannot be compiled on the host. Define the // symbol here instead: a second decoder is the cost of the harness existing // at all, and the two agree on the only thing that matters here, which is // always advancing. uint32 utf8_next(const char **pp) { const unsigned char *p = (const unsigned char *)*pp; uint32 c = *p++; int extra = 0; if ((c & 0xe0) == 0xc0) { c &= 0x1f; extra = 1; } else if ((c & 0xf0) == 0xe0) { c &= 0x0f; extra = 2; } else if ((c & 0xf8) == 0xf0) { c &= 0x07; extra = 3; } while (extra-- > 0 && (*p & 0xc0) == 0x80) c = (c << 6) | (*p++ & 0x3f); *pp = (const char *)p; return c; } int main(int argc, char **argv) { if (argc < 5) { fprintf(stderr, "usage: ttftest font.ttf px \"text\" out.pgm\n"); return 1; } const char *path = argv[1]; int px = atoi(argv[2]); const char *text = argv[3]; FILE *fp = fopen(path, "rb"); if (!fp) { perror(path); return 1; } fseek(fp, 0, SEEK_END); long n = ftell(fp); fseek(fp, 0, SEEK_SET); unsigned char *buf = malloc(n); if (fread(buf, 1, n, fp) != (size_t)n) { fprintf(stderr, "short read\n"); return 1; } fclose(fp); struct ttf f; if (ttf_open(&f, buf, (uint32)n, px) != 0) { fprintf(stderr, "not a TrueType font this can read\n"); return 1; } printf("unitsPerEm %d, %d glyphs, ascent %d descent %d, %s loca\n", f.units_per_em, f.num_glyphs, f.ascent, f.descent, f.long_loca ? "long" : "short"); struct font *fo = ttf_font(&f); int H = fo->ops->height(fo) + px; // room for descenders and padding int base = fo->ops->baseline(fo) + px / 2; int W = 16; for (const char *p = text; *p; ) { utf8_next(&p); W += px; } // generous unsigned char *img = calloc(W * H, 1); int pen = 8, drawn = 0, missing = 0; for (const char *p = text; *p; ) { uint32 cp = utf8_next(&p); struct glyph g; // Through the provider, not ttf_render, so the glyph cache is on the // path being tested rather than bypassed by the harness. if (fo->ops->glyph(fo, cp, &g) != 0) { missing++; pen += px / 2; continue; } for (int gy = 0; gy < g.h; gy++) for (int gx = 0; gx < g.w; gx++) { int X = pen + g.bearing_x + gx; int Y = base - g.bearing_y + gy; if (X < 0 || Y < 0 || X >= W || Y >= H) continue; int v = img[Y * W + X] + g.cov[gy * g.w + gx]; img[Y * W + X] = v > 255 ? 255 : v; } pen += g.advance; drawn++; } // White on black inverted to black on white, which is how text is read. for (int i = 0; i < W * H; i++) img[i] = 255 - img[i]; FILE *o = fopen(argv[4], "wb"); fprintf(o, "P5\n%d %d\n255\n", W, H); fwrite(img, 1, W * H, o); fclose(o); printf("%d glyphs drawn, %d missing, %d px advance, image %dx%d -> %s\n", drawn, missing, pen - 8, W, H, argv[4]); return drawn ? 0 : 1; }