From b649035695fb9917faa6ac188f6d0f60c52a0639 Mon Sep 17 00:00:00 2001 From: Salah1St <83543012+Salah1St@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:28:29 +0700 Subject: [PATCH 1/3] M7: FIGCRAFT_DEFAULT_FONT names the font new text starts in TextStyle::default() hard-coded Inter, which has no Thai (nor Hebrew, Devanagari, ...). In a build for users who mostly write such a script, every new text layer started in a font that can't draw what they type: fallback faces hide the boxes, but the layer still says Inter and gets Inter's metrics and line height. Like FIGCRAFT_DEFAULT_SERVER, a build variable read with option_env! names another family; unset or empty means Inter. font_family is always written, so files open in the same font whichever build reads them. docs/releasing.md gets a "The default font" section. Co-Authored-By: Claude Opus 5.5 --- crates/doc/src/text.rs | 21 ++++++++++++++++++++- docs/releasing.md | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/doc/src/text.rs b/crates/doc/src/text.rs index 458426ad..e0956b9c 100644 --- a/crates/doc/src/text.rs +++ b/crates/doc/src/text.rs @@ -253,10 +253,17 @@ pub struct TextStyle { pub style_id: Option, } +/// The font new text starts in. A build can name another (`FIGCRAFT_DEFAULT_FONT="Noto Sans Thai"`, +/// for a distribution whose users mostly write a script Inter lacks); empty means Inter. +pub const DEFAULT_FONT: &str = match option_env!("FIGCRAFT_DEFAULT_FONT") { + Some(family) if !family.is_empty() => family, + _ => "Inter", +}; + impl Default for TextStyle { fn default() -> Self { Self { - font_family: "Inter".into(), + font_family: DEFAULT_FONT.into(), font_style: "Regular".into(), font_size: 12.0, line_height: LineHeight::Auto, @@ -783,6 +790,18 @@ mod case_tests { assert!(!valid_feature_tag("lig") && !valid_feature_tag("ligatures") && !valid_feature_tag("li\u{e9}")); } + #[test] + fn new_text_starts_in_the_builds_default_font() { + // Inter, unless the build names another with `FIGCRAFT_DEFAULT_FONT` (empty means Inter). + let named = option_env!("FIGCRAFT_DEFAULT_FONT").filter(|f| !f.is_empty()); + assert_eq!(DEFAULT_FONT, named.unwrap_or("Inter")); + let st = TextStyle::default(); + assert_eq!(st.font_family, DEFAULT_FONT); + // The family is always written, so a saved file opens in the same font in any build. + let saved = serde_json::to_value(&st).unwrap(); + assert_eq!(saved["font_family"], DEFAULT_FONT); + } + #[test] fn variations_are_backward_compatible_and_imply_weight() { // Files written before variable fonts load with no custom axes, and stay byte-identical. diff --git a/docs/releasing.md b/docs/releasing.md index 07492f6c..765c24b1 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -68,6 +68,20 @@ FIGCRAFT_DEFAULT_SERVER=wss://design.example.org/ cargo build --release -p figcr The release workflow leaves it unset. The public server's terms are in [`online-sharing-terms.md`](online-sharing-terms.md). +## The default font + +New text starts in Inter (`figcraft_doc::DEFAULT_FONT`). A distribution whose users mostly write a +script Inter lacks (Thai, say) can name another family with `FIGCRAFT_DEFAULT_FONT` when building; +unset or empty means Inter. Pick a family the build embeds from craft-fonts (the web build has no +other fonts) or one its users have installed: + +```sh +FIGCRAFT_DEFAULT_FONT="Noto Sans Thai" cargo build --release -p figcraft +``` + +Files record each text's family, so they open in the same font in any build. The release workflow +leaves it unset. + ## What gets built | Platform | Artifacts | Built on | From c4be9c1a34bb398f1c5203139bdeb2b20e8025cd Mon Sep 17 00:00:00 2001 From: Salah1St <83543012+Salah1St@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:34:43 +0700 Subject: [PATCH 2/3] M7: say what a default font family in files does and doesn't promise Review: a saved family renders only where that family is available. The docs and the test's comment now say the variable changes only what new text starts in, and that a file keeps its fonts in a build with another default, falling back like any missing family elsewhere. Co-Authored-By: Claude Opus 5.5 --- crates/doc/src/text.rs | 2 +- docs/releasing.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/doc/src/text.rs b/crates/doc/src/text.rs index e0956b9c..a9bf57ba 100644 --- a/crates/doc/src/text.rs +++ b/crates/doc/src/text.rs @@ -797,7 +797,7 @@ mod case_tests { assert_eq!(DEFAULT_FONT, named.unwrap_or("Inter")); let st = TextStyle::default(); assert_eq!(st.font_family, DEFAULT_FONT); - // The family is always written, so a saved file opens in the same font in any build. + // The family is always written, so a saved file keeps it in a build with another default. let saved = serde_json::to_value(&st).unwrap(); assert_eq!(saved["font_family"], DEFAULT_FONT); } diff --git a/docs/releasing.md b/docs/releasing.md index 765c24b1..8e96a25d 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -79,8 +79,10 @@ other fonts) or one its users have installed: FIGCRAFT_DEFAULT_FONT="Noto Sans Thai" cargo build --release -p figcraft ``` -Files record each text's family, so they open in the same font in any build. The release workflow -leaves it unset. +Files record each text's family, so the variable only changes what new text starts in: a file +saved by one build keeps its fonts in a build with another default. Like any other family, it +renders only where it is available (embedded or installed); elsewhere the text falls back to the +faces that are. The release workflow leaves it unset. ## What gets built From f728b8f9fe9ec502bdf6f414a262d9667a0188a1 Mon Sep 17 00:00:00 2001 From: Salah1St <83543012+Salah1St@users.noreply.github.com> Date: Fri, 23 Oct 2026 18:07:40 +0700 Subject: [PATCH 3/3] M7: test that text.create and the Text tool start in DEFAULT_FONT Review: the doc test serialised TextStyle directly. The engine test makes text with text.create (no fontFamily) and with a Text tool click, and checks each starts in figcraft_doc::DEFAULT_FONT and writes it with the text. Passes with FIGCRAFT_DEFAULT_FONT unset and set. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/text_behaviour_tests.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/engine/src/text_behaviour_tests.rs b/crates/engine/src/text_behaviour_tests.rs index 84315f1f..c24cc49a 100644 --- a/crates/engine/src/text_behaviour_tests.rs +++ b/crates/engine/src/text_behaviour_tests.rs @@ -229,3 +229,25 @@ fn a_drag_with_the_text_tool_makes_a_fixed_size_box() { let (b3, _) = text_tool((100.0, 100.0), (180.0, 101.0), "A"); assert_eq!((b3.width(), b3.height()), (80.0, line.height())); } + +#[test] +fn new_text_starts_in_the_builds_default_font() { + // `text.create` without a fontFamily and a click with the Text tool both start in + // `figcraft_doc::DEFAULT_FONT` (Inter, unless the build names another with + // `FIGCRAFT_DEFAULT_FONT`), and the family is written with the text. + use crate::tools::{PointerEvent, PointerKind, ToolId}; + let mut s = Session::new(); + let made = text(&mut s, "Text", json!({})); + s.set_tool(ToolId::Text); + s.pointer(&PointerEvent::at(PointerKind::Down, 300.0, 300.0, 1.0)).unwrap(); + s.pointer(&PointerEvent::at(PointerKind::Up, 300.0, 300.0, 1.0)).unwrap(); + crate::textedit::insert(&mut s, "Tool").unwrap(); + let typed = s.ds().unwrap().text_edit.as_ref().unwrap().id; + assert_ne!(made, typed); + let doc = s.doc().unwrap(); + for t in [made, typed] { + let td = doc.node(t).unwrap().kind.text().unwrap(); + assert_eq!(td.style.font_family, figcraft_doc::DEFAULT_FONT); + assert_eq!(serde_json::to_value(td).unwrap()["style"]["font_family"], figcraft_doc::DEFAULT_FONT); + } +}