colorfont, shape
@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
| Function | Does |
|---|---|
painter(&outline, &filler, layers, masks, cap, bitmap, decode) -> Painter | The 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) -> i64 | What colour the glyph has: colorfont.NONE, COLR1, COLR0, CBDT or SBIX |
has_color(&face) -> bool | Whether the font has colour glyphs at all |
measure(&face, glyph, px, &im) -> bool | The image’s size and place at px pixels per em, without painting it |
render(&p, &face, glyph, px, fg, out, &im) -> bool | The 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
| Function | Does |
|---|---|
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) -> bool | Whether 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: