COOLJAPAN
← All posts

OxiFont 0.2.2 Released — Static-Instance Variable Fonts, Windows .ttc Collections, and a PDF-Ready Glyph-ID Map

OxiFont 0.2.2 ships oxifont-subset::instance() for pinning a variable font to one design location, a drop_variations subset option, TrueType Collection (.ttc) face-index support that unlocks stock Windows CJK fonts like msgothic.ttc, a public SubsetGidMap for PDF CIDFont embedding, and full GSUB/GPOS contextual-lookup remapping — the sovereign Pure Rust font layer for the COOLJAPAN ecosystem.

release oxifont pure-rust cooljapan noffi fonts typography opentype variable-fonts

Every CJK font Windows ships in the box — msgothic.ttc, meiryo.ttc, simsun.ttc, msjh.ttc — is a TrueType Collection. OxiFont’s subsetter refused to open a single one of them until today.

Today we released OxiFont 0.2.2 — adding oxifont-subset::instance() to static-instance a variable font at a pinned design location, a drop_variations subset option, TrueType Collection (.ttc) face-index support across oxifont-core and oxifont-subset, a public SubsetGidMap old↔new glyph-ID mapping for PDF CIDFont embedding, and full GSUB/GPOS contextual-lookup remapping in all three lookup formats — plus a .notdef-retention correctness fix, a DSIG stripping fix, and an sfnt-magic fix for CFF-flavoured subsets.

No FreeType. No fontconfig. No hand-rolled variable-font math trusting a gvar tuple stream it hasn’t bounds-checked. oxifont-subset runs entirely under #![forbid(unsafe_code)] — the same guarantee oxifont-core already carries — and the new instancing pipeline bounds every allocation by an already-validated length, because it parses attacker-supplied web-font bytes on some call paths. It compiles to a single static binary and runs anywhere Rust does — no C toolchain, no -sys crate, no build script vendoring a copy of FreeType.

Why OxiFont 0.2.2 is a game changer

A font subsetter that’s correct on a plain static TTF but stops there still leaves real fonts unusable:

OxiFont 0.2.2 ends all of that:

Technical Deep Dive: instancing, collections, and the CID map

  1. Static instancing (oxifont-subset::instance, five sub-modules: coords.rs, tuples.rs, ivs.rs, metrics.rs, outline.rs). The whole coordinate pipeline runs in 16.16 fixed point with FreeType’s rounded division and converts to F2Dot14 once at the end; outlines come from a full gvar tuple walk (both offset formats, shared and embedded peak tuples, intermediate regions, packed point numbers, packed deltas, IUP against the default outline), and advances/side bearings are rebuilt from the four phantom points rather than inherited, so even empty glyphs with varying advances are covered.
  2. TrueType Collection support (oxifont-core::sfnt). TTC_MAGIC, face_count, face_offset, and SfntTableMap::parse_face validate the collection header — major version, non-zero numFonts, an offset table that actually fits — before trusting any offset in it, then validate the SFNT header at the selected offset exactly as offset 0 is validated. An out-of-range index is SfntError::FaceIndexOutOfRange, never a panic.
  3. The glyph-ID map (oxifont-subset::gid_map). SubsetGidMap renumbers retained glyphs densely from 0 in ascending old-GID order after composite-component closure — new_gid, old_gid, contains_old_gid, new_to_old, and iter() expose the mapping PdfFontSubsetter::finalize_mapped needs to back a CID font.
  4. Contextual-lookup rewriting (otl_context). Parsed contextual subtables stay in an intermediate form until the final old→new lookup-index map is known, so every seqLookupRecord is written with its renumbered lookupListIndex — records whose target lookup was dropped are pruned, so there is no longer any path that can emit a stale index.

Getting Started

cargo add oxifont --features subset,bundled-noto

Pin a variable font to a specific weight, then subset the pinned instance to a static, variation-free font:

use std::collections::BTreeSet;

use oxifont::subset::{instance, subset_font_with_options, SubsetError, SubsetOptions};

fn build_static_bold_subset(variable_font_bytes: &[u8]) -> Result<Vec<u8>, SubsetError> {
    // Pin the variable font to wght=700 at face 0 -- glyph IDs are untouched,
    // so cmap/GSUB/GPOS/GDEF/kern/COLR/MATH/sbix all carry over verbatim.
    let pinned = instance(variable_font_bytes, 0, &[(*b"wght", 700.0)])?;

    // Now subset the pinned instance and drop the vestigial variation tables.
    let codepoints: BTreeSet<char> = "Hello, OxiFont!".chars().collect();
    let opts = SubsetOptions::default().drop_variations(true);
    let (subsetted, _stats) = subset_font_with_options(&pinned, &codepoints, &opts)?;
    Ok(subsetted)
}

Subset one face out of a Windows .ttc collection:

use std::collections::BTreeSet;

use oxifont::subset::{face_count, subset_font_at_face, SubsetError};

fn subset_msgothic(ttc_bytes: &[u8]) -> Result<Vec<u8>, SubsetError> {
    let faces = face_count(ttc_bytes)?; // msgothic.ttc reports more than one face
    println!("{faces} face(s) in this collection");

    let codepoints: BTreeSet<char> = "こんにちは".chars().collect();
    subset_font_at_face(ttc_bytes, 0, &codepoints) // face 0 == MS Gothic
}

crates/oxifont/examples/ also gained four runnable walkthroughs this release — discover_query_match (default features), parse_metrics_outline, subset_woff2_roundtrip, and hinting_at_ppem (the latter two need bundled-noto plus their respective feature):

cargo run -p oxifont --example subset_woff2_roundtrip --features subset,woff2,bundled-noto
cargo run -p oxifont --example hinting_at_ppem --features hinting,bundled-noto

What’s New in 0.2.2

Tips

This is the foundation

OxiFont is the font foundation under OxiText — which pulls in oxifont, oxifont-core, oxifont-parser, oxifont-bundled (with the bundled-noto feature), oxifont-subset, and oxifont-adapter-native for glyph metrics and layout — oxigaf (PDF CFF/Type-0 font embedding, the direct beneficiary of this release’s SubsetGidMap), oximedia (subtitle and OSD rendering), oxigdal-symbology (map labels), oxiphoton (image text overlay), and OxiUI (GUI text rendering, via oxifont). Outside the COOLJAPAN workspace, the tiktok/kitasan-shorts project depends on oxifont directly. None of them need a code change to pick up this release beyond a version bump — except a caller that exhaustively destructures or struct-literal-constructs PdfSubsetResult (it gained the gid_map field) or builds SubsetOptions from a struct literal instead of SubsetOptions::default() plus the builder methods (it is now #[non_exhaustive]).

Repository: https://github.com/cool-japan/oxifont

Star the repo if you want a subsetter that can actually open the CJK fonts already sitting on the Windows machines your users run.

The era of a Pure Rust font stack that can parse a .ttc header but not subset a single face out of it is over. Pure Rust typography — sovereign, safe, and FFI-free.

KitaSan at COOLJAPAN OÜ August 6, 2026

↑ Back to all posts