M10: follow bounded annotation reference chains when printing

This commit is contained in:
Kyle Kassen
2026-11-10 12:40:06 +01:00
committed by FredApps
parent 13a22a3115
commit f613f2650b
6 changed files with 158 additions and 7 deletions
+3
View File
@@ -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. |
+2 -1
View File
@@ -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.
+18 -3
View File
@@ -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<std::sync::Arc<Object>> {
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<ObjRef> {
let ap = doc.resolve(d.get(b"AP")?);
@@ -118,7 +133,7 @@ pub(crate) fn appearance(doc: &Document, d: &Dict) -> Option<ObjRef> {
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 };
+130
View File
@@ -79,6 +79,14 @@ fn saved(doc: &Document) -> Vec<u8> {
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::<Vec<_>>(), 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]
+3 -1
View File
@@ -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.<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). 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.<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`. 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).<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`. 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.<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). 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<br>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<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. |
@@ -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. |
+2 -2
View File
@@ -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"