//! Content streams (ISO 32000-2 §7.8.2, §8, §9): parse a stream into operators that keep their //! source spans, write operators back, and the matrix arithmetic content interpretation needs. //! //! Layer L2. The parser is tolerant: bytes it cannot read are skipped (and counted in //! [`Parsed::skipped`]) instead of failing the whole stream, and it never panics. Inline images //! (`BI … ID … EI`) are kept whole, so a parse → serialize round trip preserves them. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] use pdfcraft_cos::{Dict, Lexer, Object, PdfString, serialize}; #[cfg(test)] mod tests; /// One operator with its operands. #[derive(Clone, Debug, PartialEq)] pub struct Op { /// The operator keyword (`Tj`, `re`, `BI` for inline images…). pub op: Vec, pub operands: Vec, /// Inline images: the image dictionary (`operands` is empty) and the data between `ID` and /// `EI`. pub inline: Option<(Dict, Vec)>, /// The bytes of the source stream this operator came from (operands included). pub span: std::ops::Range, } impl Op { pub fn new(op: &str, operands: Vec) -> Self { Op { op: op.as_bytes().to_vec(), operands, inline: None, span: 0..0 } } pub fn is(&self, op: &str) -> bool { self.op == op.as_bytes() } /// Operand `i` as a number. pub fn num(&self, i: usize) -> Option { self.operands.get(i).and_then(Object::as_f64) } /// The last `n` operands as numbers (operators take their operands from the end). pub fn nums(&self) -> Option<[f64; N]> { let k = self.operands.len().checked_sub(N)?; let mut out = [0.0; N]; for (o, v) in out.iter_mut().zip(&self.operands[k..]) { *o = v.as_f64()?; } Some(out) } pub fn name(&self, i: usize) -> Option<&[u8]> { self.operands.get(i).and_then(Object::as_name) } } /// A parsed stream. #[derive(Clone, Debug, Default, PartialEq)] pub struct Parsed { pub ops: Vec, /// Bytes that could not be read as operands or operators. pub skipped: usize, } fn is_ws(b: u8) -> bool { matches!(b, b'\0' | b'\t' | b'\n' | b'\x0C' | b'\r' | b' ') } fn is_delim(b: u8) -> bool { matches!(b, b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%') } /// Parse a (decoded) content stream. pub fn parse(data: &[u8]) -> Parsed { let mut out = Parsed::default(); let mut lx = Lexer::new(data, 0); let mut operands: Vec = Vec::new(); let mut start: Option = None; loop { lx.skip_ws(); let Some(&b) = data.get(lx.pos) else { break }; let at = lx.pos; let operand_start = matches!(b, b'/' | b'(' | b'<' | b'[' | b'+' | b'-' | b'.') || b.is_ascii_digit(); if operand_start { match lx.object() { Ok(o) => { start.get_or_insert(at); operands.push(o); } Err(_) if b == b'(' => { // An unterminated string runs to the end of the stream. out.skipped += data.len() - at; lx.pos = data.len(); } Err(_) => { // Skip the offending byte and carry on. lx.pos = at + 1; out.skipped += 1; } } continue; } if is_delim(b) { // A stray `)`, `>`, `]`, `{`, `}`. lx.pos += 1; out.skipped += 1; continue; } let kw = lx.token(); if kw.is_empty() { lx.pos = at + 1; out.skipped += 1; continue; } match kw { b"true" => { start.get_or_insert(at); operands.push(Object::Bool(true)); } b"false" => { start.get_or_insert(at); operands.push(Object::Bool(false)); } b"null" => { start.get_or_insert(at); operands.push(Object::Null); } b"BI" => { let (dict, img, end) = inline_image(data, lx.pos); lx.pos = end; out.ops.push(Op { op: b"BI".to_vec(), operands: Vec::new(), inline: Some((dict, img)), span: start.take().unwrap_or(at)..end }); operands.clear(); } _ => { let s = start.take().unwrap_or(at); out.ops.push(Op { op: kw.to_vec(), operands: std::mem::take(&mut operands), inline: None, span: s..lx.pos }); } } } out.skipped += operands.len(); out } /// Read an inline image starting after `BI`: (parameters, data, position after `EI`). fn inline_image(data: &[u8], pos: usize) -> (Dict, Vec, usize) { let mut lx = Lexer::new(data, pos); let mut dict = Dict::new(); loop { lx.skip_ws(); if lx.eat_keyword(b"ID") { break; } let Ok(Object::Name(k)) = lx.object() else { return (dict, Vec::new(), data.len()); }; let Ok(v) = lx.object() else { return (dict, Vec::new(), data.len()); }; dict.set(k, v); } // One whitespace byte separates ID from the data. let begin = (lx.pos + 1).min(data.len()); // The data ends at whitespace + `EI` + (whitespace or end). Search for that pattern. let mut i = begin; while i + 2 <= data.len() { if data[i] == b'E' && data.get(i + 1) == Some(&b'I') && (i == begin || is_ws(data[i - 1])) && data.get(i + 2).is_none_or(|b| is_ws(*b) || is_delim(*b)) { let end_data = if i > begin && is_ws(data[i - 1]) { i - 1 } else { i }; return (dict, data[begin..end_data].to_vec(), i + 2); } i += 1; } (dict, data[begin..].to_vec(), data.len()) } /// Write one operator (with a trailing newline). pub fn write_op(op: &Op, out: &mut Vec) { if let Some((dict, img)) = &op.inline { out.extend_from_slice(b"BI"); for (k, v) in dict.iter() { out.push(b' '); serialize(&Object::Name(k.clone()), out); out.push(b' '); serialize(v, out); } out.extend_from_slice(b" ID "); out.extend_from_slice(img); out.extend_from_slice(b"\nEI\n"); return; } for o in &op.operands { serialize(o, out); out.push(b' '); } out.extend_from_slice(&op.op); out.push(b'\n'); } /// Write operators back to a content stream. pub fn serialize_ops(ops: &[Op]) -> Vec { let mut out = Vec::new(); for op in ops { write_op(op, &mut out); } out } /// A page's content streams joined into the one stream they are. Writers may split it between /// any two tokens (ISO 32000-2 §7.8.2), so an operator's operands can end one piece and its /// keyword start the next: parse the pieces joined ([`Pieces::parse`]) and map positions back /// with [`Pieces::piece_of`]. A newline separates the pieces so tokens never run together. #[derive(Clone, Debug, Default, PartialEq)] pub struct Pieces { data: Vec, /// Where each piece ends in `data`, after its separator. ends: Vec, } impl Pieces { pub fn join>(pieces: &[D]) -> Self { let mut data = Vec::new(); let mut ends = Vec::with_capacity(pieces.len()); for p in pieces { data.extend_from_slice(p.as_ref()); data.push(b'\n'); ends.push(data.len()); } Pieces { data, ends } } /// The joined bytes (the spans of [`Pieces::parse`]'s operators index into them). pub fn data(&self) -> &[u8] { &self.data } /// How many pieces there are. pub fn len(&self) -> usize { self.ends.len() } pub fn is_empty(&self) -> bool { self.ends.is_empty() } /// Parse the pieces as one stream. pub fn parse(&self) -> Parsed { parse(&self.data) } /// The piece holding joined position `at` (the last piece for positions past the end). pub fn piece_of(&self, at: usize) -> usize { self.ends.iter().position(|&e| at < e).unwrap_or(self.ends.len().saturating_sub(1)) } /// The pieces an operator spans: (the one its first token is in, the one its keyword is in). pub fn pieces_of(&self, op: &Op) -> (usize, usize) { (self.piece_of(op.span.start), self.piece_of(op.span.end.saturating_sub(1))) } /// The joined range of piece `i`, without its separator. fn range(&self, i: usize) -> std::ops::Range { let start = match i.checked_sub(1) { None => 0, Some(p) => self.ends.get(p).copied().unwrap_or(self.data.len()), }; let end = self.ends.get(i).map_or(self.data.len(), |e| e.saturating_sub(1)).max(start); start..end } /// Rebuild every piece after replacing joined byte ranges: `edits` are (range, bytes) in /// ascending, non-overlapping order. A replacement goes into the piece its range starts in, /// and the rest of a range that runs on into later pieces is removed from them, so an /// operator split across pieces is replaced or removed whole. Everything else is copied byte /// for byte. Returns every piece's new bytes (unchanged pieces come back equal). pub fn splice(&self, edits: impl IntoIterator, Vec)>) -> Vec> { let mut out: Vec> = (0..self.len()).map(|i| Vec::with_capacity(self.range(i).len())).collect(); let copy = |out: &mut Vec>, from: usize, to: usize| { for (i, piece) in out.iter_mut().enumerate() { let r = self.range(i); let (a, b) = (from.max(r.start), to.min(r.end)); if a < b { piece.extend_from_slice(self.data.get(a..b).unwrap_or_default()); } } }; let mut at = 0; for (range, bytes) in edits { let start = range.start.clamp(at, self.data.len()); copy(&mut out, at, start); if !bytes.is_empty() && let Some(piece) = out.get_mut(self.piece_of(start)) { piece.extend_from_slice(&bytes); } at = range.end.clamp(start, self.data.len()); } copy(&mut out, at, self.data.len()); out } } /// A number operand, written as an integer when it is one. pub fn num(v: f64) -> Object { if v.fract() == 0.0 && v.abs() < 1e15 { Object::Int(v as i64) } else { Object::Real((v * 10_000.0).round() / 10_000.0) } } pub fn string(bytes: Vec) -> Object { Object::String(PdfString::literal(bytes)) } /// An affine matrix `[a b c d e f]` (PDF order: a point maps to `(a·x + c·y + e, b·x + d·y + f)`). #[derive(Clone, Copy, Debug, PartialEq)] pub struct Matrix(pub [f64; 6]); impl Default for Matrix { fn default() -> Self { Matrix::IDENTITY } } impl Matrix { pub const IDENTITY: Matrix = Matrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]); pub fn translate(x: f64, y: f64) -> Self { Matrix([1.0, 0.0, 0.0, 1.0, x, y]) } pub fn from_operands(v: &[Object]) -> Option { if v.len() != 6 { return None; } let mut m = [0.0; 6]; for (o, x) in m.iter_mut().zip(v) { *o = x.as_f64()?; } Some(Matrix(m)) } /// `self` then `then` (`self × then` in PDF's row-vector convention). pub fn then(&self, then: &Matrix) -> Matrix { let [a, b, c, d, e, f] = self.0; let [a2, b2, c2, d2, e2, f2] = then.0; Matrix([a * a2 + b * c2, a * b2 + b * d2, c * a2 + d * c2, c * b2 + d * d2, e * a2 + f * c2 + e2, e * b2 + f * d2 + f2]) } pub fn apply(&self, x: f64, y: f64) -> (f64, f64) { let [a, b, c, d, e, f] = self.0; (a * x + c * y + e, b * x + d * y + f) } pub fn invert(&self) -> Option { let [a, b, c, d, e, f] = self.0; let det = a * d - b * c; if det.abs() < 1e-12 || !det.is_finite() { return None; } let (ia, ib, ic, id) = (d / det, -b / det, -c / det, a / det); Some(Matrix([ia, ib, ic, id, -(e * ia + f * ic), -(e * ib + f * id)])) } /// The bounding box of a rectangle `[x0 y0 x1 y1]` after this transform. pub fn bbox(&self, r: [f64; 4]) -> [f64; 4] { let pts = [self.apply(r[0], r[1]), self.apply(r[2], r[1]), self.apply(r[0], r[3]), self.apply(r[2], r[3])]; let mut b = [f64::MAX, f64::MAX, f64::MIN, f64::MIN]; for (x, y) in pts { b = [b[0].min(x), b[1].min(y), b[2].max(x), b[3].max(y)]; } b } } /// Do two rectangles overlap by more than `eps` in both directions? pub fn overlaps(a: [f64; 4], b: [f64; 4], eps: f64) -> bool { a[0].max(b[0]) + eps < a[2].min(b[2]) && a[1].max(b[1]) + eps < a[3].min(b[3]) } /// Is `inner` inside `outer` (within `eps`)? pub fn contains(outer: [f64; 4], inner: [f64; 4], eps: f64) -> bool { inner[0] >= outer[0] - eps && inner[1] >= outer[1] - eps && inner[2] <= outer[2] + eps && inner[3] <= outer[3] + eps }