Font Headers

tools/ttir.cpp is the host-side compiler that turns committed TTF/OTF font files into freestanding C++ headers. This page explains how to build and run the tool, how the generated .hpp file is arranged, and what each command-line option means.

What ttir Does

ttir runs on the host machine. It reads one or more font files, imports the font tables it needs, selects a subset of Unicode codepoints, builds alpha atlas data, validates the result, and writes a C++ header that can be included by FALL applications.

The runtime type declarations live in src/a_font.hpp. The parser, validator, subset planner, atlas builder, and generated header writer live in tools/ttir.cpp.

This page does not describe text rendering. Rendering is being moved toward level g, which is intended to become the cross-platform graphics abstraction for all FALL targets, including embedded targets.

Committed Fonts

The repository already has Noto font inputs under assets/:

assets/NotoSans-Regular.ttf
assets/NotoSansJP-Regular.ttf
assets/NotoSansKR-Regular.ttf
assets/NotoSansSC-Regular.ttf
assets/NotoSansTC-Regular.ttf
assets/NotoSansHK-Regular.ttf

The generated headers currently used by the tree are:

assets/noto-regular.hpp
assets/noto.hpp

Build ttir

Build only the host-side font compiler:

make ttir

The Makefile configures the Linux build directory and builds the ttir target. The resulting binary is:

build-relbare/ttir

Print the current command help:

./build-relbare/ttir --help

Print version information:

./build-relbare/ttir --version

Inspect A Font

If no -o output path is passed, ttir does not generate a header. It prints information about the input font instead.

./build-relbare/ttir assets/NotoSans-Regular.ttf

The info output reports the inferred face, cmap codepoint count, glyph count, non-empty glyph count, kerning availability, and curated script coverage.

Generate A Small Header

Generate a regular Noto header with ASCII, Latin Extended, and Cyrillic coverage:

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o assets/noto-regular.hpp \
    --namespace assets \
    --symbol noto_regular \
    --scripts ascii,latinext,cyrillic \
    --pixel-height 20 \
    --supersample 1

The same command is already wrapped by the Makefile:

make noto-regular

Use make noto-regular when you do not want to remember the ttir arguments for the lightweight single-font asset.

Generate The Noto Collection

Generate the larger collection from all committed Noto fonts:

./build-relbare/ttir \
    assets/NotoSans-Regular.ttf \
    assets/NotoSansJP-Regular.ttf \
    assets/NotoSansSC-Regular.ttf \
    assets/NotoSansTC-Regular.ttf \
    assets/NotoSansHK-Regular.ttf \
    assets/NotoSansKR-Regular.ttf \
    -o assets/noto.hpp \
    --namespace assets \
    --symbol noto \
    --scripts ascii,latinext,greek,cyrillic,jp,kr,sc,tc,hk \
    --pixel-height 8 \
    --supersample 1

The same collection command is wrapped by:

make noto

Multi-font generation requires --scripts. That prevents accidental full CJK atlas generation.

Help Commands

ttir --help lists four usage forms:

Options

-o <path> selects the generated header path. Without it, ttir is in information mode.

./build-relbare/ttir assets/NotoSans-Regular.ttf -o /tmp/noto_ascii.hpp --ascii

--namespace <name> selects the C++ namespace for generated symbols. The default is assets.

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o /tmp/ui_font.hpp \
    --namespace ui_assets \
    --symbol ui \
    --ascii

--symbol <prefix> selects the generated symbol prefix. For example, --symbol ui produces names such as ui_glyphs, ui_cmap, ui_atlases, and ui_asset.

--face <JP|KR|SC|TC|HK> assigns a CJK face tag to a generated single-font asset. The regular face is used when no CJK face is selected.

./build-relbare/ttir assets/NotoSansJP-Regular.ttf \
    -o /tmp/noto_jp.hpp \
    --symbol noto_jp \
    --face JP \
    --scripts jp

--scripts <list> selects named script coverage. The current names are ascii, latinext, greek, cyrillic, jp, kr, sc, tc, and hk.

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o /tmp/noto_text.hpp \
    --scripts ascii,latinext,cyrillic

--literal <utf8> builds a subset closed over the exact UTF-8 text you pass. It is useful for tiny firmware labels.

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o /tmp/boot_label.hpp \
    --symbol boot_label \
    --literal "Boot OK"

--ascii is shorthand for printable ASCII, U+0020 through U+007E.

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o /tmp/noto_ascii.hpp \
    --ascii

