mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-25 10:39:01 -06:00
* M7: support shaped Thai text in Add Text and UI fallback fonts Share the bounded shaping and Type 3 writer with Thai, preserving vowel and tone clusters and original Unicode through save/reopen. Keep the Thai face optional and update UI notices and their translations. Constraint: Thai font assets remain in the companion craft-fonts repository. Confidence: high Scope-risk: moderate Co-authored-by: Codex <noreply@openai.com> * M7: show Thai Add Text before and after the fix Document the rejected input on the base commit and the saved Thai text on the fixed build using genuine UI captures. Constraint: Synthetic content and licensed fonts only; system font fallback disabled. Co-authored-by: Codex <noreply@openai.com> * M7: demonstrate Thai stress cases in the comparison screenshot Show nine lines of stacked marks, both Sara Am encodings, Thai digits and mixed Latin text. Verify all 198 Unicode code points survive saving and extraction with both PdfCraft and Poppler. Co-authored-by: Codex <noreply@openai.com> * CI and releases: craft-fonts pin to 3d9ab3a (adds Noto Sans Thai) ci.yml, freebsd.yml and the craft-fonts-ref release.yml passes to craft-common-actions move from 8dcdacd to storytold/craft-fonts 3d9ab3a, the revision with Noto Sans Thai (#18), so Thai Add Text is built in and tested. * ATTRIBUTION: drop the screenshot entry again (the image isn't committed) --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
75 lines
4.0 KiB
Rust
75 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` or `Thai` face), to keep the web build within hosting limits (Cloudflare Pages:
|
|
//! 25 MiB per file).
|
|
|
|
use std::fmt::Write as _;
|
|
use std::path::PathBuf;
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
/// Family-name prefixes of the faces AGENTS.md §1.1 forbids: Adobe's Source families and Noto
|
|
/// CJK, which is Source Han under another name. The test in `src/craft.rs` has the same list.
|
|
const BARRED_FAMILIES: [&str; 5] = ["Source Han", "Source Serif", "Source Sans", "Noto Sans CJK", "Noto Serif CJK"];
|
|
|
|
/// 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}"));
|
|
};
|
|
// AGENTS.md §1.1 bars Adobe's type designs whatever their licence, so a checkout that
|
|
// carries one (craft-fonts has Noto Sans CJK, which is Source Han) never embeds it.
|
|
if BARRED_FAMILIES.iter().any(|barred| family.starts_with(barred)) {
|
|
println!("cargo::warning=craft-fonts: not embedding {family} {style} (AGENTS.md §1.1)");
|
|
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");
|
|
let web_face =
|
|
(*family == "BIZ UDPGothic" && *style == "Regular") || arabic || telugu || hebrew || scripts.split(',').any(|s| s.trim() == "Thai");
|
|
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)
|
|
}
|