From 1f4aaaec4728b18e8380ee1f0249280084d4abfc Mon Sep 17 00:00:00 2001 From: LabDennis <341377186+LabDennis@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:52:15 +0200 Subject: [PATCH] Fonts: finish Thai fallback delivery for desktop and web releases Retain the complete reviewed change, including prior integration resolutions, on current upstream. #736: deliver Thai UI fonts in desktop and web releases Use the merged craft-fonts input containing Noto Sans Thai across release, FreeBSD and Nix builds. Retain the small Thai face in the wasm subset and reject required release inputs that lack it. Exercise the reported Thai folder name through all UI families and include original font licence texts in Windows installers as well as portable packages. Refs #736; completes delivery of the fallback added in #750. Validation: UI tests, UI-only strict Clippy, 28 wasm checks, HEIF checks, assets, layers, parity and formatting pass. All 21 unrelated workspace failures reproduce with upstream Rust sources and the old font input; existing Rust 1.95 metadata/engine Clippy findings remain unchanged. --- .github/workflows/freebsd.yml | 2 +- .github/workflows/release.yml | 12 ++++++------ NOTICE | 7 ++++--- assets/ATTRIBUTION.md | 2 +- crates/engine/build.rs | 9 +++++++-- crates/ui-egui/src/panels/detail.rs | 2 +- crates/ui-egui/src/theme.rs | 27 +++++++++++++++++++++------ docs/web.md | 3 ++- flake.lock | 8 ++++---- flake.nix | 2 +- packaging/windows/font-licences.ps1 | 22 ++++++++++++++++++++++ packaging/windows/lightcraft.wxs | 3 +++ packaging/windows/package.ps1 | 4 ++++ 13 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 packaging/windows/font-licences.ps1 diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index c63fdeef5..b10e65a75 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 path: craft-fonts - name: Build and test in a FreeBSD VM uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc # v1.5.9 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7e3036d6..739a5f262 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 # bump deliberately path: craft-fonts - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: @@ -136,7 +136,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 # bump deliberately path: craft-fonts - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: @@ -194,7 +194,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 # bump deliberately path: craft-fonts - name: Install build and packaging tools run: | @@ -291,7 +291,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 # bump deliberately path: craft-fonts - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: @@ -362,7 +362,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 # bump deliberately path: craft-fonts - name: Build and package in a FreeBSD VM (tar.gz) # vmactions/freebsd-vm v1.5.9, pinned by commit (it runs our build). @@ -411,7 +411,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts - ref: 8dcdacd5153e64560d109541a47d806f26f048c0 # bump deliberately + ref: 3d9ab3a132ed0eff457e424226a435a901da30d3 # bump deliberately path: craft-fonts - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable with: diff --git a/NOTICE b/NOTICE index c9d650bb1..8b16eeba0 100644 --- a/NOTICE +++ b/NOTICE @@ -51,9 +51,10 @@ no darktable, RawSpeed, LibRaw or other GPL decoder/runtime code is included. Builds made with craft-fonts (the optional CRAFT_FONTS_DIR build input; all official releases) also embed the fonts listed in https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md -(the Japanese fonts BIZ UDPGothic, BIZ UDMincho and Shippori Mincho; the web build embeds only BIZ -UDPGothic Regular), under the SIL Open Font License 1.1. Release packages include each font's -licence as OFL-.txt. +(BIZ UDPGothic, BIZ UDMincho, Shippori Mincho, Noto Sans CJK SC, Noto Sans Arabic and Noto Sans +Thai), under the SIL Open Font License 1.1. The web build embeds BIZ UDPGothic Regular and Noto +Sans Thai Regular, and loads Noto Sans CJK SC separately. Release packages include each font's +licence as OFL-.txt; Windows installers include their texts in FONT-LICENCES.txt. Place names for reverse geocoding (Photo > Fill Location from GPS) are not part of LightCraft and are never distributed with it. On the user's request (Photo > Download Place Names) the app diff --git a/assets/ATTRIBUTION.md b/assets/ATTRIBUTION.md index 7ade9dac2..6d2d8ec22 100644 --- a/assets/ATTRIBUTION.md +++ b/assets/ATTRIBUTION.md @@ -24,7 +24,7 @@ covers a whole directory), or if a licence file referenced in the Licence column | `docs/images/*-blue-marble.jpg`, `docs/images/grid-pd.jpg` | "The Blue Marble" (AS17-148-22727) | NASA, Apollo 17 crew (1972) | https://commons.wikimedia.org/wiki/File:The_Earth_seen_from_Apollo_17.jpg | Public domain (NASA) | 2026-09-30 | developed in LightCraft; shown inside app screenshots | | `docs/images/*.jpg` (UI) | LightCraft application screenshots | LightCraft contributors | original work (captured with `docs/showcase/`) | MIT OR Apache-2.0 | 2026-09-30 | n/a | | `docs/brand/` (artcraft-logo and artcraft-mark, SVG and PNG) | ArtCraft wordmark and mark | ArtCraft Team | original work (https://getartcraft.com/) | ArtCraft trademark, see `docs/brand/LICENSE-brand.txt` (not open source) | 2026-10-01 | none | -| craft-fonts: optional build input, not files in this repository | BIZ UDPGothic (Regular, Bold), BIZ UDMincho Regular, Shippori Mincho Regular, Noto Sans CJK SC Regular | Morisawa Inc. / The BIZ UDGothic and BIZ UDMincho Project Authors; The Shippori Mincho Project Authors; Google and Adobe (Noto Sans CJK, maintainer-approved exception; see AGENTS.md) (per craft-fonts' attribution) | https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md | SIL Open Font License 1.1 (each font's OFL.txt in craft-fonts; release packages ship it as OFL-.txt) | 2026-10-06 | none; native builds made with `CRAFT_FONTS_DIR` embed the manifest fonts (all official releases); the web build embeds BIZ UDPGothic Regular and ships Noto Sans CJK SC Regular as a separate font file | +| craft-fonts: optional build input, not files in this repository | BIZ UDPGothic (Regular, Bold), BIZ UDMincho Regular, Shippori Mincho Regular, Noto Sans CJK SC Regular, Noto Sans Arabic Regular, Noto Sans Thai Regular | The Noto Project Authors (Arabic and Thai); Morisawa Inc. / The BIZ UDGothic and BIZ UDMincho Project Authors; The Shippori Mincho Project Authors; Google and Adobe (Noto Sans CJK, maintainer-approved exception; see AGENTS.md) (per craft-fonts' attribution) | https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md | SIL Open Font License 1.1 (each font's OFL.txt in craft-fonts; release packages ship it as OFL-.txt) | 2026-10-06 | none; native builds made with `CRAFT_FONTS_DIR` embed the manifest fonts (all official releases); the web build embeds BIZ UDPGothic Regular and Noto Sans Thai Regular and ships Noto Sans CJK SC Regular as a separate font file | | `crates/ui-egui/locales/uk.json`, `crates/ui-egui/locales/uk-formats.json` | Ukrainian interface catalogs | @dmatviichuk with Codex; PhotoCraft, FilmCraft and VectorCraft translation contributors for reused labels | original translations from English source; 337 initial English-key matches reviewed from [PhotoCraft 69c41f3](https://github.com/storytold/photocraft/blob/69c41f3523e147eb215da8c7b4c29b18b35d1c63/crates/ui-egui/src/i18n/uk.tsv), [FilmCraft 4854c27](https://github.com/storytold/filmcraft/blob/4854c277536fe8118257362304b6a7d82d8e704b/crates/ui-egui/src/i18n/uk.tsv), [VectorCraft 4b44265](https://github.com/storytold/vectorcraft/blob/4b44265965fdd6ffedfd6182a892c1a6c360d344/crates/ui-egui/src/i18n/uk.tsv) | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, also the source repositories' licences) | 2026-10-09 | adapted to LightCraft photo-development context, completed catalogs, reviewed spelling and placeholders | Notes diff --git a/crates/engine/build.rs b/crates/engine/build.rs index e097889ff..056c7adc8 100644 --- a/crates/engine/build.rs +++ b/crates/engine/build.rs @@ -3,7 +3,7 @@ //! Recipe from craft-fonts' docs/integration.md; craft-fonts is never a Cargo dependency, and this //! script only reads a local directory (no network). //! -//! wasm32 (the web build) embeds only the UI font, BIZ UDPGothic Regular (~4.5 MB), to keep the +//! wasm32 embeds BIZ UDPGothic Regular (~4.5 MB) and Noto Sans Thai (~214 KiB), to keep the //! `.wasm` within the web size budget (Cloudflare serves at most 25 MiB per file); the export //! watermark falls back to it there. Native builds embed every font. use std::fmt::Write as _; @@ -69,6 +69,7 @@ fn craft_fonts(dir: &Path, required: bool) -> Result { let mut out = String::new(); let mut has_japanese_ui = false; let mut has_chinese_ui = false; + let mut has_thai_ui = false; 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 { @@ -76,7 +77,8 @@ fn craft_fonts(dir: &Path, required: bool) -> Result { }; has_japanese_ui |= (*family, *style) == ("BIZ UDPGothic", "Regular"); has_chinese_ui |= (*family, *style) == ("Noto Sans CJK SC", "Regular") && scripts.split(',').any(|script| script.trim() == "Hans"); - if wasm && (*family, *style) != ("BIZ UDPGothic", "Regular") { + has_thai_ui |= (*family, *style) == ("Noto Sans Thai", "Regular") && scripts.split(',').any(|script| script.trim() == "Thai"); + if wasm && !matches!((*family, *style), ("BIZ UDPGothic" | "Noto Sans Thai", "Regular")) { continue; } let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?; @@ -89,6 +91,9 @@ fn craft_fonts(dir: &Path, required: bool) -> Result { path.display().to_string(), ); } + if required && !has_thai_ui { + return Err("required Thai UI face Noto Sans Thai Regular (Thai) is missing from fonts/manifest.txt".into()); + } if required && !has_japanese_ui { return Err("required Japanese UI face BIZ UDPGothic Regular is missing from fonts/manifest.txt".into()); } diff --git a/crates/ui-egui/src/panels/detail.rs b/crates/ui-egui/src/panels/detail.rs index 151d4a57a..a7c8c1968 100644 --- a/crates/ui-egui/src/panels/detail.rs +++ b/crates/ui-egui/src/panels/detail.rs @@ -780,7 +780,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { // the render failed while a stand-in is up: what is on screen is all there will be (also while // a library change retries it) let failed = (!rendering - && !(show_before && !split) + && (!show_before || split) && !matches!(shown, "render" | "none" | "hover") && (!app.renderer.is_pending(Slot::Main) || app.renderer.retrying(Slot::Main))) .then(|| app.renderer.failure_of(Slot::Main, id)) diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 282f9c70e..e09db8810 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -462,14 +462,15 @@ mod tests { } } - /// #736: a Thai folder name drew boxes because only CJK craft-fonts faces were fallbacks. With a - /// craft-fonts build that carries a Thai face, an English UI offers it in every family. + /// #736: the release input must carry Thai, not silently skip the check with an older pin. + /// Exercise the Thai characters of the reported folder name through every egui family. #[test] - fn thai_face_is_a_fallback_in_every_ui_family_when_craft_fonts_carry_one() { - let Some(thai) = lightcraft_engine::CRAFT_FONTS.iter().find(|f| f.covers("Thai")) else { - eprintln!("no Thai face in craft-fonts: nothing to check"); + fn thai_folder_name_has_glyphs_in_every_ui_family() { + if lightcraft_engine::CRAFT_FONTS.is_empty() { + eprintln!("built without CRAFT_FONTS_DIR: no craft-fonts to check"); return; - }; + } + let thai = lightcraft_engine::CRAFT_FONTS.iter().find(|f| f.covers("Thai")).expect("font input includes Thai"); set_language(Locale::En); let definitions = font_definitions(lightcraft_engine::CRAFT_FONTS); let name = craft_font_name(thai); @@ -480,6 +481,20 @@ mod tests { let names = definitions.families.get(&family).expect("UI font family"); assert!(names.contains(&name) || (family == semibold_family() && names.contains(&strong)), "{family:?} falls back on {name}"); } + let ctx = egui::Context::default(); + ctx.set_fonts(definitions); + let mut frame = ctx.run_ui(egui::RawInput::default(), |_| {}); + frame.textures_delta.clear(); + ctx.fonts_mut(|fonts| { + for family in [FontFamily::Proportional, FontFamily::Name(FONT_SEMIBOLD.into()), FontFamily::Monospace] { + let id = FontId::new(13.0, family); + // Check the Thai portion; egui 0.36's has_glyph reports false for characters + // in the replacement glyph's own face (including normal monospace Latin). + for ch in "Studio34 ถ่ายภาพงานแต่งงาน".chars().filter(|ch| !ch.is_ascii()) { + assert!(fonts.has_glyph(&id, ch), "{id:?} has {ch}"); + } + } + }); } #[test] diff --git a/docs/web.md b/docs/web.md index a3f705db4..b275e837d 100644 --- a/docs/web.md +++ b/docs/web.md @@ -55,7 +55,8 @@ gzip size (5.25 MB). `wasm-opt -Oz`, when installed, shrinks it further. **Fonts.** The browser has no system fonts to fall back on, so CJK text comes from [craft-fonts](https://github.com/storytold/craft-fonts), the optional `CRAFT_FONTS_DIR` build input (`CRAFT_FONTS_DIR=../craft-fonts cargo xtask web`; release builds always set it). On wasm32 -`crates/engine/build.rs` embeds only BIZ UDPGothic Regular (UI and watermark fallback). The web +`crates/engine/build.rs` embeds BIZ UDPGothic Regular (UI and watermark fallback) and Noto Sans +Thai Regular (~214 KiB, for Thai file and folder names in every UI language). The web bundle also ships Noto Sans CJK SC Regular as `lightcraft_zh_hans.otf` and loads it before the UI starts. Keeping the 16 MB Chinese face separate leaves the module under Cloudflare's 25 MiB per-file limit (the older Japanese-only module measured 21.8 MB). Without `CRAFT_FONTS_DIR`, the diff --git a/flake.lock b/flake.lock index ecf87d370..a9953fd12 100644 --- a/flake.lock +++ b/flake.lock @@ -3,17 +3,17 @@ "craft-fonts": { "flake": false, "locked": { - "lastModified": 1791311399, - "narHash": "sha256-4E9OrthE5z39xFXpvpwn4Fn6RH3wX2sF56rZZejZT+I=", + "lastModified": 1791722036, + "narHash": "sha256-mNnDzBcOp1X10dzmiW0iBAkb74QjuzMO4slWudxXCTE=", "owner": "storytold", "repo": "craft-fonts", - "rev": "8dcdacd5153e64560d109541a47d806f26f048c0", + "rev": "3d9ab3a132ed0eff457e424226a435a901da30d3", "type": "github" }, "original": { "owner": "storytold", "repo": "craft-fonts", - "rev": "8dcdacd5153e64560d109541a47d806f26f048c0", + "rev": "3d9ab3a132ed0eff457e424226a435a901da30d3", "type": "github" } }, diff --git a/flake.nix b/flake.nix index e12b1f8af..51dd933f9 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ # glyphs. Pinned to the revision the release workflow uses — bump deliberately # (`nix flake update craft-fonts`); craftrules standards/fonts.md. craft-fonts = { - url = "github:storytold/craft-fonts/8dcdacd5153e64560d109541a47d806f26f048c0"; + url = "github:storytold/craft-fonts/3d9ab3a132ed0eff457e424226a435a901da30d3"; flake = false; }; }; diff --git a/packaging/windows/font-licences.ps1 b/packaging/windows/font-licences.ps1 new file mode 100644 index 000000000..3fd143adb --- /dev/null +++ b/packaging/windows/font-licences.ps1 @@ -0,0 +1,22 @@ +# Keep the upstream licence texts intact, including the copyright notices above the OFL. +function Write-FontLicences([string] $Destination, [string] $InterLicence, [string] $CraftFontsDir) { + if ($env:CRAFT_FONTS_REQUIRED -and -not $CraftFontsDir) { throw 'CRAFT_FONTS_REQUIRED is set but CRAFT_FONTS_DIR is missing' } + $parts = [Collections.Generic.List[string]]::new() + $parts.Add("LightCraft font licences`n") + $parts.Add([IO.File]::ReadAllText($InterLicence)) + if ($CraftFontsDir) { + $manifest = Join-Path $CraftFontsDir 'fonts\manifest.txt' + $licences = Get-Content -LiteralPath $manifest | ForEach-Object { + if ($_.Trim() -and -not $_.Trim().StartsWith('#')) { + $fields = $_ -split '\|' + if ($fields.Count -lt 6) { throw "Malformed font manifest line: $_" } + $fields[5].Trim() + } + } | Sort-Object -Unique + foreach ($licence in $licences) { + $parts.Add("`n--- $licence ---`n") + $parts.Add([IO.File]::ReadAllText((Join-Path $CraftFontsDir $licence))) + } + } + [IO.File]::WriteAllText($Destination, ($parts -join "`n"), [Text.UTF8Encoding]::new($false)) +} diff --git a/packaging/windows/lightcraft.wxs b/packaging/windows/lightcraft.wxs index 38fe8f391..533c24d20 100644 --- a/packaging/windows/lightcraft.wxs +++ b/packaging/windows/lightcraft.wxs @@ -206,6 +206,9 @@ + + + diff --git a/packaging/windows/package.ps1 b/packaging/windows/package.ps1 index e2cd2a8e7..fc031c6e3 100644 --- a/packaging/windows/package.ps1 +++ b/packaging/windows/package.ps1 @@ -112,6 +112,9 @@ $Stage = Join-Path $TargetDir "windows-package\$Arch" Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $Stage | Out-Null Copy-Item (Join-Path $Bin 'lightcraft.exe'), (Join-Path $Bin 'lightcraft-cli.exe') $Stage +# Include the embedded fonts' unmodified OFL texts in both installer and portable releases. +. (Join-Path $PSScriptRoot 'font-licences.ps1') +Write-FontLicences (Join-Path $Stage 'FONT-LICENCES.txt') (Join-Path $Root 'assets\fonts\OFL-Inter.txt') $env:CRAFT_FONTS_DIR & (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'lightcraft.exe') (Join-Path $Stage 'lightcraft-cli.exe') @@ -147,6 +150,7 @@ $Portable = Join-Path $TargetDir "windows-package\lightcraft-$Version-windows-$A Remove-Item -Recurse -Force $Portable -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $Portable | Out-Null Copy-Item (Join-Path $Stage '*.exe') $Portable +Copy-Item (Join-Path $Stage 'FONT-LICENCES.txt') $Portable foreach ($f in 'README.md', 'LICENSE', 'LICENSE-MIT', 'LICENSE-APACHE', 'NOTICE') { $p = Join-Path $Root $f if (Test-Path $p) { Copy-Item $p $Portable }