mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
A rectangle's corners are equal, not nearly equal
is_nearly_equal allows a 256th of a path unit, and stroke_path_impl applies the matrix after path_as_rect, so a cm of 10,000 turns that tolerance into tens of device units: a path that only nearly closes was painted as the rectangle, with a side it does not have, and an edge off true by that much counted as axis-aligned. The fast path now compares coordinates exactly. Nothing that is a rectangle loses it: a rectangle's coordinates are equal in the content stream, not approximately equal. A path that misses takes the general route and is painted as written, which is the safe direction. I could not make a rendered case that distinguishes the two: this renderer paints a 10,000x cm as a solid page whatever the line width, so there are no pixels to compare at the scale where the tolerance shows. The change rests on the argument above, and the three existing tests still pin the behaviour that is measurable. Reported by CodeRabbit on #3327.
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+9
-3
@@ -477,7 +477,13 @@ pub(crate) fn path_as_rect(path: &BezPath) -> Option<Rect> {
|
||||
}
|
||||
}
|
||||
|
||||
let same = |a: Point, b: Point| a.x.is_nearly_equal(b.x) && a.y.is_nearly_equal(b.y);
|
||||
// Exactly equal, not nearly: `is_nearly_equal` allows a 256th of a path unit, and the matrix
|
||||
// is applied after this, so a `cm` of 10,000 turns that into tens of device units -- a path
|
||||
// that only nearly closes would be painted as the rectangle, with a side it does not have.
|
||||
// A rectangle's coordinates are equal in the content stream, not approximately equal, so
|
||||
// nothing that is one loses the fast path by this; a path that misses takes the general route
|
||||
// and is painted as written (#3327 review).
|
||||
let same = |a: Point, b: Point| a.x == b.x && a.y == b.y;
|
||||
// `m l l l l`: the fourth line goes back to the start, which `m l l l h` does with ClosePath.
|
||||
if !closed {
|
||||
if points.len() != 5 || !same(points[4], points[0]) {
|
||||
@@ -492,8 +498,8 @@ pub(crate) fn path_as_rect(path: &BezPath) -> Option<Rect> {
|
||||
// points of an axis-aligned rectangle, in order round it.
|
||||
for (i, p) in points.iter().enumerate() {
|
||||
let q = points[(i + 1) % 4];
|
||||
let along_x = p.y.is_nearly_equal(q.y) && !p.x.is_nearly_equal(q.x);
|
||||
let along_y = p.x.is_nearly_equal(q.x) && !p.y.is_nearly_equal(q.y);
|
||||
let along_x = p.y == q.y && p.x != q.x;
|
||||
let along_y = p.x == q.x && p.y != q.y;
|
||||
if !(along_x || along_y) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user