From 69f80819e1a17e8d0a58cd09fe8f1d6726c6fd95 Mon Sep 17 00:00:00 2001 From: Philip Date: Mon, 12 Oct 2026 21:04:06 +0100 Subject: [PATCH 01/19] M2: draw non-embedded CJK CID fonts with an installed face A CID font of an Adobe CJK collection (Japan1, GB1, CNS1, Korea1) that the PDF doesn't embed drew nothing unless craft-fonts had a Japanese face: text boxes from Acrobat (Kozuka Gothic) and Pages (SongtiTC) were blank. After craft-fonts, the renderer now uses one face already installed on the machine, read at runtime and never bundled. The face of a multi-region collection (PingFang SC or TC) is chosen by region. Noto CJK and Source Han are not candidates, and PDFCRAFT_SYSTEM_FONTS=0 turns it off. Co-Authored-By: Claude Sonnet 5.5 --- ROADMAP.md | 1 + crates/render/Cargo.toml | 4 +- crates/render/src/lib.rs | 1 + crates/render/src/raster.rs | 61 +++++++- crates/render/src/system_font.rs | 248 +++++++++++++++++++++++++++++++ 5 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 crates/render/src/system_font.rs diff --git a/ROADMAP.md b/ROADMAP.md index 44a26604d8..b8fc5d6956 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -141,6 +141,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log +- **2026-10-12 (M2, installed CJK fonts):** A CID font of an Adobe CJK collection (Japan1, GB1, CNS1, Korea1) that a PDF doesn't embed, such as the Kozuka Gothic of an Acrobat text box or the SongtiTC of a Pages export, drew nothing unless craft-fonts had a Japanese face. After craft-fonts, it now draws with a face already installed on the machine (PingFang, Heiti, Microsoft JhengHei / YaHei, Malgun and the like; read at runtime, never bundled; Noto CJK and Source Han excluded; `PDFCRAFT_SYSTEM_FONTS=0` turns it off). The face of a collection with several regions (PingFang SC, TC) is chosen by region. Checked on macOS with a synthetic Traditional Chinese, Simplified Chinese and Korean page; the Windows and Linux font lists are untested, and a machine with none of the listed faces still draws nothing. - **2026-10-12 (M1, #1339):** An incremental save of an unchanged classic-table file used to append `xref` and then `trailer` with no subsection, which qpdf reports as a damaged file. The section now has a zero-length subsection, so no object entry changes and a trailer-only edit is still written. Estimates unchanged. - **2026-10-12 (M12, auto-tag preview):** The engine and `accessibility_autotag_preview` propose paragraph and large-heading roles for bounded text blocks on one page, with locations and reasons. Every proposal needs review; images remain unclassified. No PDF tags are written yet. Synthetic tool tests cover normal text, a larger heading, bounds and unchanged source bytes. Estimates unchanged. - **2026-10-12 (M10, editing OCR text on scans):** Editing a recognised word on a scanned page now changes what the page shows. Before, the rewrite stayed invisible OCR text, so the scan still showed the old word; on a form, the whole OCR layer also grouped into one paragraph that rewrapped when one word changed. OCR words now group into phrases (a table cell or a tab stop splits them, and lines stay apart); an edit draws the phrase visibly in the scan's sampled ink colour over a cover in the sampled paper colour, keeping each word's width and growing rightwards. The editor covers the scanned word while typing. OCR words are now sized from their letters (a capitals-only word's box spans the cap height, not the full ascender-descender span), so recognised and rewritten text is as tall as the scanned word and sits on its baseline. Five engine tests on synthetic scans and a kittest UI test; `ocr.editable-text-output` is partial. diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 28202a38b4..71467dfeb2 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -13,6 +13,8 @@ pdfcraft-cos = { workspace = true } pdfcraft-fonts = { workspace = true } hayro = { workspace = true } hayro-syntax = { workspace = true } +# Picks the face of an installed font collection (the version pdfcraft-fonts already uses). +skrifa = "0.44" lopdf = { workspace = true } kurbo = { workspace = true } thiserror = { workspace = true } @@ -27,7 +29,5 @@ bytemuck = { version = "1" } getrandom = { version = "0.4", features = ["wasm_js"] } [dev-dependencies] -# Checks the Japanese substitute's character coverage; the version pdfcraft-fonts already uses. -skrifa = "0.44" # Builds the decompression-bomb streams in the raster tests. flate2 = { workspace = true } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index d2ee5712c4..90bc64b086 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -13,6 +13,7 @@ mod pixels; mod raster; mod reading; mod structure; +mod system_font; pub mod text; pub use inspect::{ diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 0e64ac241e..fe469958ae 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -73,7 +73,7 @@ impl RenderConfig { InterpreterSettings { ocg_overrides: self.layers.clone(), hide_comments: self.hide_comments, - font_resolver: Arc::new(move |query| japanese_fallback(query).or_else(|| standard(query))), + font_resolver: Arc::new(move |query| japanese_fallback(query).or_else(|| system_fallback(query)).or_else(|| standard(query))), ..InterpreterSettings::default() } } @@ -106,6 +106,14 @@ fn japanese_fallback(query: &FontQuery) -> Option<(FontData, u32)> { Some((Arc::new(face.bytes), 0)) } +/// An installed face for a CID font of an Adobe CJK collection that the PDF doesn't embed and +/// craft-fonts has no face for (Traditional and Simplified Chinese, Korean, and Japanese in a +/// build without craft-fonts). `None` for every other font. +fn system_fallback(query: &FontQuery) -> Option<(FontData, u32)> { + let FontQuery::Fallback(f) = query else { return None }; + crate::system_font::fallback(f) +} + #[derive(Debug, PartialEq)] enum JapaneseFace { Mincho, @@ -5515,6 +5523,57 @@ trailer << /Root 1 0 R >> } } + /// Chinese and Korean CID fonts that aren't embedded (Adobe-CNS1 `MSung-Light`, Adobe-GB1 + /// `STSong-Light`, Adobe-Korea1 `HYGoThic-Medium`; Acrobat also writes Japanese `KozGoPr6N` + /// text boxes without the font) drew nothing, because craft-fonts has only Japanese faces. + /// They now draw in a face already installed on the machine, read at runtime. A machine + /// without such a face draws nothing, as before, so each check is skipped there. + #[test] + fn non_embedded_chinese_and_korean_cid_fonts_draw_with_an_installed_face() { + use hayro::hayro_interpret::font::FallbackFontQuery; + use hayro::hayro_interpret::hayro_cmap::CharacterCollection; + // (base font, collection, ordering, encoding, UCS-2 code units of the text) + let cases = [ + ("MSung-Light", CidFamily::AdobeCNS1, "CNS1", "UniCNS-UCS2-H", "7E419AD45B57"), // 繁體字 + ("STSong-Light", CidFamily::AdobeGB1, "GB1", "UniGB-UCS2-H", "72698D448DF7"), // 物资跷 (common Simplified) + ("HYGoThic-Medium", CidFamily::AdobeKorea1, "Korea1", "UniKS-UCS2-H", "D55CAE00"), // 한글 + ]; + for (base_font, family, ordering, encoding, code) in cases { + let query = FontQuery::Fallback(FallbackFontQuery { + post_script_name: Some(base_font.into()), + character_collection: Some(CharacterCollection { family, supplement: 2 }), + ..FallbackFontQuery::default() + }); + if super::system_fallback(&query).is_none() { + eprintln!("no installed face for {base_font} (or PDFCRAFT_SYSTEM_FONTS=0): skipped"); + continue; + } + let content = format!("BT /F1 40 Tf 5 15 Td <{code}> Tj ET"); + let pdf = format!( + "%PDF-1.7 +1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj +2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj +3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 60] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj +4 0 obj << /Length {} >> stream +{content} +endstream endobj +5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /{base_font} /Encoding /{encoding} /DescendantFonts [6 0 R] >> endobj +6 0 obj << /Type /Font /Subtype /CIDFontType0 /BaseFont /{base_font} /CIDSystemInfo << /Registry (Adobe) /Ordering ({ordering}) /Supplement 2 >> + /FontDescriptor 7 0 R /DW 1000 >> endobj +7 0 obj << /Type /FontDescriptor /FontName /{base_font} /Flags 6 /FontBBox [0 -141 1000 859] /ItalicAngle 0 /Ascent 859 /Descent -141 /CapHeight 700 /StemV 80 >> endobj +trailer << /Root 1 0 R >> +%%EOF", + content.len() + ); + let mut r = PageRenderer::new(Arc::new(pdf.into_bytes()), RenderConfig::default()); + let p = r.render(RenderRequest { page: 0, kind: RequestKind::Pixels, tile: None, scale: 1.0, tag: 0, ..Default::default() }); + assert!(p.error.is_none(), "{base_font}: {:?}", p.error); + let inked = p.rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count(); + // Two to four glyphs at 40 pt cover several hundred dark pixels; a blank run has none. + assert!(inked > 300, "{base_font}: the text is drawn ({inked} dark pixels)"); + } + } + /// Half-width katakana are common in Japanese documents set in a non-embedded Mincho font /// such as HeiseiMin-W3. The Mincho substitute must have those glyphs: one that lacks them /// (Shippori Mincho) draws them as .notdef. diff --git a/crates/render/src/system_font.rs b/crates/render/src/system_font.rs new file mode 100644 index 0000000000..d2c98bb7cd --- /dev/null +++ b/crates/render/src/system_font.rs @@ -0,0 +1,248 @@ +//! Installed CJK faces for CID fonts a PDF doesn't embed. +//! +//! Acrobat and other tools often write Chinese, Japanese or Korean text with one of the Adobe +//! collections (Adobe-Japan1, GB1, CNS1, Korea1) and a font name such as `KozGoPr6N-Medium` or +//! `MSung-Light` without embedding the font. hayro maps those CIDs to Unicode itself, but it needs +//! a face to draw them with; without one the text is blank. The craft-fonts Japanese faces come +//! first (see `japanese_fallback` in `raster.rs`); this module is the last resort: one face +//! already installed on this machine. It is read at runtime and never embedded or shipped +//! (AGENTS.md §1.4), Noto CJK and Source Han are left out on purpose (§1.1), and +//! `PDFCRAFT_SYSTEM_FONTS=0` turns it off, as for the interface font. + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex, PoisonError}; + +use hayro::hayro_interpret::font::{FallbackFontQuery, FontData}; +use hayro::hayro_interpret::hayro_cmap::CidFamily; +use skrifa::MetadataProvider as _; +use skrifa::string::StringId; + +/// Larger files are not read: a font path is still untrusted input. CJK system faces are big +/// collections (PingFang is tens of megabytes). +const MAX_BYTES: u64 = 96 << 20; +/// Faces tried in a collection (`.ttc`). +const MAX_FACES: u32 = 32; + +/// The writing system a character collection is for. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum Script { + Japanese, + SimplifiedChinese, + TraditionalChinese, + Korean, +} + +impl Script { + fn of(family: &CidFamily) -> Option { + match family { + CidFamily::AdobeJapan1 => Some(Self::Japanese), + CidFamily::AdobeGB1 => Some(Self::SimplifiedChinese), + CidFamily::AdobeCNS1 => Some(Self::TraditionalChinese), + CidFamily::AdobeKorea1 => Some(Self::Korean), + CidFamily::AdobeIdentity | CidFamily::Custom { .. } => None, + } + } + + /// A character the face must have to be worth using. Each is common and not shared by the + /// other scripts' faces: 物 (Japanese; also Chinese, so it only rules out Latin-only faces), + /// 欢 (Simplified only), 說 (Traditional only), 한 (Hangul). + fn probe(self) -> char { + match self { + Self::Japanese => '\u{7269}', + Self::SimplifiedChinese => '\u{6B22}', + Self::TraditionalChinese => '\u{8AAA}', + Self::Korean => '\u{D55C}', + } + } +} + +/// One installed file and the words (in a face's family name) that pick the face for the right +/// region out of a collection that holds several (PingFang SC, TC and HK share one file). +struct Candidate { + path: PathBuf, + region: &'static [&'static str], +} + +/// The installed face to draw a CID font of `query`'s collection with, as `(data, face index)`. +/// `None` when it is turned off, the collection isn't one of the four, or nothing fits. +pub(crate) fn fallback(query: &FallbackFontQuery) -> Option<(FontData, u32)> { + if cfg!(target_arch = "wasm32") || std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0") { + return None; + } + let script = Script::of(&query.character_collection.as_ref()?.family)?; + let bold = query.is_bold || query.font_weight >= 600; + /// A face file and the index of the chosen face in it, or `None` when nothing fits. + type Face = Option<(Arc>, u32)>; + static CACHE: Mutex>> = Mutex::new(None); + let mut cache = CACHE.lock().unwrap_or_else(PoisonError::into_inner); + let found = cache.get_or_insert_with(HashMap::new).entry((script, bold)).or_insert_with(|| { + candidates(script) + .iter() + .find_map(|c| read(c, script.probe(), bold)) + .inspect(|(_, index)| log::info!("CJK font fallback for {script:?}: face {index}")) + }); + found.as_ref().map(|(bytes, index)| (Arc::clone(bytes) as FontData, *index)) +} + +fn read(candidate: &Candidate, probe: char, bold: bool) -> Option<(Arc>, u32)> { + let meta = std::fs::metadata(&candidate.path).ok()?; + if !meta.is_file() || meta.len() > MAX_BYTES { + return None; + } + let bytes = std::fs::read(&candidate.path).ok()?; + let index = pick_face(&bytes, probe, candidate.region, bold)?; + Some((Arc::new(bytes), index)) +} + +/// The best face of the file that parses and maps `probe`: the region's face when the file has +/// one, then the regular (or, for bold text, the semibold or bold) weight, then the first. +fn pick_face(bytes: &[u8], probe: char, region: &[&str], bold: bool) -> Option { + let mut best: Option<(u32, u32)> = None; + for index in 0..MAX_FACES { + let Ok(font) = skrifa::FontRef::from_index(bytes, index) else { continue }; + if font.charmap().map(probe).is_none() { + continue; + } + let names = |id: StringId| font.localized_strings(id).map(|s| s.chars().collect::()).collect::>(); + let family = names(StringId::FAMILY_NAME).join(" "); + let style = names(StringId::SUBFAMILY_NAME).join(" ").to_ascii_lowercase(); + let mut score = 1; + if region.iter().any(|r| family.split_whitespace().any(|w| w == *r)) { + score += 4; + } + let weighty = style.contains("bold") || style.contains("semibold") || style.contains("heavy"); + let plain = style.is_empty() || style.contains("regular") || style.contains("normal") || style.contains("medium") && !bold; + if (bold && weighty) || (!bold && plain && !weighty) { + score += 2; + } + if best.is_none_or(|(_, s)| score > s) { + best = Some((index, score)); + } + } + best.map(|(index, _)| index) +} + +fn candidates(script: Script) -> Vec { + let region: &'static [&'static str] = match script { + Script::Japanese => &["JP", "Japanese"], + Script::SimplifiedChinese => &["SC", "GB"], + Script::TraditionalChinese => &["TC", "HK", "CNS"], + Script::Korean => &["KR", "Korean"], + }; + let files: Vec = if cfg!(windows) { + let dir = windows_fonts(); + let names: &[&str] = match script { + Script::Japanese => &["YuGothR.ttc", "meiryo.ttc", "msgothic.ttc"], + Script::SimplifiedChinese => &["msyh.ttc", "simsun.ttc", "msjh.ttc"], + Script::TraditionalChinese => &["msjh.ttc", "mingliu.ttc", "msyh.ttc"], + Script::Korean => &["malgun.ttf", "gulim.ttc"], + }; + names.iter().map(|f| dir.join(f)).collect() + } else if cfg!(target_os = "macos") { + let names: &[&str] = match script { + Script::Japanese => &["ヒラギノ角ゴシック W3.ttc", "Hiragino Sans GB.ttc", "PingFang.ttc"], + Script::SimplifiedChinese => &["PingFang.ttc", "Hiragino Sans GB.ttc", "STHeiti Light.ttc"], + Script::TraditionalChinese => &["PingFang.ttc", "STHeiti Light.ttc", "Hiragino Sans GB.ttc"], + Script::Korean => &["AppleSDGothicNeo.ttc", "AppleGothic.ttf", "PingFang.ttc"], + }; + names.iter().map(|f| Path::new("/System/Library/Fonts").join(f)).collect() + } else { + let names: &[&str] = match script { + Script::Korean => { + &["truetype/nanum/NanumGothic.ttf", "nanum/NanumGothic.ttf", "truetype/baekmuk/gulim.ttf", "truetype/unfonts-core/UnDotum.ttf"] + } + Script::Japanese => &[ + "truetype/fonts-japanese-gothic.ttf", + "ipa-gothic/ipag.ttf", + "truetype/takao-gothic/TakaoGothic.ttf", + "truetype/vlgothic/VL-Gothic-Regular.ttf", + ], + Script::SimplifiedChinese | Script::TraditionalChinese => &[ + "truetype/wqy/wqy-microhei.ttc", + "wenquanyi/wqy-microhei/wqy-microhei.ttc", + "wqy-microhei/wqy-microhei.ttc", + "truetype/wqy/wqy-zenhei.ttc", + "wenquanyi/wqy-zenhei/wqy-zenhei.ttc", + "wqy-zenhei/wqy-zenhei.ttc", + "truetype/droid/DroidSansFallbackFull.ttf", + "google-droid-sans-fonts/DroidSansFallbackFull.ttf", + "truetype/arphic/uming.ttc", + "arphic-uming/uming.ttc", + ], + }; + ["/usr/share/fonts", "/usr/local/share/fonts"].iter().flat_map(|dir| names.iter().map(move |f| Path::new(dir).join(f))).collect() + }; + files.into_iter().map(|path| Candidate { path, region }).collect() +} + +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") +} + +#[cfg(test)] +mod tests { + use super::*; + + const SCRIPTS: [Script; 4] = [Script::Japanese, Script::SimplifiedChinese, Script::TraditionalChinese, Script::Korean]; + + #[test] + fn broken_and_missing_files_are_skipped() { + assert_eq!(pick_face(b"", '\u{7269}', &[], false), None); + assert_eq!(pick_face(b"not a font at all", '\u{7269}', &[], false), None); + assert_eq!(pick_face(&[0u8; 4096], '\u{7269}', &[], false), None); + let missing = Candidate { path: PathBuf::from("definitely/not/here.ttc"), region: &[] }; + assert!(read(&missing, '\u{7269}', false).is_none()); + // A directory is not a font. + let dir = Candidate { path: std::env::temp_dir(), region: &[] }; + assert!(read(&dir, '\u{7269}', false).is_none()); + } + + #[test] + fn a_latin_face_is_rejected_for_every_script() { + let inter = include_bytes!("../../../assets/fonts/Inter-Regular.ttf"); + for script in SCRIPTS { + assert_eq!(pick_face(inter, script.probe(), &[], false), None, "{script:?}"); + } + assert_eq!(pick_face(inter, 'A', &[], false), Some(0)); + } + + #[test] + fn candidates_are_absolute_font_files_without_adobe_designs() { + for script in SCRIPTS { + let list = candidates(script); + assert!(!list.is_empty(), "{script:?}"); + for c in list { + assert!(c.path.is_absolute(), "{}", c.path.display()); + assert!(c.path.extension().is_some_and(|e| e == "ttf" || e == "ttc"), "{}", c.path.display()); + let name = c.path.to_string_lossy().to_lowercase(); + assert!(!["notosanscjk", "notoserifcjk", "sourcehan", "source-han"].iter().any(|b| name.contains(b)), "{name}"); + } + } + } + + #[test] + fn only_the_four_adobe_collections_have_a_script() { + assert_eq!(Script::of(&CidFamily::AdobeCNS1), Some(Script::TraditionalChinese)); + assert_eq!(Script::of(&CidFamily::AdobeGB1), Some(Script::SimplifiedChinese)); + assert_eq!(Script::of(&CidFamily::AdobeKorea1), Some(Script::Korean)); + assert_eq!(Script::of(&CidFamily::AdobeJapan1), Some(Script::Japanese)); + assert_eq!(Script::of(&CidFamily::AdobeIdentity), None); + } + + /// On a Mac the face for Traditional Chinese is the TC one of PingFang, not the first of the + /// collection (which is another region's), and it maps a character only Traditional has. + #[cfg(target_os = "macos")] + #[test] + fn macos_picks_the_regional_pingfang_face() { + let path = Path::new("/System/Library/Fonts/PingFang.ttc"); + let Ok(bytes) = std::fs::read(path) else { return }; + for (script, region) in [(Script::TraditionalChinese, "TC"), (Script::SimplifiedChinese, "SC")] { + let index = pick_face(&bytes, script.probe(), &[region], false).expect("PingFang covers it"); + let font = skrifa::FontRef::from_index(&bytes, index).unwrap(); + let family: String = font.localized_strings(StringId::FAMILY_NAME).next().unwrap().chars().collect(); + assert!(family.contains(region), "{script:?} got {family:?}"); + } + } +} From 5b3b87430b493b7dc94bffdb13183bfd751ddc5e Mon Sep 17 00:00:00 2001 From: Philip Date: Mon, 19 Oct 2026 16:41:46 +0100 Subject: [PATCH 02/19] M2: review of #1567: read each installed font file once and share it CodeRabbit: the fallback cache was keyed by (script, weight) and every key read and kept its own copy of the file, so PingFang.ttc (tens of megabytes, a candidate of several scripts and both weights) could be held up to four times. The bytes now come from a process-wide cache keyed by path, remembering files that cannot be used too; the per-script cache keeps a handle and a face index. Co-Authored-By: Claude Sonnet 5.5 --- crates/render/src/system_font.rs | 47 ++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/crates/render/src/system_font.rs b/crates/render/src/system_font.rs index d2c98bb7cd..0d9d158a35 100644 --- a/crates/render/src/system_font.rs +++ b/crates/render/src/system_font.rs @@ -86,13 +86,30 @@ pub(crate) fn fallback(query: &FallbackFontQuery) -> Option<(FontData, u32)> { } fn read(candidate: &Candidate, probe: char, bold: bool) -> Option<(Arc>, u32)> { - let meta = std::fs::metadata(&candidate.path).ok()?; - if !meta.is_file() || meta.len() > MAX_BYTES { + let bytes = file_bytes(&candidate.path)?; + let index = pick_face(&bytes, probe, candidate.region, bold)?; + Some((bytes, index)) +} + +/// The bytes of an installed font file, read once for the whole process and shared: a collection +/// such as `PingFang.ttc` is tens of megabytes and is the candidate of several scripts and both +/// weights, so the per-script cache keeps only a handle and a face index. A file that cannot be +/// used is remembered too, so it is not tried again for every script. +fn file_bytes(path: &Path) -> Option>> { + /// The bytes of each file tried, or `None` for one that cannot be used. + type Files = HashMap>>>; + static FILES: Mutex> = Mutex::new(None); + let mut files = FILES.lock().unwrap_or_else(PoisonError::into_inner); + files.get_or_insert_with(HashMap::new).entry(path.to_path_buf()).or_insert_with(|| read_file(path, MAX_BYTES).map(Arc::new)).clone() +} + +/// A regular file of at most `max` bytes, or `None`: a font path is untrusted input. +fn read_file(path: &Path, max: u64) -> Option> { + let meta = std::fs::metadata(path).ok()?; + if !meta.is_file() || meta.len() > max { return None; } - let bytes = std::fs::read(&candidate.path).ok()?; - let index = pick_face(&bytes, probe, candidate.region, bold)?; - Some((Arc::new(bytes), index)) + std::fs::read(path).ok() } /// The best face of the file that parses and maps `probe`: the region's face when the file has @@ -199,6 +216,26 @@ mod tests { assert!(read(&dir, '\u{7269}', false).is_none()); } + /// The files are bounded by `max`: a larger one is not read. + #[test] + fn a_file_over_the_cap_is_not_read() { + let inter = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts/Inter-Regular.ttf")); + assert!(read_file(inter, MAX_BYTES).is_some()); + assert!(read_file(inter, 1024).is_none()); + } + + /// Two faces drawn from one installed file (two scripts, or regular and bold) hold the same + /// bytes, not a copy each; a file that can't be used is remembered as unusable. + #[test] + fn one_installed_file_is_read_once_and_shared() { + let inter = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts/Inter-Regular.ttf")); + let first = file_bytes(inter).expect("readable"); + let second = file_bytes(inter).expect("readable"); + assert!(Arc::ptr_eq(&first, &second), "one allocation for the file"); + let missing = Path::new("definitely/not/here.ttc"); + assert!(file_bytes(missing).is_none() && file_bytes(missing).is_none()); + } + #[test] fn a_latin_face_is_rejected_for_every_script() { let inter = include_bytes!("../../../assets/fonts/Inter-Regular.ttf"); From 102ceecf663fa7a506bbed8e4c38236ee574cc13 Mon Sep 17 00:00:00 2001 From: Philip Date: Mon, 19 Oct 2026 18:22:03 +0100 Subject: [PATCH 03/19] M2: review of #1567: find PingFang in the macOS font assets CodeRabbit: on recent macOS the PingFang collection is not in /System/Library/Fonts but in the downloaded font assets (/System/Library/AssetsV2/com_apple_MobileAsset_Font*/.asset/AssetData), so the fixed path missed it, and the PingFang test returned early without checking anything on such a machine. The macOS candidates now include the AssetData folders of those assets (listed once, at most 512 per kind), and a test searches the assets independently and requires the candidate list to contain what it finds. Co-Authored-By: Claude Sonnet 5.5 --- crates/render/src/system_font.rs | 73 ++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/crates/render/src/system_font.rs b/crates/render/src/system_font.rs index 0d9d158a35..40543cf46e 100644 --- a/crates/render/src/system_font.rs +++ b/crates/render/src/system_font.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex, PoisonError}; +use std::sync::{Arc, Mutex, OnceLock, PoisonError}; use hayro::hayro_interpret::font::{FallbackFontQuery, FontData}; use hayro::hayro_interpret::hayro_cmap::CidFamily; @@ -23,6 +23,8 @@ use skrifa::string::StringId; const MAX_BYTES: u64 = 96 << 20; /// Faces tried in a collection (`.ttc`). const MAX_FACES: u32 = 32; +/// Downloaded font assets looked at per kind on macOS. +const MAX_ASSETS: usize = 512; /// The writing system a character collection is for. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -163,7 +165,11 @@ fn candidates(script: Script) -> Vec { Script::TraditionalChinese => &["PingFang.ttc", "STHeiti Light.ttc", "Hiragino Sans GB.ttc"], Script::Korean => &["AppleSDGothicNeo.ttc", "AppleGothic.ttf", "PingFang.ttc"], }; - names.iter().map(|f| Path::new("/System/Library/Fonts").join(f)).collect() + // The system folder first, then the downloaded font assets: on recent macOS a face such as + // PingFang lives in `/System/Library/AssetsV2/com_apple_MobileAsset_Font*/.asset/AssetData`. + let mut dirs = vec![PathBuf::from("/System/Library/Fonts")]; + dirs.extend(macos_font_asset_dirs().iter().cloned()); + names.iter().flat_map(|f| dirs.iter().map(move |d| d.join(f))).collect() } else { let names: &[&str] = match script { Script::Korean => { @@ -193,6 +199,28 @@ fn candidates(script: Script) -> Vec { files.into_iter().map(|path| Candidate { path, region }).collect() } +/// The `AssetData` folders of the downloaded font assets of macOS, found once. The ids in their +/// names change with each asset version, so they are listed rather than named; at most +/// [`MAX_ASSETS`] are looked at. +fn macos_font_asset_dirs() -> &'static [PathBuf] { + static DIRS: OnceLock> = OnceLock::new(); + DIRS.get_or_init(|| { + let root = Path::new("/System/Library/AssetsV2"); + let Ok(kinds) = std::fs::read_dir(root) else { return Vec::new() }; + let mut dirs = Vec::new(); + for kind in kinds.flatten().filter(|k| k.file_name().to_string_lossy().starts_with("com_apple_MobileAsset_Font")) { + let Ok(assets) = std::fs::read_dir(kind.path()) else { continue }; + for asset in assets.flatten().take(MAX_ASSETS) { + let data = asset.path().join("AssetData"); + if data.is_dir() { + dirs.push(data); + } + } + } + dirs + }) +} + 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") @@ -273,8 +301,15 @@ mod tests { #[cfg(target_os = "macos")] #[test] fn macos_picks_the_regional_pingfang_face() { - let path = Path::new("/System/Library/Fonts/PingFang.ttc"); - let Ok(bytes) = std::fs::read(path) else { return }; + // Found where this macOS keeps it: the system folder, or the downloaded font assets. + let path = candidates(Script::TraditionalChinese) + .into_iter() + .map(|c| c.path) + .find(|p| p.file_name().is_some_and(|n| n == "PingFang.ttc") && p.is_file()); + let Some(bytes) = path.and_then(|p| std::fs::read(p).ok()) else { + eprintln!("PingFang is not installed here: skipped"); + return; + }; for (script, region) in [(Script::TraditionalChinese, "TC"), (Script::SimplifiedChinese, "SC")] { let index = pick_face(&bytes, script.probe(), &[region], false).expect("PingFang covers it"); let font = skrifa::FontRef::from_index(&bytes, index).unwrap(); @@ -282,4 +317,34 @@ mod tests { assert!(family.contains(region), "{script:?} got {family:?}"); } } + /// A face that this macOS keeps only in its downloaded font assets (PingFang, on recent + /// versions) is among the candidates. The assets are searched here on their own, not by the + /// code under test; where there are none, nothing is checked. + #[cfg(target_os = "macos")] + #[test] + fn macos_candidates_include_a_pingfang_that_lives_in_the_font_assets() { + fn find(dir: &Path, name: &str, depth: u32) -> Option { + for entry in std::fs::read_dir(dir).ok()?.flatten() { + let path = entry.path(); + if path.file_name().is_some_and(|n| n == name) && path.is_file() { + return Some(path); + } + if depth > 0 + && path.is_dir() + && !path.is_symlink() + && let Some(found) = find(&path, name, depth - 1) + { + return Some(found); + } + } + None + } + let Some(asset) = find(Path::new("/System/Library/AssetsV2"), "PingFang.ttc", 3) else { + eprintln!("no PingFang in the font assets here: skipped"); + return; + }; + for script in [Script::SimplifiedChinese, Script::TraditionalChinese] { + assert!(candidates(script).iter().any(|c| c.path == asset), "{script:?}: {} is not a candidate", asset.display()); + } + } } From 57a16aea171f5e015a4de17d9c47f1e5fb22a646 Mon Sep 17 00:00:00 2001 From: Philip Date: Wed, 21 Oct 2026 20:17:27 +0100 Subject: [PATCH 04/19] M2: review of #1567: draw installed CJK fonts on screen only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FredApps: AGENTS.md §1.4 allows reading an installed font at runtime only as the interface's last fallback and says nothing from it may enter a PDF or package, while the renderer fallback also served exports and printing. RenderConfig gains system_fonts, off by default. The engine sets it only on the pool that draws the viewer (display_config); exports, printing, OCR and the automation tools render with the document's own configuration, so nothing drawn with an installed font is saved or printed. Tests: the render of a non-embedded CNS1, GB1 and Korea1 font is blank without the flag and drawn with it; the viewer's pool draws such a page and an export of it does not (it fails if the flag is on everywhere). No Adobe-named font can be a candidate. Co-Authored-By: Claude Sonnet 5.5 --- ROADMAP.md | 2 +- crates/engine/src/lib.rs | 26 +++++++++++------ crates/engine/src/tests.rs | 49 ++++++++++++++++++++++++++++++++ crates/render/src/raster.rs | 28 ++++++++++++++---- crates/render/src/system_font.rs | 8 ++++-- 5 files changed, 95 insertions(+), 18 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 38f5ff3e5f..bc916e033c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -214,7 +214,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md - **2026-10-15 (M14, gap 24):** `misc.localization` and `misc.installers` move from planned to partial. Localization: 22 catalogs ship, but Chinese shows tofu in releases, Telugu has no release font and Arabic is not mirrored. Installers: macOS and Windows releases are signed, Linux packages are not. The Acrobat menu diff itself remains open. Estimates unchanged. - **2026-10-13 (G14, Vietnamese localization):** Added Vietnamese (`vi`, Tiếng Việt) as an interface and date language with a 2,591-entry catalog (`plural_none`) covering 100% of the current localizable key union (plus `theme`/`Appearance`). Locale tags `vi` / `vi-VN` / `vi_VN.UTF-8` resolve; calendar months use numeric `Tháng 1`…`12` / `tháng 1`…`12`. Native-speaker community review recorded. The twelve-language localization mean moves from ≈59% to ≈68%; weighted readiness ≈49% → ≈50%. Status stays `partial`: engine/OS details, document contents, filenames, CLI/MCP and external help stay outside the catalog by design. -- **2026-10-12 (M2, installed CJK fonts):** A CID font of an Adobe CJK collection (Japan1, GB1, CNS1, Korea1) that a PDF doesn't embed, such as the Kozuka Gothic of an Acrobat text box or the SongtiTC of a Pages export, drew nothing unless craft-fonts had a Japanese face. After craft-fonts, it now draws with a face already installed on the machine (PingFang, Heiti, Microsoft JhengHei / YaHei, Malgun and the like; read at runtime, never bundled; Noto CJK and Source Han excluded; `PDFCRAFT_SYSTEM_FONTS=0` turns it off). The face of a collection with several regions (PingFang SC, TC) is chosen by region. Checked on macOS with a synthetic Traditional Chinese, Simplified Chinese and Korean page; the Windows and Linux font lists are untested, and a machine with none of the listed faces still draws nothing. +- **2026-10-12 (M2, installed CJK fonts):** A CID font of an Adobe CJK collection (Japan1, GB1, CNS1, Korea1) that a PDF doesn't embed, such as the Kozuka Gothic of an Acrobat text box or the SongtiTC of a Pages export, drew nothing unless craft-fonts had a Japanese face. After craft-fonts, it now draws with a face already installed on the machine (PingFang, Heiti, Microsoft JhengHei / YaHei, Malgun and the like; read at runtime, never bundled, and used only for the on-screen viewer, never for exports, printing or OCR; Noto CJK and Source Han excluded; `PDFCRAFT_SYSTEM_FONTS=0` turns it off). The face of a collection with several regions (PingFang SC, TC) is chosen by region. Checked on macOS with a synthetic Traditional Chinese, Simplified Chinese and Korean page; the Windows and Linux font lists are untested, and a machine with none of the listed faces still draws nothing. - **2026-10-13 (M3, typed zoom):** The right rail's zoom percentage is an editable field: type 150, 150% or 62.5 % and press Enter, Escape discards, and nonsense keeps the current zoom. The control channel's zoom option shares its parser, which now refuses zero, negative and non-finite values. - **2026-10-13 (M4, indirect page rotation):** Rotation queries and edits resolve indirect integer values on pages and their page-tree ancestors. Turning a page already at 270° by 90° now saves it at 0°, including when its rotation is inherited. Synthetic full/incremental save-and-reopen tests cover nested inheritance, sibling preservation, negative/large angles and invalid-value fallback. Estimates unchanged. - **2026-10-14 (M1, Flate fallback):** Raw deflate whose first bytes resemble a zlib header no longer loses a decoding-limit error during fallback. Strict and tolerant filter regressions and a COS stream regression cover refusal at the caller limit and successful decoding within it. No parity status or estimate changes. diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 8f39248abd..d1b94f157b 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -1009,11 +1009,19 @@ fn set_default_layer_state(cos: &mut pdfcraft_cos::Document, layers: &[Layer]) { cos.set(root_ref, Object::Dict(root)); } +/// The configuration of the pool that draws the viewer: the document's own, plus installed CJK +/// fonts for fonts a PDF does not embed (`RenderConfig::system_fonts`). Exports, printing, OCR and +/// the automation tools render with the document's configuration itself, so nothing drawn with an +/// installed font is saved or printed (AGENTS.md §1.2, §1.4). +fn display_config(config: &RenderConfig) -> RenderConfig { + RenderConfig { system_fonts: true, ..config.clone() } +} + /// Render `doc` with the layer visibility in its `info.layers`. fn use_layer_choices(doc: &mut Document) { let overrides: Vec<(i32, i32, bool)> = doc.info.layers.iter().map(|l| (l.id.0 as i32, l.id.1 as i32, l.visible)).collect(); doc.config.layers = Arc::new(overrides); - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); } /// Apply a set-layer-visibility action to `layers`, one change at a time, so a toggle flips the @@ -3580,7 +3588,7 @@ impl Session { let sig_cache = Arc::new(pdfcraft_sign::DigestCache::default()); let signatures = editor.as_ref().map(|e| signatures_of(&e.cos, &bytes, trust, &sig_cache)).unwrap_or_default(); let display = display_bytes(editor.as_mut(), &bytes); - let renderer = RenderPool::new(display.clone(), render_threads(), config.clone()); + let renderer = RenderPool::new(display.clone(), render_threads(), display_config(&config)); let (form, adopted) = editor .as_ref() .map(|e| { @@ -3962,7 +3970,7 @@ impl Session { let signatures = if doc.signatures.is_empty() { Vec::new() } else { signatures_of(&editor.cos, &bytes, &doc.trust, &doc.sig_cache) }; doc.display = display_bytes(Some(editor), &bytes); doc.signatures = Arc::new(signatures); - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); doc.bytes = bytes; Ok(()) } @@ -4007,7 +4015,7 @@ impl Session { let signatures = if doc.signatures.is_empty() { Vec::new() } else { signatures_of(&editor.cos, &bytes, &doc.trust, &doc.sig_cache) }; doc.display = display_bytes(Some(editor), &bytes); doc.signatures = Arc::new(signatures); - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); doc.bytes = bytes; doc.fonts_stale = true; Ok(()) @@ -4032,7 +4040,7 @@ impl Session { /// After a refresh failed: draw the display bytes it left (the original, once released), /// so the page still shows a document. fn show_released(doc: &mut Document) { - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); } /// Rebuild working bytes, inspection and renderer from the current edit state. @@ -4082,7 +4090,7 @@ impl Session { let signatures = signatures_of(&editor.cos, &bytes, &doc.trust, &doc.sig_cache); doc.display = display_bytes(Some(editor), &bytes); doc.signatures = Arc::new(signatures); - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); doc.bytes = bytes; Ok(()) } @@ -4795,7 +4803,7 @@ impl Session { return false; } doc.config.hide_comments = hide; - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); doc.generation += 1; true } @@ -4808,7 +4816,7 @@ impl Session { return false; } doc.config.reading_colors = colors; - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); doc.generation += 1; true } @@ -4844,7 +4852,7 @@ impl Session { let signatures = if doc.signatures.is_empty() { Vec::new() } else { signatures_of(&editor.cos, &doc.bytes, &doc.trust, &doc.sig_cache) }; doc.display = display_bytes(Some(editor), &doc.bytes); doc.signatures = Arc::new(signatures); - doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config)); } } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f125c41dac..5e6ef3b301 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -385,6 +385,55 @@ fn edits_update_view_data_and_mark_dirty() { assert_eq!(page_texts(&s, id), ["Page 1", "Page 3"], "renderer refreshed"); } +/// A font of an Adobe CJK collection that a PDF does not embed is drawn with an installed face on +/// the viewer's pool only. An export of the same page, which can be saved, has nothing drawn with +/// an installed font (AGENTS.md §1.2, §1.4). Skipped where no such face is installed. +#[test] +fn installed_cjk_fonts_draw_in_the_viewer_but_not_in_an_export() { + let content = "BT /F1 40 Tf 5 15 Td <7E419AD45B57> Tj ET"; + let pdf = format!( + "%PDF-1.7 +1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj +2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj +3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 60] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj +4 0 obj << /Length {} >> stream +{content} +endstream endobj +5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /MSung-Light /Encoding /UniCNS-UCS2-H /DescendantFonts [6 0 R] >> endobj +6 0 obj << /Type /Font /Subtype /CIDFontType0 /BaseFont /MSung-Light /CIDSystemInfo << /Registry (Adobe) /Ordering (CNS1) /Supplement 2 >> /FontDescriptor 7 0 R /DW 1000 >> endobj +7 0 obj << /Type /FontDescriptor /FontName /MSung-Light /Flags 6 /FontBBox [0 -141 1000 859] /ItalicAngle 0 /Ascent 859 /Descent -141 /CapHeight 700 /StemV 80 >> endobj +trailer << /Root 1 0 R >> +%%EOF", + content.len() + ); + let mut s = Session::new(); + let id = s.open("cns1.pdf", None, Arc::new(pdf.into_bytes()), None).unwrap(); + let doc = s.get(id).unwrap(); + doc.renderer.set_queue(vec![pdfcraft_render::RenderRequest { + page: 0, + kind: pdfcraft_render::RequestKind::Pixels, + scale: 1.0, + ..Default::default() + }]); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30); + let viewer = loop { + if let Some(page) = doc.renderer.try_recv() { + assert!(page.error.is_none(), "{:?}", page.error); + break page; + } + assert!(std::time::Instant::now() < deadline, "page render did not finish"); + std::thread::yield_now(); + }; + let inked = |rgba: &[u8]| rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count(); + if inked(&viewer.rgba) < 300 { + eprintln!("no installed Traditional Chinese face (or PDFCRAFT_SYSTEM_FONTS=0): skipped"); + return; + } + let png = export::Exporter::new(doc).png(0, 72.0).unwrap(); + let exported = image::load_from_memory(&png).unwrap().into_rgba8(); + assert!(inked(exported.as_raw()) < 50, "an export draws nothing with an installed font ({} dark pixels)", inked(exported.as_raw())); +} + #[test] fn metadata_edit_keeps_the_renderer_without_changing_page_content() { let (mut s, id) = session_with(1); diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 2ef4870fe0..01aa89d69f 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -65,15 +65,23 @@ pub struct RenderConfig { /// View ▸ Reading colours: solid text and line-art paints are remapped for readability and /// the page clears to the setting's background. Display only; the document never changes. pub reading_colors: ReadingColors, + /// Draw a CID font of an Adobe CJK collection that the PDF does not embed with one face already + /// installed on this machine (see `system_font`). Off by default and on only for the pool that + /// draws the viewer: exports, printing and OCR render without it, so nothing drawn with an + /// installed font is saved or printed (AGENTS.md §1.2, §1.4). + pub system_fonts: bool, } impl RenderConfig { fn settings(&self) -> InterpreterSettings { let standard = InterpreterSettings::default().font_resolver; + let system_fonts = self.system_fonts; InterpreterSettings { ocg_overrides: self.layers.clone(), hide_comments: self.hide_comments, - font_resolver: Arc::new(move |query| japanese_fallback(query).or_else(|| system_fallback(query)).or_else(|| standard(query))), + font_resolver: Arc::new(move |query| { + japanese_fallback(query).or_else(|| system_fonts.then(|| system_fallback(query)).flatten()).or_else(|| standard(query)) + }), ..InterpreterSettings::default() } } @@ -6751,12 +6759,20 @@ trailer << /Root 1 0 R >> %%EOF", content.len() ); - let mut r = PageRenderer::new(Arc::new(pdf.into_bytes()), RenderConfig::default()); - let p = r.render(RenderRequest { page: 0, kind: RequestKind::Pixels, tile: None, scale: 1.0, tag: 0, ..Default::default() }); - assert!(p.error.is_none(), "{base_font}: {:?}", p.error); - let inked = p.rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count(); + let pdf = Arc::new(pdf.into_bytes()); + let ink = |config: RenderConfig| { + let mut r = PageRenderer::new(pdf.clone(), config); + let p = r.render(RenderRequest { page: 0, kind: RequestKind::Pixels, tile: None, scale: 1.0, tag: 0, ..Default::default() }); + assert!(p.error.is_none(), "{base_font}: {:?}", p.error); + p.rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count() + }; // Two to four glyphs at 40 pt cover several hundred dark pixels; a blank run has none. - assert!(inked > 300, "{base_font}: the text is drawn ({inked} dark pixels)"); + let shown = ink(RenderConfig { system_fonts: true, ..RenderConfig::default() }); + assert!(shown > 300, "{base_font}: the text is drawn ({shown} dark pixels)"); + // Off by default: exports, printing and OCR render without an installed font, so + // nothing drawn with one can be saved (AGENTS.md §1.2, §1.4). + let saved = ink(RenderConfig::default()); + assert!(saved < 50, "{base_font}: not drawn without system_fonts ({saved} dark pixels)"); } } diff --git a/crates/render/src/system_font.rs b/crates/render/src/system_font.rs index 40543cf46e..5e6391f0d2 100644 --- a/crates/render/src/system_font.rs +++ b/crates/render/src/system_font.rs @@ -5,7 +5,8 @@ //! `MSung-Light` without embedding the font. hayro maps those CIDs to Unicode itself, but it needs //! a face to draw them with; without one the text is blank. The craft-fonts Japanese faces come //! first (see `japanese_fallback` in `raster.rs`); this module is the last resort: one face -//! already installed on this machine. It is read at runtime and never embedded or shipped +//! already installed on this machine, used for the viewer only (`RenderConfig::system_fonts`, off +//! for exports, printing and OCR). It is read at runtime and never embedded or shipped //! (AGENTS.md §1.4), Noto CJK and Source Han are left out on purpose (§1.1), and //! `PDFCRAFT_SYSTEM_FONTS=0` turns it off, as for the interface font. @@ -282,7 +283,10 @@ mod tests { assert!(c.path.is_absolute(), "{}", c.path.display()); assert!(c.path.extension().is_some_and(|e| e == "ttf" || e == "ttc"), "{}", c.path.display()); let name = c.path.to_string_lossy().to_lowercase(); - assert!(!["notosanscjk", "notoserifcjk", "sourcehan", "source-han"].iter().any(|b| name.contains(b)), "{name}"); + assert!( + !["notosanscjk", "notoserifcjk", "sourcehan", "source-han", "adobe", "kozuka", "minion"].iter().any(|b| name.contains(b)), + "{name}" + ); } } } From e42c21a3c833df30ba1144e33cf8f18e4a68f1f6 Mon Sep 17 00:00:00 2001 From: Philip Date: Wed, 21 Oct 2026 20:17:27 +0100 Subject: [PATCH 05/19] =?UTF-8?q?docs:=20AGENTS.md=20=C2=A71.4=20=E2=80=94?= =?UTF-8?q?=20the=20viewer=20may=20draw=20non-embedded=20CJK=20fonts=20wit?= =?UTF-8?q?h=20an=20installed=20face?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proposed for the maintainers to accept or drop (separate commit): the page viewer, on screen only, may use one installed face for a CID font of an Adobe CJK collection that a PDF does not embed. Exports, printing, OCR and the automation tools never use it; nothing drawn with it is saved or printed; no Noto CJK, Source Han or Adobe family is a candidate. Co-Authored-By: Claude Sonnet 5.5 --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 0665ad1d3b..1cb3e1c34a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,6 +71,7 @@ Anything else is forbidden, including assets whose licence you cannot prove from - Font assets shared by the Crafting Apps, including every Japanese font, live in [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), not here. **Never commit a font file to this repository**; adding a font means adding it to craft-fonts. (The Latin UI fonts already in `assets/fonts/` — Inter, JetBrains Mono, Dancing Script — stay.) Team members: the shared rules are in [craftrules `standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md) (internal). - craft-fonts is an **optional build input**, never a Cargo dependency: `CRAFT_FONTS_DIR= cargo build` makes `crates/fonts/build.rs` embed its fonts as `pdfcraft_fonts::CRAFT_FONTS`. Without it PdfCraft builds, tests and runs, but has no Japanese face (Japanese UI text shows replacement boxes, and writing Japanese into a PDF returns a clear error). Release builds always set it (`.github/workflows/release.yml`), and `ATTRIBUTION.toml` lists it as a `[[build_input]]`. - The desktop app may read one font **already installed on the user's machine** at runtime, as the last interface fallback for characters no embedded face has (`crates/ui-egui/src/system_fonts.rs`). That font is never embedded, shipped, committed or copied, and never appears in published screenshots (`PDFCRAFT_SYSTEM_FONTS=0`, which `cargo xtask screenshots` sets). It is not a way around §1.2: nothing from it may enter a PDF, a package or the repository. +- The page viewer may also draw a CID font of an Adobe CJK collection (Japan1, GB1, CNS1, Korea1) that a PDF does **not embed** with one face already installed on the machine (`crates/render/src/system_font.rs`), **on screen only**. `RenderConfig::system_fonts` is off by default and is set only for the pool that draws the viewer (`display_config` in `crates/engine/src/lib.rs`), so exports, printing, OCR and the automation tools never use it. Like the interface font it is read at runtime and never embedded, shipped, committed or copied; Noto CJK and Source Han are never candidates (§1.1), nor is any Adobe family; and `PDFCRAFT_SYSTEM_FONTS=0` turns it off. It is not a way around §1.2 either: nothing drawn with it may be saved or printed. ### 1.5 Procedure for adding or changing an asset From 48955694765b65363ffd513122e9d5f7fabd0ef9 Mon Sep 17 00:00:00 2001 From: Philip Date: Wed, 21 Oct 2026 21:45:28 +0100 Subject: [PATCH 06/19] M2: review of #1567: the viewer-wiring test no longer skips when the wiring is lost CodeRabbit: the engine test skipped when the viewer's render was blank, so a viewer pool that lost the installed-font fallback would have skipped instead of failing. Whether this machine has a face that can draw the page is now asked of a renderer with RenderConfig::system_fonts switched on by hand, and the viewer's render must then draw it. Without the fallback on the viewer's pool the test fails. Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/tests.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 5e6ef3b301..867afc8a07 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -425,10 +425,16 @@ trailer << /Root 1 0 R >> std::thread::yield_now(); }; let inked = |rgba: &[u8]| rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count(); - if inked(&viewer.rgba) < 300 { + // Whether this machine has a face that can draw the page at all, asked of a renderer with the + // fallback switched on by hand, so a viewer pool that lost it fails below instead of skipping. + let probe = pdfcraft_render::PageRenderer::new(doc.display.clone(), pdfcraft_render::RenderConfig { system_fonts: true, ..Default::default() }) + .render(pdfcraft_render::RenderRequest { page: 0, scale: 1.0, ..Default::default() }); + assert!(probe.error.is_none(), "{:?}", probe.error); + if inked(&probe.rgba) < 300 { eprintln!("no installed Traditional Chinese face (or PDFCRAFT_SYSTEM_FONTS=0): skipped"); return; } + assert!(inked(&viewer.rgba) > 300, "the viewer's pool draws it with the installed face ({} dark pixels)", inked(&viewer.rgba)); let png = export::Exporter::new(doc).png(0, 72.0).unwrap(); let exported = image::load_from_memory(&png).unwrap().into_rgba8(); assert!(inked(exported.as_raw()) < 50, "an export draws nothing with an installed font ({} dark pixels)", inked(exported.as_raw())); From 9745b7c5ccf6f8ff41de167d556190e7136fcc15 Mon Sep 17 00:00:00 2001 From: songzhihui Date: Mon, 26 Oct 2026 16:03:04 +0800 Subject: [PATCH 07/19] M2: match print-preview fonts to output policy --- ROADMAP.md | 4 ++ crates/render/src/raster.rs | 78 +++++++++++++++++++++++++++++++++++++ docs/roadmap.md | 6 ++- 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 88139fb145..dfc8b311d8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,7 @@ # PdfCraft roadmap +> **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) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M2, PR #4172: extend on-screen CJK fallback to Combine thumbnails and figure previews; recognise FangSong and subset-prefixed Founder names; estimates unchanged) @@ -757,3 +759,5 @@ Newest first: the date, what moved, and the new overall estimate. - **2026-10-22 (PR #3227 integration):** M11: integrate upstream `80b7d215`; retain partial target-version compatibility and upstream content-boundary and image-size regression coverage. Scope and estimates unchanged. - **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. diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 0382ba67c7..592bbf536e 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -200,6 +200,7 @@ pub struct RenderRequest { /// The tag bit of a print-preview raster. Such requests always render the document's true /// colours, ignoring `RenderConfig::reading_colors`: a preview's job is fidelity to the /// printout, and the print itself — imposed from the document — is never recoloured. +/// Installed-font fallback is also disabled, matching the output's font policy. pub const PRINT_TAG: u64 = 1 << 60; /// The reading colours a request renders with: print previews ([`PRINT_TAG`]) ignore the @@ -337,6 +338,23 @@ fn render_page<'a>( config: &RenderConfig, req: RenderRequest, ) -> Result { + // Print previews share the viewer pool, but must use the output font policy. Keep the + // caller's cancellation/warning hooks, layers and annotation settings. A fresh cache is + // essential: hayro keys cached fonts by PDF dictionary, not by the font resolver, so the + // viewer cache could supply an installed face even after changing the resolver (and a + // cached missing font from print preview could blank the viewer on switching back). + let print_settings; + let print_cache; + let (settings, cache) = if config.system_fonts && req.tag & PRINT_TAG != 0 { + print_settings = InterpreterSettings { + font_resolver: RenderConfig { system_fonts: false, ..config.clone() }.settings().font_resolver, + ..settings.clone() + }; + print_cache = RenderCache::new(); + (&print_settings, &print_cache) + } else { + (settings, cache) + }; let reading = request_reading(config, &req); if req.kind == RequestKind::Text { return match catch_unwind(AssertUnwindSafe(|| crate::text::extract_page(pdf, req.page, settings))) { @@ -7909,6 +7927,66 @@ trailer << /Root 1 0 R >> } } + /// Print preview must match output even after the same worker cached an installed face. + /// Switching back to the viewer must not retain the print resolver's missing-font result. + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn print_preview_excludes_installed_fonts_without_poisoning_viewer_cache() { + use hayro::hayro_interpret::font::FallbackFontQuery; + use hayro::hayro_interpret::hayro_cmap::CharacterCollection; + let query = FontQuery::Fallback(FallbackFontQuery { + post_script_name: Some("SimSun".into()), + character_collection: Some(CharacterCollection { family: CidFamily::AdobeGB1, supplement: 2 }), + ..Default::default() + }); + if super::system_fallback(&query).is_none() { + eprintln!("no installed GB1 face (or PDFCRAFT_SYSTEM_FONTS=0): skipped"); + return; + } + let content = "BT /F1 40 Tf 5 15 Td Tj ET"; + let bytes = Arc::new(format!( + "%PDF-1.7\n\ + 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n\ + 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\ + 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 120 60] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj\n\ + 4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\n\ + 5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /SimSun /Encoding /GBK-EUC-H /DescendantFonts [6 0 R] >> endobj\n\ + 6 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /SimSun /CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 2 >> /FontDescriptor 7 0 R /DW 1000 >> endobj\n\ + 7 0 obj << /Type /FontDescriptor /FontName /SimSun /Flags 6 /FontBBox [0 -141 1000 859] /ItalicAngle 0 /Ascent 859 /Descent -141 /CapHeight 700 /StemV 80 >> endobj\n\ + trailer << /Root 1 0 R >>\n%%EOF", content.len() + ).into_bytes()); + let config = RenderConfig::default().on_screen(); + let mut inline = PageRenderer::new(bytes.clone(), config.clone()); + let mut output = PageRenderer::new(bytes.clone(), RenderConfig::default()); + let pool = RenderPool::new(bytes, 1, config); + for tile in [None, Some(Tile { x: 0, y: 0, w: 120, h: 60 })] { + // Cover both directions with a warm worker cache, including other caller tag bits. + for tag in [PRINT_TAG | 1, 2, PRINT_TAG | 3, 4] { + let req = RenderRequest { page: 0, scale: 1.0, tile, tag, ..Default::default() }; + let truth = output.render(req); + assert!(truth.error.is_none(), "{:?}", truth.error); + pool.set_queue(vec![req]); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + let worker = loop { + if let Some(page) = pool.try_recv() { + break page; + } + assert!(std::time::Instant::now() < deadline, "print-preview worker timed out"); + std::thread::sleep(std::time::Duration::from_millis(5)); + }; + for page in [inline.render(req), worker] { + assert!(page.error.is_none(), "{:?}", page.error); + if tag & PRINT_TAG != 0 { + assert_eq!(page.rgba, truth.rgba, "print preview must match output, tile={tile:?}"); + } else { + let ink = page.rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count(); + assert!(ink > 300, "viewer still draws installed Chinese glyphs: {ink}"); + } + } + } + } + } + /// Half-width katakana are common in Japanese documents set in a non-embedded Mincho font /// such as HeiseiMin-W3. The Mincho substitute must have those glyphs: one that lacks them /// (Shippori Mincho) draws them as .notdef. diff --git a/docs/roadmap.md b/docs/roadmap.md index 0ffcf06f9d..09c783e7a0 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -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: 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) > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M14 localization current state: Arabic logical catalog, explicit RTL label direction and mirrored stock chrome; native/browser RTL workflows unqualified. Native release Hans face via LXGW WenKai Lite; estimates unchanged) @@ -160,6 +162,8 @@ M2 update (2026-10-23, #234 / continuation of #1567): the desktop viewer uses in faces for non-embedded CID fonts, including Windows GBK-EUC-H books. Windows Song/Hei/Kai families are retained when installed, and reload retains the fallback. This is display-only; export, printing, OCR, automation and machines without a candidate retain their limitations. +Print preview excludes installed faces and isolates its font cache from the viewer, matching +output font availability even after repeated view/print switching (2026-10-26, PR #4172). The M2 readiness and remaining-work estimates below are unchanged. "Plan size" is the original single-agent sizing. "Done" is the estimated share of that milestone's @@ -171,7 +175,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:
- filters and crypt: every standard-security revision R2–R6 (RC4, AES-128/256), SASLprep, permissions, creating encryption;
- 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.
Corpus: open/edit/save passes on 958 files, and all 7 password-protected files open.
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). | +| 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. 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
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. | From 3994c2ca9d42a8d08b17571c80d37d536cafcb9b Mon Sep 17 00:00:00 2001 From: songzhihui Date: Mon, 26 Oct 2026 16:39:20 +0800 Subject: [PATCH 08/19] M2: document installed CJK font resolution contracts --- ROADMAP.md | 4 ++++ crates/render/src/raster.rs | 4 ++++ crates/render/src/system_font.rs | 23 +++++++++++++++++++++++ docs/roadmap.md | 4 +++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index dfc8b311d8..ba657c4d92 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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. diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 592bbf536e..74c4586dc8 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -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; diff --git a/crates/render/src/system_font.rs b/crates/render/src/system_font.rs index a5db18dfc5..8fcd167415 100644 --- a/crates/render/src/system_font.rs +++ b/crates/render/src/system_font.rs @@ -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 { 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>, 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 Vec { 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 { for entry in std::fs::read_dir(dir).ok()?.flatten() { let path = entry.path(); diff --git a/docs/roadmap.md b/docs/roadmap.md index 09c783e7a0..080416c1a6 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -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:
- filters and crypt: every standard-security revision R2–R6 (RC4, AES-128/256), SASLprep, permissions, creating encryption;
- 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.
Corpus: open/edit/save passes on 958 files, and all 7 password-protected files open.
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
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. | From 0817ce2774c000279e9a11c9fdfe510aba4ac811 Mon Sep 17 00:00:00 2001 From: songzhihui Date: Tue, 27 Oct 2026 14:12:06 +0800 Subject: [PATCH 09/19] M2: keep roadmap review history in continuous blockquotes --- ROADMAP.md | 5 ----- docs/roadmap.md | 5 ----- 2 files changed, 10 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 264b308680..ada909fabd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,15 +1,10 @@ # PdfCraft roadmap > **Last reviewed:** 2026-10-27 · **Last updated:** 2026-10-27 · **Change:** patch (M2, PR #4172: integrate main 1dd2737b; preserve viewer-only CJK fallback and regression coverage across engine, renderer and Combine module splits; estimates unchanged) - > **Last reviewed:** 2026-10-27 · **Last updated:** 2026-10-27 · **Change:** patch (M2, PR #4172: integrate main 9a9813bf and preserve viewer-only CJK fallback through the engine module split; estimates unchanged) - > **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) - > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M2, PR #4172: extend on-screen CJK fallback to Combine thumbnails and figure previews; recognise FangSong and subset-prefixed Founder names; estimates unchanged) **Stage: alpha** · next: beta, ~25% (ready for real work 50% → 75%) and ~400–700 h away diff --git a/docs/roadmap.md b/docs/roadmap.md index 5b79c0fa67..211fa11466 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,15 +1,10 @@ # PdfCraft roadmap: milestones and what's next > **Last reviewed:** 2026-10-27 · **Last updated:** 2026-10-27 · **Change:** patch (M2, PR #4172: integrate main 1dd2737b; preserve viewer-only CJK fallback and regression coverage across engine, renderer and Combine module splits; estimates unchanged) - > **Last reviewed:** 2026-10-27 · **Last updated:** 2026-10-27 · **Change:** patch (M2, PR #4172: integrate main 9a9813bf and migrate viewer-only CJK configuration to the split engine modules; estimates unchanged) - > **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) - > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M14 localization current state: Arabic logical catalog, explicit RTL label direction and mirrored stock chrome; native/browser RTL workflows unqualified. Native release Hans face via LXGW WenKai Lite; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M7, #2636 partial: pasting added text boxes and images in the engine and through `content_copy`; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M11: image optimization accounts for page-local UserUnit; estimates unchanged) From 503346efed84416cf167e8bb095c1cfe0f64338d Mon Sep 17 00:00:00 2001 From: songzhihui Date: Wed, 28 Oct 2026 08:32:17 +0800 Subject: [PATCH 10/19] M2: keep CJK roadmap separators inside blockquote --- docs/roadmap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index d6290a0ced..1ec73835e8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -15,9 +15,9 @@ > **Last reviewed:** 2026-10-23 | **Last updated:** 2026-10-23 | **Change:** minor (M10: table spans retain text from covered slots in Office exports; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M10: image export preserves soft-mask Decode opacity ranges; estimates unchanged) > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M10: vector printing preserves existing page XObjects when adding annotation appearances; estimates unchanged) - +> > **Last reviewed:** 2026-10-23 · **Last updated:** 2026-10-23 · **Change:** minor (M2, PR #4172: CJK fallback in Combine thumbnails and figure previews; FangSong and subset-prefixed Founder family matching; estimates unchanged) - +> > **Last reviewed:** 2026-10-22 · **Last updated:** 2026-10-22 · **Change:** minor (M14, #3558: rebase guarded macOS per-file association recovery onto current main; estimates unchanged) > **Last reviewed:** 2026-10-20 · **Last updated:** 2026-10-20 · **Change:** minor (M12 #3673: Character encoding accepts Identity-H/V with Adobe CJK collections; estimates unchanged) From a57095c872b903a6dae598c9b353cbcd667a37f4 Mon Sep 17 00:00:00 2001 From: songzhihui Date: Sat, 31 Oct 2026 01:39:00 +0800 Subject: [PATCH 11/19] M2: assert exact GBK glyph rasters for installed Chinese families --- ROADMAP.md | 3 + crates/render/src/raster/tests.rs | 7 +++ crates/render/src/raster/tests/gbk_glyphs.rs | 60 ++++++++++++++++++++ docs/roadmap.md | 4 +- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 crates/render/src/raster/tests/gbk_glyphs.rs diff --git a/ROADMAP.md b/ROADMAP.md index d30325d3e3..76e86471b6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,6 @@ # PdfCraft roadmap +> **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M2, PR #4172: GBK rasters compared with explicit Unicode and independently selected installed faces; wrong-glyph and wrong-family negative controls fail; estimates unchanged) > **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M2, PR #4172: integrate main fa5f9f8c; retain CJK viewer and upstream raster regressions, restore explicit display_config imports; estimates unchanged) > **Last reviewed:** 2026-10-28 · **Last updated:** 2026-10-28 · **Change:** patch (M2, PR #4172: integrate main 5af10276; retain both roadmap histories and viewer-only CJK font policy; estimates unchanged) > **Last reviewed:** 2026-10-27 · **Last updated:** 2026-10-27 · **Change:** patch (M2, PR #4172: integrate main 1dd2737b; preserve viewer-only CJK fallback and regression coverage across engine, renderer and Combine module splits; estimates unchanged) @@ -212,6 +213,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log +- **2026-10-31 (M2, PR #4172 review):** Added same-machine pixel oracles for GBK Song, Hei, Kai and FangSong, including all four subset-prefixed Founder aliases. Reference PDFs use explicit Unicode mappings and independently selected installed faces; font data and rasters remain in memory. All eight cases ran; wrong-glyph and wrong-family production mutations each failed the new assertion and were restored. Validation: 422 renderer unit tests, renderer all-target Clippy with -D warnings, formatting, layers/assets/parity and renderer WASM checks passed; existing optional-patch/WASM warnings and parity notes remain. Estimates unchanged. + - **2026-10-31 (M2, PR #4172):** Integrated main fa5f9f8c, retaining both roadmap histories and raster regression groups. Restored display_config and thumbnail_config imports after the upstream explicit-import migration. Validation: 422 renderer unit tests, engine/UI all-target checks, scoped all-target Clippy with -D warnings, formatting, layers, assets, parity and renderer/engine WASM checks passed. Existing optional-patch/WASM warnings and parity notes remain; no full engine/UI runtime test rerun. Display-only font policy and estimates unchanged. - **2026-10-28 (M2, PR #4172):** Integrated main 5af10276, preserving both roadmap histories and reviewing auto-merged export tests and accessibility UI changes. Validation: 488 scoped tests passed (18 engine, 412 renderer unit, 43 renderer integration, 15 Combine; one existing renderer integration test ignored); scoped all-target Clippy with -D warnings, formatting, layers, assets, parity and renderer/engine WASM checks passed. Viewer-only CJK fallback and print/output font policy remain unchanged; estimates unchanged. diff --git a/crates/render/src/raster/tests.rs b/crates/render/src/raster/tests.rs index 4741b7fd63..df6896b980 100644 --- a/crates/render/src/raster/tests.rs +++ b/crates/render/src/raster/tests.rs @@ -7040,6 +7040,9 @@ fn test_clip_only_text_raster_preserves_clipping_without_painting() { assert!(pixels.contains(&white), "the clip is text-shaped, not the whole page"); } +#[cfg(target_os = "windows")] +mod gbk_glyphs; + /// Chinese and Korean CID fonts that aren't embedded (Adobe-CNS1 `MSung-Light`, Adobe-GB1 /// `STSong-Light`, Adobe-Korea1 `HYGoThic-Medium`; Acrobat also writes Japanese `KozGoPr6N` /// text boxes without the font) drew nothing, because craft-fonts has only Japanese faces. @@ -7061,6 +7064,8 @@ fn non_embedded_chinese_and_korean_cid_fonts_draw_with_an_installed_face() { ("KaiTi", CidFamily::AdobeGB1, "GB1", "GBK-EUC-H", "D6D0CEC4", "CIDFontType2"), ("FangSong", CidFamily::AdobeGB1, "GB1", "GBK-EUC-H", "D6D0CEC4", "CIDFontType2"), ("ABCDEF+FZSSK--GBK1-0", CidFamily::AdobeGB1, "GB1", "GBK-EUC-H", "D6D0CEC4", "CIDFontType2"), + ("ABCDEF+FZHTK--GBK1-0", CidFamily::AdobeGB1, "GB1", "GBK-EUC-H", "D6D0CEC4", "CIDFontType2"), + ("ABCDEF+FZKTK--GBK1-0", CidFamily::AdobeGB1, "GB1", "GBK-EUC-H", "D6D0CEC4", "CIDFontType2"), ("ABCDEF+FZFSK--GBK1-0", CidFamily::AdobeGB1, "GB1", "GBK-EUC-H", "D6D0CEC4", "CIDFontType2"), ]; for (base_font, family, ordering, encoding, code, subtype) in cases { @@ -7105,6 +7110,8 @@ trailer << /Root 1 0 R >> let saved = ink(RenderConfig::default()); assert!(saved < 50, "{base_font}: not drawn without system_fonts ({saved} dark pixels)"); if encoding == "GBK-EUC-H" { + #[cfg(target_os = "windows")] + gbk_glyphs::assert_expected_glyphs(&pdf, base_font); let mut renderer = PageRenderer::new(pdf, RenderConfig { system_fonts: true, ..Default::default() }); let text = renderer.render(RenderRequest { page: 0, kind: RequestKind::Text, ..Default::default() }); assert!(text.error.is_none(), "{base_font}: {:?}", text.error); diff --git a/crates/render/src/raster/tests/gbk_glyphs.rs b/crates/render/src/raster/tests/gbk_glyphs.rs new file mode 100644 index 0000000000..8443fed624 --- /dev/null +++ b/crates/render/src/raster/tests/gbk_glyphs.rs @@ -0,0 +1,60 @@ +//! A same-machine pixel oracle, independent of GBK decoding and installed-face selection. +//! All font reads and reference rasters stay in memory; nothing is embedded or published. + +use super::*; +use hayro::hayro_interpret::font::FontData; +use skrifa::MetadataProvider as _; +use skrifa::string::StringId; + +/// Compare the real GBK PDF path with explicit Unicode and a known installed face. Unlike an +/// ink count or extracted text, this fails if drawing chooses another glyph or font family. +pub(super) fn assert_expected_glyphs(pdf: &Arc>, base_font: &str) { + let (expected, file) = match base_font { + "SimSun" | "ABCDEF+FZSSK--GBK1-0" => ("SimSun", "simsun.ttc"), + "SimHei" | "ABCDEF+FZHTK--GBK1-0" => ("SimHei", "simhei.ttf"), + "KaiTi" | "ABCDEF+FZKTK--GBK1-0" => ("KaiTi", "simkai.ttf"), + "FangSong" | "ABCDEF+FZFSK--GBK1-0" => ("FangSong", "simfang.ttf"), + other => panic!("no independent GBK oracle for {other}"), + }; + // Do not use the production candidate list, resolver or face-selection helpers here. + let directory = std::env::var_os("WINDIR").or_else(|| std::env::var_os("SystemRoot")).unwrap_or_else(|| r"C:\Windows".into()); + let path = std::path::PathBuf::from(directory).join("Fonts").join(file); + if !path.is_file() { + eprintln!("{base_font}: exact {expected} face unavailable; pixel oracle skipped"); + return; + } + let bytes = Arc::new(std::fs::read(path).expect("read installed reference font")); + let index = (0..32) + .find(|&index| { + skrifa::FontRef::from_index(&bytes, index) + .is_ok_and(|font| font.localized_strings(StringId::POSTSCRIPT_NAME).any(|name| name.chars().collect::() == expected)) + }) + .expect("reference file contains the expected PostScript face"); + let face = skrifa::FontRef::from_index(&bytes, index).unwrap(); + for character in ['\u{4e2d}', '\u{6587}'] { + assert!(face.charmap().map(character).is_some_and(|id| id.to_u32() != 0), "{expected} must contain {character}"); + } + + // Explicit ToUnicode maps arbitrary reference codes to U+4E2D and U+6587, bypassing GBK + // and Adobe-GB1 CID lookup. The fixed resolver below bypasses family classification too. + let cmap = "begincmap /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange \ + 2 beginbfchar <0001> <4E2D> <0002> <6587> endbfchar endcmap"; + let source = std::str::from_utf8(pdf).unwrap(); + assert!(source.contains("/Encoding /GBK-EUC-H") && source.contains("")); + let reference = source + .replace("/Encoding /GBK-EUC-H", "/Encoding /Identity-H /ToUnicode 8 0 R") + .replace("", "<00010002>") + .replace("trailer <<", &format!("8 0 obj << /Length {} >> stream\n{cmap}\nendstream endobj\ntrailer <<", cmap.len())); + let parsed = Pdf::new(Arc::new(reference.into_bytes())).unwrap(); + let data: FontData = bytes; + let settings = InterpreterSettings { font_resolver: Arc::new(move |_| Some((data.clone(), index))), ..Default::default() }; + let reference = hayro::render(&parsed.pages()[0], &RenderCache::new(), &settings, &RenderSettings { bg_color: WHITE, ..Default::default() }); + let mut renderer = PageRenderer::new(pdf.clone(), RenderConfig::default().on_screen()); + let actual = renderer.render(RenderRequest { page: 0, kind: RequestKind::Pixels, scale: 1.0, ..Default::default() }); + assert!(actual.error.is_none(), "{base_font}: {:?}", actual.error); + let expected_pixels = reference.data_as_u8_slice(); + assert_eq!(actual.rgba.len(), expected_pixels.len()); + assert!(expected_pixels.as_chunks::<4>().0.iter().filter(|pixel| pixel[0] < 128).count() > 300, "reference must draw two visible glyphs"); + assert!(actual.rgba.as_ref() == expected_pixels, "{base_font}: GBK raster must draw the exact {expected} glyphs for U+4E2D U+6587"); + eprintln!("{base_font}: exact {expected} Unicode-reference pixels matched"); +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 7c30577652..12161c4dff 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,5 +1,6 @@ # PdfCraft roadmap: milestones and what's next +> **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M2, PR #4172: GBK rasters compared with explicit Unicode and independently selected installed faces; wrong-glyph and wrong-family negative controls fail; estimates unchanged) > **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M2, PR #4172: integrate main fa5f9f8c; retain CJK viewer and upstream raster regressions, restore explicit display_config imports; estimates unchanged) > **Last reviewed:** 2026-10-28 · **Last updated:** 2026-10-28 · **Change:** patch (M2, PR #4172: integrate main 5af10276; retain both roadmap histories and viewer-only CJK font policy; estimates unchanged) > **Last reviewed:** 2026-10-27 · **Last updated:** 2026-10-27 · **Change:** patch (M2, PR #4172: integrate main 1dd2737b; preserve viewer-only CJK fallback and regression coverage across engine, renderer and Combine module splits; estimates unchanged) @@ -190,7 +191,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:
- filters and crypt: every standard-security revision R2–R6 (RC4, AES-128/256), SASLprep, permissions, creating encryption;
- 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.
Corpus: open/edit/save passes on 958 files, and all 7 password-protected files open.
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. Installed-font helpers and their regression contracts have function documentation. Integration with main fa5f9f8c retains viewer fallback across the split engine session modules, renderer tests and Combine thumbnail workers. | +| 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. GBK regression rasters match explicit-Unicode references with independently selected installed faces; wrong-glyph and wrong-family negative controls fail. Integration with main fa5f9f8c retains viewer fallback across the split engine session modules, renderer tests and Combine thumbnail workers. | | 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
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. | @@ -289,6 +290,7 @@ Estimates unchanged. | Date | Change | Summary | |---|---|---| +| 2026-10-31 | patch | M2, PR #4172: eight GBK font/alias cases compare exact pixels with independent installed-face and explicit-Unicode references; wrong-glyph and wrong-family negative controls fail. Estimates unchanged. | | 2026-10-31 | patch | M2, PR #4172: integrate main fa5f9f8c, preserve both raster test groups and restore explicit display_config imports; display-only CJK policy and estimates unchanged. | | 2026-10-28 | patch | M2, PR #4172: integrate main 5af10276 and retain both roadmap histories; viewer-only CJK and print/output font policies and estimates unchanged. | | 2026-10-24 | minor | M11: the PDF Optimizer and Reduce File Size store Group 3 fax images as Group 4 when smaller (`Document::fax_params`), and pack eight-bit `DeviceRGB` of only black and white (as grey) and indexed images of 2, 4 or 8 bits that use only palette entries 0 and 1 (keeping the palette) to one bit, so Monochrome Images compresses and downsamples them. Estimates unchanged. | From ec33924e80aad66d09c06105e169840b6d5dab57 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 30 Oct 2026 20:12:04 -0400 Subject: [PATCH 12/19] fix(flatpak): allow reading launcher version pins (#5490) * fix(flatpak): allow reading launcher version pins * fix(flatpak): allow reading launcher version pins --- packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml | 1 + packaging/linux/flatpak/ai.storyteller.pdfcraft.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml b/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml index fcfb8183cc..c5e4021310 100644 --- a/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml +++ b/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml @@ -17,6 +17,7 @@ runtime-version: '25.08' sdk: org.freedesktop.Sdk command: pdfcraft finish-args: + - --filesystem=xdg-data/ArtCraft/versions:ro # Display: Wayland first; X11 only when no Wayland compositor is running. - --socket=wayland - --socket=fallback-x11 diff --git a/packaging/linux/flatpak/ai.storyteller.pdfcraft.yml b/packaging/linux/flatpak/ai.storyteller.pdfcraft.yml index 0d0c25949c..5d30c5a4e3 100644 --- a/packaging/linux/flatpak/ai.storyteller.pdfcraft.yml +++ b/packaging/linux/flatpak/ai.storyteller.pdfcraft.yml @@ -18,6 +18,7 @@ sdk-extensions: - org.freedesktop.Sdk.Extension.rust-stable command: pdfcraft finish-args: + - --filesystem=xdg-data/ArtCraft/versions:ro # Display: Wayland first; X11 only when no Wayland compositor is running. - --socket=wayland - --socket=fallback-x11 From 4b70916883ce217de7895cdb9f7d30ea79326972 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 30 Oct 2026 23:22:50 -0400 Subject: [PATCH 13/19] Flatpak bundle: skip the debuginfo pass for cups-client (runner has no eu-strip) Co-Authored-By: Claude Opus 5.5 --- packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml b/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml index c5e4021310..15719a2179 100644 --- a/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml +++ b/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml @@ -51,6 +51,10 @@ modules: # the socket path matches the host's, which --socket=cups mounts. - name: cups-client buildsystem: simple + # The release runner's flatpak-builder has no eu-strip (elfutils), which the debuginfo pass + # runs on every binary this module compiles. + build-options: + no-debuginfo: true build-commands: - ./configure --prefix=/app --sysconfdir=/etc --localstatedir=/var --with-rundir=/run/cups --with-domainsocket=/run/cups/cups.sock --disable-shared --with-components=libcups From 8f16855097883aca144a4562b0eeff0d1d28856e Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 30 Oct 2026 23:22:57 -0400 Subject: [PATCH 14/19] Release 0.16.0 Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 72 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 68 +++++++++++++++++++++++++-------------------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfcb9dc362..1018013fc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4207,7 +4207,7 @@ dependencies = [ [[package]] name = "pdfcraft" -version = "0.15.0" +version = "0.16.0" dependencies = [ "chrono", "craft-update", @@ -4231,7 +4231,7 @@ dependencies = [ [[package]] name = "pdfcraft-a11y" -version = "0.15.0" +version = "0.16.0" dependencies = [ "pdfcraft-content", "pdfcraft-cos", @@ -4243,7 +4243,7 @@ dependencies = [ [[package]] name = "pdfcraft-annot" -version = "0.15.0" +version = "0.16.0" dependencies = [ "hayro-syntax", "pdfcraft-cos", @@ -4254,7 +4254,7 @@ dependencies = [ [[package]] name = "pdfcraft-audit" -version = "0.15.0" +version = "0.16.0" dependencies = [ "pdfcraft-content", "pdfcraft-cos", @@ -4265,7 +4265,7 @@ dependencies = [ [[package]] name = "pdfcraft-automation" -version = "0.15.0" +version = "0.16.0" dependencies = [ "base64 0.23.1", "flate2", @@ -4287,7 +4287,7 @@ dependencies = [ [[package]] name = "pdfcraft-cli" -version = "0.15.0" +version = "0.16.0" dependencies = [ "base64 0.23.1", "pdfcraft-audit", @@ -4305,11 +4305,11 @@ dependencies = [ [[package]] name = "pdfcraft-compare" -version = "0.15.0" +version = "0.16.0" [[package]] name = "pdfcraft-content" -version = "0.15.0" +version = "0.16.0" dependencies = [ "hayro-syntax", "pdfcraft-cos", @@ -4317,7 +4317,7 @@ dependencies = [ [[package]] name = "pdfcraft-cos" -version = "0.15.0" +version = "0.16.0" dependencies = [ "chrono", "hayro-syntax", @@ -4332,7 +4332,7 @@ dependencies = [ [[package]] name = "pdfcraft-create" -version = "0.15.0" +version = "0.16.0" dependencies = [ "encoding_rs", "hayro-ccitt", @@ -4352,7 +4352,7 @@ dependencies = [ [[package]] name = "pdfcraft-crypt" -version = "0.15.0" +version = "0.16.0" dependencies = [ "aes", "md-5", @@ -4364,7 +4364,7 @@ dependencies = [ [[package]] name = "pdfcraft-edit" -version = "0.15.0" +version = "0.16.0" dependencies = [ "hayro-syntax", "kurbo 0.13.1", @@ -4380,7 +4380,7 @@ dependencies = [ [[package]] name = "pdfcraft-engine" -version = "0.15.0" +version = "0.16.0" dependencies = [ "chrono", "craft-docpath", @@ -4422,14 +4422,14 @@ dependencies = [ [[package]] name = "pdfcraft-export" -version = "0.15.0" +version = "0.16.0" dependencies = [ "flate2", ] [[package]] name = "pdfcraft-filters" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fax", "flate2", @@ -4442,7 +4442,7 @@ dependencies = [ [[package]] name = "pdfcraft-fonts" -version = "0.15.0" +version = "0.16.0" dependencies = [ "harfrust", "pdfcraft-cos", @@ -4452,7 +4452,7 @@ dependencies = [ [[package]] name = "pdfcraft-forms" -version = "0.15.0" +version = "0.16.0" dependencies = [ "hayro-syntax", "pdfcraft-content", @@ -4465,11 +4465,11 @@ dependencies = [ [[package]] name = "pdfcraft-geom" -version = "0.15.0" +version = "0.16.0" [[package]] name = "pdfcraft-js" -version = "0.15.0" +version = "0.16.0" dependencies = [ "boa_engine", "chrono", @@ -4477,7 +4477,7 @@ dependencies = [ [[package]] name = "pdfcraft-measure" -version = "0.15.0" +version = "0.16.0" dependencies = [ "pdfcraft-annot", "pdfcraft-content", @@ -4490,14 +4490,14 @@ dependencies = [ [[package]] name = "pdfcraft-model" -version = "0.15.0" +version = "0.16.0" dependencies = [ "pdfcraft-cos", ] [[package]] name = "pdfcraft-ocr" -version = "0.15.0" +version = "0.16.0" dependencies = [ "ocrs", "pdfcraft-fonts", @@ -4508,7 +4508,7 @@ dependencies = [ [[package]] name = "pdfcraft-optimize" -version = "0.15.0" +version = "0.16.0" dependencies = [ "image", "pdfcraft-annot", @@ -4524,11 +4524,11 @@ dependencies = [ [[package]] name = "pdfcraft-oracle" -version = "0.15.0" +version = "0.16.0" [[package]] name = "pdfcraft-organize" -version = "0.15.0" +version = "0.16.0" dependencies = [ "hayro-syntax", "pdfcraft-content", @@ -4539,7 +4539,7 @@ dependencies = [ [[package]] name = "pdfcraft-platform" -version = "0.15.0" +version = "0.16.0" dependencies = [ "async-channel", "futures-lite", @@ -4549,7 +4549,7 @@ dependencies = [ [[package]] name = "pdfcraft-preflight" -version = "0.15.0" +version = "0.16.0" dependencies = [ "flate2", "pdfcraft-content", @@ -4560,7 +4560,7 @@ dependencies = [ [[package]] name = "pdfcraft-print" -version = "0.15.0" +version = "0.16.0" dependencies = [ "craft-print", "pdfcraft-content", @@ -4572,7 +4572,7 @@ dependencies = [ [[package]] name = "pdfcraft-redact" -version = "0.15.0" +version = "0.16.0" dependencies = [ "pdfcraft-annot", "pdfcraft-content", @@ -4589,7 +4589,7 @@ dependencies = [ [[package]] name = "pdfcraft-render" -version = "0.15.0" +version = "0.16.0" dependencies = [ "bytemuck", "flate2", @@ -4608,7 +4608,7 @@ dependencies = [ [[package]] name = "pdfcraft-sign" -version = "0.15.0" +version = "0.16.0" dependencies = [ "aes", "aws-lc-rs", @@ -4644,11 +4644,11 @@ dependencies = [ [[package]] name = "pdfcraft-testkit" -version = "0.15.0" +version = "0.16.0" [[package]] name = "pdfcraft-ui-egui" -version = "0.15.0" +version = "0.16.0" dependencies = [ "arboard", "base64 0.23.1", @@ -4700,7 +4700,7 @@ dependencies = [ [[package]] name = "pdfcraft-web" -version = "0.15.0" +version = "0.16.0" dependencies = [ "eframe", "egui", @@ -4712,7 +4712,7 @@ dependencies = [ [[package]] name = "pdfcraft-xfa" -version = "0.15.0" +version = "0.16.0" dependencies = [ "base64 0.23.1", "image", @@ -4726,7 +4726,7 @@ dependencies = [ [[package]] name = "pdfcraft-xfdf" -version = "0.15.0" +version = "0.16.0" dependencies = [ "pdfcraft-annot", "pdfcraft-cos", diff --git a/Cargo.toml b/Cargo.toml index 1b04ab5263..a64505405d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ exclude = ["vendor"] default-members = ["crates/*", "apps/pdfcraft", "apps/pdfcraft-cli", "xtask"] [workspace.package] -version = "0.15.0" +version = "0.16.0" edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.95" @@ -26,39 +26,39 @@ craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-l # craft-update: app-specific pretend versions apply to startup and manual checks. craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "04aa1703b32d299e54f78fcde4182f2b393a9116", features = ["egui"] } # internal -pdfcraft-geom = { path = "crates/geom", version = "0.15.0" } -pdfcraft-filters = { path = "crates/filters", version = "0.15.0" } -pdfcraft-cos = { path = "crates/cos", version = "0.15.0" } -pdfcraft-crypt = { path = "crates/crypt", version = "0.15.0" } -pdfcraft-organize = { path = "crates/organize", version = "0.15.0" } -pdfcraft-measure = { path = "crates/measure", version = "0.15.0" } -pdfcraft-annot = { path = "crates/annot", version = "0.15.0" } -pdfcraft-audit = { path = "crates/audit", version = "0.15.0" } -pdfcraft-fonts = { path = "crates/fonts", version = "0.15.0" } -pdfcraft-model = { path = "crates/model", version = "0.15.0" } -pdfcraft-content = { path = "crates/content", version = "0.15.0" } -pdfcraft-redact = { path = "crates/redact", version = "0.15.0" } -pdfcraft-print = { path = "crates/print", version = "0.15.0" } -pdfcraft-xfdf = { path = "crates/xfdf", version = "0.15.0" } -pdfcraft-sign = { path = "crates/sign", version = "0.15.0" } -pdfcraft-optimize = { path = "crates/optimize", version = "0.15.0" } -pdfcraft-a11y = { path = "crates/a11y", version = "0.15.0" } -pdfcraft-oracle = { path = "crates/oracle", version = "0.15.0" } -pdfcraft-testkit = { path = "crates/testkit", version = "0.15.0" } -pdfcraft-ocr = { path = "crates/ocr", version = "0.15.0" } -pdfcraft-js = { path = "crates/js", version = "0.15.0" } -pdfcraft-compare = { path = "crates/compare", version = "0.15.0" } -pdfcraft-preflight = { path = "crates/preflight", version = "0.15.0" } -pdfcraft-export = { path = "crates/export", version = "0.15.0" } -pdfcraft-edit = { path = "crates/edit", version = "0.15.0" } -pdfcraft-create = { path = "crates/create", version = "0.15.0" } -pdfcraft-forms = { path = "crates/forms", version = "0.15.0" } -pdfcraft-xfa = { path = "crates/xfa", version = "0.15.0" } -pdfcraft-render = { path = "crates/render", version = "0.15.0" } -pdfcraft-engine = { path = "crates/engine", version = "0.15.0" } -pdfcraft-platform = { path = "crates/platform", version = "0.15.0" } -pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.15.0" } -pdfcraft-automation = { path = "crates/automation", version = "0.15.0", default-features = false } +pdfcraft-geom = { path = "crates/geom", version = "0.16.0" } +pdfcraft-filters = { path = "crates/filters", version = "0.16.0" } +pdfcraft-cos = { path = "crates/cos", version = "0.16.0" } +pdfcraft-crypt = { path = "crates/crypt", version = "0.16.0" } +pdfcraft-organize = { path = "crates/organize", version = "0.16.0" } +pdfcraft-measure = { path = "crates/measure", version = "0.16.0" } +pdfcraft-annot = { path = "crates/annot", version = "0.16.0" } +pdfcraft-audit = { path = "crates/audit", version = "0.16.0" } +pdfcraft-fonts = { path = "crates/fonts", version = "0.16.0" } +pdfcraft-model = { path = "crates/model", version = "0.16.0" } +pdfcraft-content = { path = "crates/content", version = "0.16.0" } +pdfcraft-redact = { path = "crates/redact", version = "0.16.0" } +pdfcraft-print = { path = "crates/print", version = "0.16.0" } +pdfcraft-xfdf = { path = "crates/xfdf", version = "0.16.0" } +pdfcraft-sign = { path = "crates/sign", version = "0.16.0" } +pdfcraft-optimize = { path = "crates/optimize", version = "0.16.0" } +pdfcraft-a11y = { path = "crates/a11y", version = "0.16.0" } +pdfcraft-oracle = { path = "crates/oracle", version = "0.16.0" } +pdfcraft-testkit = { path = "crates/testkit", version = "0.16.0" } +pdfcraft-ocr = { path = "crates/ocr", version = "0.16.0" } +pdfcraft-js = { path = "crates/js", version = "0.16.0" } +pdfcraft-compare = { path = "crates/compare", version = "0.16.0" } +pdfcraft-preflight = { path = "crates/preflight", version = "0.16.0" } +pdfcraft-export = { path = "crates/export", version = "0.16.0" } +pdfcraft-edit = { path = "crates/edit", version = "0.16.0" } +pdfcraft-create = { path = "crates/create", version = "0.16.0" } +pdfcraft-forms = { path = "crates/forms", version = "0.16.0" } +pdfcraft-xfa = { path = "crates/xfa", version = "0.16.0" } +pdfcraft-render = { path = "crates/render", version = "0.16.0" } +pdfcraft-engine = { path = "crates/engine", version = "0.16.0" } +pdfcraft-platform = { path = "crates/platform", version = "0.16.0" } +pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.16.0" } +pdfcraft-automation = { path = "crates/automation", version = "0.16.0", default-features = false } # pdf hayro = "0.7.1" From 35c187ff1927b720da4d025bb746b3b445b839cc Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 30 Oct 2026 23:25:35 -0400 Subject: [PATCH 15/19] Flatpak bundle: skip the debuginfo pass manifest-wide (runner has no eu-strip) Co-Authored-By: Claude Opus 5.5 --- .../linux/flatpak/ai.storyteller.pdfcraft.bundle.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml b/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml index 15719a2179..065945c6ea 100644 --- a/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml +++ b/packaging/linux/flatpak/ai.storyteller.pdfcraft.bundle.yml @@ -45,16 +45,17 @@ finish-args: # Deliberately not granted: home/host filesystem access. No CUPS socket either: printing # runs the `lp` command (crates/print), which the freedesktop runtime doesn't ship, so Print # reports that the spooler is unavailable until it goes through the print portal. +# The release runner's flatpak-builder has no eu-strip (elfutils), which its debuginfo pass runs +# on cups-client's unstripped tools, in whichever module comes first after they're installed. The +# app's own binaries come from the release tarball, already stripped. +build-options: + no-debuginfo: true modules: # CUPS client tools for craft-print: the runtime has libcups but not lpstat/lp/lpoptions. # Linked statically against this release's libcups, so a runtime update can't break them; # the socket path matches the host's, which --socket=cups mounts. - name: cups-client buildsystem: simple - # The release runner's flatpak-builder has no eu-strip (elfutils), which the debuginfo pass - # runs on every binary this module compiles. - build-options: - no-debuginfo: true build-commands: - ./configure --prefix=/app --sysconfdir=/etc --localstatedir=/var --with-rundir=/run/cups --with-domainsocket=/run/cups/cups.sock --disable-shared --with-components=libcups From 24bb85308e4b65404fc908506ade6341a29999d7 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 30 Oct 2026 23:25:37 -0400 Subject: [PATCH 16/19] Release 0.16.1 Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 72 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 68 +++++++++++++++++++++++++-------------------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1018013fc9..b29fd629b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4207,7 +4207,7 @@ dependencies = [ [[package]] name = "pdfcraft" -version = "0.16.0" +version = "0.16.1" dependencies = [ "chrono", "craft-update", @@ -4231,7 +4231,7 @@ dependencies = [ [[package]] name = "pdfcraft-a11y" -version = "0.16.0" +version = "0.16.1" dependencies = [ "pdfcraft-content", "pdfcraft-cos", @@ -4243,7 +4243,7 @@ dependencies = [ [[package]] name = "pdfcraft-annot" -version = "0.16.0" +version = "0.16.1" dependencies = [ "hayro-syntax", "pdfcraft-cos", @@ -4254,7 +4254,7 @@ dependencies = [ [[package]] name = "pdfcraft-audit" -version = "0.16.0" +version = "0.16.1" dependencies = [ "pdfcraft-content", "pdfcraft-cos", @@ -4265,7 +4265,7 @@ dependencies = [ [[package]] name = "pdfcraft-automation" -version = "0.16.0" +version = "0.16.1" dependencies = [ "base64 0.23.1", "flate2", @@ -4287,7 +4287,7 @@ dependencies = [ [[package]] name = "pdfcraft-cli" -version = "0.16.0" +version = "0.16.1" dependencies = [ "base64 0.23.1", "pdfcraft-audit", @@ -4305,11 +4305,11 @@ dependencies = [ [[package]] name = "pdfcraft-compare" -version = "0.16.0" +version = "0.16.1" [[package]] name = "pdfcraft-content" -version = "0.16.0" +version = "0.16.1" dependencies = [ "hayro-syntax", "pdfcraft-cos", @@ -4317,7 +4317,7 @@ dependencies = [ [[package]] name = "pdfcraft-cos" -version = "0.16.0" +version = "0.16.1" dependencies = [ "chrono", "hayro-syntax", @@ -4332,7 +4332,7 @@ dependencies = [ [[package]] name = "pdfcraft-create" -version = "0.16.0" +version = "0.16.1" dependencies = [ "encoding_rs", "hayro-ccitt", @@ -4352,7 +4352,7 @@ dependencies = [ [[package]] name = "pdfcraft-crypt" -version = "0.16.0" +version = "0.16.1" dependencies = [ "aes", "md-5", @@ -4364,7 +4364,7 @@ dependencies = [ [[package]] name = "pdfcraft-edit" -version = "0.16.0" +version = "0.16.1" dependencies = [ "hayro-syntax", "kurbo 0.13.1", @@ -4380,7 +4380,7 @@ dependencies = [ [[package]] name = "pdfcraft-engine" -version = "0.16.0" +version = "0.16.1" dependencies = [ "chrono", "craft-docpath", @@ -4422,14 +4422,14 @@ dependencies = [ [[package]] name = "pdfcraft-export" -version = "0.16.0" +version = "0.16.1" dependencies = [ "flate2", ] [[package]] name = "pdfcraft-filters" -version = "0.16.0" +version = "0.16.1" dependencies = [ "fax", "flate2", @@ -4442,7 +4442,7 @@ dependencies = [ [[package]] name = "pdfcraft-fonts" -version = "0.16.0" +version = "0.16.1" dependencies = [ "harfrust", "pdfcraft-cos", @@ -4452,7 +4452,7 @@ dependencies = [ [[package]] name = "pdfcraft-forms" -version = "0.16.0" +version = "0.16.1" dependencies = [ "hayro-syntax", "pdfcraft-content", @@ -4465,11 +4465,11 @@ dependencies = [ [[package]] name = "pdfcraft-geom" -version = "0.16.0" +version = "0.16.1" [[package]] name = "pdfcraft-js" -version = "0.16.0" +version = "0.16.1" dependencies = [ "boa_engine", "chrono", @@ -4477,7 +4477,7 @@ dependencies = [ [[package]] name = "pdfcraft-measure" -version = "0.16.0" +version = "0.16.1" dependencies = [ "pdfcraft-annot", "pdfcraft-content", @@ -4490,14 +4490,14 @@ dependencies = [ [[package]] name = "pdfcraft-model" -version = "0.16.0" +version = "0.16.1" dependencies = [ "pdfcraft-cos", ] [[package]] name = "pdfcraft-ocr" -version = "0.16.0" +version = "0.16.1" dependencies = [ "ocrs", "pdfcraft-fonts", @@ -4508,7 +4508,7 @@ dependencies = [ [[package]] name = "pdfcraft-optimize" -version = "0.16.0" +version = "0.16.1" dependencies = [ "image", "pdfcraft-annot", @@ -4524,11 +4524,11 @@ dependencies = [ [[package]] name = "pdfcraft-oracle" -version = "0.16.0" +version = "0.16.1" [[package]] name = "pdfcraft-organize" -version = "0.16.0" +version = "0.16.1" dependencies = [ "hayro-syntax", "pdfcraft-content", @@ -4539,7 +4539,7 @@ dependencies = [ [[package]] name = "pdfcraft-platform" -version = "0.16.0" +version = "0.16.1" dependencies = [ "async-channel", "futures-lite", @@ -4549,7 +4549,7 @@ dependencies = [ [[package]] name = "pdfcraft-preflight" -version = "0.16.0" +version = "0.16.1" dependencies = [ "flate2", "pdfcraft-content", @@ -4560,7 +4560,7 @@ dependencies = [ [[package]] name = "pdfcraft-print" -version = "0.16.0" +version = "0.16.1" dependencies = [ "craft-print", "pdfcraft-content", @@ -4572,7 +4572,7 @@ dependencies = [ [[package]] name = "pdfcraft-redact" -version = "0.16.0" +version = "0.16.1" dependencies = [ "pdfcraft-annot", "pdfcraft-content", @@ -4589,7 +4589,7 @@ dependencies = [ [[package]] name = "pdfcraft-render" -version = "0.16.0" +version = "0.16.1" dependencies = [ "bytemuck", "flate2", @@ -4608,7 +4608,7 @@ dependencies = [ [[package]] name = "pdfcraft-sign" -version = "0.16.0" +version = "0.16.1" dependencies = [ "aes", "aws-lc-rs", @@ -4644,11 +4644,11 @@ dependencies = [ [[package]] name = "pdfcraft-testkit" -version = "0.16.0" +version = "0.16.1" [[package]] name = "pdfcraft-ui-egui" -version = "0.16.0" +version = "0.16.1" dependencies = [ "arboard", "base64 0.23.1", @@ -4700,7 +4700,7 @@ dependencies = [ [[package]] name = "pdfcraft-web" -version = "0.16.0" +version = "0.16.1" dependencies = [ "eframe", "egui", @@ -4712,7 +4712,7 @@ dependencies = [ [[package]] name = "pdfcraft-xfa" -version = "0.16.0" +version = "0.16.1" dependencies = [ "base64 0.23.1", "image", @@ -4726,7 +4726,7 @@ dependencies = [ [[package]] name = "pdfcraft-xfdf" -version = "0.16.0" +version = "0.16.1" dependencies = [ "pdfcraft-annot", "pdfcraft-cos", diff --git a/Cargo.toml b/Cargo.toml index a64505405d..771f4c9611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ exclude = ["vendor"] default-members = ["crates/*", "apps/pdfcraft", "apps/pdfcraft-cli", "xtask"] [workspace.package] -version = "0.16.0" +version = "0.16.1" edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.95" @@ -26,39 +26,39 @@ craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-l # craft-update: app-specific pretend versions apply to startup and manual checks. craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "04aa1703b32d299e54f78fcde4182f2b393a9116", features = ["egui"] } # internal -pdfcraft-geom = { path = "crates/geom", version = "0.16.0" } -pdfcraft-filters = { path = "crates/filters", version = "0.16.0" } -pdfcraft-cos = { path = "crates/cos", version = "0.16.0" } -pdfcraft-crypt = { path = "crates/crypt", version = "0.16.0" } -pdfcraft-organize = { path = "crates/organize", version = "0.16.0" } -pdfcraft-measure = { path = "crates/measure", version = "0.16.0" } -pdfcraft-annot = { path = "crates/annot", version = "0.16.0" } -pdfcraft-audit = { path = "crates/audit", version = "0.16.0" } -pdfcraft-fonts = { path = "crates/fonts", version = "0.16.0" } -pdfcraft-model = { path = "crates/model", version = "0.16.0" } -pdfcraft-content = { path = "crates/content", version = "0.16.0" } -pdfcraft-redact = { path = "crates/redact", version = "0.16.0" } -pdfcraft-print = { path = "crates/print", version = "0.16.0" } -pdfcraft-xfdf = { path = "crates/xfdf", version = "0.16.0" } -pdfcraft-sign = { path = "crates/sign", version = "0.16.0" } -pdfcraft-optimize = { path = "crates/optimize", version = "0.16.0" } -pdfcraft-a11y = { path = "crates/a11y", version = "0.16.0" } -pdfcraft-oracle = { path = "crates/oracle", version = "0.16.0" } -pdfcraft-testkit = { path = "crates/testkit", version = "0.16.0" } -pdfcraft-ocr = { path = "crates/ocr", version = "0.16.0" } -pdfcraft-js = { path = "crates/js", version = "0.16.0" } -pdfcraft-compare = { path = "crates/compare", version = "0.16.0" } -pdfcraft-preflight = { path = "crates/preflight", version = "0.16.0" } -pdfcraft-export = { path = "crates/export", version = "0.16.0" } -pdfcraft-edit = { path = "crates/edit", version = "0.16.0" } -pdfcraft-create = { path = "crates/create", version = "0.16.0" } -pdfcraft-forms = { path = "crates/forms", version = "0.16.0" } -pdfcraft-xfa = { path = "crates/xfa", version = "0.16.0" } -pdfcraft-render = { path = "crates/render", version = "0.16.0" } -pdfcraft-engine = { path = "crates/engine", version = "0.16.0" } -pdfcraft-platform = { path = "crates/platform", version = "0.16.0" } -pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.16.0" } -pdfcraft-automation = { path = "crates/automation", version = "0.16.0", default-features = false } +pdfcraft-geom = { path = "crates/geom", version = "0.16.1" } +pdfcraft-filters = { path = "crates/filters", version = "0.16.1" } +pdfcraft-cos = { path = "crates/cos", version = "0.16.1" } +pdfcraft-crypt = { path = "crates/crypt", version = "0.16.1" } +pdfcraft-organize = { path = "crates/organize", version = "0.16.1" } +pdfcraft-measure = { path = "crates/measure", version = "0.16.1" } +pdfcraft-annot = { path = "crates/annot", version = "0.16.1" } +pdfcraft-audit = { path = "crates/audit", version = "0.16.1" } +pdfcraft-fonts = { path = "crates/fonts", version = "0.16.1" } +pdfcraft-model = { path = "crates/model", version = "0.16.1" } +pdfcraft-content = { path = "crates/content", version = "0.16.1" } +pdfcraft-redact = { path = "crates/redact", version = "0.16.1" } +pdfcraft-print = { path = "crates/print", version = "0.16.1" } +pdfcraft-xfdf = { path = "crates/xfdf", version = "0.16.1" } +pdfcraft-sign = { path = "crates/sign", version = "0.16.1" } +pdfcraft-optimize = { path = "crates/optimize", version = "0.16.1" } +pdfcraft-a11y = { path = "crates/a11y", version = "0.16.1" } +pdfcraft-oracle = { path = "crates/oracle", version = "0.16.1" } +pdfcraft-testkit = { path = "crates/testkit", version = "0.16.1" } +pdfcraft-ocr = { path = "crates/ocr", version = "0.16.1" } +pdfcraft-js = { path = "crates/js", version = "0.16.1" } +pdfcraft-compare = { path = "crates/compare", version = "0.16.1" } +pdfcraft-preflight = { path = "crates/preflight", version = "0.16.1" } +pdfcraft-export = { path = "crates/export", version = "0.16.1" } +pdfcraft-edit = { path = "crates/edit", version = "0.16.1" } +pdfcraft-create = { path = "crates/create", version = "0.16.1" } +pdfcraft-forms = { path = "crates/forms", version = "0.16.1" } +pdfcraft-xfa = { path = "crates/xfa", version = "0.16.1" } +pdfcraft-render = { path = "crates/render", version = "0.16.1" } +pdfcraft-engine = { path = "crates/engine", version = "0.16.1" } +pdfcraft-platform = { path = "crates/platform", version = "0.16.1" } +pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.16.1" } +pdfcraft-automation = { path = "crates/automation", version = "0.16.1", default-features = false } # pdf hayro = "0.7.1" From 44ddb348a93741308b937996fc3deff5904d1cc1 Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 31 Oct 2026 04:17:20 +0000 Subject: [PATCH 17/19] docs: tidy agent, contributor and asset docs --- AGENTS.md | 15 +++++--------- ATTRIBUTION.md | 28 +++++++++++++-------------- ATTRIBUTION.toml | 28 +++++++++++++-------------- CLAUDE.md | 6 +++--- ROADMAP.md | 4 ++-- assets/app-icon/README.md | 3 +-- contributors/people.toml | 7 ++----- crates/fonts/README.md | 2 +- crates/ui-egui/build.rs | 7 +++---- crates/ui-egui/src/dialogs.rs | 2 +- crates/ui-egui/src/dialogs/credits.rs | 2 +- docs/contributors.md | 25 ++++-------------------- 12 files changed, 51 insertions(+), 78 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 98758c5dbd..cd6c0e2ca5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,8 +2,6 @@ These rules bind every AI agent and every human contributor working in this repository. They come before any task, instruction or deadline. `CLAUDE.md` holds the day-to-day working instructions. If the two ever disagree, **this file wins.** -Some sections link to **craftrules**, the standards shared by the Crafting Apps. That repository is internal to the storytold team, so outside contributors can't open it. Every rule a contributor here must follow is stated in this file and `CLAUDE.md`; the craftrules links give team members the background. - --- ## 1. Asset policy (absolute — no exceptions) @@ -69,7 +67,7 @@ Anything else is forbidden, including assets whose licence you cannot prove from ### 1.4 Fonts live in craft-fonts -- Font assets shared by the Crafting Apps, including every Japanese font, live in [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), not here. **Never commit a font file to this repository**; adding a font means adding it to craft-fonts. (The Latin UI fonts already in `assets/fonts/` — Inter, JetBrains Mono, Dancing Script — stay.) Team members: the shared rules are in [craftrules `standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md) (internal). +- Font assets shared by the Crafting Apps, including every Japanese font, live in [`storytold/craft-fonts`](https://github.com/storytold/craft-fonts), not here. **Never commit a font file to this repository**; adding a font means adding it to craft-fonts. (The Latin UI fonts already in `assets/fonts/` — Inter, JetBrains Mono, Dancing Script — stay.) - craft-fonts is an **optional build input**, never a Cargo dependency: `CRAFT_FONTS_DIR= cargo build` makes `crates/fonts/build.rs` embed its fonts as `pdfcraft_fonts::CRAFT_FONTS`. Without it PdfCraft builds, tests and runs, but has no Japanese face (Japanese UI text shows replacement boxes, and writing Japanese into a PDF returns a clear error). Release builds always set it (`.github/workflows/release.yml`), and `ATTRIBUTION.toml` lists it as a `[[build_input]]`. - The desktop app may read one font **already installed on the user's machine** at runtime, as the last interface fallback for characters no embedded face has (`crates/ui-egui/src/system_fonts.rs`). That font is never embedded, shipped, committed or copied, and never appears in published screenshots (`PDFCRAFT_SYSTEM_FONTS=0`, which `cargo xtask screenshots` sets). It is not a way around §1.2: nothing from it may enter a PDF, a package or the repository. - The page viewer may also draw a CID font of an Adobe CJK collection (Japan1, GB1, CNS1, Korea1) that a PDF does **not embed** with one face already installed on the machine (`crates/render/src/system_font.rs`), **on screen only**. `RenderConfig::system_fonts` is off by default and is enabled only for on-screen viewer and preview rasters (`RenderConfig::on_screen`, used by the engine viewer, Combine thumbnails and accessibility figure previews), so exports, printing, OCR and the automation tools never use it. Like the interface font it is read at runtime and never embedded, shipped, committed or copied; Noto CJK and Source Han are never candidates (§1.1), nor is any Adobe family; and `PDFCRAFT_SYSTEM_FONTS=0` turns it off. It is not a way around §1.2 either: nothing drawn with it may be saved or printed. @@ -93,7 +91,7 @@ If you find an asset that breaks these rules, stop and remove it from the reposi - Behaviour comes only from public specifications (ISO 32000-2, the Arlington model), public documentation and black-box observation. - Never copy GPL/AGPL code. Copyleft tools run only as external oracle processes. - Observe Acrobat only with synthetic fixtures. Never capture personal data, account information or recent files. -- Shared real-file test oracles (Photoshop-authored PSDs, etc.) live in [`storytold/photocraft-corpus`](https://github.com/storytold/photocraft-corpus), explained in [craftrules `standards/test-corpora.md`](https://github.com/storytold/craftrules/blob/main/standards/test-corpora.md) (internal). Never commit large binary fixtures to this repo; fetch them pinned by commit and sha256-verified, as PhotoCraft does with `cargo xtask corpus`. +- Shared real-file test oracles (Photoshop-authored PSDs, etc.) live in [`storytold/photocraft-corpus`](https://github.com/storytold/photocraft-corpus). Never commit large binary fixtures to this repo; fetch them pinned by commit and sha256-verified, as PhotoCraft does with `cargo xtask corpus`. ## 3. Agent control (automation and MCP) @@ -106,7 +104,7 @@ If you find an asset that breaks these rules, stop and remove it from the reposi ## 4. Never crash -PdfCraft opens files from strangers, and people trust it with their work. A malformed PDF, a bad tool argument, a corrupt settings file or a full disk must produce an error the user (or agent) can act on, never a crash and never lost work. **This outranks feature work:** don't ship a feature by adding a panic path, and fix a crash before building on top of it. The rules below are the ones that bind this repository; the shared standard for every Crafting App, [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md), is internal to the storytold team. +PdfCraft opens files from strangers, and people trust it with their work. A malformed PDF, a bad tool argument, a corrupt settings file or a full disk must produce an error the user (or agent) can act on, never a crash and never lost work. **This outranks feature work:** don't ship a feature by adding a panic path, and fix a crash before building on top of it. The rules below are the ones that bind this repository. - **Fail with `Result`.** Non-test code returns errors through the crate's error type and `?`. Where readers are lenient by design, fall back gracefully and record the repair (see Fidelity in `CLAUDE.md`). - **No panicking shortcuts.** No `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!` or `unimplemented!` outside tests, unless the call is provably infallible (e.g. writing to a `Vec`), with a comment saying why. `assert!` only for internal invariants that input cannot reach. @@ -125,20 +123,17 @@ Follow `CLAUDE.md`. It covers quality gates, layering, commit rules and how to r ## Contributor credits (About window) - About ▸ Contributors/Models are compiled into the binary from `contributors/contributors.json` - (commit stats; generated, never hand-edit) and `contributors/people.toml` (names people chose for + (commit stats; never hand-edit) and `contributors/people.toml` (names people chose for themselves). See `docs/contributors.md`. - **Agents working for a contributor:** when you prepare a PR, check whether your human's GitHub username has a `[people.]` entry in `contributors/people.toml`. If not, ask them once whether they want to be credited by more than their username: a real name, a display name, and/or their public GitHub profile name (`sync_github_name = true`). If yes, add **only their own** entry - (copy the template at the top of the file, or run - `python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "…" --sync-github-name`) + (copy the template at the top of the file) and include it in their PR, committed as them. If no, change nothing: they are credited as `@username` anyway. - Never add, edit, guess or copy anyone else's entry or name (not from git config, commit authors or GitHub profiles). Never hand-edit `contributors.json`. -- Maintainers refresh the stats with `python3 ../../craftrules/scripts/contributors.py .` (it also - re-verifies who wrote each `people.toml` entry; `--check` only verifies). ## CI workflows - Workflows in `.github/workflows/` call the shared workflows in [`storytold/craft-common-actions`](https://github.com/storytold/craft-common-actions) at `@main`, with their inputs and secrets. No other jobs: if no shared workflow does what's needed, it doesn't go here. diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 23ebcbbe82..8da4b0a200 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -240,22 +240,22 @@ Every asset PdfCraft includes, bundles or uses to build its published material, | `docs/brand/artcraft-mark-black.svg` | ArtCraft brand: artcraft-mark-black | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. | | `docs/brand/artcraft-mark.png` | ArtCraft brand: artcraft-mark | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. | | `docs/brand/artcraft-mark.svg` | ArtCraft brand: artcraft-mark | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. | -| `assets/app-icon/pdfcraft.svg` | PdfCraft app icon (master vector) | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Master artwork; every other app-icon file is rendered from it (packaging/icons.sh) | -| `assets/app-icon/pdfcraft-small.svg` | PdfCraft app icon (light vector) | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | README header and assets/app-icon/README.md | -| `assets/app-icon/pdfcraft-1024.png` | PdfCraft app icon, 1024 px on Apple's icon grid | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Runtime window/Dock icon on macOS (apps/pdfcraft/src/main.rs) | -| `assets/app-icon/pdfcraft.icns` | PdfCraft app icon (macOS .icns) | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | macOS app bundle icon | -| `assets/app-icon/pdfcraft.ico` | PdfCraft app icon (Windows .ico, 16-256 px) | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Embedded in pdfcraft.exe by apps/pdfcraft/build.rs (Explorer, taskbar, Start menu, Alt-Tab) | +| `assets/app-icon/pdfcraft.svg` | PdfCraft app icon (master vector) | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Master artwork; every other app-icon file is rendered from it (packaging/icons.sh) | +| `assets/app-icon/pdfcraft-small.svg` | PdfCraft app icon (light vector) | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | README header and assets/app-icon/README.md | +| `assets/app-icon/pdfcraft-1024.png` | PdfCraft app icon, 1024 px on Apple's icon grid | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Runtime window/Dock icon on macOS (apps/pdfcraft/src/main.rs) | +| `assets/app-icon/pdfcraft.icns` | PdfCraft app icon (macOS .icns) | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | macOS app bundle icon | +| `assets/app-icon/pdfcraft.ico` | PdfCraft app icon (Windows .ico, 16-256 px) | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Embedded in pdfcraft.exe by apps/pdfcraft/build.rs (Explorer, taskbar, Start menu, Alt-Tab) | | `assets/app-icon/pdfcraft-document.svg` | PdfCraft PDF document icon (master vector) | Lucide Icons and Contributors (document outline); PdfCraft contributors (PDF badge, lettering and composition) | ISC AND (MIT OR Apache-2.0) | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/file-text.svg); contributor-original composition (assets/app-icon/README.md) | Master artwork for the Windows PDF file type icon; rendered by packaging/icons.sh, without fonts or external images | | `assets/app-icon/pdfcraft-document.ico` | PdfCraft PDF document icon (Windows .ico, 16-256 px) | Lucide Icons and Contributors (document outline); PdfCraft contributors (PDF badge, lettering and composition) | ISC AND (MIT OR Apache-2.0) | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/file-text.svg); contributor-original composition (assets/app-icon/README.md) | Embedded in pdfcraft.exe as resource 2; the Windows installer uses it for PdfCraft.Document and Applications/pdfcraft.exe PDF file associations | -| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 16 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 24 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 32 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 48 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 64 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 128 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 256 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop); runtime window icon on Windows and Linux (apps/pdfcraft/src/main.rs) | -| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 512 px | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | -| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.pdfcraft.svg` | PdfCraft app icon (Linux scalable) | PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 16 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 24 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 32 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 48 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 64 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 128 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 256 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop); runtime window icon on Windows and Linux (apps/pdfcraft/src/main.rs) | +| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.pdfcraft.png` | PdfCraft app icon, 512 px | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | +| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.pdfcraft.svg` | PdfCraft app icon (Linux scalable) | PdfCraft contributors (drawn by the project owner in ArtCraft) | MIT OR Apache-2.0 | Contributor-original (see assets/app-icon/README.md) | Linux hicolor theme icon (packaging/linux/ai.storyteller.pdfcraft.desktop) | | `docs/images/pdfcraft-bookmarks.png` | PdfCraft screenshot: Bookmarks | PdfCraft contributors (generated by `cargo xtask screenshots`) | MIT OR Apache-2.0 | this repository | README: navigation. Shows only PdfCraft UI, Lucide icons and the showcase PDF built from the [[fetched]] OFL fonts | | `docs/images/pdfcraft-dark.png` | PdfCraft screenshot: Dark theme | PdfCraft contributors (generated by `cargo xtask screenshots`) | MIT OR Apache-2.0 | this repository | README: themes. Shows only PdfCraft UI, Lucide icons and the showcase PDF built from the [[fetched]] OFL fonts | | `docs/images/pdfcraft-find.png` | PdfCraft screenshot: Find text | PdfCraft contributors (generated by `cargo xtask screenshots`) | MIT OR Apache-2.0 | this repository | README: search. Shows only PdfCraft UI, Lucide icons and the showcase PDF built from the [[fetched]] OFL fonts | diff --git a/ATTRIBUTION.toml b/ATTRIBUTION.toml index 7dcd882f84..84f71f904e 100644 --- a/ATTRIBUTION.toml +++ b/ATTRIBUTION.toml @@ -2873,7 +2873,7 @@ sha256 = "f87af81b6fa411da8820eed2bfdcc54c0ee9cd453b4e868c64f56c69db142d0e" [[asset]] path = "assets/app-icon/pdfcraft.svg" title = "PdfCraft app icon (master vector)" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2884,7 +2884,7 @@ sha256 = "27745c021830f82c033b0f15f2d8d9f8e099138743297887ff7e2c9e7ed069da" [[asset]] path = "assets/app-icon/pdfcraft-small.svg" title = "PdfCraft app icon (light vector)" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2895,7 +2895,7 @@ sha256 = "b524df1a583f8034a5a23836955e4fb423e0149429d695e80d95e5056f408678" [[asset]] path = "assets/app-icon/pdfcraft-1024.png" title = "PdfCraft app icon, 1024 px on Apple's icon grid" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2906,7 +2906,7 @@ sha256 = "68ac6730b8990d07503351d32eacea81e9ec171a7a6f09daf52d6555d8245c8a" [[asset]] path = "assets/app-icon/pdfcraft.icns" title = "PdfCraft app icon (macOS .icns)" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2917,7 +2917,7 @@ sha256 = "fca7e7a0e34cc5c5ecae5bca97ee896aac68c4a34b088bc3a0b3271484f23974" [[asset]] path = "assets/app-icon/pdfcraft.ico" title = "PdfCraft app icon (Windows .ico, 16-256 px)" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2950,7 +2950,7 @@ sha256 = "910be5e0ecdc94ce00368077ca1bef449bd5d62a75516feeb37884296c6fac4d" [[asset]] path = "assets/app-icon/hicolor/16x16/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 16 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2961,7 +2961,7 @@ sha256 = "660d2aace45ec1a28bc7e3708eab71ffee5cb1203f57a6256f7bd7c727193567" [[asset]] path = "assets/app-icon/hicolor/24x24/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 24 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2972,7 +2972,7 @@ sha256 = "1989441bdcc8d1df0d73cfe7eb836e9211abaa0fd7fd6ec7e4634bfcd472995d" [[asset]] path = "assets/app-icon/hicolor/32x32/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 32 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2983,7 +2983,7 @@ sha256 = "7fd1ac555f7f6b13ef3fba2696dfb86900cfcb7f7b669aece38590bf2306cf2e" [[asset]] path = "assets/app-icon/hicolor/48x48/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 48 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -2994,7 +2994,7 @@ sha256 = "784ef3bd9f9e479cb2df22a0aee259a441a9c358390e646639c3a0ed79d41288" [[asset]] path = "assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 64 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -3005,7 +3005,7 @@ sha256 = "deb1a9f849549d3f0652fb6d3c30b6eb6ea54498eaea8c1007d901bbafe7067c" [[asset]] path = "assets/app-icon/hicolor/128x128/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 128 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -3016,7 +3016,7 @@ sha256 = "1933bbeaea290497facd328e917dafcc433d0c0caa9f1e207aa45ac1e430898c" [[asset]] path = "assets/app-icon/hicolor/256x256/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 256 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -3027,7 +3027,7 @@ sha256 = "47094c09fb77eb35d465eee46445626a4d78c7ef50629cc7d04a602de3e144e9" [[asset]] path = "assets/app-icon/hicolor/512x512/apps/ai.storyteller.pdfcraft.png" title = "PdfCraft app icon, 512 px" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" @@ -3038,7 +3038,7 @@ sha256 = "dea8010a8b41011aba054d3bfb8b790da57bc958c60869f508e1bbd288d3f280" [[asset]] path = "assets/app-icon/hicolor/scalable/apps/ai.storyteller.pdfcraft.svg" title = "PdfCraft app icon (Linux scalable)" -author = "PdfCraft contributors (drawn by the project owner in ArtCraft; vectorised with craftrules vectorize_tile.py)" +author = "PdfCraft contributors (drawn by the project owner in ArtCraft)" source = "Contributor-original (see assets/app-icon/README.md)" licence = "MIT OR Apache-2.0" licence_file = "assets/app-icon/LICENSE.txt" diff --git a/CLAUDE.md b/CLAUDE.md index b6c91b1fe6..5e9328f8d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ PdfCraft is a clean-room, open-source, Rust-native PDF application targeting Ado ## Start every session here 1. Read `plan/STATUS.md`: the current milestone, the next unchecked task and any blockers. `ROADMAP.md` is the one-page summary (stage, numbers, progress log) and `docs/roadmap.md` the milestone table; at the end of every session update the milestone row and add a line to the ROADMAP.md progress log. - - Read `docs/gaps.md` (the ranked gaps: Acrobat interop, the open-issue backlog, the renderer and a fidelity harness, editing existing content, Pro workflows, 1.0 polish) and `docs/target-app-parity.md` (the numbers and how they are measured) before choosing work. Prefer the gaps over new P2/P3 features, and keep both documents true when things change (`docs/` progress docs follow craftrules `standards/progress-docs.md`: status line, revision history). + - Read `docs/gaps.md` (the ranked gaps: Acrobat interop, the open-issue backlog, the renderer and a fidelity harness, editing existing content, Pro workflows, 1.0 polish) and `docs/target-app-parity.md` (the numbers and how they are measured) before choosing work. Prefer the gaps over new P2/P3 features, and keep both documents true when things change (`docs/` progress docs carry a status line and a revision history). - "Shipped" in `parity/` means "exists and tested", not "as good as Acrobat". Don't mark a feature shipped on a generic test, and say in its notes what is still missing. 2. Read that task in `plan/execution-plan.md` §3, the relevant section of `plan/architecture.md`, and the README of the crate you're touching. 3. Follow the **autonomous operation protocol** in `plan/execution-plan.md` §7 (orient → plan → implement + test → verify → record → commit). Don't stop to ask unless §7 lists the decision as the user's. @@ -13,7 +13,7 @@ PdfCraft is a clean-room, open-source, Rust-native PDF application targeting Ado ## Non-negotiables - **Assets: read `AGENTS.md` §1 before adding or showing any icon, image, font or document.** No assets from Adobe products, ever. Only openly licensed or contributor-original assets are allowed, each with an entry in `ATTRIBUTION.toml`. `cargo xtask assets` enforces this. `AGENTS.md` overrides this file. -- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts).** Never commit font files here (`AGENTS.md` §1.4; team members: [craftrules `standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md), internal). It is the optional build input `CRAFT_FONTS_DIR`: `git clone https://github.com/storytold/craft-fonts ../craft-fonts && CRAFT_FONTS_DIR=../craft-fonts cargo test --workspace` embeds the Japanese fonts (UI fallback, Japanese text in edited PDFs) and runs their tests, which otherwise skip. Code using `pdfcraft_fonts::CRAFT_FONTS` must work when it is empty. +- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts).** Never commit font files here (`AGENTS.md` §1.4). It is the optional build input `CRAFT_FONTS_DIR`: `git clone https://github.com/storytold/craft-fonts ../craft-fonts && CRAFT_FONTS_DIR=../craft-fonts cargo test --workspace` embeds the Japanese fonts (UI fallback, Japanese text in edited PDFs) and runs their tests, which otherwise skip. Code using `pdfcraft_fonts::CRAFT_FONTS` must work when it is empty. - **Clean-room.** - Never read, disassemble or copy anything inside the Acrobat bundle (names and listings only). **Never open `Contents/Resources/JavaScripts/`.** - Behaviour comes from public docs, specs (ISO 32000-2, the Arlington model) and black-box observation (`plan/acrobat/`). @@ -32,7 +32,7 @@ PdfCraft is a clean-room, open-source, Rust-native PDF application targeting Ado - Cap allocations sized by input, and bound recursion with depth limits or seen-sets. - Handle lock poisoning. - No `unsafe` (`unsafe_code = "forbid"`). - - Every crash fix gets a synthetic regression test. See `AGENTS.md` §4 (and, for team members, the internal `craftrules/standards/never-crash.md`). + - Every crash fix gets a synthetic regression test. See `AGENTS.md` §4. - **Rust only** in the product and build (`xtask`). No handwritten JS/TS. - **Time goes through `chrono`** (workspace dependency). Never hand-roll UTC/calendar conversion (days-to-date algorithms, leap-year math); a crate owns only its output format string. The one exception is range extension: past the years chrono can represent, shift the date by whole 400-year Gregorian cycles (146,097 days, after which the calendar repeats), let chrono compute the month and day, and add the cycles back to the year (splitting seconds into hours, minutes and seconds is plain arithmetic). - **Review every update as you make it, at four zooms.** Not an end-of-task pass — a per-update habit, so a defect never gets built on. **Clause:** checked arithmetic, `get()` not `[i]`, char-boundary slicing, the right error through `?`, `<` against `<=`, the 1-based/0-based seam, capped allocations. **Function:** is it total — name an input that panics; walk empty, zero, max, malformed, cyclic; is the return type honest. **File/module:** two sources of truth, dead or duplicated code, is the public surface still smallest and the doc comment still true. **Project:** layering, wasm, undo/redo, save fidelity, encryption and permissions, a matching `automation` tool or registry entry, and whether a `parity/` status or note just became untrue. Scale the zoom to the change, and fix what a level turns up before widening. diff --git a/ROADMAP.md b/ROADMAP.md index 76e86471b6..60a005cf23 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -555,8 +555,8 @@ Newest first: the date, what moved, and the new overall estimate. - **2026-10-10 (M3, #740):** Column select, as in Acrobat: holding Alt (Option on macOS) when a drag starts selects only the text inside the rectangle, with the Select tool, the text markup tools and Redact; Edit ▸ Column select does it with a plain drag, for Linux desktops whose window manager takes Alt-drag (Xfce, Cinnamon, MATE, KDE Plasma 5 by default). Copy gives rows top to bottom with tab-separated cells (right to left in right-to-left rows, reading order for vertical text); `text_extract` takes the same rectangle as `rect`. The new label is in all 15 catalogs. Also repaired `parity/acrobat-features.toml`, which #650 had left unparseable (duplicate keys on `form.field-list`). Still missing: column selection across pages and ⇧-click extension. Estimates unchanged. - **2026-10-10 (progress docs, hours per audience):** Each readiness number gets its own hours to ≈ 95%: full ≈ 49% / 780–1,400 h, mainstream ≈ 55% / 500–900 h, essentials ≈ 66% / 180–330 h. The full number is now the standard's additive weighted sum over the dimensions (written weights), 47% → 49%: method aligned with the standard, no new evidence. Beta ≈ 26 points, ≈ 400–700 h. - **2026-10-10 (progress docs, gate consistency check):** Re-judged the convert-and-share row with the rule applied across all Crafting Apps (a workflow blocks only when it can't be completed at all; lossy exchange and missing sub-cases are beta items, as for FilmCraft's .prproj or VectorCraft's .ai). Unencrypted sharing works, encryption interop (#774) and Office→PDF are sub-cases, Word export is lossy rather than absent: partial, not blocking. **Stage pre-alpha → alpha**; numbers unchanged (47%, mainstream 55%; beta ≈ 400–700 h). Added an essentials-user score: ≈ 66% for someone who only opens, reads, comments, fills, signs, prints and rearranges pages. -- **2026-10-10 (progress docs, second look and alpha gate):** At the owner's request, after practitioners praised PdfCraft, re-examined the numbers. User sentiment: 7,595 stars, 177k + 39k downloads of 0.4.0/0.5.0, 23 user issues with explicit praise and switching reports (#179, #232, #273), issue themes concentrated in core paths (launch/GPU, rendering, editing, CJK, signing, printing). The checklist is not niche-dominated (497 mainstream vs 311 specialist rows; P0 95% / 91%). The first pass's 9-point cross-cutting deduction double-counted Windows printing, interop and glyphs: **ready for real work 40% → 47%**, and **≈ 55% for the mainstream practitioner** (new). The new core-workflow gate (craftrules progress-docs) fails on convert and share (#774, no Office import, #773): **stage alpha → pre-alpha**, ≈ 40–70 h from alpha; beta still ≈ 400–700 h. -- **2026-10-10 (progress docs, full re-measure):** Re-measured against Acrobat Pro 26.002.21931 (installed bundle inspected: document types, localizations, plug-in and framework names). Checklist: 423 shipped + 74 partial of 808 (56.9%; P0 94.2%), ≈ 67% tier-weighted. Ready for real work ≈ 40%; stage **alpha**; ≈ 400–700 h to beta, ≈ 780–1,400 h to full parity (up from 600–1,100 h: the first public releases surfaced Acrobat interop of encrypted output, Windows printing, Chinese UI glyphs, Word-export layout and a 245-issue backlog). This file restructured to the craftrules progress-docs standard; the honest assessment moved to `docs/target-app-parity.md`, the gap list to `docs/gaps.md`, the milestones (duplicated rows merged) to `docs/roadmap.md`; new `docs/architecture.md`, `docs/localization-parity.md`, `docs/file-format-parity.md`, `docs/pdf-spec-parity.md`, `docs/ui-parity.md`, `docs/hardware-parity.md`. +- **2026-10-10 (progress docs, second look and alpha gate):** At the owner's request, after practitioners praised PdfCraft, re-examined the numbers. User sentiment: 7,595 stars, 177k + 39k downloads of 0.4.0/0.5.0, 23 user issues with explicit praise and switching reports (#179, #232, #273), issue themes concentrated in core paths (launch/GPU, rendering, editing, CJK, signing, printing). The checklist is not niche-dominated (497 mainstream vs 311 specialist rows; P0 95% / 91%). The first pass's 9-point cross-cutting deduction double-counted Windows printing, interop and glyphs: **ready for real work 40% → 47%**, and **≈ 55% for the mainstream practitioner** (new). The new core-workflow gate fails on convert and share (#774, no Office import, #773): **stage alpha → pre-alpha**, ≈ 40–70 h from alpha; beta still ≈ 400–700 h. +- **2026-10-10 (progress docs, full re-measure):** Re-measured against Acrobat Pro 26.002.21931 (installed bundle inspected: document types, localizations, plug-in and framework names). Checklist: 423 shipped + 74 partial of 808 (56.9%; P0 94.2%), ≈ 67% tier-weighted. Ready for real work ≈ 40%; stage **alpha**; ≈ 400–700 h to beta, ≈ 780–1,400 h to full parity (up from 600–1,100 h: the first public releases surfaced Acrobat interop of encrypted output, Windows printing, Chinese UI glyphs, Word-export layout and a 245-issue backlog). This file restructured; the honest assessment moved to `docs/target-app-parity.md`, the gap list to `docs/gaps.md`, the milestones (duplicated rows merged) to `docs/roadmap.md`; new `docs/architecture.md`, `docs/localization-parity.md`, `docs/file-format-parity.md`, `docs/pdf-spec-parity.md`, `docs/ui-parity.md`, `docs/hardware-parity.md`. - **2026-10-10 (M8, redact word lists):** Find text and redact gains "Multiple words or phrases": a list with one word or phrase per line, typed or imported from a `.txt` file, whose every match is marked in one undo step. Lists are trimmed, deduplicated and bounded (1,000 entries of 256 characters, files up to 1 MB). `redact_mark` takes `words` and reports how many marks each made, so an agent sees which found nothing. Tests cover the list parsing, the tool end to end (mark, undo, apply, invalid lists) and the dialog with an imported list; the new labels are translated in all 13 UI catalogs. `protect.redact-word-lists` is shipped; whole-word and case-sensitive matching and exporting the list are still missing. Estimates unchanged (about 30–35%). - **2026-10-10 (M4, bookmarks from structure):** Bookmark options ▸ New bookmarks from structure (command `bookmark.from_structure`, tool `bookmark_from_structure`) turns a tagged document's headings into bookmarks: H1–H6 through the role map, a plain H by its section nesting, nested by level under a new first "Untitled" bookmark as in Acrobat, each linked to its structure element (/SE). Titles come from /ActualText, the marked text on the page (decoded through the font's ToUnicode or encoding), /Alt or /T. The walk is bounded (depth, visits, heading and title caps, cycle-safe), an untagged document gets a clear error, and the edit is one undo step. Tests cover role maps, nesting, ToUnicode text, a self-referencing heading, the tool, undo and the panel. Still missing: Acrobat's element-type choice and destinations at the heading's position. The M4 row no longer lists Replace pages as missing (it shipped earlier). Overall estimate unchanged (about 30–35%). - **2026-10-10 (M9, signing encrypted documents):** Digital signatures, certification and document timestamps work on password-protected PDFs instead of failing with "not supported: signing encrypted documents". `pdfcraft-cos` no longer encrypts or decrypts a signature dictionary's `/Contents` (§7.6.2; by `/Type /Sig|/DocTimeStamp` or `/Contents` + `/ByteRange`), so the placeholder is patched after hashing as in a plain file and existing signatures in encrypted files read their real CMS (which also fixes their DSS `/VRI` keys and keeps them intact in a full rewrite). Opened with the user password, the signer applies the document's permissions (fill-forms for existing fields and timestamps, annotate + modify for new fields, modify for certifying) and names the permissions password when it refuses. Synthetic tests cover all four algorithms, counter-signing, tampering, permissions and the automation tools. Overall estimate unchanged (about 30–35%). diff --git a/assets/app-icon/README.md b/assets/app-icon/README.md index 58ef07d37c..7c17ad0045 100644 --- a/assets/app-icon/README.md +++ b/assets/app-icon/README.md @@ -20,8 +20,7 @@ icons use the full-bleed tile. macOS icons put it on Apple's grid (an 824 px bod 1024 px canvas). **Provenance:** the project owner's original drawing, made in ArtCraft (2880 px, keyed to the palette), then -vectorised with craftrules `assets/logo-options/_tools/vectorize_tile.py` (potrace; no filtering or -warping). The source drawing is kept in craftrules at `assets/app-icons/pdfcraft/source.png`, not here. +vectorised (potrace; no filtering or warping). Licence: [LICENSE.txt](LICENSE.txt) (`MIT OR Apache-2.0`, like the repo). ## Files diff --git a/contributors/people.toml b/contributors/people.toml index 53cc2391ed..a5a47f5c52 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -1,13 +1,10 @@ -# Contributor names for the About window (craftrules standards/contributors.md). +# Contributor names for the About window. # # Everyone who contributes is credited by GitHub username automatically. Anything more is up to # you: add YOUR OWN entry below, in your own PR (commit it yourself). Never add someone else; a # maintainer may add a person only when that person asked, recording where in consent_source. # -# Easiest: python3 ../../craftrules/scripts/contributors.py --add-me . \ -# [--real-name "Ada Lovelace"] [--display-name "Ada"] [--sync-github-name] -# -# Or copy this block, replacing your-github-username (every field is optional): +# Copy this block, replacing your-github-username (every field is optional): # # [people.your-github-username] # real_name = "Your Name" # shown when the viewer picks "Real name" diff --git a/crates/fonts/README.md b/crates/fonts/README.md index 36b305bd9d..b20b930a91 100644 --- a/crates/fonts/README.md +++ b/crates/fonts/README.md @@ -72,7 +72,7 @@ needed. wasm32 builds embed only BIZ UDPGothic Regular and any `Arab`, `Telu`, `Hebr` or `Thai` face, to keep the web build small: the web build currently has no Chinese face, so Chinese there still shows the replacement glyph. Font files are never -committed here (`AGENTS.md` §1.4; team members: [craftrules `standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md), internal). +committed here (`AGENTS.md` §1.4). The full font subsystem (parsing, shaping, subsetting and embedding) arrives with M2.2/M7. diff --git a/crates/ui-egui/build.rs b/crates/ui-egui/build.rs index f40007fb2f..3116b01db3 100644 --- a/crates/ui-egui/build.rs +++ b/crates/ui-egui/build.rs @@ -1,8 +1,7 @@ -//! Bake the contributor credits into the binary as static Rust tables (craftrules -//! standards/contributors.md). Nothing is read at run time. +//! Bake the contributor credits into the binary as static Rust tables. Nothing is read at run time. //! -//! - contributors/contributors.json: commit stats per GitHub username, generated by craftrules -//! scripts/contributors.py, which also marks whose people.toml entry it verified. +//! - contributors/contributors.json: commit stats per GitHub username, with a mark for whose people.toml +//! entry is verified. //! - contributors/people.toml: the names people chose for themselves. A name is used only when the //! JSON says the entry was verified (`people_entry`). //! diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index d24e62cb7d..bfb0da686a 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1506,7 +1506,7 @@ fn dialog_shortcuts(ui: &mut egui::Ui) -> bool { /// `Dialog::About` content. Returns `false` when it has its own buttons and no footer is wanted. fn dialog_about(ui: &mut egui::Ui, req: &mut Pending) -> bool { let t = Tokens::get(ui.ctx()); - // Tabs About · Contributors · Models (craftrules standards/contributors.md). + // Tabs About · Contributors · Models. let tab_id = egui::Id::unique("about_tab"); let mut tab = ui.data_mut(|d| d.get_temp::(tab_id)).unwrap_or(0); ui.horizontal_wrapped(|ui| { diff --git a/crates/ui-egui/src/dialogs/credits.rs b/crates/ui-egui/src/dialogs/credits.rs index d509a3b0eb..ad45ec77b6 100644 --- a/crates/ui-egui/src/dialogs/credits.rs +++ b/crates/ui-egui/src/dialogs/credits.rs @@ -1,6 +1,6 @@ //! The About window's credits: every contributor (GitHub login always; display and real names only //! with recorded consent) and the AI models that helped. The tables are baked into the binary by -//! build.rs from contributors/contributors.json (craftrules standards/contributors.md); nothing is +//! build.rs from contributors/contributors.json; nothing is //! read at run time. use std::cmp::Ordering; diff --git a/docs/contributors.md b/docs/contributors.md index a1b0bcba13..fa7e988611 100644 --- a/docs/contributors.md +++ b/docs/contributors.md @@ -1,16 +1,14 @@ # Contributors in the About window **About ▸ Contributors** credits everyone who contributed to PdfCraft, and **About ▸ Models** credits -the AI models named in `Co-Authored-By` trailers. This follows the shared craftrules standard -[`standards/contributors.md`](https://github.com/storytold/craftrules/blob/main/standards/contributors.md); -this page is the local copy of the decision. +the AI models named in `Co-Authored-By` trailers. ## Decision - Two files, both **compiled into the binary** by the UI crate's `build.rs` (nothing is read from disk or the network at run time; the web build has them too): - [`contributors/contributors.json`](../contributors/contributors.json): commit stats per GitHub - username. **Generated** by `craftrules/scripts/contributors.py`; never hand-edit. + username. - [`contributors/people.toml`](../contributors/people.toml): the names people chose for themselves. **Each contributor adds only their own entry**, in their own PR. - Each contributor gets one line: GitHub username (always), display name (opt-in, or their synced @@ -36,20 +34,5 @@ display_name = "Nickname" sync_github_name = true # use the name on your public GitHub profile as display name ``` -or, with craftrules checked out next to `craft-apps/`: - -```sh -python3 ../../craftrules/scripts/contributors.py --add-me . --real-name "Your Name" --sync-github-name -``` - -Names show after the next refresh: the script checks that each entry was committed by its own -user (or by a maintainer with a `consent_source`). Delete your entry to remove your names. - -## Refreshing (maintainers) - -```sh -python3 ../../craftrules/scripts/contributors.py . # regenerate + verify; needs git, Python 3.11+, gh -python3 ../../craftrules/scripts/contributors.py --check . # verify people.toml only -``` - -Commit the updated `contributors/contributors.json`. Git author names and emails are never recorded. +Names show for entries committed by their own user (or by a maintainer with a `consent_source`). +Delete your entry to remove your names. Git author names and emails are never recorded. From da4cc2c145578c3167ac4ce5c0de7094d26186a3 Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 31 Oct 2026 04:25:36 +0000 Subject: [PATCH 18/19] docs: tidy wording --- ROADMAP.md | 6 +++--- docs/roadmap.md | 2 +- docs/target-app-parity.md | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 60a005cf23..56f6237e8d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -553,7 +553,7 @@ Newest first: the date, what moved, and the new overall estimate. - **2026-10-10 (L Print, #756):** Windows prints. The Print dialog lists the system's printers and its default from `Win32_Printer` (through PowerShell's CIM; `wmic` is gone from Windows 11), and Print spools the print-ready PDF: the in-box Windows.Data.Pdf renders each sheet at the printer's own resolution and System.Drawing.Printing carries copies, collation, duplex and colour as the job's settings — both reached through `powershell`, so the Rust code touches no Win32 API and the machine needs no PDF reader. This closes #756 and the "printer not showing" duplicates #160, #244, #293, #465 and #644, using the CIM approach community-tested in #452; the tests render the job's sheets through the whole WinRT path on CI (dry run, no printer needed) and, where a printer can be added, catch a real job in its paused queue. Web printing is still missing; milestone and effort estimates are unchanged. - **2026-10-10 (M3, #740):** Column select, as in Acrobat: holding Alt (Option on macOS) when a drag starts selects only the text inside the rectangle, with the Select tool, the text markup tools and Redact; Edit ▸ Column select does it with a plain drag, for Linux desktops whose window manager takes Alt-drag (Xfce, Cinnamon, MATE, KDE Plasma 5 by default). Copy gives rows top to bottom with tab-separated cells (right to left in right-to-left rows, reading order for vertical text); `text_extract` takes the same rectangle as `rect`. The new label is in all 15 catalogs. Also repaired `parity/acrobat-features.toml`, which #650 had left unparseable (duplicate keys on `form.field-list`). Still missing: column selection across pages and ⇧-click extension. Estimates unchanged. -- **2026-10-10 (progress docs, hours per audience):** Each readiness number gets its own hours to ≈ 95%: full ≈ 49% / 780–1,400 h, mainstream ≈ 55% / 500–900 h, essentials ≈ 66% / 180–330 h. The full number is now the standard's additive weighted sum over the dimensions (written weights), 47% → 49%: method aligned with the standard, no new evidence. Beta ≈ 26 points, ≈ 400–700 h. +- **2026-10-10 (progress docs, hours per audience):** Each readiness number gets its own hours to ≈ 95%: full ≈ 49% / 780–1,400 h, mainstream ≈ 55% / 500–900 h, essentials ≈ 66% / 180–330 h. The full number is now the additive weighted sum over the dimensions (written weights), 47% → 49%; no new evidence. Beta ≈ 26 points, ≈ 400–700 h. - **2026-10-10 (progress docs, gate consistency check):** Re-judged the convert-and-share row with the rule applied across all Crafting Apps (a workflow blocks only when it can't be completed at all; lossy exchange and missing sub-cases are beta items, as for FilmCraft's .prproj or VectorCraft's .ai). Unencrypted sharing works, encryption interop (#774) and Office→PDF are sub-cases, Word export is lossy rather than absent: partial, not blocking. **Stage pre-alpha → alpha**; numbers unchanged (47%, mainstream 55%; beta ≈ 400–700 h). Added an essentials-user score: ≈ 66% for someone who only opens, reads, comments, fills, signs, prints and rearranges pages. - **2026-10-10 (progress docs, second look and alpha gate):** At the owner's request, after practitioners praised PdfCraft, re-examined the numbers. User sentiment: 7,595 stars, 177k + 39k downloads of 0.4.0/0.5.0, 23 user issues with explicit praise and switching reports (#179, #232, #273), issue themes concentrated in core paths (launch/GPU, rendering, editing, CJK, signing, printing). The checklist is not niche-dominated (497 mainstream vs 311 specialist rows; P0 95% / 91%). The first pass's 9-point cross-cutting deduction double-counted Windows printing, interop and glyphs: **ready for real work 40% → 47%**, and **≈ 55% for the mainstream practitioner** (new). The new core-workflow gate fails on convert and share (#774, no Office import, #773): **stage alpha → pre-alpha**, ≈ 40–70 h from alpha; beta still ≈ 400–700 h. - **2026-10-10 (progress docs, full re-measure):** Re-measured against Acrobat Pro 26.002.21931 (installed bundle inspected: document types, localizations, plug-in and framework names). Checklist: 423 shipped + 74 partial of 808 (56.9%; P0 94.2%), ≈ 67% tier-weighted. Ready for real work ≈ 40%; stage **alpha**; ≈ 400–700 h to beta, ≈ 780–1,400 h to full parity (up from 600–1,100 h: the first public releases surfaced Acrobat interop of encrypted output, Windows printing, Chinese UI glyphs, Word-export layout and a 245-issue backlog). This file restructured; the honest assessment moved to `docs/target-app-parity.md`, the gap list to `docs/gaps.md`, the milestones (duplicated rows merged) to `docs/roadmap.md`; new `docs/architecture.md`, `docs/localization-parity.md`, `docs/file-format-parity.md`, `docs/pdf-spec-parity.md`, `docs/ui-parity.md`, `docs/hardware-parity.md`. @@ -765,11 +765,11 @@ Newest first: the date, what moved, and the new overall estimate. | 2026-10-11 | minor | M7: #594 paragraph-drag candidate and source regressions prepared; subsequent Linux validation passed all eight CI gates. Estimates unchanged. | | 2026-10-10 | minor | Recorded the remaining System appearance repair after #881; estimates unchanged | | 2026-10-11 | minor | Contributor fork: document-font selection and subset completion, CFF, style ranges, contextual/bidi save/reopen and all-page Chinese keyboard acceptance; curve/hole hit testing (shared clips stay fixed, as on main). Scope remains bounded; percentages unchanged | -| 2026-10-10 | minor | Readiness table with hours per audience; full number restated as the additive weighted sum over dimensions (47% → 49%; method aligned with the standard, no new evidence) | +| 2026-10-10 | minor | Readiness table with hours per audience; full number restated as the additive weighted sum over dimensions (47% → 49%; no new evidence) | | 2026-10-10 | minor | Essentials-user score added (≈ 66%) | | 2026-10-10 | minor | Gate consistency check: convert and share re-judged partial, not blocking; stage pre-alpha → alpha | | 2026-10-10 | minor | Second look: ready 40% → 47% (cross-cutting double count removed), mainstream-practitioner numbers and user sentiment added; core-workflow alpha gate applied, stage alpha → pre-alpha | -| 2026-10-10 | major | Restructured to the progress-docs standard (stage, two numbers, dimensions, features, languages, upcoming, log); full re-measure; assessment, gaps and milestones moved to `docs/`; log sorted newest first and a stray merged entry dated | +| 2026-10-10 | major | Progress docs restructured (stage, two numbers, dimensions, features, languages, upcoming, log); full re-measure; assessment, gaps and milestones moved to `docs/`; log sorted newest first and a stray merged entry dated | | 2026-10-07 | minor | 806 tracked features, 51.0% shipped, effort-weighted ≈ 30–35%, 600–1,100 h | | 2026-10-05 | major | Honest assessment and "Where we're lacking" added | | 2026-10-12 | minor | M8: cleanup removes replacement descriptions only from changed marked-content spans, preserving surviving accessibility text. Estimates unchanged. | diff --git a/docs/roadmap.md b/docs/roadmap.md index 12161c4dff..0f1fb6f4d5 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -129,7 +129,7 @@ encryption and storage-write retry behavior are unchanged; this does not resolve ## Alpha gate -The stage standard's gate: Acrobat Pro's everyday core workflows must each work end to end on the +The alpha gate: Acrobat Pro's everyday core workflows must each work end to end on the main platform (macOS), with the work saved and reopened. Any `no`, or a `partial` that blocks the workflow, keeps PdfCraft pre-alpha; those rows are the alpha checklist. Assessed 2026-10-10 with the rule applied across the Crafting Apps: a row blocks only when a typical user cannot complete the diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 46767848cc..d03c27c95c 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -98,8 +98,8 @@ platforms 5%, hardware 4%, ecosystem 1%, AI 1%: History: the first version of this file subtracted 9 points from the area figure for cross-cutting shortfalls (40%); the second look found that a double count (Windows printing, encrypted-output interop and Chinese glyphs were already scored in their areas) and used a 2-point deduction (47%); -this version replaces the deduction with the standard's weighted sum over dimensions (49%). Method -aligned with the standard, no new evidence. Multiplicative discounts are used only for the +this version replaces the deduction with the weighted sum over dimensions (49%), with no new +evidence. Multiplicative discounts are used only for the mainstream and essentials numbers. ## Mainstream practitioner vs full Acrobat Pro @@ -306,7 +306,7 @@ licensed OCR or AI model. | 2026-10-11 | minor | Web printing shipped (gap 4): Hardware, Platforms and L Print text updated. Text only: scores, readiness and hours not re-judged | | 2026-10-11 | minor | Contributor fork: document-font selection and subset completion, CFF, style ranges, contextual/bidi save/reopen and all-page Chinese keyboard acceptance; curve/hole hit testing (shared clips stay fixed, as on main). Scope remains bounded; percentages unchanged | | 2026-10-11 | minor | Synced with closed issues: encrypted output opens in Acrobat (#774, #836), Windows prints (#756, #847), column select (#740), 1:1 touchpad scrolling (#759), same-page multi-object move (#844), #825 and the #799–#821 fix batch. Text only: scores, readiness and hours not re-judged | -| 2026-10-10 | minor | Readiness table: hours to ≈ 95% per audience (full 780–1,400, mainstream 500–900, essentials 180–330). Full number restated as the standard's additive weighted sum over dimensions with written weights: 47% → 49%. Method aligned with the standard, no new evidence | +| 2026-10-10 | minor | Readiness table: hours to ≈ 95% per audience (full 780–1,400, mainstream 500–900, essentials 180–330). Full number restated as the additive weighted sum over dimensions with written weights: 47% → 49%. No new evidence | | 2026-10-10 | minor | Added the essentials-user score (≈ 66%): weighted depth of 13 everyday features, discounted for launch, discoverability and opening others' files | | 2026-10-10 | minor | Consistency check across apps: re-judged the convert-and-share gate row with the shared rule (blocking only when the workflow can't be completed at all; lossy exchange and missing sub-cases are beta items). Encrypted output (#774) and Office→PDF are sub-cases, Word export is lossy not absent → partial, not blocking; stage pre-alpha → **alpha**. Numbers unchanged | | 2026-10-10 | minor | Second look requested by the owner after practitioners praised PdfCraft: user sentiment gathered (stars, downloads, 23 praising issues, switching reports, issue themes); checklist split into mainstream (497) and specialist (311) features; the 9-point cross-cutting deduction removed as a double count (Windows printing, interop and glyphs were already scored in their areas). Ready for real work 40% → 47%; mainstream practitioner ≈ 55% added. Core-workflow alpha gate applied: convert and share fails → stage alpha → **pre-alpha**, ≈ 40–70 h from alpha | From 635a84016413181d71226962a1ca44d12ff4a89b Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 31 Oct 2026 04:43:07 +0000 Subject: [PATCH 19/19] updates: show check results with craft-update's CheckResultDialog --- Cargo.lock | 21 ++++---- Cargo.toml | 14 ++--- crates/ui-egui/src/i18n/ar.tsv | 5 -- crates/ui-egui/src/i18n/bg.tsv | 5 -- crates/ui-egui/src/i18n/ca.tsv | 5 -- crates/ui-egui/src/i18n/ckb.tsv | 5 -- crates/ui-egui/src/i18n/cs.tsv | 5 -- crates/ui-egui/src/i18n/de.tsv | 5 -- crates/ui-egui/src/i18n/el.tsv | 5 -- crates/ui-egui/src/i18n/es.tsv | 5 -- crates/ui-egui/src/i18n/fa.tsv | 5 -- crates/ui-egui/src/i18n/fi.tsv | 5 -- crates/ui-egui/src/i18n/fr.tsv | 5 -- crates/ui-egui/src/i18n/hi.tsv | 5 -- crates/ui-egui/src/i18n/hu.tsv | 5 -- crates/ui-egui/src/i18n/id.tsv | 5 -- crates/ui-egui/src/i18n/it.tsv | 5 -- crates/ui-egui/src/i18n/ja.tsv | 5 -- crates/ui-egui/src/i18n/kk.tsv | 5 -- crates/ui-egui/src/i18n/ko.tsv | 5 -- crates/ui-egui/src/i18n/lo.tsv | 5 -- crates/ui-egui/src/i18n/lt.tsv | 5 -- crates/ui-egui/src/i18n/nb.tsv | 5 -- crates/ui-egui/src/i18n/pl.tsv | 5 -- crates/ui-egui/src/i18n/pt-br.tsv | 5 -- crates/ui-egui/src/i18n/ro.tsv | 5 -- crates/ui-egui/src/i18n/ru.tsv | 5 -- crates/ui-egui/src/i18n/sl.tsv | 5 -- crates/ui-egui/src/i18n/sq.tsv | 5 -- crates/ui-egui/src/i18n/te.tsv | 5 -- crates/ui-egui/src/i18n/th.tsv | 5 -- crates/ui-egui/src/i18n/tr.tsv | 5 -- crates/ui-egui/src/i18n/uk.tsv | 5 -- crates/ui-egui/src/i18n/vi.tsv | 5 -- crates/ui-egui/src/i18n/zh-hans.tsv | 5 -- crates/ui-egui/src/i18n/zh-hant.tsv | 5 -- crates/ui-egui/src/platform/updates.rs | 75 ++++++-------------------- crates/ui-egui/tests/it/updates.rs | 7 +-- 38 files changed, 38 insertions(+), 249 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b29fd629b2..fd186d995c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,17 +1178,17 @@ dependencies = [ [[package]] name = "craft-docpath" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" [[package]] name = "craft-fonts-build" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" [[package]] name = "craft-i18n" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "fixed_decimal", "icu_decimal", @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "craft-keymap" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "egui", "serde", @@ -1221,7 +1221,7 @@ dependencies = [ [[package]] name = "craft-menu" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "craft-keymap", "craft-quit", @@ -1234,9 +1234,10 @@ dependencies = [ [[package]] name = "craft-print" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "craft-winprint-gdi", + "log", "miniz_oxide 0.9.1", "roxmltree 0.21.2", "tempfile", @@ -1247,7 +1248,7 @@ dependencies = [ [[package]] name = "craft-quit" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "dispatch2", "egui", @@ -1258,7 +1259,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545#e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "craft-fonts-build", "craft-i18n", @@ -1279,7 +1280,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=04aa1703b32d299e54f78fcde4182f2b393a9116#04aa1703b32d299e54f78fcde4182f2b393a9116" +source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7" dependencies = [ "chrono", "dunce", @@ -6765,7 +6766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix 1.1.5", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index 771f4c9611..85d02570ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,18 +13,18 @@ repository = "https://github.com/storytold/pdfcraft" [workspace.dependencies] # Shared frontend components; engines remain independent of egui. -craft-ui = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545", features = ["serde"] } +craft-ui = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["serde"] } # Quit the macOS way (#1136): ⌘Q asks about unsaved changes, and closing quits through `terminate:`. -craft-quit = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" } +craft-quit = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" } # One menu definition for the native macOS menu bar and the in-window bar elsewhere. -craft-menu = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" } -craft-keymap = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545", default-features = false } +craft-menu = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" } +craft-keymap = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", default-features = false } # Shared system spooler; document composition remains in pdfcraft-print. -craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" } +craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" } # Whether a path stored in a document may be opened (GoToR link targets). -craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e11f9d9aebea5f6b8e74643c1ea1ed172ba5f545" } +craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" } # craft-update: app-specific pretend versions apply to startup and manual checks. -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "04aa1703b32d299e54f78fcde4182f2b393a9116", features = ["egui"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] } # internal pdfcraft-geom = { path = "crates/geom", version = "0.16.1" } pdfcraft-filters = { path = "crates/filters", version = "0.16.1" } diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index ce5bf387be..56f62b7734 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -823,14 +823,9 @@ Copy Image in Original Format نسخ الصورة بتنسيقها الأصلي The image has been copied as a {ext} file نُسخت الصورة كملف {ext} The page has changed; select the image again تغيّرت الصفحة؛ حدّد الصورة مرة أخرى - Check for updates التحقق من التحديثات No check has run yet. لم يُجرَ أي تحقق بعد. - Checking for a newer version… جارٍ التحقق من وجود إصدار أحدث… PdfCraft {v} is available. PdfCraft {v} متاح. You have version {c}. Download the new version from its release page. لديك الإصدار {c}. نزّل الإصدار الجديد من صفحة إصداره. - PdfCraft {c} is up to date. PdfCraft {c} محدَّث. - Couldn't check for updates: {e} تعذّر التحقق من التحديثات: {e} - You have version {c}. All releases are listed at {page}. لديك الإصدار {c}. كل الإصدارات مدرجة في {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. يسأل GitHub عن أحدث إصدار. لا يُنزَّل أو يُثبَّت شيء تلقائيًا. Download تنزيل Later لاحقًا diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index eb37bdd775..ceccdf9582 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -486,14 +486,9 @@ Copy Image in Original Format Копирай изображението в оригиналния формат The image has been copied as a {ext} file Изображението е копирано като файл {ext} The page has changed; select the image again Страницата е променена; изберете изображението отново - Check for updates Провери за актуализации No check has run yet. Още не е извършвана проверка. - Checking for a newer version… Проверка за по-нова версия… PdfCraft {v} is available. Налична е PdfCraft {v}. You have version {c}. Download the new version from its release page. Имате версия {c}. Изтеглете новата версия от страницата на изданието. - PdfCraft {c} is up to date. PdfCraft {c} е актуална. - Couldn't check for updates: {e} Неуспешна проверка за актуализации: {e} - You have version {c}. All releases are listed at {page}. Имате версия {c}. Всички издания са изброени на {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Запитва GitHub за най-новото издание. Нищо не се изтегля и не се инсталира автоматично. Download Изтегли Later По-късно diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 513016faf4..83750ba124 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -777,14 +777,9 @@ Copy Image in Original Format Copia la imatge en el format original The image has been copied as a {ext} file S'ha copiat la imatge com a fitxer {ext} The page has changed; select the image again La pàgina ha canviat; torna a seleccionar la imatge - Check for updates Cerca actualitzacions No check has run yet. Encara no s'ha fet cap comprovació. - Checking for a newer version… S'està cercant una versió més recent… PdfCraft {v} is available. PdfCraft {v} està disponible. You have version {c}. Download the new version from its release page. Tens la versió {c}. Baixa la versió nova des de la seva pàgina de publicació. - PdfCraft {c} is up to date. PdfCraft {c} està actualitzat. - Couldn't check for updates: {e} No s'han pogut cercar actualitzacions: {e} - You have version {c}. All releases are listed at {page}. Tens la versió {c}. Totes les versions es llisten a {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Consulta a GitHub la darrera versió. No es baixa ni s'instal·la res automàticament. Download Baixa Later Més tard diff --git a/crates/ui-egui/src/i18n/ckb.tsv b/crates/ui-egui/src/i18n/ckb.tsv index 645f174416..f6d91e3f42 100644 --- a/crates/ui-egui/src/i18n/ckb.tsv +++ b/crates/ui-egui/src/i18n/ckb.tsv @@ -787,14 +787,9 @@ Copy Image in Original Format کۆپیکردنی وێنە بە فۆرماتی ڕەسەن The image has been copied as a {ext} file وێنەکە وەک فایلی {ext} کۆپی کرا The page has changed; select the image again پەڕەکە گۆڕاوە؛ دووبارە وێنەکە هەڵبژێرە - Check for updates پشکنین بۆ نوێکردنەوەکان No check has run yet. هێشتا هیچ پشکنینێک نەکراوە. - Checking for a newer version… پشکنین بۆ وەشانێکی نوێتر… PdfCraft {v} is available. PdfCraft {v} بەردەستە. You have version {c}. Download the new version from its release page. وەشانی {c} هەیە. وەشانە نوێیەکە لە پەڕەی وەشانەکەیەوە دابگرە. - PdfCraft {c} is up to date. PdfCraft {c} نوێکراوەتەوە. - Couldn't check for updates: {e} پشکنین بۆ نوێکردنەوە نەکرا: {e} - You have version {c}. All releases are listed at {page}. وەشانی {c} هەیە. هەموو وەشانەکان لە {page} پێڕست کراون. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. لە GitHub دەپرسێت بۆ نوێترین وەشان. هیچ شتێک بە شێوەی خۆکار داگیر ناکرێت یان دادەمەزرێنرێت. Download داگرتن Later دواتر diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 4ab47a4c3c..8bfde72a85 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -902,14 +902,9 @@ key Space Mezerník Copy Image in Original Format Kopírovat obrázek v původním formátu The image has been copied as a {ext} file Obrázek byl zkopírován jako soubor {ext} The page has changed; select the image again Stránka se změnila; vyberte obrázek znovu - Check for updates Zkontrolovat aktualizace No check has run yet. Kontrola zatím neproběhla. - Checking for a newer version… Hledání novější verze… PdfCraft {v} is available. Je k dispozici PdfCraft {v}. You have version {c}. Download the new version from its release page. Máte verzi {c}. Novou verzi si stáhněte z její stránky s vydáním. - PdfCraft {c} is up to date. PdfCraft {c} je aktuální. - Couldn't check for updates: {e} Aktualizace nelze zkontrolovat: {e} - You have version {c}. All releases are listed at {page}. Máte verzi {c}. Všechna vydání najdete na {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Zjistí na GitHubu nejnovější vydání. Nic se automaticky nestahuje ani neinstaluje. Download Stáhnout Later Později diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index fab8b0bff5..83684c08ea 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -801,14 +801,9 @@ Copy Image in Original Format Bild im Originalformat kopieren The image has been copied as a {ext} file Das Bild wurde als {ext}-Datei kopiert The page has changed; select the image again Die Seite hat sich geändert; wählen Sie das Bild erneut aus - Check for updates Nach Updates suchen No check has run yet. Es wurde noch nicht gesucht. - Checking for a newer version… Suche nach einer neueren Version… PdfCraft {v} is available. PdfCraft {v} ist verfügbar. You have version {c}. Download the new version from its release page. Du hast Version {c}. Lade die neue Version von ihrer Release-Seite herunter. - PdfCraft {c} is up to date. PdfCraft {c} ist auf dem neuesten Stand. - Couldn't check for updates: {e} Updates konnten nicht gesucht werden: {e} - You have version {c}. All releases are listed at {page}. Du hast Version {c}. Alle Releases findest du unter {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Fragt GitHub nach dem neuesten Release. Es wird nichts automatisch heruntergeladen oder installiert. Download Herunterladen Later Später diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 707f15f99a..576cdfcd6d 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -272,7 +272,6 @@ General Γενικά About Πληροφορίες About PdfCraft Σχετικά με το PdfCraft - Check for updates Έλεγχος για ενημερώσεις Keyboard shortcuts Συντομεύσεις πληκτρολογίου Default Προεπιλογή Custom Προσαρμοσμένο @@ -394,12 +393,8 @@ Replace Image… Αντικατάσταση εικόνας… Save Image As… Αποθήκευση εικόνας ως… No check has run yet. Δεν έχει πραγματοποιηθεί ακόμη έλεγχος. - Checking for a newer version… Έλεγχος για νεότερη έκδοση… PdfCraft {v} is available. Η έκδοση {v} του PdfCraft είναι διαθέσιμη. You have version {c}. Download the new version from its release page. Έχετε την έκδοση {c}. Κάντε λήψη της νέας έκδοσης από τη σελίδα εκδόσεών της. - PdfCraft {c} is up to date. Το PdfCraft {c} είναι ενημερωμένο. - Couldn't check for updates: {e} Δεν ήταν δυνατός ο έλεγχος για ενημερώσεις: {e} - You have version {c}. All releases are listed at {page}. Έχετε την έκδοση {c}. Όλες οι εκδόσεις βρίσκονται στη διεύθυνση {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Αναζητά την τελευταία έκδοση στο GitHub. Δεν γίνεται αυτόματα λήψη ή εγκατάσταση. Later Αργότερα none κανένα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index a465e5a1f8..3216e4e448 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -817,14 +817,9 @@ Copy Image in Original Format Copiar imagen en formato original The image has been copied as a {ext} file Se ha copiado la imagen como archivo {ext} The page has changed; select the image again La página ha cambiado; vuelva a seleccionar la imagen - Check for updates Buscar actualizaciones No check has run yet. Aún no se ha hecho ninguna comprobación. - Checking for a newer version… Buscando una versión más reciente… PdfCraft {v} is available. PdfCraft {v} está disponible. You have version {c}. Download the new version from its release page. Tienes la versión {c}. Descarga la nueva versión desde su página de publicación. - PdfCraft {c} is up to date. PdfCraft {c} está actualizado. - Couldn't check for updates: {e} No se pudieron buscar actualizaciones: {e} - You have version {c}. All releases are listed at {page}. Tienes la versión {c}. Todas las versiones aparecen en {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Consulta a GitHub cuál es la última versión. No se descarga ni se instala nada automáticamente. Download Descargar Later Más tarde diff --git a/crates/ui-egui/src/i18n/fa.tsv b/crates/ui-egui/src/i18n/fa.tsv index 99a156bc58..292b4fbdb3 100644 --- a/crates/ui-egui/src/i18n/fa.tsv +++ b/crates/ui-egui/src/i18n/fa.tsv @@ -818,14 +818,9 @@ Copy Image in Original Format کپی تصویر در قالب اصلی The image has been copied as a {ext} file تصویر به‌صورت یک فایل {ext} کپی شد The page has changed; select the image again صفحه تغییر کرده است؛ تصویر را دوباره انتخاب کنید - Check for updates بررسی به‌روز‌رسانی‌ها No check has run yet. هنوز هیچ بررسیای انجام نشده است. - Checking for a newer version… بررسی برای نگارش تازه‌تر… PdfCraft {v} is available. PdfCraft {v} در دسترس است. You have version {c}. Download the new version from its release page. شما نگارش {c} را دارید. نگارش جدید را از صفحه انتشار آن دانلود کنید. - PdfCraft {c} is up to date. PdfCraft {c} به‌روز است. - Couldn't check for updates: {e} بررسی به‌روز‌رسانی‌ها انجام نشد: {e} - You have version {c}. All releases are listed at {page}. شما نگارش {c} را دارید. همه انتشار‌ها در {page} فهرست شدهاند. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. آخرین انتشار را از GitHub می‌پرسد. هیچ چیزی به‌طور خودکار دانلود یا نصب نمی‌شود. Download دانلود Later بعداً diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b1dbaffc1e..80d2a0f859 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -776,14 +776,9 @@ Copy Image in Original Format Kopioi kuva alkuperäisessä muodossa The image has been copied as a {ext} file Kuva on kopioitu {ext}-tiedostona The page has changed; select the image again Sivu on muuttunut; valitse kuva uudelleen - Check for updates Tarkista päivitykset No check has run yet. Tarkistusta ei ole vielä tehty. - Checking for a newer version… Tarkistetaan uudempaa versiota… PdfCraft {v} is available. PdfCraft {v} on saatavilla. You have version {c}. Download the new version from its release page. Käytössä on versio {c}. Lataa uusi versio sen julkaisusivulta. - PdfCraft {c} is up to date. PdfCraft {c} on ajan tasalla. - Couldn't check for updates: {e} Päivitysten tarkistus epäonnistui: {e} - You have version {c}. All releases are listed at {page}. Käytössä on versio {c}. Kaikki julkaisut on lueteltu sivulla {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Kysyy uusinta julkaisua GitHubista. Mitään ei ladata tai asenneta automaattisesti. Download Lataa Later Myöhemmin diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 513ba27b3f..6092231df3 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -797,14 +797,9 @@ Copy Image in Original Format Copier l'image au format d'origine The image has been copied as a {ext} file L'image a été copiée en tant que fichier {ext} The page has changed; select the image again La page a changé ; sélectionnez de nouveau l'image - Check for updates Rechercher des mises à jour No check has run yet. Aucune vérification n'a encore été faite. - Checking for a newer version… Recherche d'une version plus récente… PdfCraft {v} is available. PdfCraft {v} est disponible. You have version {c}. Download the new version from its release page. Vous avez la version {c}. Téléchargez la nouvelle version depuis sa page de publication. - PdfCraft {c} is up to date. PdfCraft {c} est à jour. - Couldn't check for updates: {e} Impossible de rechercher les mises à jour : {e} - You have version {c}. All releases are listed at {page}. Vous avez la version {c}. Toutes les versions sont listées sur {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Demande à GitHub la dernière version. Rien n'est téléchargé ni installé automatiquement. Download Télécharger Later Plus tard diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index fa59df3a15..dfaa94b5d6 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -760,14 +760,9 @@ Copy Image in Original Format छवि मूल प्रारूप में कॉपी करें The image has been copied as a {ext} file छवि {ext} फ़ाइल के रूप में कॉपी हो गई है The page has changed; select the image again पृष्ठ बदल गया है; छवि फिर से चुनें - Check for updates अपडेट के लिए जाँचें No check has run yet. अभी तक कोई जाँच नहीं चली है। - Checking for a newer version… नए संस्करण की जाँच हो रही है… PdfCraft {v} is available. PdfCraft {v} उपलब्ध है। You have version {c}. Download the new version from its release page. आपके पास संस्करण {c} है। नया संस्करण इसके रिलीज़ पृष्ठ से डाउनलोड करें। - PdfCraft {c} is up to date. PdfCraft {c} अद्यतन है। - Couldn't check for updates: {e} अपडेट की जाँच नहीं हो सकी: {e} - You have version {c}. All releases are listed at {page}. आपके पास संस्करण {c} है। सभी रिलीज़ {page} पर सूचीबद्ध हैं। Asks GitHub for the latest release. Nothing is downloaded or installed automatically. GitHub से नवीनतम रिलीज़ की क्वेरी करता है। कुछ भी स्वतः डाउनलोड या इंस्टॉल नहीं होता। Download डाउनलोड Later बाद में diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index bc4614e104..53b81062bb 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -794,14 +794,9 @@ Copy Image in Original Format Kép másolása eredeti formátumban The image has been copied as a {ext} file A kép {ext}-fájlként a vágólapra került The page has changed; select the image again Az oldal megváltozott; jelölje ki újra a képet - Check for updates Frissítések keresése No check has run yet. Még nem futott ellenőrzés. - Checking for a newer version… Újabb verzió keresése… PdfCraft {v} is available. Elérhető a PdfCraft {v}. You have version {c}. Download the new version from its release page. A jelenlegi verzió: {c}. Töltsd le az újat a kiadási oldaláról. - PdfCraft {c} is up to date. A PdfCraft {c} naprakész. - Couldn't check for updates: {e} A frissítések keresése nem sikerült: {e} - You have version {c}. All releases are listed at {page}. A jelenlegi verzió: {c}. Minden kiadás itt található: {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. A GitHubtól kéri le a legújabb kiadást. Semmi sem töltődik le vagy települ automatikusan. Download Letöltés Later Később diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 52b2dfbe4f..0eb02928f1 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -334,12 +334,10 @@ Check Again Periksa Lagi Check again Periksa lagi Check box is checked by default Kotak centang tercentang secara bawaan - Check for updates Periksa pembaruan Check spelling Periksa ejaan Check the pages Periksa halaman Check the pages of {name}: {why} Periksa halaman {name}: {why} Checking Options ({on} of 32 in all categories) Memeriksa Opsi ({on} dari 32 di semua kategori) - Checking for a newer version… Mencari versi yang lebih baru… Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Pilih gambar PNG atau JPEG (hingga 4 MiB). Transparansi dipertahankan. Choose a folder for the exported files Pilih folder untuk file yang diekspor Choose a folder for the extracted pages Pilih folder untuk halaman yang diekstrak @@ -398,7 +396,6 @@ Copies: Salinan: Copy selected text Salin teks yang dipilih Copy text Salin teks - Couldn't check for updates: {e} Tidak dapat memeriksa pembaruan: {e} Couldn't open the printer's preferences: {e} Tidak dapat membuka preferensi printer: {e} Counterclockwise 90 degrees 90 derajat berlawanan arah jarum jam Country/Region Negara/Wilayah @@ -859,7 +856,6 @@ Patterns (graphics objects) Pola (objek grafis) PdfCraft didn't shut down normally. These documents had changes that were autosaved: PdfCraft tidak berhenti secara normal. Dokumen-dokumen ini memiliki perubahan yang disimpan otomatis: PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. PdfCraft bekerja secara luring. Tidak ada telemetri, tidak ada akun, dan tidak ada pemrosesan awan kecuali Anda menambahkan penyedia. - PdfCraft {c} is up to date. PdfCraft {c} sudah terbaru. PdfCraft {v} is available. PdfCraft {v} tersedia. Percentage Persentase Permissions password Kata sandi izin @@ -1278,7 +1274,6 @@ XFA form: its fields and its XFA data are kept in step, so other viewers show what you fill in. Formulir XFA: kolom dan data XFA-nya dijaga tetap sinkron, sehingga penampil lain menampilkan yang Anda isi. You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Anda akan menerapkan 1 tanda sensor. Teks, gambar, dan gambar-gambar di bawah tanda tersebut, serta komentar dan kolom formulir yang bertumpang tindih dengannya, akan dihapus permanen. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Anda akan menerapkan {n} tanda sensor. Teks, gambar, dan gambar-gambar di bawah tanda tersebut, serta komentar dan kolom formulir yang bertumpang tindih dengannya, akan dihapus permanen. - You have version {c}. All releases are listed at {page}. Anda memiliki versi {c}. Semua rilis tercantum di {page}. You have version {c}. Download the new version from its release page. Anda memiliki versi {c}. Unduh versi baru dari halaman rilisnya. Your changes will be lost if you don't save them. Perubahan Anda akan hilang jika tidak Anda simpan. ZIP ZIP diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 3228312cca..a3dc999d6d 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -820,14 +820,9 @@ Copy Image in Original Format Copia immagine nel formato originale The image has been copied as a {ext} file L'immagine è stata copiata come file {ext} The page has changed; select the image again La pagina è cambiata; seleziona di nuovo l'immagine - Check for updates Verifica aggiornamenti No check has run yet. Nessuna verifica eseguita finora. - Checking for a newer version… Ricerca di una versione più recente… PdfCraft {v} is available. PdfCraft {v} è disponibile. You have version {c}. Download the new version from its release page. Hai la versione {c}. Scarica la nuova versione dalla sua pagina di rilascio. - PdfCraft {c} is up to date. PdfCraft {c} è aggiornato. - Couldn't check for updates: {e} Impossibile verificare gli aggiornamenti: {e} - You have version {c}. All releases are listed at {page}. Hai la versione {c}. Tutte le versioni sono elencate su {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Chiede a GitHub qual è l'ultima versione. Nulla viene scaricato o installato automaticamente. Download Scarica Later Più tardi diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 37ddfd4c58..5e64a1a8ce 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -824,14 +824,9 @@ Copy Image in Original Format 画像を元の形式でコピー The image has been copied as a {ext} file 画像を {ext} ファイルとしてコピーしました The page has changed; select the image again ページが変更されました。画像をもう一度選択してください - Check for updates アップデートを確認 No check has run yet. まだ確認していません。 - Checking for a newer version… 新しいバージョンを確認中… PdfCraft {v} is available. PdfCraft {v} が利用できます。 You have version {c}. Download the new version from its release page. 現在のバージョンは {c} です。リリースページから新しいバージョンをダウンロードしてください。 - PdfCraft {c} is up to date. PdfCraft {c} は最新です。 - Couldn't check for updates: {e} アップデートを確認できませんでした: {e} - You have version {c}. All releases are listed at {page}. 現在のバージョンは {c} です。すべてのリリースは {page} に掲載されています。 Asks GitHub for the latest release. Nothing is downloaded or installed automatically. GitHub で最新リリースを確認します。自動でダウンロードやインストールは行いません。 Download ダウンロード Later 後で diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index a28d532498..5c8e594759 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -780,14 +780,9 @@ Copy Image in Original Format Суретті бастапқы пішімде көшіру The image has been copied as a {ext} file Сурет {ext} файлы ретінде көшірілді The page has changed; select the image again Бет өзгерді; суретті қайта таңдаңыз - Check for updates Жаңартуларды тексеру No check has run yet. Тексеру әлі жүргізілген жоқ. - Checking for a newer version… Жаңа нұсқа бар ма екені тексерілуде… PdfCraft {v} is available. PdfCraft {v} қолжетімді. You have version {c}. Download the new version from its release page. Сізде {c} нұсқасы бар. Жаңа нұсқаны шығарылым бетінен жүктеп алыңыз. - PdfCraft {c} is up to date. PdfCraft {c} нұсқасы ең соңғы. - Couldn't check for updates: {e} Жаңартуларды тексеру мүмкін болмады: {e} - You have version {c}. All releases are listed at {page}. Сізде {c} нұсқасы бар. Барлық шығарылымдар {page} бетінде көрсетілген. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. GitHub-тан соңғы шығарылымды сұрайды. Ештеңе автоматты түрде жүктелмейді немесе орнатылмайды. Download Жүктеп алу Later Кейінірек diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index ecf2b20faa..1e461bbdff 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -760,14 +760,9 @@ Copy Image in Original Format 원본 형식으로 이미지 복사 The image has been copied as a {ext} file 이미지가 {ext} 파일로 복사되었습니다 The page has changed; select the image again 페이지가 변경되었으므로 이미지를 다시 선택하세요 - Check for updates 업데이트 확인 No check has run yet. 아직 확인이 실행되지 않았습니다. - Checking for a newer version… 새 버전이 있는지 확인하는 중… PdfCraft {v} is available. PdfCraft {v}을(를) 사용할 수 있습니다. You have version {c}. Download the new version from its release page. 현재 버전은 {c}입니다. 릴리스 페이지에서 새 버전을 다운로드하세요. - PdfCraft {c} is up to date. PdfCraft {c}은(는) 최신 버전입니다. - Couldn't check for updates: {e} 업데이트를 확인할 수 없습니다: {e} - You have version {c}. All releases are listed at {page}. 현재 버전은 {c}입니다. 모든 릴리스는 {page}에 표시됩니다. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. GitHub에 최신 릴리스를 요청합니다. 어떤 것도 자동으로 다운로드하거나 설치하지 않습니다. Download 다운로드 Later 나중에 diff --git a/crates/ui-egui/src/i18n/lo.tsv b/crates/ui-egui/src/i18n/lo.tsv index ca2d8bc7ea..c4d45c90d1 100644 --- a/crates/ui-egui/src/i18n/lo.tsv +++ b/crates/ui-egui/src/i18n/lo.tsv @@ -786,14 +786,9 @@ Copy Image in Original Format ສຳເນົາຮູບພາບໃນຮູບແບບຕົ້ນສະບັບ The image has been copied as a {ext} file ສຳເນົາຮູບພາບເປັນໄຟລ໌ {ext} ແລ້ວ The page has changed; select the image again ໜ້າປ່ຽນໄປແລ້ວ; ກະລຸນາເລືອກຮູບພາບອີກຄັ້ງ - Check for updates ກວດສອບການອັບເດດ No check has run yet. ຍັງບໍ່ທັນມີການກວດສອບເທື່ອ. - Checking for a newer version… ກຳລັງກວດສອບເວີຊັນໃໝ່… PdfCraft {v} is available. ມີ PdfCraft {v} ພ້ອມໃຫ້ໃຊ້ງານແລ້ວ. You have version {c}. Download the new version from its release page. ທ່ານກຳລັງໃຊ້ເວີຊັນ {c}. ດາວໂຫຼດເວີຊັນໃໝ່ໄດ້ຈາກໜ້າ release. - PdfCraft {c} is up to date. PdfCraft {c} ເປັນເວີຊັນລ່າສຸດແລ້ວ. - Couldn't check for updates: {e} ບໍ່ສາມາດກວດສອບການອັບເດດໄດ້: {e} - You have version {c}. All releases are listed at {page}. ທ່ານກຳລັງໃຊ້ເວີຊັນ {c}. ລາຍການ release ທັງໝົດແມ່ນສະແດງຢູ່ທີ່ {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. ກວດສອບລຸ້ນລ່າສຸດຈາກ GitHub. ຈະບໍ່ມີການດາວໂຫຼດ ຫຼື ຕິດຕັ້ງໂດຍອັດຕະໂນມັດ. Download ດາວໂຫຼດ Later ພາຍຫຼັງ diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index b2e588c48a..61fd9dc9a2 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -344,12 +344,10 @@ Check Again Tikrinti dar kartą Check again Tikrinti dar kartą Check box is checked by default Žymimasis langelis pažymėtas pagal numatytuosius parametrus - Check for updates Tikrinti, ar yra naujinimų Check spelling Tikrinti rašybą Check the pages Tikrinti puslapius Check the pages of {name}: {why} Tikrinti dokumento {name} puslapius: {why} Checking Options ({on} of 32 in all categories) Tikrinimo parinktys (visose kategorijose: {on} iš 32) - Checking for a newer version… Tikrinama, ar yra naujesnė versija… Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Pasirinkite PNG arba JPEG paveikslėlį (iki 4 MiB). Skaidrumas išsaugomas. Choose a folder for the exported files Pasirinkite aplanką eksportuojamiems failams Choose a folder for the extracted pages Pasirinkite aplanką išskirtiems puslapiams @@ -411,7 +409,6 @@ Copies: Kopijų skaičius: Copy selected text Kopijuoti pažymėtą tekstą Copy text Kopijuoti tekstą - Couldn't check for updates: {e} Nepavyko patikrinti, ar yra naujinimų: {e} Couldn't open the printer's preferences: {e} Nepavyko atverti spausdintuvo nuostatų: {e} Counterclockwise 90 degrees 90 laipsnių prieš laikrodžio rodyklę Country/Region Šalis / regionas @@ -916,7 +913,6 @@ Patterns (graphics objects) Šablonai (grafiniai objektai) PdfCraft didn't shut down normally. These documents had changes that were autosaved: „PdfCraft“ buvo uždaryta netinkamai. Šių dokumentų pakeitimai buvo automatiškai įrašyti: PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. „PdfCraft“ veikia neprisijungus. Nerenkami telemetrijos duomenys, nereikia paskyros ir nenaudojamos debesijos paslaugos, nebent pridėtumėte jų teikėją. - PdfCraft {c} is up to date. Naudojate naujausią „PdfCraft“ versiją {c}. PdfCraft {v} is available. Išleista „PdfCraft“ versija {v}. Percentage Procentai Permissions password Teisių slaptažodis @@ -1344,7 +1340,6 @@ XFA form: its fields and its XFA data are kept in step, so other viewers show what you fill in. XFA forma: jos laukai ir XFA duomenys sinchronizuojami, todėl kitose žiūryklėse rodoma tai, ką įvedate. You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Ketinate pritaikyti 1 šalinimo žymą. Po žymomis esantis tekstas, paveikslėliai ir brėžiniai, taip pat su jomis persidengiantys komentarai ir formos laukai bus pašalinti visam laikui. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Ketinate pritaikyti šalinimo žymų: {n}. Po žymomis esantis tekstas, paveikslėliai ir brėžiniai, taip pat su jomis persidengiantys komentarai ir formos laukai bus pašalinti visam laikui. - You have version {c}. All releases are listed at {page}. Turite versiją {c}. Visos laidos išvardytos čia: {page}. You have version {c}. Download the new version from its release page. Turite versiją {c}. Atsisiųskite naują versiją iš jos laidos puslapio. Your changes will be lost if you don't save them. Jei pakeitimų neįrašysite, jie bus prarasti. ZIP ZIP diff --git a/crates/ui-egui/src/i18n/nb.tsv b/crates/ui-egui/src/i18n/nb.tsv index c196fd2394..e81b84e341 100644 --- a/crates/ui-egui/src/i18n/nb.tsv +++ b/crates/ui-egui/src/i18n/nb.tsv @@ -341,12 +341,10 @@ Check Again Sjekk igjen Check again Sjekk igjen Check box is checked by default Avkrysningsboksen er avkrysset som standard - Check for updates Se etter oppdateringer Check spelling Stavekontroll Check the pages Kontroller sidene Check the pages of {name}: {why} Kontroller sidene i {name}: {why} Checking Options ({on} of 32 in all categories) Kontrollalternativer ({on} av 32 i alle kategorier) - Checking for a newer version… Ser etter en nyere versjon… Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Velg et PNG- eller JPEG-bilde (opptil 4 MiB). Gjennomsiktighet bevares. Choose a folder for the exported files Velg en mappe for de eksporterte filene Choose a folder for the extracted pages Velg en mappe for de uttrukne sidene @@ -406,7 +404,6 @@ Copies: Antall kopier: Copy selected text Kopier merket tekst Copy text Kopier tekst - Couldn't check for updates: {e} Kunne ikke se etter oppdateringer: {e} Couldn't open the printer's preferences: {e} Kunne ikke åpne skriverinnstillingene: {e} Counterclockwise 90 degrees 90 grader mot klokka Country/Region Land/region @@ -868,7 +865,6 @@ Patterns (graphics objects) Mønstre (grafikkobjekter) PdfCraft didn't shut down normally. These documents had changes that were autosaved: PdfCraft ble ikke avsluttet normalt. Disse dokumentene hadde endringer som ble autolagret: PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. PdfCraft fungerer uten nett. Ingen telemetri, ingen konto og ingen skybehandling med mindre du legger til en leverandør. - PdfCraft {c} is up to date. PdfCraft {c} er oppdatert. PdfCraft {v} is available. PdfCraft {v} er tilgjengelig. Percentage Prosent Permissions password Tillatelsespassord @@ -1289,7 +1285,6 @@ XFA form: its fields and its XFA data are kept in step, so other viewers show what you fill in. XFA-skjema: feltene og XFA-dataene holdes i takt, så andre visere viser det du fyller inn. You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Du er i ferd med å bruke 1 sladdemarkering. Tekst, bilder og tegninger under markeringene, og kommentarer og skjemafelt som overlapper dem, fjernes permanent. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Du er i ferd med å bruke {n} sladdemarkeringer. Tekst, bilder og tegninger under markeringene, og kommentarer og skjemafelt som overlapper dem, fjernes permanent. - You have version {c}. All releases are listed at {page}. Du har versjon {c}. Alle utgivelser er listet på {page}. You have version {c}. Download the new version from its release page. Du har versjon {c}. Last ned den nye versjonen fra utgivelsessiden. Your changes will be lost if you don't save them. Endringene dine går tapt hvis du ikke lagrer dem. ZIP ZIP diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index ae6463a156..203cad58d8 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -816,14 +816,9 @@ Copy Image in Original Format Kopiuj obraz w oryginalnym formacie The image has been copied as a {ext} file Obraz został skopiowany jako plik {ext} The page has changed; select the image again Strona się zmieniła; zaznacz obraz ponownie - Check for updates Sprawdź aktualizacje No check has run yet. Nie sprawdzono jeszcze aktualizacji. - Checking for a newer version… Sprawdzanie dostępności nowszej wersji… PdfCraft {v} is available. PdfCraft {v} jest dostępny. You have version {c}. Download the new version from its release page. Masz wersję {c}. Pobierz nową wersję ze strony wydania. - PdfCraft {c} is up to date. PdfCraft {c} jest aktualny. - Couldn't check for updates: {e} Nie można sprawdzić aktualizacji: {e} - You have version {c}. All releases are listed at {page}. Masz wersję {c}. Wszystkie wydania są dostępne na {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Sprawdza najnowsze wydanie na GitHubie. Nic nie jest pobierane ani instalowane automatycznie. Download Pobierz Later Później diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 70309ffc15..45f827171b 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -441,14 +441,9 @@ Copy Image in Original Format Copiar imagem no formato original The image has been copied as a {ext} file A imagem foi copiada como arquivo {ext} The page has changed; select the image again A página mudou; selecione a imagem novamente - Check for updates Verificar atualizações No check has run yet. Nenhuma verificação foi executada ainda. - Checking for a newer version… Verificando se há uma versão mais recente… PdfCraft {v} is available. O PdfCraft {v} está disponível. You have version {c}. Download the new version from its release page. Você tem a versão {c}. Baixe a nova versão na página de lançamentos. - PdfCraft {c} is up to date. O PdfCraft {c} está atualizado. - Couldn't check for updates: {e} Não foi possível verificar atualizações: {e} - You have version {c}. All releases are listed at {page}. Você tem a versão {c}. Todos os lançamentos estão listados em {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Consulta o GitHub para obter o lançamento mais recente. Nada é baixado ou instalado automaticamente. Download Baixar Later Mais tarde diff --git a/crates/ui-egui/src/i18n/ro.tsv b/crates/ui-egui/src/i18n/ro.tsv index f8a2deb83e..6b23515b8e 100644 --- a/crates/ui-egui/src/i18n/ro.tsv +++ b/crates/ui-egui/src/i18n/ro.tsv @@ -342,12 +342,10 @@ Check Again Verificare din nou Check again Verificare din nou Check box is checked by default Caseta de selectare este bifată implicit - Check for updates Căutare actualizări Check spelling Verificare ortografică Check the pages Verificați paginile Check the pages of {name}: {why} Verificați paginile din {name}: {why} Checking Options ({on} of 32 in all categories) Opțiuni de verificare ({on} din 32 în toate categoriile) - Checking for a newer version… Se caută o versiune mai nouă… Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Alegeți o imagine PNG sau JPEG (până la 4 MiB). Transparența se păstrează. Choose a folder for the exported files Alegeți un folder pentru fișierele exportate Choose a folder for the extracted pages Alegeți un folder pentru paginile extrase @@ -407,7 +405,6 @@ Copies: Copii: Copy selected text Copiere text selectat Copy text Copiere text - Couldn't check for updates: {e} Nu s-au putut căuta actualizări: {e} Couldn't open the printer's preferences: {e} Nu s-au putut deschide preferințele imprimantei: {e} Counterclockwise 90 degrees 90 de grade în sens invers acelor de ceasornic Country/Region Țară/regiune @@ -868,7 +865,6 @@ Patterns (graphics objects) Modele (obiecte grafice) PdfCraft didn't shut down normally. These documents had changes that were autosaved: PdfCraft nu s-a închis normal. Aceste documente aveau modificări salvate automat: PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. PdfCraft funcționează offline. Fără telemetrie, fără cont și fără procesare în cloud, cu excepția cazului în care adăugați un furnizor. - PdfCraft {c} is up to date. PdfCraft {c} este la zi. PdfCraft {v} is available. Este disponibil PdfCraft {v}. Percentage Procent Permissions password Parolă de permisiuni @@ -1287,7 +1283,6 @@ XFA form: its fields and its XFA data are kept in step, so other viewers show what you fill in. Formular XFA: câmpurile și datele XFA sunt sincronizate, ca alte vizualizatoare să arate ce completați. You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Urmează să aplicați 1 marcaj de anonimizare. Textul, imaginile și desenele de sub marcaje, precum și comentariile și câmpurile de formular care se suprapun cu ele, se elimină definitiv. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Urmează să aplicați {n} marcaje de anonimizare. Textul, imaginile și desenele de sub marcaje, precum și comentariile și câmpurile de formular care se suprapun cu ele, se elimină definitiv. - You have version {c}. All releases are listed at {page}. Aveți versiunea {c}. Toate versiunile sunt listate la {page}. You have version {c}. Download the new version from its release page. Aveți versiunea {c}. Descărcați versiunea nouă de pe pagina ei de lansare. Your changes will be lost if you don't save them. Modificările se vor pierde dacă nu le salvați. ZIP ZIP diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 87042f7bd9..59c483526a 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -817,14 +817,9 @@ Copy Image in Original Format Копировать изображение в исходном формате The image has been copied as a {ext} file Изображение скопировано как файл {ext} The page has changed; select the image again Страница изменилась; выберите изображение снова - Check for updates Проверить обновления No check has run yet. Проверка ещё не выполнялась. - Checking for a newer version… Проверка новой версии… PdfCraft {v} is available. Доступна PdfCraft {v}. You have version {c}. Download the new version from its release page. У вас версия {c}. Скачайте новую версию со страницы релизов. - PdfCraft {c} is up to date. У вас последняя версия PdfCraft {c}. - Couldn't check for updates: {e} Не удалось проверить обновления: {e} - You have version {c}. All releases are listed at {page}. У вас версия {c}. Все релизы перечислены на {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Запрашивает у GitHub последний релиз. Ничего не скачивается и не устанавливается автоматически. Download Скачать Later Позже diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index 57d31bd66e..83cb594158 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -343,7 +343,6 @@ Check box is checked by default Potrditveno polje je privzeto označeno Check for accessibility Preveri dostopnost Check for accessibility… Preveri dostopnost… - Check for updates Preveri posodobitve Check for updates… Preveri posodobitve… Check spelling Preveri črkovanje Check that the tags read in the order a sighted reader would. Use the Reading Order tool or the Tags panel to correct it. Preverite, ali se oznake berejo v vrstnem redu, v katerem bi jih bral bralec. Za popravek uporabite orodje Vrstni red branja ali razdelek Oznake. @@ -352,7 +351,6 @@ Checkbox Potrditveno polje Checked Označeno Checking Options ({on} of 32 in all categories) Možnosti preverjanja ({on} od 32 v vseh kategorijah) - Checking for a newer version… Preverjanje za novejšo različico… Checkmark Kljukica Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Izberite sliko PNG ali JPEG (do 4 MiB). Prosojnost je ohranjena. Choose a folder for the exported files Izberite mapo za izvožene datoteke @@ -457,7 +455,6 @@ Correct recognized text Popravi prepoznano besedilo Could not save: {e} Shranjevanje ni uspelo: {e} Couldn't add {name}: {e} Datoteke {name} ni bilo mogoče dodati: {e} - Couldn't check for updates: {e} Posodobitev ni bilo mogoče preveriti: {e} Couldn't combine files: {e} Datotek ni bilo mogoče združiti: {e} Couldn't create a PDF: {e} PDF-ja ni bilo mogoče ustvariti: {e} Couldn't download {name}: {e} Datoteke {name} ni bilo mogoče prenesti: {e} @@ -1368,7 +1365,6 @@ PdfCraft on GitHub PdfCraft na GitHubu PdfCraft web page Spletna stran PdfCraft PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. PdfCraft deluje brez povezave. Brez telemetrije, brez računa in brez obdelave v oblaku, razen če dodate ponudnika. - PdfCraft {c} is up to date. PdfCraft {c} je posodobljen. PdfCraft {v} is available. Na voljo je PdfCraft {v}. People may need more time to respond. Check that nothing has a time limit. Ljudje bodo morda potrebovali več časa za odziv. Preverite, da nič nima časovne omejitve. Percentage Odstotek @@ -2063,7 +2059,6 @@ You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Uveljavili boste 1 oznako za počrnitev. Besedilo, slike in risbe pod oznakami ter komentarji in polja obrazca, ki se z njimi prekrivajo, bodo trajno odstranjeni. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Uveljavili boste oznake za počrnitev: {n}. Besedilo, slike in risbe pod oznakami ter komentarji in polja obrazca, ki se z njimi prekrivajo, bodo trajno odstranjeni. You can pin up to {n} folders. Unpin one first. Pripnete lahko do {n} map. Najprej eno odpnite. - You have version {c}. All releases are listed at {page}. Imate različico {c}. Vse izdaje so navedene na {page}. You have version {c}. Download the new version from its release page. Imate različico {c}. Novo različico prenesite z njene strani izdaje. Your changes will be lost if you don't save them. Vaše spremembe bodo izgubljene, če jih ne shranite. Your name Vaše ime diff --git a/crates/ui-egui/src/i18n/sq.tsv b/crates/ui-egui/src/i18n/sq.tsv index 4b1769c0f3..e5a8f5b037 100644 --- a/crates/ui-egui/src/i18n/sq.tsv +++ b/crates/ui-egui/src/i18n/sq.tsv @@ -337,7 +337,6 @@ Check box is checked by default Kutia e zgjedhjes është e zgjedhur si parazgjedhje Check for accessibility Kontrollo qasshmërinë Check for accessibility… Kontrollo qasshmërinë… - Check for updates Kontrollo për përditësime Check for updates… Kontrollo për përditësime… Check spelling Kontrollo drejtshkrimin Check that the tags read in the order a sighted reader would. Use the Reading Order tool or the Tags panel to correct it. Kontrolloni që etiketat lexohen në radhën që do t'i lexonte një lexues që sheh. Përdorni mjetin e radhës së leximit ose panelin e etiketave për ta korrigjuar. @@ -346,7 +345,6 @@ Checkbox Kuti zgjedhjeje Checked E zgjedhur Checking Options ({on} of 32 in all categories) Opsionet e kontrollit ({on} nga 32 në të gjitha kategoritë) - Checking for a newer version… Duke kontrolluar për një version më të ri… Checkmark Shenjë vlefshmërie Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Zgjidhni një imazh PNG ose JPEG (deri në 4 MiB). Transparenca ruhet. Choose a folder for the exported files Zgjidhni një dosje për skedarët e eksportuar @@ -450,7 +448,6 @@ Correct recognized text Korrigjo tekstin e njohur Could not save: {e} Nuk u ruajt: {e} Couldn't add {name}: {e} Nuk u shtua {name}: {e} - Couldn't check for updates: {e} Nuk u kontrollua për përditësime: {e} Couldn't combine files: {e} Nuk u bashkuan skedarët: {e} Couldn't create a PDF: {e} Nuk u krijua dot një PDF: {e} Couldn't download {name}: {e} Nuk u shkarkua {name}: {e} @@ -1317,7 +1314,6 @@ PdfCraft on GitHub PdfCraft në GitHub PdfCraft web page Faqja ueb e PdfCraft PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. PdfCraft funksionon pa internet. Pa telemetri, pa llogari dhe pa përpunim në re, përveçse nëse shton një ofrues. - PdfCraft {c} is up to date. PdfCraft {c} është i përditësuar. PdfCraft {v} is available. PdfCraft {v} është i disponueshëm. People may need more time to respond. Check that nothing has a time limit. Njerëzit mund të kenë nevojë për më shumë kohë për të përgjigjur. Kontrollo që asgjë të mos ketë kufi kohe. Percentage Përqindja @@ -2002,7 +1998,6 @@ You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Jeni gati të zbatoni 1 shenjë redaktimi. Teksti, imazhet dhe vizatimet nën shenjat, si dhe komentet dhe fushat e formularit që mbivendosen me to, hiqen përgjithmonë. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Jeni gati të zbatoni {n} shenja redaktimi. Teksti, imazhet dhe vizatimet nën shenjat, si dhe komentet dhe fushat e formularit që mbivendosen me to, hiqen përgjithmonë. You can pin up to {n} folders. Unpin one first. Mund të fiksoni deri në {n} dosje. Hiqni fiksimin e njërës më parë. - You have version {c}. All releases are listed at {page}. Keni versionin {c}. Të gjitha botimet janë të listuara te {page}. You have version {c}. Download the new version from its release page. Keni versionin {c}. Shkarkoni versionin e ri nga faqja e botimit. Your changes will be lost if you don't save them. Ndryshimet tuaja do të humbasin nëse nuk i ruani. Your name Emri juaj diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index 5b4ba64345..0bb56f4793 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -798,14 +798,9 @@ comment menu Edit సవరించు Copy Image in Original Format చిత్రాన్ని అసలు ఫార్మాట్‌లో కాపీ చేయి The image has been copied as a {ext} file చిత్రం {ext} ఫైల్‌గా కాపీ చేయబడింది The page has changed; select the image again పేజీ మారింది; చిత్రాన్ని మళ్లీ ఎంచుకోండి - Check for updates అప్‌డేట్‌ల కోసం తనిఖీ చేయి No check has run yet. ఇంకా తనిఖీ జరగలేదు. - Checking for a newer version… కొత్త వెర్షన్ కోసం తనిఖీ చేస్తోంది… PdfCraft {v} is available. PdfCraft {v} అందుబాటులో ఉంది. You have version {c}. Download the new version from its release page. మీ వద్ద వెర్షన్ {c} ఉంది. కొత్త వెర్షన్‌ను దాని విడుదల పేజీ నుండి డౌన్‌లోడ్ చేసుకోండి. - PdfCraft {c} is up to date. PdfCraft {c} తాజా వెర్షన్. - Couldn't check for updates: {e} అప్‌డేట్‌ల కోసం తనిఖీ చేయడం సాధ్యం కాలేదు: {e} - You have version {c}. All releases are listed at {page}. మీ వద్ద వెర్షన్ {c} ఉంది. అన్ని విడుదలల జాబితా: {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. తాజా విడుదల గురించి GitHubను అడుగుతుంది. ఏదీ ఆటోమేటిక్‌గా డౌన్‌లోడ్ లేదా ఇన్‌స్టాల్ చేయబడదు. Download డౌన్‌లోడ్ చేయి Later తర్వాత diff --git a/crates/ui-egui/src/i18n/th.tsv b/crates/ui-egui/src/i18n/th.tsv index 71c7d38fe8..0c2b58a812 100644 --- a/crates/ui-egui/src/i18n/th.tsv +++ b/crates/ui-egui/src/i18n/th.tsv @@ -771,14 +771,9 @@ Copy Image in Original Format คัดลอกรูปภาพในรูปแบบเดิม The image has been copied as a {ext} file คัดลอกรูปภาพเป็นไฟล์ {ext} แล้ว The page has changed; select the image again หน้ามีการเปลี่ยนแปลง โปรดเลือกรูปภาพอีกครั้ง - Check for updates ตรวจหาการอัปเดต No check has run yet. ยังไม่ได้ตรวจสอบ - Checking for a newer version… กำลังตรวจหารุ่นที่ใหม่กว่า… PdfCraft {v} is available. มี PdfCraft {v} ให้ใช้งานแล้ว You have version {c}. Download the new version from its release page. คุณใช้รุ่น {c} ดาวน์โหลดรุ่นใหม่ได้จากหน้ารุ่นที่เผยแพร่ - PdfCraft {c} is up to date. PdfCraft {c} เป็นรุ่นล่าสุดแล้ว - Couldn't check for updates: {e} ตรวจหาการอัปเดตไม่ได้: {e} - You have version {c}. All releases are listed at {page}. คุณใช้รุ่น {c} รายการรุ่นทั้งหมดอยู่ที่ {page} Asks GitHub for the latest release. Nothing is downloaded or installed automatically. ถาม GitHub ว่ามีรุ่นล่าสุดหรือไม่ จะไม่มีการดาวน์โหลดหรือติดตั้งโดยอัตโนมัติ Download ดาวน์โหลด Later ภายหลัง diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f76f7f5cd0..b52688606b 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -779,14 +779,9 @@ Copy Image in Original Format Resmi Özgün Biçimde Kopyala The image has been copied as a {ext} file Resim {ext} dosyası olarak kopyalandı The page has changed; select the image again Sayfa değişti; resmi yeniden seçin - Check for updates Güncellemeleri denetle No check has run yet. Henüz denetim yapılmadı. - Checking for a newer version… Daha yeni bir sürüm denetleniyor… PdfCraft {v} is available. PdfCraft {v} kullanılabilir. You have version {c}. Download the new version from its release page. {c} sürümüne sahipsiniz. Yeni sürümü sürüm sayfasından indirin. - PdfCraft {c} is up to date. PdfCraft {c} güncel. - Couldn't check for updates: {e} Güncellemeler denetlenemedi: {e} - You have version {c}. All releases are listed at {page}. {c} sürümüne sahipsiniz. Tüm sürümler {page} adresinde listelenmiştir. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. En son sürüm için GitHub'ı sorgular. Hiçbir şey otomatik olarak indirilmez veya kurulmaz. Download İndir Later Daha sonra diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index eb7bb2ec4b..dc65faf7a2 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -821,14 +821,9 @@ Copy Image in Original Format Копіювати зображення в оригінальному форматі The image has been copied as a {ext} file Зображення скопійовано як файл {ext} The page has changed; select the image again Сторінка змінилася; виберіть зображення ще раз - Check for updates Перевірити оновлення No check has run yet. Перевірку ще не виконано. - Checking for a newer version… Перевірка наявності новішої версії… PdfCraft {v} is available. Доступна версія PdfCraft {v}. You have version {c}. Download the new version from its release page. У вас версія {c}. Завантажте нову версію зі сторінки випуску. - PdfCraft {c} is up to date. Ваша версія PdfCraft {c} актуальна. - Couldn't check for updates: {e} Не вдалося перевірити оновлення: {e} - You have version {c}. All releases are listed at {page}. У вас версія {c}. Усі випуски наведено на {page}. Asks GitHub for the latest release. Nothing is downloaded or installed automatically. Запитує в GitHub найновіший випуск. Нічого не завантажується й не встановлюється автоматично. Download Завантажити Later Пізніше diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 860884ae48..e9a9a73f2b 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -342,12 +342,10 @@ theme Appearance Giao diện Check Again Kiểm tra lại Check again Kiểm tra lại Check box is checked by default Hộp kiểm được chọn theo mặc định - Check for updates Kiểm tra bản cập nhật Check spelling Kiểm tra chính tả Check the pages Kiểm tra các trang Check the pages of {name}: {why} Kiểm tra các trang của {name}: {why} Checking Options ({on} of 32 in all categories) Tùy chọn kiểm tra ({on} trên 32 ở tất cả danh mục) - Checking for a newer version… Đang kiểm tra phiên bản mới hơn… Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Chọn ảnh PNG hoặc JPEG (tối đa 4 MiB). Độ trong suốt được giữ nguyên. Choose a folder for the exported files Chọn thư mục cho các tệp đã xuất Choose a folder for the extracted pages Chọn thư mục cho các trang đã trích xuất @@ -408,7 +406,6 @@ theme Appearance Giao diện Copies: Số bản sao: Copy selected text Sao chép văn bản đã chọn Copy text Sao chép văn bản - Couldn't check for updates: {e} Không thể kiểm tra bản cập nhật: {e} Couldn't open the printer's preferences: {e} Không thể mở tùy chọn của máy in: {e} Counterclockwise 90 degrees Ngược chiều kim đồng hồ 90 độ Country/Region Quốc gia/Khu vực @@ -910,7 +907,6 @@ theme Appearance Giao diện Patterns (graphics objects) Họa tiết (đối tượng đồ họa) PdfCraft didn't shut down normally. These documents had changes that were autosaved: PdfCraft không đóng bình thường. Các tài liệu này có thay đổi đã được tự động lưu: PdfCraft works offline. No telemetry, no account, and no cloud processing unless you add a provider. PdfCraft hoạt động ngoại tuyến. Không thu thập dữ liệu sử dụng, không cần tài khoản, và không xử lý đám mây trừ khi bạn thêm nhà cung cấp. - PdfCraft {c} is up to date. PdfCraft {c} đã là bản mới nhất. PdfCraft {v} is available. PdfCraft {v} đã có sẵn. Percentage Phần trăm Permissions password Mật khẩu quyền hạn @@ -1333,7 +1329,6 @@ theme Appearance Giao diện XFA form: its fields and its XFA data are kept in step, so other viewers show what you fill in. Biểu mẫu XFA: các trường và dữ liệu XFA của nó được giữ đồng bộ, nên các trình xem khác hiển thị những gì bạn điền. You are about to apply 1 redaction mark. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Bạn sắp áp dụng 1 dấu xóa. Văn bản, hình ảnh và bản vẽ nằm dưới các dấu, cũng như bình luận và trường biểu mẫu chồng lên chúng, sẽ bị xóa vĩnh viễn. You are about to apply {n} redaction marks. Text, images and drawings under the marks, and comments and form fields that overlap them, are removed permanently. Bạn sắp áp dụng {n} dấu xóa. Văn bản, hình ảnh và bản vẽ nằm dưới các dấu, cũng như bình luận và trường biểu mẫu chồng lên chúng, sẽ bị xóa vĩnh viễn. - You have version {c}. All releases are listed at {page}. Bạn đang dùng phiên bản {c}. Tất cả các bản phát hành được liệt kê tại {page}. You have version {c}. Download the new version from its release page. Bạn đang dùng phiên bản {c}. Hãy tải phiên bản mới từ trang phát hành của nó. Your changes will be lost if you don't save them. Các thay đổi của bạn sẽ bị mất nếu bạn không lưu. ZIP ZIP diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 995acc78e6..5f08576b8e 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -492,14 +492,9 @@ Copy Image in Original Format 以原始格式复制图像 The image has been copied as a {ext} file 图像已复制为 {ext} 文件 The page has changed; select the image again 页面已更改;请重新选择图像 - Check for updates 检查更新 No check has run yet. 尚未运行检查。 - Checking for a newer version… 正在检查新版本… PdfCraft {v} is available. PdfCraft {v} 可用。 You have version {c}. Download the new version from its release page. 当前版本为 {c}。请从其发布页面下载新版本。 - PdfCraft {c} is up to date. PdfCraft {c} 已是最新。 - Couldn't check for updates: {e} 无法检查更新:{e} - You have version {c}. All releases are listed at {page}. 当前版本为 {c}。所有版本列在 {page}。 Asks GitHub for the latest release. Nothing is downloaded or installed automatically. 向 GitHub 查询最新版本。不会自动下载或安装任何内容。 Download 下载 Later 稍后 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 79b67e6e70..2570f12f1c 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -445,14 +445,9 @@ Copy Image in Original Format 以原始格式複製圖片 The image has been copied as a {ext} file 圖片已複製為 {ext} 檔案 The page has changed; select the image again 頁面已變更;請重新選取圖片 - Check for updates 檢驗更新 No check has run yet. 尚未執行檢驗。 - Checking for a newer version… 正在檢驗新版本… PdfCraft {v} is available. PdfCraft {v} 可用。 You have version {c}. Download the new version from its release page. 目前版本為 {c}。請從其釋出頁面下載新版本。 - PdfCraft {c} is up to date. PdfCraft {c} 已是最新。 - Couldn't check for updates: {e} 無法檢驗更新:{e} - You have version {c}. All releases are listed at {page}. 目前版本為 {c}。所有版本列在 {page}。 Asks GitHub for the latest release. Nothing is downloaded or installed automatically. 向 GitHub 搜尋最新版本。不會自動下載或安裝任何內容。 Download 下載 Later 稍後 diff --git a/crates/ui-egui/src/platform/updates.rs b/crates/ui-egui/src/platform/updates.rs index 7ffebdcbc0..6b7ea291fa 100644 --- a/crates/ui-egui/src/platform/updates.rs +++ b/crates/ui-egui/src/platform/updates.rs @@ -1,11 +1,13 @@ //! Help ▸ Check for updates and the check at start, through `craft_update`: the flow itself is its -//! `Updater`; this module supplies the suite, the quit path and the status dialog. +//! `Updater`; this module supplies the suite and the quit path, and shows the result in +//! `craft_update`'s `CheckResultDialog`. #[cfg(not(target_arch = "wasm32"))] use craft_update::updater::{Action, CheckSource, Status, Updater}; -use egui::{Align, Layout}; +#[cfg(not(target_arch = "wasm32"))] +use craft_update::{CheckResultDialog, CheckResultResponse}; -use crate::{PdfCraftApp, theme, widgets}; +use crate::PdfCraftApp; /// Where every PdfCraft release is listed. pub const RELEASES_PAGE: &str = "https://github.com/storytold/pdfcraft/releases"; @@ -99,63 +101,18 @@ pub(crate) fn dialog(app: &mut PdfCraftApp, ctx: &egui::Context) { if app.updates.open && !app.updates.check_requested && matches!(app.updates.updater.status(), Status::Idle) { app.updates.open = false; } - } - if !app.updates.open { - return; - } - let t = theme::Tokens::get(ctx); - let current = env!("CARGO_PKG_VERSION"); - let mut close = false; - let modal = crate::widgets::show_modal(ctx, "updates", |ui| { - ui.set_width(420.0); - ui.horizontal(|ui| { - ui.add(crate::icons::image("cloud", 22.0, t.accent)); - ui.label(egui::RichText::new(tl!("Check for updates")).font(theme::semibold(16.0))); - }); - ui.add_space(8.0); - #[cfg(not(target_arch = "wasm32"))] - match app.updates.updater.status() { - Status::Idle => { - ui.horizontal(|ui| { - ui.spinner(); - ui.label(tl!("Checking for a newer version…")); - }); - } - Status::Checking => { - ui.horizontal(|ui| { - ui.spinner(); - ui.label(tl!("Checking for a newer version…")); - }); - } - Status::UpToDate => { - ui.label(crate::i18n::fmt(tl!("PdfCraft {c} is up to date."), &[("c", current)])); - } - Status::Failed(e) => { - ui.label(crate::i18n::fmt(tl!("Couldn't check for updates: {e}"), &[("e", e)])); - ui.label( - egui::RichText::new(crate::i18n::fmt( - tl!("You have version {c}. All releases are listed at {page}."), - &[("c", current), ("page", RELEASES_PAGE)], - )) - .color(t.text_muted), - ); + if app.updates.open { + let dialog = CheckResultDialog::new(&crate::UPDATE_APP, env!("CARGO_PKG_VERSION")); + match dialog.show(ctx, app.updates.updater.status()) { + CheckResultResponse::None => {} + CheckResultResponse::Close => { + app.updates.open = false; + app.updates.updater.dismiss_status(); + } + CheckResultResponse::CheckAgain => app.updates.check_requested = true, } } - #[cfg(target_arch = "wasm32")] - let _ = (&t, current); - ui.add_space(10.0); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if widgets::pill_button(ui, tl!("Close"), true).clicked() { - close = true; - } - }); - }); - if modal.should_close() { - close = true; - } - if close { - app.updates.open = false; - #[cfg(not(target_arch = "wasm32"))] - app.updates.updater.dismiss_status(); } + #[cfg(target_arch = "wasm32")] + let _ = (app, ctx); } diff --git a/crates/ui-egui/tests/it/updates.rs b/crates/ui-egui/tests/it/updates.rs index d2eaa7c660..3ea55e434e 100644 --- a/crates/ui-egui/tests/it/updates.rs +++ b/crates/ui-egui/tests/it/updates.rs @@ -38,7 +38,7 @@ fn settle(h: &mut Harness<'static, PdfCraftApp>) { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30); loop { h.run_steps(2); - if h.query_by_label_contains("Checking for a newer version").is_none() { + if h.query_by_label_contains("Checking for updates").is_none() { break; } assert!(std::time::Instant::now() < deadline, "timed out waiting for the update check"); @@ -64,13 +64,14 @@ fn an_up_to_date_or_failed_check_says_so() { let mut h = harness(Ok(None)); h.state_mut().execute("help.check_updates"); settle(&mut h); - h.get_by_label_contains("is up to date"); + h.get_by_label_contains("is the newest version available"); assert!(h.query_by_label("Download").is_none()); let mut h = harness(Err("couldn't reach GitHub")); h.state_mut().execute("help.check_updates"); settle(&mut h); - h.get_by_label_contains("Couldn't check for updates: couldn't reach GitHub"); + h.get_by_label_contains("Could not check for updates."); + h.get_by_label_contains("couldn't reach GitHub"); } #[test]