mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
Merge pull request #19689 from calixteman/use_path2d
[api-minor] Use a Path2D when doing a path operation in the canvas (bug 1946953)
This commit is contained in:
commit
d009e4b3a7
10 changed files with 367 additions and 374 deletions
|
@ -16,6 +16,7 @@
|
|||
import {
|
||||
AbortException,
|
||||
assert,
|
||||
DrawOPS,
|
||||
FONT_IDENTITY_MATRIX,
|
||||
FormatError,
|
||||
IDENTITY_MATRIX,
|
||||
|
@ -925,7 +926,7 @@ class PartialEvaluator {
|
|||
smaskOptions,
|
||||
operatorList,
|
||||
task,
|
||||
stateManager.state.clone(),
|
||||
stateManager.state.clone({ newPath: true }),
|
||||
localColorSpaceCache
|
||||
);
|
||||
}
|
||||
|
@ -1383,80 +1384,112 @@ class PartialEvaluator {
|
|||
return promise;
|
||||
}
|
||||
|
||||
buildPath(operatorList, fn, args, parsingText = false) {
|
||||
const lastIndex = operatorList.length - 1;
|
||||
if (!args) {
|
||||
args = [];
|
||||
}
|
||||
if (
|
||||
lastIndex < 0 ||
|
||||
operatorList.fnArray[lastIndex] !== OPS.constructPath
|
||||
) {
|
||||
// Handle corrupt PDF documents that contains path operators inside of
|
||||
// text objects, which may shift subsequent text, by enclosing the path
|
||||
// operator in save/restore operators (fixes issue10542_reduced.pdf).
|
||||
//
|
||||
// Note that this will effectively disable the optimization in the
|
||||
// `else` branch below, but given that this type of corruption is
|
||||
// *extremely* rare that shouldn't really matter much in practice.
|
||||
if (parsingText) {
|
||||
warn(`Encountered path operator "${fn}" inside of a text object.`);
|
||||
operatorList.addOp(OPS.save, null);
|
||||
buildPath(fn, args, state) {
|
||||
const { pathMinMax: minMax, pathBuffer } = state;
|
||||
switch (fn | 0) {
|
||||
case OPS.rectangle: {
|
||||
const x = (state.currentPointX = args[0]);
|
||||
const y = (state.currentPointY = args[1]);
|
||||
const width = args[2];
|
||||
const height = args[3];
|
||||
const xw = x + width;
|
||||
const yh = y + height;
|
||||
if (width === 0 || height === 0) {
|
||||
pathBuffer.push(
|
||||
DrawOPS.moveTo,
|
||||
x,
|
||||
y,
|
||||
DrawOPS.lineTo,
|
||||
xw,
|
||||
yh,
|
||||
DrawOPS.closePath
|
||||
);
|
||||
} else {
|
||||
pathBuffer.push(
|
||||
DrawOPS.moveTo,
|
||||
x,
|
||||
y,
|
||||
DrawOPS.lineTo,
|
||||
xw,
|
||||
y,
|
||||
DrawOPS.lineTo,
|
||||
xw,
|
||||
yh,
|
||||
DrawOPS.lineTo,
|
||||
x,
|
||||
yh,
|
||||
DrawOPS.closePath
|
||||
);
|
||||
}
|
||||
minMax[0] = Math.min(minMax[0], x, xw);
|
||||
minMax[1] = Math.min(minMax[1], y, yh);
|
||||
minMax[2] = Math.max(minMax[2], x, xw);
|
||||
minMax[3] = Math.max(minMax[3], y, yh);
|
||||
break;
|
||||
}
|
||||
|
||||
let minMax;
|
||||
switch (fn) {
|
||||
case OPS.rectangle:
|
||||
const x = args[0] + args[2];
|
||||
const y = args[1] + args[3];
|
||||
minMax = [
|
||||
Math.min(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[1], args[0], args[1]];
|
||||
break;
|
||||
default:
|
||||
minMax = [Infinity, Infinity, -Infinity, -Infinity];
|
||||
break;
|
||||
case OPS.moveTo: {
|
||||
const x = (state.currentPointX = args[0]);
|
||||
const y = (state.currentPointY = args[1]);
|
||||
pathBuffer.push(DrawOPS.moveTo, x, y);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
operatorList.addOp(OPS.constructPath, [[fn], args, minMax]);
|
||||
|
||||
if (parsingText) {
|
||||
operatorList.addOp(OPS.restore, null);
|
||||
case OPS.lineTo: {
|
||||
const x = (state.currentPointX = args[0]);
|
||||
const y = (state.currentPointY = args[1]);
|
||||
pathBuffer.push(DrawOPS.lineTo, x, y);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const opArgs = operatorList.argsArray[lastIndex];
|
||||
opArgs[0].push(fn);
|
||||
opArgs[1].push(...args);
|
||||
const minMax = opArgs[2];
|
||||
|
||||
// Compute min/max in the worker instead of the main thread.
|
||||
// If the current matrix (when drawing) is a scaling one
|
||||
// then min/max can be easily computed in using those values.
|
||||
// Only rectangle, lineTo and moveTo are handled here since
|
||||
// Bezier stuff requires to have the starting point.
|
||||
switch (fn) {
|
||||
case OPS.rectangle:
|
||||
const x = args[0] + args[2];
|
||||
const y = args[1] + args[3];
|
||||
minMax[0] = Math.min(minMax[0], args[0], x);
|
||||
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.min(minMax[1], args[1]);
|
||||
minMax[2] = Math.max(minMax[2], args[0]);
|
||||
minMax[3] = Math.max(minMax[3], args[1]);
|
||||
break;
|
||||
case OPS.curveTo: {
|
||||
const startX = state.currentPointX;
|
||||
const startY = state.currentPointY;
|
||||
const [x1, y1, x2, y2, x, y] = args;
|
||||
state.currentPointX = x;
|
||||
state.currentPointY = y;
|
||||
pathBuffer.push(DrawOPS.curveTo, x1, y1, x2, y2, x, y);
|
||||
Util.bezierBoundingBox(startX, startY, x1, y1, x2, y2, x, y, minMax);
|
||||
break;
|
||||
}
|
||||
case OPS.curveTo2: {
|
||||
const startX = state.currentPointX;
|
||||
const startY = state.currentPointY;
|
||||
const [x1, y1, x, y] = args;
|
||||
state.currentPointX = x;
|
||||
state.currentPointY = y;
|
||||
pathBuffer.push(DrawOPS.curveTo, startX, startY, x1, y1, x, y);
|
||||
Util.bezierBoundingBox(
|
||||
startX,
|
||||
startY,
|
||||
startX,
|
||||
startY,
|
||||
x1,
|
||||
y1,
|
||||
x,
|
||||
y,
|
||||
minMax
|
||||
);
|
||||
break;
|
||||
}
|
||||
case OPS.curveTo3: {
|
||||
const startX = state.currentPointX;
|
||||
const startY = state.currentPointY;
|
||||
const [x1, y1, x, y] = args;
|
||||
state.currentPointX = x;
|
||||
state.currentPointY = y;
|
||||
pathBuffer.push(DrawOPS.curveTo, x1, y1, x, y, x, y);
|
||||
Util.bezierBoundingBox(startX, startY, x1, y1, x, y, x, y, minMax);
|
||||
break;
|
||||
}
|
||||
case OPS.closePath:
|
||||
pathBuffer.push(DrawOPS.closePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1731,7 +1764,6 @@ class PartialEvaluator {
|
|||
|
||||
const self = this;
|
||||
const xref = this.xref;
|
||||
let parsingText = false;
|
||||
const localImageCache = new LocalImageCache();
|
||||
const localColorSpaceCache = new LocalColorSpaceCache();
|
||||
const localGStateCache = new LocalGStateCache();
|
||||
|
@ -1847,7 +1879,7 @@ class PartialEvaluator {
|
|||
null,
|
||||
operatorList,
|
||||
task,
|
||||
stateManager.state.clone(),
|
||||
stateManager.state.clone({ newPath: true }),
|
||||
localColorSpaceCache
|
||||
)
|
||||
.then(function () {
|
||||
|
@ -1909,12 +1941,6 @@ class PartialEvaluator {
|
|||
})
|
||||
);
|
||||
return;
|
||||
case OPS.beginText:
|
||||
parsingText = true;
|
||||
break;
|
||||
case OPS.endText:
|
||||
parsingText = false;
|
||||
break;
|
||||
case OPS.endInlineImage:
|
||||
const cacheKey = args[0].cacheKey;
|
||||
if (cacheKey) {
|
||||
|
@ -2237,8 +2263,40 @@ class PartialEvaluator {
|
|||
case OPS.curveTo3:
|
||||
case OPS.closePath:
|
||||
case OPS.rectangle:
|
||||
self.buildPath(operatorList, fn, args, parsingText);
|
||||
self.buildPath(fn, args, stateManager.state);
|
||||
continue;
|
||||
case OPS.stroke:
|
||||
case OPS.closeStroke:
|
||||
case OPS.fill:
|
||||
case OPS.eoFill:
|
||||
case OPS.fillStroke:
|
||||
case OPS.eoFillStroke:
|
||||
case OPS.closeFillStroke:
|
||||
case OPS.closeEOFillStroke:
|
||||
case OPS.endPath: {
|
||||
const {
|
||||
state: { pathBuffer, pathMinMax },
|
||||
} = stateManager;
|
||||
if (
|
||||
fn === OPS.closeStroke ||
|
||||
fn === OPS.closeFillStroke ||
|
||||
fn === OPS.closeEOFillStroke
|
||||
) {
|
||||
pathBuffer.push(DrawOPS.closePath);
|
||||
}
|
||||
if (pathBuffer.length === 0) {
|
||||
operatorList.addOp(OPS.constructPath, [fn, [null], null]);
|
||||
} else {
|
||||
operatorList.addOp(OPS.constructPath, [
|
||||
fn,
|
||||
[new Float32Array(pathBuffer)],
|
||||
pathMinMax.slice(),
|
||||
]);
|
||||
pathBuffer.length = 0;
|
||||
pathMinMax.set([Infinity, Infinity, -Infinity, -Infinity], 0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case OPS.markPoint:
|
||||
case OPS.markPointProps:
|
||||
case OPS.beginCompat:
|
||||
|
@ -4935,6 +4993,16 @@ class EvalState {
|
|||
this._fillColorSpace = this._strokeColorSpace = ColorSpaceUtils.gray;
|
||||
this.patternFillColorSpace = null;
|
||||
this.patternStrokeColorSpace = null;
|
||||
|
||||
// Path stuff.
|
||||
this.currentPointX = this.currentPointY = 0;
|
||||
this.pathMinMax = new Float32Array([
|
||||
Infinity,
|
||||
Infinity,
|
||||
-Infinity,
|
||||
-Infinity,
|
||||
]);
|
||||
this.pathBuffer = [];
|
||||
}
|
||||
|
||||
get fillColorSpace() {
|
||||
|
@ -4953,8 +5021,18 @@ class EvalState {
|
|||
this._strokeColorSpace = this.patternStrokeColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return Object.create(this);
|
||||
clone({ newPath = false } = {}) {
|
||||
const clone = Object.create(this);
|
||||
if (newPath) {
|
||||
clone.pathBuffer = [];
|
||||
clone.pathMinMax = new Float32Array([
|
||||
Infinity,
|
||||
Infinity,
|
||||
-Infinity,
|
||||
-Infinity,
|
||||
]);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -703,6 +703,12 @@ class OperatorList {
|
|||
transfers.push(arg.data.buffer);
|
||||
}
|
||||
break;
|
||||
case OPS.constructPath:
|
||||
const [, [data], minMax] = argsArray[i];
|
||||
if (data) {
|
||||
transfers.push(data.buffer, minMax.buffer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return transfers;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
DrawOPS,
|
||||
FeatureTest,
|
||||
FONT_IDENTITY_MATRIX,
|
||||
IDENTITY_MATRIX,
|
||||
|
@ -58,6 +59,10 @@ const MAX_SIZE_TO_COMPILE = 1000;
|
|||
|
||||
const FULL_CHUNK_HEIGHT = 16;
|
||||
|
||||
// Only used in rescaleAndStroke. The goal is to avoid
|
||||
// creating a new DOMMatrix object each time we need it.
|
||||
const SCALE_MATRIX = new DOMMatrix();
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -502,19 +507,6 @@ class CanvasExtraState {
|
|||
return clone;
|
||||
}
|
||||
|
||||
setCurrentPoint(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
updatePathMinMax(transform, x, y) {
|
||||
[x, y] = Util.applyTransform([x, y], transform);
|
||||
this.minX = Math.min(this.minX, x);
|
||||
this.minY = Math.min(this.minY, y);
|
||||
this.maxX = Math.max(this.maxX, x);
|
||||
this.maxY = Math.max(this.maxY, y);
|
||||
}
|
||||
|
||||
updateRectMinMax(transform, rect) {
|
||||
const p1 = Util.applyTransform(rect, transform);
|
||||
const p2 = Util.applyTransform(rect.slice(2), transform);
|
||||
|
@ -527,22 +519,6 @@ class CanvasExtraState {
|
|||
this.maxY = Math.max(this.maxY, p1[1], p2[1], p3[1], p4[1]);
|
||||
}
|
||||
|
||||
updateScalingPathMinMax(transform, minMax) {
|
||||
Util.scaleMinMax(transform, minMax);
|
||||
this.minX = Math.min(this.minX, minMax[0]);
|
||||
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, minMax);
|
||||
if (minMax) {
|
||||
return;
|
||||
}
|
||||
this.updateRectMinMax(transform, box);
|
||||
}
|
||||
|
||||
getPathBoundingBox(pathType = PathType.FILL, transform = null) {
|
||||
const box = [this.minX, this.minY, this.maxX, this.maxY];
|
||||
if (pathType === PathType.STROKE) {
|
||||
|
@ -1612,156 +1588,54 @@ class CanvasGraphics {
|
|||
}
|
||||
|
||||
// Path
|
||||
constructPath(ops, args, minMax) {
|
||||
const ctx = this.ctx;
|
||||
const current = this.current;
|
||||
let x = current.x,
|
||||
y = current.y;
|
||||
let startX, startY;
|
||||
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:
|
||||
// we can compute min/max first and then smartly "apply" the
|
||||
// transform (see Util.scaleMinMax).
|
||||
// For rectangle, moveTo and lineTo, min/max are computed in the
|
||||
// worker (see evaluator.js).
|
||||
const isScalingMatrix =
|
||||
(currentTransform[0] === 0 && currentTransform[3] === 0) ||
|
||||
(currentTransform[1] === 0 && currentTransform[2] === 0);
|
||||
const minMaxForBezier = isScalingMatrix ? minMax.slice(0) : null;
|
||||
|
||||
for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
|
||||
switch (ops[i] | 0) {
|
||||
case OPS.rectangle:
|
||||
x = args[j++];
|
||||
y = args[j++];
|
||||
const width = args[j++];
|
||||
const height = args[j++];
|
||||
|
||||
const xw = x + width;
|
||||
const yh = y + height;
|
||||
ctx.moveTo(x, y);
|
||||
if (width === 0 || height === 0) {
|
||||
ctx.lineTo(xw, yh);
|
||||
} else {
|
||||
ctx.lineTo(xw, y);
|
||||
ctx.lineTo(xw, yh);
|
||||
ctx.lineTo(x, yh);
|
||||
}
|
||||
if (!isScalingMatrix) {
|
||||
current.updateRectMinMax(currentTransform, [x, y, xw, yh]);
|
||||
}
|
||||
ctx.closePath();
|
||||
break;
|
||||
case OPS.moveTo:
|
||||
x = args[j++];
|
||||
y = args[j++];
|
||||
ctx.moveTo(x, y);
|
||||
if (!isScalingMatrix) {
|
||||
current.updatePathMinMax(currentTransform, x, y);
|
||||
}
|
||||
break;
|
||||
case OPS.lineTo:
|
||||
x = args[j++];
|
||||
y = args[j++];
|
||||
ctx.lineTo(x, y);
|
||||
if (!isScalingMatrix) {
|
||||
current.updatePathMinMax(currentTransform, x, y);
|
||||
}
|
||||
break;
|
||||
case OPS.curveTo:
|
||||
startX = x;
|
||||
startY = y;
|
||||
x = args[j + 4];
|
||||
y = args[j + 5];
|
||||
ctx.bezierCurveTo(
|
||||
args[j],
|
||||
args[j + 1],
|
||||
args[j + 2],
|
||||
args[j + 3],
|
||||
x,
|
||||
y
|
||||
);
|
||||
current.updateCurvePathMinMax(
|
||||
currentTransform,
|
||||
startX,
|
||||
startY,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
args[j + 2],
|
||||
args[j + 3],
|
||||
x,
|
||||
y,
|
||||
minMaxForBezier
|
||||
);
|
||||
j += 6;
|
||||
break;
|
||||
case OPS.curveTo2:
|
||||
startX = x;
|
||||
startY = y;
|
||||
ctx.bezierCurveTo(
|
||||
x,
|
||||
y,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
args[j + 2],
|
||||
args[j + 3]
|
||||
);
|
||||
current.updateCurvePathMinMax(
|
||||
currentTransform,
|
||||
startX,
|
||||
startY,
|
||||
x,
|
||||
y,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
args[j + 2],
|
||||
args[j + 3],
|
||||
minMaxForBezier
|
||||
);
|
||||
x = args[j + 2];
|
||||
y = args[j + 3];
|
||||
j += 4;
|
||||
break;
|
||||
case OPS.curveTo3:
|
||||
startX = x;
|
||||
startY = y;
|
||||
x = args[j + 2];
|
||||
y = args[j + 3];
|
||||
ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
|
||||
current.updateCurvePathMinMax(
|
||||
currentTransform,
|
||||
startX,
|
||||
startY,
|
||||
args[j],
|
||||
args[j + 1],
|
||||
x,
|
||||
y,
|
||||
x,
|
||||
y,
|
||||
minMaxForBezier
|
||||
);
|
||||
j += 4;
|
||||
break;
|
||||
case OPS.closePath:
|
||||
ctx.closePath();
|
||||
break;
|
||||
constructPath(op, data, minMax) {
|
||||
let [path] = data;
|
||||
if (!minMax) {
|
||||
// The path is empty, so no need to update the current minMax.
|
||||
path ||= data[0] = new Path2D();
|
||||
this[op](path);
|
||||
return;
|
||||
}
|
||||
if (!(path instanceof Path2D)) {
|
||||
// Using a SVG string is slightly slower than using the following loop.
|
||||
const path2d = (data[0] = new Path2D());
|
||||
for (let i = 0, ii = path.length; i < ii; ) {
|
||||
switch (path[i++]) {
|
||||
case DrawOPS.moveTo:
|
||||
path2d.moveTo(path[i++], path[i++]);
|
||||
break;
|
||||
case DrawOPS.lineTo:
|
||||
path2d.lineTo(path[i++], path[i++]);
|
||||
break;
|
||||
case DrawOPS.curveTo:
|
||||
path2d.bezierCurveTo(
|
||||
path[i++],
|
||||
path[i++],
|
||||
path[i++],
|
||||
path[i++],
|
||||
path[i++],
|
||||
path[i++]
|
||||
);
|
||||
break;
|
||||
case DrawOPS.closePath:
|
||||
path2d.closePath();
|
||||
break;
|
||||
default:
|
||||
warn(`Unrecognized drawing path operator: ${path[i - 1]}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
path = path2d;
|
||||
}
|
||||
|
||||
if (isScalingMatrix) {
|
||||
current.updateScalingPathMinMax(currentTransform, minMaxForBezier);
|
||||
}
|
||||
|
||||
current.setCurrentPoint(x, y);
|
||||
this.current.updateRectMinMax(getCurrentTransform(this.ctx), minMax);
|
||||
this[op](path);
|
||||
}
|
||||
|
||||
closePath() {
|
||||
this.ctx.closePath();
|
||||
}
|
||||
|
||||
stroke(consumePath = true) {
|
||||
stroke(path, consumePath = true) {
|
||||
const ctx = this.ctx;
|
||||
const strokeColor = this.current.strokeColor;
|
||||
// For stroke we want to temporarily change the global alpha to the
|
||||
|
@ -1769,6 +1643,9 @@ class CanvasGraphics {
|
|||
ctx.globalAlpha = this.current.strokeAlpha;
|
||||
if (this.contentVisible) {
|
||||
if (typeof strokeColor === "object" && strokeColor?.getPattern) {
|
||||
const baseTransform = strokeColor.isModifyingCurrentTransform()
|
||||
? ctx.getTransform()
|
||||
: null;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = strokeColor.getPattern(
|
||||
ctx,
|
||||
|
@ -1776,31 +1653,41 @@ class CanvasGraphics {
|
|||
getCurrentTransformInverse(ctx),
|
||||
PathType.STROKE
|
||||
);
|
||||
this.rescaleAndStroke(/* saveRestore */ false);
|
||||
if (baseTransform) {
|
||||
const newPath = new Path2D();
|
||||
newPath.addPath(
|
||||
path,
|
||||
ctx.getTransform().invertSelf().multiplySelf(baseTransform)
|
||||
);
|
||||
path = newPath;
|
||||
}
|
||||
this.rescaleAndStroke(path, /* saveRestore */ false);
|
||||
ctx.restore();
|
||||
} else {
|
||||
this.rescaleAndStroke(/* saveRestore */ true);
|
||||
this.rescaleAndStroke(path, /* saveRestore */ true);
|
||||
}
|
||||
}
|
||||
if (consumePath) {
|
||||
this.consumePath(this.current.getClippedPathBoundingBox());
|
||||
this.consumePath(path, this.current.getClippedPathBoundingBox());
|
||||
}
|
||||
// Restore the global alpha to the fill alpha
|
||||
ctx.globalAlpha = this.current.fillAlpha;
|
||||
}
|
||||
|
||||
closeStroke() {
|
||||
this.closePath();
|
||||
this.stroke();
|
||||
closeStroke(path) {
|
||||
this.stroke(path);
|
||||
}
|
||||
|
||||
fill(consumePath = true) {
|
||||
fill(path, consumePath = true) {
|
||||
const ctx = this.ctx;
|
||||
const fillColor = this.current.fillColor;
|
||||
const isPatternFill = this.current.patternFill;
|
||||
let needRestore = false;
|
||||
|
||||
if (isPatternFill) {
|
||||
const baseTransform = fillColor.isModifyingCurrentTransform()
|
||||
? ctx.getTransform()
|
||||
: null;
|
||||
ctx.save();
|
||||
ctx.fillStyle = fillColor.getPattern(
|
||||
ctx,
|
||||
|
@ -1808,16 +1695,24 @@ class CanvasGraphics {
|
|||
getCurrentTransformInverse(ctx),
|
||||
PathType.FILL
|
||||
);
|
||||
if (baseTransform) {
|
||||
const newPath = new Path2D();
|
||||
newPath.addPath(
|
||||
path,
|
||||
ctx.getTransform().invertSelf().multiplySelf(baseTransform)
|
||||
);
|
||||
path = newPath;
|
||||
}
|
||||
needRestore = true;
|
||||
}
|
||||
|
||||
const intersect = this.current.getClippedPathBoundingBox();
|
||||
if (this.contentVisible && intersect !== null) {
|
||||
if (this.pendingEOFill) {
|
||||
ctx.fill("evenodd");
|
||||
ctx.fill(path, "evenodd");
|
||||
this.pendingEOFill = false;
|
||||
} else {
|
||||
ctx.fill();
|
||||
ctx.fill(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1825,40 +1720,38 @@ class CanvasGraphics {
|
|||
ctx.restore();
|
||||
}
|
||||
if (consumePath) {
|
||||
this.consumePath(intersect);
|
||||
this.consumePath(path, intersect);
|
||||
}
|
||||
}
|
||||
|
||||
eoFill() {
|
||||
eoFill(path) {
|
||||
this.pendingEOFill = true;
|
||||
this.fill();
|
||||
this.fill(path);
|
||||
}
|
||||
|
||||
fillStroke() {
|
||||
this.fill(false);
|
||||
this.stroke(false);
|
||||
fillStroke(path) {
|
||||
this.fill(path, false);
|
||||
this.stroke(path, false);
|
||||
|
||||
this.consumePath();
|
||||
this.consumePath(path);
|
||||
}
|
||||
|
||||
eoFillStroke() {
|
||||
eoFillStroke(path) {
|
||||
this.pendingEOFill = true;
|
||||
this.fillStroke();
|
||||
this.fillStroke(path);
|
||||
}
|
||||
|
||||
closeFillStroke() {
|
||||
this.closePath();
|
||||
this.fillStroke();
|
||||
closeFillStroke(path) {
|
||||
this.fillStroke(path);
|
||||
}
|
||||
|
||||
closeEOFillStroke() {
|
||||
closeEOFillStroke(path) {
|
||||
this.pendingEOFill = true;
|
||||
this.closePath();
|
||||
this.fillStroke();
|
||||
this.fillStroke(path);
|
||||
}
|
||||
|
||||
endPath() {
|
||||
this.consumePath();
|
||||
endPath(path) {
|
||||
this.consumePath(path);
|
||||
}
|
||||
|
||||
// Clipping
|
||||
|
@ -3168,7 +3061,7 @@ class CanvasGraphics {
|
|||
|
||||
// Helper functions
|
||||
|
||||
consumePath(clipBox) {
|
||||
consumePath(path, clipBox) {
|
||||
const isEmpty = this.current.isEmptyClip();
|
||||
if (this.pendingClip) {
|
||||
this.current.updateClipFromPath();
|
||||
|
@ -3180,9 +3073,9 @@ class CanvasGraphics {
|
|||
if (this.pendingClip) {
|
||||
if (!isEmpty) {
|
||||
if (this.pendingClip === EO_CLIP) {
|
||||
ctx.clip("evenodd");
|
||||
ctx.clip(path, "evenodd");
|
||||
} else {
|
||||
ctx.clip();
|
||||
ctx.clip(path);
|
||||
}
|
||||
}
|
||||
this.pendingClip = null;
|
||||
|
@ -3267,15 +3160,16 @@ class CanvasGraphics {
|
|||
|
||||
// Rescale before stroking in order to have a final lineWidth
|
||||
// with both thicknesses greater or equal to 1.
|
||||
rescaleAndStroke(saveRestore) {
|
||||
const { ctx } = this;
|
||||
const { lineWidth } = this.current;
|
||||
rescaleAndStroke(path, saveRestore) {
|
||||
const {
|
||||
ctx,
|
||||
current: { lineWidth },
|
||||
} = this;
|
||||
const [scaleX, scaleY] = this.getScaleForStroking();
|
||||
|
||||
ctx.lineWidth = lineWidth || 1;
|
||||
|
||||
if (scaleX === 1 && scaleY === 1) {
|
||||
ctx.stroke();
|
||||
if (scaleX === scaleY) {
|
||||
ctx.lineWidth = (lineWidth || 1) * scaleX;
|
||||
ctx.stroke(path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3285,6 +3179,10 @@ class CanvasGraphics {
|
|||
}
|
||||
|
||||
ctx.scale(scaleX, scaleY);
|
||||
SCALE_MATRIX.a = 1 / scaleX;
|
||||
SCALE_MATRIX.d = 1 / scaleY;
|
||||
const newPath = new Path2D();
|
||||
newPath.addPath(path, SCALE_MATRIX);
|
||||
|
||||
// How the dashed line is rendered depends on the current transform...
|
||||
// so we added a rescale to handle too thin lines and consequently
|
||||
|
@ -3299,7 +3197,8 @@ class CanvasGraphics {
|
|||
ctx.lineDashOffset /= scale;
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
ctx.lineWidth = lineWidth || 1;
|
||||
ctx.stroke(newPath);
|
||||
|
||||
if (saveRestore) {
|
||||
ctx.restore();
|
||||
|
|
|
@ -43,6 +43,10 @@ class BaseShadingPattern {
|
|||
}
|
||||
}
|
||||
|
||||
isModifyingCurrentTransform() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getPattern() {
|
||||
unreachable("Abstract method `getPattern` called.");
|
||||
}
|
||||
|
@ -388,6 +392,10 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||
};
|
||||
}
|
||||
|
||||
isModifyingCurrentTransform() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getPattern(ctx, owner, inverse, pathType) {
|
||||
applyBoundingBox(ctx, this._bbox);
|
||||
let scale;
|
||||
|
@ -704,6 +712,10 @@ class TilingPattern {
|
|||
}
|
||||
}
|
||||
|
||||
isModifyingCurrentTransform() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getPattern(ctx, owner, inverse, pathType) {
|
||||
// PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix.
|
||||
let matrix = inverse;
|
||||
|
|
|
@ -341,6 +341,15 @@ const OPS = {
|
|||
setFillTransparent: 93,
|
||||
};
|
||||
|
||||
// In order to have a switch statement that is fast (i.e. which use a jump
|
||||
// table), we need to have the OPS in a contiguous range.
|
||||
const DrawOPS = {
|
||||
moveTo: 0,
|
||||
lineTo: 1,
|
||||
curveTo: 2,
|
||||
closePath: 3,
|
||||
};
|
||||
|
||||
const PasswordResponses = {
|
||||
NEED_PASSWORD: 1,
|
||||
INCORRECT_PASSWORD: 2,
|
||||
|
@ -667,57 +676,6 @@ class Util {
|
|||
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
|
||||
}
|
||||
|
||||
// Apply a scaling matrix to some min/max values.
|
||||
// If a scaling factor is negative then min and max must be
|
||||
// swapped.
|
||||
static scaleMinMax(transform, minMax) {
|
||||
let temp;
|
||||
if (transform[0]) {
|
||||
if (transform[0] < 0) {
|
||||
temp = minMax[0];
|
||||
minMax[0] = minMax[2];
|
||||
minMax[2] = temp;
|
||||
}
|
||||
minMax[0] *= transform[0];
|
||||
minMax[2] *= transform[0];
|
||||
|
||||
if (transform[3] < 0) {
|
||||
temp = minMax[1];
|
||||
minMax[1] = minMax[3];
|
||||
minMax[3] = temp;
|
||||
}
|
||||
minMax[1] *= transform[3];
|
||||
minMax[3] *= transform[3];
|
||||
} else {
|
||||
temp = minMax[0];
|
||||
minMax[0] = minMax[1];
|
||||
minMax[1] = temp;
|
||||
temp = minMax[2];
|
||||
minMax[2] = minMax[3];
|
||||
minMax[3] = temp;
|
||||
|
||||
if (transform[1] < 0) {
|
||||
temp = minMax[1];
|
||||
minMax[1] = minMax[3];
|
||||
minMax[3] = temp;
|
||||
}
|
||||
minMax[1] *= transform[1];
|
||||
minMax[3] *= transform[1];
|
||||
|
||||
if (transform[2] < 0) {
|
||||
temp = minMax[0];
|
||||
minMax[0] = minMax[2];
|
||||
minMax[2] = temp;
|
||||
}
|
||||
minMax[0] *= transform[2];
|
||||
minMax[2] *= transform[2];
|
||||
}
|
||||
minMax[0] += transform[4];
|
||||
minMax[1] += transform[5];
|
||||
minMax[2] += transform[4];
|
||||
minMax[3] += transform[5];
|
||||
}
|
||||
|
||||
// Concatenates two transformation matrices together and returns the result.
|
||||
static transform(m1, m2) {
|
||||
return [
|
||||
|
@ -1223,6 +1181,7 @@ export {
|
|||
bytesToString,
|
||||
createValidAbsoluteUrl,
|
||||
DocumentActionEventType,
|
||||
DrawOPS,
|
||||
FeatureTest,
|
||||
FONT_IDENTITY_MATRIX,
|
||||
FormatError,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue