diff --git a/src/display/canvas.js b/src/display/canvas.js index baa2dfa69..9bbc2cb21 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -63,6 +63,9 @@ const FULL_CHUNK_HEIGHT = 16; // creating a new DOMMatrix object each time we need it. const SCALE_MATRIX = new DOMMatrix(); +// Used to get some coordinates. +const XY = new Float32Array(2); + /** * Overrides certain methods on a 2d ctx so that when they are called they * will also call the same method on the destCtx. The methods that are @@ -522,9 +525,9 @@ class CanvasExtraState { } // Stroked paths can be outside of the path bounding box by 1/2 the line // width. - const scale = Util.singularValueDecompose2dScale(transform); - const xStrokePad = (scale[0] * this.lineWidth) / 2; - const yStrokePad = (scale[1] * this.lineWidth) / 2; + Util.singularValueDecompose2dScale(transform, XY); + const xStrokePad = (XY[0] * this.lineWidth) / 2; + const yStrokePad = (XY[1] * this.lineWidth) / 2; box[0] -= xStrokePad; box[1] -= yStrokePad; box[2] += xStrokePad; @@ -777,15 +780,14 @@ function getImageSmoothingEnabled(transform, interpolate) { return true; } - const scale = Util.singularValueDecompose2dScale(transform); + Util.singularValueDecompose2dScale(transform, XY); // Round to a 32bit float so that `<=` check below will pass for numbers that // are very close, but not exactly the same 64bit floats. - scale[0] = Math.fround(scale[0]); - scale[1] = Math.fround(scale[1]); const actualScale = Math.fround( OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS ); - return scale[0] <= actualScale && scale[1] <= actualScale; + // `XY` is a Float32Array. + return XY[0] <= actualScale && XY[1] <= actualScale; } const LINE_CAP_STYLES = ["butt", "round", "square"]; @@ -1958,12 +1960,12 @@ class CanvasGraphics { [a, b, c, d, 0, 0], invPatternTransform ); - const [sx, sy] = Util.singularValueDecompose2dScale(transf); + Util.singularValueDecompose2dScale(transf, XY); // Cancel the pattern scaling of the line width. // If sx and sy are different, unfortunately we can't do anything and // we'll have a rendering bug. - ctx.lineWidth *= Math.max(sx, sy) / fontSize; + ctx.lineWidth *= Math.max(XY[0], XY[1]) / fontSize; ctx.stroke( this.#getScaledPath(path, currentTransform, patternStrokeTransform) ); @@ -2639,9 +2641,7 @@ class CanvasGraphics { rect[2] = width; rect[3] = height; - const [scaleX, scaleY] = Util.singularValueDecompose2dScale( - getCurrentTransform(this.ctx) - ); + Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx), XY); const { viewportScale } = this; const canvasWidth = Math.ceil( width * this.outputScaleX * viewportScale @@ -2659,7 +2659,7 @@ class CanvasGraphics { this.annotationCanvas.savedCtx = this.ctx; this.ctx = context; this.ctx.save(); - this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY); + this.ctx.setTransform(XY[0], 0, 0, -XY[1], 0, height * XY[1]); resetCtxToDefault(this.ctx); } else { diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index 792b5bc36..d3aa1d561 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -398,16 +398,18 @@ class MeshShadingPattern extends BaseShadingPattern { getPattern(ctx, owner, inverse, pathType) { applyBoundingBox(ctx, this._bbox); - let scale; + const scale = new Float32Array(2); if (pathType === PathType.SHADING) { - scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx)); - } else { + Util.singularValueDecompose2dScale(getCurrentTransform(ctx), scale); + } else if (this.matrix) { // Obtain scale from matrix and current transformation matrix. - scale = Util.singularValueDecompose2dScale(owner.baseTransform); - if (this.matrix) { - const matrixScale = Util.singularValueDecompose2dScale(this.matrix); - scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]]; - } + Util.singularValueDecompose2dScale(this.matrix, scale); + const [matrixScaleX, matrixScaleY] = scale; + Util.singularValueDecompose2dScale(owner.baseTransform, scale); + scale[0] *= matrixScaleX; + scale[1] *= matrixScaleY; + } else { + Util.singularValueDecompose2dScale(owner.baseTransform, scale); } // Rasterizing on the main thread since sending/queue large canvases @@ -517,12 +519,12 @@ class TilingPattern { const height = y1 - y0; // Obtain scale from matrix and current transformation matrix. - const matrixScale = Util.singularValueDecompose2dScale(this.matrix); - const curMatrixScale = Util.singularValueDecompose2dScale( - this.baseTransform - ); - const combinedScaleX = matrixScale[0] * curMatrixScale[0]; - const combinedScaleY = matrixScale[1] * curMatrixScale[1]; + const scale = new Float32Array(2); + Util.singularValueDecompose2dScale(this.matrix, scale); + const [matrixScaleX, matrixScaleY] = scale; + Util.singularValueDecompose2dScale(this.baseTransform, scale); + const combinedScaleX = matrixScaleX * scale[0]; + const combinedScaleY = matrixScaleY * scale[1]; let canvasWidth = width, canvasHeight = height, diff --git a/src/shared/util.js b/src/shared/util.js index cfbd9995d..d2f47893a 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -732,23 +732,17 @@ class Util { // This calculation uses Singular Value Decomposition. // The SVD can be represented with formula A = USV. We are interested in the // matrix S here because it represents the scale values. - static singularValueDecompose2dScale(m) { - const transpose = [m[0], m[2], m[1], m[3]]; - + static singularValueDecompose2dScale([m0, m1, m2, m3], output) { // Multiply matrix m with its transpose. - const a = m[0] * transpose[0] + m[1] * transpose[2]; - const b = m[0] * transpose[1] + m[1] * transpose[3]; - const c = m[2] * transpose[0] + m[3] * transpose[2]; - const d = m[2] * transpose[1] + m[3] * transpose[3]; + const a = m0 ** 2 + m1 ** 2; + const b = m0 * m2 + m1 * m3; + const c = m2 ** 2 + m3 ** 2; // Solve the second degree polynomial to get roots. - const first = (a + d) / 2; - const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2; - const sx = first + second || 1; - const sy = first - second || 1; - - // Scale values are the square roots of the eigenvalues. - return [Math.sqrt(sx), Math.sqrt(sy)]; + const first = (a + c) / 2; + const second = Math.sqrt(first ** 2 - (a * c - b ** 2)); + output[0] = Math.sqrt(first + second || 1); + output[1] = Math.sqrt(first - second || 1); } // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)