text, font, fill
@profile(t1);
import fs from "fs";
import gfx from "gfx";
import font from "font";
import fill from "fill";
import text from "text";
import io from "io";
/* What text works in: the caller's memory, no allocation inside */
struct TextMemory {
glyphs: [256]text.Glyph;
atlas: [65536]u8;
edges: [2048]fill.Edge;
order: [2048]i32;
active: [2048]i32;
cov: [512]u32;
outline: font.Outline;
filler: fill.Filler;
}
var mem: TextMemory;
fn main() -> i32 {
let bytes: string = fs.read_file("C:/Windows/Fonts/arial.ttf") or { return 1; };
let face: font.Face = font.open(bytes) or { io.println("not a font: {err}"); return 2; };
fill.init(&mem.filler, mem.edges[0..2048], mem.order[0..2048], mem.active[0..2048], mem.cov[0..512]);
var c: text.Context;
text.init(&c, &face, 24, mem.glyphs[0..256], mem.atlas[0..65536], &mem.outline, &mem.filler);
var s: gfx.Surface = gfx.new_surface(320, 64, gfx.Format.Rgbx) or { return 3; };
s.clear(#FFFFFF);
let t: text.Target = text.target(s.pixels() as *u32, s.width, s.height, s.stride, true);
let end_x: i64 = text.draw(&c, &t, 10, 40, 0x202020, 255, "Hyvää päivää, tape!");
io.println("drawn {end_x - 10} px wide; measure says {text.measure(&c, "Hyvää päivää, tape!")}");
s.free();
return 0;
}Three modules draw TrueType text: font reads the font file, fill
turns outlines into anti-aliased coverage, and text caches glyphs and
draws lines of UTF-8. They allocate nothing: you give them their memory,
so the same code runs in a program, in a kernel and on Uusi. gfx’s own
draw_text is a small bitmap font; use these for real fonts at any size.
font
| Function | Does |
|---|---|
open(bytes) -> Face or FontError | Read a TrueType file (.ttf); the Face borrows bytes, so keep them |
glyph_index(&face, code_point) -> i64 | The glyph for a code point (0: the font has none) |
advance(&face, glyph), left_bearing(&face, glyph) | Metrics in font units |
kerning(&face, left, right) | The kerning between two glyphs, font units |
scale(units, px, face.units_per_em) | Font units to pixels at px pixels per em |
outline(&face, glyph, &o) | The glyph’s contours into an Outline |
face.ascender, descender (negative) and line_gap are in font units.
OpenType fonts with CFF outlines (.otf that start OTTO) are refused
with NotTrueType; a damaged font gives an error, never a crash.
text
| Function | Does |
|---|---|
init(&c, &face, px, glyphs, atlas, &outline, &filler) | A context for face at px pixels per em, caching in glyphs and atlas |
target(pixels, width, height, stride, bgr) -> Target | Pixels to draw on |
draw(&c, &target, x, baseline_y, rgb, alpha, s) -> i64 | Draw one line of UTF-8; the x after it |
measure(&c, s) -> i64 | Its width in pixels |
draw_tracked, measure_tracked | The same with extra spacing after each glyph (26.6 pixels) |
draw_fx, measure_fx | Positions and widths in 26.6 fixed point, for a layout that adds them up |
c.ascent, c.descent and c.line_height are in pixels: the next line’s
baseline is c.line_height below. rgb is 0xRRGGBB and alpha 0 to 255.
A target clips to its clip_x, clip_y, clip_w, clip_h rectangle
(the whole surface from target). bgr: true means the pixels are
0xRRGGBB words, as a gfx.Surface and Windows bitmaps are.
Glyphs are drawn at quarter-pixel positions and kerned, so text keeps the font’s spacing. When the cache’s memory is full it starts over: give it room for the sizes you use (64 bytes a glyph record, 256 KiB of atlas for a few sizes of interface text).
One code point is one glyph: no ligatures, right-to-left text or combining marks.
fill
fill is the rasterizer under text, for any outline: lines, quadratic
and cubic curves in 26.6 fixed-point pixels (fill.ONE is one pixel, y
down), filled into an 8-bit coverage Mask with the nonzero or even-odd
rule.
| Function | Does |
|---|---|
init(&f, edges, order, active, cov) | A filler working in your arrays (a few hundred edges for a glyph) |
begin(&f, width, height) | Start a shape the size of the mask |
line, quad, cubic | Add its segments |
fill(&f, &mask, rule) | Write the coverage (0 to 255 a pixel) |
More edges than the arrays hold set f.overflow instead of writing past
them.
Last modified: