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 }