Merge pull request #2696 from Silvertree2010/fix/2674-pdf-import-lines-with-line-width

Import PDF 0 w strokes as hairlines instead of dropping them
This commit is contained in:
BFlat
2026-10-17 20:02:28 +07:00
committed by GitHub
2 changed files with 25 additions and 3 deletions
+12 -3
View File
@@ -951,8 +951,17 @@ fn mean_scale(a: Affine) -> f64 {
a.determinant().abs().sqrt()
}
/// Width a PDF line width of 0 imports as, points: a print hairline. In PDF `0 w` is the thinnest
/// line the device can draw (ISO 32000-1, 8.4.3.2), not no line.
const HAIRLINE: f64 = 0.25;
/// Document width of the stroke `p` drawn at `scale`: `0 w` becomes a [`HAIRLINE`].
fn line_width(p: &StrokeProps, scale: f64) -> f64 {
if p.line_width == 0.0 { HAIRLINE } else { p.line_width as f64 * scale }
}
fn stroke_layer(paint: Paint, opacity: f32, p: &StrokeProps, scale: f64) -> StrokeLayer {
let mut st = StrokeLayer::new(paint, p.line_width as f64 * scale);
let mut st = StrokeLayer::new(paint, line_width(p, scale));
st.opacity = opacity;
st.cap = match p.line_cap {
kurbo::Cap::Butt => LineCap::Butt,
@@ -1733,7 +1742,7 @@ impl<'p> Builder<'p> {
None => named(&info.face),
};
let (paint, opacity) = self.paint(paint, stroke.is_some(), self.walked(true));
let stroke = stroke.map(|p| (paint.clone(), p.line_width as f64 * scale));
let stroke = stroke.map(|p| (paint.clone(), line_width(p, scale)));
let look = Look {
font: key,
family: info.family,
@@ -1850,7 +1859,7 @@ impl<'a> Device<'a> for Builder<'_> {
let (paint, opacity) = self.paint(paint, stroke, walked);
// A gradient that doesn't extend past its ends paints only between them.
let width = match draw_mode {
PathDrawMode::Stroke(p) => p.line_width as f64 * mean_scale(transform),
PathDrawMode::Stroke(p) => line_width(p, mean_scale(transform)),
PathDrawMode::Fill(_) => 0.0,
};
let band = shading.and_then(|sp| extend_clip(sp, bp.bounding_box().inflate(width, width)));
+13
View File
@@ -644,3 +644,16 @@ fn clips_with_the_page_as_bounding_box_still_clip() {
assert!(!clipped("0 0 100 100 re"), "the page");
assert!(!clipped("50 -60 m 160 50 l 50 160 l -60 50 l h"), "a turned square around the page");
}
/// Issue #2674: `0 w` is the thinnest line the device can draw (ISO 32000-1, 8.4.3.2), not no
/// line: it imports as a visible hairline and survives a PDF round trip.
#[test]
fn zero_width_lines_import_as_hairlines() {
let d = open(&one_page("0 0 0 RG 0 w 10 50 m 90 50 l S 0.9 g 0 w 20 60 30 20 re b 1 w 10 20 m 90 20 l S", "", &[]));
let widths = |d: &Document| -> Vec<f64> { all(d).iter().filter_map(|n| n.appearance.stroke().map(|s| s.width)).collect() };
let w = widths(&d);
assert_eq!(w.len(), 3, "{w:?}");
assert!(w.iter().all(|w| *w > 0.0 && *w <= 1.0), "zero width strokes become hairlines: {w:?}");
let back = widths(&roundtrip(&d));
assert_eq!(back.len(), 3, "hairlines survive the PDF round trip: {back:?}");
}