Never crash: PDF close-path operators and CFF outlines without a current point
The s, b and b* operators closed the path even when there was no current point, which kurbo asserts against (a panic in debug builds; found by fuzzing). Close only when a subpath is open. CFF charstrings that draw before their first rmoveto now start the subpath at the current point, as Type 1 charstrings already did. Denies the panic lints in the crate (it had no panic sites). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
10644a83aa
commit
8e4e29844e
|
|
@ -0,0 +1,4 @@
|
|||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
allow-panic-in-tests = true
|
||||
allow-indexing-slicing-in-tests = true
|
||||
|
|
@ -646,12 +646,21 @@ impl T2<'_> {
|
|||
self.path.move_to((self.x, self.y));
|
||||
self.open = true;
|
||||
}
|
||||
/// A segment before the first moveto starts at the current point.
|
||||
fn ensure_open(&mut self) {
|
||||
if !self.open {
|
||||
self.path.move_to((self.x, self.y));
|
||||
self.open = true;
|
||||
}
|
||||
}
|
||||
fn line_to(&mut self, dx: f64, dy: f64) {
|
||||
self.ensure_open();
|
||||
self.x += dx;
|
||||
self.y += dy;
|
||||
self.path.line_to((self.x, self.y));
|
||||
}
|
||||
fn curve(&mut self, d: [f64; 6]) {
|
||||
self.ensure_open();
|
||||
let (x1, y1) = (self.x + d[0], self.y + d[1]);
|
||||
let (x2, y2) = (x1 + d[2], y1 + d[3]);
|
||||
self.x = x2 + d[4];
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
//! children are the document's **layers**: one group per top-level optional-content group
|
||||
//! (Illustrator layers), with content outside them gathered into groups of its own.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
mod build;
|
||||
mod ccitt;
|
||||
mod cff;
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ impl<'a> Interp<'a> {
|
|||
// Painting.
|
||||
"S" => self.paint(false, None, true, depth),
|
||||
"s" => {
|
||||
self.path.close_path();
|
||||
self.close_subpath();
|
||||
self.paint(false, None, true, depth)
|
||||
}
|
||||
"f" | "F" => self.paint(true, Some(FillRule::NonZero), false, depth),
|
||||
|
|
@ -340,11 +340,11 @@ impl<'a> Interp<'a> {
|
|||
"B" => self.paint(true, Some(FillRule::NonZero), true, depth),
|
||||
"B*" => self.paint(true, Some(FillRule::EvenOdd), true, depth),
|
||||
"b" => {
|
||||
self.path.close_path();
|
||||
self.close_subpath();
|
||||
self.paint(true, Some(FillRule::NonZero), true, depth)
|
||||
}
|
||||
"b*" => {
|
||||
self.path.close_path();
|
||||
self.close_subpath();
|
||||
self.paint(true, Some(FillRule::EvenOdd), true, depth)
|
||||
}
|
||||
"n" => self.paint(false, None, false, depth),
|
||||
|
|
@ -549,6 +549,13 @@ impl<'a> Interp<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Close the current subpath; nothing to close without a current point.
|
||||
fn close_subpath(&mut self) {
|
||||
if self.cur.is_some() {
|
||||
self.path.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_start(&mut self) {
|
||||
if self.cur.is_none() {
|
||||
self.path.move_to((0.0, 0.0));
|
||||
|
|
|
|||
|
|
@ -295,3 +295,12 @@ fn truncated_and_corrupt_eps_never_panic() {
|
|||
assert!(r.is_ok(), "{}", String::from_utf8_lossy(prog));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_an_empty_path_is_a_no_op() {
|
||||
// "s", "b" and "b*" closed the path without a current point (kurbo asserts on that).
|
||||
for ops in ["s", "b", "b*", "0 0 m 50 0 l 50 50 l b s b*", "f s 10 10 m 20 20 l s"] {
|
||||
let doc = parse(&page_pdf(ops, "", vec![])).unwrap();
|
||||
let _ = render(&doc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue