mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Render: cap a stroke's dash count so a tiny dash on a huge shape can't abort on open
This commit is contained in:
@@ -1442,6 +1442,7 @@ pub(crate) fn stroke(
|
||||
if let Some(d) = &line.dash {
|
||||
let pat: Vec<f64> = d.pattern().iter().map(|v| v * lw.max(0.75)).collect();
|
||||
let pat = dash_with_caps(pat, cap, sw);
|
||||
let pat = limit_dash_count(path, pat);
|
||||
if !pat.is_empty() && pat.iter().all(|v| *v > 0.0) {
|
||||
if cap == kurbo::Cap::Square {
|
||||
sk = sk.with_caps(kurbo::Cap::Butt);
|
||||
@@ -1582,6 +1583,30 @@ fn dash_with_caps(mut pat: Vec<f64>, cap: kurbo::Cap, width: f64) -> Vec<f64> {
|
||||
pat
|
||||
}
|
||||
|
||||
/// The most dashes one stroke is cut into. Stroking expands every dash into its own outline, so a
|
||||
/// tiny dash on a huge shape (a 1 EMU-wide `custDash` on a 2147483647 EMU rectangle) would
|
||||
/// otherwise need gigabytes and abort the app when the slide is drawn. Past this count the
|
||||
/// pattern is scaled up evenly, which looks the same: the dashes are far below a pixel by then.
|
||||
const MAX_DASHES: f64 = 20_000.0;
|
||||
|
||||
fn limit_dash_count(path: &BezPath, pat: Vec<f64>) -> Vec<f64> {
|
||||
use kurbo::ParamCurveArclen;
|
||||
let period: f64 = pat.iter().sum();
|
||||
if !(period.is_finite() && period > 0.0) {
|
||||
return pat;
|
||||
}
|
||||
let len: f64 = path.segments().map(|s| s.arclen(1e-2)).sum();
|
||||
let dashes = len / period;
|
||||
if !len.is_finite() {
|
||||
return vec![];
|
||||
}
|
||||
if dashes <= MAX_DASHES {
|
||||
return pat;
|
||||
}
|
||||
let k = dashes / MAX_DASHES;
|
||||
pat.into_iter().map(|v| v * k).collect()
|
||||
}
|
||||
|
||||
fn end_points(path: &BezPath) -> Option<((Point, Point), (Point, Point))> {
|
||||
let els: Vec<PathEl> = path.elements().to_vec();
|
||||
let mut pts: Vec<Point> = vec![];
|
||||
|
||||
@@ -744,3 +744,31 @@ fn a_3d_shapes_face_is_lit_even_without_a_bevel() {
|
||||
let img = render(vec![shape(r#"<a:scene3d><a:camera prst="orthographicFront"/><a:lightRig rig="threePt" dir="t"/></a:scene3d>"#)]);
|
||||
assert!(close(img.pixel(175, 150), base, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tiny_dash_on_a_huge_shape_draws_without_running_out_of_memory() {
|
||||
// Found by fuzzing the pptx round trip: a 1 EMU `custDash` on a 2147483647 EMU square asked
|
||||
// the stroker for hundreds of thousands of dashes and aborted on a 7.5 GB allocation.
|
||||
let sh = Shape {
|
||||
id: ShapeId(12),
|
||||
xfrm: Some(Xfrm::new(0.0, 0.0, 169_093.0, 169_093.0)),
|
||||
geom: Geom::preset("rect"),
|
||||
line: Some(Line {
|
||||
fill: solid(RED),
|
||||
width: Some(0.0001),
|
||||
dash: Some(deckcraft_model::style::Dash::Custom { pattern: vec![(1.0, 1.0)] }),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let img = render_slide(&deck_with(vec![sh]), 0, &RenderOpts { scale: 0.25, ..Default::default() });
|
||||
assert!(img.width > 0);
|
||||
// The pattern is scaled up only past the limit; ordinary lines keep theirs.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((0.0, 0.0));
|
||||
p.line_to((100.0, 0.0));
|
||||
assert_eq!(limit_dash_count(&p, vec![1.0, 1.0]), vec![1.0, 1.0]);
|
||||
p.line_to((4.0e6, 0.0));
|
||||
let pat = limit_dash_count(&p, vec![1.0, 1.0]);
|
||||
assert!((4.0e6 / (pat[0] + pat[1]) - MAX_DASHES).abs() < 1.0, "{pat:?}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user