From 89f6adb6fbc3d9d5a19d0be177c19daede7794d0 Mon Sep 17 00:00:00 2001 From: moppeeer Date: Thu, 15 Oct 2026 01:25:04 +0200 Subject: [PATCH] Find Font: a style that differs only in spacing isn't missing Montilla Design's Flex Layout file names its body face "Regular " (with a trailing space, as the designer's copy of Articulat CF did). Face lookup already ignores spacing and case, so the text is drawn in Regular, but font.list (Find Font) compared names exactly and reported the style missing. It now uses the lookup's comparison (designcraft_fonts::same_style). Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 1 + crates/engine/src/cmd/fonts.rs | 20 +++++++++++++++++++- crates/fonts/src/fontdb.rs | 6 ++++++ crates/fonts/src/lib.rs | 2 +- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9f8168e50..0e9bd51a9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -132,6 +132,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m Newest first. +- **2026-10-15** · Find Font no longer reports a style missing when it differs from the installed one only in spacing or case (InDesign files can name `Regular ` with a trailing space); the text was already drawn in the right face. No readiness percentage change. - **2026-10-13** · Object › Fitting has InDesign's shortcuts: Fill Frame Proportionally ⌘⌥⇧C, Fit Content Proportionally ⌘⌥⇧E, Fit Frame to Content ⌘⌥C, Fit Content to Frame ⌘⌥E, Center Content ⌘⇧E. No readiness percentage change. - **2026-10-13** · Gradients between process colours blend as inks, as InDesign shows them: magenta to cyan passes through C 50 M 50 (a light lilac) on screen instead of the darker RGB mix, and a midpoint between CMYK or grey stops no longer turns the PDF shading RGB. No readiness percentage change. - **2026-10-14** · The Swatches panel has InDesign's header: a Fill/Stroke proxy, Formatting Affects Container/Text and the Tint of the colour in front (a tint alone keeps each object's own swatch). Clicking a swatch applies it to the fill or stroke in front (of the text, when formatting affects text), and the bottom row has New Color Group, New Swatch and Delete Swatch. No readiness percentage change. diff --git a/crates/engine/src/cmd/fonts.rs b/crates/engine/src/cmd/fonts.rs index 3b173c199..f119dd087 100644 --- a/crates/engine/src/cmd/fonts.rs +++ b/crates/engine/src/cmd/fonts.rs @@ -73,7 +73,7 @@ fn list(d: &Document) -> Vec { .into_iter() .map(|((family, style), n)| { let missing = !db.has_family(&family); - let style_missing = !missing && !db.styles(&family).iter().any(|s| s.eq_ignore_ascii_case(&style)); + let style_missing = !missing && !db.styles(&family).iter().any(|s| designcraft_fonts::same_style(s, &style)); let source = (!missing).then(|| match db.face(&family, &style).source { FontSource::Bundled => "bundled", FontSource::Installed(_) => "installed", @@ -225,4 +225,22 @@ mod tests { assert_eq!((&l[0]["family"], &l[0]["missing"]), (&json!("Nope (OTF)"), &json!(true)), "{l}"); assert!(kinds(&mut s).iter().any(|k| k == "missingFont")); } + + /// A style name that differs only in spacing (Montilla Design's file sets `Regular ` with a + /// trailing space) is the face the text is drawn in, not a missing style. + #[test] + fn a_style_with_extra_spacing_is_not_missing() { + let mut s = Session::new(); + s.execute("file.new", &json!({})).unwrap(); + let r = s.execute("frame.create", &json!({"rect": [72, 72, 400, 300], "content": "text", "text": "Text"})).unwrap(); + let sid = s.doc().unwrap().doc.item(designcraft_doc::ItemId(r["id"].as_u64().unwrap())).unwrap().text_frame().unwrap().story; + s.execute("text.select", &json!({"story": sid.0, "anchor": 0, "focus": 4})).unwrap(); + s.execute("type.char", &json!({"fontFamily": "Source Serif 4", "fontStyle": "Regular "})).unwrap(); + let l = s.execute("font.list", &json!({})).unwrap(); + let entry = l.as_array().unwrap().iter().find(|f| f["style"] == "Regular ").unwrap(); + assert_eq!((&entry["missing"], &entry["styleMissing"]), (&json!(false), &json!(false)), "{l}"); + s.execute("type.char", &json!({"fontFamily": "Source Serif 4", "fontStyle": "Nope"})).unwrap(); + let l = s.execute("font.list", &json!({})).unwrap(); + assert_eq!(l[0]["styleMissing"], json!(true), "{l}"); + } } diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index bc9c9e65f..0ee10a6b4 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -845,6 +845,12 @@ pub fn base_style(style: &str) -> &str { style.split('{').next().unwrap_or(style).trim() } +/// Do two style names name the same face? Spacing, punctuation and case don't count, as when a face +/// is looked up (InDesign documents can carry `Regular ` with a trailing space). +pub fn same_style(a: &str, b: &str) -> bool { + norm(a) == norm(b) +} + fn norm(s: &str) -> String { s.chars().filter(|c| c.is_alphanumeric()).flat_map(|c| c.to_lowercase()).collect() } diff --git a/crates/fonts/src/lib.rs b/crates/fonts/src/lib.rs index 5ff395df8..088fba02c 100644 --- a/crates/fonts/src/lib.rs +++ b/crates/fonts/src/lib.rs @@ -16,7 +16,7 @@ mod vertical; pub use fontdb::{ DOCUMENT_FONTS_FOLDER, DocumentFonts, FALLBACK_FAMILY, FaceRef, FontDb, FontFace, FontSource, MAX_DOCUMENT_FONT_BYTES, MAX_DOCUMENT_FONT_FILES, - MAX_DOCUMENT_FONTS_TOTAL, ScopedFonts, base_style, bundled, system_font_dirs, + MAX_DOCUMENT_FONTS_TOTAL, ScopedFonts, base_style, bundled, same_style, system_font_dirs, }; pub use group::{FamilyInfo, FontGroup, sort_for_menu}; pub use harfrust::Feature;