When a key in an inline image dictionary was not a name (`BI /W 4 H 4 ID ... EI`), the parser ended the image at the end of the stream and counted nothing as skipped, so every operator after the image was lost: text_lines found no text on a page whose text follows such an image. An image whose parameters can't be read is now skipped up to the EI that ends it, the skipped bytes are counted, and parsing resumes after it. Without an EI the image still runs to the end of the stream, as before. Adds tests::an_unreadable_inline_image_is_skipped_to_its_end, which fails before this change.
This commit is contained in:
parent
2ee8ce1ac1
commit
240ea53b8b
|
|
@ -125,12 +125,19 @@ pub fn parse(data: &[u8]) -> Parsed {
|
|||
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();
|
||||
}
|
||||
b"BI" => match inline_image(data, lx.pos) {
|
||||
Ok((dict, img, end)) => {
|
||||
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();
|
||||
}
|
||||
Err(end) => {
|
||||
out.skipped += end.saturating_sub(at);
|
||||
lx.pos = end;
|
||||
start = None;
|
||||
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 });
|
||||
|
|
@ -141,8 +148,9 @@ pub fn parse(data: &[u8]) -> Parsed {
|
|||
out
|
||||
}
|
||||
|
||||
/// Read an inline image starting after `BI`: (parameters, data, position after `EI`).
|
||||
fn inline_image(data: &[u8], pos: usize) -> (Dict, Vec<u8>, usize) {
|
||||
/// Read an inline image starting after `BI`: (parameters, data, position after `EI`). `Err` holds
|
||||
/// the position after the `EI` of an image whose parameters can't be read.
|
||||
fn inline_image(data: &[u8], pos: usize) -> Result<(Dict, Vec<u8>, usize), usize> {
|
||||
let mut lx = Lexer::new(data, pos);
|
||||
let mut dict = Dict::new();
|
||||
loop {
|
||||
|
|
@ -150,17 +158,31 @@ fn inline_image(data: &[u8], pos: usize) -> (Dict, Vec<u8>, usize) {
|
|||
if lx.eat_keyword(b"ID") {
|
||||
break;
|
||||
}
|
||||
let Ok(Object::Name(k)) = lx.object() else {
|
||||
return (dict, Vec::new(), data.len());
|
||||
let at = lx.pos;
|
||||
let entry = match lx.object() {
|
||||
Ok(Object::Name(k)) => lx.object().ok().map(|v| (k, v)),
|
||||
_ => None,
|
||||
};
|
||||
let Ok(v) = lx.object() else {
|
||||
return (dict, Vec::new(), data.len());
|
||||
let Some((k, v)) = entry else {
|
||||
// Without an `EI` after it, the image runs to the end of the stream, as before.
|
||||
return match end_of_image(data, at) {
|
||||
Some((_, end)) => Err(end),
|
||||
None => Ok((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.
|
||||
Ok(match end_of_image(data, begin) {
|
||||
Some((end_data, end)) => (dict, data[begin..end_data].to_vec(), end),
|
||||
None => (dict, data[begin..].to_vec(), data.len()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Where inline image data starting at `begin` ends: (end of the data, position after `EI`). The
|
||||
/// data ends at whitespace + `EI` + (whitespace, a delimiter or the end).
|
||||
fn end_of_image(data: &[u8], begin: usize) -> Option<(usize, usize)> {
|
||||
let mut i = begin;
|
||||
while i + 2 <= data.len() {
|
||||
if data[i] == b'E'
|
||||
|
|
@ -169,11 +191,11 @@ fn inline_image(data: &[u8], pos: usize) -> (Dict, Vec<u8>, usize) {
|
|||
&& 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);
|
||||
return Some((end_data, i + 2));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
(dict, data[begin..].to_vec(), data.len())
|
||||
None
|
||||
}
|
||||
|
||||
/// Write one operator (with a trailing newline).
|
||||
|
|
|
|||
|
|
@ -48,6 +48,20 @@ fn damage_is_skipped_not_fatal() {
|
|||
assert!(parse(b"BI /W 1").ops.len() == 1);
|
||||
}
|
||||
|
||||
/// Issue #1318: an inline image whose dictionary can't be read is skipped up to its `EI`, counted,
|
||||
/// and what follows it is parsed.
|
||||
#[test]
|
||||
fn an_unreadable_inline_image_is_skipped_to_its_end() {
|
||||
let src = b"q BI /W 4 H 4 ID aa EI Q BT (After) Tj ET";
|
||||
let p = parse(src);
|
||||
let names: Vec<&[u8]> = p.ops.iter().map(|o| o.op.as_slice()).collect();
|
||||
assert_eq!(names, [&b"q"[..], b"Q", b"BT", b"Tj", b"ET"]);
|
||||
assert_eq!(p.skipped, b"BI /W 4 H 4 ID aa EI".len());
|
||||
let control = parse(b"q BI /W 4 /H 4 ID aa EI Q BT (After) Tj ET");
|
||||
assert_eq!(control.skipped, 0);
|
||||
assert_eq!(control.ops.len(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrices_compose_and_invert() {
|
||||
let a = Matrix([2.0, 0.0, 0.0, 3.0, 10.0, 20.0]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue