Fonts: the Japanese interface uses craft-fonts first; absolute CRAFT_FONTS_DIR
The merged Japanese interface (#38) looked only for a system font. When FilmCraft is built with craft-fonts, theme::install already appends the Japanese faces to every font family, so switching to Japanese uses them (no system scan) and works on the web too; otherwise it falls back to an installed system font as before. Docs use an absolute CRAFT_FONTS_DIR (build.rs runs in crates/text, so a relative path resolves from there). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9953befe28
commit
7f8e102b13
|
|
@ -95,7 +95,7 @@ reproduce an image (for example, point lists traced from someone else's icon).
|
|||
9. **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this
|
||||
repo.** Don't commit new font files (the small Latin UI fonts already in `assets/fonts/` stay). A
|
||||
font FilmCraft needs is added to craft-fonts, which the app reads through the optional build input
|
||||
`CRAFT_FONTS_DIR=<craft-fonts checkout>` (`crates/text/build.rs` embeds the fonts in its
|
||||
`CRAFT_FONTS_DIR=<absolute path of a craft-fonts checkout>` (`crates/text/build.rs` embeds the fonts in its
|
||||
`fonts/manifest.txt` as `filmcraft_text::fonts::CRAFT_FONTS`; unset, it is empty and the app uses its
|
||||
bundled and system fonts). Never add craft-fonts to a `Cargo.toml`. Code using `CRAFT_FONTS` must work
|
||||
when it is empty. Standard: [`craftrules/standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md).
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ FilmCraft is a clean-room, open-source, pure-Rust non-linear video editor target
|
|||
## Non-negotiables
|
||||
**Read [`AGENTS.md`](AGENTS.md) first; its rules override everything here.**
|
||||
- **Never crash** ([`AGENTS.md`](AGENTS.md) §0, standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md)). People depend on FilmCraft; a crash loses their work, so this outranks feature work. No `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!` or `unsafe` outside tests: return `Result<T, E>` and propagate with `?` (`ok_or`, `let … else`, `if let`). Treat every number from media, project files, presets or CLI/MCP/control params as hostile: `get()` instead of `[i]`, checked/saturating math, no division by possibly-zero values, capped allocations, bounded recursion. `lock().unwrap_or_else(PoisonError::into_inner)`; background jobs run under `catch_unwind`. The panic hook + UI `catch_unwind` (`crates/ui-egui/src/crash.rs`) are the last-resort guard, not a licence. Every crash fix gets a regression test that panicked before. In particular (§1): never use Adobe iconography, images or any other Adobe asset. Every asset must be openly licensed (OSS / public domain / Creative Commons, or original work by a contributor) and must have a `<file>.attribution` sidecar plus an entry in `ATTRIBUTION.md`. `cargo xtask assets` enforces this.
|
||||
- **Fonts** live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo (no new font files here; [`AGENTS.md`](AGENTS.md) §1.9, standard: [`craftrules/standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). Build with them: `git clone https://github.com/storytold/craft-fonts ../craft-fonts && CRAFT_FONTS_DIR=../craft-fonts cargo run -p filmcraft` (`crates/text/build.rs` embeds them as `filmcraft_text::fonts::CRAFT_FONTS`, used for Japanese in the UI and titles). Unset, `CRAFT_FONTS` is empty and everything must still build, test and run; never add craft-fonts to a `Cargo.toml`. Tests on its glyphs skip when it is empty, so run the gates with and without `CRAFT_FONTS_DIR` when you touch fonts.
|
||||
- **Fonts** live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo (no new font files here; [`AGENTS.md`](AGENTS.md) §1.9, standard: [`craftrules/standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). Build with them: `git clone https://github.com/storytold/craft-fonts ../craft-fonts && CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run -p filmcraft` (`crates/text/build.rs` embeds them as `filmcraft_text::fonts::CRAFT_FONTS`, used for Japanese in the UI and titles). Unset, `CRAFT_FONTS` is empty and everything must still build, test and run; never add craft-fonts to a `Cargo.toml`. Tests on its glyphs skip when it is empty, so run the gates with and without `CRAFT_FONTS_DIR` when you touch fonts.
|
||||
- **Clean-room.** Never read/disassemble anything inside Adobe app bundles (names/listings only). Never copy Adobe icons, presets, LUTs, fonts. Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, MLT, Kdenlive, Shotcut, Olive, LAME…). ffmpeg/ffprobe run only as external test oracles / fixture generators — never linked or shipped.
|
||||
- **Pure Rust** in the product. OS media APIs only via Rust bindings in `crates/platform` (hardware decoding; the one crate allowed `unsafe`, see `AGENTS.md` §0.3 and `docs/adr/0001-platform-ffi.md`), optional, behind traits, with our decoders as the fallback.
|
||||
- **Layering** ([`docs/architecture.md`](docs/architecture.md) §1, enforced by `cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal. L0 codec/container crates depend only on `filmcraft-bitstream`.
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ Japanese text in the interface and in titles comes from [craft-fonts](https://gi
|
|||
|
||||
```sh
|
||||
git clone https://github.com/storytold/craft-fonts ../craft-fonts
|
||||
CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p filmcraft
|
||||
CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run --release -p filmcraft
|
||||
```
|
||||
|
||||
The control protocol is documented in [docs/control-protocol.md](docs/control-protocol.md). Stuck, or want to show what you made? Ask in [Discord](https://discord.gg/artcraft).
|
||||
|
|
|
|||
|
|
@ -205,4 +205,4 @@ Status: ✅ done · 🟡 in progress · ⬜ not started. Estimates are remaining
|
|||
|
||||
### Interface localization
|
||||
|
||||
Edit > Language switches English/Japanese and persists in UI state. Commands `app.language.english` and `app.language.japanese` are reachable through the control channel. Core native/in-window menu labels are translated; untranslated labels use English. Japanese text uses a Japanese font already installed on the system (none is bundled); without one, switching to Japanese is refused with a message. Dialog/panel translation coverage remains open. Existing vertical text support is preserved.
|
||||
Edit > Language switches English/Japanese and persists in UI state. Commands `app.language.english` and `app.language.japanese` are reachable through the control channel. Core native/in-window menu labels are translated; untranslated labels use English. Japanese text uses the Japanese fonts from [craft-fonts](https://github.com/storytold/craft-fonts) when FilmCraft is built with them (`CRAFT_FONTS_DIR`; all official releases, including the web build), otherwise a Japanese font already installed on the system; with neither, switching to Japanese is refused with a message. Dialog/panel translation coverage remains open. Existing vertical text support is preserved.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
//! Optional craft-fonts build input (see storytold/craft-fonts `docs/integration.md` and
|
||||
//! craftrules `standards/fonts.md`).
|
||||
//!
|
||||
//! With `CRAFT_FONTS_DIR=<craft-fonts checkout>` this embeds every font listed in its
|
||||
//! With `CRAFT_FONTS_DIR=<absolute path of a craft-fonts checkout>` (a relative path resolves from
|
||||
//! `crates/text`) this embeds every font listed in its
|
||||
//! `fonts/manifest.txt` as [`CRAFT_FONTS`](crate::fonts::CRAFT_FONTS); unset, `CRAFT_FONTS` is
|
||||
//! empty and nothing else changes. A `CRAFT_FONTS_DIR` that is not a checkout is a warning, or an
|
||||
//! error with `CRAFT_FONTS_REQUIRED=1` (release builds).
|
||||
|
|
|
|||
|
|
@ -852,7 +852,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn japanese_vertical_titles_with_an_installed_font() {
|
||||
// Japanese glyphs come from a system font (none is bundled): skip where none is installed.
|
||||
// Japanese glyphs come from the craft-fonts (built with CRAFT_FONTS_DIR) or a system font:
|
||||
// skip where neither is present.
|
||||
crate::fonts::scan_system();
|
||||
if !crate::fonts::all_faces().iter().any(|f| "日本語縦書き".chars().all(|c| f.has_char(c))) {
|
||||
eprintln!("SKIPPED: no Japanese font installed");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
//! Interface translations. Command ids, document text and file names remain stable.
|
||||
//! Untranslated labels fall back to English so coverage can grow incrementally.
|
||||
//!
|
||||
//! No Japanese font is bundled: Japanese uses a font already installed on the system
|
||||
//! ([`system_japanese_font`]); without one, switching to Japanese is refused with a message.
|
||||
//! Japanese text uses the Japanese craft-fonts when FilmCraft was built with them (`CRAFT_FONTS_DIR`;
|
||||
//! `theme::install` already puts them in every font family, see [`craft_japanese_font`]), otherwise
|
||||
//! a font already installed on the system ([`system_japanese_font`]); with neither, switching to
|
||||
//! Japanese is refused with a message.
|
||||
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
|
|
@ -90,10 +92,24 @@ pub fn system_japanese_font() -> Option<Arc<egui::FontData>> {
|
|||
.clone()
|
||||
}
|
||||
|
||||
/// Add the system's Japanese font as the last fallback of every theme font family, from the next
|
||||
/// Whether the craft-fonts build input (empty unless built with `CRAFT_FONTS_DIR`) supplies a
|
||||
/// Japanese interface font: some craft-fonts face covers [`JAPANESE_SAMPLE`]. `theme::install` adds
|
||||
/// these faces to every font family, so nothing else needs installing (and no system scan runs).
|
||||
pub fn craft_japanese_font() -> bool {
|
||||
filmcraft_text::fonts::craft_japanese().next().is_some()
|
||||
&& filmcraft_text::fonts::all_faces()
|
||||
.iter()
|
||||
.any(|f| f.info.origin == filmcraft_text::fonts::CRAFT_ORIGIN && JAPANESE_SAMPLE.chars().all(|c| f.has_char(c)))
|
||||
}
|
||||
|
||||
/// Japanese for the interface: true when built with the craft-fonts (already installed by
|
||||
/// `theme::install`). Otherwise add the system's Japanese font as the last fallback of every theme font family, from the next
|
||||
/// pass on. Returns false (and changes nothing) when no Japanese font is installed. Call it again
|
||||
/// after `theme::install`, which replaces the font definitions.
|
||||
pub fn install_japanese_font(ctx: &egui::Context) -> bool {
|
||||
if craft_japanese_font() {
|
||||
return true;
|
||||
}
|
||||
let Some(font) = system_japanese_font() else { return false };
|
||||
let families = crate::theme::font_families()
|
||||
.into_iter()
|
||||
|
|
@ -229,8 +245,9 @@ mod tests {
|
|||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &crate::theme::Tokens::for_kind(crate::theme::ThemeKind::default()));
|
||||
let r = crate::menus::invoke(&mut app, &ctx, "app.language.japanese", serde_json::json!({}));
|
||||
if system_japanese_font().is_none() {
|
||||
// nothing is bundled: refused, and the interface stays English
|
||||
let craft = craft_japanese_font();
|
||||
if !craft && system_japanese_font().is_none() {
|
||||
// no craft-fonts and no system font: refused, and the interface stays English
|
||||
assert!(r.is_err(), "{r:?}");
|
||||
assert_eq!(app.ui.language, Language::En);
|
||||
return;
|
||||
|
|
@ -239,10 +256,16 @@ mod tests {
|
|||
let mut output = ctx.run_ui(egui::RawInput::default(), |_| {});
|
||||
output.textures_delta.clear();
|
||||
ctx.fonts_mut(|fonts| {
|
||||
// every theme family falls back to the system Japanese font
|
||||
// every theme family falls back to the craft-fonts (when built with them; no system
|
||||
// font is added then) or to the system Japanese font
|
||||
for family in crate::theme::font_families() {
|
||||
let stack = fonts.definitions().families.get(&family).cloned().unwrap_or_default();
|
||||
assert_eq!(stack.last().map(String::as_str), Some(JAPANESE_FONT), "{family:?}: {stack:?}");
|
||||
if craft {
|
||||
assert!(stack.last().is_some_and(|n| n.starts_with("craft:")), "{family:?}: {stack:?}");
|
||||
assert!(!stack.iter().any(|n| n == JAPANESE_FONT), "{family:?}: {stack:?}");
|
||||
} else {
|
||||
assert_eq!(stack.last().map(String::as_str), Some(JAPANESE_FONT), "{family:?}: {stack:?}");
|
||||
}
|
||||
}
|
||||
// and the glyphs resolve. (Only families whose replacement-box face is another font:
|
||||
// egui's `has_glyph` reports false for any character served by the face it also uses
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ pub fn panel_command_id(p: PanelKind) -> String {
|
|||
/// Execute a UI or engine command by id.
|
||||
pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, params: Value) -> Result<Value, String> {
|
||||
if matches!(id, "app.language.english" | "app.language.japanese") {
|
||||
// no Japanese font is bundled: Japanese needs one installed on the system
|
||||
// Japanese needs the craft-fonts (built with CRAFT_FONTS_DIR) or a font installed on the system
|
||||
if id == "app.language.japanese" && !crate::i18n::install_japanese_font(ctx) {
|
||||
return Err("no Japanese font is installed on this system (for example Noto Sans CJK JP); the interface stays in English".into());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,10 +53,11 @@ dependency:
|
|||
|
||||
```sh
|
||||
git clone https://github.com/storytold/craft-fonts ../craft-fonts
|
||||
CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p filmcraft
|
||||
CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run --release -p filmcraft
|
||||
```
|
||||
|
||||
`crates/text/build.rs` reads its `fonts/manifest.txt` and embeds the fonts as
|
||||
Use an absolute path: `build.rs` runs in `crates/text`, so a relative `CRAFT_FONTS_DIR` would
|
||||
resolve from there. `crates/text/build.rs` reads its `fonts/manifest.txt` and embeds the fonts as
|
||||
`filmcraft_text::fonts::CRAFT_FONTS` (native: all of them; the web build: only BIZ UDPGothic
|
||||
Regular, to keep the wasm small). The Japanese ones become the last fallback of every UI font family
|
||||
(`crates/ui-egui/src/theme.rs`, BIZ UDPGothic first) and fallback faces in the text engine (Gothic
|
||||
|
|
|
|||
Loading…
Reference in New Issue