colorfont, shape

tape
@profile(t1);
import fs from "fs";
import io from "io";
import png from "png";
import font from "font";
import fill from "fill";
import shape from "shape";
import colorfont from "colorfont";

struct Memory {
    edges: [4096]fill.Edge;
    order: [4096]i32;
    active: [4096]i32;
    cov: [1024]u32;
    outline: font.Outline;
    filler: fill.Filler;
    layers: [98304]u32;          // six layers of 128 x 128
    masks: [98304]u8;
    bitmap: [32768]u32;
    out: [16384]u32;
}

var m: Memory;

fn main() -> i32 {
    let bytes: string = fs.read_file("C:/Windows/Fonts/seguiemj.ttf") or { return 1; };
    let face: font.Face = font.open(bytes) or { return 2; };
    fill.init(&m.filler, m.edges[0..4096], m.order[0..4096], m.active[0..4096], m.cov[0..1024]);
    var p: colorfont.Painter = colorfont.painter(&m.outline, &m.filler, m.layers[0..98304], m.masks[0..98304],
                                                 16384, m.bitmap[0..32768], png.decode_argb);
    // a family: four code points and three joiners
    var family: [7]i64 = [0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466];
    var plan: shape.Plan;
    shape.plan(&plan, &face);
    var run: shape.Run;
    shape.load(&run, &face, family[0..7]);
    shape.run(&plan, &face, &run);
    io.println("{run.count} glyphs");
    for (k in 0..run.count) {
        var im: colorfont.Image;
        if (colorfont.render(&p, &face, run.glyphs[k], 48, 0x000000, m.out[0..16384], &im)) {
            io.println("  glyph {run.glyphs[k]}: {im.width} x {im.height}, offset {run.x_off[k]}, advance {run.x_adv[k]}");
        }
    }
    return 0;
}

Emoji are colour glyphs in a font, and a sequence of code points can be one glyph: 👨‍👩‍👧‍👦 is seven code points, 👍🏽 two, a flag two regional indicators, 1️⃣ three. These two modules turn them into pixels; text uses them to draw emoji in the middle of a line, and the widgets find the platform’s emoji font for every label and field. Use them directly to draw emoji yourself. Like font, fill and text, they allocate nothing: the memory is yours.

colorfont

FunctionDoes
painter(&outline, &filler, layers, masks, cap, bitmap, decode) -> PainterThe memory colour glyphs are painted in: layers and masks of cap pixels each (the largest image you will draw), a buffer for a decoded bitmap, and the function that decodes PNG
kind(&face, glyph) -> i64What colour the glyph has: colorfont.NONE, COLR1, COLR0, CBDT or SBIX
has_color(&face) -> boolWhether the font has colour glyphs at all
measure(&face, glyph, px, &im) -> boolThe image’s size and place at px pixels per em, without painting it
render(&p, &face, glyph, px, fg, out, &im) -> boolThe image into out: premultiplied 0xAARRGGBB, im.width x im.height, rows without padding
resample(src, sw, sh, dst, dw, dh)Premultiplied pixels scaled, as bitmaps are

The image’s top-left corner is im.left pixels right of the pen and im.top pixels above the baseline. fg (0xRRGGBB) is used where the font asks for the text’s colour; p.used_fg says whether it did.

The formats are the emoji fonts’: COLR version 1 (Segoe UI Emoji on Windows 11, Noto Color Emoji’s COLRv1 build: layers, solid colours, linear, radial and sweep gradients, transforms, glyph clips and all the composite modes), COLR version 0 (layers of outlines), CBDT (Noto Color Emoji on Linux) and sbix (Apple Color Emoji). CBDT and sbix glyphs are PNG images at fixed sizes, scaled to yours: averaged down, smoothly up. You pass the PNG decoder: png.decode_argb is the one to pass (pure tape, no image library); with null bitmap glyphs draw nothing (render is false) and COLR glyphs still draw. render is also false for a glyph without colour, or one larger than out or cap.

shape

FunctionDoes
plan(&p, &face)What the face needs to shape: its lookups, found once
load(&run, &face, code_points)A run of the glyphs of up to 64 code points
run(&plan, &face, &run)Substitute and position: run.glyphs[0..run.count], with run.x_adv, run.x_off, run.y_off in font units
default_ignorable(cp) -> boolWhether a font need not draw cp (a joiner, a variation selector, a tag)

A glyph goes at the pen plus its offset (y up), and the pen moves on by its advance. The OpenType fonts (Segoe UI Emoji, Noto) shape through their GSUB and GPOS tables, Apple’s through its morx table: the same glyphs HarfBuzz gives. In Segoe UI Emoji a family is four glyphs, the children hung on the parents with no advance of their own (the example prints them); in Noto and Apple’s it is one. Joiners and variation selectors a font doesn’t need are dropped. Segoe UI Emoji has no flags: their regional indicators stay two letters, as Windows draws them.

Only emoji sequences are shaped this way: Latin text keeps text’s kerning, and ligatures of text, right-to-left scripts and positioned combining marks are not done.

Last modified: