Skip to content
render

render

Mojo module 🡭

render

Text rendering: font matching from font_discovery.mojo, glyph outlines and metrics from ttf.mojo via glyph_outline.mojo, and rasterization from fill_path_aa (path.mojo) under FillRule.NONZERO, the rule TrueType outlines are drawn with, which also puts every glyph on the exact-area rasterizer (canvas.aa_area). The glyph path is unhinted. Glyphs fill through the same fill_path_aa every other shape uses, so translucent text composites through set_pixel like any other fill.

Unrotated text goes through a glyph mask cache on the FontCache: each (face, size, glyph, sub-pixel offset) is rasterized once, as the sub-sample counts fill_path_aa’s sweep computes, and every later occurrence composites the cached counts through the sweep’s own alpha arithmetic (_composite_glyph_mask), so the pixels are the ones a direct fill writes. The sub-pixel offset is the glyph origin’s fractional part rounded to 1/64 px (_SUBPIXEL_STEPS): whole-pixel anchors are unchanged, and a fractional anchor places each glyph within 1/128 px of where the unrounded outline would go, well inside the sweep’s 1/4 px sample spacing. Rotated text still fills each glyph’s outline directly (#170).

draw_text’s (x, y) is the baseline’s left end for LEFT alignment, not a top-left corner like fill_rect’s. CENTER/RIGHT shift each line horizontally against that same anchor.

Rotation and multi-line share one code path with the single-line case. _layout_block and draw_text’s render pass both walk each line’s glyphs from a shared anchor-relative local layout. A rotated block, and a block under a canvas transform that is a similarity, places each glyph by mapping its pen position through one matrix and compositing a mask cached at that orientation and scale (_draw_block_similarity); a non-uniform scale or skew fills each mapped outline directly (_draw_block_direct). At rotation=0.0 with one line the layout’s cos=1/sin=0 leaves every point unchanged.

One shaping step (_shape_line) turns each line’s text into the glyph sequence every pass then walks, which is what keeps measure_text and draw_text from disagreeing. It splits the line into bidi runs (bidi.visual_runs), shapes each run in logical order, and concatenates the runs left to right with a right-to-left run’s glyphs reversed. Shaping per run in logical order is what the order has to be: joining and ligature formation are defined between the characters typed either side of a letter, which in a right-to-left run are not the ones drawn either side of it.

Shaping a run maps each character through cmap and applies the font’s GSUB features for the run’s script (ttf.mojo). A Latin run gets ccmp and liga, so “f” and “i” become the one “fi” glyph a font that has it draws. An Arabic run additionally classifies each letter’s contextual form from its logical neighbors (joining.mojo) and enables that one of isol/init/medi/fina on that one glyph, then runs rlig, liga and calt; so “بسم” draws as initial beh, medial seen, final meem rather than three isolated letters. ligatures=False skips substitution entirely and lays out one glyph per character, which for Arabic is the isolated forms – one flag rather than two, because joining and ligatures are the same machinery and an Arabic font’s rlig is not optional once the letters are joined.

Adjacent glyphs kern against each other in the same per-run pass, through the font’s GPOS pair adjustment or kern table (_apply_run_kerning). Kerning is between the substituted glyphs, the order a shaper applies the two tables in: a pair adjustment written for “f” does not apply across an “fi” ligature, because the ligature is what sits on the line. The pair is looked up in logical order, since that is the order a font states an adjustment for, and the result rides on the glyph a pass reaches second (_ShapedGlyph.kern_before) – for a right-to-left run that is the first of the two logically, because reversing the run swaps which one is drawn second. Kerning stops at a run boundary. The adjustment moves the pen between two glyphs and nothing else, so the glyph mask cache – which holds a glyph’s coverage, not its place on the line – is untouched by it. kerning=False leaves every kern_before at zero, restoring the plain sum of hmtx advances.

Shaping only substitutes among characters the primary face has glyphs for, since a ligature is defined over glyphs of one font. Everything else goes through font fallback (_resolve_glyph): a codepoint the requested family has no real glyph for (glyph index 0, “.notdef”) resolves through resolve_font_file_for_char. This package bundles no fonts, so a CJK/Cyrillic/symbol character requested under a Latin-only family renders through whatever installed font has it; one missing everywhere degrades to the unconstrained best match. Fallback faces cache alongside the primary face.

Two entry points draw the same layout differently. stroke_text hands each glyph’s outline to stroke_path_aa instead of filling it, which is a label that reads over a busy background. draw_text_on_path puts the baseline on a curve: the string is laid out straight, then each glyph is placed at its own arc length along the path (_ArcLengthPath) and turned to the tangent there. Neither is a different layout – both walk what _shape_line produced, so text on a curve kerns and ligates exactly as straight text does.

FontSlant/FontWeight come from font_discovery.mojo and TextAlign from text_align.mojo, both re-exported here.

Structs

  • TextMetrics: A single line’s measured size. width/height are its tight ink bounding box; advance is the logical cursor-advance distance, which differs whenever leading/trailing whitespace contributes advance but no ink. TextAlign’s CENTER/RIGHT use advance.
  • TextBlockBounds: The axis-aligned bounding box draw_text’s ink would occupy for a given text/rotation/align/font, anchor-relative: x/y are the top-left corner relative to draw_text’s (x, y) anchor, and either can be negative (RIGHT-aligned text extends left of the anchor; a label rotated upward extends above it). x + width/y + height is the bottom-right corner.
  • TextLayout: Text already shaped, kerned, line-broken and aligned, ready to be measured or drawn without doing that work again.

Functions