mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
A page's /Contents array is one content stream in pieces that writers may split between any two tokens (ISO 32000-2 §7.8.2). Edit text parsed each piece on its own, so an operator whose operands ended one piece and whose keyword started the next arrived with no operands: a `[(After) -20 (wards)]` / `TJ` split made the line vanish from text_lines, text_paragraphs and text_edit, and from the Edit text tool. The joined-parse logic the redaction interpreter used (#153) moves into pdfcraft-content as `Pieces`: join the pieces, parse them as one stream, map positions back to pieces, and splice joined byte ranges back into per-piece bytes (an edit replacing or removing an operator that spans pieces rewrites every piece it touches; untouched pieces keep their objects). text_lines now interprets the joined operators, and replace_line and rewrite_block splice through `Pieces`. Redaction uses the same helper. Lines and paragraphs are grouped by the piece their first operator starts in, as before, so pages whose pieces are separate drawings don't merge. Co-authored-by: Claude Opus 5.5 <[email protected]>
pdfcraft-content
Layer L2: content streams (ISO 32000-2 §7.8.2, §8, §9).
let parsed = parse(&stream.decoded()?); // operators with operands and source spans
for op in &parsed.ops { if op.is("Tj") { /* … */ } }
let bytes = serialize_ops(&parsed.ops); // write back (inline images kept whole)
let m = Matrix::from_operands(&op.operands); // cm / Tm arithmetic, invert, bbox
The parser is tolerant (unreadable bytes are skipped and counted, never fatal) and never
panics. redact and (later) edit build their interpreters on it; the graphics-state tracker
and the Patch API of architecture §6 grow here.