From 86aad2bb6aef02a7b8267bbdc0306f48bc1c36e0 Mon Sep 17 00:00:00 2001 From: AganzeFelicite Date: Sat, 24 Oct 2026 18:39:44 +0200 Subject: [PATCH] M7: offer the installed styles of an embedded font family (#1246) Release builds embed some families with only a few styles (Poppins Regular, Italic and Bold), and the first font scan dropped every installed face of such a family, so an installed Poppins SemiBold or Medium was never offered. The first scan now keeps the embedded faces, which still win for the styles they cover, and offers the installed styles they lack beside them, as a rescan already did. Closes #1246 --- ROADMAP.md | 4 +++- crates/text/src/fontdb.rs | 29 +++++++++++++++++++---------- crates/text/src/fontdb_tests.rs | 33 +++++++++++++++++++++++++++++++++ docs/roadmap.md | 3 ++- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 69d3bf73..3b116651 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents) -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Dev Mode measurements kept on the canvas and a read-only layers panel; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Installed weights of an embedded font family (#1246); Dev Mode measurements kept on the canvas and a read-only layers panel; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow, native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through @@ -132,6 +132,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md). ## Progress log +- **2026-10-24** · Installed weights of an embedded font family (M7, #1246): release builds embed some families with only a few styles (Poppins Regular, Italic and Bold), and the first font scan dropped every installed face of such a family, so an installed Poppins SemiBold or Medium was never offered (a rescan did offer them). The first scan now keeps the embedded faces, which still win for the styles they cover, and offers the installed styles they lack beside them. Regression test in the font database tests. Numbers unchanged. - **2026-10-24** · Dev Mode tail (M6, gap 16): Measurements kept on the canvas (gap 16): `devMode.addMeasurement {from?, to?, side?}` places one between two layers, along one side of a layer or (neither) to the layer around it, stored on the layer it starts from (saved in `.figcraft`, synced, undoable; old files open without them); Dev Mode draws them as labelled redlines that follow the layers, the Inspect tab lists them (those of the selected layer, or every one on the page), places and removes them; `devMode.removeMeasurement` and `devMode.measurements` remove and list them; a copy keeps those to layers copied with it. The layers panel is read-only in Dev Mode: rows select and expand, but don't rename, reorder, hide or lock. Dev Mode 98% / 78%. - **2026-10-24** · Boards (M12): File ▸ New board makes a board, a file kind of its own saved in `.figcraft` and carried to people joining a live session. Sticky notes (8 colours, text that shrinks as the note grows, the author's name, Shift+Cmd/Ctrl+Enter for the next one), shapes with text (7 outlines), connectors that re-route when what they join moves (computed in the engine's normalize pass, so local edits, undo and others' edits agree), marker, highlighter and eraser, stamps with the placer's name on hover; a board toolbar, a contextual bar over the selection and the Design panel hidden until asked for; prototyping and Dev Mode refuse on boards. Design files and boards have their own marks (a pen nib, a note) on tabs, the home screen and the Open files list. Checked in the app, with two app instances in one live session, and through MCP; the web build compiles ([boards-parity](docs/boards-parity.md)). - **2026-10-15** · Auto-save (M1, #305): Preferences ▸ Save files automatically (`pref.autoSave`, off by default, desktop only) saves each open file that has a path 3 s after its last change, never during a gesture, a live text preview or while a version is shown; the file is encoded off the UI thread, a failed save says why and is tried again a minute later. Numbers unchanged. @@ -329,6 +330,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md). | Date | Change | Summary | |---|---|---| +| 2026-10-24 | minor | Installed weights of an embedded font family (M7, #1246): release builds embed some families with only a few styles (Poppins Regular, Italic and Bold), and the first font scan dropped every installed face of such a family, so an installed Poppins SemiBold or Medium was never offered (a rescan did offer them). The first scan now keeps the embedded faces, which still win for the styles they cover, and offers the installed styles they lack beside them. Regression test in the font database tests. Numbers unchanged | | 2026-10-24 | minor | Dev Mode tail (M6, gap 16): Measurements kept on the canvas (gap 16): `devMode.addMeasurement {from?, to?, side?}` places one between two layers, along one side of a layer or (neither) to the layer around it, stored on the layer it starts from (saved in `.figcraft`, synced, undoable; old files open without them); Dev Mode draws them as labelled redlines that follow the layers, the Inspect tab lists them (those of the selected layer, or every one on the page), places and removes them; `devMode.removeMeasurement` and `devMode.measurements` remove and list them; a copy keeps those to layers copied with it. The layers panel is read-only in Dev Mode: rows select and expand, but don't rename, reorder, hide or lock. Dev Mode 97% → 98% breadth, 76% → 78% ready, 2–4 h → 1–2 h; headline numbers unchanged at this rounding | | 2026-10-24 | minor | Boards (M12): a file kind of its own (`file.newBoard`, saved in `.figcraft`, carried to live-session joiners) with sticky notes, shapes with text, connectors that re-route when what they join moves (an engine recompute, the same for local edits, undo and others' edits), marker, highlighter and eraser, stamps with the placer's name, a board toolbar and a contextual bar; prototyping and Dev Mode refuse on boards; design files and boards have their own marks on tabs and file rows. Boards row added outside the weighted numbers; headline numbers unchanged at this rounding | | 2026-10-15 | minor | Auto-save (#305, #296): Preferences ▸ Save files automatically (`pref.autoSave`, off by default; desktop only, the browser has no files to save to) saves each open file that has a path 3 s after its last change, so steady editing doesn't save every few seconds, and never during a gesture, a live text preview or while a version is shown. The file is encoded on a thread of its own and written as of that moment (edits made meanwhile stay unsaved); a failed save says why in a toast and is tried again a minute later, and version history checkpoints keep coming with it on. Numbers unchanged | diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index 079a7efe..9ee655c2 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -720,13 +720,26 @@ impl FontDb { return; } self.system_scanned = true; + let mut found = BTreeMap::new(); for dir in system_font_dirs() { - scan_dir(&dir, 0, &mut self.pending); + scan_dir(&dir, 0, &mut found); } - // Families already embedded win. - let embedded: Vec = self.families.keys().cloned().collect(); - for k in embedded { - self.pending.remove(&k); + self.merge_scan(found); + } + + /// Offer the families of the first walk of the font folders. A family already embedded keeps + /// its faces, which win, and the installed styles it lacks wait beside them (#1246: release + /// builds embed Poppins Regular, Italic and Bold, so an installed Poppins SemiBold went unseen). + fn merge_scan(&mut self, found: BTreeMap)>) { + for (k, (display, mut entries)) in found { + if let Some((_, idx)) = self.families.get(&k) { + let have: Vec<&Arc> = idx.iter().filter_map(|i| self.faces.get(*i)).collect(); + entries.retain(|e| !have.iter().any(|f| norm_style(&f.style) == norm_style(&e.style) || f.instance(&e.style).is_some())); + if entries.is_empty() { + continue; + } + } + self.pending.entry(k).or_insert((display, entries)); } } } @@ -925,11 +938,7 @@ pub fn scan_system_fonts() { } let mut w = raw_db().write().unwrap_or_else(std::sync::PoisonError::into_inner); w.system_scanned = true; - for (k, v) in found { - if !w.families.contains_key(&k) { - w.pending.entry(k).or_insert(v); - } - } + w.merge_scan(found); drop(w); crate::clear_layout_cache(); } diff --git a/crates/text/src/fontdb_tests.rs b/crates/text/src/fontdb_tests.rs index f2456d28..032e924f 100644 --- a/crates/text/src/fontdb_tests.rs +++ b/crates/text/src/fontdb_tests.rs @@ -247,6 +247,39 @@ fn a_rescan_offers_fonts_installed_since() { assert_eq!(waiting(&db), ["Black"]); } +/// #1246: the first scan offers the installed styles an embedded family lacks (release builds embed +/// Poppins Regular, Italic and Bold; an installed SemiBold and Medium went unseen). Embedded styles win. +#[test] +fn the_scan_offers_installed_styles_an_embedded_family_lacks() { + // Any embedded or installed font stands in for the embedded face (none without craft-fonts on CI). + let stand_in = CRAFT_FONTS.first().map(|f| f.bytes.to_vec()).or_else(|| { + let mut installed = BTreeMap::new(); + for dir in system_font_dirs() { + scan_dir(&dir, 0, &mut installed); + } + installed.values().flat_map(|(_, v)| v).find_map(|p| std::fs::read(&p.path).ok()) + }); + let Some(mut face) = stand_in.and_then(|b| Face::from_bytes(FontBytes::Shared(Arc::new(b)), 0, Some("Brand Sans"), Some("Regular"))) else { + eprintln!("skipping: no font to stand in for an embedded one"); + return; + }; + // A variable stand-in's named instances aren't this family's styles. + face.instances.clear(); + let mut db = FontDb::default(); + db.add(face); + let entry = |s: &str| Pending { path: format!("no-such-dir/{s}.ttf").into(), index: 0, style: s.into() }; + let found = + |family: &str, styles: &[&str]| BTreeMap::from([(family.to_lowercase(), (family.to_string(), styles.iter().map(|s| entry(s)).collect()))]); + db.merge_scan(found("Brand Sans", &["Regular"])); + assert!(!db.pending.contains_key("brand sans"), "an embedded style isn't read from disk again"); + db.merge_scan(found("Brand Sans", &["Regular", "SemiBold", "Medium"])); + let waiting: Vec = db.pending.get("brand sans").map(|(_, v)| v.iter().map(|e| e.style.clone()).collect()).unwrap_or_default(); + assert_eq!(waiting, ["SemiBold", "Medium"]); + assert_eq!(db.families(), ["Brand Sans"]); + db.merge_scan(found("CJK UI", &["Regular", "Bold"])); + assert_eq!(left(&db), ["Regular", "Bold"]); +} + /// Font file bytes add their faces once; junk adds nothing. #[test] fn font_bytes_load_into_the_db() { diff --git a/docs/roadmap.md b/docs/roadmap.md index 8e093ac9..1d068b9c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # FigCraft roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (M12 Boards; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-24 · **Change:** minor (Installed weights of an embedded font family (#1246); M12 Boards; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) Forward-looking plan. Where FigCraft stands today: [`ROADMAP.md`](../ROADMAP.md) and [`target-app-parity.md`](target-app-parity.md); the full ranked list: [`gaps.md`](gaps.md). Hours @@ -75,6 +75,7 @@ blockers. | Date | Change | Summary | |---|---|---| +| 2026-10-24 | minor | Installed weights of an embedded font family (M7, #1246): release builds embed some families with only a few styles (Poppins Regular, Italic and Bold), and the first font scan dropped every installed face of such a family, so an installed Poppins SemiBold or Medium was never offered (a rescan did offer them). The first scan now keeps the embedded faces, which still win for the styles they cover, and offers the installed styles they lack beside them. Regression test in the font database tests. Numbers unchanged | | 2026-10-24 | minor | Boards (M12): a file kind of its own (`file.newBoard`, saved in `.figcraft`, carried to live-session joiners) with sticky notes, shapes with text, connectors that re-route when what they join moves (an engine recompute, the same for local edits, undo and others' edits), marker, highlighter and eraser, stamps with the placer's name, a board toolbar and a contextual bar; prototyping and Dev Mode refuse on boards; design files and boards have their own marks on tabs and file rows. Milestone M12 Boards added; AI features move to M13 | | 2026-10-15 | minor | Auto-save (#305, #296): Preferences ▸ Save files automatically (`pref.autoSave`, off by default; desktop only, the browser has no files to save to) saves each open file that has a path 3 s after its last change, so steady editing doesn't save every few seconds, and never during a gesture, a live text preview or while a version is shown. The file is encoded on a thread of its own and written as of that moment (edits made meanwhile stay unsaved); a failed save says why in a toast and is tried again a minute later, and version history checkpoints keep coming with it on. Numbers unchanged | | 2026-10-15 | minor | A text box by dragging (#363, #360): a drag with the Text tool makes a fixed-size text box the size of the drag (at least one line tall); a click, or a drag shorter than the drag threshold, still makes auto-width text. Numbers unchanged |