diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 4ccb5e0b7..5d11b3dff 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -1386,17 +1386,17 @@ class PartialEvaluator { const y = args[1] + args[3]; minMax = [ Math.min(args[0], x), - Math.max(args[0], x), Math.min(args[1], y), + Math.max(args[0], x), Math.max(args[1], y), ]; break; case OPS.moveTo: case OPS.lineTo: - minMax = [args[0], args[0], args[1], args[1]]; + minMax = [args[0], args[1], args[0], args[1]]; break; default: - minMax = [Infinity, -Infinity, Infinity, -Infinity]; + minMax = [Infinity, Infinity, -Infinity, -Infinity]; break; } operatorList.addOp(OPS.constructPath, [[fn], args, minMax]); @@ -1420,15 +1420,15 @@ class PartialEvaluator { const x = args[0] + args[2]; const y = args[1] + args[3]; minMax[0] = Math.min(minMax[0], args[0], x); - minMax[1] = Math.max(minMax[1], args[0], x); - minMax[2] = Math.min(minMax[2], args[1], y); + minMax[1] = Math.min(minMax[1], args[1], y); + minMax[2] = Math.max(minMax[2], args[0], x); minMax[3] = Math.max(minMax[3], args[1], y); break; case OPS.moveTo: case OPS.lineTo: minMax[0] = Math.min(minMax[0], args[0]); - minMax[1] = Math.max(minMax[1], args[0]); - minMax[2] = Math.min(minMax[2], args[1]); + minMax[1] = Math.min(minMax[1], args[1]); + minMax[2] = Math.max(minMax[2], args[0]); minMax[3] = Math.max(minMax[3], args[1]); break; } diff --git a/src/display/canvas.js b/src/display/canvas.js index 808c9c693..384b9fd51 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -529,18 +529,14 @@ class CanvasExtraState { updateScalingPathMinMax(transform, minMax) { Util.scaleMinMax(transform, minMax); this.minX = Math.min(this.minX, minMax[0]); - this.maxX = Math.max(this.maxX, minMax[1]); - this.minY = Math.min(this.minY, minMax[2]); + this.minY = Math.min(this.minY, minMax[1]); + this.maxX = Math.max(this.maxX, minMax[2]); this.maxY = Math.max(this.maxY, minMax[3]); } updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) { - const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3); + const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax); if (minMax) { - minMax[0] = Math.min(minMax[0], box[0], box[2]); - minMax[1] = Math.max(minMax[1], box[0], box[2]); - minMax[2] = Math.min(minMax[2], box[1], box[3]); - minMax[3] = Math.max(minMax[3], box[1], box[3]); return; } this.updateRectMinMax(transform, box); diff --git a/src/shared/util.js b/src/shared/util.js index 77a1592d5..32e450b59 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -651,52 +651,52 @@ class Util { // Apply a scaling matrix to some min/max values. // If a scaling factor is negative then min and max must be - // swaped. + // swapped. static scaleMinMax(transform, minMax) { let temp; if (transform[0]) { if (transform[0] < 0) { temp = minMax[0]; - minMax[0] = minMax[1]; - minMax[1] = temp; + minMax[0] = minMax[2]; + minMax[2] = temp; } minMax[0] *= transform[0]; - minMax[1] *= transform[0]; + minMax[2] *= transform[0]; if (transform[3] < 0) { - temp = minMax[2]; - minMax[2] = minMax[3]; + temp = minMax[1]; + minMax[1] = minMax[3]; minMax[3] = temp; } - minMax[2] *= transform[3]; + minMax[1] *= transform[3]; minMax[3] *= transform[3]; } else { temp = minMax[0]; - minMax[0] = minMax[2]; - minMax[2] = temp; - temp = minMax[1]; - minMax[1] = minMax[3]; + minMax[0] = minMax[1]; + minMax[1] = temp; + temp = minMax[2]; + minMax[2] = minMax[3]; minMax[3] = temp; if (transform[1] < 0) { - temp = minMax[2]; - minMax[2] = minMax[3]; + temp = minMax[1]; + minMax[1] = minMax[3]; minMax[3] = temp; } - minMax[2] *= transform[1]; + minMax[1] *= transform[1]; minMax[3] *= transform[1]; if (transform[2] < 0) { temp = minMax[0]; - minMax[0] = minMax[1]; - minMax[1] = temp; + minMax[0] = minMax[2]; + minMax[2] = temp; } minMax[0] *= transform[2]; - minMax[1] *= transform[2]; + minMax[2] *= transform[2]; } minMax[0] += transform[4]; - minMax[1] += transform[4]; - minMax[2] += transform[5]; + minMax[1] += transform[5]; + minMax[2] += transform[4]; minMax[3] += transform[5]; } @@ -822,76 +822,116 @@ class Util { return [xLow, yLow, xHigh, yHigh]; } + static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) { + if (t <= 0 || t >= 1) { + return; + } + const mt = 1 - t; + const tt = t * t; + const ttt = tt * t; + const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3; + const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3; + minMax[0] = Math.min(minMax[0], x); + minMax[1] = Math.min(minMax[1], y); + minMax[2] = Math.max(minMax[2], x); + minMax[3] = Math.max(minMax[3], y); + } + + static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) { + if (Math.abs(a) < 1e-12) { + if (Math.abs(b) >= 1e-12) { + this.#getExtremumOnCurve( + x0, + x1, + x2, + x3, + y0, + y1, + y2, + y3, + -c / b, + minMax + ); + } + return; + } + + const delta = b ** 2 - 4 * c * a; + if (delta < 0) { + return; + } + const sqrtDelta = Math.sqrt(delta); + const a2 = 2 * a; + this.#getExtremumOnCurve( + x0, + x1, + x2, + x3, + y0, + y1, + y2, + y3, + (-b + sqrtDelta) / a2, + minMax + ); + this.#getExtremumOnCurve( + x0, + x1, + x2, + x3, + y0, + y1, + y2, + y3, + (-b - sqrtDelta) / a2, + minMax + ); + } + // From https://github.com/adobe-webplatform/Snap.svg/blob/b365287722a72526000ac4bfcf0ce4cac2faa015/src/path.js#L852 - static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) { - const tvalues = [], - bounds = [[], []]; - let a, b, c, t, t1, t2, b2ac, sqrtb2ac; - for (let i = 0; i < 2; ++i) { - if (i === 0) { - b = 6 * x0 - 12 * x1 + 6 * x2; - a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; - c = 3 * x1 - 3 * x0; - } else { - b = 6 * y0 - 12 * y1 + 6 * y2; - a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; - c = 3 * y1 - 3 * y0; - } - if (Math.abs(a) < 1e-12) { - if (Math.abs(b) < 1e-12) { - continue; - } - t = -c / b; - if (0 < t && t < 1) { - tvalues.push(t); - } - continue; - } - b2ac = b * b - 4 * c * a; - sqrtb2ac = Math.sqrt(b2ac); - if (b2ac < 0) { - continue; - } - t1 = (-b + sqrtb2ac) / (2 * a); - if (0 < t1 && t1 < 1) { - tvalues.push(t1); - } - t2 = (-b - sqrtb2ac) / (2 * a); - if (0 < t2 && t2 < 1) { - tvalues.push(t2); - } + static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) { + if (minMax) { + minMax[0] = Math.min(minMax[0], x0, x3); + minMax[1] = Math.min(minMax[1], y0, y3); + minMax[2] = Math.max(minMax[2], x0, x3); + minMax[3] = Math.max(minMax[3], y0, y3); + } else { + minMax = [ + Math.min(x0, x3), + Math.min(y0, y3), + Math.max(x0, x3), + Math.max(y0, y3), + ]; } - - let j = tvalues.length, - mt; - const jlen = j; - while (j--) { - t = tvalues[j]; - mt = 1 - t; - bounds[0][j] = - mt * mt * mt * x0 + - 3 * mt * mt * t * x1 + - 3 * mt * t * t * x2 + - t * t * t * x3; - bounds[1][j] = - mt * mt * mt * y0 + - 3 * mt * mt * t * y1 + - 3 * mt * t * t * y2 + - t * t * t * y3; - } - - bounds[0][jlen] = x0; - bounds[1][jlen] = y0; - bounds[0][jlen + 1] = x3; - bounds[1][jlen + 1] = y3; - bounds[0].length = bounds[1].length = jlen + 2; - - return [ - Math.min(...bounds[0]), - Math.min(...bounds[1]), - Math.max(...bounds[0]), - Math.max(...bounds[1]), - ]; + this.#getExtremum( + x0, + x1, + x2, + x3, + y0, + y1, + y2, + y3, + 3 * (-x0 + 3 * (x1 - x2) + x3), + 6 * (x0 - 2 * x1 + x2), + 3 * (x1 - x0), + minMax + ); + this.#getExtremum( + x0, + x1, + x2, + x3, + y0, + y1, + y2, + y3, + 3 * (-y0 + 3 * (y1 - y2) + y3), + 6 * (y0 - 2 * y1 + y2), + 3 * (y1 - y0), + minMax + ); + return minMax; } } diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index e1bbd0d77..98362f4bf 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -4548,7 +4548,7 @@ describe("annotation", function () { expect(opList.argsArray[5][0]).toEqual([OPS.moveTo, OPS.curveTo]); expect(opList.argsArray[5][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); // Min-max. - expect(opList.argsArray[5][2]).toEqual([1, 1, 2, 2]); + expect(opList.argsArray[5][2]).toEqual([1, 2, 1, 2]); }); }); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index ad15bff75..666ada005 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -771,7 +771,7 @@ describe("api", function () { [ [OPS.moveTo, OPS.lineTo], [0, 9.75, 0.5, 9.75], - [0, 0.5, 9.75, 9.75], + [0, 9.75, 0.5, 9.75], ], null, ]);