diff --git a/src/display/canvas.js b/src/display/canvas.js index f65df318d882f..69644b4eea201 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -711,10 +711,12 @@ class CanvasGraphics { resetCtxToDefault(this.ctx); if (transform) { this.ctx.transform(...transform); + this.dependencyTracker?.transform(...transform); this.outputScaleX = transform[0]; this.outputScaleY = transform[0]; } this.ctx.transform(...viewport.transform); + this.dependencyTracker?.transform(...viewport.transform); this.viewportScale = viewport.scale; this.baseTransform = getCurrentTransform(this.ctx); @@ -1477,7 +1479,9 @@ class CanvasGraphics { } transform(opIdx, a, b, c, d, e, f) { - this.dependencyTracker?.recordIncrementalData("transform", opIdx); + this.dependencyTracker + ?.recordIncrementalData("transform", opIdx) + .transform(a, b, c, d, e, f); this.ctx.transform(a, b, c, d, e, f); this._cachedScaleForStroking[0] = -1; @@ -1503,7 +1507,6 @@ class CanvasGraphics { .resetBBox(opIdx) .recordBBox( opIdx, - this.ctx, minMax[0] - outerExtraSize, minMax[2] + outerExtraSize, minMax[1] - outerExtraSize, @@ -1845,7 +1848,9 @@ class CanvasGraphics { } this.current.fontSizeScale = size / browserFontSize; - this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; + const fontString = `${italic} ${bold} ${browserFontSize}px ${typeface}`; + this.ctx.font = fontString; + this.dependencyTracker?.setFont(fontString); } setTextRenderingMode(opIdx, mode) { @@ -1941,7 +1946,12 @@ class CanvasGraphics { ctx.translate(x, y); ctx.scale(fontSize, -fontSize); - this.dependencyTracker?.recordCharacterBBox(opIdx, ctx, font); + this.dependencyTracker?.withLocalTransform(dt => + dt + .translate(x, y) + .scale(fontSize, -fontSize) + .recordCharacterBBox(opIdx, null, font) + ); let currentTransform; if ( @@ -1999,12 +2009,11 @@ class CanvasGraphics { ctx.fillText(character, x, y); this.dependencyTracker?.recordCharacterBBox( opIdx, - ctx, + character, font, fontSize, x, - y, - () => ctx.measureText(character) + y ); } if ( @@ -2013,9 +2022,7 @@ class CanvasGraphics { ) { if (this.dependencyTracker) { this.dependencyTracker - ?.recordCharacterBBox(opIdx, ctx, font, fontSize, x, y, () => - ctx.measureText(character) - ) + ?.recordCharacterBBox(opIdx, character, font, fontSize, x, y) .recordDependencies(opIdx, Dependencies.stroke); } ctx.strokeText(character, x, y); @@ -2033,7 +2040,7 @@ class CanvasGraphics { }); this.dependencyTracker?.recordCharacterBBox( opIdx, - ctx, + null, font, fontSize, x, @@ -2062,12 +2069,13 @@ class CanvasGraphics { } showText(opIdx, glyphs) { - if (this.dependencyTracker) { - this.dependencyTracker + const { dependencyTracker } = this; + if (dependencyTracker !== null) { + dependencyTracker .recordDependencies(opIdx, Dependencies.showText) .resetBBox(opIdx); if (this.current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { - this.dependencyTracker + dependencyTracker .recordFutureForcedDependency("textClip", opIdx) .inheritPendingDependenciesAsFutureForcedDependencies(); } @@ -2077,7 +2085,7 @@ class CanvasGraphics { const font = current.font; if (font.isType3Font) { this.showType3Text(opIdx, glyphs); - this.dependencyTracker?.recordShowTextOperation(opIdx); + dependencyTracker?.recordShowTextOperation(opIdx); return undefined; } @@ -2109,11 +2117,17 @@ class CanvasGraphics { ctx.transform(...current.textMatrix); } ctx.translate(current.x, current.y + current.textRise); + const scaleY = fontDirection > 0 ? -1 : 1; + ctx.scale(textHScale, scaleY); - if (fontDirection > 0) { - ctx.scale(textHScale, -1); - } else { - ctx.scale(textHScale, 1); + if (dependencyTracker !== null) { + dependencyTracker.save(opIdx); + if (current.textMatrix) { + dependencyTracker.transform(...current.textMatrix); + } + dependencyTracker + .translate(current.x, current.y + current.textRise) + .scale(textHScale, scaleY); } let patternFillTransform, patternStrokeTransform; @@ -2142,6 +2156,7 @@ class CanvasGraphics { if (fontSizeScale !== 1.0) { ctx.scale(fontSizeScale, fontSizeScale); + this.dependencyTracker?.scale(fontSizeScale, fontSizeScale); lineWidth /= fontSizeScale; } @@ -2184,18 +2199,18 @@ class CanvasGraphics { } const joinedChars = chars.join(""); ctx.fillText(joinedChars, 0, 0); - if (this.dependencyTracker !== null) { - const measure = ctx.measureText(joinedChars); - this.dependencyTracker + if (dependencyTracker !== null) { + const measure = dependencyTracker.measureText(joinedChars); + dependencyTracker .recordBBox( opIdx, - this.ctx, -measure.actualBoundingBoxLeft, measure.actualBoundingBoxRight, -measure.actualBoundingBoxAscent, measure.actualBoundingBoxDescent ) - .recordShowTextOperation(opIdx); + .recordShowTextOperation(opIdx) + .restore(opIdx); } current.x += width * widthAdvanceScale * textHScale; ctx.restore(); @@ -2263,13 +2278,12 @@ class CanvasGraphics { this.dependencyTracker?.recordCharacterBBox( opIdx, - ctx, + character, // If we already measured the character, force usage of that measure ? { bbox: null } : font, fontSize / fontSizeScale, scaledX, - scaledY, - () => measure ?? ctx.measureText(character) + scaledY ); } else { this.paintChar( @@ -2312,9 +2326,8 @@ class CanvasGraphics { current.x += x * textHScale; } ctx.restore(); + this.dependencyTracker?.restore(opIdx).recordShowTextOperation(opIdx); this.compose(); - - this.dependencyTracker?.recordShowTextOperation(opIdx); return undefined; } @@ -2346,21 +2359,32 @@ class CanvasGraphics { ctx.transform(...current.textMatrix); } ctx.translate(current.x, current.y + current.textRise); - ctx.scale(textHScale, fontDirection); - // Type3 fonts have their own operator list. Avoid mixing it up with the - // dependency tracker of the main operator list. const dependencyTracker = this.dependencyTracker; - this.dependencyTracker = dependencyTracker - ? new CanvasNestedDependencyTracker(dependencyTracker, opIdx) - : null; + if (dependencyTracker) { + dependencyTracker.save(opIdx); + if (current.textMatrix) { + dependencyTracker.transform(...current.textMatrix); + } + dependencyTracker + .translate(current.x, current.y + current.textRise) + .scale(textHScale, fontDirection); + + // Type3 fonts have their own operator list. Avoid mixing it up with the + // dependency tracker of the main operator list. + this.dependencyTracker = new CanvasNestedDependencyTracker( + dependencyTracker, + opIdx + ); + } for (i = 0; i < glyphsLength; ++i) { glyph = glyphs[i]; if (typeof glyph === "number") { spacingLength = (spacingDir * glyph * fontSize) / 1000; this.ctx.translate(spacingLength, 0); + this.dependencyTracker?.translate(spacingLength, 0); current.x += spacingLength * textHScale; continue; } @@ -2373,6 +2397,9 @@ class CanvasGraphics { this.save(); ctx.scale(fontSize, fontSize); ctx.transform(...fontMatrix); + this.dependencyTracker + ?.scale(fontSize, fontSize) + .transform(...fontMatrix); this.executeOperatorList(operatorList); this.restore(); } @@ -2382,10 +2409,12 @@ class CanvasGraphics { width = p[0] * fontSize + spacing; ctx.translate(width, 0); + this.dependencyTracker?.translate(width, 0); current.x += width * textHScale; } ctx.restore(); if (dependencyTracker) { + dependencyTracker.restore(opIdx); this.dependencyTracker = dependencyTracker; } } @@ -2401,8 +2430,8 @@ class CanvasGraphics { clip.rect(llx, lly, urx - llx, ury - lly); this.ctx.clip(clip); this.dependencyTracker - ?.recordBBox(opIdx, this.ctx, llx, urx, lly, ury) - .recordClipBox(opIdx, this.ctx, llx, urx, lly, ury); + ?.recordBBox(opIdx, llx, urx, lly, ury) + .recordClipBox(opIdx, llx, urx, lly, ury); this.endPath(opIdx); } @@ -2580,7 +2609,7 @@ class CanvasGraphics { const clip = new Path2D(); clip.rect(x0, y0, x1 - x0, y1 - y0); this.ctx.clip(clip); - this.dependencyTracker?.recordClipBox(opIdx, this.ctx, x0, x1, y0, y1); + this.dependencyTracker?.recordClipBox(opIdx, x0, x1, y0, y1); this.endPath(opIdx); } } @@ -2748,13 +2777,25 @@ class CanvasGraphics { // except the blend mode, soft mask, and alpha constants. copyCtxState(currentCtx, groupCtx); this.ctx = groupCtx; - this.dependencyTracker - ?.inheritSimpleDataAsFutureForcedDependencies([ - "fillAlpha", - "strokeAlpha", - "globalCompositeOperation", - ]) - .pushBaseTransform(currentCtx); + if (this.dependencyTracker) { + const savedTransform = this.dependencyTracker.getTransform().slice(); + this.dependencyTracker + .inheritSimpleDataAsFutureForcedDependencies([ + "fillAlpha", + "strokeAlpha", + "globalCompositeOperation", + ]) + .setTransform(1, 0, 0, 1, offsetX, offsetY) + .pushBaseTransform() + .setTransform( + savedTransform[0], + savedTransform[1], + savedTransform[2], + savedTransform[3], + savedTransform[4] - offsetX, + savedTransform[5] - offsetY + ); + } this.setGState(opIdx, [ ["BM", "source-over"], ["ca", 1], @@ -2826,6 +2867,7 @@ class CanvasGraphics { if (this.baseTransform) { this.ctx.setTransform(...this.baseTransform); + this.dependencyTracker?.setTransform(...this.baseTransform); } if (rect) { @@ -2916,17 +2958,19 @@ class CanvasGraphics { // transform to draw to the identity. ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY); - this.dependencyTracker - ?.resetBBox(opIdx) - .recordBBox( - opIdx, - this.ctx, - mask.offsetX, - mask.offsetX + maskCanvas.width, - mask.offsetY, - mask.offsetY + maskCanvas.height - ) - .recordOperation(opIdx); + this.dependencyTracker?.withLocalTransform(dt => + dt + .setTransform(1, 0, 0, 1, 0, 0) + .resetBBox(opIdx) + .recordBBox( + opIdx, + mask.offsetX, + mask.offsetX + maskCanvas.width, + mask.offsetY, + mask.offsetY + maskCanvas.height + ) + .recordOperation(opIdx) + ); ctx.restore(); if (mask.canvasEntry) { this.canvasFactory.destroy(mask.canvasEntry); @@ -2963,7 +3007,17 @@ class CanvasGraphics { mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5] ); - this.dependencyTracker?.resetBBox(opIdx); + this.dependencyTracker + ?.save(opIdx) + .setTransform( + 1, + 0, + 0, + 1, + mask.offsetX - currentTransform[4], + mask.offsetY - currentTransform[5] + ) + .resetBBox(opIdx); for (let i = 0, ii = positions.length; i < ii; i += 2) { const trans = Util.transform(currentTransform, [ scaleX, @@ -2979,20 +3033,18 @@ class CanvasGraphics { ctx.drawImage(mask.canvas, trans[4], trans[5]); this.dependencyTracker?.recordBBox( opIdx, - this.ctx, trans[4], trans[4] + mask.canvas.width, trans[5], trans[5] + mask.canvas.height ); } + this.dependencyTracker?.restore(opIdx).recordOperation(opIdx); ctx.restore(); if (mask.canvasEntry) { this.canvasFactory.destroy(mask.canvasEntry); } this.compose(); - - this.dependencyTracker?.recordOperation(opIdx); } paintImageMaskXObjectGroup(opIdx, images) { @@ -3036,6 +3088,12 @@ class CanvasGraphics { ctx.save(); ctx.transform(...transform); ctx.scale(1, -1); + this.dependencyTracker?.withLocalTransform(dt => + dt + .transform(...transform) + .scale(1, -1) + .recordBBox(opIdx, 0, width, 0, height) + ); drawImageAtIntegerCoords( ctx, maskCanvas.canvas, @@ -3050,7 +3108,6 @@ class CanvasGraphics { ); this.canvasFactory.destroy(maskCanvas); - this.dependencyTracker?.recordBBox(opIdx, ctx, 0, width, 0, height); ctx.restore(); } this.compose(); @@ -3139,6 +3196,7 @@ class CanvasGraphics { // scale the image to the unit square ctx.scale(1 / width, -1 / height); + this.dependencyTracker?.scale(1 / width, -1 / height); let imgToPaint; let inlineImgCanvas = null; @@ -3171,7 +3229,7 @@ class CanvasGraphics { if (this.dependencyTracker) { this.dependencyTracker .resetBBox(opIdx) - .recordBBox(opIdx, ctx, 0, width, -height, 0) + .recordBBox(opIdx, 0, width, -height, 0) .recordDependencies(opIdx, Dependencies.imageXObject) .recordOperation(opIdx); this.imagesTracker?.record( @@ -3229,6 +3287,12 @@ class CanvasGraphics { ctx.save(); ctx.transform(...entry.transform); ctx.scale(1, -1); + this.dependencyTracker?.withLocalTransform(dt => + dt + .transform(...entry.transform) + .scale(1, -1) + .recordBBox(opIdx, 0, 1, -1, 0) + ); drawImageAtIntegerCoords( ctx, imgToPaint, @@ -3241,7 +3305,6 @@ class CanvasGraphics { 1, 1 ); - this.dependencyTracker?.recordBBox(opIdx, ctx, 0, 1, -1, 0); ctx.restore(); } if (inlineImgCanvas) { @@ -3257,7 +3320,7 @@ class CanvasGraphics { } this.dependencyTracker ?.resetBBox(opIdx) - .recordBBox(opIdx, this.ctx, 0, 1, 0, 1) + .recordBBox(opIdx, 0, 1, 0, 1) .recordDependencies(opIdx, Dependencies.fill) .recordOperation(opIdx); this.ctx.fillRect(0, 0, 1, 1); diff --git a/src/display/canvas_dependency_tracker.js b/src/display/canvas_dependency_tracker.js index 55ac72d386603..9ff348b4e0cff 100644 --- a/src/display/canvas_dependency_tracker.js +++ b/src/display/canvas_dependency_tracker.js @@ -78,6 +78,10 @@ const ensureDebugMetadata = (map, key) => class CanvasBBoxTracker { #baseTransformStack = [[1, 0, 0, 1, 0, 0]]; + #currentTransform = [1, 0, 0, 1, 0, 0]; + + #transformSaveStack = []; + #clipBox = [-Infinity, -Infinity, Infinity, Infinity]; // Float32Array @@ -128,6 +132,7 @@ class CanvasBBoxTracker { save(opIdx) { this.#clipBox = { __proto__: this.#clipBox }; + this.#transformSaveStack.push(this.#currentTransform.slice()); this._savesStack.push(opIdx); return this; } @@ -140,6 +145,7 @@ class CanvasBBoxTracker { return this; } this.#clipBox = previous; + this.#currentTransform = this.#transformSaveStack.pop(); const lastSave = this._savesStack.pop(); if (lastSave !== undefined) { @@ -189,12 +195,9 @@ class CanvasBBoxTracker { return this; } - pushBaseTransform(ctx) { + pushBaseTransform() { this.#baseTransformStack.push( - Util.multiplyByDOMMatrix( - this.#baseTransformStack.at(-1), - ctx.getTransform() - ) + Util.transform(this.#baseTransformStack.at(-1), this.#currentTransform) ); return this; } @@ -214,10 +217,10 @@ class CanvasBBoxTracker { return this; } - recordClipBox(idx, ctx, minX, maxX, minY, maxY) { - const transform = Util.multiplyByDOMMatrix( + recordClipBox(idx, minX, maxX, minY, maxY) { + const transform = Util.transform( this.#baseTransformStack.at(-1), - ctx.getTransform() + this.#currentTransform ); const clipBox = BBOX_INIT.slice(); Util.axialAlignedBoundingBox([minX, minY, maxX, maxY], transform, clipBox); @@ -234,15 +237,15 @@ class CanvasBBoxTracker { return this; } - recordBBox(idx, ctx, minX, maxX, minY, maxY) { + recordBBox(idx, minX, maxX, minY, maxY) { const clipBox = this.#clipBox; if (clipBox[0] === Infinity) { return this; } - const transform = Util.multiplyByDOMMatrix( + const transform = Util.transform( this.#baseTransformStack.at(-1), - ctx.getTransform() + this.#currentTransform ); if (clipBox[0] === -Infinity) { Util.axialAlignedBoundingBox( @@ -354,7 +357,52 @@ class CanvasBBoxTracker { return this; } - recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) { + setFont(fontString) { + return this; + } + + measureText(text) { + return null; + } + + recordCharacterBBox(idx, character, font, scale = 1, x = 0, y = 0) { + return this; + } + + getTransform() { + return this.#currentTransform; + } + + setTransform(a, b, c, d, e, f) { + this.#currentTransform = [a, b, c, d, e, f]; + return this; + } + + transform(a, b, c, d, e, f) { + Util.transformInPlace(this.#currentTransform, [a, b, c, d, e, f]); + return this; + } + + translate(tx, ty) { + const t = this.#currentTransform; + t[4] += t[0] * tx + t[2] * ty; + t[5] += t[1] * tx + t[3] * ty; + return this; + } + + scale(sx, sy) { + const t = this.#currentTransform; + t[0] *= sx; + t[1] *= sx; + t[2] *= sy; + t[3] *= sy; + return this; + } + + withLocalTransform(callback) { + const saved = this.#currentTransform.slice(); + callback(this); + this.#currentTransform = saved; return this; } @@ -408,6 +456,12 @@ class CanvasDependencyTracker { #fontBBoxTrustworthy = new Map(); + // TODO: No tests are failing, but we should set the lang here as it can + // affect the used fallback fonts. Probably no tests are failing because we + // round bboxes anyway, so it's extremely unlikely that after rounding the + // two fonts still have different bboxes. + #fontMeasureCtx = new OffscreenCanvas(0, 0).getContext("2d"); + #debugMetadata; #recordDebugMetadataDepenencyAfterRestore; @@ -504,8 +558,8 @@ class CanvasDependencyTracker { return this; } - pushBaseTransform(ctx) { - this.#bboxTracker.pushBaseTransform(ctx); + pushBaseTransform() { + this.#bboxTracker.pushBaseTransform(); return this; } @@ -514,6 +568,35 @@ class CanvasDependencyTracker { return this; } + getTransform() { + return this.#bboxTracker.getTransform(); + } + + setTransform(a, b, c, d, e, f) { + this.#bboxTracker.setTransform(a, b, c, d, e, f); + return this; + } + + transform(a, b, c, d, e, f) { + this.#bboxTracker.transform(a, b, c, d, e, f); + return this; + } + + translate(tx, ty) { + this.#bboxTracker.translate(tx, ty); + return this; + } + + scale(sx, sy) { + this.#bboxTracker.scale(sx, sy); + return this; + } + + withLocalTransform(callback) { + this.#bboxTracker.withLocalTransform(() => callback(this)); + return this; + } + /** * @param {SimpleDependency} name * @param {number} idx @@ -584,17 +667,26 @@ class CanvasDependencyTracker { return this; } - recordClipBox(idx, ctx, minX, maxX, minY, maxY) { - this.#bboxTracker.recordClipBox(idx, ctx, minX, maxX, minY, maxY); + recordClipBox(idx, minX, maxX, minY, maxY) { + this.#bboxTracker.recordClipBox(idx, minX, maxX, minY, maxY); + return this; + } + + recordBBox(idx, minX, maxX, minY, maxY) { + this.#bboxTracker.recordBBox(idx, minX, maxX, minY, maxY); return this; } - recordBBox(idx, ctx, minX, maxX, minY, maxY) { - this.#bboxTracker.recordBBox(idx, ctx, minX, maxX, minY, maxY); + setFont(fontString) { + this.#fontMeasureCtx.font = fontString; return this; } - recordCharacterBBox(idx, ctx, font, scale = 1, x = 0, y = 0, getMeasure) { + measureText(text) { + return this.#fontMeasureCtx.measureText(text); + } + + recordCharacterBBox(idx, character, font, scale = 1, x = 0, y = 0) { const fontBBox = font.bbox; let isBBoxTrustworthy; let computedBBox; @@ -617,7 +709,6 @@ class CanvasDependencyTracker { if (isBBoxTrustworthy) { return this.recordBBox( idx, - ctx, computedBBox[0], computedBBox[2], computedBBox[1], @@ -627,13 +718,13 @@ class CanvasDependencyTracker { } } - if (!getMeasure) { + if (!character) { // We have no way of telling how big this character actually is, record // a full page bounding box. return this.recordFullPageBBox(idx); } - const measure = getMeasure(); + const measure = this.#fontMeasureCtx.measureText(character); if (fontBBox && computedBBox && isBBoxTrustworthy === undefined) { // If it's the first time we can compare the font bbox with the actual @@ -650,7 +741,6 @@ class CanvasDependencyTracker { if (isBBoxTrustworthy) { return this.recordBBox( idx, - ctx, computedBBox[0], computedBBox[2], computedBBox[1], @@ -663,7 +753,6 @@ class CanvasDependencyTracker { // return the bounding box based on .measureText(). return this.recordBBox( idx, - ctx, x - measure.actualBoundingBoxLeft, x + measure.actualBoundingBoxRight, y - measure.actualBoundingBoxAscent, @@ -848,8 +937,8 @@ class CanvasNestedDependencyTracker { return this; } - pushBaseTransform(ctx) { - this.#dependencyTracker.pushBaseTransform(ctx); + pushBaseTransform() { + this.#dependencyTracker.pushBaseTransform(); return this; } @@ -858,6 +947,44 @@ class CanvasNestedDependencyTracker { return this; } + setFont(fontString) { + this.#dependencyTracker.setFont(fontString); + return this; + } + + measureText(text) { + return this.#dependencyTracker.measureText(text); + } + + getTransform() { + return this.#dependencyTracker.getTransform(); + } + + setTransform(a, b, c, d, e, f) { + this.#dependencyTracker.setTransform(a, b, c, d, e, f); + return this; + } + + transform(a, b, c, d, e, f) { + this.#dependencyTracker.transform(a, b, c, d, e, f); + return this; + } + + translate(tx, ty) { + this.#dependencyTracker.translate(tx, ty); + return this; + } + + scale(sx, sy) { + this.#dependencyTracker.scale(sx, sy); + return this; + } + + withLocalTransform(callback) { + this.#dependencyTracker.withLocalTransform(() => callback(this)); + return this; + } + /** * @param {SimpleDependency} name * @param {number} idx @@ -929,11 +1056,10 @@ class CanvasNestedDependencyTracker { return this; } - recordClipBox(idx, ctx, minX, maxX, minY, maxY) { + recordClipBox(idx, minX, maxX, minY, maxY) { if (!this.#ignoreBBoxes) { this.#dependencyTracker.recordClipBox( this.#opIdx, - ctx, minX, maxX, minY, @@ -943,30 +1069,22 @@ class CanvasNestedDependencyTracker { return this; } - recordBBox(idx, ctx, minX, maxX, minY, maxY) { + recordBBox(idx, minX, maxX, minY, maxY) { if (!this.#ignoreBBoxes) { - this.#dependencyTracker.recordBBox( - this.#opIdx, - ctx, - minX, - maxX, - minY, - maxY - ); + this.#dependencyTracker.recordBBox(this.#opIdx, minX, maxX, minY, maxY); } return this; } - recordCharacterBBox(idx, ctx, font, scale, x, y, getMeasure) { + recordCharacterBBox(idx, character, font, scale, x, y) { if (!this.#ignoreBBoxes) { this.#dependencyTracker.recordCharacterBBox( this.#opIdx, - ctx, + character, font, scale, x, - y, - getMeasure + y ); } return this; diff --git a/src/shared/util.js b/src/shared/util.js index 6d00bb8249cca..58ac501ddfada 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -759,6 +759,21 @@ class Util { ]; } + static transformInPlace(m1, m2) { + const a = m1[0] * m2[0] + m1[2] * m2[1]; + const b = m1[1] * m2[0] + m1[3] * m2[1]; + const c = m1[0] * m2[2] + m1[2] * m2[3]; + const d = m1[1] * m2[2] + m1[3] * m2[3]; + const e = m1[0] * m2[4] + m1[2] * m2[5] + m1[4]; + const f = m1[1] * m2[4] + m1[3] * m2[5] + m1[5]; + m1[0] = a; + m1[1] = b; + m1[2] = c; + m1[3] = d; + m1[4] = e; + m1[5] = f; + } + // Multiplies m (an array-based transform) by md (a DOMMatrix transform). static multiplyByDOMMatrix(m, md) { return [