M8: Redaction drops /PCAdded from rewritten content streams so redacted added text does not stay in the file (#1)

Text added with Edit ▸ Add content keeps its source string in the stream dictionary's /PCAdded.
Applying a redaction rewrote the stream's glyphs but copied the dictionary unchanged, so the
redacted text survived in the saved PDF. The redacted item now stays as plain drawn content and
is no longer editable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
storybored379andClaude Opus 5.5 authored and GitHub committed 2026-10-04 00:14:56 -04:00
1 parent 9d99f4b278
commit 0523ccdfdc
2 files changed
+23 -1

No files matched your search

+4
View File
@@ -296,6 +296,10 @@ pub fn apply(doc: &mut Document, pages: Option<&[usize]>) -> Result<Report, Reda
_ => Dict::new(),
};
dict.remove(b"Length");
// Edit ▸ Add content keeps an added item's source text in `/PCAdded`; once its glyphs
// are redacted, keeping it would leave the redacted text in the file. The item stays
// as plain drawn content but is no longer editable.
dict.remove(b"PCAdded");
// A new object: the original may be shared with other pages.
new_list[i] = Object::Ref(doc.add(Object::Stream(Stream::flate(dict, &bytes))));
changed = true;
+19 -1
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use printcraft_annot::{Meta, NewAnnotation, Shape, Style, add_annotation, rect_quad};
use printcraft_cos::{Document, SaveOptions, write_incremental};
use printcraft_cos::{Document, SaveOptions, write_full, write_incremental};
use super::*;
@@ -99,6 +99,24 @@ fn glyphs_under_a_mark_go_and_the_rest_stays_put() {
assert!(!content(&doc, 0).contains("1234"));
}
#[test]
fn added_text_parameters_do_not_keep_redacted_text() {
// An Edit ▸ Add content item: its stream dictionary keeps the source text under /PCAdded.
let mut doc = pdf(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 << /Font << /F1 5 0 R >> >> >>".to_vec(),
stream("/PCMark /Added /PCAdded << /Kind /Text /Text (AB1234CD) >>", b"BT /F1 10 Tf 10 100 Td (AB1234CD) Tj ET"),
FONT.replace("95 0 R", "6 0 R").into_bytes(),
widths(),
]);
mark(&mut doc, 0, &[[20.0, 95.0, 40.0, 110.0]], "");
apply(&mut doc, None).unwrap();
assert!(!content(&doc, 0).contains("1234"));
let bytes = write_full(&doc, &SaveOptions::default()).unwrap();
assert!(!bytes.windows(4).any(|w| w == b"1234"), "redacted text left in the saved file");
}
#[test]
fn kerning_spacing_scaling_and_line_operators_are_honoured() {
// TJ kerning, character and word spacing, 50% horizontal scaling, ' and ".