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:
storybored379 2026-10-03 21:14:56 -07:00 committed by GitHub
parent 9d99f4b278
commit 0523ccdfdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 23 additions and 1 deletions

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;

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 ".