mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-12 13:54:09 +00:00
Review of the walk's read of a form's `/Matrix`. Removing hidden layers alone never asks where content lands, but a form whose `/Matrix` isn't six numbers was counted in its scan and refused, in a document without layers too. Such a form is now walked as it stands there; Apply Redactions, and Remove Hidden Information with hidden text, which tells hidden text by where it lands, still refuse it. `matrix_of` resolved and copied every entry before checking there were six, and the walk reads it at every `Do`. Measured in review (debug build), 20,000 draws of a form whose `/Matrix` is 20,000 entries long took 9 s by reference (16 ms before the walk read entries) and 12 s written directly (2.6 s before, the array being copied by `resolve`). It now reads an array written directly in place, and checks the length before resolving any entry. Tests: `a_form_whose_matrix_is_not_six_numbers_leaves_removing_hidden_layers_alone`, and `tests/matrix_work.rs` (a binary of its own), whose `a_long_form_matrix_costs_no_more_than_a_short_one` times 4,000 draws of a form with a 4,000-entry `/Matrix` against a 7-entry one, written directly and by reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
60 lines
3.0 KiB
Rust
60 lines
3.0 KiB
Rust
//! Work regression for a form's `/Matrix`, which the redaction walk reads at every `Do` of the
|
|
//! form: an array of any length costs no more than one of seven entries, written directly or by
|
|
//! reference. A test binary of its own, so that none of the crate's other tests shares its process.
|
|
//! The test compares two timings of its own, the least of three runs each, not a fixed time.
|
|
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use pdfcraft_annot::{Meta, NewAnnotation, Shape, Style, add_annotation, rect_quad};
|
|
use pdfcraft_cos::Document;
|
|
use pdfcraft_redact::apply;
|
|
use pdfcraft_testkit::{Pdf, stream_obj};
|
|
|
|
/// One page drawing a form `draws` times under a mark, the form's `/Matrix` an array of `entries`
|
|
/// zeros, written in its dictionary or, `by_reference`, as object 6.
|
|
fn document(draws: usize, entries: usize, by_reference: bool) -> Document {
|
|
let zeros = format!("[{}]", vec!["0"; entries].join(" "));
|
|
let matrix = if by_reference { "6 0 R".to_string() } else { zeros.clone() };
|
|
let objs = vec![
|
|
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
|
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
|
|
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 4 0 R /Resources << /XObject << /Fm 5 0 R >> >> >>".to_vec(),
|
|
stream_obj("", "/Fm Do ".repeat(draws).as_bytes()),
|
|
stream_obj(&format!("/Type /XObject /Subtype /Form /BBox [0 0 300 300] /Matrix {matrix}"), b"0 0 10 10 re f"),
|
|
zeros.into_bytes(),
|
|
];
|
|
let mut doc = Document::open(Arc::new(Pdf::new().objs(objs).build())).unwrap();
|
|
let shape = Shape::Redact { quads: vec![rect_quad([0.0, 0.0, 300.0, 300.0])], overlay: String::new(), look: Default::default() };
|
|
let style = Style::default_for(&shape);
|
|
add_annotation(&mut doc, &NewAnnotation { page: 0, shape, style, contents: String::new(), author: "Synthetic test".into() }, &Meta::default())
|
|
.unwrap();
|
|
doc
|
|
}
|
|
|
|
/// How long redacting `document(draws, entries, by_reference)` takes, the least of three runs. A
|
|
/// matrix that isn't six numbers is refused.
|
|
fn redaction(draws: usize, entries: usize, by_reference: bool) -> Duration {
|
|
(0..3)
|
|
.map(|_| {
|
|
let mut doc = document(draws, entries, by_reference);
|
|
let start = Instant::now();
|
|
let refused = apply(&mut doc, None).is_err();
|
|
let took = start.elapsed();
|
|
assert!(refused, "a /Matrix of {entries} entries is refused");
|
|
took
|
|
})
|
|
.min()
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Written when the walk resolved and copied every entry before checking there were six: 4,000
|
|
/// draws of a form whose `/Matrix` is 4,000 entries long read sixteen million entries.
|
|
#[test]
|
|
fn a_long_form_matrix_costs_no_more_than_a_short_one() {
|
|
for by_reference in [false, true] {
|
|
let (short, long) = (redaction(4_000, 7, by_reference), redaction(4_000, 4_000, by_reference));
|
|
assert!(long < short * 3 + Duration::from_millis(50), "by reference: {by_reference}: long {long:?}, short {short:?}");
|
|
}
|
|
}
|