mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-30 13:08:48 -06:00
* Allow OFL typefaces whoever designed them (AGENTS.md §1.1) Noto CJK and Source Han from their open-source releases are allowed like any other OFL font. The fonts build no longer skips them, the asset check accepts an OFL font credited to Adobe, and the docs describe the Chinese faces that result. The ban on Adobe icons, artwork, UI graphics and the fonts Adobe products install stays. * Leave out whole CJK faces other than the Japanese ones for size A Chinese or Korean face is embedded whole only under 4 MiB, so the 16.4 MB Noto Sans CJK SC in craft-fonts stays out of the binary and the embedded total against the pinned craft-fonts is unchanged (25,204,828 bytes). * docs: replacement boxes for Chinese without craft-fonts only on the web or without an installed Chinese face * xtask assets: the OFL font exemption needs at least one licence ID * fonts build: count a CJK script tag with a region (Hant-HK) as CJK, shared with the embedding test * AGENTS.md: the asset gate exempts OFL fonts from the Adobe-author check * fonts build: count a Hangul-only face (Hang) as CJK for the size rule * fonts: the CJK embedding test checks the size rule, sharing its constants with build.rs * docs: the missing Hans face is a property of the pinned craft-fonts, not of every build
83 lines
4.0 KiB
Rust
83 lines
4.0 KiB
Rust
//! Embeds the fonts of the optional craft-fonts build input (`CRAFT_FONTS_DIR`) as `CRAFT_FONTS`.
|
|
//! The recipe is craft-fonts' `docs/integration.md`; unset, `CRAFT_FONTS` is empty. It only reads
|
|
//! the local checkout (no network). On wasm32 it embeds only the UI faces (BIZ UDPGothic Regular
|
|
//! and any `Arab`, `Telu`, `Hebr`, `Thai` or `Deva` face), to keep the web build within hosting
|
|
//! limits (Cloudflare Pages: 25 MiB per file).
|
|
|
|
use std::fmt::Write as _;
|
|
use std::path::PathBuf;
|
|
|
|
#[path = "src/cjk_scripts.rs"]
|
|
mod cjk_scripts;
|
|
|
|
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");
|
|
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() => {
|
|
println!("cargo::error=CRAFT_FONTS_DIR={}: {e}", dir.display());
|
|
}
|
|
Err(e) => println!("cargo::warning=building without craft-fonts: CRAFT_FONTS_DIR={}: {e}", dir.display()),
|
|
}
|
|
}
|
|
src.push_str("];\n");
|
|
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("craft_fonts.rs");
|
|
if let Err(e) = std::fs::write(&out, src) {
|
|
println!("cargo::error=writing {}: {e}", out.display());
|
|
}
|
|
}
|
|
|
|
/// One `CraftFont { .. }` initialiser per manifest line.
|
|
fn craft_fonts(dir: &std::path::Path) -> Result<String, String> {
|
|
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()))?;
|
|
let wasm = std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|a| a == "wasm32");
|
|
let mut out = String::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 {
|
|
return Err(format!("malformed manifest line: {line}"));
|
|
};
|
|
// Whole CJK faces aren't embedded for size; interface CJK comes from cut faces once the app
|
|
// moves to craft-fonts-build (storytold/craft-libs#213).
|
|
let cjk = scripts.split(',').any(cjk_scripts::is_cjk_script);
|
|
if cjk
|
|
&& !cjk_scripts::WHOLE_CJK_FAMILIES.contains(family)
|
|
&& std::fs::metadata(dir.join(file)).is_ok_and(|m| m.len() > cjk_scripts::MAX_OTHER_CJK_BYTES)
|
|
{
|
|
continue;
|
|
}
|
|
// Arabic interface faces are small, so the web build keeps them too.
|
|
let arabic = scripts.split(',').any(|s| s.trim() == "Arab");
|
|
// Telugu faces too, for the Telugu interface.
|
|
let telugu = scripts.split(',').any(|s| s.trim() == "Telu");
|
|
// And the Hebrew ones (small), for Hebrew text and Edit text on the web.
|
|
let hebrew = scripts.split(',').any(|s| s.trim() == "Hebr");
|
|
// Devanagari too, for the Hindi interface (one small face).
|
|
let devanagari = scripts.split(',').any(|s| s.trim() == "Deva");
|
|
let web_face = (*family == "BIZ UDPGothic" && *style == "Regular")
|
|
|| arabic
|
|
|| telugu
|
|
|| hebrew
|
|
|| scripts.split(',').any(|s| s.trim() == "Thai")
|
|
|| devanagari;
|
|
if wasm && !web_face {
|
|
continue;
|
|
}
|
|
let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?;
|
|
println!("cargo::rerun-if-changed={}", path.display());
|
|
let scripts: Vec<String> = scripts.split(',').map(|s| format!("{:?}", s.trim())).collect();
|
|
let _ = writeln!(
|
|
out,
|
|
" CraftFont {{ family: {family:?}, style: {style:?}, scripts: &[{}], bytes: include_bytes!({:?}) }},",
|
|
scripts.join(", "),
|
|
path.display().to_string(),
|
|
);
|
|
}
|
|
Ok(out)
|
|
}
|