You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The binary reader (crates/shift-backends/src/binary/reader.rs) is currently a "draw what skrifa gives us" importer. That's fine as a preview, but as an import path into an editor it silently discards most of the structure we care about. Found while dogfooding with a subset Space Grotesk VF (see the shift-dogfood repo): the TTF import can't exercise component decomposition, anchors, or variable editing because none of that data survives.
#48 already tracks the fvar slice of this. This issue is the umbrella for the rest: what a real binary import path should preserve.
What gets lost today
1. Components are flattened to contours. font_from_skrifa() (binary/reader.rs:147) draws every glyph through skrifa's OutlinePen with DrawSettings::unhinted(...) (reader.rs:179), which resolves composite glyphs into outlines. A font with 652 composites imports as 100% contours — there is nothing to decompose, and editing a base glyph no longer propagates. Instead we should read glyf composite records directly (component glyph id + transform) and build Component IR, the same shape the designspace reader produces.
2. Unencoded glyphs are skipped entirely.
The import loop iterates char_map.mappings() (reader.rs:173), so anything without a cmap entry — a.alt, .case/.sc variants, and unencoded marks used as composite bases — never imports. Should iterate the full glyph set (maxp/glyf), not the charmap.
3. Glyph names ignore the post table.
Names are synthesized as the literal character or uniXXXX (reader.rs:193). A post format 2.0 font carries production names (eacute, acutecomb) — we should use them, falling back to AGL-style names only for post format 3. Literal-character names (é) are also going to be hostile to anything keyed by glyph name (features, kerning groups, .shift round-trips).
4. All variation data is dropped.
Both metrics and outlines are read at LocationRef::default() only (reader.rs:154, reader.rs:179). #48 covers reading fvar axes; the bigger design question for a variable-first editor is what to do with gvar: reconstruct editable masters from the deltas (min/default/max per axis, plus intermediates where gvar has them), or import static-at-default and tell the user. Either is defensible — silently static is not.
5. No anchors (expected, worth documenting).
Anchors don't exist in compiled fonts; they're baked into GPOS mark-attachment at build time. Deriving anchors from GPOS mark/mkmk lookups is possible and would make imported binaries much more editable, but it's inference, not recovery — fine to declare out of scope, in which case the import UX should say so.
Scope notes
OTF/CFF has no composites at all (CFF charstrings are flat, modulo legacy seac), so item 1 is TTF/glyf-only; the CFF path stays outline-based.
Dispatch is shared: both TTF and OTF route through BytesFontAdaptor (font_loader.rs:111-112).
Once 1–4 land, a shift convert font.ttf out.shift CLI subcommand becomes worth exposing — the pieces (binary::read_font_file + ShiftSourcePackage::save_font) already exist, it's just not worth wiring up while the reader is this lossy.
Suggested order
Full glyph set + post names (2, 3) — small, unblocks everything keyed by name
Importing the shift-dogfood subset TTF (397 glyphs, 169 composites, wght 300–700) should yield: 397 glyphs including unencoded ones, eacute as a live component of e + acutecomb with production names, and either editable wght masters or an explicit "imported static at default location" notice.
Summary
The binary reader (
crates/shift-backends/src/binary/reader.rs) is currently a "draw what skrifa gives us" importer. That's fine as a preview, but as an import path into an editor it silently discards most of the structure we care about. Found while dogfooding with a subset Space Grotesk VF (see theshift-dogfoodrepo): the TTF import can't exercise component decomposition, anchors, or variable editing because none of that data survives.#48 already tracks the
fvarslice of this. This issue is the umbrella for the rest: what a real binary import path should preserve.What gets lost today
1. Components are flattened to contours.
font_from_skrifa()(binary/reader.rs:147) draws every glyph through skrifa'sOutlinePenwithDrawSettings::unhinted(...)(reader.rs:179), which resolves composite glyphs into outlines. A font with 652 composites imports as 100% contours — there is nothing to decompose, and editing a base glyph no longer propagates. Instead we should readglyfcomposite records directly (component glyph id + transform) and buildComponentIR, the same shape the designspace reader produces.2. Unencoded glyphs are skipped entirely.
The import loop iterates
char_map.mappings()(reader.rs:173), so anything without a cmap entry —a.alt,.case/.scvariants, and unencoded marks used as composite bases — never imports. Should iterate the full glyph set (maxp/glyf), not the charmap.3. Glyph names ignore the
posttable.Names are synthesized as the literal character or
uniXXXX(reader.rs:193). Apostformat 2.0 font carries production names (eacute,acutecomb) — we should use them, falling back to AGL-style names only forpostformat 3. Literal-character names (é) are also going to be hostile to anything keyed by glyph name (features, kerning groups, .shift round-trips).4. All variation data is dropped.
Both metrics and outlines are read at
LocationRef::default()only (reader.rs:154,reader.rs:179). #48 covers readingfvaraxes; the bigger design question for a variable-first editor is what to do withgvar: reconstruct editable masters from the deltas (min/default/max per axis, plus intermediates where gvar has them), or import static-at-default and tell the user. Either is defensible — silently static is not.5. No anchors (expected, worth documenting).
Anchors don't exist in compiled fonts; they're baked into GPOS mark-attachment at build time. Deriving anchors from GPOS
mark/mkmklookups is possible and would make imported binaries much more editable, but it's inference, not recovery — fine to declare out of scope, in which case the import UX should say so.Scope notes
seac), so item 1 is TTF/glyf-only; the CFF path stays outline-based.BytesFontAdaptor(font_loader.rs:111-112).shift convert font.ttf out.shiftCLI subcommand becomes worth exposing — the pieces (binary::read_font_file+ShiftSourcePackage::save_font) already exist, it's just not worth wiring up while the reader is this lossy.Suggested order
postnames (2, 3) — small, unblocks everything keyed by nameglyfcomposites asComponentIR (1)fvaraxes (TTF/OTF loader does not read fvar — variable font axes are lost #48), then the gvar/masters design decision (4)Acceptance sketch
Importing the shift-dogfood subset TTF (397 glyphs, 169 composites, wght 300–700) should yield: 397 glyphs including unencoded ones,
eacuteas a live component ofe+acutecombwith production names, and either editable wght masters or an explicit "imported static at default location" notice.