Files
craft-libs/crates/font-manager
aiongg 5120c0ce2d Font store and Font Manager: review fixes
- FontStore::remove_files only touches names the family's manifest entry
  lists and that are safe file names.
- A user source's folder carries a hash of its full ID, so sources whose
  IDs slug alike don't share a folder.
- The download dialog and the sidebar note name the family's own source,
  and the server for a source the user added.
- The face cache takes out only faces the last frame didn't draw.
- The catalog test lists missing translations instead of failing.
- ATTRIBUTION lists the three bundled craft-font-index files.
2026-10-25 19:41:46 +00:00
..

craft-font-manager

Craft Font Manager as a library: every font the suite's apps can use, and the font collections to add to them: Google Fonts, Noto, and index URLs the user adds. ArtCraft Launcher shows it as its Fonts tab; the standalone app (aiongg/craft-font-manager) shows it as its window.

Part Feature What it does
filter always Search over the index: family names ignoring case and accents, category, language (English name, autonym or id) and script
index always The pinned built-in indexes (Google Fonts and Noto) compiled into craft-font-store (bundled), each family's source (source_label: its name and whether it is curated), and the categories
installed always The installed fonts craft-font-catalog sees, grouped by family and source
store always Installer, what the interface needs from the font store: the download consent, install, remove, downloads for a preview, the indexes (indexes, fetch_indexes) and the sources the user added (user_sources, add_user_source, remove_user_source). SuiteStore is craft-font-store's store, FakeStore a stand-in for tests, NoStore one for when the store folder is unknown
FontsPanel egui The whole interface as one egui panel

The panel

The layout follows Adobe Fonts' and Google Fonts' browse pages.

  • Across the top: Browse and Installed, with their counts, and one search field.
  • Filters at the reading start, each under its label: category, language (searchable by its English name or its own, "Japanese · 日本語"), script, source, and installed or not; each option with its count.
  • Family cards, one per row: the name, style count, category and source on one line with the card's state at its end (Install and Preview, Installed, a progress bar while downloading, Couldn't install with Retry, "Not curated" for a source the user added), and a large sample in the face below. The list is virtual: only the cards on screen are laid out, and their faces load as they come into view. The arrow keys step through the cards and Enter opens one.
  • Sample toolbar: a preset (a sentence in the language filtered by, or in the family's own script when it has no Latin; the alphabet; numerals), the sample text, and the size.
  • Details sheet over the list: every style in its own face with Install or Remove for each, Install all and Remove all, the source, designer, licence, scripts and languages, and what a preview downloads.
  • Installed: system and font store fonts as the same cards, filtered by source. Only font store families can be removed; system fonts are removed with the system's own tools.
  • Sources (sources_ui, for a host's settings page): the built-in sources, the index URLs the user added, marked not curated and flagged when the file at their address changed since it was added, adding one, and Update sources.

FontsPanel fills the Ui it is given and has no window, menu or interface fonts of its own; the host's Look gives it the host's colours and font families. The faces it draws samples in are egui font families of its own, added a few per frame and taken out again past 64, without touching the host's fonts. Right-to-left interfaces mirror through craft_ui::direction; sample text keeps its own direction.

let catalog = Arc::clone(Catalog::global());
catalog.scan_in_background();
let installer: Arc<dyn Installer> = match SuiteStore::for_user(Arc::clone(&catalog)) {
    Some(store) => Arc::new(store),
    None => Arc::new(NoStore),
};
let index = installer.indexes().map_err(|e| e.to_string());
let options = Options { catalog, installer, index, runner: Runner::Thread };
let mut panel = FontsPanel::new(options, Settings::default());
// each frame
panel.ui(ui);
Call Use
set_look(look) The host's palettes (dark and light) and font families, and whether labels are set in capitals
settings(), settings_mut() What the host saves: the sample text, preset and size, and whether the downloads note was closed (serde)
downloads_allowed(), set_downloads_allowed(on) The download consent, for a host's settings page. It is the font store's own setting, shared by every Craft app
show_downloads_switch(false) For a host with the switch in its own settings
sources_ui(ui) The font sources, for a host's settings page
set_index(index) The indexes, after the host fetched them
set_language(tag) The interface language, the host's own; English when its catalog isn't embedded
install_interface_fonts(ctx, craft_faces) For a host without interface fonts of its own (the standalone app): craft-ui's font stack for the panel's language, craft-fonts' faces the app embeds with craft-fonts-build, and the interface direction

Network use

Nothing is downloaded until the user allows it. Downloads are off by default; the first Install or Preview asks first. Files come from each source's repositories at the commit or tag its index records, through jsDelivr (raw GitHub as the fallback), and each is checked against the index's size and sha256 before use. Previews stay in memory and are never installed. A source the user adds is an index at an https URL; its families are never curated.

Strings

Every interface string goes through craft-i18n with the English text as the key. The panel embeds its own Japanese and Arabic catalogs (src/panel/catalogs/) until its rows are part of craft-i18n; style and weight names, licence names and product names stay in English.

Testing

cargo test -p craft-font-manager --all-features
CRAFT_FONT_MANAGER_SNAPSHOTS=<dir> cargo test -p craft-font-manager --features egui --test flows screenshots -- --test-threads=1

tests/flows.rs drives the panel with egui_kittest against a small index, a font folder made per test and FakeStore. They install egui's Hack and Ubuntu Light faces into temporary folders. With CRAFT_FONT_MANAGER_GOOGLE_FONTS=<google/fonts checkout>, the fake store also "downloads" the index's files from the checkout, so screenshots show real previews.