mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 19:48:14 -06:00
ROADMAP.md: kept main's stage line and status lines, added this PR's status and log lines. docs/roadmap.md: kept main's status lines and M8/M9/M10 rows (main had merged the duplicate M8 row), added this PR's status line and its M10 sentence. crates/ui-egui/src/lib.rs: persist keeps main's `v` and its separately set keys; print_paper set the same way. crates/ui-egui/tests/print.rs: kept both sides' tests.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 #2080 candidate: explicit search jumps join per-tab history; rail and control-channel Back/Forward; estimates unchanged)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M4 #1910: Organize Pages opens on the current page; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 candidate: coordinate control clicks keep press/release together on slow frames and preserve text multi-click selection; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M10 #2156 candidate: remember selected print paper by canonical name; estimates unchanged)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (M4 #1310 #1311 #1312: undo, revert and the next save keep the last saved revision; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 #1980: exact quit modifiers preserve Ctrl+Alt+Q email input; M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -270,6 +271,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
- **2026-10-14 (community, M7, #128 Hebrew in Add text):** Add text and `page_add_text` draw Hebrew with the craft-fonts Hebr face (Noto Sans Hebrew), through the same shaped path as Arabic and Thai: right-to-left display order with numbers and Latin in place, points and cantillation kept on their letter as one Type 3 glyph, ToUnicode and per-line ActualText so extraction, search and the editor get typing order. Builds without the face refuse Hebrew rather than write `?`. Regular style only. Estimates unchanged.
|
||||
- **2026-10-14 (M9, #1787):** Changes after signing are judged by the role an object held when signed for a page's resources too: a rewritten font, XObject, graphics state or resources dictionary is page content however its new version is labelled, so a `/FT` or `/Subtype /Widget` added by the attacker no longer reads as a permitted form fill. A category dictionary that only gained a name is judged as before.
|
||||
- **2026-10-14 (M10, #1818):** Properties… reads a CUPS printer's driver options off the GUI thread, the dialog saying so meanwhile, and `lpoptions` is given ten seconds before it is killed and the PPD's defaults stand; a read that dies leaves the panel with nothing rather than a frozen window. The bounded wait is tested with a stand-in command that never answers.
|
||||
- **2026-10-14 (M10 #2156 candidate):** Print paper selection survives Cancel and settings restoration; canonical names avoid preset-index dependence. Other print preferences and estimates unchanged.
|
||||
|
||||
- **2026-10-13 (M3, native-opening candidate):** Chosen tools survive preparation, conversion and password retries; startup intake waits for pending preparation. Regression tests cover completion, cancellation and document identity. Estimates unchanged.
|
||||
- **2026-10-13 (community, M3, #531 touchpad momentum):** On Wayland a two-finger swipe keeps gliding after the fingers lift and slows to a stop, measured from the gesture's last 0.1 s and decaying with a 0.35 s time constant through the #759 1:1 path. Any wheel input, click, key, zoom or edge ends it (fingers resting on the touchpad don't yet: the vendored winit binds no Wayland hold gestures); single-page paging, macOS (which sends its own momentum), the web, and Animation effects Off never glide. Unit tests for the physics and two headless view tests. Estimates unchanged.
|
||||
- **2026-10-13 (M3, native-open admission):** Later prepared replies stay queued while a modal or native picker owns the foreground. Picker completion, cancellation and abandonment wake retained opens. Regressions cover recipient ownership, exactly-once admission, source/history preservation and one-shot repaint requests. Estimates unchanged.
|
||||
|
||||
@@ -36,6 +36,12 @@ Each source page becomes a Form XObject (its content wrapped in q/Q, plus the pr
|
||||
annotations); sheets place them with a clip. The result is a fresh, unencrypted,
|
||||
garbage-collected file (callers check the print permission).
|
||||
|
||||
The GUI Print dialog remembers its last selected paper size across application sessions,
|
||||
including when Print is cancelled. The preference stores a canonical name such as `A4`;
|
||||
unrecognized saved values retain the current selection (US Letter on a fresh app).
|
||||
This change does not alter persistence of other print preferences.
|
||||
Headless `doc_print` defaults are unchanged.
|
||||
|
||||
`spool` delegates discovery, driver options, preferences and submission to
|
||||
[`craft-print`](https://github.com/storytold/craft-libs/tree/main/crates/print).
|
||||
PdfCraft retains page selection, imposition, annotations and print-permission checks.
|
||||
|
||||
@@ -2323,6 +2323,8 @@ impl PdfCraftApp {
|
||||
// Set outside json! above, which is at serde_json's macro recursion limit.
|
||||
v["hide_empty_highlight_hover"] = serde_json::Value::Bool(self.comment_prefs.hide_empty_highlight_hover);
|
||||
v["organize_zoom"] = serde_json::json!(self.organize_zoom);
|
||||
// Canonical names survive changes to the order of PAPERS and do not depend on UI language.
|
||||
v["print_paper"] = serde_json::json!(pdfcraft_engine::print::PAPERS.get(self.print_draft.paper).map(|(name, _)| *name));
|
||||
v.to_string()
|
||||
}
|
||||
|
||||
@@ -2384,6 +2386,13 @@ impl PdfCraftApp {
|
||||
if let Ok(units) = serde_json::from_value::<units::Unit>(v["units"].clone()) {
|
||||
self.units = units;
|
||||
}
|
||||
if let Some(paper) = v
|
||||
.get("print_paper")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.and_then(|name| pdfcraft_engine::print::PAPERS.iter().position(|(canonical, _)| *canonical == name))
|
||||
{
|
||||
self.print_draft.paper = paper;
|
||||
}
|
||||
if let Some(on) = v["highlight_fields"].as_bool() {
|
||||
self.view_defaults.highlight_fields = on;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,16 @@ use egui_kittest::kittest::{NodeT, Queryable};
|
||||
use pdfcraft_ui_egui::{Dialog, PdfCraftApp};
|
||||
|
||||
fn harness() -> Harness<'static, PdfCraftApp> {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
|
||||
harness_with_settings(None)
|
||||
}
|
||||
|
||||
fn harness_with_settings(settings: Option<String>) -> Harness<'static, PdfCraftApp> {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
if let Some(json) = &settings {
|
||||
app.restore(json);
|
||||
}
|
||||
app.open_bytes("form.pdf", None, include_bytes!("data/form.pdf").to_vec()).unwrap();
|
||||
app
|
||||
});
|
||||
@@ -736,3 +743,125 @@ fn print_without_a_document_does_not_blame_security() {
|
||||
h.run_steps(30);
|
||||
assert!(h.query_by_label_contains("don't allow printing").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paper_choice_survives_cancel_and_settings_restore() {
|
||||
use pdfcraft_engine::print::{Orientation, PAPERS};
|
||||
let a4 = PAPERS.iter().position(|(name, _)| *name == "A4").unwrap();
|
||||
let letter = PAPERS.iter().position(|(name, _)| *name == "US Letter").unwrap();
|
||||
let mut h = harness();
|
||||
assert!(h.state_mut().execute("print.dialog"));
|
||||
h.run_steps(3);
|
||||
h.get_by_value("US Letter").click();
|
||||
h.run_steps(2);
|
||||
h.get_by_label("A4").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().print_draft.paper, a4);
|
||||
// Only paper should be added to persistence; these choices still belong to this session.
|
||||
let draft = &mut h.state_mut().print_draft;
|
||||
draft.copies = 5;
|
||||
draft.which = pdfcraft_ui_egui::PrintWhich::Range;
|
||||
draft.range = "1".into();
|
||||
draft.orientation = Orientation::Landscape;
|
||||
draft.grayscale = true;
|
||||
let before_cancel = draft.clone();
|
||||
h.get_by_label("Cancel").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().dialog, None);
|
||||
assert_eq!(h.state().print_draft, before_cancel, "cancel keeps the existing draft behavior");
|
||||
let saved = h.state().persist();
|
||||
let json: serde_json::Value = serde_json::from_str(&saved).unwrap();
|
||||
assert_eq!(json["print_paper"], "A4", "store the canonical name, not its position");
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.restore(&saved);
|
||||
let expected = pdfcraft_ui_egui::PrintDraft { paper: a4, ..Default::default() };
|
||||
assert_eq!(app.print_draft, expected, "no other print preference becomes persistent");
|
||||
let mut restarted = harness_with_settings(Some(saved));
|
||||
assert!(restarted.state_mut().execute("print.dialog"));
|
||||
restarted.run_steps(3);
|
||||
restarted.get_by_value("A4");
|
||||
assert_eq!(restarted.state().print_draft.paper, a4);
|
||||
restarted.get_by_value("A4").click();
|
||||
restarted.run_steps(2);
|
||||
restarted.get_by_label("US Letter").click();
|
||||
restarted.run_steps(2);
|
||||
restarted.get_by_label("Cancel").click();
|
||||
restarted.run_steps(2);
|
||||
let saved = restarted.state().persist();
|
||||
assert_eq!(serde_json::from_str::<serde_json::Value>(&saved).unwrap()["print_paper"], "US Letter");
|
||||
let mut again = harness_with_settings(Some(saved));
|
||||
assert!(again.state_mut().execute("print.dialog"));
|
||||
again.run_steps(3);
|
||||
again.get_by_value("US Letter");
|
||||
assert_eq!(again.state().print_draft.paper, letter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paper_restore_accepts_canonical_names_and_ignores_invalid_settings() {
|
||||
use pdfcraft_engine::print::PAPERS;
|
||||
for (index, (name, _)) in PAPERS.iter().enumerate() {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.restore(&serde_json::json!({"print_paper": name}).to_string());
|
||||
assert_eq!(app.print_draft.paper, index);
|
||||
assert_eq!(serde_json::from_str::<serde_json::Value>(&app.persist()).unwrap()["print_paper"], *name);
|
||||
}
|
||||
for invalid in [
|
||||
"not JSON",
|
||||
"{}",
|
||||
r#"{"print_paper":null}"#,
|
||||
r#"{"print_paper":true}"#,
|
||||
r#"{"print_paper":[]}"#,
|
||||
r#"{"print_paper":{}}"#,
|
||||
r#"{"print_paper":"unknown"}"#,
|
||||
r#"{"print_paper":"a4"}"#,
|
||||
r#"{"print_paper":4}"#,
|
||||
r#"{"print_paper":-1}"#,
|
||||
r#"{"print_paper":4294967296}"#,
|
||||
r#"{"print_paper":18446744073709551615}"#,
|
||||
r#"{"print_paper":1e100}"#,
|
||||
] {
|
||||
let mut app = PdfCraftApp::new();
|
||||
let factory = app.print_draft.paper;
|
||||
app.restore(invalid);
|
||||
assert_eq!(app.print_draft.paper, factory, "{invalid}");
|
||||
app.restore(r#"{"print_paper":"A4"}"#);
|
||||
let selected = app.print_draft.paper;
|
||||
app.restore(invalid);
|
||||
assert_eq!(app.print_draft.paper, selected, "invalid input must not replace a valid choice: {invalid}");
|
||||
}
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.print_draft.paper = usize::MAX;
|
||||
assert_eq!(serde_json::from_str::<serde_json::Value>(&app.persist()).unwrap()["print_paper"], serde_json::Value::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_paper_keeps_preview_and_saved_sheet_dimensions() {
|
||||
use pdfcraft_engine::print::{self, PAPERS};
|
||||
let a4 = PAPERS.iter().find(|(name, _)| *name == "A4").unwrap().1;
|
||||
let mut h = harness_with_settings(Some(r#"{"print_paper":"A4"}"#.into()));
|
||||
assert!(h.state_mut().execute("print.dialog"));
|
||||
h.run_steps(3);
|
||||
h.get_by_value("A4");
|
||||
h.get_by_label("Sheet 1 of 1");
|
||||
let id = h.state().views[h.state().active.unwrap()].id;
|
||||
let doc = h.state().session.get(id).unwrap();
|
||||
let sizes: Vec<_> = doc.info.pages.iter().map(|p| (f64::from(p.width), f64::from(p.height))).collect();
|
||||
let labels: Vec<_> = doc.info.pages.iter().map(|p| p.label.clone()).collect();
|
||||
let settings = h.state().print_draft.settings(sizes.len(), &labels).unwrap();
|
||||
assert_eq!(settings.paper, a4);
|
||||
let sheets = print::layout(&sizes, &settings).unwrap();
|
||||
assert_eq!(sheets.len(), 1);
|
||||
assert_eq!(sheets[0].size, a4, "preview uses the restored paper");
|
||||
let out = std::env::temp_dir().join(format!("pdfcraft-restored-paper-{}.pdf", std::process::id()));
|
||||
h.state_mut().print_draft.printer = None;
|
||||
h.state_mut().save_override = Some(out.to_string_lossy().into_owned());
|
||||
h.get_by_label("Save as PDF").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().dialog, None);
|
||||
let printed = pdfcraft_cos::Document::open(std::sync::Arc::new(std::fs::read(&out).unwrap())).unwrap();
|
||||
let pages = pdfcraft_model::pages(&printed);
|
||||
assert_eq!(pages.len(), sheets.len());
|
||||
let size = pages[0].display_size(&printed);
|
||||
assert!((size.0 - a4.0).abs() < 0.01 && (size.1 - a4.1).abs() < 0.01, "saved sheet: {size:?}");
|
||||
std::fs::remove_file(out).unwrap();
|
||||
}
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 candidate: coordinate control clicks on slow frames and coalesced text selection; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-15 · **Change:** minor (M2: sampled functions index a flat table, one image decode serves every zoom level, patch meshes copy less; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M3 #2080 candidate: explicit search jumps join per-tab history; rail and control-channel Back/Forward; estimates unchanged)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (M10 #2156 candidate: remember selected print paper by canonical name; estimates unchanged)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (M3 candidate: native opens retain chosen tools through password retries and defer startup intake while preparation is pending; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (M4 #1310 #1311 #1312: undo, revert and the next save keep the last saved revision; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-15 · **Change:** minor (M14: PNG export writes opaque pages as RGB at deflate level 3, the same size 3–5× faster; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -149,7 +150,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`). Redaction cleans indirect and form structure references, preserving surviving form tags. Redaction retires inherited resource dictionaries, so a parent `/Pages` can't keep a removed XObject reachable; unredacted pages are left untouched. Image redaction removes alternate images and unsupported masks only on affected images. Changed marked-content spans lose replacement descriptions; untouched spans keep theirs. Hidden-text cleanup retires unused named descriptions across inherited/shared page and form resources (64 focused tests and scoped Rust 1.96 Clippy/wasm checks pass; publication CI pending). 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, page validation overlays separate from original signature artwork (baked Not Verified captions remain; #79 open), snapshot tabs that reuse instead of multiplying. 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; 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); OCR can also read pictures on pages that have text, without adding that text twice (#1028). Done since: Word invoice export uses borderless inferred tables, floating positioned text/artwork only where text overlaps an image or is rotated, and per-page sizes, covered by synthetic invoice automation regressions (#773); printing on the web through the browser's print dialog, sheets drawn by PdfCraft (gap 4). Lossless image export applies per-component Decode, including Indexed mapping (#1201 partial). Existing 16-bit precision and color-management limits remain. #3532 partial: vector printing retains existing appearances with absent Subtype through a print-owned Form copy; source artwork stays unchanged. #3120: Office export reads form XObjects 50 levels deep, as the renderer does, and up to 50 000 a page; pages past that are listed in `truncated_pages`. Missing: OCR languages beyond Latin, editable-text OCR output, full Office layout fidelity/import |
|
||||
| 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); OCR can also read pictures on pages that have text, without adding that text twice (#1028). Done since: Word invoice export uses borderless inferred tables, floating positioned text/artwork only where text overlaps an image or is rotated, and per-page sizes, covered by synthetic invoice automation regressions (#773); printing on the web through the browser's print dialog, sheets drawn by PdfCraft (gap 4). Lossless image export applies per-component Decode, including Indexed mapping (#1201 partial). Existing 16-bit precision and color-management limits remain. #3532 partial: vector printing retains existing appearances with absent Subtype through a print-owned Form copy; source artwork stays unchanged. #3120: Office export reads form XObjects 50 levels deep, as the renderer does, and up to 50 000 a page; pages past that are listed in `truncated_pages`. Missing: OCR languages beyond Latin, editable-text OCR output, full Office layout fidelity/import #2156 candidate: last selected print paper persists across sessions; other print preferences unchanged. |
|
||||
| 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). Locatable figure, annotation and form findings highlight their page bounds.<br>Done since: 2D distance, perimeter and area measurements with persistent viewport calibration, snapping, live information and CSV export; a bounded, text-only auto-tag proposal tool for one page. Marking a figure decorative rewrites only that stream (#1324). An XFA radio item whose value contains a space is written back unchanged (#1319). Missing: writing proposed tags and parent tree, field autotag, visual Tags/Order/Content panels, Reading Order tool, 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. `text_find` also caps the per-page match ranges it materializes at the remaining result limit plus one continuation match. CLI robustness scans visit each directory once through overlapping roots and symlink aliases. |
|
||||
|
||||
@@ -7936,8 +7936,8 @@ milestone = "M10"
|
||||
status = "shipped"
|
||||
commands = ["print.dialog"]
|
||||
tools = ["doc_print", "printers"]
|
||||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf", "crates/engine/src/tests.rs::printing_lays_out_sheets_that_render", "crates/ui-egui/src/web_print.rs::a_job_draws_every_sheet_at_its_planned_resolution_then_builds_the_page", "crates/ui-egui/src/web_print.rs::the_print_page_turns_sideways_sheets_names_other_sizes_and_ends_without_a_blank_page"]
|
||||
notes = "⌘P; settings and live sheet preview; on the web the same dialog prints through the browser's print dialog"
|
||||
evidence = ["crates/ui-egui/tests/print.rs::paper_choice_survives_cancel_and_settings_restore", "crates/ui-egui/tests/print.rs::restored_paper_keeps_preview_and_saved_sheet_dimensions", "crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf", "crates/engine/src/tests.rs::printing_lays_out_sheets_that_render", "crates/ui-egui/src/web_print.rs::a_job_draws_every_sheet_at_its_planned_resolution_then_builds_the_page", "crates/ui-egui/src/web_print.rs::the_print_page_turns_sideways_sheets_names_other_sizes_and_ends_without_a_blank_page"]
|
||||
notes = "⌘P; settings and live sheet preview; on the web the same dialog prints through the browser's print dialog; last paper selection, including cancelled Print dialogs, persists by canonical name; this change does not alter persistence of other print preferences"
|
||||
|
||||
[[feature]]
|
||||
id = "print.printer-selection"
|
||||
|
||||
Reference in New Issue
Block a user