mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 14:08:23 -06:00
M2: document installed CJK font resolution contracts
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# PdfCraft roadmap
|
||||
|
||||
> **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** patch (M2, PR #4172: document installed-font resolution helpers and regression contracts; behavior and estimates unchanged)
|
||||
|
||||
> **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** patch (M2, PR #4172: print preview excludes installed CJK fonts and isolates its font cache from the viewer; estimates unchanged)
|
||||
|
||||
> **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** patch (M2, PR #4172: reconcile current main while retaining display-only CJK fallback and upstream Japanese font selection; estimates unchanged)
|
||||
@@ -761,3 +763,5 @@ Newest first: the date, what moved, and the new overall estimate.
|
||||
- **2026-10-23 (M2, PR #4172):** Combine thumbnails and accessibility figure previews use the viewer's display-only CJK fallback. Windows family matching includes FangSong and subset-prefixed Founder names. Export, print, OCR and automation remain off by default; estimates unchanged.
|
||||
|
||||
- **2026-10-26 (M2, PR #4172 print-preview review):** Print-preview requests disable installed CJK fallback and use an isolated cache so preview font availability matches output. Viewer fallback survives switching in either direction. Synthetic GBK regressions cover inline and single-worker rendering, full pages and tiles; estimates unchanged.
|
||||
|
||||
- **2026-10-26 (M2, PR #4172 documentation review):** Added function documentation for installed-font script selection, bounded candidate reads, platform paths, resolver precedence and regression contracts. No runtime behavior or estimate change.
|
||||
|
||||
@@ -88,6 +88,10 @@ impl RenderConfig {
|
||||
Self { system_fonts: true, ..self }
|
||||
}
|
||||
|
||||
/// Build interpreter settings with layer and comment preferences and a font resolver that
|
||||
/// tries bundled Japanese faces, then opt-in installed CJK faces, then standard substitutes.
|
||||
/// Passwords belong to parser construction; request-specific preview overrides are applied
|
||||
/// by `render_page`, which also preserves the caller's warning and cancellation hooks.
|
||||
fn settings(&self) -> InterpreterSettings {
|
||||
let standard = InterpreterSettings::default().font_resolver;
|
||||
let system_fonts = self.system_fonts;
|
||||
|
||||
@@ -38,6 +38,8 @@ enum Script {
|
||||
}
|
||||
|
||||
impl Script {
|
||||
/// Map the four supported Adobe CJK collections to their writing systems. Identity and
|
||||
/// custom collections provide no regional hint and cannot select an installed face here.
|
||||
fn of(family: &CidFamily) -> Option<Self> {
|
||||
match family {
|
||||
CidFamily::AdobeJapan1 => Some(Self::Japanese),
|
||||
@@ -133,6 +135,9 @@ pub(crate) fn fallback(query: &FallbackFontQuery) -> Option<(FontData, u32)> {
|
||||
found.as_ref().map(|(bytes, index)| (Arc::clone(bytes) as FontData, *index))
|
||||
}
|
||||
|
||||
/// Read a candidate through the bounded file cache and select a face that maps `probe`,
|
||||
/// preferring the requested region and weight. Unreadable files and unsuitable faces return
|
||||
/// `None`, allowing the caller to try the next candidate without changing the PDF.
|
||||
fn read(candidate: &Candidate, probe: char, bold: bool) -> Option<(Arc<Vec<u8>>, u32)> {
|
||||
let bytes = file_bytes(&candidate.path)?;
|
||||
let index = pick_face(&bytes, probe, candidate.region, bold)?;
|
||||
@@ -197,6 +202,10 @@ fn pick_face(bytes: &[u8], probe: char, region: &[&str], bold: bool) -> Option<u
|
||||
best.map(|(index, _)| index)
|
||||
}
|
||||
|
||||
/// List installed-font paths in preference order, with regional face-name hints for collections.
|
||||
/// Windows Simplified Chinese first tries the named document family; its generic list also
|
||||
/// prefers a bold face for bold requests. macOS includes bounded downloaded-font locations.
|
||||
/// Only fixed platform filenames are used: a PDF font name never becomes a filesystem path.
|
||||
fn candidates(script: Script, family: ChineseFamily, bold: bool) -> Vec<Candidate> {
|
||||
let region: &'static [&'static str] = match script {
|
||||
Script::Japanese => &["JP", "Japanese"],
|
||||
@@ -283,6 +292,8 @@ fn macos_font_asset_dirs() -> &'static [PathBuf] {
|
||||
})
|
||||
}
|
||||
|
||||
/// Locate the Windows Fonts directory using `WINDIR`, then `SystemRoot`, then `C:\Windows`.
|
||||
/// This only constructs the path; candidate reads still enforce regular-file and size limits.
|
||||
fn windows_fonts() -> PathBuf {
|
||||
let dir = std::env::var_os("WINDIR").or_else(|| std::env::var_os("SystemRoot")).map_or_else(|| PathBuf::from(r"C:\Windows"), PathBuf::from);
|
||||
dir.join("Fonts")
|
||||
@@ -294,6 +305,8 @@ mod tests {
|
||||
|
||||
const SCRIPTS: [Script; 4] = [Script::Japanese, Script::SimplifiedChinese, Script::TraditionalChinese, Script::Korean];
|
||||
|
||||
/// Standard, localized and subset-prefixed names retain their Chinese family hint;
|
||||
/// unrelated Latin names and generic YaHei requests use the default candidate list.
|
||||
#[test]
|
||||
fn chinese_family_hints_include_subset_and_localized_names() {
|
||||
for name in ["SimSun", "ABCDEF+SimSun", "STSong-Light", "宋体"] {
|
||||
@@ -340,6 +353,8 @@ mod tests {
|
||||
assert!(fangsong.first().is_some_and(|c| c.path.ends_with("simfang.ttf")));
|
||||
}
|
||||
|
||||
/// Invalid font bytes, missing paths and directories produce no candidate face rather
|
||||
/// than preventing the resolver from trying a later installed font.
|
||||
#[test]
|
||||
fn broken_and_missing_files_are_skipped() {
|
||||
assert_eq!(pick_face(b"", '\u{7269}', &[], false), None);
|
||||
@@ -372,6 +387,8 @@ mod tests {
|
||||
assert!(file_bytes(missing).is_none() && file_bytes(missing).is_none());
|
||||
}
|
||||
|
||||
/// A Latin-only face fails every CJK probe even though its Latin glyph remains readable,
|
||||
/// distinguishing unsupported script coverage from an unparseable font file.
|
||||
#[test]
|
||||
fn a_latin_face_is_rejected_for_every_script() {
|
||||
let inter = include_bytes!("../../../assets/fonts/Inter-Regular.ttf");
|
||||
@@ -381,6 +398,8 @@ mod tests {
|
||||
assert_eq!(pick_face(inter, 'A', &[], false), Some(0));
|
||||
}
|
||||
|
||||
/// Platform candidates are absolute TrueType paths and exclude the font families barred
|
||||
/// by the asset policy, independently of whether those files are installed locally.
|
||||
#[test]
|
||||
fn candidates_are_absolute_font_files_without_adobe_designs() {
|
||||
for script in SCRIPTS {
|
||||
@@ -398,6 +417,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The four regional Adobe collections select their corresponding scripts; Identity
|
||||
/// carries no regional information and must not acquire an installed fallback by guess.
|
||||
#[test]
|
||||
fn only_the_four_adobe_collections_have_a_script() {
|
||||
assert_eq!(Script::of(&CidFamily::AdobeCNS1), Some(Script::TraditionalChinese));
|
||||
@@ -465,6 +486,8 @@ mod tests {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn macos_candidates_include_a_pingfang_that_lives_in_the_font_assets() {
|
||||
/// Independently locate an installed asset by filename within the test's depth limit,
|
||||
/// providing an oracle for the production candidate list without using its discovery.
|
||||
fn find(dir: &Path, name: &str, depth: u32) -> Option<PathBuf> {
|
||||
for entry in std::fs::read_dir(dir).ok()?.flatten() {
|
||||
let path = entry.path();
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
# PdfCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** patch (M2, PR #4172: document installed-font helpers and regression contracts; behavior and estimates unchanged)
|
||||
|
||||
> **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** patch (M2, PR #4172: print-preview font policy matches output, with isolated caches and GBK switching regressions; estimates unchanged)
|
||||
|
||||
> **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** patch (M2, PR #4172: reconcile current main and retain Japanese serif/weight selection alongside display-only CJK fallback; estimates unchanged)
|
||||
@@ -175,7 +177,7 @@ total matches the parity estimate.
|
||||
|---|---|---|---|---|---|
|
||||
| M0 | Skeleton: workspace, xtask gates, CI | 15–30 | 98% | 1–2 | GitHub workflow, `deny.toml` (licence audit of every dependency), parity checklist (826 features, `xtask parity`) done. Missing: remaining crate stubs, testkit/oracle crates |
|
||||
| M1 | COS: filters, crypt, parser, xref, writer | 120–200 | 80% | 20–40 | Done:<br>- filters and crypt: every standard-security revision R2–R6 (RC4, AES-128/256), SASLprep, permissions, creating encryption;<br>- cos: parse and repair (including invalid xref object-number ranges), decoded-stream limits including unfiltered data, decrypt on load, re-encrypt on save, incremental and full writing.<br>Corpus: open/edit/save passes on 958 files, and all 7 password-protected files open.<br>Full saves now pack objects into compressed object streams. An unchanged incremental save of a classic table writes a zero-length cross-reference subsection instead of a section with no subsections (#1339). A full rewrite keeps unknown trailer entries, including objects reachable only from them (#1338). Fuzzing runs nightly (`xtask fuzz`). Raw-deflate fallback preserves decoding-limit errors, including through tolerant COS stream decoding. Done since: #2658: macOS atomic saves retain destination extended attributes, including Finder tags, and reject partial resource-fork reads after size changes. Metadata failures retain the original PDF and unsaved edit. Missing: ≥ 250 tests, own image codecs |
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings. Packed TIFF-predictor RGB rows retain later rows in decoding and rendering. Page marks, added content and print output retain small page-unit transform coefficients when serialized. Forms without Resources use page Properties for ActualText and layer visibility; own dictionaries stop fallback. Oversized missing inline-image samples, including header and terminator overhead, no longer pin the content window (#3107 / PR #3438); normal large streamed images retain deterministic sample boundaries. Radial luminosity fades have synthetic coverage for the existing #2132 mask guard, including per-pixel references, antialiased clips, previews, scales and tiles; no additional production fix was needed. Dotted TOC leaders no longer inflate letter tracking or hide word spaces (#3086); phrase search and selection are covered. Short tracked ellipses retain their gap samples and remain searchable. Missing spacing glyphs in non-embedded Japanese CID substitutes render blank without changing PDF advances or Unicode (#3601). The desktop viewer can use installed CJK fonts, including Windows GBK Song/Hei/Kai/FangSong and subset-prefixed Founder names; Combine thumbnails and accessibility figure previews use the same display-only fallback, without changing export or print font policy (#234, continuation of #1567, review #4172). Print previews exclude installed faces and isolate their font cache; GBK regressions cover view/print switching for inline and pooled whole-page/tile rendering. |
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings. Packed TIFF-predictor RGB rows retain later rows in decoding and rendering. Page marks, added content and print output retain small page-unit transform coefficients when serialized. Forms without Resources use page Properties for ActualText and layer visibility; own dictionaries stop fallback. Oversized missing inline-image samples, including header and terminator overhead, no longer pin the content window (#3107 / PR #3438); normal large streamed images retain deterministic sample boundaries. Radial luminosity fades have synthetic coverage for the existing #2132 mask guard, including per-pixel references, antialiased clips, previews, scales and tiles; no additional production fix was needed. Dotted TOC leaders no longer inflate letter tracking or hide word spaces (#3086); phrase search and selection are covered. Short tracked ellipses retain their gap samples and remain searchable. Missing spacing glyphs in non-embedded Japanese CID substitutes render blank without changing PDF advances or Unicode (#3601). The desktop viewer can use installed CJK fonts, including Windows GBK Song/Hei/Kai/FangSong and subset-prefixed Founder names; Combine thumbnails and accessibility figure previews use the same display-only fallback, without changing export or print font policy (#234, continuation of #1567, review #4172). Print previews exclude installed faces and isolate their font cache; GBK regressions cover view/print switching for inline and pooled whole-page/tile rendering. Installed-font helpers and their regression contracts have function documentation. |
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings. Packed TIFF-predictor RGB rows retain later rows in decoding and rendering. The renderer keeps its one-point crop-size floor in physical points after UserUnit scaling, with regression coverage for crop origins, rotations and f32 reciprocal rounding. Existing unit policy and nearly-zero fallback are retained. |
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). #3808: the Pages inspector offers a 50–150% thumbnail slider and buttons, also available through `ui.set`. Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). Selected-text Copy in Edit and the canvas context menu is covered by focused regressions and native clipboard checks (#1225); document copy restrictions apply to the floating action too. #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. Page-label navigation displays logical labels, retains the current page on unchanged submission and refreshes focused same-document replacements; clean file-watcher reload preserves the chosen scrolling mode, preventing a Single Page reader from jumping to page 1 during replacement. Synthetic control regressions cover these behaviors. #3149: page-list changes discard render demand based on old geometry; actual raster dimensions are checked against the GPU limit before upload. Synthetic crash and continued-rendering regressions cover the replacement path. Save-prompt shortcuts yield to dialogs above them; stacked link-consent UI regression covers Escape and Return. Comment hover follows individual marked lines and original annotation paint order (#3541/#3620), independently of spatial inspection sorting; synthetic rotation, differing-upper-edge overlap and unchanged-document regressions. Optional page gaps join page edges without cropping PDF margins; current-document toggle, persisted default and UI control options are covered by viewer tests. Gap changes in Organize Pages preserve document scroll position, and both comparison panes follow menu and control choices. Repeated window resizes by an edge and moves by the tab strip on GNOME/Wayland work without a click in between. |
|
||||
|
||||
Reference in New Issue
Block a user