diff --git a/ROADMAP.md b/ROADMAP.md index 403f973ff8..5d995fff4c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -45,6 +45,7 @@ > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (web: same-document host mode on the canvas, `data-host="page"`; estimates unchanged) > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M10: vector printing resolves annotation rectangle coordinates; estimates unchanged) > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M10: vector printing resolves annotation rectangles and appearance-state names; estimates unchanged) +> **Last reviewed:** 2026-10-26 | **Last updated:** 2026-10-26 | **Change:** minor (M10: vector printing follows bounded annotation-value reference chains; 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) @@ -309,6 +310,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md - **2026-10-25 (M7, #2750):** Single-style Keep source fonts edits retain source glyphs and batch only missing glyphs in fallback fonts. Per-rewrite character eligibility and widths are cached; source and fallback edge spaces stay extractable. Estimates unchanged. - **2026-10-25 (web, same-document host mode):** A page that loads the PdfCraft web module into its own document (no iframe) can opt into the host bridge with `data-host="page"` on the app's canvas — the exact value `page`; any other value keeps the normal app with downloads, unless a same-origin framed `?host=parent` selects the parent bridge (unframed or cross-origin, `?host=parent` also keeps the normal app). It is the `?host=parent` bridge with the transport swapped: the same five messages travel as non-bubbling `CustomEvent`s on the canvas (`detail` holds the parent messages' fields without `type`; the host answers with a `pdfcraft:open` event), so saves reach the host instead of downloading and dirty and start-up-failure reporting are unchanged. Unit tests cover the opt-in rule; no parity or estimate changes. - **2026-10-25 (M10):** Vector printing keeps field and comment artwork whose rectangle coordinates are indirect, requiring exactly four finite resolved values. +- **2026-10-26 (M10):** Vector printing follows up to 64 indirect references for annotation rectangles, coordinates and appearance-state names, rejecting cycles and longer chains without changing source objects. - **2026-10-25 (M10):** Vector printing keeps field and comment artwork whose rectangle coordinates or appearance-state names are indirect, requiring exactly four finite resolved rectangle values and a usable state name for state dictionaries. Synthetic CLI and source regressions cover the behavior. Estimates unchanged. @@ -855,6 +857,7 @@ Newest first: the date, what moved, and the new overall estimate. | 2026-10-27 | minor | M10: OCR stages SHA-256-verified ONNX models after the upstream RTEN URL brownout; discovery prefers a complete ONNX pair per directory, falls back to a complete RTEN pair, and never mixes formats or directories; language/web limits and estimates unchanged. | | 2026-10-20 | minor | M9 #79 review, M2, M8: /ActualText that marks no glyph extracts where it paints, as far as it is visible; redaction strips /ActualText around paint it clips; named-layer recognition needs the layer form to draw n1 and n3. Estimates unchanged. | | 2026-10-25 | minor | M10: vector printing resolves annotation rectangle coordinates; synthetic regression evidence, estimates unchanged. | +| 2026-10-26 | minor | M10: annotation rectangles, coordinates and appearance-state names follow up to 64 indirect references with cycle detection; standalone-object save/reopen and malformed-chain regressions, estimates unchanged. | | 2026-10-25 | minor | M10: vector printing resolves annotation rectangles and appearance-state names; synthetic save/reopen and print-content-mode evidence, estimates unchanged. | | 2026-10-23 | minor | M8, #3205 follow-up: phone-pattern redaction includes the +39 country code on labelled Italian geographic numbers with a slash; dates, protocol identifiers and whole-run guards remain. Estimates unchanged. | | 2026-10-23 | minor | M11: image downsampling measures physical resolution after page-local UserUnit scaling, including shared nested forms; synthetic measurement and optimization regressions. Estimates unchanged. | diff --git a/crates/print/README.md b/crates/print/README.md index 4848441666..7a7af29888 100644 --- a/crates/print/README.md +++ b/crates/print/README.md @@ -134,5 +134,6 @@ the driver's paper settings and physical calibration before relying on drawing s Vector printing resolves direct and indirect annotation rectangle coordinates before placing existing appearances. Rectangles must contain exactly four finite numbers; malformed rectangles are skipped. For an appearance-state dictionary, direct or indirect `AS` names select the existing artwork. -Missing, invalid, unresolved, cyclic, or unknown state names leave that annotation unprinted; a single normal appearance stream does not require `AS`. +Rectangle arrays, individual coordinates and `AS` names follow up to 64 indirect references with cycle detection. +Missing, invalid, unresolved, cyclic, overly deep, or unknown state names leave that annotation unprinted; a single normal appearance stream does not require `AS`. The print content mode still controls whether a field or comment prints, and printing preserves the source objects. diff --git a/crates/print/src/imposition.rs b/crates/print/src/imposition.rs index 4716bbca3b..1102406569 100644 --- a/crates/print/src/imposition.rs +++ b/crates/print/src/imposition.rs @@ -111,6 +111,21 @@ pub(crate) fn prints(doc: &Document, d: &Dict, content: Content) -> bool { } } +/// Read an annotation value through at most 64 indirect references. +/// Standalone indirect objects can themselves hold references (ISO 32000-2 §7.3.10). +/// Cycles and longer chains are unusable, like other invalid annotation values. +fn annotation_value(doc: &Document, value: &Object) -> Option> { + let mut value = std::sync::Arc::new(value.clone()); + let mut seen = std::collections::HashSet::new(); + while let Object::Ref(reference) = &*value { + if seen.len() == 64 || !seen.insert(*reference) { + return None; + } + value = doc.get(*reference); + } + Some(value) +} + /// The normal appearance stream to draw, resolving the annotation's `/AS` state name. pub(crate) fn appearance(doc: &Document, d: &Dict) -> Option { let ap = doc.resolve(d.get(b"AP")?); @@ -118,7 +133,7 @@ pub(crate) fn appearance(doc: &Document, d: &Dict) -> Option { match &*doc.resolve(&normal) { Object::Stream(_) => normal.as_ref(), Object::Dict(states) => { - let state = doc.resolve(d.get(b"AS")?); + let state = annotation_value(doc, d.get(b"AS")?)?; states.get(state.as_name()?)?.as_ref() } _ => None, @@ -184,9 +199,9 @@ pub(crate) fn page_form( } // A rectangle has four numbers, and each can be an indirect object (ISO 32000-2 §7.3.10). let rect = d.get(b"Rect").and_then(|r| { - let rect = doc.resolve(r); + let rect = annotation_value(doc, r)?; let [x0, y0, x1, y1] = rect.as_array()?.as_slice() else { return None }; - let number = |v: &Object| doc.resolve(v).as_f64().filter(|v| v.is_finite()); + let number = |v: &Object| annotation_value(doc, v)?.as_f64().filter(|v| v.is_finite()); Some([number(x0)?, number(y0)?, number(x1)?, number(y1)?]) }); let Some([x0, y0, x1, y1]) = rect else { continue }; diff --git a/crates/print/src/tests/appearances.rs b/crates/print/src/tests/appearances.rs index 1eb74ec0d4..e36ade1dda 100644 --- a/crates/print/src/tests/appearances.rs +++ b/crates/print/src/tests/appearances.rs @@ -79,6 +79,14 @@ fn saved(doc: &Document) -> Vec { write_full(doc, &SaveOptions::default()).unwrap() } +/// Wrap a synthetic value in exactly `depth` standalone indirect objects. +fn reference_chain(doc: &mut Document, mut value: Object, depth: usize) -> Object { + for _ in 0..depth { + value = Object::Ref(doc.add(value)); + } + value +} + fn xobject(doc: &Document, owner: &Dict, name: &[u8]) -> ObjRef { let resources = doc.resolve(owner.get(b"Resources").unwrap()); let objects = doc.resolve(resources.as_dict().unwrap().get(b"XObject").unwrap()); @@ -272,6 +280,128 @@ fn indirect_annotation_states_keep_their_printed_appearances() { } } +/// Standalone indirect objects can contain further references, including rectangle arrays, +/// individual coordinates and AS names. Printing must match the equivalent direct values. +#[test] +fn chained_annotation_values_keep_their_printed_appearances() { + for subtype in ["Widget", "Square"] { + for (state_name, color) in [("Yes", [0, 255, 0, 255]), ("Off", [255, 0, 0, 255])] { + for (rect_depth, coordinate_depth, state_depth) in [(2, 0, 0), (0, 2, 0), (0, 0, 2), (2, 2, 2), (64, 64, 64)] { + let (mut doc, on, annot) = widget_fixture(Some(Object::name("Form")), None); + let off = doc.add(Object::Stream(Stream::flate(doc.get(on).as_dict().unwrap().clone(), b"/Ink cs 1 0 0 scn 0 0 20 10 re f"))); + let values = [Object::Int(50), Object::Real(40.25), Object::Int(10), Object::Real(20.25)]; + doc.update_dict(annot, |d| { + d.set(b"Subtype".to_vec(), Object::name(subtype)); + d.set(b"Rect".to_vec(), Object::Array(values.to_vec())); + d.set(b"AS".to_vec(), Object::name(state_name)); + d.set( + b"AP".to_vec(), + Object::Dict(Dict::from_iter([( + b"N".to_vec(), + Object::Dict(Dict::from_iter([(b"Yes".to_vec(), Object::Ref(on)), (b"Off".to_vec(), Object::Ref(off))])), + )])), + ); + }) + .unwrap(); + let modes = [Content::Document, Content::DocumentAndMarkups, Content::DocumentAndStamps, Content::FormFieldsOnly]; + let controls: Vec<_> = modes.iter().map(|&content| render(impose(&doc, &print_settings(content)).unwrap())).collect(); + // Mix direct, one-hop and chained integer/real coordinates in the same rectangle. + let coordinates = values + .into_iter() + .enumerate() + .map(|(i, value)| { + reference_chain( + &mut doc, + value, + if coordinate_depth == 0 || i == 0 { + 0 + } else if i == 2 { + 1 + } else { + coordinate_depth + }, + ) + }) + .collect(); + let rect = reference_chain(&mut doc, Object::Array(coordinates), rect_depth); + let state = reference_chain(&mut doc, Object::name(state_name), state_depth); + doc.update_dict(annot, |d| { + d.set(b"Rect".to_vec(), rect); + d.set(b"AS".to_vec(), state); + }) + .unwrap(); + // ISO 32000-2 permits reference-to-reference objects outside object streams. + let options = SaveOptions { object_streams: false, ..SaveOptions::default() }; + let doc = Document::open(Arc::new(write_full(&doc, &options).unwrap())).unwrap(); + let before = write_full(&doc, &options).unwrap(); + let state = doc.read_state(); + let graph: Vec<_> = doc.scan_objects().collect(); + for (&content, control) in modes.iter().zip(controls) { + let printed = render(impose(&doc, &print_settings(content)).unwrap()); + let shown = subtype == "Widget" || content == Content::DocumentAndMarkups; + assert_eq!( + pixel(&printed, 30, 70), + if shown { color } else { [255; 4] }, + "{subtype}, {state_name}, depths={rect_depth}/{coordinate_depth}/{state_depth}, {content:?}" + ); + assert_eq!(printed.rgba, control.rgba, "chained values preserve the complete direct-control output"); + } + assert!(state.matches(&doc), "printing preserves the source read state"); + assert_eq!(doc.scan_objects().collect::>(), graph, "printing preserves the source object graph"); + assert_eq!(write_full(&doc, &options).unwrap(), before, "printing preserves standalone reference chains"); + } + } + } +} + +/// Cycles, missing targets and reference chains beyond the print reader's limit skip only +/// the unusable annotation, retaining page content and leaving source values unchanged. +#[test] +fn invalid_annotation_reference_chains_are_skipped_when_printing() { + for property in ["rectangle", "coordinate", "state"] { + for kind in ["cycle", "unresolved", "too-deep"] { + let (mut doc, ap, annot) = widget_fixture(Some(Object::name("Form")), None); + let value = match kind { + "cycle" => { + let first = doc.add(Object::Null); + let second = doc.add(Object::Ref(first)); + doc.set(first, Object::Ref(second)); + Object::Ref(first) + } + "unresolved" => reference_chain(&mut doc, Object::Ref(ObjRef::new(999_999, 0)), 2), + "too-deep" => { + let value = match property { + "rectangle" => numbers(&[10.0, 20.0, 50.0, 40.0]), + "coordinate" => Object::Int(40), + "state" => Object::name("Yes"), + _ => panic!("unknown property {property}"), + }; + reference_chain(&mut doc, value, 65) + } + _ => panic!("unknown kind {kind}"), + }; + doc.update_dict(annot, |d| match property { + "rectangle" => d.set(b"Rect".to_vec(), value), + "coordinate" => d.set(b"Rect".to_vec(), Object::Array(vec![10.into(), 20.into(), 50.into(), value])), + "state" => { + d.set(b"AS".to_vec(), value); + d.set( + b"AP".to_vec(), + Object::Dict(Dict::from_iter([(b"N".to_vec(), Object::Dict(Dict::from_iter([(b"Yes".to_vec(), Object::Ref(ap))])))])), + ); + } + _ => panic!("unknown property {property}"), + }) + .unwrap(); + let before = saved(&doc); + let printed = render(impose(&doc, &print_settings(Content::Document)).unwrap()); + assert_eq!(pixel(&printed, 30, 70), [255; 4], "invalid {property} chain ({kind}) does not select artwork"); + assert_eq!(pixel(&printed, 4, 96), [0, 0, 255, 255], "page content survives invalid {property} chain ({kind})"); + assert_eq!(saved(&doc), before, "invalid {property} chain ({kind}) is not rewritten"); + } + } +} + /// Malformed, missing and cyclic AS values do not select arbitrary appearance states, while /// a single normal appearance stream continues to work without a usable state name. #[test] diff --git a/docs/roadmap.md b/docs/roadmap.md index 61c0b37719..36623d165f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -35,6 +35,7 @@ > **Last reviewed:** 2026-10-26 · **Last updated:** 2026-10-26 · **Change:** minor (M11: lossless JBIG2 stores each distinct mark once, as a symbol; estimates unchanged) > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M10: vector printing resolves annotation rectangle coordinates; estimates unchanged) > **Last reviewed:** 2026-10-25 | **Last updated:** 2026-10-25 | **Change:** minor (M10: vector printing resolves annotation rectangles and appearance-state names; estimates unchanged) +> **Last reviewed:** 2026-10-26 | **Last updated:** 2026-10-26 | **Change:** minor (M10: vector printing follows bounded annotation-value reference chains; 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-24 · **Last updated:** 2026-10-24 · **Change:** minor (M11: the optimizer stores Group 3 fax images as Group 4, and packs black-and-white RGB and indexed images that use only palette entries 0 and 1 to one bit; estimates unchanged) > **Last reviewed:** 2026-10-24 · **Last updated:** 2026-10-24 · **Change:** minor (M11: black-and-white images above 450 ppi are averaged down to 300 ppi by the optimizer; estimates unchanged) @@ -259,7 +260,7 @@ total matches the parity estimate. | 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). Target-version compatibility is partial: newer content is not detected or converted. Content stream boundaries preserve comments, split operands and graphics state when image fragments merge. Image downsampling includes page-local `/UserUnit` scaling when measuring physical resolution. Done since: reject invalid image and soft-mask dimensions with checked sizing (#2178); black-and-white images stored losslessly as CCITT Group 4 when smaller, averaged down to 300 ppi above 450 (halftones aside), Group 3 stored again as Group 4, and black-and-white RGB and indexed images that use only palette entries 0 and 1 packed to one bit. Missing: fonts and transparency panels, space audit, preflight, PDF/A/X/UA | | 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.
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). The redaction audit counts a filled cover that hides half of any one word of a long line (#2662), applied or not. Phone-pattern redaction also matches labelled Italian geographic numbers with `+39` and a slash after the area code (#3205 follow-up); unlabelled international slash numbers remain unsupported. 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.
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).
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`. Word table cells use the grid columns they span, including cells after a merged cell. Done since: text in a table slot covered by another cell's span remains as paragraph text in Word, HTML, RTF and PowerPoint. Vector printing keeps field and comment artwork whose rectangle coordinates or appearance-state names are indirect, requiring exactly four finite resolved rectangle values and a usable state name for state dictionaries. Missing: OCR languages beyond Latin, editable-text OCR output, full Office layout fidelity/import. Done since: dotted-leader contents entries stay in separate complete paragraphs (#1757) #2156 candidate: last selected print paper persists across sessions; other print preferences unchanged. Image export applies soft-mask Decode opacity intervals before resizing, including indirect entries and inverted ranges. Vector printing gives annotation appearances unoccupied XObject names so existing page artwork survives. | +| 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).
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`. Word table cells use the grid columns they span, including cells after a merged cell. Done since: text in a table slot covered by another cell's span remains as paragraph text in Word, HTML, RTF and PowerPoint. Vector printing keeps field and comment artwork whose rectangle coordinates or appearance-state names are indirect, requiring exactly four finite resolved rectangle values and a usable state name for state dictionaries. Rectangle arrays, coordinates and state names follow up to 64 indirect references with cycle detection. Missing: OCR languages beyond Latin, editable-text OCR output, full Office layout fidelity/import. Done since: dotted-leader contents entries stay in separate complete paragraphs (#1757) #2156 candidate: last selected print paper persists across sessions; other print preferences unchanged. Image export applies soft-mask Decode opacity intervals before resizing, including indirect entries and inverted ranges. Vector printing gives annotation appearances unoccupied XObject names so existing page artwork survives. | | 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). Target-version compatibility is partial: newer content is not detected or converted. Content stream boundaries preserve comments, split operands and graphics state when image fragments merge. Image downsampling includes page-local `/UserUnit` scaling when measuring physical resolution. Done since: reject invalid image and soft-mask dimensions with checked sizing (#2178). 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. Character encoding accepts Identity-H/V with Adobe CJK collections and predefined CJK CMaps independently (#3673); per-glyph validation and embedded-font extraction remain separate.
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). Done since: #2569: Growable multiline XFA text reflows after edits, with undo/redo, save/reopen and print coverage; oversized fields still become read-only page text. Structured-data synchronization warnings retain accepted whitespace without relayout from stale data. Done since: #2572: Every repeated page-master button resolves its own field to the shared XFA click script, including print buttons. Done since: #2570: Dynamic XFA rich-text fields carry exData/maxLength into AcroForm MaxLen for reported and enforced Unicode character limits, retained after save/reopen. Done since: #2589: Active XFA skips PDF-container fallback scripts while retaining requested messages and saved scripts; ordinary/static/unlaid-out documents and explicit console/field calls keep their existing behavior. Missing: writing proposed tags and parent tree, field autotag, visual Tags/Order/Content panels, Reading Order tool, compare, geospatial/3D measurement, search index, XFA
Reading colours: pending page backgrounds follow the configured colour (#2059). | | 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
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. | @@ -355,6 +356,7 @@ Estimates unchanged. | 2026-10-27 | minor | M10: OCR stages SHA-256-verified ONNX models after the upstream RTEN URL brownout; discovery prefers a complete ONNX pair per directory, falls back to a complete RTEN pair, and never mixes formats or directories; language/web limits and estimates unchanged. | | 2026-10-20 | minor | M9 #79 review, M2, M8: /ActualText that marks no glyph extracts where it paints, as far as it is visible; redaction strips /ActualText around paint it clips; named-layer recognition needs the layer form to draw n1 and n3. Estimates unchanged. | | 2026-10-25 | minor | M10: vector printing resolves annotation rectangle coordinates; synthetic regression evidence, estimates unchanged. | +| 2026-10-26 | minor | M10: annotation rectangles, coordinates and appearance-state names follow up to 64 indirect references with cycle detection; standalone-object save/reopen and malformed-chain regressions, estimates unchanged. | | 2026-10-25 | minor | M10: vector printing resolves annotation rectangles and appearance-state names; synthetic save/reopen and print-content-mode evidence, estimates unchanged. | | 2026-10-24 | minor | M3, #3970 follow-up: the button release given back after a window move or resize goes before the pointer leaving, so hover doesn't stick. Estimates unchanged. | | 2026-10-23 | minor | M8, #3205 follow-up: phone-pattern redaction includes the +39 country code on labelled Italian geographic numbers with a slash; dates, protocol identifiers and whole-run guards remain. Estimates unchanged. | diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 9f8e17b156..8793d66c79 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -8333,8 +8333,8 @@ milestone = "M10" status = "shipped" commands = ["print.dialog"] tools = ["doc_print"] -evidence = ["crates/print/src/tests.rs::imposed_pdf_has_the_sheets_and_honours_comments_and_forms", "crates/print/src/tests/appearances.rs::annotation_appearances_do_not_replace_page_xobjects", "crates/print/src/tests/appearances.rs::printable_link_appearance_survives_vector_print", "crates/print/src/tests/appearances.rs::indirect_annotation_rectangles_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_rectangles_are_skipped_when_printing", "crates/print/src/tests/appearances.rs::indirect_annotation_states_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_states_do_not_select_an_appearance"] -notes = "Existing field and comment appearances retain direct or indirect rectangle coordinates and appearance-state names. State dictionaries require a usable AS name; missing/invalid/unresolved/cyclic or unknown names select no appearance, while a single normal stream needs no AS. Print content modes and source objects are preserved. Malformed rectangles are skipped; missing appearances are not generated. Print and Hidden flags honoured; existing Link appearances print as document content, but missing Link appearances are not synthesized. Appearance resource names preserve existing page XObjects and distinct annotations, including inherited resources" +evidence = ["crates/print/src/tests.rs::imposed_pdf_has_the_sheets_and_honours_comments_and_forms", "crates/print/src/tests/appearances.rs::annotation_appearances_do_not_replace_page_xobjects", "crates/print/src/tests/appearances.rs::printable_link_appearance_survives_vector_print", "crates/print/src/tests/appearances.rs::indirect_annotation_rectangles_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_rectangles_are_skipped_when_printing", "crates/print/src/tests/appearances.rs::indirect_annotation_states_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_states_do_not_select_an_appearance", "crates/print/src/tests/appearances.rs::chained_annotation_values_keep_their_printed_appearances", "crates/print/src/tests/appearances.rs::invalid_annotation_reference_chains_are_skipped_when_printing"] +notes = "Existing field and comment appearances retain direct or indirect rectangle coordinates and appearance-state names, including reference chains of up to 64 objects with cycle detection. State dictionaries require a usable AS name; missing/invalid/unresolved/cyclic/overly deep or unknown names select no appearance, while a single normal stream needs no AS. Print content modes and source objects are preserved. Malformed rectangles are skipped; missing appearances are not generated. Print and Hidden flags honoured; existing Link appearances print as document content, but missing Link appearances are not synthesized. Appearance resource names preserve existing page XObjects and distinct annotations, including inherited resources" [[feature]] id = "print.print-with-comment-summary"