Skip to content
LazyGreed edited this page Nov 11, 2026 · 3 revisions

Fonts

Legacy generation draws Khmer and English text from separate pools. Opt-in --multilingual searches all recursively discovered font directories or the explicit --font-pool NAME. Arbitrary top-level directories form named pools. Whole-line coverage is checked before selection; the new mode requires RAQM and does not use legacy Khmer/ASCII spans for general bidi text. See the multilingual capability matrix.

Directory layout

fonts/
├── khmer/     <- .ttf / .otf / .ttc / .woff / .woff2 Khmer fonts
├── english/   <- .ttf / .otf / .ttc / .woff / .woff2 English fonts
└── *.ttf      <- files directly here join BOTH pools as fallbacks

The root directory is set with --fonts DIR (default fonts/) or the YAML key fonts. The khmer/ and english/ subdirectories are scanned recursively, so you can organise fonts into further subfolders.

Supported formats

Extension Notes
.ttf TrueType — the common case
.otf OpenType
.ttc, .otc Font collections; face index 0 for both rendering and cmap coverage
.woff Web Open Font Format
.woff2 Web Open Font Format 2

Script selection

For each text line the generator decides which pool to draw from:

  • Text containing Khmer characters → the Khmer pool.
  • Pure Latin text → the English pool.
  • Mixed Khmer/English → the pool for the dominant script, plus per-span font switching when --mixed-font-prob is non-zero (see below).

Fonts sitting directly in fonts/ are always eligible, which makes them a useful fallback when one pool is thin.

Glyph coverage

Not every font covers every glyph. Before using a font the renderer checks that it can actually draw the text via ImageRenderer._is_text_supported:

Real font files use a shared fontTools Unicode cmap policy with or without Rust, cached by path and collection face index (bounded at 256 faces). Shaping/bidi controls and variation selectors need no standalone glyph but remain in logical text. Other controls fail closed; errors do not silently assert coverage. Non-file font objects retain a raster signature fallback. See the multilingual guide for the explicit control list and variation-sequence limitation.

Legacy selection still retries up to --retry-limit N (default 10). Multilingual selection first enumerates eligible whole-line faces, independently of random retry luck; unsupported lines produce a preflight error. Cmap coverage alone cannot certify linguistic correctness or cluster shaping.

Width selection and font sizes

Fonts are registered at a set of standard sizes (28, 32, 36, 40, 44, 48). Actual FreeType faces are loaded lazily on first use and kept in a bounded LRU cache (1024 entries) so a long run does not pin every face it has ever touched in memory.

With --font-size-mode proportional, sizes are instead derived per image from the sampled canvas height. See Variable Line Height.

Bold and italic variants

Bold and italic text decorations use real variant fonts, not synthesised weight/slant. A font file is tagged by inspecting its filename stem and its internal style name:

  • bold in the name (or style name) → tagged bold
  • italic or oblique in the name (or style name) → tagged italic

Selection rules for --text-deco-bold-prob / --text-deco-italic-prob:

Requested Behaviour
bold any font tagged bold (a bold-italic font qualifies)
italic any font tagged italic/oblique
bold and italic prefer a font tagged both, else fall back to a bold font

If no matching variant exists in the pool, the decoration is skipped for that line rather than faking it. So --text-deco-bold-prob 0.5 on a pool with no bold fonts simply produces no bold lines. Ship real variant files (e.g. NotoSansKhmer-Bold.ttf, NotoSansKhmer-Italic.ttf) if you want these decorations to fire.

Mixed-script rendering

--mixed-font-prob F (default 0.0) sets the probability that a line containing both scripts is rendered with per-span font selection: each contiguous run of Khmer or Latin text gets a font drawn from its own pool. At 0.0 the whole line uses a single font chosen for the dominant script.

khocr-gen generate --corpus corpus/corpus.txt --fonts fonts --mixed-font-prob 0.6

Span splitting is accelerated in Rust (_rust_accel.split_text_spans) with an equivalent pure-Python fallback. See Rust Acceleration.

--font-mode

Value Behaviour
random (default) Render --copies augmented copies per text line, each with a randomly chosen font
all Render one image per font per line

font-mode=all touches every font at every standard size, which is the heaviest path for the font cache. Expect a longer warm-up and more memory than random.

Practical advice

  • More fonts beat more copies. A pool of 3–5 Khmer fonts gives far more useful variation than raising --copies on a single font.
  • Include one broadly-covering Khmer font as a fallback. Khmer coverage varies a lot between fonts, and missing glyphs cause retries and skipped lines.
  • Check what you have. Run a tiny generation with --count-only first, then generate a few hundred samples and inspect them with khocr-gen view.
  • Add variant files deliberately if you plan to use bold/italic decorations.

See also

Clone this wiki locally