Hatch: a view covering a pattern hatch leaves its lines exactly as they are (#762)

With a view, each span of a pattern line was cut to the view's extent
intersected with the boundary's. Spans already lie within the boundary,
and the boundary extent comes from rotating the corners directly while a
span end comes from interpolating along an edge, so where a line passes
through a corner the two differ by a rounding error and the cut moved the
end (by 3e-16 on Windows, whose sin/cos round differently). The spans are
now cut to the view's extent only, so normal_zoom_pattern_is_unchanged
holds on every platform.
This commit is contained in:
Books authored and GitHub committed 2026-10-13 04:34:30 -04:00
1 parent f5d58913cc
commit 488308a2de
1 file changed
+11 -4
+11 -4
View File
@@ -409,9 +409,12 @@ impl Family<'_> {
if bb.is_empty() {
return;
}
// The lines and spans swept: the boundary's extent, cut to the view's (its corners in
// this frame) when there is one.
// The lines swept: the boundary's extent, cut to the view's (its corners in this frame)
// when there is one. Spans are cut to the view's extent only: they already lie within the
// boundary, and cutting them to its extent too would move an end that sits on a boundary
// corner by a rounding error, so a view covering the hatch would change its lines.
let (mut lo, mut hi) = (bb.min, bb.max);
let mut span_cut = None;
if let Some(v) = view {
let vb = Bounds2::from_points(v.corners().iter().map(|p| self.to_local(*p)));
if vb.is_empty() || !vb.intersects(&bb) {
@@ -419,6 +422,7 @@ impl Family<'_> {
}
lo = Vec2::new(lo.x.max(vb.min.x), lo.y.max(vb.min.y));
hi = Vec2::new(hi.x.min(vb.max.x), hi.y.min(vb.max.y));
span_cut = Some((vb.min.x, vb.max.x));
}
let shift_per_line = self.delta.rotate(-self.ang).x;
let k0 = (lo.y / self.spacing).floor() as i64;
@@ -451,8 +455,11 @@ impl Family<'_> {
let offset = shift_per_line * k as f64;
for span in xs.chunks(2) {
let [x0, x1] = span else { continue };
let (x0, x1) = if view.is_some() { (x0.max(lo.x), x1.min(hi.x)) } else { (*x0, *x1) };
if view.is_some() && x1 <= x0 {
let (x0, x1) = match span_cut {
Some((a, b)) => (x0.max(a), x1.min(b)),
None => (*x0, *x1),
};
if span_cut.is_some() && x1 <= x0 {
continue;
}
if solid_line {