Merge pull request #15281 from Snuffleupagus/getTransform

Remove `mozCurrentTransform`/`mozCurrentTransformInverse` usage
This commit is contained in:
Tim van der Meij 2022-08-06 14:34:03 +02:00 committed by GitHub
commit d55903764e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 235 deletions

View File

@ -26,7 +26,12 @@ import {
Util,
warn,
} from "../shared/util.js";
import { getRGB, PixelsPerInch } from "./display_utils.js";
import {
getCurrentTransform,
getCurrentTransformInverse,
getRGB,
PixelsPerInch,
} from "./display_utils.js";
import {
getShadingPattern,
PathType,
@ -54,13 +59,6 @@ const MAX_SIZE_TO_COMPILE = 1000;
const FULL_CHUNK_HEIGHT = 16;
// Because of https://bugs.chromium.org/p/chromium/issues/detail?id=1170396
// some curves aren't rendered correctly.
// Multiplying lineWidth by this factor should help to have "correct"
// rendering with no artifacts.
// Once the bug is fixed upstream, we can remove this constant and its use.
const LINEWIDTH_SCALE_FACTOR = 1.000001;
/**
* 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
@ -191,177 +189,21 @@ function mirrorContextOperations(ctx, destCtx) {
};
}
function addContextCurrentTransform(ctx) {
if (ctx._transformStack) {
// Reset the transform stack.
ctx._transformStack = [];
}
// If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
if (ctx.mozCurrentTransform) {
return;
}
ctx._originalSave = ctx.save;
ctx._originalRestore = ctx.restore;
ctx._originalRotate = ctx.rotate;
ctx._originalScale = ctx.scale;
ctx._originalTranslate = ctx.translate;
ctx._originalTransform = ctx.transform;
ctx._originalSetTransform = ctx.setTransform;
ctx._originalResetTransform = ctx.resetTransform;
ctx._transformMatrix = ctx._transformMatrix || [1, 0, 0, 1, 0, 0];
ctx._transformStack = [];
try {
// The call to getOwnPropertyDescriptor throws an exception in Node.js:
// "TypeError: Method lineWidth called on incompatible receiver
// #<CanvasRenderingContext2D>".
const desc = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(ctx),
"lineWidth"
);
ctx._setLineWidth = desc.set;
ctx._getLineWidth = desc.get;
Object.defineProperty(ctx, "lineWidth", {
set: function setLineWidth(width) {
this._setLineWidth(width * LINEWIDTH_SCALE_FACTOR);
},
get: function getLineWidth() {
return this._getLineWidth();
},
});
} catch (_) {}
Object.defineProperty(ctx, "mozCurrentTransform", {
get: function getCurrentTransform() {
return this._transformMatrix;
},
});
Object.defineProperty(ctx, "mozCurrentTransformInverse", {
get: function getCurrentTransformInverse() {
// Calculation done using WolframAlpha:
// http://www.wolframalpha.com/input/?
// i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
const [a, b, c, d, e, f] = this._transformMatrix;
const ad_bc = a * d - b * c;
const bc_ad = b * c - a * d;
return [
d / ad_bc,
b / bc_ad,
c / bc_ad,
a / ad_bc,
(d * e - c * f) / bc_ad,
(b * e - a * f) / ad_bc,
];
},
});
ctx.save = function ctxSave() {
const old = this._transformMatrix;
this._transformStack.push(old);
this._transformMatrix = old.slice(0, 6);
this._originalSave();
};
ctx.restore = function ctxRestore() {
if (this._transformStack.length === 0) {
warn("Tried to restore a ctx when the stack was already empty.");
}
const prev = this._transformStack.pop();
if (prev) {
this._transformMatrix = prev;
this._originalRestore();
}
};
ctx.translate = function ctxTranslate(x, y) {
const m = this._transformMatrix;
m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5];
this._originalTranslate(x, y);
};
ctx.scale = function ctxScale(x, y) {
const m = this._transformMatrix;
m[0] *= x;
m[1] *= x;
m[2] *= y;
m[3] *= y;
this._originalScale(x, y);
};
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
const m = this._transformMatrix;
this._transformMatrix = [
m[0] * a + m[2] * b,
m[1] * a + m[3] * b,
m[0] * c + m[2] * d,
m[1] * c + m[3] * d,
m[0] * e + m[2] * f + m[4],
m[1] * e + m[3] * f + m[5],
];
ctx._originalTransform(a, b, c, d, e, f);
};
ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
this._transformMatrix = [a, b, c, d, e, f];
ctx._originalSetTransform(a, b, c, d, e, f);
};
ctx.resetTransform = function ctxResetTransform() {
this._transformMatrix = [1, 0, 0, 1, 0, 0];
ctx._originalResetTransform();
};
ctx.rotate = function ctxRotate(angle) {
const cosValue = Math.cos(angle);
const sinValue = Math.sin(angle);
const m = this._transformMatrix;
this._transformMatrix = [
m[0] * cosValue + m[2] * sinValue,
m[1] * cosValue + m[3] * sinValue,
m[0] * -sinValue + m[2] * cosValue,
m[1] * -sinValue + m[3] * cosValue,
m[4],
m[5],
];
this._originalRotate(angle);
};
}
class CachedCanvases {
constructor(canvasFactory) {
this.canvasFactory = canvasFactory;
this.cache = Object.create(null);
}
getCanvas(id, width, height, trackTransform) {
getCanvas(id, width, height) {
let canvasEntry;
if (this.cache[id] !== undefined) {
canvasEntry = this.cache[id];
this.canvasFactory.reset(canvasEntry, width, height);
// reset canvas transform for emulated mozCurrentTransform, if needed
canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
} else {
canvasEntry = this.canvasFactory.create(width, height);
this.cache[id] = canvasEntry;
}
if (trackTransform) {
addContextCurrentTransform(canvasEntry.context);
}
return canvasEntry;
}
@ -390,7 +232,7 @@ function drawImageAtIntegerCoords(
destW,
destH
) {
const [a, b, c, d, tx, ty] = ctx.mozCurrentTransform;
const [a, b, c, d, tx, ty] = getCurrentTransform(ctx);
if (b === 0 && c === 0) {
// top-left corner is at (X, Y) and
// bottom-right one is at (X + width, Y + height).
@ -1250,11 +1092,7 @@ class CanvasGraphics {
this.outputScaleY = 1;
this.backgroundColor = pageColors?.background || null;
this.foregroundColor = pageColors?.foreground || null;
if (canvasCtx) {
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
// the transformation must already be set in canvasCtx._transformMatrix.
addContextCurrentTransform(canvasCtx);
}
this._cachedScaleForStroking = null;
this._cachedGetSinglePixelWidth = null;
this._cachedBitmapsMap = new Map();
@ -1350,8 +1188,7 @@ class CanvasGraphics {
const transparentCanvas = this.cachedCanvases.getCanvas(
"transparent",
width,
height,
/* trackTransform */ true
height
);
this.compositeCtx = this.ctx;
this.transparentCanvas = transparentCanvas.canvas;
@ -1359,7 +1196,7 @@ class CanvasGraphics {
this.ctx.save();
// The transform can be applied before rendering, transferring it to
// the new canvas.
this.ctx.transform(...this.compositeCtx.mozCurrentTransform);
this.ctx.transform(...getCurrentTransform(this.compositeCtx));
}
this.ctx.save();
@ -1372,7 +1209,7 @@ class CanvasGraphics {
this.ctx.transform(...viewport.transform);
this.viewportScale = viewport.scale;
this.baseTransform = this.ctx.mozCurrentTransform.slice();
this.baseTransform = getCurrentTransform(this.ctx);
if (this.imageLayer) {
this.imageLayer.beginLayout();
@ -1529,8 +1366,7 @@ class CanvasGraphics {
tmpCanvas = this.cachedCanvases.getCanvas(
tmpCanvasId,
newWidth,
newHeight,
/* trackTransform */ false
newHeight
);
tmpCtx = tmpCanvas.context;
tmpCtx.clearRect(0, 0, newWidth, newHeight);
@ -1562,7 +1398,7 @@ class CanvasGraphics {
const { width, height } = img;
const fillColor = this.current.fillColor;
const isPatternFill = this.current.patternFill;
const currentTransform = ctx.mozCurrentTransform;
const currentTransform = getCurrentTransform(ctx);
let cache, cacheKey, scaled, maskCanvas;
if ((img.bitmap || img.data) && img.count > 1) {
@ -1603,12 +1439,7 @@ class CanvasGraphics {
}
if (!scaled) {
maskCanvas = this.cachedCanvases.getCanvas(
"maskCanvas",
width,
height,
/* trackTransform */ false
);
maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
putBinaryImageMask(maskCanvas.context, img);
}
@ -1634,8 +1465,7 @@ class CanvasGraphics {
const fillCanvas = this.cachedCanvases.getCanvas(
"fillCanvas",
drawnWidth,
drawnHeight,
/* trackTransform */ true
drawnHeight
);
const fillCtx = fillCanvas.context;
@ -1652,7 +1482,7 @@ class CanvasGraphics {
// Pre-scale if needed to improve image smoothing.
scaled = this._scaleImage(
maskCanvas.canvas,
fillCtx.mozCurrentTransformInverse
getCurrentTransformInverse(fillCtx)
);
scaled = scaled.img;
if (cache && isPatternFill) {
@ -1661,7 +1491,7 @@ class CanvasGraphics {
}
fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(
fillCtx.mozCurrentTransform,
getCurrentTransform(fillCtx),
img.interpolate
);
@ -1679,7 +1509,7 @@ class CanvasGraphics {
);
fillCtx.globalCompositeOperation = "source-in";
const inverse = Util.transform(fillCtx.mozCurrentTransformInverse, [
const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [
1,
0,
0,
@ -1830,13 +1660,12 @@ class CanvasGraphics {
const scratchCanvas = this.cachedCanvases.getCanvas(
cacheId,
drawnWidth,
drawnHeight,
/* trackTransform */ true
drawnHeight
);
this.suspendedCtx = this.ctx;
this.ctx = scratchCanvas.context;
const ctx = this.ctx;
ctx.setTransform(...this.suspendedCtx.mozCurrentTransform);
ctx.setTransform(...getCurrentTransform(this.suspendedCtx));
copyCtxState(this.suspendedCtx, ctx);
mirrorContextOperations(ctx, this.suspendedCtx);
@ -1940,7 +1769,7 @@ class CanvasGraphics {
let x = current.x,
y = current.y;
let startX, startY;
const currentTransform = ctx.mozCurrentTransform;
const currentTransform = getCurrentTransform(ctx);
// Most of the time the current transform is a scaling matrix
// so we don't need to transform points before computing min/max:
@ -2096,7 +1925,7 @@ class CanvasGraphics {
ctx.strokeStyle = strokeColor.getPattern(
ctx,
this,
ctx.mozCurrentTransformInverse,
getCurrentTransformInverse(ctx),
PathType.STROKE
);
this.rescaleAndStroke(/* saveRestore */ false);
@ -2129,7 +1958,7 @@ class CanvasGraphics {
ctx.fillStyle = fillColor.getPattern(
ctx,
this,
ctx.mozCurrentTransformInverse,
getCurrentTransformInverse(ctx),
PathType.FILL
);
needRestore = true;
@ -2383,7 +2212,7 @@ class CanvasGraphics {
if (isAddToPathSet) {
const paths = this.pendingTextPaths || (this.pendingTextPaths = []);
paths.push({
transform: ctx.mozCurrentTransform,
transform: getCurrentTransform(ctx),
x,
y,
fontSize,
@ -2398,8 +2227,7 @@ class CanvasGraphics {
const { context: ctx } = this.cachedCanvases.getCanvas(
"isFontSubpixelAAEnabled",
10,
10,
/* trackTransform */ false
10
);
ctx.scale(1.5, 1);
ctx.fillText("I", 0, 10);
@ -2459,10 +2287,10 @@ class CanvasGraphics {
const pattern = current.fillColor.getPattern(
ctx,
this,
ctx.mozCurrentTransformInverse,
getCurrentTransformInverse(ctx),
PathType.FILL
);
patternTransform = ctx.mozCurrentTransform;
patternTransform = getCurrentTransform(ctx);
ctx.restore();
ctx.fillStyle = pattern;
}
@ -2663,8 +2491,7 @@ class CanvasGraphics {
let pattern;
if (IR[0] === "TilingPattern") {
const color = IR[1];
const baseTransform =
this.baseTransform || this.ctx.mozCurrentTransform.slice();
const baseTransform = this.baseTransform || getCurrentTransform(this.ctx);
const canvasGraphicsFactory = {
createCanvasGraphics: ctx => {
return new CanvasGraphics(
@ -2735,11 +2562,11 @@ class CanvasGraphics {
ctx.fillStyle = pattern.getPattern(
ctx,
this,
ctx.mozCurrentTransformInverse,
getCurrentTransformInverse(ctx),
PathType.SHADING
);
const inv = ctx.mozCurrentTransformInverse;
const inv = getCurrentTransformInverse(ctx);
if (inv) {
const canvas = ctx.canvas;
const width = canvas.width;
@ -2790,13 +2617,13 @@ class CanvasGraphics {
this.transform(...matrix);
}
this.baseTransform = this.ctx.mozCurrentTransform;
this.baseTransform = getCurrentTransform(this.ctx);
if (bbox) {
const width = bbox[2] - bbox[0];
const height = bbox[3] - bbox[1];
this.ctx.rect(bbox[0], bbox[1], width, height);
this.current.updateRectMinMax(this.ctx.mozCurrentTransform, bbox);
this.current.updateRectMinMax(getCurrentTransform(this.ctx), bbox);
this.clip();
this.endPath();
}
@ -2847,7 +2674,7 @@ class CanvasGraphics {
warn("Knockout groups not supported.");
}
const currentTransform = currentCtx.mozCurrentTransform;
const currentTransform = getCurrentTransform(currentCtx);
if (group.matrix) {
currentCtx.transform(...group.matrix);
}
@ -2859,7 +2686,7 @@ class CanvasGraphics {
// will actually be.
let bounds = Util.getAxialAlignedBoundingBox(
group.bbox,
currentCtx.mozCurrentTransform
getCurrentTransform(currentCtx)
);
// Clip the bounding box to the current canvas.
const canvasBounds = [
@ -2896,8 +2723,7 @@ class CanvasGraphics {
const scratchCanvas = this.cachedCanvases.getCanvas(
cacheId,
drawnWidth,
drawnHeight,
/* trackTransform */ true
drawnHeight
);
const groupCtx = scratchCanvas.context;
@ -2959,7 +2785,7 @@ class CanvasGraphics {
this.restore();
} else {
this.ctx.restore();
const currentMtx = this.ctx.mozCurrentTransform;
const currentMtx = getCurrentTransform(this.ctx);
this.restore();
this.ctx.save();
this.ctx.setTransform(...currentMtx);
@ -3003,7 +2829,7 @@ class CanvasGraphics {
rect[3] = height;
const [scaleX, scaleY] = Util.singularValueDecompose2dScale(
this.ctx.mozCurrentTransform
getCurrentTransform(this.ctx)
);
const { viewportScale } = this;
const canvasWidth = Math.ceil(
@ -3021,7 +2847,6 @@ class CanvasGraphics {
this.annotationCanvasMap.set(id, canvas);
this.annotationCanvas.savedCtx = this.ctx;
this.ctx = context;
addContextCurrentTransform(this.ctx);
this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
resetCtxToDefault(this.ctx, this.foregroundColor);
@ -3100,7 +2925,7 @@ class CanvasGraphics {
const ctx = this.ctx;
ctx.save();
const currentTransform = ctx.mozCurrentTransform;
const currentTransform = getCurrentTransform(ctx);
ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
const mask = this._createMaskCanvas(img);
@ -3137,8 +2962,7 @@ class CanvasGraphics {
const maskCanvas = this.cachedCanvases.getCanvas(
"maskCanvas",
width,
height,
/* trackTransform */ false
height
);
const maskCtx = maskCanvas.context;
maskCtx.save();
@ -3152,7 +2976,7 @@ class CanvasGraphics {
? fillColor.getPattern(
maskCtx,
this,
ctx.mozCurrentTransformInverse,
getCurrentTransformInverse(ctx),
PathType.FILL
)
: fillColor;
@ -3241,17 +3065,19 @@ class CanvasGraphics {
const tmpCanvas = this.cachedCanvases.getCanvas(
"inlineImage",
width,
height,
/* trackTransform */ false
height
);
const tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
imgToPaint = tmpCanvas.canvas;
}
const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse);
const scaled = this._scaleImage(
imgToPaint,
getCurrentTransformInverse(ctx)
);
ctx.imageSmoothingEnabled = getImageSmoothingEnabled(
ctx.mozCurrentTransform,
getCurrentTransform(ctx),
imgData.interpolate
);
@ -3290,12 +3116,7 @@ class CanvasGraphics {
const w = imgData.width;
const h = imgData.height;
const tmpCanvas = this.cachedCanvases.getCanvas(
"inlineImage",
w,
h,
/* trackTransform */ false
);
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
const tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
@ -3409,7 +3230,7 @@ class CanvasGraphics {
getSinglePixelWidth() {
if (!this._cachedGetSinglePixelWidth) {
const m = this.ctx.mozCurrentTransform;
const m = getCurrentTransform(this.ctx);
if (m[1] === 0 && m[2] === 0) {
// Fast path
this._cachedGetSinglePixelWidth =
@ -3433,7 +3254,7 @@ class CanvasGraphics {
// to 1 after transform.
if (!this._cachedScaleForStroking) {
const { lineWidth } = this.current;
const m = this.ctx.mozCurrentTransform;
const m = getCurrentTransform(this.ctx);
let scaleX, scaleY;
if (m[1] === 0 && m[2] === 0) {
@ -3489,7 +3310,7 @@ class CanvasGraphics {
let savedMatrix, savedDashes, savedDashOffset;
if (saveRestore) {
savedMatrix = ctx.mozCurrentTransform.slice();
savedMatrix = getCurrentTransform(ctx);
savedDashes = ctx.getLineDash().slice();
savedDashOffset = ctx.lineDashOffset;
}
@ -3517,7 +3338,7 @@ class CanvasGraphics {
}
getCanvasPosition(x, y) {
const transform = this.ctx.mozCurrentTransform;
const transform = getCurrentTransform(this.ctx);
return [
transform[0] * x + transform[2] * y + transform[4],
transform[1] * x + transform[3] * y + transform[5],

View File

@ -641,6 +641,16 @@ function binarySearchFirstItem(items, condition, start = 0) {
return minIndex; /* === maxIndex */
}
function getCurrentTransform(ctx) {
const { a, b, c, d, e, f } = ctx.getTransform();
return [a, b, c, d, e, f];
}
function getCurrentTransformInverse(ctx) {
const { a, b, c, d, e, f } = ctx.getTransform().invertSelf();
return [a, b, c, d, e, f];
}
export {
binarySearchFirstItem,
deprecated,
@ -649,6 +659,8 @@ export {
DOMStandardFontDataFactory,
DOMSVGFactory,
getColorValues,
getCurrentTransform,
getCurrentTransformInverse,
getFilenameFromUrl,
getPdfFilenameFromUrl,
getRGB,

View File

@ -21,6 +21,7 @@ import {
Util,
warn,
} from "../shared/util.js";
import { getCurrentTransform } from "./display_utils.js";
import { isNodeJS } from "../shared/is_node.js";
const PathType = {
@ -96,7 +97,7 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
if (pathType === PathType.STROKE || pathType === PathType.FILL) {
const ownerBBox = owner.current.getClippedPathBoundingBox(
pathType,
ctx.mozCurrentTransform
getCurrentTransform(ctx)
) || [0, 0, 0, 0];
// Create a canvas that is only as big as the current path. This doesn't
// allow us to cache the pattern, but it generally creates much smaller
@ -409,7 +410,7 @@ class MeshShadingPattern extends BaseShadingPattern {
applyBoundingBox(ctx, this._bbox);
let scale;
if (pathType === PathType.SHADING) {
scale = Util.singularValueDecompose2dScale(ctx.mozCurrentTransform);
scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx));
} else {
// Obtain scale from matrix and current transformation matrix.
scale = Util.singularValueDecompose2dScale(owner.baseTransform);
@ -584,7 +585,7 @@ class TilingPattern {
this.clipBbox(graphics, adjustedX0, adjustedY0, adjustedX1, adjustedY1);
graphics.baseTransform = graphics.ctx.mozCurrentTransform.slice();
graphics.baseTransform = getCurrentTransform(graphics.ctx);
graphics.executeOperatorList(operatorList);
@ -620,7 +621,7 @@ class TilingPattern {
const bboxWidth = x1 - x0;
const bboxHeight = y1 - y0;
graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
graphics.current.updateRectMinMax(graphics.ctx.mozCurrentTransform, [
graphics.current.updateRectMinMax(getCurrentTransform(graphics.ctx), [
x0,
y0,
x1,