--full emits the chosen cmap. It is the default when no subset option is set. Be careful with large CJK fonts because this can produce very large headers.

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o /tmp/noto_regular_full.hpp \
    --full

--pixel-height <n> selects the alpha atlas pixel height. Valid values are 1 through 512. The default is 32.

--padding <n> selects atlas glyph padding in pixels. Valid values are 0 through 64. The default is 1.

--supersample <n> selects alpha raster supersampling. Valid values are 1, 2, 4, and 8. The default is 4.

./build-relbare/ttir assets/NotoSans-Regular.ttf \
    -o /tmp/noto_24px.hpp \
    --ascii \
    --pixel-height 24 \
    --padding 1 \
    --supersample 2

Generated Header Shape

A generated header starts with #pragma once, includes a_font.hpp, and opens the selected namespace.

#pragma once

#include "a_font.hpp"

namespace assets {
    // generated symbols live here
}

The header then defines static data tables. The exact symbol names use the selected --symbol prefix.

After the tables, the header defines names and a manifest:

static constexpr char noto_regular_family_name[] = "noto_regular";
static constexpr char noto_regular_style_name[] = "regular";

static constexpr a::font::asset_manifest noto_regular_manifest{
    a::font::ttir_schema_version,
    /* generator_version */ 1u,
    /* hashes */ ...,
    a::view<const char>{noto_regular_family_name},
    a::view<const char>{noto_regular_style_name},
    a::font::face_kind::regular
};

The main public value for a single generated font is the asset view. For --symbol noto_regular, the symbol is noto_regular_asset.

static constexpr a::font::asset_view noto_regular_asset{
    a::font::metrics{...},
    a::font::cmap_storage::sorted_array,
    a::view<const a::font::glyph_metric>{noto_regular_glyphs, ...},
    a::view<const a::font::cmap_entry>{noto_regular_cmap, ...},
    a::view<const a::font::kern_entry>{noto_regular_kerning, ...},
    a::view<const a::font::atlas_view>{noto_regular_atlases, ...},
    &noto_regular_manifest
};

The generated header validates itself at compile time:

static_assert(a::font::validate_asset(noto_regular_asset) == a::font::error::ok,
              "generated font asset failed validation");

Generated Bundle Metadata

The header also emits tag types and an a::bundle specification that describes the generated static data as deterministic storage.

struct noto_regular_glyphs_tag final {};
struct noto_regular_cmap_tag final {};
struct noto_regular_atlas_pixels_tag final {};
struct noto_regular_asset_tag final {};

struct noto_regular_bundle_spec final {
    using permanent = a::resources<
        a::array<a::font::glyph_metric, ..., noto_regular_glyphs_tag>,
        a::array<a::font::cmap_entry, ..., noto_regular_cmap_tag>,
        a::buffer<noto_regular_atlas_pixels_tag, ...>,
        a::object<a::font::asset_view, noto_regular_asset_tag>
    >;
};

using noto_regular_bundle = a::bundle<noto_regular_bundle_spec>;

The generated size constants let firmware code inspect the amount of read-only data and the equivalent bundle footprint.

static constexpr a::usize noto_regular_rodata_bytes = ...;
static constexpr a::usize noto_regular_bundle_bytes = noto_regular_bundle::size_bytes();
static constexpr a::usize noto_regular_bundle_alignment = noto_regular_bundle::alignment_bytes();

Collections

When multiple font files are passed, the generated header emits one asset per face and then a collection view. make noto currently produces faces for regular, JP, SC, TC, HK, and KR.

static constexpr a::font::font_collection_face noto_faces[] = {
    a::font::font_collection_face{a::font::face_kind::regular, &noto_regular_asset},
    a::font::font_collection_face{a::font::face_kind::jp, &noto_jp_asset},
    a::font::font_collection_face{a::font::face_kind::sc, &noto_sc_asset},
    a::font::font_collection_face{a::font::face_kind::tc, &noto_tc_asset},
    a::font::font_collection_face{a::font::face_kind::hk, &noto_hk_asset},
    a::font::font_collection_face{a::font::face_kind::kr, &noto_kr_asset}
};

static constexpr a::font::font_collection_view noto_collection{
    a::view<const a::font::font_collection_face>{noto_faces, 6u}
};

Collections are also validated at compile time:

static_assert(a::font::validate_collection(noto_collection) == a::font::error::ok,
              "generated font collection failed validation");