fix(audit): report faux-redaction rectangles in top-left page points (#1900)

doc_audit_redactions and `pdfcraft-cli audit` returned rectangles in PDF
user space (origin at the bottom-left of the media box), while comment_list,
doc_info, text_lines and the redaction tools give points from the top-left
of the page as displayed. The same mark read [144.14, 91.2, 265.95, 102.2]
in comment_list and [144.138, 739.8, 265.952, 750.8] in the audit.

Both surfaces now convert with the helper doc_info uses (view_rect, made
public for the CLI), so they report the same rectangles, after /Rotate.
The CLI reads the page geometry only when there is a finding to place.
AuditFinding::rect stays in user space; the audit README says which space
each surface uses.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Alex
2026-10-15 02:28:25 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 OpenAI Codex Brandon Thomas
parent b9f0f84ff8
commit b5dc84df2a
8 changed files with 91 additions and 9 deletions
+7 -2
View File
@@ -161,15 +161,20 @@ fn audit(args: &[String]) -> Result<(), CliError> {
validate_options("audit", args, AUDIT_OPTIONS)?;
let path = *positional(args).first().ok_or("audit: missing file")?;
let bytes = read(path)?;
let doc = pdfcraft_cos::Document::open_with_password(bytes, flag(args, "--password")).map_err(|e| format!("{path}: {e}"))?;
let password = flag(args, "--password");
let doc = pdfcraft_cos::Document::open_with_password(bytes.clone(), password).map_err(|e| format!("{path}: {e}"))?;
let findings = pdfcraft_audit::audit_redactions(&doc).map_err(|e| format!("{path}: {e}"))?;
// Rectangles in points from the top-left of the displayed page, as `doc_audit_redactions`
// reports them; the page geometry is read only when there is something to place.
let info = if findings.is_empty() { None } else { Some(inspect(bytes, password).map_err(|e| format!("{path}: {e}"))?) };
let items: Vec<serde_json::Value> = findings
.into_iter()
.map(|f| {
let rect = f.rect.map(|v| v as f32);
serde_json::json!({
"page": f.page + 1,
"kind": f.kind.id(),
"rect": f.rect,
"rect": info.as_ref().map_or(rect, |i| pdfcraft_automation::view_rect(i, f.page, rect)),
"covered_text": f.covered_text,
})
})