Skip to content

Add load_with_options: partial loading and per-source failure reporting - #32

Closed
tomconroy wants to merge 1 commit into
simoncozens:mainfrom
tomconroy:silent-source-drop-report
Closed

Add load_with_options: partial loading and per-source failure reporting#32
tomconroy wants to merge 1 commit into
simoncozens:mainfrom
tomconroy:silent-source-drop-report

Conversation

@tomconroy

@tomconroy tomconroy commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Reworked to implement the load_with_options API you sketched in #33, folding in the source-failure reporting this PR originally proposed.

API

pub struct LoadOptions {
    pub load_axes: bool,     // all default to true
    pub load_masters: bool,
    pub load_glyphs: bool,
    pub load_layers: bool,
}

pub struct LoadResult {
    pub font: Font,
    pub source_failures: Vec<SourceLoadFailure>, // { filename, error }
}

pub fn load_with_options(filename: impl Into<PathBuf>, options: &LoadOptions)
    -> Result<LoadResult, BabelfontError>;

load() is now a thin façade over load_with_options and behaves exactly as before (including silently skipping unloadable designspace sources).

Semantics

  • load_axes: false — no axes on the returned font.
  • load_masters: false — masters are bare records of what the file declares (name, id, location; for designspace sources also filename/style name), and designspace source UFOs are not opened at all. Combined with load_glyphs: false this parses just the designspace XML: your "axes/masters/instances without the sources" case. Implies no layers, since layers belong to masters.
  • load_glyphs: false — no glyphs.
  • load_layers: false — glyphs are layer-less stubs. For UFO/designspace the names come from the default layer's contents.plist, so no .glif file is parsed anywhere (norad DataRequest::default().layers(false)); lib-derived categories, production names and export flags are kept, codepoints aren't (they live in the glifs). Glyphs-format stubs keep codepoints and kern-group membership.

Implementation notes

  • UFO, designspace, and Glyphs convertors skip the ingestion work natively. The remaining formats (fontra, vfj, sfd, rcjk, vfb, ttf, .babelfont) load fully and are filtered afterwards, so every format honours the options with the same result shape; native skipping can be added per-format later without an API change.
  • Designspace: the default source is only opened when masters or glyphs are requested. Sources that fail to load land in source_failures instead of vanishing in the filter_map; each failure leaves the font without that master.
  • Glyphs format: masters and axes are always converted internally (axis min/max/default interpretation needs the master locations) and the unrequested parts are dropped at the end, so load_masters: false still returns correctly-interpreted axes.

Numbers

Measured against googlefonts/roboto-serif's RobotoSerif.designspace (81 sources, release build) — the case that motivated #33:

options time result
full load 5.0 s 77 masters, 1264 glyphs with layers
load_layers: false 460 ms 77 masters with metrics+kerning, 1264 glyph stubs
load_masters/glyphs: false 0.6 ms 81 bare master records, axes, instances

(The remaining 4 of the 81 sources fail on an unrelated norad fontinfo issue — linebender/norad#414 — and are now visible in source_failures rather than silently missing.)

🤖 Generated with Claude Code

@tomconroy tomconroy mentioned this pull request Jul 13, 2026
Implements the API sketched in simoncozens#33: a LoadOptions struct with
load_axes / load_masters / load_glyphs / load_layers flags, all
defaulting to true, and a top-level load_with_options returning
LoadResult { font, source_failures }. load() is now a thin façade over
it and behaves exactly as before.

The UFO, designspace, and Glyphs convertors skip the ingestion work for
parts that are not requested; the remaining formats are loaded in full
and then filtered, so the result has the same shape for every format.

- load_layers: false parses no .glif file at all (norad's
  DataRequest::default().layers(false)); UFO/designspace glyphs become
  name-only stubs read from the default layer's contents.plist, keeping
  lib-derived categories, production names, and export flags.
- load_masters: false does not open designspace source UFOs at all;
  masters become bare records of what the document declares (name,
  location, filename).
- Designspace <source> elements that fail to load are reported in
  LoadResult::source_failures instead of being silently dropped.

Measured on googlefonts/roboto-serif (RobotoSerif.designspace,
81 sources, release build): full load 5.0s; load_layers: false 460ms;
document only (load_masters/glyphs: false) 0.6ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tomconroy
tomconroy force-pushed the silent-source-drop-report branch from 3920c63 to 057528d Compare July 13, 2026 12:38
@tomconroy tomconroy changed the title designspace: report sources that fail to load Add load_with_options: partial loading and per-source failure reporting Jul 13, 2026
Comment thread babelfont/Cargo.toml
chrono = { version = "0.4.3", features = ["serde"] }
glyphslib = { version = "0.2.6", optional = true }
norad = { version = "0.17.0", features = ["kurbo"], optional = true }
plist = { version = "1", optional = true }

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this dependency is actually used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in the new ufo::converters::default_layer_glyph_names – added so that we can still output the glyph names without any layers:

// full load                              // load_layers: false
{                                         {
  "name": "A",                              "name": "A",
  "category": "Base",                       "category": "Base"
  "codepoints": [65],                     }
  "layers": [ /* 2 layers */ ]
}

@tomconroy tomconroy closed this Jul 19, 2026
@tomconroy
tomconroy deleted the silent-source-drop-report branch July 19, 2026 10:08
@tomconroy

Copy link
Copy Markdown
Contributor Author

See #33 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants