Files
pdfcraft/crates/ui-egui/tests/fonts.rs
T
Brandon ThomasandClaude Opus 5.5 1d67833599 Fonts: use craft-fonts as an optional build input; move the Japanese fonts out
Shippori Mincho (and its OFL file) leaves assets/fonts/: the Japanese fonts now
live in storytold/craft-fonts. printcraft-fonts gets craft-fonts' build.rs recipe:
with CRAFT_FONTS_DIR=<checkout> it embeds the manifest's fonts as CRAFT_FONTS
(wasm32: only BIZ UDPGothic Regular); without it CRAFT_FONTS is empty and the
app builds, tests and runs as before, minus the Japanese face.

- SHIPPORI_MINCHO is now Option<&[u8]> from CRAFT_FONTS; japanese_glyph
  (was shippori_glyph) uses document_japanese_font() (Shippori Mincho, then
  BIZ UDMincho, then any Jpan face) and returns GlyphError::NoFont without one.
- Editing PDF text into Japanese without craft-fonts is a clear EditError that
  leaves the page untouched.
- UI: every Jpan craft font (BIZ UDPGothic first) is the last fallback of
  every egui family, sharing the same static bytes.
- Tests: Japanese glyph/edit tests skip without craft-fonts; new UI tests for
  no-tofu Japanese with craft-fonts and working fonts without it.
- CI: the Linux test job checks out craft-fonts at a pinned SHA and runs the
  tests with CRAFT_FONTS_DIR. Releases build every platform with it
  (CRAFT_FONTS_REQUIRED=1) and ship each font's OFL as OFL-<family>.txt.
- ATTRIBUTION.toml gains [[build_input]] (checked by xtask assets); NOTICE,
  AGENTS.md, CLAUDE.md, README and docs say fonts live in craft-fonts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 11:36:23 -04:00

59 lines
2.8 KiB
Rust

//! The interface fonts with and without the optional craft-fonts build input (`CRAFT_FONTS_DIR`).
use egui::epaint::text::{Fonts, TextOptions};
use egui::{Color32, FontFamily, FontId};
use printcraft_ui_egui::theme;
const JAPANESE: &str = "日本語の文字";
fn families() -> Vec<FontId> {
vec![FontId::proportional(13.0), FontId::monospace(13.0), theme::medium(13.0), theme::semibold(17.0)]
}
/// Lays `text` out in every interface family and returns each galley's width.
fn layout_widths(fonts: &mut Fonts, text: &str) -> Vec<f32> {
let mut view = fonts.with_pixels_per_point(2.0);
families().into_iter().map(|id| view.layout_no_wrap(text.to_owned(), id, Color32::BLACK).size().x).collect()
}
/// Built with craft-fonts, Japanese text renders with real glyphs (no tofu) in every family,
/// from a craft-fonts face placed after the app's own fonts.
#[test]
fn japanese_ui_text_uses_craft_fonts() {
if printcraft_fonts::ui_japanese_fonts().is_empty() {
eprintln!("skipping japanese_ui_text_uses_craft_fonts: built without craft-fonts (set CRAFT_FONTS_DIR to run it)");
return;
}
let defs = theme::font_definitions();
for family in [FontFamily::Proportional, FontFamily::Monospace] {
let stack = &defs.families[&family];
let first_jp = stack.iter().position(|n| n.starts_with("BIZ UDPGothic")).expect("BIZ UDPGothic is a fallback");
let own = stack.iter().position(|n| n == "Inter" || n == "JetBrainsMono").expect("the app's own font");
assert!(own < first_jp, "{family:?}: {stack:?}");
assert!(stack[first_jp..].iter().all(|n| printcraft_fonts::CRAFT_FONTS.iter().any(|f| f.name() == *n)), "{stack:?}");
}
let mut fonts = Fonts::new(TextOptions::default(), defs);
for id in families() {
assert!(fonts.has_glyphs(&id, JAPANESE), "{id:?} lacks {JAPANESE}");
}
// Real glyphs are about a full em wide each; tofu boxes and missing glyphs are not.
for w in layout_widths(&mut fonts, JAPANESE) {
assert!(w > 13.0 * 0.8 * JAPANESE.chars().count() as f32, "{w}");
}
}
/// Without craft-fonts the interface fonts still install and lay out any text (Japanese falls
/// back to egui's replacement glyph) without panicking; Latin text is unaffected.
#[test]
fn ui_fonts_work_without_craft_fonts() {
let mut fonts = Fonts::new(TextOptions::default(), theme::font_definitions());
for id in families() {
assert!(fonts.has_glyphs(&id, "PrintCraft"), "{id:?}");
assert_eq!(fonts.has_glyphs(&id, JAPANESE), !printcraft_fonts::ui_japanese_fonts().is_empty(), "{id:?}");
}
assert!(layout_widths(&mut fonts, JAPANESE).iter().all(|w| w.is_finite() && *w > 0.0));
assert!(layout_widths(&mut fonts, "PrintCraft").iter().all(|w| *w > 20.0));
let ctx = egui::Context::default();
theme::install_fonts(&ctx);
}