mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
fix(redact): read text-state operands from the end, as the renderer does (#5951)
This commit is contained in:
1 parent
44a26f4b93
commit
ca56433f5f
2 files changed
+31
-10
No files matched your search
+17
-10
@@ -591,8 +591,10 @@ impl Walk {
|
||||
}
|
||||
|
||||
fn select_font(&mut self, doc: &Document, scope: &mut Scope<'_>, op: &Op) {
|
||||
self.gs.size = op.num(1).unwrap_or(self.gs.size);
|
||||
let Some(name) = op.name(0) else { return };
|
||||
// `/F1 12 Tf` takes its last two operands, as the renderer does: a surplus before them
|
||||
// is ignored, not read in their place.
|
||||
self.gs.size = op.nums::<1>().map_or(self.gs.size, |[size]| size);
|
||||
let Some(name) = op.operands.len().checked_sub(2).and_then(|i| op.name(i)) else { return };
|
||||
let font_dict = self.fonts_res.get(name).and_then(|f| doc.resolve(f).as_dict().cloned());
|
||||
self.gs.vertical = font_dict.as_ref().is_some_and(|d| is_vertical(doc, d));
|
||||
self.gs.type3_inherits = font_dict.as_ref().is_some_and(|d| type3_without_resources(doc, d));
|
||||
@@ -609,17 +611,20 @@ impl Walk {
|
||||
/// Text state and text-positioning operators.
|
||||
fn text_state(&mut self, op: &Op) {
|
||||
let gs = &mut self.gs;
|
||||
// The operand immediately before the operator, as the renderer reads it: surplus operands
|
||||
// before it must not move where glyphs are placed (`1 50 Ts` raises text by 50, not 1).
|
||||
let last = op.nums::<1>().map(|[v]| v);
|
||||
match op.op.as_slice() {
|
||||
b"BT" => {
|
||||
self.tm = Matrix::IDENTITY;
|
||||
self.tlm = Matrix::IDENTITY;
|
||||
}
|
||||
b"Tc" => gs.char_spacing = op.num(0).unwrap_or(0.0),
|
||||
b"Tw" => gs.word_spacing = op.num(0).unwrap_or(0.0),
|
||||
b"Tz" => gs.scale = op.num(0).unwrap_or(100.0) / 100.0,
|
||||
b"TL" => gs.leading = op.num(0).unwrap_or(0.0),
|
||||
b"Ts" => gs.rise = op.num(0).unwrap_or(0.0),
|
||||
b"Tr" => gs.render_mode = op.num(0).unwrap_or(0.0) as i64,
|
||||
b"Tc" => gs.char_spacing = last.unwrap_or(0.0),
|
||||
b"Tw" => gs.word_spacing = last.unwrap_or(0.0),
|
||||
b"Tz" => gs.scale = last.unwrap_or(100.0) / 100.0,
|
||||
b"TL" => gs.leading = last.unwrap_or(0.0),
|
||||
b"Ts" => gs.rise = last.unwrap_or(0.0),
|
||||
b"Tr" => gs.render_mode = last.unwrap_or(0.0) as i64,
|
||||
b"Td" | b"TD" => {
|
||||
if let Some([x, y]) = op.nums::<2>() {
|
||||
if op.is("TD") {
|
||||
@@ -656,8 +661,10 @@ impl Walk {
|
||||
}
|
||||
let o = op.op.as_slice();
|
||||
if o == b"\"" {
|
||||
self.gs.word_spacing = op.num(0).unwrap_or(self.gs.word_spacing);
|
||||
self.gs.char_spacing = op.num(1).unwrap_or(self.gs.char_spacing);
|
||||
// `aw ac string "`: the two numbers before the string, whatever surplus precedes them.
|
||||
let at = |back: usize| op.operands.len().checked_sub(back).and_then(|i| op.num(i));
|
||||
self.gs.word_spacing = at(3).unwrap_or(self.gs.word_spacing);
|
||||
self.gs.char_spacing = at(2).unwrap_or(self.gs.char_spacing);
|
||||
}
|
||||
if matches!(o, b"'" | b"\"") {
|
||||
self.tlm = Matrix::translate(0.0, -self.gs.leading).then(&self.tlm);
|
||||
|
||||
@@ -25,3 +25,17 @@ fn a_form_inherits_the_text_state_of_the_page_that_draws_it() {
|
||||
let r = apply(&mut doc, None).unwrap();
|
||||
assert_eq!((r.glyphs, r.forms_rewritten), (6, 1), "{r:?}");
|
||||
}
|
||||
|
||||
/// `1 50 Ts` raises text by 50, the operand before the operator, as the renderer reads it; the
|
||||
/// surplus `1` before it is ignored. Reading the first operand put the glyphs 49 pt too low, so a
|
||||
/// mark over the text the page shows missed it.
|
||||
#[test]
|
||||
fn surplus_text_state_operands_do_not_move_the_glyphs() {
|
||||
let mut doc = one_page(b"BT /F1 10 Tf 1 50 Ts 10 100 Td (SECRET) Tj ET", "", vec![]);
|
||||
assert_eq!(under(&mut doc, 0, &[SECRET_AT_10_100]), 0, "nothing is drawn at y 100");
|
||||
let raised = [5.0, 145.0, 45.0, 160.0];
|
||||
assert_eq!(under(&mut doc, 0, &[raised]), 6);
|
||||
mark(&mut doc, 0, &[raised], "");
|
||||
assert_eq!(apply(&mut doc, None).unwrap().glyphs, 6);
|
||||
assert!(!content(&doc, 0).contains("SECRET"));
|
||||
}
|
||||
Reference in new issue
Block a user