Merge pull request #1332 from dotCooCoo/feat/eps-setdash-cap
EPS: loops under a long dash array no longer hang the import
This commit is contained in:
commit
3c5d097458
|
|
@ -26,6 +26,9 @@ const HAIRLINE: f64 = 0.25;
|
|||
const MAX_INKS: usize = 32;
|
||||
/// Most dashes `strokepath` makes of a path (more and it outlines the path undashed).
|
||||
const MAX_DASHES: f64 = 1e5;
|
||||
/// Most entries a dash pattern has: the most elements an array has in a typical PostScript
|
||||
/// implementation (PLRM 3rd ed., Table B.1).
|
||||
const MAX_DASH_ENTRIES: usize = 65_535;
|
||||
/// Most entries an indexed colour space's table has.
|
||||
const MAX_HIVAL: usize = 4095;
|
||||
/// Most elements the current path may have.
|
||||
|
|
@ -121,7 +124,8 @@ pub(crate) struct GState {
|
|||
pub cap: LineCap,
|
||||
pub join: LineJoin,
|
||||
pub miter: f64,
|
||||
pub dash: Vec<f64>,
|
||||
/// The dash pattern, shared by the states saved or copied from this one.
|
||||
pub dash: Rc<[f64]>,
|
||||
pub dash_offset: f64,
|
||||
/// The current path, in document space.
|
||||
pub path: BezPath,
|
||||
|
|
@ -151,7 +155,7 @@ impl Default for GState {
|
|||
cap: LineCap::Butt,
|
||||
join: LineJoin::Miter,
|
||||
miter: 10.0,
|
||||
dash: vec![],
|
||||
dash: Rc::from([]),
|
||||
dash_offset: 0.0,
|
||||
path: BezPath::new(),
|
||||
cur: None,
|
||||
|
|
@ -564,16 +568,19 @@ impl Interp<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn stroke_path(&mut self, bp: BezPath) {
|
||||
fn stroke_path(&mut self, bp: BezPath) -> Res {
|
||||
if bp.elements().is_empty() || self.g.paint.is_none() || self.g.null {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
let st = self.stroke_layer();
|
||||
let st = self.stroke_layer()?;
|
||||
self.out.stroke(bp, st, &self.g.clips);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The stroke the graphics state draws, in document space.
|
||||
fn stroke_layer(&self) -> StrokeLayer {
|
||||
/// The stroke the graphics state draws, in document space. Each entry of the dash pattern it
|
||||
/// copies counts as an operation.
|
||||
fn stroke_layer(&mut self) -> Res<StrokeLayer> {
|
||||
self.spend(self.g.dash.len() as u64)?;
|
||||
let k = self.scale();
|
||||
let w = self.g.width * k;
|
||||
let mut st = StrokeLayer::new(self.g.paint.clone(), if w > 0.0 && w.is_finite() { w } else { HAIRLINE });
|
||||
|
|
@ -584,7 +591,7 @@ impl Interp<'_> {
|
|||
let pattern: Vec<f64> = self.g.dash.iter().map(|d| d * k).collect();
|
||||
let dash = Dash { pattern, offset: self.g.dash_offset * k, align_corners: false };
|
||||
st.dash = dash.is_dashed().then_some(dash);
|
||||
st
|
||||
Ok(st)
|
||||
}
|
||||
|
||||
/// The current path in user space, each element as its operator and points (quadratic
|
||||
|
|
@ -707,10 +714,7 @@ impl Interp<'_> {
|
|||
}
|
||||
let bp = self.take_path();
|
||||
match op {
|
||||
Op::UStroke => {
|
||||
self.stroke_path(bp);
|
||||
Ok(())
|
||||
}
|
||||
Op::UStroke => self.stroke_path(bp),
|
||||
_ => self.fill_path(bp, if op == Op::UEoFill { FillRule::EvenOdd } else { FillRule::NonZero }),
|
||||
}
|
||||
});
|
||||
|
|
@ -1201,7 +1205,7 @@ impl Interp<'_> {
|
|||
ExecForm => self.exec_form()?,
|
||||
StrokePath => {
|
||||
let bp = self.take_path();
|
||||
let mut st = self.stroke_layer();
|
||||
let mut st = self.stroke_layer()?;
|
||||
// A dash pattern far finer than the path would make countless pieces.
|
||||
let period: f64 = st.dash.as_ref().map_or(0.0, |d| d.pattern.iter().sum());
|
||||
let periods = if st.dash.is_some() && period > 0.0 { bp.perimeter(1e-3) / period } else { 0.0 };
|
||||
|
|
@ -1312,11 +1316,18 @@ impl Interp<'_> {
|
|||
SetDash => {
|
||||
let offset = self.pop_num()?;
|
||||
let items = self.pop_array()?;
|
||||
if items.len() > MAX_DASH_ENTRIES {
|
||||
return ps_err("limitcheck", "");
|
||||
}
|
||||
// Each entry read counts as an operation.
|
||||
self.spend(items.len() as u64)?;
|
||||
let v: Option<Vec<f64>> = items.borrow().iter().map(|o| o.as_num().filter(|v| v.is_finite() && *v >= 0.0)).collect();
|
||||
self.g.dash = v.ok_or(PsError::Ps("typecheck", String::new()))?;
|
||||
self.g.dash = v.ok_or(PsError::Ps("typecheck", String::new()))?.into();
|
||||
self.g.dash_offset = offset;
|
||||
}
|
||||
CurrentDash => {
|
||||
// The array made counts against the memory budget.
|
||||
self.alloc(self.g.dash.len() * std::mem::size_of::<Obj>())?;
|
||||
let d = self.g.dash.iter().map(|v| Obj::Real(*v)).collect();
|
||||
self.push(Obj::array(d))?;
|
||||
self.push_num(self.g.dash_offset)?;
|
||||
|
|
@ -1651,7 +1662,7 @@ impl Interp<'_> {
|
|||
}
|
||||
Stroke => {
|
||||
let bp = self.take_path();
|
||||
self.stroke_path(bp);
|
||||
self.stroke_path(bp)?;
|
||||
}
|
||||
RectFill => {
|
||||
let bp = self.rects()?;
|
||||
|
|
@ -1667,7 +1678,7 @@ impl Interp<'_> {
|
|||
let xf = self.xf();
|
||||
bp.apply_affine(xf * m * xf.inverse());
|
||||
}
|
||||
self.stroke_path(bp);
|
||||
self.stroke_path(bp)?;
|
||||
}
|
||||
ShFill => {
|
||||
let d = self.pop_dict()?;
|
||||
|
|
|
|||
|
|
@ -171,6 +171,41 @@ fn line_state_reaches_the_stroke() {
|
|||
assert!(stroke(&objects(&read("0 setlinewidth 0 0 moveto 9 9 lineto stroke").document)[0]).unwrap().width > 0.0);
|
||||
}
|
||||
|
||||
/// `setdash` takes a dash pattern of up to 65,535 entries, the most elements an array has in a
|
||||
/// typical implementation (PLRM 3rd ed., Table B.1), and raises `limitcheck` for a longer one.
|
||||
#[test]
|
||||
fn dash_patterns_hold_at_most_65535_entries() {
|
||||
let dash = |n: usize| format!("/a {n} array def 0 1 {} {{ a exch 1 put }} for a 0 setdash", n - 1);
|
||||
check(&format!("{} currentdash pop length 65535 eq", dash(65535)));
|
||||
let r = read(&format!("0 0 10 10 rectfill {}", dash(65536)));
|
||||
assert_eq!(objects(&r.document).len(), 1, "{:?}", r.warnings);
|
||||
assert!(r.warnings.iter().any(|w| w.contains("limitcheck in `setdash`")), "{:?}", r.warnings);
|
||||
}
|
||||
|
||||
/// Saved and copied graphics states share the dash pattern, and `setdash`, `currentdash` and
|
||||
/// each stroke count the entries they read or copy against the budgets. Under a pattern of
|
||||
/// 65,535 entries, 4,000,000 passes of `gsave grestore` and 2,000,000 passes of
|
||||
/// `gstate currentgstate setgstate` finish, and loops of `setdash`, `currentdash` and `strokepath`
|
||||
/// run out of a budget.
|
||||
#[test]
|
||||
fn work_on_the_longest_dash_pattern_ends_soon() {
|
||||
let dash = "0 0 10 10 rectfill /a 65535 array def 0 1 65534 { a exch 1 put } for a 0 setdash";
|
||||
for body in ["4000000 { gsave grestore } repeat", "2000000 { gstate currentgstate setgstate } repeat"] {
|
||||
let r = read_soon(&format!("{dash} {body} 20 20 10 10 rectfill"));
|
||||
assert_eq!(objects(&r.document).len(), 2, "{body}: {:?}", r.warnings);
|
||||
assert!(r.warnings.is_empty(), "{body}: {:?}", r.warnings);
|
||||
}
|
||||
for (body, why) in [
|
||||
("{ a 0 setdash } loop", "runs too long"),
|
||||
("{ currentdash pop pop } loop", "uses too much memory"),
|
||||
("{ newpath 0 0 moveto 100 0 lineto strokepath } loop", "runs too long"),
|
||||
] {
|
||||
let r = read_soon(&format!("{dash} {body}"));
|
||||
assert_eq!(objects(&r.document).len(), 1, "{body}: {:?}", r.warnings);
|
||||
assert!(r.warnings.iter().any(|w| w.contains(why)), "{body}: {:?}", r.warnings);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn procedures_bind_def_loops_and_dictionaries() {
|
||||
let r = read(
|
||||
|
|
|
|||
Loading…
Reference in New Issue