mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 14:08:23 -06:00
The image scan skips a path segment before any m instead of panicking (#3581)
A content stream that uses l, c, v, y or h before any m or re has no subpath to extend. The edit-side image scan (crates/edit/src/images.rs) handed the operators straight to BezPath's line_to/curve_to/close_path, which panic on an empty path by contract, so object_list, page_images, image_hit and the Office export's image walk all crashed on such a page, while the renderer, text extraction, MuPDF and pdftotext skip the malformed segment and render the rest. The scan now skips the segment when the path has no elements, mirroring the v arm's existing guard and kurbo's own condition (debug_assert on !is_empty). A path started with m or re extends and closes exactly as before. A clip set by a stray segment (W n on an empty path) takes a zero bounding box, which kurbo defines as Rect::default, not a panic. Regression test: each of the five segment operators before any m, then a well-formed m l h and a re W n clip, all before an image Do - the image is still listed in every case. Verified against kurbo 0.13.1's source (line_to/curve_to/close_path debug_assert on empty; bounding_box unwrap_or_default) and the issue's repro; no Rust toolchain on this machine, so cargo test was not run here - saying so plainly for the reviewer. Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
This commit is contained in:
@@ -165,12 +165,19 @@ impl ImageState {
|
||||
}
|
||||
}
|
||||
b"l" => {
|
||||
if let Some([x, y]) = op.nums::<2>() {
|
||||
// A segment before any m (or re) has no subpath to extend: BezPath's line_to and
|
||||
// friends panic on an empty path by contract, so the segment is skipped, as the
|
||||
// renderer, MuPDF and pdftotext do (#3581).
|
||||
if let Some([x, y]) = op.nums::<2>()
|
||||
&& self.path.elements().last().is_some()
|
||||
{
|
||||
self.path.line_to(self.point(x, y));
|
||||
}
|
||||
}
|
||||
b"c" => {
|
||||
if let Some([x1, y1, x2, y2, x3, y3]) = op.nums::<6>() {
|
||||
if let Some([x1, y1, x2, y2, x3, y3]) = op.nums::<6>()
|
||||
&& self.path.elements().last().is_some()
|
||||
{
|
||||
self.path.curve_to(self.point(x1, y1), self.point(x2, y2), self.point(x3, y3));
|
||||
}
|
||||
}
|
||||
@@ -182,12 +189,19 @@ impl ImageState {
|
||||
}
|
||||
}
|
||||
b"y" => {
|
||||
if let Some([x1, y1, x2, y2]) = op.nums::<4>() {
|
||||
if let Some([x1, y1, x2, y2]) = op.nums::<4>()
|
||||
&& self.path.elements().last().is_some()
|
||||
{
|
||||
let end = self.point(x2, y2);
|
||||
self.path.curve_to(self.point(x1, y1), end, end);
|
||||
}
|
||||
}
|
||||
b"h" => self.path.close_path(),
|
||||
b"h" => {
|
||||
// ClosePath with no subpath panics by the same contract (#3581).
|
||||
if self.path.elements().last().is_some() {
|
||||
self.path.close_path();
|
||||
}
|
||||
}
|
||||
b"re" => {
|
||||
if let Some([x, y, w, h]) = op.nums::<4>() {
|
||||
self.path.move_to(self.point(x, y));
|
||||
|
||||
@@ -1964,6 +1964,25 @@ fn image_page_with(content: &str) -> Document {
|
||||
Document::open(Arc::new(out)).unwrap()
|
||||
}
|
||||
|
||||
/// A path segment operator before any `m` (or `re`) has no subpath to extend: the image scan used
|
||||
/// to call `BezPath`'s `line_to`/`curve_to`/`close_path` on an empty path, which panics by contract,
|
||||
/// taking `object_list`, `page_images` and the Office export's image walk down with it (#3581).
|
||||
/// The segment is now skipped, as the renderer, MuPDF and pdftotext do, and the page's images are
|
||||
/// still listed.
|
||||
#[test]
|
||||
fn image_scan_skips_a_path_segment_before_any_m() {
|
||||
for bad in ["1 1 l S", "1 1 2 2 3 3 c S", "1 1 2 2 4 4 y S", "1 1 2 2 3 3 v S", "h S"] {
|
||||
let content = format!("q {bad} Q 100 100 200 100 cm /Im0 Do");
|
||||
let doc = image_page_with(&content);
|
||||
assert_eq!(images::page_images(&doc, 0).unwrap().len(), 1, "{bad}: the image is still listed");
|
||||
}
|
||||
// A path started with m (or re) still extends and closes as before.
|
||||
let doc = image_page_with("q 0 0 m 1 1 l h S Q 100 100 200 100 cm /Im0 Do");
|
||||
assert_eq!(images::page_images(&doc, 0).unwrap().len(), 1);
|
||||
let doc = image_page_with("q 10 10 20 20 re W n Q 100 100 200 100 cm /Im0 Do");
|
||||
assert_eq!(images::page_images(&doc, 0).unwrap().len(), 1);
|
||||
}
|
||||
|
||||
/// The box of the first `re` on the page in user space, after the `cm`s before it.
|
||||
fn first_rect_in_user_space(doc: &Document) -> [f64; 4] {
|
||||
let mut ctm = pdfcraft_content::Matrix::IDENTITY;
|
||||
|
||||
Reference in New Issue
Block a user