From 87cc53b0987435d6683a6bdf018a792e4a04431c Mon Sep 17 00:00:00 2001 From: skylartaylor Date: Sun, 25 Oct 2026 05:21:56 +0100 Subject: [PATCH] E21: restore Latin coverage and enforce required font inputs --- .github/workflows/ci.yml | 12 +- .github/workflows/freebsd.yml | 2 +- .github/workflows/release.yml | 2 +- crates/fonts/build.rs | 258 ++++++++++++++++-- crates/fonts/src/lib.rs | 20 ++ .../fonts/tests/fixtures/latin-families.txt | 52 ++++ docs/egui-037-migration-plan.md | 80 +++--- 7 files changed, 365 insertions(+), 61 deletions(-) create mode 100644 crates/fonts/tests/fixtures/latin-families.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b342bca..907aa336 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,15 +17,14 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - # The migration's font pin gate stays a local preflight. A reusable-workflow job cannot run - # custom steps or export CRAFT_FONTS_REQUIRED into gate.yml, so the check is its own job and - # the shared gate waits for it. Keep this pin identical to the `craft-fonts-ref` below, - # freebsd.yml and release.yml. + # Keep the early manifest preflight separate from the reusable workflow. The shared gate + # also runs with require-craft-fonts: true, so the build script enforces actual required faces. + # Keep this pin identical to gate, freebsd.yml and release.yml. font-pin: runs-on: ubuntu-latest env: # Same craft-fonts commit as release.yml. - CRAFT_FONTS_REF: b076417b5880bd4154bd22f00695b1d28c6bea83 + CRAFT_FONTS_REF: 37827adc41fd165f9a9234ac40f94dbad353aa69 steps: - name: Check out craft-fonts uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -55,7 +54,8 @@ jobs: uses: storytold/craft-common-actions/.github/workflows/gate.yml@main with: # Same craft-fonts commit as release.yml. - craft-fonts-ref: b076417b5880bd4154bd22f00695b1d28c6bea83 + craft-fonts-ref: 37827adc41fd165f9a9234ac40f94dbad353aa69 + require-craft-fonts: true packages: >- libasound2-dev libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 02655ac5..94ccca50 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -31,4 +31,4 @@ jobs: # CRAFT_FONTS_DIR to its tests but has no CRAFT_FONTS_REQUIRED input yet, so it does not # qualify the required Chinese input after the pin advances; that gate stays explicitly # pending in docs/egui-037-migration-plan.md section 5.1. - craft-fonts-ref: b076417b5880bd4154bd22f00695b1d28c6bea83 + craft-fonts-ref: 37827adc41fd165f9a9234ac40f94dbad353aa69 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc960c04..9d061bce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: # Keep identical to ci.yml and freebsd.yml. The shared release workflow sets # CRAFT_FONTS_REQUIRED=1 in its packaging steps, so a pin without the reviewed Chinese # manifest fails the release instead of silently skipping it. - craft-fonts-ref: b076417b5880bd4154bd22f00695b1d28c6bea83 + craft-fonts-ref: 37827adc41fd165f9a9234ac40f94dbad353aa69 version: ${{ inputs.version }} secrets: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} diff --git a/crates/fonts/build.rs b/crates/fonts/build.rs index b9689941..432ccbc1 100644 --- a/crates/fonts/build.rs +++ b/crates/fonts/build.rs @@ -4,9 +4,10 @@ //! `CRAFT_FONTS`, Latin first. Native builds additionally read //! `fonts/simplified-chinese-manifest.txt`; it is optional for ordinary local builds but a required //! native input (`CRAFT_FONTS_REQUIRED`) fails without it. Unset, `CRAFT_FONTS` is empty. Web -//! (wasm32) builds embed only Inter -//! Regular/Bold and BIZ UDPGothic Regular, to stay within the web size budget. It only reads the -//! local checkout: no network. +//! (wasm32) builds embed only Inter Regular/Bold (or a supported Variable face) and BIZ UDPGothic +//! Regular, to stay within the web size budget. Required builds also verify those faces survive +//! exclusion (and, on wasm, the web filter), so a pin without the Latin manifest fails instead of +//! silently dropping Inter. It only reads the local checkout: no network. use std::fmt::Write as _; use std::path::PathBuf; @@ -23,6 +24,21 @@ fn is_excluded_family(family: &str) -> bool { || family.starts_with("Noto Sans Mono CJK") } +/// The generated initialisers plus the faces that were actually embedded after exclusion and web +/// filtering. Validation must use the latter, not the raw manifest text. +#[derive(Default)] +struct FontInput { + source: String, + faces: Vec<(String, String)>, +} + +impl FontInput { + fn append(&mut self, other: Self) { + self.source.push_str(&other.source); + self.faces.extend(other.faces); + } +} + fn main() { println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR"); println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED"); @@ -44,22 +60,40 @@ fn main() { /// One `CraftFont { .. }` initialiser per manifest line. fn input_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool) -> Result { + load_fonts(dir, wasm, required).map(|input| input.source) +} + +fn load_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool) -> Result { match dir { Some(dir) => craft_fonts(dir, wasm, required), None if required => Err("CRAFT_FONTS_REQUIRED is set but CRAFT_FONTS_DIR is missing".into()), - None => Ok(String::new()), + None => Ok(FontInput::default()), } } -fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result { - let mut out = String::new(); +fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result { + let mut input = FontInput::default(); // The Latin presentation fonts are optional within craft-fonts (older checkouts lack them). + // A malformed or incomplete optional manifest is skipped as a unit: the main Japanese/symbol/RTL + // faces and the native Chinese addition stay usable, and required builds still fail validation. let latin = dir.join("fonts/latin-manifest.txt"); println!("cargo::rerun-if-changed={}", latin.display()); - if latin.exists() { - out.push_str(&manifest(dir, &latin, wasm)?); + let latin_exists = match latin.try_exists() { + Ok(exists) => exists, + Err(e) if required => return Err(format!("{}: {e}", latin.display())), + Err(e) => { + println!("cargo::warning=skipping optional Latin fonts: {}: {e}", latin.display()); + false + } + }; + if latin_exists { + match manifest(dir, &latin, wasm) { + Ok(entries) => input.append(entries), + Err(e) if required => return Err(e), + Err(e) => println!("cargo::warning=skipping optional Latin fonts: {e}"), + } } - out.push_str(&manifest(dir, &dir.join("fonts/manifest.txt"), wasm)?); + input.append(manifest(dir, &dir.join("fonts/manifest.txt"), wasm)?); // Native consumers opt in; older craft-fonts checkouts need not carry this manifest. // An invalid optional addition must never discard the main Japanese/symbol/RTL faces. if !wasm { @@ -75,7 +109,7 @@ fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result out.push_str(&entries), + Ok(entries) => input.append(entries), Err(e) if required => return Err(e), Err(e) => println!("cargo::warning=skipping optional Chinese fonts: {e}"), } @@ -85,13 +119,16 @@ fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result Result { +fn manifest(dir: &std::path::Path, manifest: &std::path::Path, wasm: bool) -> Result { println!("cargo::rerun-if-changed={}", manifest.display()); let text = std::fs::read_to_string(manifest).map_err(|e| format!("{}: {e}", manifest.display()))?; - let mut out = String::new(); + let mut input = FontInput::default(); for line in text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')) { let f: Vec<&str> = line.split(" | ").map(str::trim).collect(); let [family, style, file, scripts, ..] = f.as_slice() else { @@ -107,14 +144,41 @@ fn manifest(dir: &std::path::Path, manifest: &std::path::Path, wasm: bool) -> Re let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?; println!("cargo::rerun-if-changed={}", path.display()); let scripts: Vec = scripts.split(',').map(|s| format!("{:?}", s.trim())).collect(); + input.faces.push(((*family).to_owned(), (*style).to_owned())); let _ = writeln!( - out, + input.source, " CraftFont {{ family: {family:?}, style: {style:?}, scripts: &[{}], bytes: include_bytes!({:?}) }},", scripts.join(", "), path.display().to_string(), ); } - Ok(out) + Ok(input) +} + +/// Required builds must actually carry the faces the platform promises: Inter Regular and Bold +/// (or one Variable face) and BIZ UDPGothic Regular everywhere, plus LXGW WenKai Lite Regular for +/// native builds. This runs after exclusion and, on wasm, the web filter, so an absent manifest, +/// empty/all-excluded rows, a wrong-family row or a partial catalog cannot satisfy the gate. +fn validate_required_faces(faces: &[(String, String)], wasm: bool) -> Result<(), String> { + let has = |family: &str, style: &str| faces.iter().any(|(f, s)| f == family && s == style); + let inter = has("Inter", "Variable") || (has("Inter", "Regular") && has("Inter", "Bold")); + let biz = has("BIZ UDPGothic", "Regular"); + let chinese = wasm || has("LXGW WenKai Lite", "Regular"); + let mut missing = Vec::new(); + if !inter { + missing.push("Inter Regular/Bold (or Inter Variable)"); + } + if !biz { + missing.push("BIZ UDPGothic Regular"); + } + if !chinese { + missing.push("LXGW WenKai Lite Regular"); + } + if missing.is_empty() { + Ok(()) + } else { + Err(format!("CRAFT_FONTS_REQUIRED is set but the embedded font set after filtering lacks {}", missing.join(" and "))) + } } #[cfg(test)] @@ -123,6 +187,7 @@ mod tests { use std::sync::atomic::{AtomicU64, Ordering}; static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0); + const CHINESE_ROW: &str = "LXGW WenKai Lite | Regular | fonts/fixture.ttf | Hani,Hans | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n"; struct InputFixture(PathBuf); @@ -140,6 +205,25 @@ mod tests { Self(path) } + fn main(&self, text: &str) { + std::fs::write(self.0.join("fonts/manifest.txt"), text).unwrap(); + } + + fn latin(&self, text: &str) { + std::fs::write(self.0.join("fonts/latin-manifest.txt"), text).unwrap(); + } + + fn no_latin(&self) { + let _ = std::fs::remove_file(self.0.join("fonts/latin-manifest.txt")); + } + + fn valid_latin(&self) { + self.latin( + "Inter | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\ + Inter | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + ); + } + fn optional(&self, text: &str) { std::fs::write(self.0.join("fonts/simplified-chinese-manifest.txt"), text).unwrap(); } @@ -172,8 +256,9 @@ mod tests { #[test] fn native_opt_in_does_not_change_wasm_embedded_bytes() { let fixture = InputFixture::new(); + fixture.valid_latin(); let before = input_fonts(Some(&fixture.0), true, true).unwrap(); - fixture.optional("LXGW WenKai Lite | Regular | fonts/fixture.ttf | Hani,Hans | OFL-1.1 | OFL.txt | fixture | https://example.invalid"); + fixture.optional(CHINESE_ROW); let native = input_fonts(Some(&fixture.0), false, true).unwrap(); assert!(native.contains("LXGW WenKai Lite")); assert_eq!(input_fonts(Some(&fixture.0), true, true).unwrap(), before); @@ -184,6 +269,7 @@ mod tests { #[test] fn required_native_input_requires_the_chinese_manifest() { let fixture = InputFixture::new(); + fixture.valid_latin(); assert!(input_fonts(Some(&fixture.0), false, true).unwrap_err().contains("simplified-Chinese")); // Optional native builds and wasm keep working without it. assert!(input_fonts(Some(&fixture.0), false, false).is_ok()); @@ -210,7 +296,8 @@ mod tests { assert!(!is_excluded_family(family), "{family} is an allowed input"); } let fixture = InputFixture::new(); - let mut rows = String::new(); + fixture.valid_latin(); + let mut rows = String::from(CHINESE_ROW); for family in ["Noto Sans CJK SC", "Noto Serif CJK JP", "Noto Sans Mono CJK KR"] { rows.push_str(&format!("{family} | Regular | fonts/fixture.ttf | Hani | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n")); } @@ -218,5 +305,142 @@ mod tests { let native = input_fonts(Some(&fixture.0), false, true).unwrap(); assert!(!native.contains("Noto Sans CJK") && !native.contains("Noto Serif CJK") && !native.contains("Noto Sans Mono CJK")); assert!(native.contains("BIZ UDPGothic"), "the allowed main manifest is retained"); + assert!(native.contains("LXGW WenKai Lite"), "the valid Chinese row is retained"); + } + + #[test] + fn required_native_chinese_manifest_must_emit_the_reviewed_face() { + let fixture = InputFixture::new(); + fixture.valid_latin(); + let excluded = "Noto Sans CJK SC | Regular | fonts/fixture.ttf | Hani | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\ + Noto Serif CJK JP | Regular | fonts/fixture.ttf | Hani | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n"; + let wrong_family = "LXGW WenKai | Regular | fonts/fixture.ttf | Hani,Hans | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n"; + for rows in ["", excluded, wrong_family] { + fixture.optional(rows); + let e = input_fonts(Some(&fixture.0), false, true).unwrap_err(); + assert!(e.contains("LXGW WenKai Lite Regular"), "rows={rows:?}: {e}"); + assert!(input_fonts(Some(&fixture.0), false, false).is_ok(), "optional builds still preserve the valid core"); + } + fixture.optional(CHINESE_ROW); + let native = input_fonts(Some(&fixture.0), false, true).unwrap(); + assert!(native.contains("CraftFont { family: \"LXGW WenKai Lite\", style: \"Regular\""), "{native}"); + assert!(native.contains("BIZ UDPGothic") && native.contains("Inter"), "the rest of the required catalog is retained"); + } + + #[test] + fn optional_broken_latin_manifest_is_skipped_without_losing_core_or_chinese() { + let fixture = InputFixture::new(); + fixture.optional(CHINESE_ROW); + let missing_asset = "Inter | Regular | fonts/missing.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n"; + for latin in [None, Some("malformed row"), Some(missing_asset)] { + match latin { + Some(text) => fixture.latin(text), + None => fixture.no_latin(), + } + for wasm in [false, true] { + let catalog = input_fonts(Some(&fixture.0), wasm, false).unwrap(); + assert!(catalog.contains("BIZ UDPGothic"), "wasm={wasm}: the main manifest is retained"); + assert!(!catalog.contains("Inter"), "wasm={wasm}: the broken Latin manifest is skipped as a unit"); + if !wasm { + assert!(catalog.contains("LXGW WenKai Lite"), "the valid Chinese addition is retained"); + } + } + assert!(input_fonts(Some(&fixture.0), false, true).is_err(), "required builds must not skip the missing required faces"); + } + } + + #[test] + fn full_prior_latin_catalog_keeps_permitted_native_families() { + let fixture = InputFixture::new(); + fixture.latin(include_str!("tests/fixtures/latin-families.txt")); + fixture.optional(CHINESE_ROW); + let native = input_fonts(Some(&fixture.0), false, true).unwrap(); + assert_eq!(native.matches("CraftFont {").count(), 45, "43 permitted Latin faces plus BIZ and LXGW"); + for family in [ + "Carlito", + "Caladea", + "Liberation Serif", + "Montserrat", + "Lato", + "Open Sans", + "Roboto", + "Merriweather", + "Playfair Display", + "Poppins", + "Nunito Sans", + ] { + assert!(native.contains(family), "missing prior presentation family {family}"); + } + let web = input_fonts(Some(&fixture.0), true, true).unwrap(); + assert_eq!(web.matches("CraftFont {").count(), 3, "Inter Regular/Bold and BIZ only"); + for catalog in [&native, &web] { + assert!(!catalog.contains("Source Sans 3")); + assert!(!catalog.contains("Source Serif 4")); + } + } + + #[test] + fn required_builds_need_the_latin_catalog_for_inter() { + let fixture = InputFixture::new(); + fixture.optional(CHINESE_ROW); + for wasm in [true, false] { + let e = input_fonts(Some(&fixture.0), wasm, true).unwrap_err(); + assert!(e.contains("Inter Regular/Bold"), "wasm={wasm}: {e}"); + assert!(input_fonts(Some(&fixture.0), wasm, false).is_ok(), "optional builds still work"); + } + } + + #[test] + fn required_builds_reject_partial_or_unrelated_faces() { + let fixture = InputFixture::new(); + fixture.optional(CHINESE_ROW); + for latin in [ + "Inter | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + "Inter | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + "Inter | Italic | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\ + Inter | SemiBold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + "Carlito | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + "Source Sans 3 | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\ + Source Sans 3 | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + ] { + fixture.latin(latin); + for wasm in [true, false] { + let e = input_fonts(Some(&fixture.0), wasm, true).unwrap_err(); + assert!(e.contains("Inter Regular/Bold"), "wasm={wasm}: {e}"); + assert!(input_fonts(Some(&fixture.0), wasm, false).is_ok(), "optional builds still work"); + } + } + } + + #[test] + fn required_builds_need_biz_udpgothic_regular() { + let fixture = InputFixture::new(); + fixture.valid_latin(); + fixture.main( + "Inter | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\ + Inter | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n", + ); + fixture.optional(CHINESE_ROW); + for wasm in [true, false] { + let e = input_fonts(Some(&fixture.0), wasm, true).unwrap_err(); + assert!(e.contains("BIZ UDPGothic Regular"), "wasm={wasm}: {e}"); + assert!(input_fonts(Some(&fixture.0), wasm, false).is_ok(), "optional builds still work"); + } + } + + #[test] + fn required_builds_accept_static_or_variable_inter_with_biz() { + let fixture = InputFixture::new(); + fixture.valid_latin(); + fixture.optional(CHINESE_ROW); + for wasm in [true, false] { + let static_catalog = input_fonts(Some(&fixture.0), wasm, true).unwrap(); + assert!(static_catalog.contains("Inter") && static_catalog.contains("BIZ UDPGothic")); + } + fixture.latin("Inter | Variable | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n"); + for wasm in [true, false] { + let variable_catalog = input_fonts(Some(&fixture.0), wasm, true).unwrap(); + assert!(variable_catalog.contains("Inter") && variable_catalog.contains("BIZ UDPGothic")); + } } } diff --git a/crates/fonts/src/lib.rs b/crates/fonts/src/lib.rs index a9f0c050..7f7b583a 100644 --- a/crates/fonts/src/lib.rs +++ b/crates/fonts/src/lib.rs @@ -411,14 +411,34 @@ mod tests { #[test] fn craft_fonts_latin_when_present() { + let required = option_env!("CRAFT_FONTS_REQUIRED").is_some(); if !CRAFT_FONTS.iter().any(|f| f.family == "Inter") { + assert!(!required, "CRAFT_FONTS_REQUIRED must embed Inter rather than skip the optional Latin manifest"); eprintln!("skipped: built without craft-fonts' Latin manifest (set CRAFT_FONTS_DIR)"); return; } + let inter = |style: &str| CRAFT_FONTS.iter().find(|f| f.family == "Inter" && f.style == style); + let (regular, bold, variable) = (inter("Regular"), inter("Bold"), inter("Variable")); + if required { + assert!( + variable.is_some() || (regular.is_some() && bold.is_some()), + "CRAFT_FONTS_REQUIRED must embed Inter Regular and Bold (or a Variable face)" + ); + } let db = FontDb::with_font_dirs(Vec::new()); db.set_system_fallback(false); assert_eq!(db.face("Inter", "Regular").family, "Inter"); assert_eq!(db.face("Aptos", "Regular").family, "Inter"); + if let (Some(regular), Some(bold)) = (regular, bold) { + // The build input carries one entry per face; real static Bold must not be the Regular + // file relabelled in the manifest. + assert_ne!(regular.bytes, bold.bytes, "Inter Bold must be a distinct face file from Regular"); + let (r, b) = (db.face("Inter", "Regular"), db.face("Inter", "Bold")); + assert_eq!((r.family.as_str(), r.style.as_str()), ("Inter", "Regular")); + assert_eq!((b.family.as_str(), b.style.as_str()), ("Inter", "Bold")); + assert_ne!(r.id(), b.id(), "Inter Bold must load as its own face"); + assert!(b.weight > r.weight, "Inter Bold weight {} is not above Regular {}", b.weight, r.weight); + } if CRAFT_FONTS.iter().any(|f| f.family == "Carlito") { assert_eq!(db.face("Calibri", "Bold").family, "Carlito"); } diff --git a/crates/fonts/tests/fixtures/latin-families.txt b/crates/fonts/tests/fixtures/latin-families.txt new file mode 100644 index 00000000..91839018 --- /dev/null +++ b/crates/fonts/tests/fixtures/latin-families.txt @@ -0,0 +1,52 @@ +# Synthetic paths; family/style inventory from craft-fonts 76ba5a0496503c0e2841abd626295038cbe07cde. +Inter | Regular | fonts/fixture.ttf | Latn +Inter | Italic | fonts/fixture.ttf | Latn +Inter | SemiBold | fonts/fixture.ttf | Latn +Inter | Bold | fonts/fixture.ttf | Latn +Carlito | Regular | fonts/fixture.ttf | Latn +Carlito | Bold | fonts/fixture.ttf | Latn +Carlito | Italic | fonts/fixture.ttf | Latn +Carlito | Bold Italic | fonts/fixture.ttf | Latn +Caladea | Regular | fonts/fixture.ttf | Latn +Caladea | Bold | fonts/fixture.ttf | Latn +Caladea | Italic | fonts/fixture.ttf | Latn +Caladea | Bold Italic | fonts/fixture.ttf | Latn +Liberation Sans | Regular | fonts/fixture.ttf | Latn +Liberation Sans | Bold | fonts/fixture.ttf | Latn +Liberation Sans | Italic | fonts/fixture.ttf | Latn +Liberation Sans | Bold Italic | fonts/fixture.ttf | Latn +Liberation Serif | Regular | fonts/fixture.ttf | Latn +Liberation Serif | Bold | fonts/fixture.ttf | Latn +Liberation Serif | Italic | fonts/fixture.ttf | Latn +Liberation Serif | Bold Italic | fonts/fixture.ttf | Latn +Liberation Mono | Regular | fonts/fixture.ttf | Latn +Liberation Mono | Bold | fonts/fixture.ttf | Latn +Liberation Mono | Italic | fonts/fixture.ttf | Latn +Liberation Mono | Bold Italic | fonts/fixture.ttf | Latn +Source Sans 3 | Regular | fonts/fixture.ttf | Latn +Source Sans 3 | Italic | fonts/fixture.ttf | Latn +Source Sans 3 | Semibold | fonts/fixture.ttf | Latn +Source Sans 3 | Bold | fonts/fixture.ttf | Latn +Source Serif 4 | Regular | fonts/fixture.ttf | Latn +Source Serif 4 | Italic | fonts/fixture.ttf | Latn +Source Serif 4 | Semibold | fonts/fixture.ttf | Latn +Source Serif 4 | Bold | fonts/fixture.ttf | Latn +Montserrat | Variable | fonts/fixture.ttf | Latn +Montserrat | Italic Variable | fonts/fixture.ttf | Latn +Lato | Regular | fonts/fixture.ttf | Latn +Lato | Bold | fonts/fixture.ttf | Latn +Lato | Italic | fonts/fixture.ttf | Latn +Open Sans | Variable | fonts/fixture.ttf | Latn +Open Sans | Italic Variable | fonts/fixture.ttf | Latn +Roboto | Variable | fonts/fixture.ttf | Latn +Roboto | Italic Variable | fonts/fixture.ttf | Latn +Merriweather | Regular | fonts/fixture.ttf | Latn +Merriweather | Bold | fonts/fixture.ttf | Latn +Merriweather | Italic | fonts/fixture.ttf | Latn +Playfair Display | Variable | fonts/fixture.ttf | Latn +Playfair Display | Italic Variable | fonts/fixture.ttf | Latn +Poppins | Regular | fonts/fixture.ttf | Latn +Poppins | Bold | fonts/fixture.ttf | Latn +Poppins | Italic | fonts/fixture.ttf | Latn +Nunito Sans | Variable | fonts/fixture.ttf | Latn +Nunito Sans | Italic Variable | fonts/fixture.ttf | Latn diff --git a/docs/egui-037-migration-plan.md b/docs/egui-037-migration-plan.md index a2658c18..c802f0fe 100644 --- a/docs/egui-037-migration-plan.md +++ b/docs/egui-037-migration-plan.md @@ -1,10 +1,12 @@ # DeckCraft: egui 0.37 migration plan -Status update (2026-10-25): local implementation includes main `6a1c7354c152564c33d8489f6ea07ac585da0651`, preserving its animation changes unchanged. All eight CI stages passed: 1,995 passing test executions, zero failed, six ignored across 60 summaries. Strict lint, assets, layering and WASM checks passed; the integration received independent review. Pins and lockfile are unchanged. Native/browser GUI, native IME and paired application performance remain open. Publication requires separate explicit approval; human review and human merge are required. +Current candidate font pin: `37827adc41fd165f9a9234ac40f94dbad353aa69` restores the complete prior Latin catalog alongside the migration fallbacks. All three workflows now use that revision; it must be published before this app branch. + +Status update (2026-10-25): local implementation includes main `6a1c7354c152564c33d8489f6ea07ac585da0651`, preserving its animation changes unchanged. Managed run `egui037-deckcraft-font-final-ci-20261025-0724` passed the focused font tests, all eight CI stages and a separate required-font WASM check: 2,045 passing test executions, zero failures and six ignored across 63 summaries. These are execution counts, including repeated tests. Strict lint, assets, layering and full workspace WASM checks passed. Required native input now verifies Inter, BIZ and LXGW after filtering; required web input verifies Inter and BIZ. Broken optional Latin input preserves the valid core fonts. Fresh independent review found no blocking issue. The Cargo dependency pins and lockfile are unchanged by this font follow-up. Native/browser GUI, native IME and paired application performance remain open. Publication requires separate explicit approval; human review and human merge are required. The original planning record below is historical. -## Current qualification — 2026-10-25 +## Earlier qualification before the combined font input — 2026-10-25 The local candidate reconciles `main` through `38d2ade2b77de39c0e06278ddfd44d1082ff80a8` with the migration base @@ -17,8 +19,10 @@ The final regression additions exercise the Notes accessibility toggle, no-docum Open/Actions/Preferences/File-menu shortcuts, ignored document-only shortcuts, and typing/IME isolation. Earlier validation failures and source-completeness warnings below describe retained history; the current review packet records the complete -commit and its parents. Native/browser workflows and paired app performance remain -open. No publication or merge is authorized by these results. +commit and its parents. **The current review/approval packet is stale for publication +pending a combined font pin:** `b076417…` has no Latin manifest/Inter, so the pinned +input cannot supply the default document family. Native/browser workflows and paired +app performance remain open. No publication or merge is authorized by these results. ## Historical planning and review records @@ -706,28 +710,32 @@ so its root patch and lock are not crates.io publication artifacts, but releases still resolve the public storytold revisions. No stage is pushed or published without fresh human approval of the exact contents and validation. -**Font pin gate (before publication, not local work).** The native Simplified-Chinese fallback is -currently qualified against a local, dirty `craft-fonts-implementation` worktree: its -`fonts/simplified-chinese-manifest.txt`, `fonts/lxgw-wenkai-lite/` files and `fonts/manifest.txt` -row are uncommitted. That is expected unpublished work and must not be committed or published from -the DeckCraft checkout; it does not block local builds and tests, which read that worktree through -`CRAFT_FONTS_DIR`. Before any publication or release claims the Chinese migration, commit the -reviewed manifest and face in `craft-fonts`, obtain an immutable full SHA, and update **all three** -pins together: `.github/workflows/ci.yml` (`CRAFT_FONTS_REF`), `.github/workflows/freebsd.yml` and -`.github/workflows/release.yml` (`craft-fonts-ref`). Do not invent or pre-commit a future SHA: the -current shared pin remains `43913056ce82a3fdad0ef69f6b88d1dc922a8104`, whose tree has no Chinese -manifest. +**Font pin gate (before publication, not local work; stale pending a combined pin).** The three +workflow pins currently name `b076417b5880bd4154bd22f00695b1d28c6bea83`, whose tree carries the +reviewed Chinese manifest but has no `fonts/latin-manifest.txt` or Inter. The earlier +`43913056ce82a3fdad0ef69f6b88d1dc922a8104` tree carried the Latin manifest/Inter but not the +Chinese input. The parent is restoring the licensed Inter faces in the central +`craft-fonts-implementation` checkout; that work is unpublished and must not be committed from the +DeckCraft checkout. Therefore this approval packet is **stale**: do not approve or publish it until +the reviewed Latin manifest/faces and Chinese manifest are committed together in `craft-fonts`, an +immutable full SHA is obtained, and **all three** pins move together: +`.github/workflows/ci.yml` (`CRAFT_FONTS_REF`), `.github/workflows/freebsd.yml` and +`.github/workflows/release.yml` (`craft-fonts-ref`). Do not invent or pre-commit a future SHA, and do +not advance only one workflow. `crates/fonts/build.rs` now validates required builds after exclusion +and, for wasm, the web filter: Inter Regular/Bold (or a supported Variable face) and BIZ UDPGothic +Regular must actually be embedded, so a partial pin fails instead of silently losing the default +document/UI face. -The required-input gate is explicit but cannot fire until that pin advances: +The required-input gate is explicit, but CI and FreeBSD qualification remain partial: -- `ci.yml` runs the `Font pin gate` as a local preflight job before the shared gate. While the - pin is the known pending `43913056…` commit it emits a warning; any *other* pin without - `fonts/simplified-chinese-manifest.txt` fails the job, so advancing the pin cannot silently - keep the gate off. The shared `gate.yml` job does not accept a `CRAFT_FONTS_REQUIRED` input or +- `ci.yml` runs the `Font pin gate` as a local preflight job before the shared gate. It checks the + Chinese manifest but not the Latin manifest, so the build script's required-mode validation is + the guard for Inter. The shared `gate.yml` job does not accept a `CRAFT_FONTS_REQUIRED` input or another workflow's exported environment, so the CI job verifies the pin but cannot yet force the required-font environment into `cargo xtask ci`; release and FreeBSD notes below apply. - The shared release workflow already sets `CRAFT_FONTS_REQUIRED=1` in every packaging step, so a - release on a pin without the manifest fails instead of shipping without Chinese fonts. + release on a pin without the Chinese manifest or the required Inter/BIZ faces fails instead of + shipping without them. - The shared FreeBSD workflow passes `CRAFT_FONTS_DIR` to `cargo test` but currently exposes no `CRAFT_FONTS_REQUIRED` input. Its required-font gate therefore remains **pending** until `craft-common-actions` adds one (or the caller can otherwise set that environment); do not cite @@ -1422,22 +1430,22 @@ This section records the final local state after Earlier sections remain the historical plan and implementation amendments; where they describe an earlier snapshot or an open plan-time gap, this section is the current local status. -### Source identity (uncommitted, not a qualified commit) +### Current review follow-up — 2026-10-25 -- Local base HEAD: `770f4653bff0700b33d8475181e168da90a7ad4f`. -- Reviewed source: that base plus tracked edits and **five untracked source files**, which must be - included in any eventual commit: - `crates/engine/tests/show_paragraph_builds.rs`, - `crates/format/src/rtf.rs`, - `crates/pptx/tests/chart_data_labels.rs`, - `crates/ui-egui/src/field_frame_tests.rs` and - `crates/ui-egui/src/preferences_tests.rs`. -- `crates/format/src/lib.rs` declares `pub mod rtf;` and the UI modules declare the two - `*_tests` files, so a clean checkout or archive of `770f4653` alone cannot build or run the - reviewed source. `770f4653` is a local base for the dirty union, **not** a qualified commit. -- Focused reviewed hashes: `crates/ui-egui/src/thumbs.rs` `f21c09ba…`, - `crates/ui-egui/src/ime_tests.rs` `c433b847…`, `crates/ui-egui/src/lib.rs` `df8b48ba…`, - `Cargo.toml` `467ccc3e…`, `Cargo.lock` `33a3d3d8…`. +The candidate workflows now use `37827adc41fd165f9a9234ac40f94dbad353aa69`, combining +all prior Latin presentation fonts with the migration's core and Chinese fallbacks. +This commit must be published before these workflow pins can be fetched remotely. +The reusable CI gate explicitly sets `require-craft-fonts: true`; its current default +also enables required mode. The earlier statement that this cannot be passed to the +shared gate is obsolete. + +The local base for this follow-up is `8372d51552c3ed943ffeb283f26a482e94a2c12f`. +The five source files listed in the earlier dirty-union review are already tracked in +that commit. The follow-up also includes `crates/fonts/tests/fixtures/latin-families.txt`; +its 51-row family/style inventory comes from the prior `76ba5a0` catalog, whose font +bytes are preserved by the combined candidate. Include that fixture with the code. +Full follow-up checks and the final local review commit are recorded in the next +publication packet; old dirty-union hashes below are historical evidence only. ### Final pins