diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 6f2ac49..e336d78 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -1,8 +1,9 @@ # Font attribution Every font in `fonts/`, with its source and licence. The files are byte-identical to the pinned -upstream sources (checked by `craft-fonts-tests`, which verifies each SHA-256 in -`fonts/manifest.txt`). +upstream sources (checked by `craft-fonts-tests`, which verifies each SHA-256 in the manifest that +lists it: `fonts/manifest.txt` for the default embedded set, and +`fonts/simplified-chinese-manifest.txt` for the opt-in native-only Simplified Chinese face). | File | Family / style | Author | Source (pinned) | Licence | |---|---|---|---|---| @@ -14,7 +15,17 @@ upstream sources (checked by `craft-fonts-tests`, which verifies each SHA-256 in | `fonts/noto-sans-arabic/NotoSansArabic.ttf` | Noto Sans Arabic (variable) | The Noto Project Authors | [google/fonts@7085eb8](https://github.com/google/fonts/tree/7085eb89a950e85db5b166b7a58d414544b4140c/ofl/notosansarabic) | OFL-1.1 (`fonts/noto-sans-arabic/OFL.txt`) | | `fonts/noto-sans-thai/NotoSansThai.ttf` | Noto Sans Thai (variable) | The Noto Project Authors | [google/fonts@8b0a1d0](https://github.com/google/fonts/tree/8b0a1d0f5983c89bc2b93f1b5fb55f9e252744b5/ofl/notosansthai) | OFL-1.1 (`fonts/noto-sans-thai/OFL.txt`) | | `fonts/noto-sans-georgian/NotoSansGeorgian.ttf` | Noto Sans Georgian (variable) | The Noto Project Authors | [google/fonts@8b0a1d0](https://github.com/google/fonts/tree/8b0a1d0f5983c89bc2b93f1b5fb55f9e252744b5/ofl/notosansgeorgian) | OFL-1.1 (`fonts/noto-sans-georgian/OFL.txt`) | +| `fonts/nanum-gothic/NanumGothic-Regular.ttf` | NanumGothic Regular | NHN Corporation; Sandoll Communications | [google/fonts@bd8f81d](https://github.com/google/fonts/tree/bd8f81ddb5c74d5c8897b36ad88b440266245103/ofl/nanumgothic) | OFL-1.1 (`fonts/nanum-gothic/OFL.txt`) | +| `fonts/noto-sans-symbols/NotoSansSymbols.ttf` | Noto Sans Symbols Regular | The Noto Project Authors | [google/fonts@47d54ca](https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols) | OFL-1.1 (`fonts/noto-sans-symbols/OFL.txt`) | +| `fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf` | Noto Sans Symbols 2 Regular | The Noto Project Authors | [google/fonts@47d54ca](https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols2) | OFL-1.1 (`fonts/noto-sans-symbols-2/OFL.txt`) | +| `fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf` | Noto Sans Hebrew Regular | The Noto Project Authors | [notofonts/hebrew NotoSansHebrew-v3.001](https://github.com/notofonts/hebrew/releases/tag/NotoSansHebrew-v3.001) | OFL-1.1 (`fonts/noto-sans-hebrew/OFL.txt`) | +| `fonts/lxgw-wenkai-lite/LXGWWenKaiLite-Regular.ttf` | LXGW WenKai Lite Regular | LXGW / Luo Xia Gu Wu; derived from Fontworks Klee One | [lxgw/LxgwWenKai-Lite v1.530](https://github.com/lxgw/LxgwWenKai-Lite/releases/tag/v1.530) | OFL-1.1 (`fonts/lxgw-wenkai-lite/OFL.txt`) | The OFL allows these fonts to be bundled, embedded and redistributed with software, provided the licence text travels with them and the fonts are not sold on their own. "Shippori Mincho", -"BIZ UD" and "Noto" are the authors' names for their fonts; we use the files unmodified. +"BIZ UD", "Noto" and "LXGW WenKai Lite" are the authors' names for their fonts; we use the files +unmodified. LXGW WenKai Lite's upstream OFL reserves the names 霞鹜, 霞鶩, 落霞孤鹜, 落霞孤鶩 and +LXGW; the bundled release member and Klee-One copyright notice stay byte-identical to upstream. + +The Symbols variable font keeps its upstream bytes; only its filename changes from +`NotoSansSymbols[wght].ttf` to `NotoSansSymbols.ttf`. diff --git a/README.md b/README.md index f0b3b4f..eec2987 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,11 @@ This library is meant to have the following: | Path | What | |---|---| | `fonts/` | The font files, one directory per family, each with its licence. | -| `fonts/manifest.txt` | Machine-readable list of every font: family, style, file, scripts, licence, SHA-256, pinned source. | +| `fonts/manifest.txt` | Machine-readable list of the default fonts: family, style, file, scripts, licence, SHA-256, pinned source. | +| `fonts/simplified-chinese-manifest.txt` | Opt-in native-only Simplified Chinese row, same eight-field format. Not in `FONTS` or the embed payload. | | `ATTRIBUTION.md` | Authors, pinned sources and licences. | | `crates/craft-fonts` | Rust: the manifest as a static table, finding a checkout (`CRAFT_FONTS_DIR`), loading it, and an `embed` feature that compiles the fonts in. | -| `crates/craft-fonts-tests` | Tests asserted on these fonts: checksums, licences, family names, Japanese coverage and outlines, vertical-text (`vert`/`vrt2`) support. | +| `crates/craft-fonts-tests` | Tests asserted on these fonts: checksums, licences, family/style names, Japanese/Korean/Hebrew/symbol coverage and outlines, static-face metadata and vertical-text (`vert`/`vrt2`) support. | | `docs/integration.md` | How an app uses craft-fonts as a fully optional build input. | ### Fonts @@ -38,17 +39,25 @@ This library is meant to have the following: | Noto Sans Arabic | Regular (variable: wdth, wght) | Arabic UI and document text (sans) | OFL-1.1 | | Noto Sans Thai | Regular (variable: wdth, wght) | Thai UI and document text (sans) | OFL-1.1 | | Noto Sans Georgian | Regular (variable: wdth, wght) | Georgian UI and document text (sans) | OFL-1.1 | +| NanumGothic | Regular | Korean UI and document text | OFL-1.1 | +| Noto Sans Symbols | Regular (variable: wght) | UI key/modifier symbols (`⌃`, arrows) | OFL-1.1 | +| Noto Sans Symbols 2 | Regular | UI symbols (`✕`, `◀`, `✓`) | OFL-1.1 | +| Noto Sans Hebrew | Regular (static) | Hebrew UI and document text | OFL-1.1 | +| LXGW WenKai Lite | Regular (static) | Native-only opt-in Simplified Chinese fallback (Kai) | OFL-1.1 | ## How the apps use it **Fully optional, never required.** The apps don't list craft-fonts in `Cargo.toml` (Cargo would fetch it for every build). Building with `CRAFT_FONTS_DIR=` makes an app embed the fonts in `fonts/manifest.txt`; without it, the app builds and runs as before. Release -builds check out a pinned commit of this repo and set the option. See -[`docs/integration.md`](docs/integration.md). +builds check out a pinned commit of this repo and set the option. Apps that explicitly opt in may +also read `fonts/simplified-chinese-manifest.txt` on native builds only; it is never part of the +default set or a wasm allowlist. See [`docs/integration.md`](docs/integration.md). **Font assets for the Crafting Apps belong here**, not in the app repos: add the file, its licence, -a manifest line and an attribution row, and the tests check it. +a manifest line and an attribution row, and the tests check it. Put a face that only some native +consumers should embed in the opt-in manifest described in `docs/integration.md`, not in the +default `fonts/manifest.txt`. ## Develop diff --git a/crates/craft-fonts-tests/Cargo.toml b/crates/craft-fonts-tests/Cargo.toml index a27ff7d..9abe4a0 100644 --- a/crates/craft-fonts-tests/Cargo.toml +++ b/crates/craft-fonts-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "craft-fonts-tests" -description = "Assertions on the fonts themselves: checksums, licences, names, script coverage and vertical-text support." +description = "Assertions on the fonts themselves: checksums, licences, names, static-face metadata, script coverage and vertical-text support." version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/craft-fonts-tests/tests/fonts.rs b/crates/craft-fonts-tests/tests/fonts.rs index dc85440..3e490f2 100644 --- a/crates/craft-fonts-tests/tests/fonts.rs +++ b/crates/craft-fonts-tests/tests/fonts.rs @@ -1,8 +1,12 @@ //! Assertions on the font files themselves. -use craft_fonts::{FONTS, Font, for_script}; +use craft_fonts::{Entry, FONTS, Font, for_script, parse_manifest}; use sha2::{Digest, Sha256}; -use skrifa::raw::TableProvider; +use skrifa::attribute::{Style, Weight}; +use skrifa::instance::{LocationRef, Size}; +use skrifa::outline::{DrawSettings, OutlinePen}; +use skrifa::raw::tables::os2::SelectionFlags; +use skrifa::raw::{ReadError, TableProvider}; use skrifa::{FontRef, MetadataProvider, string::StringId}; fn bytes(f: &Font) -> Vec { @@ -60,6 +64,133 @@ fn every_font_is_the_family_the_manifest_says() { } } +#[test] +fn every_font_is_the_style_the_manifest_says() { + for f in FONTS { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + let names: Vec = [ + StringId::SUBFAMILY_NAME, + StringId::TYPOGRAPHIC_SUBFAMILY_NAME, + ] + .into_iter() + .flat_map(|id| { + font.localized_strings(id) + .map(|s| s.to_string()) + .collect::>() + }) + .collect(); + assert!( + names.iter().any(|n| n == f.style), + "{}: name table styles are {names:?}, manifest says {:?}", + f.file, + f.style + ); + } +} + +/// The exact manifest rows for the faces this prerequisite adds. The rows are pinned to the +/// verified upstream hashes above; a missing row or a drifted path/script/licence fails here +/// rather than silently skipping the Korean and symbol coverage below. +#[test] +fn prerequisite_faces_have_the_documented_manifest_rows() { + fn exact(family: &str, style: &str, file: &str, scripts: &[&str]) -> &'static Font { + let found: Vec<&'static Font> = FONTS.iter().filter(|f| f.family == family).collect(); + assert_eq!( + found.len(), + 1, + "manifest lists {} rows for {family}; expected exactly one", + found.len() + ); + let f = found[0]; + assert_eq!(f.style, style, "{}: manifest style", f.file); + assert_eq!(f.file, file, "{family}: manifest file"); + assert_eq!(f.scripts, scripts, "{family}: manifest scripts"); + assert_eq!(f.licence, "OFL-1.1", "{family}: licence"); + f + } + + exact( + "NanumGothic", + "Regular", + "fonts/nanum-gothic/NanumGothic-Regular.ttf", + &["Hang", "Kore"], + ); + exact( + "Noto Sans Symbols", + "Regular", + "fonts/noto-sans-symbols/NotoSansSymbols.ttf", + &["Zsym"], + ); + + // Symbols 2 is required, not optional: the pinned Symbols 1 bytes lack U+2715 `✕` and + // U+25C0 `◀`, which Symbols 2 provides. + exact( + "Noto Sans Symbols 2", + "Regular", + "fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf", + &["Zsym"], + ); +} + +/// An outline pen that counts drawing commands, so a cmap entry with an empty outline cannot +/// pass as coverage. +struct OutlineCount(usize); + +impl OutlinePen for OutlineCount { + fn move_to(&mut self, _: f32, _: f32) {} + fn line_to(&mut self, _: f32, _: f32) { + self.0 += 1; + } + fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { + self.0 += 1; + } + fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { + self.0 += 1; + } + fn close(&mut self) {} +} + +/// Assert that `c` maps to a glyph that actually draws something in `font`. +fn assert_glyph_outline(font: &FontRef<'_>, file: &str, c: char) { + let gid = font + .charmap() + .map(c) + .unwrap_or_else(|| panic!("{file}: no glyph for {c}")); + let glyph = font + .outline_glyphs() + .get(gid) + .unwrap_or_else(|| panic!("{file}: no outline for {c}")); + let mut pen = OutlineCount(0); + glyph + .draw( + DrawSettings::unhinted(Size::new(64.0), LocationRef::default()), + &mut pen, + ) + .unwrap_or_else(|e| panic!("{file}: drawing {c}: {e}")); + assert!(pen.0 > 0, "{file}: {c} maps to a glyph that draws nothing"); +} + +/// Assert that `f` itself (not merely some other face) maps `c` to a drawn outline. +fn assert_face_glyph_outline(f: &Font, c: char) { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + assert_glyph_outline(&font, f.file, c); +} + +/// The one manifest row for `family`. +fn face(family: &str) -> &'static Font { + let mut found = FONTS.iter().filter(|f| f.family == family); + let f = found + .next() + .unwrap_or_else(|| panic!("the manifest lists no {family} row")); + assert!( + found.next().is_none(), + "the manifest lists more than one {family} row" + ); + f +} + /// Text every Japanese font must cover: kana, common kanji, Japanese punctuation, full-width /// forms and basic Latin (UI strings mix them). const JAPANESE: &str = concat!( @@ -94,42 +225,11 @@ fn japanese_fonts_cover_japanese_text() { #[test] fn japanese_glyphs_have_outlines() { - use skrifa::instance::{LocationRef, Size}; - use skrifa::outline::{DrawSettings, OutlinePen}; - struct Count(usize); - impl OutlinePen for Count { - fn move_to(&mut self, _: f32, _: f32) {} - fn line_to(&mut self, _: f32, _: f32) { - self.0 += 1; - } - fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) { - self.0 += 1; - } - fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) { - self.0 += 1; - } - fn close(&mut self) {} - } for f in for_script("Jpan") { let data = bytes(f); let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); - let outlines = font.outline_glyphs(); for c in ['漢', 'あ', 'ア', '。'] { - let gid = font - .charmap() - .map(c) - .unwrap_or_else(|| panic!("{}: no glyph for {c}", f.file)); - let glyph = outlines - .get(gid) - .unwrap_or_else(|| panic!("{}: no outline for {c}", f.file)); - let mut pen = Count(0); - glyph - .draw( - DrawSettings::unhinted(Size::new(64.0), LocationRef::default()), - &mut pen, - ) - .unwrap_or_else(|e| panic!("{}: drawing {c}: {e}", f.file)); - assert!(pen.0 > 0, "{} {}: {c} draws nothing", f.family, f.style); + assert_glyph_outline(&font, f.file, c); } } } @@ -227,3 +327,402 @@ fn georgian_fonts_cover_georgian_text() { ), ); } + +/// Korean interface labels used by the GridCraft UI, not a single sample character. +const KOREAN_UI: &str = concat!( + "데이터 한글 파일 편집 보기 삽입 서식 저장 열기 닫기 인쇄 취소 확인 ", + "가나다라마바사아자차카타파하", +); + +#[test] +fn korean_fonts_cover_korean_ui_text() { + covers("Hang", KOREAN_UI); + covers("Kore", KOREAN_UI); +} + +/// The Korean `Hang` face really covers all modern Hangul syllables and the 51 compatibility +/// Jamo. These exact counts come from the pinned NanumGothic cmap, not from a sample string. +#[test] +fn korean_fonts_cover_every_modern_syllable_and_compatibility_jamo() { + let syllables = '\u{ac00}'..='\u{d7a3}'; + let jamo = '\u{3131}'..='\u{3163}'; + assert_eq!(syllables.clone().count(), 11_172); + assert_eq!(jamo.clone().count(), 51); + + let fonts: Vec<&Font> = for_script("Hang").collect(); + assert!(!fonts.is_empty(), "the manifest lists no Hang font"); + for f in fonts { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + let charmap = font.charmap(); + let missing_syllables = syllables + .clone() + .filter(|c| charmap.map(*c).is_none()) + .count(); + let missing_jamo = jamo.clone().filter(|c| charmap.map(*c).is_none()).count(); + assert_eq!( + missing_syllables, 0, + "{}: {missing_syllables} of 11,172 modern Hangul syllables are missing", + f.file + ); + assert_eq!( + missing_jamo, 0, + "{}: {missing_jamo} of the 51 compatibility Jamo are missing", + f.file + ); + } +} + +#[test] +fn korean_glyphs_have_outlines() { + let fonts: Vec<&Font> = for_script("Hang").collect(); + assert!( + !fonts.is_empty(), + "the manifest lists no Hang font; add NanumGothic Regular from the pinned source in docs/egui-037-font-coverage-plan.md" + ); + for f in fonts { + let data = bytes(f); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + for c in ['한', '글', '각', '힣'] { + assert_glyph_outline(&font, f.file, c); + } + } +} + +/// Exact face assignment for the observed symbol gaps. Symbols 1 covers `⌃` and two of the +/// arrows but not `✕`, `◀` or `✓`; Symbols 2 covers `✕`, `◀` and `✓` but not `⌃`. `📋` is +/// supplied by egui's emoji faces, so it is intentionally not asserted against craft-fonts. +#[test] +fn zsym_faces_cover_the_documented_symbol_gaps() { + let symbols = face("Noto Sans Symbols"); + let symbols2 = face("Noto Sans Symbols 2"); + assert_eq!( + symbols.scripts, + ["Zsym"].as_slice(), + "{}: scripts", + symbols.file + ); + assert_eq!( + symbols2.scripts, + ["Zsym"].as_slice(), + "{}: scripts", + symbols2.file + ); + + for c in ['⌃', '↘', '↑'] { + assert_face_glyph_outline(symbols, c); + } + for c in ['✕', '◀', '✓'] { + assert_face_glyph_outline(symbols2, c); + } +} + +/// The Hebrew face is the verified upstream static Regular release, not the old variable +/// fixture. It must remain one face/file: no `fvar` (and no named instances), no `avar`, no +/// `STAT`, OS/2 weight 400 and an upright `REGULAR` selection. A consumer registers this one +/// face once per family set that includes it, never the nine instances of the variable font. +#[test] +fn hebrew_face_is_a_single_static_regular_face() { + const HEBREW_FILE: &str = "fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf"; + const HEBREW_LICENCE_FILE: &str = "fonts/noto-sans-hebrew/OFL.txt"; + const HEBREW_SOURCE: &str = + "https://github.com/notofonts/hebrew/releases/tag/NotoSansHebrew-v3.001"; + const HEBREW_SHA256: &str = "671951828bd5c95db818e5bb12dcea2d0c0dda00311888522be061ee6835125e"; + + let f = face("Noto Sans Hebrew"); + assert_eq!(for_script("Hebr").count(), 1); + assert_eq!(f.style, "Regular"); + assert_eq!(f.file, HEBREW_FILE); + assert_eq!(f.scripts, ["Hebr"].as_slice()); + assert_eq!(f.licence, "OFL-1.1"); + assert_eq!(f.licence_file, HEBREW_LICENCE_FILE); + assert_eq!(f.source, HEBREW_SOURCE); + assert_eq!(f.sha256, HEBREW_SHA256); + + let data = bytes(f); + // A TrueType sfnt, not a TrueType Collection: one face in the file. + assert_eq!(&data[..4], &[0x00, 0x01, 0x00, 0x00]); + let font = FontRef::new(&data).unwrap_or_else(|e| panic!("{}: {e}", f.file)); + for (name, table) in [ + ("fvar", font.fvar().map(|_| ())), + ("avar", font.avar().map(|_| ())), + ("STAT", font.stat().map(|_| ())), + ] { + assert!( + matches!(table, Err(ReadError::TableIsMissing(_))), + "{}: expected no {name} table; the Hebrew file must stay a single static face", + f.file + ); + } + + let os2 = font + .os2() + .unwrap_or_else(|e| panic!("{}: OS/2: {e}", f.file)); + assert_eq!(os2.us_weight_class(), 400, "{}: OS/2 weight", f.file); + assert_eq!(os2.us_width_class(), 5, "{}: OS/2 width", f.file); + let selection = os2.fs_selection(); + assert!( + selection.contains(SelectionFlags::REGULAR), + "{}: fsSelection lacks REGULAR", + f.file + ); + for flag in [ + SelectionFlags::ITALIC, + SelectionFlags::BOLD, + SelectionFlags::OBLIQUE, + ] { + assert!( + !selection.contains(flag), + "{}: fsSelection unexpectedly contains {flag:?}", + f.file + ); + } + + let attributes = font.attributes(); + assert_eq!(attributes.weight, Weight::NORMAL, "{}: weight", f.file); + assert_eq!(attributes.style, Style::Normal, "{}: style", f.file); +} + +/// All 27 Hebrew letters `U+05D0..U+05EA` map to drawn outlines, not merely cmap entries. +#[test] +fn hebrew_letters_have_drawn_outlines() { + let f = face("Noto Sans Hebrew"); + let letters = '\u{05d0}'..='\u{05ea}'; + assert_eq!(letters.clone().count(), 27); + for c in letters { + assert_face_glyph_outline(f, c); + } +} + +/// All 24 assigned points, marks and punctuation in `U+05B0..U+05C7` map to drawn outlines. +/// The combining marks require GPOS positioning in a shaping engine. This font-only test +/// asserts coverage, not application-level niqqud positioning or bidi order. +#[test] +fn hebrew_points_have_drawn_outlines() { + let f = face("Noto Sans Hebrew"); + let points = '\u{05b0}'..='\u{05c7}'; + assert_eq!(points.clone().count(), 24); + for c in points { + assert_face_glyph_outline(f, c); + } +} + +/// The unpointed Hebrew sample used by mixed acceptance text such as `שלום deck 42`. +/// craft-fonts only claims the Hebrew part here, which must map to drawn outlines. +#[test] +fn hebrew_fonts_cover_unpointed_hebrew_text() { + const HEBREW_SAMPLE: &str = "שלום עברית אבגדהוזחטיכלמנסעפצקרשת"; + covers("Hebr", HEBREW_SAMPLE); + let f = face("Noto Sans Hebrew"); + for c in "שלום".chars() { + assert_face_glyph_outline(f, c); + } +} + +/// The opt-in Simplified Chinese manifest. It is deliberately separate from +/// `fonts/manifest.txt`: consumers that do not opt in (including every wasm build) must not +/// embed this face, and it is not part of craft-fonts' `FONTS`/embed payload. +const SIMPLIFIED_CHINESE_MANIFEST: &str = "fonts/simplified-chinese-manifest.txt"; +const LXGW_FILE: &str = "fonts/lxgw-wenkai-lite/LXGWWenKaiLite-Regular.ttf"; +const LXGW_LICENCE_FILE: &str = "fonts/lxgw-wenkai-lite/OFL.txt"; +const LXGW_SOURCE: &str = "https://github.com/lxgw/LxgwWenKai-Lite/releases/tag/v1.530"; +const LXGW_SHA256: &str = "f799a538a3355877e597a0ae04050e897932c9ae7689eb9227c2953c5dd549ea"; +/// The plan's hard ceiling for one added font file. +const LXGW_MAX_BYTES: usize = 24 * 1024 * 1024; +/// The DeckCraft UI sample whose missing `简` U+7B80 triggered this opt-in face. +const DECK_CHINESE_SAMPLE: &str = "简体中文演示文稿"; + +fn optional_entries(relative: &str) -> Vec { + let path = craft_fonts_tests::repo_root().join(relative); + let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display())); + parse_manifest(&text).unwrap_or_else(|e| panic!("{}: {e}", path.display())) +} + +fn lxgw_entry() -> Entry { + let entries = optional_entries(SIMPLIFIED_CHINESE_MANIFEST); + assert_eq!( + entries.len(), + 1, + "{SIMPLIFIED_CHINESE_MANIFEST} must hold exactly the one reviewed LXGW row" + ); + entries.into_iter().next().unwrap() +} + +#[test] +fn simplified_chinese_opt_in_row_is_exact_and_not_in_the_default_manifest() { + let e = lxgw_entry(); + assert_eq!(e.family, "LXGW WenKai Lite"); + assert_eq!(e.style, "Regular"); + assert_eq!(e.file, LXGW_FILE); + assert_eq!( + e.scripts.iter().map(String::as_str).collect::>(), + ["Hani", "Hans"], + "DeckCraft's script classifier emits Hani; Hans names the Simplified Chinese face" + ); + assert_eq!(e.licence, "OFL-1.1"); + assert_eq!(e.licence_file, LXGW_LICENCE_FILE); + assert_eq!(e.sha256, LXGW_SHA256); + assert_eq!(e.source, LXGW_SOURCE); + + assert!( + !FONTS.iter().any(|f| f.family == e.family.as_str()), + "the opt-in LXGW face must not be in the default FONTS table or embed payload" + ); + let default = + std::fs::read_to_string(craft_fonts_tests::repo_root().join("fonts/manifest.txt")).unwrap(); + assert!( + !default.contains("LXGW") && !default.contains("simplified-chinese"), + "the default font manifest must not list the opt-in Chinese face" + ); +} + +#[test] +fn simplified_chinese_face_matches_its_hash_licence_and_size_ceiling() { + let e = lxgw_entry(); + let data = bytes_from_entry(&e); + let digest = Sha256::digest(&data); + let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect(); + assert_eq!( + hex, e.sha256, + "{}: the file changed; update the opt-in manifest only after checking the new upstream source", + e.file + ); + assert!( + data.len() <= LXGW_MAX_BYTES, + "{}: {} bytes exceeds the 24 MiB single-face ceiling", + e.file, + data.len() + ); + assert_eq!( + std::path::Path::new(&e.file).parent(), + std::path::Path::new(&e.licence_file).parent(), + "the licence must sit next to the font it covers" + ); + let licence = std::fs::read_to_string(craft_fonts_tests::repo_root().join(&e.licence_file)) + .unwrap_or_else(|err| panic!("{}: {err}", e.licence_file)); + assert!(licence.contains("SIL OPEN FONT LICENSE Version 1.1")); +} + +#[test] +fn simplified_chinese_licence_retains_klee_copyright_and_reserved_font_names() { + let e = lxgw_entry(); + let licence = std::fs::read_to_string(craft_fonts_tests::repo_root().join(&e.licence_file)) + .unwrap_or_else(|err| panic!("{}: {err}", e.licence_file)); + assert!(licence.contains("Reserved Font Name")); + assert!(licence.contains("Copyright 2020 The Klee Project Authors")); + for name in ["霞鹜", "霞鶩", "落霞孤鹜", "落霞孤鶩", "LXGW"] { + assert!( + licence.contains(name), + "{}: the upstream Reserved Font Name {name} is missing", + e.licence_file + ); + } +} + +#[test] +fn simplified_chinese_face_is_one_static_regular_400_face() { + let e = lxgw_entry(); + let data = bytes_from_entry(&e); + assert_eq!( + &data[..4], + &[0x00, 0x01, 0x00, 0x00], + "{}: expected a single TrueType sfnt, not a collection or variable font", + e.file + ); + let font = FontRef::new(&data).unwrap_or_else(|err| panic!("{}: {err}", e.file)); + for (name, table) in [ + ("fvar", font.fvar().map(|_| ())), + ("avar", font.avar().map(|_| ())), + ("STAT", font.stat().map(|_| ())), + ] { + assert!( + matches!(table, Err(ReadError::TableIsMissing(_))), + "{}: expected no {name} table; the opt-in file must stay a single static face", + e.file + ); + } + + let families: Vec = [StringId::TYPOGRAPHIC_FAMILY_NAME, StringId::FAMILY_NAME] + .into_iter() + .flat_map(|id| { + font.localized_strings(id) + .map(|s| s.to_string()) + .collect::>() + }) + .collect(); + assert!( + families.iter().any(|n| n.as_str() == e.family), + "{}: name table has {families:?}, manifest says {:?}", + e.file, + e.family + ); + let styles: Vec = [ + StringId::SUBFAMILY_NAME, + StringId::TYPOGRAPHIC_SUBFAMILY_NAME, + ] + .into_iter() + .flat_map(|id| { + font.localized_strings(id) + .map(|s| s.to_string()) + .collect::>() + }) + .collect(); + assert!( + styles.iter().any(|n| n.as_str() == e.style), + "{}: name table styles are {styles:?}, manifest says {:?}", + e.file, + e.style + ); + let versions: Vec = font + .localized_strings(StringId::VERSION_STRING) + .map(|s| s.to_string()) + .collect(); + assert!( + versions.iter().any(|v| v.contains("Version 1.530")), + "{}: expected Version 1.530, found {versions:?}", + e.file + ); + + let os2 = font + .os2() + .unwrap_or_else(|err| panic!("{}: OS/2: {err}", e.file)); + assert_eq!(os2.us_weight_class(), 400, "{}: OS/2 weight", e.file); + let selection = os2.fs_selection(); + assert!( + selection.contains(SelectionFlags::REGULAR), + "{}: fsSelection lacks REGULAR", + e.file + ); + for flag in [ + SelectionFlags::ITALIC, + SelectionFlags::BOLD, + SelectionFlags::OBLIQUE, + ] { + assert!( + !selection.contains(flag), + "{}: fsSelection unexpectedly contains {flag:?}", + e.file + ); + } + let attributes = font.attributes(); + assert_eq!(attributes.weight, Weight::NORMAL, "{}: weight", e.file); + assert_eq!(attributes.style, Style::Normal, "{}: style", e.file); +} + +#[test] +fn simplified_chinese_face_draws_the_deckcraft_sample_and_jian() { + let e = lxgw_entry(); + let data = bytes_from_entry(&e); + let font = FontRef::new(&data).unwrap_or_else(|err| panic!("{}: {err}", e.file)); + assert_eq!(DECK_CHINESE_SAMPLE.chars().count(), 8); + assert!(DECK_CHINESE_SAMPLE.contains('简')); + for c in DECK_CHINESE_SAMPLE.chars() { + assert_glyph_outline(&font, &e.file, c); + } + assert_glyph_outline(&font, &e.file, '简'); +} + +fn bytes_from_entry(e: &Entry) -> Vec { + std::fs::read(craft_fonts_tests::repo_root().join(&e.file)) + .unwrap_or_else(|err| panic!("{}: {err}", e.file)) +} diff --git a/crates/craft-fonts/Cargo.toml b/crates/craft-fonts/Cargo.toml index ac41111..25fe08e 100644 --- a/crates/craft-fonts/Cargo.toml +++ b/crates/craft-fonts/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true publish = false [features] -# Compile every font in the manifest into the binary (about 24 MB). +# Compile every font in the manifest into the binary (about 43 MiB). embed = [] [lints] diff --git a/crates/craft-fonts/src/lib.rs b/crates/craft-fonts/src/lib.rs index 2121a5b..7e46f83 100644 --- a/crates/craft-fonts/src/lib.rs +++ b/crates/craft-fonts/src/lib.rs @@ -3,7 +3,9 @@ //! The fonts live in this repository's `fonts/` directory and are listed in `fonts/manifest.txt`. //! This crate exposes that manifest ([`FONTS`]), finds a craft-fonts checkout at run time //! ([`dir_from_env`]), loads fonts from one ([`load`]), and with the `embed` feature compiles them -//! into the binary ([`embedded`]). +//! into the binary ([`embedded`]). `fonts/simplified-chinese-manifest.txt` is a separate opt-in +//! native-only manifest; it is not part of [`FONTS`] or the `embed` payload, and consumers or +//! tests parse it with [`parse_manifest`]. //! //! The apps do **not** depend on this crate: Cargo resolves even optional git dependencies, so a //! dependency would make craft-fonts required for every build. Apps read `fonts/manifest.txt` diff --git a/crates/craft-fonts/src/parse.rs b/crates/craft-fonts/src/parse.rs index bcb417d..eaeee12 100644 --- a/crates/craft-fonts/src/parse.rs +++ b/crates/craft-fonts/src/parse.rs @@ -1,6 +1,7 @@ //! The `fonts/manifest.txt` format, shared by `build.rs` and the library (`#[path]`-included by -//! both, so it has no dependencies). Apps that read the manifest from their own `build.rs` can -//! copy this file. +//! both, so it has no dependencies). The same parser reads the opt-in +//! `fonts/simplified-chinese-manifest.txt`. Apps that read a manifest from their own `build.rs` +//! can copy this file. /// One line of the manifest. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/docs/egui-037-deck-font-gaps-plan.md b/docs/egui-037-deck-font-gaps-plan.md new file mode 100644 index 0000000..65f05ef --- /dev/null +++ b/docs/egui-037-deck-font-gaps-plan.md @@ -0,0 +1,475 @@ +# egui 0.37 DeckCraft font gaps: Chinese UI fallback and table-style fixture + +Status: central font assets, the opt-in manifest, and DeckCraft's native build/UI/table-fixture +changes are implemented locally. Central managed job `egui037-fonts-candidate-20261023-chinese-05` +passed 4 embed tests, 25 font tests and strict all-features clippy. Deck's fresh review found and +repaired tofu-permissive assertions and a Chinese sample that was not visible at startup; +managed `egui037-deckcraft-candidate-20261023-font-input-14` passed the two targeted font +regressions but failed its full UI suite on seven other tests. Later jobs +`egui037-deckcraft-candidate-20261024-upstream-tests-24` and `-25` passed 307 UI +tests with 4 ignored (serial and parallel), and `-upstream-lints-26` passed strict +linting. The earlier parallel SIGSEGV remains an open diagnostic, not a fixed issue. +Central `egui037-fonts-candidate-20261024-final-06` also passed default/embed tests, +formatting and strict linting after the CI-only upstream fast-forward to `fff559f`. +Native GUI, web-size and performance qualification remain open. The planning observations below +are retained as history; the acquisition and implementation sections supersede the speculative GB choice. + +## Observed failures + +### 1. Chinese interface text + +The managed candidate run +`/home/codex/work/ssd2/task-evidence/egui037-deckcraft-candidate-20261023/task-jobs/egui037-deckcraft-candidate-20261023-ime-tests-11/output.log` +reports: + +```text +No glyph for "简" (U+7B80) in Proportional. +Installed fonts: ["craft-1-BIZ UDPGothic-Regular", "craft-4-Noto Sans Arabic-Regular", +"craft-7-NanumGothic-Regular", "craft-8-Noto Sans Symbols-Regular", +"craft-9-Noto Sans Symbols 2-Regular", "craft-10-Noto Sans Hebrew-Regular", +"Ubuntu-Light", "egui-icons", "NotoEmoji-Regular", "emoji-icon-font", "Hack"] +``` + +This is the DeckCraft UI test +`crates/ui-egui/src/ime_tests.rs::cjk_text_in_the_interface_is_drawn_with_real_glyphs` (lines +182-220). The test's Chinese sample is `简体中文演示文稿` (line 189) and the missing character is +`简` U+7B80. Noto Sans CJK is present in the central repository but DeckCraft's +`crates/fonts/build.rs` excludes `Noto Sans CJK SC` under its asset policy, so it is correctly +absent from the candidate's installed fonts. + +The existing `system_has(text)` skip at lines 200-204 is not a safe fix. The test builds the app +with `harness(s)` before it reaches that loop, and the app paints a UI surface containing the +Chinese section name. Under egui 0.37's strict missing-glyph policy the paint panics before the +skip can run. Even if the skip were reachable, skipping Chinese would violate the requirement that +the real UI regression remain covered. Do not remove the Chinese assertion or compare against +tofu. + +The current central plan says: + +- `docs/egui-037-font-coverage-plan.md` lines 353-358: Chinese stays system-only in GridCraft + while Noto CJK is barred; a permitted Chinese face may be added later with the same provenance, + cmap/outline and measurement evidence. +- `docs/integration.md`, web allowlist section: the web allowlist is fixed and excludes Noto CJK. A new + native Chinese face must not be added to that allowlist implicitly. + +### 2. Table style Regular/Bold fixture + +The same managed run reports: + +```text +tests::tables_with_a_file_defined_style_edit_and_resize +panicked at crates/engine/src/tests.rs:3070: +the header is laid out in the file style's (regular) weight: +(177.70600000000002, 114.4) vs (177.70600000000002, 114.4) +``` + +The test uses `Session::with_new()` (lines 5-7), whose default theme names `Inter`. It takes the +built-in header position with the built-in style's bold weight, then applies a file style with +`bold: Some(false)` and expects a different caret x. In the candidate checkout there is no +`fonts/latin-manifest.txt` and no `Inter` face in the central manifest. `FontDb::face` therefore +falls through its family/substitute list to the first loaded face for both Regular and Bold +(`crates/fonts/src/fontdb.rs` lines 1006-1025). Both requests resolve to the same static face, so +the two positions are identical. + +This is not evidence that the table-style implementation is wrong. The fixture is asking for two +metrics from a family that is not present. Adding Inter solely to satisfy this test would solve +the symptom but would also add a new, unreviewed Latin asset and change the default document font +for every candidate. The smaller, honest fixture fix is to make the test use a family with a real +Regular and Bold face that is already part of the candidate. + +## Decision summary + +1. **Chinese UI:** add one native-only, permitted Simplified Chinese face, supplied by + craft-fonts. Preferred candidate is **LXGW WenKai GB Regular** because it targets Simplified + Chinese, is derived from Fontworks Klee One under OFL-1.1, and should be smaller than the full + LXGW WenKai family. Keep it out of every wasm allowlist. Do not use Noto CJK. +2. **Table fixture:** use the existing `BIZ UDPGothic` Regular/Bold pair in the test, not a new + Inter/Latin asset. A read-only hmtx check of the existing bytes gives 12,767 font units for + `Hello world` in Regular and 13,237 in Bold at 2,048 upem (112.21 pt versus 116.34 pt at 18 pt; + a 4.13 pt difference). That is sufficient to make the assertion meaningful. The test may + choose another installed family with a real Regular/Bold pair only when the candidate is + absent; under `CRAFT_FONTS_REQUIRED=1` a missing pair must fail, not skip. +3. **System-only Chinese with an owned test fixture:** retain as a fallback only if the LXGW + candidate cannot pass provenance, licence/RFN or byte gates. It is not the preferred answer: + it proves the fallback machinery with a fixture but leaves Linux users without a shipped + Chinese face. If used, the fixture must be contributor-original or openly licensed, attributed, + and the production limitation must be stated explicitly. It must never be presented as + production Chinese coverage. + +No Chinese font addition or production fallback change should land until the provenance gate in this document is complete. The table fixture fix can be reviewed independently because it uses existing BIZ bytes and does not add an asset. + +## Chinese candidate research + +### Policy fit + +DeckCraft's asset policy (repository `AGENTS.md` §1.1-1.4) bars Microsoft, Adobe, Avid and +Autodesk visual assets, including Adobe's Source Han and Noto CJK. It permits OFL-1.1 assets from +their original source with attribution. Fontworks Klee One is not one of the barred vendors, and +LXGW WenKai is a derivative of Klee One under OFL-1.1, so the family is a policy candidate. This is +a licence/policy finding, not a provenance or quality pass. + +`LXGW` is the project abbreviation for the Pinyin `Luo Xia Gu Wu`; the Debian package description +also identifies the upstream homepage and the Klee One derivation. Record the derivative author +as **LXGW / Luo Xia Gu Wu**, and record the original design/copyright holder as **Fontworks Inc. +(Klee One)**. Do not attribute the derivative to Adobe or Google. + +### Release and provenance evidence available locally + +The server has no working DNS for `github.com`, `raw.githubusercontent.com` or +`api.github.com`; attempts to read the two official repositories failed before any content was +fetched. This document therefore does **not** claim an upstream release tag, commit or asset hash +that it has not inspected. + +The strongest local evidence is Debian trixie's package metadata for the base family: + +- Debian source package: `fonts-lxgw-wenkai` version `1.511+repack-1`. +- Upstream homepage: `https://github.com/lxgw/LxgwWenKai`. +- Description: an open-source Chinese font derived from Fontworks' Klee One; `LXGW` is the + abbreviation for `Luo Xia Gu Wu`. +- Package contains six faces: `LXGWWenKai-{Light,Regular,Bold}.ttf` and + `LXGWWenKaiMono-{Light,Regular,Bold}.ttf`. +- Binary package size: 60,097,836 bytes compressed; installed size: 116,871 KiB (119,675,904 + bytes) for all six files. The average is 19,945,984 bytes (19.02 MiB) per face. These are Debian + package figures, not a measured upstream Regular file. + +The Debian version is **evidence of a release number, not an acceptable pin for the font bytes**: +the archive is a Debian `repack`, and the exact upstream tag, commit, release archive SHA-256 and +member hash still need to be recorded. `LxgwWenkaiGB` has no local package metadata at all, so its +release must be verified independently before it can be selected. + +### Candidate table + +| Field | Base candidate | GB candidate | Required evidence before adoption | +|---|---|---|---| +| Project | `lxgw/LxgwWenKai` | `lxgw/LxgwWenkaiGB` | Official release page and immutable tag/commit | +| Candidate release | `1.511` (Debian-observed; upstream tag/commit unverified) | not locally verified | Release tag, commit, release date, archive URL and SHA-256 | +| Expected file | `LXGWWenKai-Regular.ttf` | `LXGWWenKaiGB-Regular.ttf` (exact name to verify) | Byte-identical upstream member; no rename/subset unless reviewed | +| Derivative author | LXGW / Luo Xia Gu Wu | LXGW / Luo Xia Gu Wu | `name` table and upstream licence header | +| Original face | Fontworks Klee One | Fontworks Klee One | Upstream OFL and copyright notice | +| Licence | OFL-1.1 | OFL-1.1 expected; verify | Byte-identical `OFL.txt`, hash and destination | +| RFN | Not proven locally | Not proven locally | Read the full OFL header and record every Reserved Font Name; ship unmodified bytes | +| Script | `Hans` (plus exact inherited coverage) | `Hans` expected | Actual cmap/script data, not a guess from the family name | +| Coverage | Full family expected to be broader | GB-oriented; verify exact set | Outline checks for `简体中文演示文稿` and `简` U+7B80, plus documented limits | +| Native-only | Yes | Yes | No wasm allowlist entry | + +**RFN handling:** the plan must not state "no Reserved Font Name" until the upstream OFL text has +been read. Regardless of the result, ship the release member unmodified and retain the upstream +family name. Do not subset, rename or create a "DeckCraft Chinese" face. If the upstream licence +does contain an RFN, the unmodified redistribution is still the safe path; any later modified +face needs a separate OFL review. + +### Coverage acceptance + +The bundled face is accepted for the current regression only when it draws every character of +`简体中文演示文稿` and the exact failing codepoint `简` U+7B80 with non-empty outlines. Do not +infer coverage from `scripts.contains("Hans")`. Also record which GB repertoire the chosen release +claims (for example GB 2312/GBK/GB18030) and do not advertise broader coverage without a cmap +check. + +For mixed UI text, assert both proportional and bold interface families, as the current test does. +Fallback shaping is not the hard part here: these are ordinary Han characters with no combining +marks. Still, a real UI screenshot and an edit/reopen workflow are required, not just a glyph +lookup. + +## Native byte budget and WASM exclusion + +### Current bytes + +The central `fonts/manifest.txt` currently names 45,600,892 font bytes (43.49 MiB). The +16,437,364-byte `Noto Sans CJK SC` file is still in the repository for other consumers but is +excluded from DeckCraft native builds by `crates/fonts/build.rs`. + +DeckCraft's web branch embeds only: + +- `BIZ UDPGothic` Regular +- `Inter` Regular, Bold or Variable + +The wasm branch skips every other manifest row (`crates/fonts/build.rs` lines 49-62). A new +Chinese row in a native-only manifest therefore has a 0-byte wasm delta by construction. Do not +edit that allowlist and do not add the Chinese face to GridCraft's `WEB_FONTS`. + +### Budget gates + +Use these as hard gates until exact upstream bytes are available: + +| Gate | Limit | Measurement / action | +|---|---|---| +| One font file | <= 24 MiB | Upstream file size; abort if larger | +| Licence file | <= 8 KiB | Byte hash and destination under `fonts//` | +| Native executable/bundle growth | <= 26 MiB | Release build before/after, recorded per app | +| Additional runtime font storage | <= 50 MiB peak | Startup and first Chinese paint; egui keeps a copy of each face | +| Wasm raw and compressed delta | 0 bytes | Build with the unchanged allowlist and assert no LXGW family in `CRAFT_FONTS` | +| Number of added faces | exactly 1 | One static Regular row, no Light/Bold/Mono sibling files | + +The 24 MiB font ceiling is intentionally above the 19.02 MiB Debian-package average. It gives room +for a single full base Regular while rejecting an accidental six-face package. If the verified GB +Regular is smaller, record its actual size; do not replace the measurement with an estimate. + +`crates/ui-egui/src/uifonts.rs` already accepts non-colour faces up to 128 MiB and an aggregate of +384 MiB. One 20-24 MiB face is within that policy, but the user-visible startup and memory cost +must still be measured rather than inferred from that constant. + +### Packaging choice + +Keep the existing native consumers unchanged unless they explicitly opt in. Add the Chinese face +under a separate opt-in manifest, for example `fonts/simplified-chinese-manifest.txt`, and have +DeckCraft's native build read it after the Latin and general manifests. This avoids silently +adding ~20 MiB to GridCraft and every other native consumer, and preserves GridCraft's documented +system-only Chinese policy. If the maintainers instead decide that all native consumers want the +face, a single row in `fonts/manifest.txt` is simpler, but then every consumer's native growth +must be reviewed and documented. + +The wasm build must ignore the opt-in manifest entirely. Add a build-time or test assertion that +the Chinese family/file is absent from the wasm `CRAFT_FONTS` slice even when +`CRAFT_FONTS_REQUIRED=1`. + +## Table-style test fixture + +### Minimal fix + +Do not add Inter and do not change production default fonts for this test. Before taking +`built_in`, set the first-row text run's font to a family whose Regular and Bold faces are both +present. Prefer `BIZ UDPGothic`, because it is already in the central manifest and is required by +the current candidate. + +The read-only hmtx check above proves the existing BIZ files are not metric clones: + +- `BIZUDPGothic-Regular.ttf`: `Hello world` = 12,767 units / 2,048 upem +- `BIZUDPGothic-Bold.ttf`: `Hello world` = 13,237 units / 2,048 upem +- At 18 pt: 112.21 pt vs 116.34 pt (4.13 pt difference) + +That is enough for the test's `> 0.5 pt` threshold. The check is a static hmtx measurement, not a +layout pass; the managed test still has to prove the caret actually moves. + +For a no-candidate run, choose another available family with genuinely different Regular/Bold +faces (for example DejaVu Sans or Liberation Sans if installed), or skip only when +`CRAFT_FONTS_REQUIRED` is unset. Under `CRAFT_FONTS_REQUIRED=1`, missing BIZ Regular/Bold must be a +failure, not a skip. The test must not silently return to the current "both weights are the same +fallback face" condition. + +### Separate Latin-manifest gap + +The absence of `fonts/latin-manifest.txt` and `Inter` is still a real production gap for DeckCraft's +default theme and document metrics. It is not necessary to solve it inside this table fixture. +Do not hide the gap by making the test pass with a CJK face. Record it as a separate prerequisite: +pin and verify the Latin manifest, add the reviewed Inter faces, and measure native/web growth. +The table test can proceed with BIZ in the meantime. + +## Alternatives considered + +### A. Native-only LXGW WenKai GB Regular (recommended) + +- Pros: one real permitted face; fixes the Chinese UI regression on Linux and any platform without + a CJK system font; deterministic test; no wasm growth; keeps Noto CJK banned. +- Cons: ~10-24 MiB native cost; exact GB release/provenance not yet verified; Kai style may be + less UI-neutral than a sans, so it should remain a fallback behind system CJK when present. +- Decision: adopt only after the provenance, licence/RFN, coverage and byte gates pass. + +### B. Native-only full LXGW WenKai Regular + +- Pros: broader coverage and the release number is corroborated by Debian 1.511; same licence + family. +- Cons: likely ~19 MiB or more per face; larger than the GB variant; more glyphs than the UI + regression needs. +- Decision: fallback if GB cannot be pinned or does not cover the sample. Do not ship both. + +### C. System-only Chinese plus owned fixture + +- Pros: no shipped Chinese font, no native size growth, no new font licence. +- Cons: Linux/bare CI still cannot render Chinese; the UI test uses a fixture rather than + production coverage; a strict-glyph paint can panic before a runtime skip. It is honest only if + the product explicitly accepts that Chinese requires an installed system font. +- Decision: contingency only. The fixture must be contributor-original or openly licensed, + attributed, and clearly separated from production acceptance. Do not call this "real Chinese + coverage". + +### D. Noto Sans CJK SC + +- Rejected by DeckCraft policy. It remains in the central repository for other consumers but is + filtered out of DeckCraft's native build and must stay out of its wasm allowlist. + +### E. Remove or skip the Chinese assertion + +- Rejected. It would hide a real missing-glyph panic and contradict the requirement to keep the + UI regression meaningful. Tofu comparison or `allow_missing_glyphs` is not acceptance. + +## Required tests and evidence before implementation + +### Acquisition and provenance (must run before code changes) + +1. Read the official release page for the selected LXGW project. Record the release tag, commit + SHA, release date, archive URL, archive byte size and archive SHA-256. +2. Download only the reviewed release archive into the durable acquisition evidence directory. + Record every release asset name and size. Do not use a package mirror's repacked binary as the + font source. +3. Extract the single Regular TTF member without renaming, subsetting or regenerating it. Record + its byte size and SHA-256, and the member's byte identity against the archive. +4. Extract the upstream `OFL.txt`; record byte size, SHA-256 and the complete copyright/RFN + language. Confirm the original Fontworks Klee One notice is retained. +5. Read the font's `name` table, OS/2 weight/width, sfnt/collection status and `fvar`/`STAT` + presence. Require one static Regular face if that is what is selected. +6. Read cmap and outlines for every character of `简体中文演示文稿`, especially `简` U+7B80. + Record the claimed GB repertoire and do not generalize beyond it. +7. Store the acquisition record, licence, hashes and inspection output under the existing + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/font-acquisition/` structure. + +If any of these fail, stop. Do not add the bytes and do not switch to "system-only" without +recording the product limitation. + +### craft-fonts tests + +Add, but do not run outside the managed workflow: + +- exact manifest row for family/style/file/script/licence/hash/source; +- SHA-256 and licence adjacency checks; +- one static face, exact `name` table family/style, weight 400, no accidental variable instances; +- exact script `Hans` (unless the verified metadata justifies an additional script tag); +- outline checks for the full sample and U+7B80; +- file-size ceiling; +- a test that the opt-in manifest is not read by a wasm consumer, or an equivalent build-time + assertion that the wasm `CRAFT_FONTS` slice has no LXGW entry. + +### DeckCraft tests + +Run with the exact candidate and `CRAFT_FONTS_REQUIRED=1`: + +```text +cargo test --config -p deckcraft-ui-egui --lib \ + ime_tests::cjk_text_in_the_interface_is_drawn_with_real_glyphs +cargo test --config -p deckcraft-engine --lib \ + tests::tables_with_a_file_defined_style_edit_and_resize +cargo test --config -p deckcraft-ui-egui --lib +cargo test --config -p deckcraft-engine --lib +``` + +The Chinese test must assert all four samples are checked on every platform where the bundled +face is present, and must not reach the missing-glyph panic before the assertion. The table test +must use the BIZ pair and show a caret delta greater than 0.5 pt. + +Also run the no-candidate path separately. It may skip the table-metric assertion only when +`CRAFT_FONTS_REQUIRED` is unset; it must not claim Chinese coverage without a real face. + +### Wasm and size checks + +- Run the repository's wasm gate with the unchanged allowlist. +- Assert the generated wasm `CRAFT_FONTS` contains no LXGW family and no Chinese font bytes. +- Record raw and compressed wasm sizes before/after; require a 0-byte delta for this font. +- Record release native binary/bundle size before/after and enforce the <=26 MiB gate. +- Record peak memory before/after first Chinese paint. + +### Performance + +Use the existing font-registration benchmark protocol rather than a new ad-hoc timer: + +- five alternating before/after pairs, warm-up, at least 100 repeated registrations per sample; +- additional median registration time <= 1 ms; +- within-run CV <= 5%; if noisier, do not claim a pass; +- register one face per family set that includes it, not multiple font copies per character; +- record the exact font bytes, build profile, commit and command. + +Also record startup-to-usable and first-Chinese-paint timings, plus the UI screenshot. A glyph +lookup or unit test alone is not a UI acceptance result. + +## Implementation sequence (after this plan is approved) + +1. Complete acquisition/provenance and freeze the exact release and hashes. +2. Add the single face, `OFL.txt`, opt-in manifest row, attribution/README rows and integration + documentation. +3. Add craft-fonts metadata/coverage/size tests. +4. Teach DeckCraft's native build to read the opt-in manifest; leave the wasm branch unchanged. +5. Add the exact family to DeckCraft's native interface fallback only if the existing dynamic + `UiFonts` path does not deterministically select it. Prefer the existing fallback path; do not + make the Chinese face a primary Latin/UI face. +6. Change the table-style fixture to use BIZ UDPGothic Regular/Bold. Do not add Inter here. +7. Run the managed checks above, capture screenshots and measurements, and preserve all evidence. +8. Update the central Chinese section to distinguish GridCraft's system-only policy from + DeckCraft's native bundled fallback. Do not add Chinese to any web allowlist. + +## Open issues and limitations + +- Official GitHub release metadata, exact archive hashes and the upstream `OFL.txt`/RFN text could + not be read in this environment because DNS/network access failed. The only local version + evidence is Debian's `1.511+repack-1` packaging of the base family; that is not a font-byte pin. +- The GB release, exact file name, size and coverage are therefore unverified. The plan must not + be treated as authorization to fetch or commit a font. +- No native or wasm size, startup, memory or registration benchmark was measured here. The table + BIZ advance check is a read-only hmtx measurement, not a layout or performance result. +- Native-only Chinese fixes the strict-glyph UI failure only when the build actually includes the + reviewed face. The default no-font path remains a documented limitation and must not claim + Chinese coverage. +- The table fixture change is test-scoped. The missing `latin-manifest.txt`/Inter issue remains a + separate production prerequisite; it must not be papered over by selecting a CJK family for + the default theme. + + +## Acquisition and v2 review dispositions (2026-10-23) + +The independent v2 review is saved in the migration evidence. Implement these corrections with +the original plan; they supersede the speculative GB choice and any broader coverage claim. + +- The GB v1.530 Regular release is 26,170,696 bytes, above the planned 24 MiB font ceiling. + Select the official **LXGW WenKai Lite Regular v1.530**, commit + `b712c83d98acf977bacfb7fb010207802808bda9`, instead. The unmodified release TTF is + 13,949,472 bytes, SHA256 `f799a538a3355877e597a0ae04050e897932c9ae7689eb9227c2953c5dd549ea`. + The official release ZIP is 51,990,239 bytes, SHA256 + `7fa084dfdb76f36a40c183dbb50cb5d2f92b2ea26553454988e08c889f8ed5fa`. + Its extracted OFL.txt is 5,176 bytes, SHA256 + `0333a85fe077b6849f8e417acc44e8bd879442ceb681e271acf93e2d57fd4e2e`. + All bytes and metadata are retained under the migration evidence's + `font-acquisition/lxgw-wenkai-lite/`. Static one-face sfnt, weight400, no fvar, actual + family `LXGW WenKai Lite`, style `Regular`, version1.530; no renaming/subsetting. + Official source: https://github.com/lxgw/LxgwWenKai-Lite/releases/tag/v1.530 . + RFNs are 霞鹜, 霞鶩, 落霞孤鹜, 落霞孤鶩, LXGW; retain full licence and Klee copyright. +- Add only `fonts/simplified-chinese-manifest.txt`, with the existing eight-field parser format. + Use `Hani,Hans` because Deck's script classifier emits Hani, and restrict coverage claims to + actually checked text. Tests explicitly parse this optional manifest, then verify hashes, + licence, family/style, weight400, no fvar, outlines and <=24MiB size. Do not add it to FONTS + or the default embedded manifest. Update central attribution to cover both manifests. +- Accept the existing bundled-first, per-character fallback order. This fixes missing glyphs + for document-derived UI text and adds a native static-label fallback; it does not promise + uniform Chinese typography or system-first precedence. BIZ remains ahead for characters it + already covers. Add LXGW after existing native families in theme::font_definitions. +- The observed panic is kittest's strict MissingGlyphPolicy::Panic; production defaults to + Tofu. Describe production missing glyphs, not an unproven production crash. +- Deck reads the opt-in manifest only for native builds. Missing file is compatible with older + craft-fonts checkouts. If present but malformed/unreadable, required-font mode fails; optional + mode warns and retains the successfully parsed main fonts. Required-font mode with no + CRAFT_FONTS_DIR is an error. No Chinese font is added to any wasm allowlist. +- The initial-document regression must paint Chinese before any user event. With the LXGW face + bundled or required-font mode enabled, require all four CJK samples on Linux too; no skips. + Verify actual proportional/bold glyph ink, including 简, rather than only a cmap entry. +- The table fixture sets explicit BIZ UDPGothic on the first-row runs before either caret + measurement, leaves bold unset so table style controls it, and requires distinct actual + regular/bold faces. Required-font mode fails when the pair is absent. In optional mode the + metric-only assertion may skip with a visible reason; preserve independent edit/resize checks. +- Update Deck attribution, localization-parity and central integration docs. Native size, + startup/registration measurements, screenshot and wasm zero-byte delta remain acceptance + gates, not claims established by this acquisition. + +The release metadata was obtained through GitHub's read-only connector. Because it cannot +return binary assets, the exact public release ZIP was downloaded through the normal host +network path and verified against GitHub's published SHA256. No repository asset has been +modified or published by acquisition alone. + +### Central craft-fonts implementation status (2026-10-24) + +The central side is implemented locally, with no DeckCraft source edited here: + +- Copied the reviewed upstream `LXGWWenKaiLite-Regular.ttf` (13,949,472 bytes, SHA-256 + `f799a538a3355877e597a0ae04050e897932c9ae7689eb9227c2953c5dd549ea`) and `OFL.txt` + (5,176 bytes, SHA-256 `0333a85fe077b6849f8e417acc44e8bd879442ceb681e271acf93e2d57fd4e2e`) + unmodified to `fonts/lxgw-wenkai-lite/`. +- Added one row to the opt-in `fonts/simplified-chinese-manifest.txt`: `LXGW WenKai Lite`, + `Regular`, `Hani,Hans`, OFL-1.1, adjacent licence path, the TTF hash and the v1.530 release + source. The default `fonts/manifest.txt`, `FONTS` and the `embed` payload are unchanged. +- Added central tests that parse the optional manifest with `parse_manifest` and check the exact + row, hash, adjacent licence and reserved-font-name/copyright text, one static weight-400 face + without `fvar`/`avar`/`STAT`, drawn outlines for `简体中文演示文稿` and `简`, the 24 MiB + ceiling, and absence from the default manifest/`FONTS`. +- Updated `ATTRIBUTION.md`, `README.md`, `docs/integration.md` and the Chinese section of + `docs/egui-037-font-coverage-plan.md` to distinguish GridCraft's system-only policy from + DeckCraft's native-only opt-in. +- DeckCraft build-script/UI integration and the BIZ Regular/Bold table fixture are implemented; + current automated and central checks are recorded above. Native/wasm size measurements, + visible edit/save/reopen screenshots and performance remain consumer acceptance gates. + This central font resource does not own the downstream app/VM resources. diff --git a/docs/egui-037-font-coverage-plan.md b/docs/egui-037-font-coverage-plan.md new file mode 100644 index 0000000..0eab757 --- /dev/null +++ b/docs/egui-037-font-coverage-plan.md @@ -0,0 +1,495 @@ +# egui 0.37 fallback-font prerequisite + +## Status: central prerequisite validated locally; consumer qualification continues + +The central asset/library change is validated atop `fff559f`. Managed +`egui037-fonts-candidate-20261023-chinese-05` passed all 29 all-feature tests and +strict clippy. `egui037-fonts-candidate-20261024-final-06` passed formatting, +28 default workspace tests, 4 explicit embed tests and strict all-feature clippy. +Fresh implementation review `egui037-fonts-upstream-final-review-20261024-root-v3` +confirmed all 13 font hashes, the five added font/licence pairs, metadata, +provenance and default/opt-in behavior. Consumer native GUI, font registration, +web size and performance checks remain in their separate app resources. + +## Follow-up prerequisite: Hebrew consumer coverage + +DeckCraft needs an actual Hebrew fallback for its mixed-script editing paths. The first +amendment incorrectly called the migration fixture's default instance Regular: the variable +font at google/fonts 8b0a1d0 defaults to Thin (`wght=100`) and enumerates nine named instances. +Keep that original fixture at `fixtures/noto-sans-hebrew/NotoSansHebrew.ttf` unchanged; it is +not added to this repository. Instead use the upstream **static Regular** release, which needs no +per-app variation-coordinate exception and registers one face. + +### Provenance (verified on disk) + +- Official release: [NotoSansHebrew-v3.001](https://github.com/notofonts/hebrew/releases/tag/NotoSansHebrew-v3.001), + target commit `036f3206f67caac235cf8546a7751d3440771a7e` (2024-06-03). +- Archive `NotoSansHebrew-v3.001.zip`: 3,734,185 bytes, SHA-256 + `df0a71814b4e63644cf40fcc4529111b61266b7a2dafbe95068b29a7520cc3cb` + (`archive-provenance.json`; internal timestamps `2024-06-03 13:44` match the release date). +- Archive member `NotoSansHebrew/full/ttf/NotoSansHebrew-Regular.ttf` is **byte-identical** to + the extracted `full.ttf`: no rename, subset, generated instance or internal name edit. +- Font: 66,156 bytes, SHA-256 + `671951828bd5c95db818e5bb12dcea2d0c0dda00311888522be061ee6835125e`. +- Archive-root `OFL.txt`, extracted as `license.txt`, is byte-identical: 4,382 bytes, SHA-256 + `9b9fe028b5ba74d231659a1bbaf0ed09b11e759d1ca6a070999e16d151616b47`. +- Acquisition and all three upstream static variants (`full.ttf`, `hinted.ttf`, `unhinted.ttf`) + are retained at + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/font-acquisition/noto-sans-hebrew-static/`. + The directory's `archive-provenance.json` records the URL, release commit, zip bytes and zip + SHA-256. The top-level `acquisition.json` also records the Hebrew font and licence members, + including their release URL, release commit, sizes and hashes. The evidence owner added + those records before the fresh implementation review. +- Metadata: no `fvar`, `avar` or `STAT` table; sfnt version 1.0; 17 tables; 470 glyphs; OS/2 + `usWeightClass=400`, `usWidthClass=5`, `fsSelection=REGULAR|USE_TYPO_METRICS`, `macStyle=0`, + `italicAngle=0`; names `Noto Sans Hebrew`, `Regular`, `Noto Sans Hebrew Regular`, + `NotoSansHebrew-Regular`, version `3.001`. +- Coverage: all 27 letters `U+05D0..U+05EA` and all 24 assigned points/punctuation + `U+05B0..U+05C7` map to non-empty outlines. 88 of the 112 code points in `U+0590..U+05FF` are + mapped and every mapped glyph draws. The hinted/unhinted variants are smaller subsets (147 + cmap entries / 151 glyphs vs 464 / 470) with the same Hebrew coverage, so the full face is + chosen to avoid assuming coverage from a subset. + +### Repository addition (applied locally) + +- `fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf`, 66,156 bytes, SHA-256 + `67195182…5125e`, byte-identical to the verified upstream member. +- `fonts/noto-sans-hebrew/OFL.txt`, 4,382 bytes, SHA-256 `9b9fe028…616b47`, byte-identical to + the archive-root `OFL.txt`. OFL-1.1 declares no Reserved Font Name, so shipping the unmodified + file with the adjacent licence is compliant. +- Manifest row (eight fields; the `source` field names the release tag): + + ```text + Noto Sans Hebrew | Regular | fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf | Hebr | OFL-1.1 | fonts/noto-sans-hebrew/OFL.txt | 671951828bd5c95db818e5bb12dcea2d0c0dda00311888522be061ee6835125e | https://github.com/notofonts/hebrew/releases/tag/NotoSansHebrew-v3.001 + ``` + +- Matching `ATTRIBUTION.md` and README rows: family `Noto Sans Hebrew`, style `Regular`, script + `Hebr`, author The Noto Project Authors, OFL-1.1, licence destination + `fonts/noto-sans-hebrew/OFL.txt`. +- `docs/integration.md` lists the `Hebr` face in the central fallback set and states that it is + native-only for this round. +- The `crates/craft-fonts/Cargo.toml` "about 43 MiB" embedded-size comment remains accurate: + the manifest's font bytes become 45,600,892 (43.49 MiB) after +66,156, so no comment edit is + needed. This is the documented "embedded-size comment" result, not a silent omission. +- Every existing font, manifest line, attribution row, README row and the original baseline + fixture stay byte-identical. + +### Registration and size gates + +- Exactly +66,156 font bytes and +4,382 licence bytes (70,538 total) in the central asset set. +- Exactly one additional native FontFace **file/face**, never nine instances. "One face" means + one manifest row and one static face with no `fvar`/named instances; a consumer registers that + face once per family set that includes it (egui's proportional family and, where present, its + `CELL`/monospace family), so two families may reference the same face without duplicating bytes + or creating instances. +- Native GridCraft embeds the new non-barred row under its existing policy; record its + +66,156-byte font growth and the separate executable/bundle growth. GridCraft's five-face web + allowlist stays unchanged, and DeckCraft's current web allowlist also excludes Hebrew. This + amendment claims **native** required-font acceptance only; web support remains an explicit + separate qualification gap. +- **Shaping qualification:** this range includes combining marks and punctuation; the marks + need GPOS positioning. The storytold fork uses HarfRust and bidi layout (epaint + `font_face.rs` shaper data and `text_layout.rs`), so the earlier review + statement that this path has no shaping was incorrect. Font coverage alone does not prove + correctly positioned or reordered vocalized Hebrew. The initial glyph acceptance uses + unpointed mixed text such as `שלום deck 42` (and unpointed `שלום` + alone); it must not claim correctly shaped niqqud. +- Benchmark identical native DeckCraft font-registration operations before/after, five + alternating pairs with warm-up and at least 100 repeated registrations per sample; require + additional median time <=1 ms per registration and within-run CV <=5%. No performance pass if + measurement is too noisy. Register the one static face, not nine instances. + +### Consumer acceptance + +- DeckCraft native required-font acceptance must assert exact family `Noto Sans Hebrew`, static + Regular weight (OS/2 400, no `fvar`), Hebrew coverage, and mixed Hebrew/Latin caret/edit + behavior for `שלום deck 42` with system font discovery isolated (`CRAFT_FONTS_DIR` set, no + system font provider). The no-font path must build/run without claiming bundled Hebrew. Strict + glyph/accessibility checks remain enabled; no `allow_missing_glyphs`, tofu comparison or + silent skip is acceptance. +- Run central `fmt`, `clippy`, default/embed tests and the hash/licence/metadata tests below, + then the named DeckCraft required/no-font tests and actual mixed-direction editing. This + revision received the fresh adversarial review in + `egui037-font-hebrew-plan-review-20261023-root-v2.md`; the fresh implementation review + and central validation are recorded above. Nothing is published without explicit approval. + +GridCraft's strict egui_kittest run exposes missing UI symbols and Korean glyphs. This +prerequisite adds only the fonts that are actually missing, keeps every existing face and asset +byte-identical, and keeps `CRAFT_FONTS_DIR` optional. It does not copy font binaries into app +repositories, and it does not use the barred Adobe / Source Han / Noto CJK families in GridCraft. + +Fresh remote state used for this plan: + +- `craft-fonts` PR 23 is **closed, unmerged**, head + `64b29ef11d9bfb2f50517ef659d4cfb410ad1afc`. Reuse only its selected, verified NanumGothic + provenance below; do not merge the PR, its Latin manifest or its other faces. +- `craft-fonts` PR 46 is **open**, head `692413113844e5c621239acc429d621addfdb084`. Reuse only + the selected Noto Sans Symbols provenance below; do not merge the whole PR or its CJK, emoji, + Math and UI face set. + +## Implementation status and verified assets + +The exact upstream assets have been acquired, verified and installed locally. The acquisition +evidence is `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/font-acquisition/` +with `acquisition.json` recording every URL, path, byte count and SHA-256 for the three faces +below. Every recomputed hash matches the recorded value and the pins below. The later Hebrew +face's structured record is in the follow-up section above and in +`font-acquisition/noto-sans-hebrew-static/archive-provenance.json`. + +Read-only cmap and `glyf` inspection of the downloaded bytes established the actual coverage: + +- NanumGothic Regular covers all 11,172 modern Hangul syllables (`U+AC00..U+D7A3`) and all 51 + compatibility Jamo (`U+3131..U+3163`), with drawn outlines for `한`, `글`, `각` and `힣`. It + does not cover `✕`, `⌃`, `✓` or `📋`. +- Noto Sans Symbols Regular (Symbols 1) covers `⌃` U+2303, `↘` U+2198 and `↑` U+2191 with drawn + outlines. It does **not** cover `✕` U+2715, `◀` U+25C0 or `✓` U+2713. +- Noto Sans Symbols 2 Regular covers `✕` U+2715, `◀` U+25C0 and `✓` U+2713 with drawn outlines. + It does **not** cover `⌃`, `↘` or `↑`, so it cannot replace Symbols 1. + +Symbols 2 is therefore **required**, not conditional: Symbols 1 lacks `✕` and `◀`, and no +existing permitted face covers `✕`. `📋` U+1F4CB is supplied by egui's bundled emoji faces and +is deliberately not claimed as craft-fonts coverage. + +**Upstream filename correction:** the Symbols 1 file at the pinned upstream commit is +`NotoSansSymbols[wght].ttf`, not `NotoSansSymbols.ttf` as an earlier draft said. The local +manifest row and destination use `NotoSansSymbols.ttf`; this is a filename-only rename of the +byte-identical download. The internal family, style, PostScript and full names are unchanged +(`Noto Sans Symbols`, `Regular`, `NotoSansSymbols-Regular`, `Noto Sans Symbols Regular`) and the +SHA-256 is unchanged. + +## Verified upstream assets + +All files are upstream, unmodified, unsubsetted and unrenamed except for the documented Symbols 1 +filename-only rename. All three fonts are OFL-1.1; their `OFL.txt` files are installed next to +them and are not copied into app repositories. + +| Face | Upstream pin and file | Local destination | Bytes | SHA-256 | +|---|---|---|---|---| +| NanumGothic | [google/fonts@bd8f81d](https://github.com/google/fonts/tree/bd8f81ddb5c74d5c8897b36ad88b440266245103/ofl/nanumgothic) / `NanumGothic-Regular.ttf` | `fonts/nanum-gothic/NanumGothic-Regular.ttf` | 2,054,744 | `76f45ef4a6bcff344c837c95a7dcc26e017e38b5846d5ae0cdcb5b86be2e2d31` | +| Noto Sans Symbols | [google/fonts@47d54ca](https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols) / `NotoSansSymbols[wght].ttf` | `fonts/noto-sans-symbols/NotoSansSymbols.ttf` | 372,684 | `f7e7e04b4a24b6c78893d50cbfd2b2f6cae49617ab047bfef668d252adb128f7` | +| Noto Sans Symbols 2 | [google/fonts@47d54ca](https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols2) / `NotoSansSymbols2-Regular.ttf` | `fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf` | 1,233,128 | `7d5fb73b7ca67a6798101741f5d280a3d016a56a197afcd4199dbb57b4b82a21` | + +The licence files are: + +- `fonts/nanum-gothic/OFL.txt`, 4,534 bytes, + SHA-256 `eeacf16032901d0ed0456876ec77b8f0fda6b3fecec7d972f8543eb602e6c30f`. +- `fonts/noto-sans-symbols/OFL.txt` and `fonts/noto-sans-symbols-2/OFL.txt`, both 4,383 bytes, + SHA-256 `b118dd41337806a5d4797052c77caf3bd096aed783e5eb21b4d11154351e1ac0`. + +Exact acquisition commands (recomputed hashes must match before a manifest edit): + +```sh +mkdir -p fonts/nanum-gothic fonts/noto-sans-symbols fonts/noto-sans-symbols-2 +curl -fL -o fonts/nanum-gothic/NanumGothic-Regular.ttf \ + https://raw.githubusercontent.com/google/fonts/bd8f81ddb5c74d5c8897b36ad88b440266245103/ofl/nanumgothic/NanumGothic-Regular.ttf +curl -fL -o fonts/nanum-gothic/OFL.txt \ + https://raw.githubusercontent.com/google/fonts/bd8f81ddb5c74d5c8897b36ad88b440266245103/ofl/nanumgothic/OFL.txt +curl -fL -o 'fonts/noto-sans-symbols/NotoSansSymbols.ttf' \ + 'https://raw.githubusercontent.com/google/fonts/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols/NotoSansSymbols%5Bwght%5D.ttf' +curl -fL -o fonts/noto-sans-symbols/OFL.txt \ + https://raw.githubusercontent.com/google/fonts/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols/OFL.txt +curl -fL -o fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf \ + https://raw.githubusercontent.com/google/fonts/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols2/NotoSansSymbols2-Regular.ttf +curl -fL -o fonts/noto-sans-symbols-2/OFL.txt \ + https://raw.githubusercontent.com/google/fonts/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols2/OFL.txt +sha256sum fonts/nanum-gothic/NanumGothic-Regular.ttf \ + fonts/noto-sans-symbols/NotoSansSymbols.ttf \ + fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf +``` + +### NanumGothic Regular (required) + +- Family **`NanumGothic`** (exact spelling, no space), style `Regular`, scripts `Hang,Kore`. +- Licence header: `Copyright (c) 2010, NHN Corporation (http://www.nhncorp.com), with Reserved + Font Name Nanum, Naver Nanum, NanumGothic, ...`. Authors recorded by the PR 23 evidence: NHN + Corporation; Sandoll Communications. +- `METADATA.pb` was read for author/licence confirmation but is not copied into `fonts/`: this + repository ships the font, `OFL.txt`, the manifest row and the attribution row only. +- Font: 13,297 glyphs (`maxp`), 11,827 cmap entries; 11,172/11,172 modern Hangul syllables; 51/51 compatibility Jamo + `U+3131..U+3163`; 42 of the 43 archaic compatibility Jamo `U+3164..U+318E`; no conjoining + Hangul Jamo blocks (`U+1100..U+11FF`, `U+A960..U+A97F`, `U+D7B0..U+D7FF`). +- Outlines: `한`, `글` and `각` are simple glyphs; `힣` is a composite whose two referenced + components both have non-empty outlines. + +### Noto Sans Symbols Regular (required) + +- Family **`Noto Sans Symbols`**, style `Regular`, script `Zsym`. This is a variable font with a + `wght` axis (`100..900`, default `400`); the name table's subfamily is `Regular` and the + PostScript name is `NotoSansSymbols-Regular`. +- The upstream filename is `NotoSansSymbols[wght].ttf`; the local file is the same bytes under + `NotoSansSymbols.ttf`. +- cmap/outline evidence: `⌃` U+2303, `↘` U+2198 and `↑` U+2191 map to non-empty outlines. + `✕` U+2715, `◀` U+25C0, `✓` U+2713 and `📋` U+1F4CB are absent. + +### Noto Sans Symbols 2 Regular (required) + +- Family **`Noto Sans Symbols 2`**, style `Regular`, script `Zsym`. +- cmap/outline evidence: `✕` U+2715, `◀` U+25C0 and `✓` U+2713 map to non-empty outlines. + `⌃`, `↘` and `↑` are absent, so Symbols 2 cannot replace Symbols 1. Symbols 2 also has + `📋` U+1F4CB, but that glyph is supplied to GridCraft by egui's emoji faces and is not + claimed as craft-fonts coverage. +- Added only because the actual cmap proves it is required, not because PR 46 proposed it. + +### Faces that must not be added here + +- `📋` U+1F4CB is supplied by egui's bundled emoji faces (`Noto Emoji`, `emoji-icon-font`). Do + not add a craft emoji face or claim craft-fonts covers `📋`. +- Do not add Noto CJK, Source Han, Adobe, color emoji or other large CJK/emoji assets for this + prerequisite. + +## Actual cmap coverage, old and new faces + +Independently checked with a read-only cmap reader against the current and downloaded files (not +a build). `yes` means the codepoint maps to a drawn outline at the default instance +(`glyf` for TrueType faces; CFF for Noto Sans CJK SC). + +| Face | `↘` | `✕` | `◀` | `↑` | `📋` | `✓` | `⌃` | +|---|---|---|---|---|---|---|---| +| BIZ UDPGothic Regular / Bold | yes | no | yes | yes | no | yes | no | +| BIZ UDMincho Regular | yes | no | yes | yes | no | yes | no | +| BIZ Shippori Mincho Regular | no | no | no | yes | no | no | no | +| Noto Sans CJK SC Regular | yes | no | yes | yes | no | yes | no | +| egui Hack Regular | yes | no | yes | yes | no | no | no | +| egui Noto Emoji Regular | yes | no | yes | no | yes | no | no | +| egui emoji-icon-font | no | no | no | no | yes | no | no | +| egui Ubuntu Light / egui-icons | no | no | no | no | no | no | no | +| NanumGothic Regular | yes | no | yes | yes | no | no | no | +| Noto Sans Symbols Regular | yes | no | no | yes | no | no | yes | +| Noto Sans Symbols 2 Regular | no | yes | yes | no | yes | yes | no | + +Consequences: + +- The no-font path cannot promise all seven symbols. `✕` and `⌃` are real gaps, and `✓` is not + in the bundled egui faces even though BIZ and Noto Sans CJK SC have it. +- With the selected craft faces, `⌃` comes from Symbols 1; `✕`, `◀` and `✓` come from Symbols 2; + `↘` and `↑` come from Symbols 1 (NanumGothic also happens to have them); `📋` comes from egui. +- Noto Sans CJK SC happens to cover Korean and `↘ ◀ ↑ ✓`, but GridCraft bars it. It cannot be + used as the Korean or symbol answer. + +## Repository changes (applied locally) + +These three manifest lines were added by the earlier prerequisite; the Hebrew follow-up adds +one more row (above). Every pre-existing line is unchanged: + +```text +NanumGothic | Regular | fonts/nanum-gothic/NanumGothic-Regular.ttf | Hang,Kore | OFL-1.1 | fonts/nanum-gothic/OFL.txt | 76f45ef4a6bcff344c837c95a7dcc26e017e38b5846d5ae0cdcb5b86be2e2d31 | https://github.com/google/fonts/tree/bd8f81ddb5c74d5c8897b36ad88b440266245103/ofl/nanumgothic +Noto Sans Symbols | Regular | fonts/noto-sans-symbols/NotoSansSymbols.ttf | Zsym | OFL-1.1 | fonts/noto-sans-symbols/OFL.txt | f7e7e04b4a24b6c78893d50cbfd2b2f6cae49617ab047bfef668d252adb128f7 | https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols +Noto Sans Symbols 2 | Regular | fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf | Zsym | OFL-1.1 | fonts/noto-sans-symbols-2/OFL.txt | 7d5fb73b7ca67a6798101741f5d280a3d016a56a197afcd4199dbb57b4b82a21 | https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols2 +``` + +Matching `ATTRIBUTION.md` rows and README `Fonts` rows were added for the same three files; the +Hebrew follow-up adds matching rows for its one file. Every pre-existing font, asset, manifest, +attribution and README row is unchanged. The eight-field manifest format is kept; PR 46's +optional ninth `role` field and its `ui` faces are not copied. + +## Consumer behavior + +### No-font/default mode (no `CRAFT_FONTS_DIR`) + +- The app must still build, test and run. +- Paint an explicit unavailable-icon state or fall back for UI icons that bundled/system faces + do not provide (notably `✕` and `⌃`, and `✓` in an egui-only environment). Do not paint tofu, + and do not weaken strict glyph checks, accessibility or `MissingGlyphPolicy` to make a test + pass. +- Acceptance tests in this mode assert only glyphs that bundled/system faces really provide. + They document `✓`/`⌃`/`✕` as optional-input or platform gaps rather than claiming all seven. +- Choosing an unavailable script must produce deliberate, tested fallback behavior. A hidden + local fixture path is never a prerequisite. + +### Extended acceptance mode (`CRAFT_FONTS_DIR` set, `CRAFT_FONTS_REQUIRED=1`) + +- Require the exact union `↘ ✕ ◀ ↑ 📋 ✓ ⌃` in both proportional and `CELL` families. + `📋` comes from egui's emoji faces; the other faces must be present in the installed + fallback set. +- Require Korean UI strings such as `데이터` and `한글`. +- Fail, do not return early or skip, when a required face or codepoint is missing. The existing + `symbol_glyphs.rs` early return when craft-fonts is absent is not acceptable acceptance + behavior. +- No `allow_missing_glyphs()` / tofu comparison may substitute for the exact-codepoint + assertion. + +### Fallback ordering in GridCraft + +Preserve the actual order, not the generic integration-doc order: + +1. app/bundled faces; +2. Hack/emoji faces already added by `font_definitions`; +3. system script fallbacks; +4. allowed craft-fonts faces last. + +Append NanumGothic, Noto Sans Symbols and Noto Sans Symbols 2 only when allowed and successfully +parsed. They must not displace system faces that already cover a glyph. Match exact family and +style: `NanumGothic` / `Regular`, `Noto Sans Symbols` / `Regular` and +`Noto Sans Symbols 2` / `Regular`; not `Nanum Gothic` and not a Symbols 2 row standing in for +Symbols 1. The manifest parser accepts arbitrary script strings, so exact `Hang,Kore` and `Zsym` +assertions belong in tests. + +### Barred families in GridCraft + +Keep the central repository's barred faces for other consumers, but filter them in GridCraft: + +- `Source Han*` +- `Source Sans*` +- `Source Serif*` +- `Noto Sans CJK*` +- `Noto Serif CJK*` +- any Adobe-family face + +Do not use a broad `Noto Sans` prefix that would also reject the permitted `Noto Sans Symbols` +and `Noto Sans Symbols 2`. + +## Web allowlist + +Before this migration, GridCraft's wasm `build.rs` embedded no craft-fonts. The local candidate +now uses the following explicit allowlist, retaining regular and bold Japanese UI faces. +The five font files total 12,970,840 raw bytes; this is not a measured wasm size or budget pass: + +- `BIZ UDPGothic Regular` +- `BIZ UDPGothic Bold` +- `NanumGothic Regular` +- `Noto Sans Symbols Regular` +- `Noto Sans Symbols 2 Regular` (required by the verified cmap above) + +Do not include Noto CJK or any barred family. Measure and record: + +- raw bytes of each allowlisted font; +- raw wasm size before and after; +- compressed (gzip/brotli as used by the consumer) wasm size before and after; +- the consumer's actual web size gate. + +Name the budget and add or use a real measurement gate; do not say "existing budget" without a +number and a check. If Korean web support is not accepted, remove Korean from the web language +picker and restored web preferences and record the gap honestly rather than shipping a claim +that cannot render. + +## Chinese + +Chinese stays documented as **system-only** in GridCraft while Noto CJK is barred. Keep Chinese +out of GridCraft's strict multilingual sample. Do not claim craft-fonts Chinese coverage from a +filtered face. + +DeckCraft has a separate **native-only opt-in**, not a change to the default manifest: +`fonts/simplified-chinese-manifest.txt` lists the unmodified official LXGW WenKai Lite Regular +v1.530 (`Hani,Hans`, OFL-1.1, upstream commit +`b712c83d98acf977bacfb7fb010207802808bda9`). It is not in `fonts/manifest.txt`, `FONTS` or the +`embed` payload, and it must stay out of every wasm allowlist. It fixes document-derived UI +text such as `简体中文演示文稿` and U+7B80 `简` through the consumer's existing per-character +fallback after existing native families. It does not promise uniform Chinese typography, +system-font-first precedence, or static localized UI-label coverage. The central tests parse the +optional manifest with `parse_manifest` and check the exact row, hash, adjacent licence and RFN +text, one static weight-400 face, outlines for the full sample and `简`, and the 24 MiB ceiling. + +## Tests in craft-fonts + +`crates/craft-fonts-tests/tests/fonts.rs` now covers: + +- exact style from the font `name` table for every manifest row; +- exact manifest metadata for all three new faces: `NanumGothic` / `Regular` / `Hang,Kore` and + `Noto Sans Symbols` plus `Noto Sans Symbols 2`, both `Regular` / `Zsym`, all three OFL-1.1; +- Korean UI strings (`데이터`, `한글`, `파일`, `편집`, `보기`, `삽입`, `서식`, `저장`, `열기`, + `닫기`, `인쇄`, `취소`, `확인`); +- the observed NanumGothic cmap: all 11,172 modern Hangul syllables and all 51 compatibility + Jamo; +- outline checks (not cmap presence alone) for `한`, `글`, `각` and `힣`; +- exact per-face symbol assignment with outlines: Symbols 1 covers `⌃ ↘ ↑`; Symbols 2 covers + `✕ ◀ ✓`; `📋` is not asserted against craft-fonts. +- the exact Hebrew manifest row and source, one Hebr face, and the one-face/static invariant: + sfnt (not TTC), no `fvar`/`avar`/`STAT`, OS/2 weight 400/width 5 and upright `REGULAR`; +- all 27 Hebrew letters `U+05D0..U+05EA` and all 24 assigned points/punctuation + `U+05B0..U+05C7` with drawn outlines (coverage, not GPOS shaping), plus the unpointed + sample `שלום עברית …` used by the mixed acceptance string. +- the optional `fonts/simplified-chinese-manifest.txt` row for `LXGW WenKai Lite` / `Regular` + (`Hani,Hans`), parsed with `parse_manifest` and kept out of `FONTS`/`fonts/manifest.txt`; + exact SHA-256, adjacent OFL licence with the five upstream reserved names and Klee copyright, + one static OS/2 weight-400 face with no `fvar`/`avar`/`STAT`, drawn outlines for + `简体中文演示文稿` and U+7B80 `简`, and a 24 MiB file ceiling. The optional manifest is not + read by craft-fonts' default build or embed payload; consumers must keep it out of wasm. + +Every existing checksum, licence, family, Japanese, Chinese, Arabic, Thai, Georgian, +vertical-text and embed test is preserved. No test uses an `if present` skip or a tofu +comparison in place of coverage. These assertions passed the managed 05/06 checks recorded above. + +## Regression sources + +- PR 23 evidence: + `/home/codex/work/task-evidence/ci-contributor-overlap-20261015/prs/craft-fonts/23/diff.txt` + (NanumGothic manifest row, OFL header, destination paths). +- PR 46 evidence: + `/home/codex/work/task-evidence/language-adversarial-review-20261021/sources/craft-fonts-46/fonts/manifest.txt` + and `craft-fonts-46-metadata.json` (Symbols 1/2 rows, hashes, sizes and coverage notes). +- Acquisition evidence: + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/font-acquisition/` + (`acquisition.json`, `METADATA.pb`, `OFL.txt` and the downloaded fonts). +- Independent plan reviews: + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/egui037-font-coverage-plan-review-20261023-root-v1.md` + (overall plan) and + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/egui037-font-hebrew-plan-review-20261023-root-v1.md` + (which found the variable fixture's Thin default and nine named instances) and + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/egui037-font-hebrew-plan-review-20261023-root-v2.md` + (approval of this static-face revision, with the provenance and shaping nits). +- Hebrew acquisition evidence: + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/font-acquisition/noto-sans-hebrew-static/` + (`archive-provenance.json`, the release zip, `full.ttf`, `hinted.ttf`, `unhinted.ttf` and + `license.txt`). The existing variable fixture at + `/home/codex/work/CraftApps/egui037-migration-20261022/fixtures/noto-sans-hebrew/` is + retained unchanged and is not a regression input for the new static face. +- DeckCraft LXGW opt-in evidence: + `/home/codex/work/ssd2/task-evidence/egui037-migration-20261022/font-acquisition/lxgw-wenkai-lite/` + (`LXGWWenKaiLite-Regular.ttf`, `OFL.txt`, the public v1.530 release ZIP and + `metadata-inspection.json`). The reviewed TTF and OFL are copied unmodified into + `fonts/lxgw-wenkai-lite/` and listed only in the opt-in Simplified Chinese manifest. + +## Managed validation commands (completed as recorded above) + +```sh +python3 /home/codex/work/maintenance/task.py run \ + --job-id -font-fmt-01 --kind build --task \ + --source /home/codex/work/CraftApps/egui037-migration-20261022/craft-fonts-implementation \ + --evidence /home/codex/work/ssd2/task-evidence/ \ + -- cargo fmt --all --check +python3 /home/codex/work/maintenance/task.py run \ + --job-id -font-clippy-01 --kind build --task \ + --source /home/codex/work/CraftApps/egui037-migration-20261022/craft-fonts-implementation \ + --evidence /home/codex/work/ssd2/task-evidence/ \ + -- cargo clippy --workspace --all-targets --all-features --locked -- -D warnings +python3 /home/codex/work/maintenance/task.py run \ + --job-id -font-test-01 --kind build --task \ + --source /home/codex/work/CraftApps/egui037-migration-20261022/craft-fonts-implementation \ + --evidence /home/codex/work/ssd2/task-evidence/ \ + -- cargo test --workspace --locked +python3 /home/codex/work/maintenance/task.py run \ + --job-id -font-embed-01 --kind build --task \ + --source /home/codex/work/CraftApps/egui037-migration-20261022/craft-fonts-implementation \ + --evidence /home/codex/work/ssd2/task-evidence/ \ + -- cargo test -p craft-fonts --features embed --locked +``` + +## Completion gate + +Locally satisfied by this implementation: + +1. the exact downloaded files match the recomputed SHA-256 and immutable upstream pins; +2. cmap and outline inspection records which face covers `✕`, `⌃`, the arrows and the Korean + strings, and the real Hangul/Jamo counts; +3. `OFL.txt`, manifest, `ATTRIBUTION.md`, README, `docs/integration.md` and the regression + tests are updated for exactly the files added; +4. existing fonts and assets are byte-identical, and the variable Hebrew fixture is untouched; +5. the Hebrew add is exactly one static 400-weight face (70,538 bytes with its licence), with + no `fvar`/`avar`/`STAT` and no nine-instance registration plan. + +Central prerequisite complete: + +6. managed fmt, clippy, workspace and embed checks passed in 05/06; fresh implementation + review verified the tested source and asset bytes. + +Downstream acceptance remains open in the separately owned app resources: +7. DeckCraft's native required/no-font Hebrew acceptance and mixed-direction editing are run + with system discovery isolated; +8. GridCraft's no-font and extended-mode behavior, fallback order, barred-family filter, native + Hebrew growth measurements and web allowlist/measurements are validated separately in the + GridCraft checkout; +9. The fresh implementation review's consumer-acceptance gaps are closed. The Hebrew + acquisition records are complete; the asset review verified their hashes and provenance. + +Keep all changes local. No push, tag or PR publication without fresh explicit user approval of +the concrete diff and validation. diff --git a/docs/integration.md b/docs/integration.md index 7e98284..c465010 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -1,8 +1,10 @@ # Using craft-fonts in a Crafting App craft-fonts is a **fully optional** source of fonts. An app must build, test and run without it; -with it, the app embeds the fonts listed in `fonts/manifest.txt` and uses them (today: Japanese -UI and document fallback). +with it, the app embeds the fonts listed in `fonts/manifest.txt` and uses them (today: Japanese, +Korean, Hebrew and UI-symbol fallback). A native consumer may additionally opt in to +`fonts/simplified-chinese-manifest.txt` for the LXGW WenKai Lite Simplified Chinese fallback; that +manifest is never part of the default set or a wasm build. ## Why not a Cargo dependency @@ -42,14 +44,17 @@ fn main() { println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR"); println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED"); let mut src = String::from("pub static CRAFT_FONTS: &[CraftFont] = &[\n"); + let required = std::env::var_os("CRAFT_FONTS_REQUIRED").is_some(); if let Some(dir) = std::env::var_os("CRAFT_FONTS_DIR").map(PathBuf::from) { match craft_fonts(&dir) { Ok(entries) => src.push_str(&entries), - Err(e) if std::env::var_os("CRAFT_FONTS_REQUIRED").is_some() => { + Err(e) if required => { println!("cargo::error=CRAFT_FONTS_DIR={}: {e}", dir.display()); } Err(e) => println!("cargo::warning=building without craft-fonts: CRAFT_FONTS_DIR={}: {e}", dir.display()), } + } else if required { + println!("cargo::error=CRAFT_FONTS_REQUIRED is set but CRAFT_FONTS_DIR is missing"); } src.push_str("];\n"); let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("craft_fonts.rs"); @@ -63,10 +68,20 @@ fn craft_fonts(dir: &std::path::Path) -> Result { let manifest = dir.join("fonts/manifest.txt"); println!("cargo::rerun-if-changed={}", manifest.display()); let text = std::fs::read_to_string(&manifest).map_err(|e| format!("{}: {e}", manifest.display()))?; - // Web builds have a size budget (e.g. Cloudflare's 25 MiB per file): embed only the UI font there. - const WEB_FONTS: &[(&str, &str)] = &[("BIZ UDPGothic", "Regular")]; + // Web builds use an explicit allowlist. Never embed the whole manifest or Noto CJK. + // GridCraft's selected faces are BIZ UDPGothic Regular/Bold, NanumGothic Regular, + // Noto Sans Symbols Regular and Noto Sans Symbols 2 Regular (verified cmap coverage requires + // both symbol faces). Measure raw font bytes, raw wasm and compressed wasm before/after. + const WEB_FONTS: &[(&str, &str)] = &[ + ("BIZ UDPGothic", "Regular"), + ("BIZ UDPGothic", "Bold"), + ("NanumGothic", "Regular"), + ("Noto Sans Symbols", "Regular"), + ("Noto Sans Symbols 2", "Regular"), + ]; let wasm = std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|a| a == "wasm32"); let mut out = String::new(); + let mut included = Vec::new(); 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,6 +91,7 @@ fn craft_fonts(dir: &std::path::Path) -> Result { continue; } let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?; + included.push((*family, *style)); println!("cargo::rerun-if-changed={}", path.display()); let scripts: Vec = scripts.split(',').map(|s| format!("{:?}", s.trim())).collect(); let _ = writeln!( @@ -85,6 +101,13 @@ fn craft_fonts(dir: &std::path::Path) -> Result { path.display().to_string(), ); } + if std::env::var_os("CRAFT_FONTS_REQUIRED").is_some() { + for &(family, style) in WEB_FONTS { + if !included.contains(&(family, style)) { + return Err(format!("required fallback face is missing: {family} {style}")); + } + } + } Ok(out) } ``` @@ -107,20 +130,72 @@ include!(concat!(env!("OUT_DIR"), "/craft_fonts.rs")); `CRAFT_FONTS` is always defined, so there is no `cfg` to thread through: an empty slice means the app was built without craft-fonts. Code that uses it must work when it is empty. +## Optional native-only manifest + +`fonts/simplified-chinese-manifest.txt` uses the same eight-field format and parser, but it is a +separate opt-in input. Read it only for non-wasm builds, after the main manifest has been parsed: + +- **Native builds only.** Never read it from a wasm branch and never add its row to `WEB_FONTS`. + Native builds that do not opt in embed no added bytes. +- **Missing file is compatible.** An older craft-fonts checkout without the optional manifest is + not an error; continue with the main manifest's fonts. +- **Malformed or unreadable when present.** Under `CRAFT_FONTS_REQUIRED=1`, fail the build. In + optional mode, emit a `cargo::warning` and keep every successfully parsed font from the main + manifest. An optional-manifest error must never discard the main fonts. +- **Required mode still needs a directory.** `CRAFT_FONTS_REQUIRED=1` with no `CRAFT_FONTS_DIR` + is a build error, as in the recipe above. +- **Not in `FONTS`/embed.** `craft-fonts`' static table and `embed` feature cover only + `fonts/manifest.txt`; the optional row does not appear there. Apps supply their own static + initializer from the parsed optional file. +- **Native fallback order.** Append `LXGW WenKai Lite` / `Regular` after the existing native + families, so it fills characters they lack without taking precedence over BIZ for characters + BIZ already covers. It is a per-character fallback, not a promise of uniform Chinese + typography or of system-font-first precedence. It does not make static localized UI labels + render in Chinese unless the consumer installs the face for those labels too. + +The central tests in `crates/craft-fonts-tests/tests/fonts.rs` parse the optional file with +`craft_fonts::parse_manifest` and verify the exact row, SHA-256, adjacent licence and RFN text, +one static weight-400 face without `fvar`/`avar`/`STAT`, outlines for `简体中文演示文稿` and +`简`, the 24 MiB ceiling, and that the face is absent from `FONTS`/`fonts/manifest.txt`. + ## Using the fonts -- **UI (egui):** add each `Jpan` font as a fallback at the end of every font family, after the - app's own fonts and before system-font discovery, so Japanese text renders the same everywhere - the app was built with craft-fonts. -- **Document text engines:** register them as fallback faces for Japanese, after the document's - requested font and the app's bundled fonts. -- **Web (wasm32):** the recipe embeds only `BIZ UDPGothic Regular` (~4.5 MB); all four fonts are - ~24 MB, over typical per-file hosting limits. If the app's web build has a size check, measure - with `CRAFT_FONTS_DIR` set and shrink `WEB_FONTS` (even to empty) if it fails. +- **UI (egui):** append permitted craft faces at the end of each interface font family, after + bundled fonts, system fonts and existing script fallbacks. Register Japanese (`Jpan`) faces, + `NanumGothic` / `Regular` for `Hang,Kore`, `Noto Sans Hebrew` / `Regular` for `Hebr`, and both + `Noto Sans Symbols` / `Regular` and `Noto Sans Symbols 2` / `Regular` for `Zsym`. The Hebrew + file is one static face: add it once per family set that includes it (for example the + proportional family and, where the consumer has one, the `CELL`/monospace family). Do not + expand it into the nine named instances of the old variable fixture. A native consumer that + opts in to the Simplified Chinese manifest adds `LXGW WenKai Lite` / `Regular` for `Hani,Hans` + after those existing families. Match the exact family/style spelling; in particular, + `NanumGothic` has no space. Verify the installed + production set with `CRAFT_FONTS_REQUIRED=1` and strict glyph assertions for each advertised + script and symbol. Hebrew coverage here means each codepoint has a drawn outline. The + storytold egui fork uses HarfRust shaping and bidi layout, but font-only tests do not + qualify correctly positioned vocalized (pointed) Hebrew in an app. The initial glyph + acceptance text is unpointed, e.g. `שלום deck 42`; shaping needs separate app-level checks. +- **Document text engines:** register the same permitted script and symbol faces after the + document's requested font and the app's existing fallback faces. Preserve the app's asset + exclusions, and do not treat a script tag as proof that every character in that script exists. +- **Web (wasm32):** the recipe uses a fixed allowlist. Never embed the whole manifest: the + current faces together are roughly 43 MiB (45.5 MB), over typical per-file hosting limits. Measure the + raw bytes of each allowlisted font, plus raw and compressed wasm before and after, against the + consumer's actual web gate. Do not call the result "within budget" without a named number and + a real check. GridCraft's decision is documented in + [`egui-037-font-coverage-plan.md`](egui-037-font-coverage-plan.md): BIZ UDPGothic Regular/Bold, + NanumGothic Regular, Noto Sans Symbols Regular and Noto Sans Symbols 2 Regular (the verified + cmap shows Symbols 1 lacks `✕`/`◀` and Symbols 2 supplies them), and no Noto CJK. Hebrew is + deliberately **not** in that web allowlist: `Noto Sans Hebrew` is a native-only addition in + this round, and web Hebrew remains an unqualified gap. `LXGW WenKai Lite` is likewise + native-only and must stay out of every wasm allowlist. - Prefer `BIZ UDPGothic` for UI text and `Shippori Mincho` / `BIZ UDMincho` for serif document text. -- Tests that assert on these fonts' glyphs must skip (not fail) when `CRAFT_FONTS` is empty, and - CI should run them with `CRAFT_FONTS_DIR` set so they are exercised. +- Optional-input diagnostics may skip when `CRAFT_FONTS` is empty. Acceptance tests that + validate advertised scripts must run with `CRAFT_FONTS_DIR` set and `CRAFT_FONTS_REQUIRED=1`, + and must fail (not skip) when a required face or codepoint is missing. Never weaken strict + glyph checks, substitute tofu comparisons, or use `allow_missing_glyphs()` to make acceptance + pass. ## Releases and licences @@ -147,6 +222,13 @@ craft-fonts' `ATTRIBUTION.md`. ## Adding a font 1. Put the file and its licence under `fonts//`, from a pinned upstream commit. -2. Add a manifest line (with its SHA-256) and an `ATTRIBUTION.md` row. -3. `cargo test --workspace`: `craft-fonts-tests` checks the checksum, licence, family name and, - for Japanese fonts, coverage and vertical-text support. Add assertions for any new script. +2. Add a manifest line (with its SHA-256) and an `ATTRIBUTION.md` row. A face that only some + native consumers should embed belongs in `fonts/simplified-chinese-manifest.txt`, not the + default `fonts/manifest.txt`. +3. `cargo test --workspace`: `craft-fonts-tests` checks the checksum, licence, family and style + names, and, for Japanese fonts, coverage and vertical-text support. Add exact ISO 15924 + script assertions for any new face. Coverage tests may claim only glyphs the face really + contains: check outlines as well as cmap entries, do not claim a codepoint supplied by + another face (egui's emoji faces supply `📋`), and do not claim full-script coverage from a + sample string. Static faces also get metadata assertions (no `fvar`/`STAT`, weight 400), + so a variable font with a Thin default cannot pass as a one-face Regular. diff --git a/fonts/lxgw-wenkai-lite/LXGWWenKaiLite-Regular.ttf b/fonts/lxgw-wenkai-lite/LXGWWenKaiLite-Regular.ttf new file mode 100644 index 0000000..b6a9187 Binary files /dev/null and b/fonts/lxgw-wenkai-lite/LXGWWenKaiLite-Regular.ttf differ diff --git a/fonts/lxgw-wenkai-lite/OFL.txt b/fonts/lxgw-wenkai-lite/OFL.txt new file mode 100644 index 0000000..44deedc --- /dev/null +++ b/fonts/lxgw-wenkai-lite/OFL.txt @@ -0,0 +1,94 @@ +Copyright 2021-2026 LXGW (https://github.com/lxgw/LxgwWenKai-Lite), with Reserved Font Name '霞鹜', '霞鶩', '落霞孤鹜', '落霞孤鶩' and 'LXGW'. [ADDITIONAL PERMISSION] The Reserved Font Names '霞鹜', '霞鶩', '落霞孤鹜', '落霞孤鶩' and 'LXGW' may continue to be used in Modified Versions recompiled from the Original Version, without modifications to the font source code; or in Modified Versions subsetted or converted to other formats (e.g., WOFF/WOFF2) solely for web font delivery, provided such Modified Versions are not made available as installable desktop fonts (e.g., on mainstream platforms like Google Fonts, or third-party non-commercial platforms recognized by the author @lxgw; other web font platforms please contact the author @lxgw for confirmation). +Copyright 2020 The Klee Project Authors (https://github.com/fontworks-fonts/Klee) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/manifest.txt b/fonts/manifest.txt index 0065c32..2a7ed8a 100644 --- a/fonts/manifest.txt +++ b/fonts/manifest.txt @@ -10,3 +10,7 @@ Noto Sans CJK SC | Regular | fonts/noto-sans-cjk-sc/NotoSansCJKsc-Regular.otf | Noto Sans Arabic | Regular | fonts/noto-sans-arabic/NotoSansArabic.ttf | Arab | OFL-1.1 | fonts/noto-sans-arabic/OFL.txt | 63111b5b2e074dd48cc67692e0a2726d86ee94c1c37fe8598257b7b4e87e869e | https://github.com/google/fonts/tree/7085eb89a950e85db5b166b7a58d414544b4140c/ofl/notosansarabic Noto Sans Thai | Regular | fonts/noto-sans-thai/NotoSansThai.ttf | Thai | OFL-1.1 | fonts/noto-sans-thai/OFL.txt | 5a1c559bb539583c8a1fd99d1c5b9491e5e14478c9cd2bd0970d5c3096cc9ef8 | https://github.com/google/fonts/tree/8b0a1d0f5983c89bc2b93f1b5fb55f9e252744b5/ofl/notosansthai Noto Sans Georgian | Regular | fonts/noto-sans-georgian/NotoSansGeorgian.ttf | Geor | OFL-1.1 | fonts/noto-sans-georgian/OFL.txt | dc591156f36842d38996c4a7a17fee9bb58e45da3e2cac7a31b7d33de700adb9 | https://github.com/google/fonts/tree/8b0a1d0f5983c89bc2b93f1b5fb55f9e252744b5/ofl/notosansgeorgian +NanumGothic | Regular | fonts/nanum-gothic/NanumGothic-Regular.ttf | Hang,Kore | OFL-1.1 | fonts/nanum-gothic/OFL.txt | 76f45ef4a6bcff344c837c95a7dcc26e017e38b5846d5ae0cdcb5b86be2e2d31 | https://github.com/google/fonts/tree/bd8f81ddb5c74d5c8897b36ad88b440266245103/ofl/nanumgothic +Noto Sans Symbols | Regular | fonts/noto-sans-symbols/NotoSansSymbols.ttf | Zsym | OFL-1.1 | fonts/noto-sans-symbols/OFL.txt | f7e7e04b4a24b6c78893d50cbfd2b2f6cae49617ab047bfef668d252adb128f7 | https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols +Noto Sans Symbols 2 | Regular | fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf | Zsym | OFL-1.1 | fonts/noto-sans-symbols-2/OFL.txt | 7d5fb73b7ca67a6798101741f5d280a3d016a56a197afcd4199dbb57b4b82a21 | https://github.com/google/fonts/tree/47d54ca1ebb14fd864695f1d39dc734f39017c33/ofl/notosanssymbols2 +Noto Sans Hebrew | Regular | fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf | Hebr | OFL-1.1 | fonts/noto-sans-hebrew/OFL.txt | 671951828bd5c95db818e5bb12dcea2d0c0dda00311888522be061ee6835125e | https://github.com/notofonts/hebrew/releases/tag/NotoSansHebrew-v3.001 diff --git a/fonts/nanum-gothic/NanumGothic-Regular.ttf b/fonts/nanum-gothic/NanumGothic-Regular.ttf new file mode 100644 index 0000000..2aa2b20 Binary files /dev/null and b/fonts/nanum-gothic/NanumGothic-Regular.ttf differ diff --git a/fonts/nanum-gothic/OFL.txt b/fonts/nanum-gothic/OFL.txt new file mode 100644 index 0000000..69ff401 --- /dev/null +++ b/fonts/nanum-gothic/OFL.txt @@ -0,0 +1,96 @@ +Copyright (c) 2010, NHN Corporation (http://www.nhncorp.com), +with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver +NanumGothic, NanumMyeongjo, Naver NanumMyeongjo, NanumBrush, Naver +NanumBrush, NanumPen, Naver NanumPen. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf b/fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf new file mode 100644 index 0000000..6024771 Binary files /dev/null and b/fonts/noto-sans-hebrew/NotoSansHebrew-Regular.ttf differ diff --git a/fonts/noto-sans-hebrew/OFL.txt b/fonts/noto-sans-hebrew/OFL.txt new file mode 100644 index 0000000..56662b6 --- /dev/null +++ b/fonts/noto-sans-hebrew/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/hebrew) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf b/fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf new file mode 100644 index 0000000..caf89dd Binary files /dev/null and b/fonts/noto-sans-symbols-2/NotoSansSymbols2-Regular.ttf differ diff --git a/fonts/noto-sans-symbols-2/OFL.txt b/fonts/noto-sans-symbols-2/OFL.txt new file mode 100644 index 0000000..7c95767 --- /dev/null +++ b/fonts/noto-sans-symbols-2/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/noto-sans-symbols/NotoSansSymbols.ttf b/fonts/noto-sans-symbols/NotoSansSymbols.ttf new file mode 100644 index 0000000..a0061bf Binary files /dev/null and b/fonts/noto-sans-symbols/NotoSansSymbols.ttf differ diff --git a/fonts/noto-sans-symbols/OFL.txt b/fonts/noto-sans-symbols/OFL.txt new file mode 100644 index 0000000..7c95767 --- /dev/null +++ b/fonts/noto-sans-symbols/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/simplified-chinese-manifest.txt b/fonts/simplified-chinese-manifest.txt new file mode 100644 index 0000000..5ae2438 --- /dev/null +++ b/fonts/simplified-chinese-manifest.txt @@ -0,0 +1,7 @@ +# Opt-in native-only Simplified Chinese fonts. Same 8-field format as fonts/manifest.txt: +# family | style | file (relative to the repo root) | scripts (ISO 15924, comma-separated) | licence | licence file | sha256 | source +# Apps read this file separately from fonts/manifest.txt, and native builds only. It is not part +# of craft-fonts' default FONTS table or embed payload and must never be added to a wasm allowlist. +# DeckCraft classifies Han characters as Hani, so the row carries Hani,Hans. +# Pinned release: v1.530, commit b712c83d98acf977bacfb7fb010207802808bda9. +LXGW WenKai Lite | Regular | fonts/lxgw-wenkai-lite/LXGWWenKaiLite-Regular.ttf | Hani,Hans | OFL-1.1 | fonts/lxgw-wenkai-lite/OFL.txt | f799a538a3355877e597a0ae04050e897932c9ae7689eb9227c2953c5dd549ea | https://github.com/lxgw/LxgwWenKai-Lite/releases/tag/v1.530