diff --git a/crates/pdf/src/import/state.rs b/crates/pdf/src/import/state.rs index e5011e0ca..0f02e7a26 100644 --- a/crates/pdf/src/import/state.rs +++ b/crates/pdf/src/import/state.rs @@ -245,6 +245,8 @@ pub(super) fn stroke_layer(paint: Paint, opacity: f32, p: &StrokeProps, scale: f /// The most entries of a dash array kept (an even count keeps it a pattern of dashes and gaps). const MAX_DASH_ENTRIES: usize = 64; +/// Most dash periods [`stroke_outline`] cuts a stroke into. +const MAX_OUTLINE_DASHES: f64 = 1e5; /// The area stroke `p` paints along `path` (in its own space, before `transform`), in the document: /// where a shading painting the stroke shows. Width, caps, joins and dashes as [`stroke_layer`] @@ -258,11 +260,14 @@ pub(super) fn stroke_outline(path: &BezPath, transform: Affine, p: &StrokeProps) kurbo::Stroke::new(line_width(p, scale) / scale).with_join(p.line_join).with_caps(p.line_cap).with_miter_limit(p.miter_limit as f64); let dashes: Vec = p.dash_array.iter().take(MAX_DASH_ENTRIES).map(|v| *v as f64).collect(); let dash = Dash { pattern: dashes.clone(), offset: p.dash_offset as f64, align_corners: false }; - if dash.is_dashed() { + // A dash pattern far finer than the path would cut it into countless pieces: it strokes solid. + let period: f64 = dashes.iter().sum(); + if dash.is_dashed() && period > 0.0 && kurbo::Shape::perimeter(path, 1e-3) / period < MAX_OUTLINE_DASHES { style = style.with_dashes(dash.offset, dashes); } - // To a hundredth of a point in the document. - let mut out = kurbo::stroke(path.iter(), &style, &kurbo::StrokeOpts::default(), 0.01 / scale); + // To a hundredth of a point in the document, within reason at extreme scales. + let tolerance = (0.01 / scale).clamp(1e-4, 1.0); + let mut out = kurbo::stroke(path.iter(), &style, &kurbo::StrokeOpts::default(), tolerance); out.apply_affine(transform); (!out.elements().is_empty()).then_some(out) } @@ -301,3 +306,22 @@ pub(super) fn resize_alpha(a: &LumaData, w: u32, h: u32) -> Vec { None => vec![255; (w * h) as usize], } } + +#[cfg(test)] +mod stroke_outline_tests { + use super::*; + + /// A dash pattern far finer than the path strokes it solid instead of cutting it into + /// millions of pieces; one in reason dashes it. + #[test] + fn a_dash_far_finer_than_the_path_strokes_solid() { + let mut path = BezPath::new(); + path.move_to((0.0, 0.0)); + path.line_to((1000.0, 0.0)); + let props = |dash: f32| StrokeProps { line_width: 2.0, dash_array: [dash; 4].into(), ..StrokeProps::default() }; + let solid = stroke_outline(&path, Affine::IDENTITY, &props(1e-6)).unwrap(); + assert!(solid.elements().len() < 20, "{} elements", solid.elements().len()); + let dashed = stroke_outline(&path, Affine::IDENTITY, &props(10.0)).unwrap(); + assert!(dashed.elements().len() > 100, "dashed: {} elements", dashed.elements().len()); + } +}