Print: architectural paper presets and PDF scale controls (#599 follow-up) (#1123)

* M10.5: Restore architectural paper presets and PDF scale controls

Port the missing paper catalog and scale controls from PR #599 onto the merged Windows printing implementation. Keep the spooler and background discovery from #847/#1007. Document the feature-by-feature audit and verification limits.

Assisted-by: OpenAI Codex

* Drop the provenance write-up and the screenshot example (the kittest covers the dialog)

* Refresh catalog hashes

---------

Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
durtyrobbinandBrandon Thomas authored and GitHub committed 2026-10-11 14:49:32 -04:00
1 parent a3753c52f9
commit f036d3841b
23 files changed
+293 -22

No files matched your search

+11 -11
View File
@@ -19,7 +19,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "7936028709844d28d14489d608810480a1af5a707e883032161bc2fe95ff5d67"
sha256 = "fd1660ea192c41ec38d11344035a5d2a5f453479c43a1f0bb56b65919fa5073d"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "781ac07860770313bc2a74cb1e7b312a5e027f3299d137c84d799829345f1eee"
sha256 = "7d9b83a8f699b37e191849d79e0f116dff45486d8fccef50bd4b20c8172f95b0"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "cec6518ba35fdd26669c200283b3a46e908f2ba940f6917b414bf1f68bcb19ce"
sha256 = "111430ed0180b24a65f6d2b76e6bbc51b88d11c81f0da0e6d0975744584f7ad4"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "6bb45afb959816bc6c69a1b262d568d49fe8c9a3dcec8a321231196b0831251f"
sha256 = "cf5866438a696013303886a4a60de3348f5acb5acc4f97ec6889f5759c785a04"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "771b72fbed8f7ef9a5689fd84322b2feb27e36295f7f0228f755fee678c34774"
sha256 = "5564af1f42d201c1d2e2d14a77c50eff19c254f5ef9e03165552c16f6806a35b"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "3b37271fc6aed595b01ab42fd39cefe3dfd0e5e0e25905620590d9be5bc29c02"
sha256 = "f1d9178f5c73fff154c7408c01ee26a12cb0c76b614a4e16e0dd6a27b6bacb4e"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "8e6549bb81c37757822776e3f8f46b3479688bd950508c9c311a3723a1a7b173"
sha256 = "00473565c3545c1fe86aff596c57e004eb25ae914d831f628b57ec08113f03d8"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "8d8ec98406f3d8b44766c3daad04af5beac39a5e69cceedfb1b881bd20457023"
sha256 = "a38c9276670bfd292358d021ef55d12b17f944f9236180a068d55f59d454957d"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "b995a67d0fdac2b2d567e2f37d2688a31e5a6222ffca59a95c304c7f331e18ad"
sha256 = "c6391eb0e85439a8473eb152ab58e817831e8ad48ec6c4a54315126cf4a46ba2"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "32f471f14b9f3cff21d57a3f69336717c697f8cd6bf502c7aa01591d69c42e90"
sha256 = "1d2f66915cde08456309a2ca726257e6b025e56bcc49377d7cd3fc095d294a2f"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d46933f539a253fce67e81c06b6bf9d295cc7805cd36842c9d189fcc57b17950"
sha256 = "7551eba21c7bfdba960684711e1580c32059f5620a02903898441058d1b93ed3"
[[asset]]
path = "assets/icons/accessibility.svg"
+3
View File
@@ -2,6 +2,7 @@
**Stage: alpha** · next: beta, ~26% (ready for real work 49% → 75%) and ~400–700 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (architectural paper controls from #599; localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** M14 #307 Plan 3 uses one marker traversal on open and full refresh, and the combined scanner borrows metadata instead of cloning scan-only containers; includes a behavior-preserving `organize` binding for the workspace Clippy gate · earlier: minor (localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (closed issues synced: #774, #756 and the #799–#821 batch; earlier: localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
@@ -134,6 +135,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
## Progress log
- **2026-10-11 (M10.5, focused follow-up to #599):** Carry the architectural/ANSI/large ISO paper presets, current-page matching at 100%, and previewed PDF scale into the current print dialog and `doc_print`. Retains the #847/#1007 spooler and background discovery. Driver substitution, physical calibration, vector Windows printing and managed-endpoint restrictions remain separate gaps. Overall estimates unchanged.
Newest first: the date, what moved, and the new overall estimate.
- **2026-10-11 (M14, #307 Plan 3):** Engine open and full refresh use one page/content traversal to populate marks and added content. The independent public scans remain available; the combined scanner borrows direct objects and holds referenced objects through `Arc<Object>` instead of cloning scan-only `/Contents` arrays or `/PCAdded` dictionaries. Differential tests cover an indirect `/PCAdded` dictionary as well as ordering, malformed/direct/indirect/repeated entries and `/UserUnit`. Exact `bbbe600`→final release CLI measurements used 10 alternating pairs: wall-time medians were 183.645→174.440 ms on 10,000 marker-free pages (10/10 wins), 33.587→32.752 ms on 1,500 mixed pages (8/10), and 24.331→20.133 ms on the 100,000-entry array stress fixture (10/10). The stress fixture's maximum-RSS median fell 28,655,616→19,038,208 B; peak-footprint medians were 14,747,136→14,673,408 B. From P0, the follow-up stress run was 20.886→20.201 ms (10/10), with RSS 23,953,408→19,087,360 B and peak footprint 14,706,144→14,697,952 B. `doc_open` stdout hashes matched every pair. The ancillary `organize` binding preserves the prior `prune_legacy` update condition. Estimates unchanged.
+8 -2
View File
@@ -105,9 +105,15 @@ impl Automation {
None => PAPERS[0].1,
Some(p) => PAPERS
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(p) || n.replace("US ", "").eq_ignore_ascii_case(p))
.find(|(n, _)| {
n.eq_ignore_ascii_case(p)
|| n.replace("US ", "").eq_ignore_ascii_case(p)
|| n.split(" (").next().is_some_and(|name| name.eq_ignore_ascii_case(p))
})
.map(|x| x.1)
.ok_or_else(|| bad(format!("unknown paper {p:?} (Letter, Legal, Tabloid, A3, A4, A5)")))?,
.ok_or_else(|| {
bad(format!("unknown paper {p:?}; choose one of {}", PAPERS.iter().map(|(name, _)| *name).collect::<Vec<_>>().join(", ")))
})?,
};
let settings = print::Settings { pages, paper, orientation, layout, content };
let sizes: Vec<(f64, f64)> = self.doc(a)?.info.pages.iter().map(|p| (p.width as f64, p.height as f64)).collect();
+1 -1
View File
@@ -461,7 +461,7 @@ pub fn tools() -> Vec<ToolDef> {
t(
"doc_print",
"Print",
"Print with Acrobat's Print dialog options, or save the print-ready PDF. pages: a range such as \"1-3, 6, 9-\" (page labels allowed; default all); subset odd/even; reverse. layout: fit (default), actual, shrink, custom (scale %), multiple (per_sheet 2/4/6/9/16, order, border, auto_rotate; cut-stack order arranges single-sided sheets for cutting into piles and stacking left to right, top to bottom, keeping sheet order within each pile; duplex must be off), booklet (booklet_subset both/front/back, binding left/right), poster (scale %, overlap pt, cut_marks). orientation auto/portrait/landscape; comments_forms document / document-and-markups (default) / document-and-stamps / form-fields-only; paper Letter/Legal/Tabloid/A3/A4/A5. Then path (save) or printer (a name or \"default\") with copies, collate, duplex off/long-edge/short-edge, grayscale, and options (driver options from printer_options, CUPS).",
"Print with Acrobat's Print dialog options, or save the print-ready PDF. pages: a range such as \"1-3, 6, 9-\" (page labels allowed; default all); subset odd/even; reverse. layout: fit (default), actual, shrink, custom (scale %), multiple (per_sheet 2/4/6/9/16, order, border, auto_rotate; cut-stack order arranges single-sided sheets for cutting into piles and stacking left to right, top to bottom, keeping sheet order within each pile; duplex must be off), booklet (booklet_subset both/front/back, binding left/right), poster (scale %, overlap pt, cut_marks). orientation auto/portrait/landscape; comments_forms document / document-and-markups (default) / document-and-stamps / form-fields-only; paper Letter/Legal/Tabloid, ARCH A/B/C/D/E1/E, ANSI C/D/E, or A0/A1/A2/A3/A4/A5 (short names or full preset labels); use layout actual for 100% in the print-ready PDF; printer drivers may still substitute paper or scale. Then path (save) or printer (a name or \"default\") with copies, collate, duplex off/long-edge/short-edge, grayscale, and options (driver options from printer_options, CUPS).",
)
.with(schema(
json!({
+48
View File
@@ -4197,3 +4197,51 @@ fn text_extract_with_a_rect_takes_one_column() {
assert!(matches!(e, ToolError::InvalidArgs(_)), "{bad}: {e}");
}
}
/// Independent inch/mm dimensions, checked in the saved file through the tool path.
#[test]
fn architectural_and_iso_papers_through_tools() {
let dir = workdir("plotter-papers");
let mut a = auto(&dir);
let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap();
let mm = 72.0 / 25.4;
for (name, w, h) in [
("ARCH A", 9.0 * 72.0, 12.0 * 72.0),
("arch b", 12.0 * 72.0, 18.0 * 72.0),
("ARCH C", 18.0 * 72.0, 24.0 * 72.0),
("ARCH D", 24.0 * 72.0, 36.0 * 72.0),
("ARCH E1", 30.0 * 72.0, 42.0 * 72.0),
("ARCH E (36 x 48 in)", 36.0 * 72.0, 48.0 * 72.0),
("ANSI C", 17.0 * 72.0, 22.0 * 72.0),
("ANSI D", 22.0 * 72.0, 34.0 * 72.0),
("ANSI E", 34.0 * 72.0, 44.0 * 72.0),
("A2", 420.0 * mm, 594.0 * mm),
("A1", 594.0 * mm, 841.0 * mm),
("A0", 841.0 * mm, 1189.0 * mm),
("Letter", 612.0, 792.0),
("US Legal", 612.0, 1008.0),
("Tabloid", 792.0, 1224.0),
] {
for orientation in ["portrait", "landscape"] {
let out = ok(
&mut a,
"doc_print",
json!({"doc": doc, "pages": "1", "paper": name,
"layout": "actual", "orientation": orientation, "path": "plot.pdf"}),
);
assert_eq!(out["sheets"], 1);
let bytes = std::fs::read(dir.join("plot.pdf")).unwrap();
let saved = pdfcraft_cos::Document::open(std::sync::Arc::new(bytes)).unwrap();
let pages = pdfcraft_model::pages(&saved);
assert_eq!(pages.len(), 1);
let (ew, eh) = if orientation == "landscape" { (h, w) } else { (w, h) };
let (aw, ah) = pages[0].display_size(&saved);
assert!((aw - ew).abs() < 0.02 && (ah - eh).abs() < 0.02, "{name} {orientation}: {aw} x {ah}");
}
}
let before = std::fs::read(dir.join("plot.pdf")).unwrap();
let bad = a.call("doc_print", &json!({"doc": doc, "paper": "ARCH Z", "path": "plot.pdf"}));
assert!(matches!(bad, Err(ToolError::InvalidArgs(m)) if m.contains("ARCH E1") && m.contains("A0")));
assert_eq!(std::fs::read(dir.join("plot.pdf")).unwrap(), before);
assert!(a.call("doc_print", &json!({"doc": doc, "paper": "ARCH D", "path": "../escape.pdf"})).is_err());
}
+22
View File
@@ -46,3 +46,25 @@ printer isn't available yet; the print-ready PDF can always be saved.
Not yet: the web spooler, print as image, poster labels, PostScript output, colour conversion for
grayscale.
## Architectural sheets and scale
The Paper menu and `doc_print` share Letter, Legal, Tabloid, A3/A4/A5 plus
ARCH A/B/C/D/E1/E, ANSI C/D/E and ISO A0/A1/A2. ARCH D is 24 × 36 inches;
ANSI D is 22 × 34 inches. Dimensions follow the [HP DesignJet paper-size table](https://support.hp.com/hr-en/document/bpp01676).
Automation accepts short names such as `"ARCH D"` (case-insensitive) or the full menu label.
For example, `doc_print` with `paper: "ARCH D"`, `layout: "actual"`,
`orientation: "auto"` and `path: "plan.pdf"` writes a print-ready PDF at 100%.
**Match page at 100%** uses the currently viewed document page to choose a standard
sheet (either orientation, within 0.5 pt for rounded metric dimensions), Actual size
and Auto orientation. It leaves the selected page range intact. It is disabled
for a custom page size with no matching preset. One preset applies to the whole job;
this is not per-page paper-source selection.
**PDF scale** describes the source-to-sheet transform in the previewed print-ready
PDF, not measured physical output. Fit reserves an 18 pt margin and can reduce even
a matching page. The current Windows backend still rasterises and can substitute
paper/shrink a job if its driver lacks the requested size. These controls do not
add #599's strict PrintTicket validation or vector spooler. Verify the driver's
paper/scaling settings and physical calibration before relying on drawing scale.
+23 -1
View File
@@ -161,15 +161,37 @@ impl Default for Settings {
}
/// Common paper sizes (points, portrait).
pub const PAPERS: [(&str, (f64, f64)); 6] = [
pub const PAPERS: [(&str, (f64, f64)); 18] = [
("US Letter", (612.0, 792.0)),
("US Legal", (612.0, 1008.0)),
("Tabloid", (792.0, 1224.0)),
("A3", (841.89, 1190.55)),
("A4", (595.28, 841.89)),
("A5", (419.53, 595.28)),
("ARCH A (9 x 12 in)", (648.0, 864.0)),
("ARCH B (12 x 18 in)", (864.0, 1296.0)),
("ARCH C (18 x 24 in)", (1296.0, 1728.0)),
("ARCH D (24 x 36 in)", (1728.0, 2592.0)),
("ARCH E1 (30 x 42 in)", (2160.0, 3024.0)),
("ARCH E (36 x 48 in)", (2592.0, 3456.0)),
("ANSI C (17 x 22 in)", (1224.0, 1584.0)),
("ANSI D (22 x 34 in)", (1584.0, 2448.0)),
("ANSI E (34 x 44 in)", (2448.0, 3168.0)),
("A2", (1190.55, 1683.78)),
("A1", (1683.78, 2383.94)),
("A0", (2383.94, 3370.39)),
];
/// Match a displayed PDF page to a standard sheet, independent of orientation.
/// Half a point allows rounded metric PDF dimensions without confusing distinct sizes.
pub fn matching_paper(size: (f64, f64)) -> Option<usize> {
if !size.0.is_finite() || !size.1.is_finite() {
return None;
}
let size = (size.0.min(size.1), size.0.max(size.1));
PAPERS.iter().position(|(_, p)| (p.0 - size.0).abs() <= 0.5 && (p.1 - size.1).abs() <= 0.5)
}
/// The unprintable margin assumed around the sheet for Fit, Multiple and Booklet.
pub const MARGIN: f64 = 18.0;
+27
View File
@@ -631,3 +631,30 @@ fn a_scaled_appearance_is_not_scaled_twice() {
assert!(close(m.0[0], 2.0) && close(m.0[3], 2.0), "{:?}", m.0);
assert!(close(m.0[4], 10.0) && close(m.0[5], 10.0), "{:?}", m.0);
}
#[test]
fn architectural_sheets_preserve_full_scale() {
let index = matching_paper((2592.0, 1728.0)).unwrap();
assert_eq!(PAPERS[index].0, "ARCH D (24 x 36 in)");
assert_eq!(matching_paper((1728.0, 2592.0)), Some(index));
assert_eq!(matching_paper((f64::NAN, 2592.0)), None);
let s = Settings { pages: vec![0], paper: PAPERS[index].1, layout: Layout::Size(SizeMode::Actual), ..Settings::default() };
let sheets = layout(&[(2592.0, 1728.0)], &s).unwrap();
assert_eq!(sheets[0].size, (2592.0, 1728.0));
assert_eq!(sheets[0].placed[0].matrix.0, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
let fit = layout(&[(2592.0, 1728.0)], &Settings { layout: Layout::Size(SizeMode::Fit), ..s }).unwrap();
assert!(fit[0].placed[0].matrix.0[0] < 1.0, "Fit adds margins and must not masquerade as full scale");
}
#[test]
fn paper_matching_accepts_rounded_metric_sizes_but_not_custom_or_invalid_sizes() {
for (i, (_, (w, h))) in PAPERS.iter().enumerate() {
assert_eq!(matching_paper((*w, *h)), Some(i));
assert_eq!(matching_paper((*h, *w)), Some(i));
assert_eq!(matching_paper((w.round(), h.round())), Some(i));
assert_eq!(matching_paper((w + 0.51, *h)), None);
}
for size in [(0.0, 0.0), (-1728.0, 2592.0), (f64::INFINITY, 2592.0), (1728.0, f64::NAN), (1000.0, 1000.0)] {
assert_eq!(matching_paper(size), None);
}
}
+7
View File
@@ -2232,6 +2232,13 @@ key Space مسافة
Panels اللوحات
Resize panels تغيير حجم اللوحات
Resize panel window تغيير حجم نافذة اللوحة
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in صفحة PDF: {w} × {h} بوصة
Match page at 100% مطابقة الورق للصفحة بنسبة 100%
PDF scale: {scale}% مقياس PDF: {scale}%
Printer paper and scaling may differ. Check driver settings. قد يختلف ورق الطابعة ومقياس الطباعة. تحقق من إعدادات برنامج التشغيل.
Drawing scale changes. Use Actual size for full-scale plans. يتغير مقياس الرسم. استخدم الحجم الفعلي لطباعة المخططات بمقياسها الأصلي.
Reload documents when their files change on disk إعادة تحميل المستندات عند تغيّر ملفاتها على القرص
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. تغيّر هذا الملف على القرص. أعد تحميله لعرض النسخة الجديدة؛ ستفقد تغييراتك غير المحفوظة.
Reload إعادة تحميل
Can't render this file because it contains an unexpected character in line 6 and column 47.
+7
View File
@@ -2174,6 +2174,13 @@ key Space Интервал
Resize panel window Преоразмери прозореца на панела
Tools Инструменти
Inspector Инспектор
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in PDF страница: {w} × {h} инча
Match page at 100% Хартия по страницата, мащаб 100%
PDF scale: {scale}% Мащаб на PDF: {scale}%
Printer paper and scaling may differ. Check driver settings. Хартията и мащабът на принтера може да се различават. Проверете настройките на драйвера.
Drawing scale changes. Use Actual size for full-scale plans. Мащабът на чертежа се променя. Използвайте Действителен размер за планове в оригинален мащаб.
Reload documents when their files change on disk Презареждай документите, когато файловете им се променят на диска
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. Този файл е променен на диска. Презаредете го, за да видите новата версия; незаписаните ви промени ще бъдат загубени.
Reload Презареди
Can't render this file because it contains an unexpected character in line 8 and column 16.
+7
View File
@@ -2216,6 +2216,13 @@ key Space Leertaste
Resize panel window Größe des Bereichsfensters ändern
Tools Werkzeuge
Inspector Inspektor
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in PDF-Seite: {w} × {h} Zoll
Match page at 100% Papier an Seite anpassen, 100%
PDF scale: {scale}% PDF-Maßstab: {scale}%
Printer paper and scaling may differ. Check driver settings. Druckerpapier und Skalierung können abweichen. Treibereinstellungen prüfen.
Drawing scale changes. Use Actual size for full-scale plans. Der Zeichnungsmaßstab ändert sich. Für maßstabsgetreue Pläne Tatsächliche Größe verwenden.
Reload documents when their files change on disk Dokumente neu laden, wenn sich ihre Dateien auf dem Datenträger ändern
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. Diese Datei wurde auf dem Datenträger geändert. Lade sie neu, um die neue Version zu sehen; deine nicht gespeicherten Änderungen gehen dabei verloren.
Reload Neu laden
Can't render this file because it contains an unexpected character in line 6 and column 51.
+7
View File
@@ -2216,6 +2216,13 @@ key Space Espace
Resize panel window Redimensionner la fenêtre du panneau
Tools Outils
Inspector Inspecteur
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in Page PDF : {w} × {h} po
Match page at 100% Adapter le papier à la page à 100 %
PDF scale: {scale}% Échelle PDF : {scale}%
Printer paper and scaling may differ. Check driver settings. Le papier et l’échelle de l’imprimante peuvent différer. Vérifiez les réglages du pilote.
Drawing scale changes. Use Actual size for full-scale plans. L’échelle du dessin change. Utilisez Taille réelle pour les plans à l’échelle.
Select page {label} Sélectionner la page {label}
Rotate page {label} counterclockwise Faire pivoter la page {label} dans le sens antihoraire
Rotate page {label} clockwise Faire pivoter la page {label} dans le sens horaire
Can't render this file because it contains an unexpected character in line 6 and column 39.
+7
View File
@@ -2210,6 +2210,13 @@ key Space Szóköz
Resize panel window Panelablak átméretezése
Tools Eszközök
Inspector Tulajdonságvizsgáló
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in PDF-oldal: {w} × {h} hüvelyk
Match page at 100% Papír illesztése az oldalhoz, 100%
PDF scale: {scale}% PDF-méretarány: {scale}%
Printer paper and scaling may differ. Check driver settings. A nyomtató papírja és méretezése eltérhet. Ellenőrizze az illesztőprogram beállításait.
Drawing scale changes. Use Actual size for full-scale plans. A rajz méretaránya változik. Méretarányos tervekhez használja a Tényleges méret beállítást.
Reload documents when their files change on disk Dokumentumok újratöltése, ha a fájljuk megváltozik a lemezen
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. Ez a fájl megváltozott a lemezen. Töltsd újra az új változat megtekintéséhez; a nem mentett módosításaid elvesznek.
Reload Újratöltés
Can't render this file because it contains an unexpected character in line 7 and column 44.
+7
View File
@@ -2219,6 +2219,13 @@ key Space Spazio
Panels Pannelli
Resize panels Ridimensiona pannelli
Resize panel window Ridimensiona finestra pannello
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in Pagina PDF: {w} × {h} pollici
Match page at 100% Adatta il foglio alla pagina al 100%
PDF scale: {scale}% Scala PDF: {scale}%
Printer paper and scaling may differ. Check driver settings. Carta e scala della stampante possono differire. Controlla le impostazioni del driver.
Drawing scale changes. Use Actual size for full-scale plans. La scala del disegno cambia. Usa Dimensioni effettive per le planimetrie in scala.
Reload documents when their files change on disk Ricarica i documenti quando i loro file cambiano sul disco
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. Questo file è cambiato sul disco. Ricaricalo per vedere la nuova versione; le modifiche non salvate andranno perse.
Reload Ricarica
Can't render this file because it contains an unexpected character in line 6 and column 47.
+7
View File
@@ -2212,6 +2212,13 @@ key Space スペース
Resize panel window パネルウィンドウのサイズを変更
Tools ツール
Inspector インスペクター
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in PDFページ: {w} × {h} インチ
Match page at 100% ページに合わせて100%に設定
PDF scale: {scale}% PDFの倍率: {scale}%
Printer paper and scaling may differ. Check driver settings. プリンターの用紙や倍率は異なる場合があります。ドライバーの設定を確認してください。
Drawing scale changes. Use Actual size for full-scale plans. 図面の縮尺が変わります。原寸で印刷するには「実際のサイズ」を使用してください。
Reload documents when their files change on disk ディスク上のファイルが変更されたら文書を再読み込みする
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. このファイルはディスク上で変更されました。再読み込みすると新しいバージョンが表示されますが、未保存の変更は失われます。
Reload 再読み込み
Can't render this file because it contains an unexpected character in line 6 and column 47.
+7
View File
@@ -2213,6 +2213,13 @@ key Space Espaço
Resize panel window Redimensionar janela do painel
Tools Ferramentas
Inspector Inspetor
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in Página PDF: {w} × {h} pol.
Match page at 100% Ajustar papel à página a 100%
PDF scale: {scale}% Escala do PDF: {scale}%
Printer paper and scaling may differ. Check driver settings. O papel e a escala da impressora podem variar. Confira as configurações do driver.
Drawing scale changes. Use Actual size for full-scale plans. A escala do desenho muda. Use Tamanho real para plantas em escala.
Reload documents when their files change on disk Recarregar documentos quando os arquivos mudarem no disco
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. Este arquivo foi alterado no disco. Recarregue-o para ver a nova versão; suas alterações não salvas serão perdidas.
Reload Recarregar
Can't render this file because it contains an unexpected character in line 9 and column 16.
+7
View File
@@ -2129,6 +2129,13 @@ key Space Пробел
Resize panel window Изменить размер окна панели
Tools Инструменты
Inspector Инспектор
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in Страница PDF: {w} × {h} дюйм.
Match page at 100% Подобрать бумагу, масштаб 100%
PDF scale: {scale}% Масштаб PDF: {scale}%
Printer paper and scaling may differ. Check driver settings. Бумага и масштаб принтера могут отличаться. Проверьте настройки драйвера.
Drawing scale changes. Use Actual size for full-scale plans. Масштаб чертежа изменяется. Для печати в исходном масштабе выберите Фактический размер.
Select page {label} Выбрать страницу {label}
Rotate page {label} counterclockwise Повернуть страницу {label} против часовой стрелки
Rotate page {label} clockwise Повернуть страницу {label} по часовой стрелке
Can't render this file because it contains an unexpected character in line 9 and column 16.
+7
View File
@@ -2233,6 +2233,13 @@ key Space Пробіл
Panels Панелі
Resize panels Змінити розмір панелей
Resize panel window Змінити розмір вікна панелі
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in Сторінка PDF: {w} × {h} дюйм.
Match page at 100% Підібрати папір, масштаб 100%
PDF scale: {scale}% Масштаб PDF: {scale}%
Printer paper and scaling may differ. Check driver settings. Папір і масштаб принтера можуть відрізнятися. Перевірте налаштування драйвера.
Drawing scale changes. Use Actual size for full-scale plans. Масштаб креслення змінюється. Для друку в початковому масштабі виберіть Фактичний розмір.
Reload documents when their files change on disk Перезавантажувати документи, коли їхні файли змінюються на диску
This file changed on disk. Reload it to see the new version; your unsaved changes will be lost. Цей файл змінено на диску. Перезавантажте його, щоб побачити нову версію; незбережені зміни буде втрачено.
Reload Перезавантажити
Can't render this file because it contains an unexpected character in line 6 and column 47.
+7
View File
@@ -2180,6 +2180,13 @@ key Space 空格
Resize panel window 调整面板窗口大小
Tools 工具
Inspector 检查器
# Architectural print controls (follow-up to #599).
PDF page: {w} × {h} in PDF 页面:{w} × {h} 英寸
Match page at 100% 匹配页面并设为 100%
PDF scale: {scale}% PDF 缩放:{scale}%
Printer paper and scaling may differ. Check driver settings. 打印机纸张和缩放可能不同。请检查驱动程序设置。
Drawing scale changes. Use Actual size for full-scale plans. 图纸比例发生变化。按原比例打印图纸时请使用实际大小。
Select page {label} 选择页面 {label}
Rotate page {label} counterclockwise 逆时针旋转页面 {label}
Rotate page {label} clockwise 顺时针旋转页面 {label}
Can't render this file because it contains an unexpected character in line 9 and column 16.
+28 -2
View File
@@ -603,7 +603,8 @@ pub(crate) fn body(
);
ui.end_row();
ui.label(tl!("Paper:"));
egui::ComboBox::from_id_salt("paper").selected_text(PAPERS[d.paper].0).width(160.0).show_ui(ui, |ui| {
let paper_name = PAPERS.get(d.paper).map_or("US Letter", |p| p.0);
egui::ComboBox::from_id_salt("paper").selected_text(paper_name).width(260.0).show_ui(ui, |ui| {
for (i, (name, _)) in PAPERS.iter().enumerate() {
ui.selectable_value(&mut d.paper, i, *name);
}
@@ -616,6 +617,21 @@ pub(crate) fn body(
if d.show_driver_options {
driver_options_panel(ui, d, t);
}
if let Some(&source) = sizes.get(d.current_page) {
ui.horizontal_wrapped(|ui| {
ui.label(crate::i18n::fmt(tl!("PDF page: {w} × {h} in"), &[("w", &format!("{:.2}", source.0 / 72.0)), ("h", &format!("{:.2}", source.1 / 72.0))]));
let matched = print::matching_paper(source);
if ui.add_enabled(matched.is_some(), egui::Button::new(tl!("Match page at 100%"))).clicked()
&& let Some(paper) = matched
{
d.paper = paper;
d.handling = Handling::Size;
d.size = SizeMode::Actual;
d.orientation = Orientation::Auto;
d.sheet = 0;
}
});
}
ui.add_space(6.0);
widgets::section_title(ui, tl!("Pages to Print"));
ui.horizontal(|ui| {
@@ -841,9 +857,19 @@ pub(crate) fn body(
d.sheet += 1;
}
});
if let Some(s) = sheets.first() {
if let Some(s) = sheets.get(d.sheet) {
let (w, h) = (s.size.0 / 72.0, s.size.1 / 72.0);
ui.label(egui::RichText::new(format!("{w:.2} × {h:.2} in")).small().color(t.text_muted));
if d.handling == Handling::Size
&& let Some(p) = s.placed.first()
{
let scale = p.matrix.0[0].hypot(p.matrix.0[1]) * 100.0;
ui.label(crate::i18n::fmt(tl!("PDF scale: {scale}%"), &[("scale", &format!("{scale:.2}"))]));
ui.label(egui::RichText::new(tl!("Printer paper and scaling may differ. Check driver settings.")).small().color(t.text_muted));
if (scale - 100.0).abs() > 0.01 {
ui.label(egui::RichText::new(tl!("Drawing scale changes. Use Actual size for full-scale plans.")).small().color(t.text_muted));
}
}
}
});
});
+40 -1
View File
@@ -1,7 +1,7 @@
//! The Print dialog in the real shell (egui_kittest): settings, preview sheets, Save as PDF.
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use egui_kittest::kittest::{NodeT, Queryable};
use pdfcraft_ui_egui::{Dialog, PdfCraftApp};
fn harness() -> Harness<'static, PdfCraftApp> {
@@ -243,3 +243,42 @@ fn printers_listed_in_the_background_preselect_the_default_unless_one_was_picked
assert!(!h.state().print_draft.printers_loading);
assert_eq!(h.state().print_draft.printer, None, "Save as PDF, as picked");
}
#[test]
fn architectural_page_can_be_matched_at_100_percent() {
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 1000.0)).build_eframe(|_cc| {
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
let bytes = app.session.create_blank(2592.0, 1728.0, 1).unwrap();
app.open_bytes("architectural.pdf", None, bytes.as_ref().clone()).unwrap();
app
});
h.run_steps(4);
assert!(h.state_mut().execute("print.dialog"));
h.run_steps(3);
h.get_by_label("PDF page: 36.00 × 24.00 in");
h.get_by_label("Match page at 100%").click();
h.run_steps(3);
h.get_by_value("ARCH D (24 x 36 in)");
h.get_by_label("PDF scale: 100.00%");
h.get_by_label("36.00 × 24.00 in");
h.get_by_label("Printer paper and scaling may differ. Check driver settings.");
let settings = h.state().print_draft.settings(1, &[]).unwrap();
assert_eq!(settings.paper, (1728.0, 2592.0));
assert_eq!(settings.layout, pdfcraft_engine::print::Layout::Size(pdfcraft_engine::print::SizeMode::Actual));
}
#[test]
fn custom_page_cannot_silently_match_a_standard_paper() {
let mut h = harness();
assert!(h.state_mut().execute("print.dialog"));
h.run_steps(3);
// The 300 x 400 pt fixture is not a standard sheet.
assert!(pdfcraft_engine::print::matching_paper((300.0, 400.0)).is_none());
let before = h.state().print_draft.clone();
assert!(h.get_by_label("Match page at 100%").accesskit_node().is_disabled());
h.get_by_label("Match page at 100%").click();
h.run_steps(3);
assert_eq!(h.state().print_draft.paper, before.paper);
assert_eq!(h.state().print_draft.size, before.size);
}
+2 -1
View File
@@ -1,5 +1,6 @@
# PdfCraft roadmap: milestones and what's next
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (architectural paper controls from #599; M6: respect existing form-font encodings; retain source fonts on appearance fallback; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (direct Organize overview and page actions; localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (closed issues synced: #774, #756, #817–#821, #809, #819, #816, #844; earlier: localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M6: respect existing form-font encodings; retain source fonts on appearance fallback; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
@@ -84,7 +85,7 @@ total matches the parity estimate.
| M7 | Content editing (text, images, header/footer, watermark) | 250–500 | 20% | 100–180 | Done: header & footer, watermarks, backgrounds, Bates; added text and images that stay editable (move, resize, format, rotate, flip, crop, replace); links (Link tool, Link Properties, create from URLs, remove all). Missing: editing existing text and images in place (the longest pole), image/PDF watermarks |
| M8 | Security + redaction | 100–180 | 68% | 15–30 | Done: opening protected documents, permissions, Protect Using Password, Remove security; redaction (mark text/areas/pages, Search & Redact with patterns, apply removing glyphs, image pixels, vectors, XObject content, annotations and fields, verification, full rewrite on save); Remove hidden information and Sanitize.<br>Done since: redaction codes (U.S. FOIA and Privacy Act sets as overlay text, in Redaction properties and `redact_mark`). Missing: certificate security, custom redaction code sets and pattern locales, DCT re-encoding |
| M9 | Signatures (PAdES, validation) | 160–280 | 60% | 40–70 | Done: new `sign` crate (DER, X.509, CMS, PKCS #12 on RustCrypto; aws-lc-rs for RSA private keys); PAdES B-B signing (visible, invisible, existing fields, certification with DocMDP); validation with trust store and changes-after-signing classification; self-signed digital IDs; Signatures panel, message bar, sign dialogs; agent tools. Checked with pdfsig and OpenSSL.<br>Done since: macOS Keychain and Windows Current User Personal store signing (software-backed CNG keys), certificate viewer, signed documents protected from rewrites. Missing: timestamps (B-T), LTV (DSS, OCSP, CRL), FieldMDP, smart cards, PKCS #11 |
| M10 | OCR, create, export, print | 200–350 | 33% | 120–215 | Done: create from blank/text/PNG/JPEG/TIFF (multi-page)/GIF/BMP; export PNG/JPEG/TIFF and text; Print (Acrobat's sizing, n-up, booklet, poster, comments & forms, preview, the CUPS and Windows spoolers, print-ready PDF).<br>Done since: embedded/72/custom DPI choices for image imports; export all images; OCR (searchable image for pages, ranges and multiple files); single-sided cut-and-stack imposition with cut marks, through Print and doc_print; Create PDF from multiple files (PDFs, images and text, edited as pages in the grid). Missing: OCR languages beyond Latin, editable-text OCR output, Office export/import, web printing |
| M10 | OCR, create, export, print | 200–350 | 33% | 120–215 | Done: create from blank/text/PNG/JPEG/TIFF (multi-page)/GIF/BMP; export PNG/JPEG/TIFF and text; Print (Acrobat's sizing, n-up, booklet, poster, comments & forms, preview, the CUPS and Windows spoolers, print-ready PDF).<br>Done since: embedded/72/custom DPI choices for image imports; export all images; OCR (searchable image for pages, ranges and multiple files); single-sided cut-and-stack imposition with cut marks, through Print and doc_print; architectural/ANSI/large ISO sheet presets, Match page at 100% and PDF-scale preview (#599 follow-up); Create PDF from multiple files (PDFs, images and text, edited as pages in the grid). Missing: OCR languages beyond Latin, editable-text OCR output, Office export/import, web printing |
| M11 | Optimize, preflight, PDF/A/X/UA, print production | 200–350 | 18% | 80–140 | Done: new `optimize` crate: Reduce File Size and the PDF Optimizer (images measured where drawn, bicubic downsampling, JPEG/ZIP recompression only when smaller, discard objects and user data, Flate clean-up, resource merging, object streams). Missing: fonts and transparency panels, space audit, preflight, PDF/A/X/UA |
| M12 | Accessibility, compare, measure, search, XFA | 200–380 | 30% | 90–160 | Done: new `a11y` crate with the Accessibility Checker (all 32 rules, report, Fix/Skip/Explain, options dialog and results panel, agent tools).<br>Done since: 2D distance, perimeter and area measurements with persistent viewport calibration, snapping, live information and CSV export. Missing: autotag, Tags/Order/Content panels, Reading Order tool, alt-text workflow, compare, geospatial/3D measurement, search index, XFA |
| M13 | Automation (MCP, Action Wizard, CLI) + AI providers | 60–120 | 45% | 25–45 | Done: MCP resources (document info, text, page images); headless tool table (123 tools incl. signing, optimizing, initial view, links, stamps, data exchange, comment review, forms authoring and scripts, redaction, sanitize, print, add content), opt-in MCP server over stdio, CLI `run`/`tools` (closed stdout pipes exit cleanly), UI control channel with drag. Missing: Action Wizard, AI providers<br>Done since: CLI run and MCP reject missing, empty, repeated and option-valued --root arguments before any tool/script execution or MCP initialization. A single-hyphen directory name remains valid; synthetic CLI regressions preserve a would-be overwritten file. |
+3 -3
View File
@@ -7938,8 +7938,8 @@ milestone = "M10"
status = "shipped"
commands = ["print.dialog"]
tools = ["doc_print"]
evidence = ["crates/print/src/tests.rs::size_modes"]
notes = "fit, actual size, shrink oversized, custom %"
evidence = ["crates/print/src/tests.rs::size_modes", "crates/print/src/tests.rs::architectural_sheets_preserve_full_scale", "crates/automation/tests/automation.rs::architectural_and_iso_papers_through_tools", "crates/ui-egui/tests/print.rs::architectural_page_can_be_matched_at_100_percent"]
notes = "fit, actual size, shrink oversized, custom %; ARCH A/B/C/D/E1/E, ANSI C/D/E, ISO A0/A1/A2 presets; Match page at 100% matches the current page to a standard sheet. PDF scale shows the previewed source-to-sheet transform, not calibrated physical output; driver substitution/scaling remains possible"
[[feature]]
id = "print.paper-source-by-size"
@@ -8010,7 +8010,7 @@ milestone = "M10"
status = "shipped"
commands = ["print.dialog"]
tools = ["doc_print"]
evidence = ["crates/print/src/tests.rs::size_modes"]
evidence = ["crates/print/src/tests.rs::size_modes", "crates/print/src/tests.rs::architectural_sheets_preserve_full_scale", "crates/automation/tests/automation.rs::architectural_and_iso_papers_through_tools", "crates/ui-egui/tests/print.rs::architectural_page_can_be_matched_at_100_percent"]
notes = "auto portrait/landscape"
[[feature]]