Compare commits

...

6 Commits

Author SHA1 Message Date
calixteman
d009e4b3a7
Merge pull request #19689 from calixteman/use_path2d
[api-minor] Use a Path2D when doing a path operation in the canvas (bug 1946953)
2025-03-22 21:46:27 +01:00
Tim van der Meij
72212a892b
Merge pull request #19705 from timvandermeij/integration-tests-scripting-isolate
Isolate the scripting integration tests
2025-03-22 20:38:50 +01:00
Calixte Denizet
be1f5671bb [api-minor] Use a Path2D when doing a path operation in the canvas (bug 1946953)
With this patch, all the paths components are collected in the worker until a path
operation is met (i.e., stroke, fill, ...).
Then in the canvas a Path2D is created and will replace the path data transfered from the worker,
this way when rescaling, the Path2D can be reused.
In term of performances, using Path2D is very slightly improving speed when scaling the canvas.
2025-03-22 20:35:24 +01:00
Tim van der Meij
1c73e526e0
Isolate the scripting integration tests
To avoid being able to introduce dependencies between tests, and to
bring existing dependencies to the surface, this commit makes sure that
we close the document between tests so that we can't accidentally rely
on state set by a previous test. This prevents multiple tests from
failing if one of them fails and makes debugging easier by being able to
run each test on their own independent of other tests.

This commit, combined with the previous ones, is enough to make the
scripting integration test suite pass consistently if random mode in
Jasmine is enabled, proving that the tests are fully isolated now.
2025-03-22 18:31:23 +01:00
Tim van der Meij
0f3692a621
Remove the dependency between the order-dependent scripting integration tests
The integration tests are order-dependent because they rely on input
field state set by a previous test. This commit fixes the issue by
updating the values to match the initial state of the document, which
makes sure that we don't build upon values from previous tests while
still testing the intended logic in the individual tests like before.
2025-03-22 18:31:23 +01:00
Tim van der Meij
334d681359
Refactor the order-dependent scripting integration tests
By checking for the expected value directly we can shorten the code, and
it simplifies removing the dependencies between the tests in the next
commit (by having fewer places to change). Note that this follows the
same pattern as PRs #19192, #19001 and #18399 and also helps to remove
any further possibilities for intermittent failures.
2025-03-22 18:31:10 +01:00
11 changed files with 486 additions and 530 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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,

View File

@ -74,11 +74,11 @@ describe("Interaction", () => {
describe("in 160F-2019.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("160F-2019.pdf", getSelector("416R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -150,15 +150,12 @@ describe("Interaction", () => {
await page.type(getSelector("416R"), "3.14159");
await page.click(getSelector("419R"));
const valueFnStr = `${getQuerySelector("416R")}.value !== "3.14159"`;
await page.waitForFunction(valueFnStr);
const text = await page.$eval(getSelector("416R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("3,14");
const sum = await page.$eval(getSelector("427R"), el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("3,14");
await page.waitForFunction(
`${getQuerySelector("416R")}.value === "3,14"`
);
await page.waitForFunction(
`${getQuerySelector("427R")}.value === "3,14"`
);
})
);
});
@ -170,36 +167,23 @@ describe("Interaction", () => {
await page.type(getSelector("448R"), "61803");
await page.click(getSelector("419R"));
const valueOneFnStr = `${getQuerySelector("448R")}.value !== "61803"`;
await page.waitForFunction(valueOneFnStr);
let text = await page.$eval(getSelector("448R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("61.803,00");
await page.waitForFunction(
`${getQuerySelector("448R")}.value === "61.803,00"`
);
await page.click(getSelector("448R"));
const valueTwoFnStr = `${getQuerySelector(
"448R"
)}.value !== "61.803,00"`;
await page.waitForFunction(valueTwoFnStr);
text = await page.$eval(getSelector("448R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("61803");
await page.waitForFunction(
`${getQuerySelector("448R")}.value === "61803"`
);
// Clear the textfield
await clearInput(page, getSelector("448R"));
await page.type(getSelector("448R"), "1.61803");
await page.click(getSelector("419R"));
const valueThreeFnStr = `${getQuerySelector(
"448R"
)}.value !== "1.61803"`;
await page.waitForFunction(valueThreeFnStr);
text = await page.$eval(getSelector("448R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("1,62");
await page.waitForFunction(
`${getQuerySelector("448R")}.value === "1,62"`
);
})
);
});
@ -209,24 +193,14 @@ describe("Interaction", () => {
pages.map(async ([browserName, page]) => {
await waitForScripting(page);
const prevSum = await page.$eval(getSelector("427R"), el => el.value);
await page.type(getSelector("422R"), "2.7182818");
await page.keyboard.press("Tab");
await page.waitForFunction(
`${getQuerySelector("422R")}.value !== "2.7182818"`
`${getQuerySelector("422R")}.value === "2,72"`
);
const text = await page.$eval(getSelector("422R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("2,72");
await page.waitForFunction(
`${getQuerySelector("427R")}.value !== "${prevSum}"`
`${getQuerySelector("427R")}.value === "2,72"`
);
const sum = await page.$eval(getSelector("427R"), el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("5,86");
})
);
});
@ -236,21 +210,14 @@ describe("Interaction", () => {
pages.map(async ([browserName, page]) => {
await waitForScripting(page);
let sum = await page.$eval(getSelector("471R"), el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("4,24");
await page.type(getSelector("436R"), "0.69314");
await page.keyboard.press("Escape");
const text = await page.$eval(getSelector("436R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("0.69314");
await page.waitForFunction(
`${getQuerySelector("471R")}.value !== "${sum}"`
`${getQuerySelector("436R")}.value === "0.69314"`
);
await page.waitForFunction(
`${getQuerySelector("471R")}.value === "0,69"`
);
sum = await page.$eval(getSelector("471R"), el => el.value);
expect(sum).withContext(`In ${browserName}`).toEqual("3,55");
})
);
});
@ -260,19 +227,14 @@ describe("Interaction", () => {
pages.map(async ([browserName, page]) => {
await waitForScripting(page);
const prevSum = await page.$eval(getSelector("427R"), el => el.value);
await page.type(getSelector("419R"), "0.577215");
await page.keyboard.press("Enter");
const text = await page.$eval(getSelector("419R"), el => el.value);
expect(text).toEqual("0.577215");
await page.waitForFunction(
`${getQuerySelector("427R")}.value !== "${prevSum}"`
`${getQuerySelector("419R")}.value === "0.577215"`
);
await page.waitForFunction(
`${getQuerySelector("427R")}.value === "0,58"`
);
const sum = await page.$eval(getSelector("427R"), el => el.value);
expect(sum).toEqual("6,44");
})
);
});
@ -329,11 +291,11 @@ describe("Interaction", () => {
describe("in js-buttons.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("js-buttons.pdf", getSelector("80R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -456,7 +418,7 @@ describe("Interaction", () => {
describe("in doc_actions.pdf for printing", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("doc_actions.pdf", getSelector("47R"), null, {
earlySetup: () => {
// No need to trigger the print dialog.
@ -497,11 +459,11 @@ describe("Interaction", () => {
describe("in doc_actions.pdf for saving", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("doc_actions.pdf", getSelector("47R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -544,11 +506,11 @@ describe("Interaction", () => {
describe("in doc_actions.pdf for page actions", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("doc_actions.pdf", getSelector("47R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -601,11 +563,11 @@ describe("Interaction", () => {
describe("in js-authors.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("js-authors.pdf", getSelector("25R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -632,11 +594,11 @@ describe("Interaction", () => {
describe("in listbox_actions.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("listbox_actions.pdf", getSelector("33R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -774,11 +736,11 @@ describe("Interaction", () => {
describe("in js-colors.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("js-colors.pdf", getSelector("34R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -847,11 +809,11 @@ describe("Interaction", () => {
describe("in issue13132.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue13132.pdf", getSelector("171R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -906,11 +868,11 @@ describe("Interaction", () => {
describe("Check field properties", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("evaljs.pdf", getSelector("55R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -981,11 +943,11 @@ describe("Interaction", () => {
describe("in issue13269.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue13269.pdf", getSelector("27R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1016,13 +978,14 @@ describe("Interaction", () => {
describe("in secHandler.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("secHandler.pdf", getSelector("25R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
it("must print securityHandler value in a text field", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
@ -1044,7 +1007,7 @@ describe("Interaction", () => {
describe("in issue14307.pdf (1)", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue14307.pdf", getSelector("30R"));
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
@ -1053,7 +1016,7 @@ describe("Interaction", () => {
});
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1087,7 +1050,7 @@ describe("Interaction", () => {
describe("in issue14307.pdf (2)", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue14307.pdf", getSelector("30R"));
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
@ -1096,7 +1059,7 @@ describe("Interaction", () => {
});
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1132,7 +1095,7 @@ describe("Interaction", () => {
describe("in issue14307.pdf (3)", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue14307.pdf", getSelector("30R"));
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
@ -1141,7 +1104,7 @@ describe("Interaction", () => {
});
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1177,7 +1140,7 @@ describe("Interaction", () => {
describe("in issue14862.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue14862.pdf", getSelector("27R"));
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
@ -1186,7 +1149,7 @@ describe("Interaction", () => {
});
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1272,7 +1235,7 @@ describe("Interaction", () => {
describe("in issue14705.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue14705.pdf", getSelector("29R"));
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
@ -1281,7 +1244,7 @@ describe("Interaction", () => {
});
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1316,11 +1279,11 @@ describe("Interaction", () => {
describe("in bug1766987.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1766987.pdf", getSelector("75R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1348,11 +1311,11 @@ describe("Interaction", () => {
describe("in issue15053.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue15053.pdf", getSelector("44R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1401,11 +1364,11 @@ describe("Interaction", () => {
describe("in bug1675139.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1675139.pdf", getSelector("48R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1447,11 +1410,11 @@ describe("Interaction", () => {
describe("in issue15092.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue15092.pdf", getSelector("39R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1482,11 +1445,11 @@ describe("Interaction", () => {
describe("in bug1782564.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1782564.pdf", getSelector("7R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1527,11 +1490,11 @@ describe("Interaction", () => {
describe("in bug1802888.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1802888.pdf", getSelector("30R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1557,11 +1520,11 @@ describe("Interaction", () => {
describe("in issue15753.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue15753.pdf", getSelector("27R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1602,11 +1565,11 @@ describe("Interaction", () => {
describe("in issue15815.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue15815.pdf", getSelector("24R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1649,11 +1612,11 @@ describe("Interaction", () => {
describe("in issue15818.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue15818.pdf", getSelector("27R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1696,7 +1659,7 @@ describe("Interaction", () => {
describe("in autoprint.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
// Autoprinting is triggered by the `Open` event, which is one of the
// first events to be dispatched to the sandbox, even before scripting
// is reported to be ready. It's therefore important that `loadAndWait`
@ -1725,7 +1688,7 @@ describe("Interaction", () => {
});
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1747,11 +1710,11 @@ describe("Interaction", () => {
describe("in bug1811694.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1811694.pdf", getSelector("25R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1779,11 +1742,11 @@ describe("Interaction", () => {
describe("in bug1811510.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1811510.pdf", getSelector("22R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1819,11 +1782,11 @@ describe("Interaction", () => {
describe("in issue16067.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue16067.pdf", getSelector("6R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1854,11 +1817,11 @@ describe("Interaction", () => {
describe("in bug1825002.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1825002.pdf", getSelector("23R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1908,11 +1871,11 @@ describe("Interaction", () => {
describe("in bug1844576.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1844576.pdf", getSelector("9R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -1963,14 +1926,14 @@ describe("Interaction", () => {
describe("in annotation_hidden_noview.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait(
"annotation_hidden_noview.pdf",
getSelector("11R")
);
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2022,11 +1985,11 @@ describe("Interaction", () => {
describe("in issue16863.pdf", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue16863.pdf", getSelector("334R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2086,7 +2049,7 @@ describe("Interaction", () => {
let pages;
let otherPages;
beforeAll(async () => {
beforeEach(async () => {
otherPages = await Promise.all(
global.integrationSessions.map(async session =>
session.browser.newPage()
@ -2095,7 +2058,7 @@ describe("Interaction", () => {
pages = await loadAndWait("evaljs.pdf", getSelector("55R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
await Promise.all(otherPages.map(page => page.close()));
});
@ -2129,11 +2092,11 @@ describe("Interaction", () => {
describe("Textfield with a Blur callback", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1863910.pdf", getSelector("25R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2161,11 +2124,11 @@ describe("Interaction", () => {
describe("Radio button without T value", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1860602.pdf", getSelector("22R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2218,11 +2181,11 @@ describe("Interaction", () => {
describe("Textfield with a number and some decimals", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue17540.pdf", getSelector("15R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2251,11 +2214,11 @@ describe("Interaction", () => {
describe("Textfield with a zip code starting with 0", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1889122.pdf", getSelector("24R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2279,11 +2242,11 @@ describe("Interaction", () => {
describe("Value of event.change when a choice list is modified", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue17998.pdf", getSelector("7R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2313,11 +2276,11 @@ describe("Interaction", () => {
describe("PageOpen and PageClose actions in fields", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue18305.pdf", getSelector("7R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2358,11 +2321,11 @@ describe("Interaction", () => {
describe("Compute product of different fields", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue18536.pdf", getSelector("34R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2395,11 +2358,11 @@ describe("Interaction", () => {
describe("Calculate field value even if one callback throws", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue18561.pdf", getSelector("24R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2423,11 +2386,11 @@ describe("Interaction", () => {
describe("Correctly format numbers", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1918115.pdf", getSelector("33R"));
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2451,11 +2414,11 @@ describe("Interaction", () => {
describe("Change radio property", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1922766.pdf", "[data-annotation-id='44R']");
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2494,11 +2457,11 @@ describe("Interaction", () => {
describe("Date creation must be timezone consistent", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("bug1934157.pdf", "[data-annotation-id='24R']");
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});
@ -2532,11 +2495,11 @@ describe("Interaction", () => {
describe("Skip throwing actions (issue 19505)", () => {
let pages;
beforeAll(async () => {
beforeEach(async () => {
pages = await loadAndWait("issue19505.pdf", "[data-annotation-id='24R']");
});
afterAll(async () => {
afterEach(async () => {
await closePages(pages);
});

View File

@ -0,0 +1 @@
https://bugzilla.mozilla.org/attachment.cgi?id=9464841

View File

@ -12002,5 +12002,15 @@
"md5": "9993aa298c0214a3d3ff5f90ce0d40bb",
"rounds": 1,
"type": "eq"
},
{
"id": "bug1946953",
"file": "pdfs/bug1946953.pdf",
"md5": "a71fb64e348b9c7161945e48e75c6681",
"rounds": 1,
"link": true,
"firstPage": 1,
"lastPage": 1,
"type": "eq"
}
]

View File

@ -26,6 +26,7 @@ import {
AnnotationFieldFlag,
AnnotationFlag,
AnnotationType,
DrawOPS,
OPS,
RenderingIntentFlag,
stringToBytes,
@ -4285,14 +4286,13 @@ describe("annotation", function () {
null
);
expect(opList.fnArray.length).toEqual(16);
expect(opList.fnArray.length).toEqual(15);
expect(opList.fnArray).toEqual([
OPS.beginAnnotation,
OPS.save,
OPS.transform,
OPS.constructPath,
OPS.clip,
OPS.endPath,
OPS.constructPath,
OPS.beginText,
OPS.setFillRGBColor,
OPS.setCharSpacing,
@ -4659,7 +4659,7 @@ describe("annotation", function () {
null
);
expect(opList.argsArray.length).toEqual(8);
expect(opList.argsArray.length).toEqual(7);
expect(opList.fnArray).toEqual([
OPS.beginAnnotation,
OPS.setLineWidth,
@ -4667,7 +4667,6 @@ describe("annotation", function () {
OPS.setLineJoin,
OPS.setStrokeRGBColor,
OPS.constructPath,
OPS.stroke,
OPS.endAnnotation,
]);
@ -4680,10 +4679,23 @@ describe("annotation", function () {
// Color.
expect(opList.argsArray[4]).toEqual(new Uint8ClampedArray([0, 255, 0]));
// Path.
expect(opList.argsArray[5][0]).toEqual([OPS.moveTo, OPS.curveTo]);
expect(opList.argsArray[5][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
expect(opList.argsArray[5][0]).toEqual(OPS.stroke);
expect(opList.argsArray[5][1]).toEqual([
new Float32Array([
DrawOPS.moveTo,
1,
2,
DrawOPS.curveTo,
3,
4,
5,
6,
7,
8,
]),
]);
// Min-max.
expect(opList.argsArray[5][2]).toEqual([1, 2, 1, 2]);
expect(opList.argsArray[5][2]).toEqual(new Float32Array([1, 2, 7, 8]));
});
});
@ -4831,13 +4843,12 @@ describe("annotation", function () {
null
);
expect(opList.argsArray.length).toEqual(6);
expect(opList.argsArray.length).toEqual(5);
expect(opList.fnArray).toEqual([
OPS.beginAnnotation,
OPS.setFillRGBColor,
OPS.setGState,
OPS.constructPath,
OPS.eoFill,
OPS.endAnnotation,
]);
});
@ -4953,13 +4964,12 @@ describe("annotation", function () {
null
);
expect(opList.argsArray.length).toEqual(6);
expect(opList.argsArray.length).toEqual(5);
expect(opList.fnArray).toEqual([
OPS.beginAnnotation,
OPS.setFillRGBColor,
OPS.setGState,
OPS.constructPath,
OPS.fill,
OPS.endAnnotation,
]);
});

View File

@ -17,6 +17,7 @@ import {
AnnotationEditorType,
AnnotationMode,
AnnotationType,
DrawOPS,
ImageKind,
InvalidPDFException,
isNodeJS,
@ -794,17 +795,25 @@ describe("api", function () {
OPS.setLineWidth,
OPS.setStrokeRGBColor,
OPS.constructPath,
OPS.closeStroke,
]);
expect(opList.argsArray).toEqual([
[0.5],
new Uint8ClampedArray([255, 0, 0]),
[
[OPS.moveTo, OPS.lineTo],
[0, 9.75, 0.5, 9.75],
[0, 9.75, 0.5, 9.75],
OPS.closeStroke,
[
new Float32Array([
DrawOPS.moveTo,
0,
9.75,
DrawOPS.lineTo,
0.5,
9.75,
DrawOPS.closePath,
]),
],
new Float32Array([0, 9.75, 0.5, 9.75]),
],
null,
]);
expect(opList.lastChunk).toEqual(true);
@ -4236,8 +4245,8 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
const opListAnnotEnable = await pdfPage.getOperatorList({
annotationMode: AnnotationMode.ENABLE,
});
expect(opListAnnotEnable.fnArray.length).toBeGreaterThan(140);
expect(opListAnnotEnable.argsArray.length).toBeGreaterThan(140);
expect(opListAnnotEnable.fnArray.length).toBeGreaterThan(130);
expect(opListAnnotEnable.argsArray.length).toBeGreaterThan(130);
expect(opListAnnotEnable.lastChunk).toEqual(true);
expect(opListAnnotEnable.separateAnnots).toEqual({
form: false,
@ -4270,8 +4279,8 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
const opListAnnotEnableStorage = await pdfPage.getOperatorList({
annotationMode: AnnotationMode.ENABLE_STORAGE,
});
expect(opListAnnotEnableStorage.fnArray.length).toBeGreaterThan(170);
expect(opListAnnotEnableStorage.argsArray.length).toBeGreaterThan(170);
expect(opListAnnotEnableStorage.fnArray.length).toBeGreaterThan(150);
expect(opListAnnotEnableStorage.argsArray.length).toBeGreaterThan(150);
expect(opListAnnotEnableStorage.lastChunk).toEqual(true);
expect(opListAnnotEnableStorage.separateAnnots).toEqual({
form: false,

View File

@ -74,8 +74,8 @@ describe("evaluator", function () {
);
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(1);
expect(result.fnArray[0]).toEqual(OPS.fill);
expect(result.argsArray[0]).toEqual(null);
expect(result.fnArray[0]).toEqual(OPS.constructPath);
expect(result.argsArray[0]).toEqual([OPS.fill, [null], null]);
});
it("should handle one operation", async function () {
@ -130,9 +130,14 @@ describe("evaluator", function () {
);
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(3);
expect(result.fnArray[0]).toEqual(OPS.fill);
expect(result.fnArray[1]).toEqual(OPS.fill);
expect(result.fnArray[2]).toEqual(OPS.fill);
expect(result.fnArray).toEqual([
OPS.constructPath,
OPS.constructPath,
OPS.constructPath,
]);
expect(result.argsArray[0][0]).toEqual(OPS.fill);
expect(result.argsArray[1][0]).toEqual(OPS.fill);
expect(result.argsArray[2][0]).toEqual(OPS.fill);
});
it("should handle three glued operations #2", async function () {
@ -145,10 +150,14 @@ describe("evaluator", function () {
resources
);
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(3);
expect(result.fnArray[0]).toEqual(OPS.eoFillStroke);
expect(result.fnArray[1]).toEqual(OPS.fillStroke);
expect(result.fnArray[2]).toEqual(OPS.eoFill);
expect(result.fnArray).toEqual([
OPS.constructPath,
OPS.constructPath,
OPS.constructPath,
]);
expect(result.argsArray[0][0]).toEqual(OPS.eoFillStroke);
expect(result.argsArray[1][0]).toEqual(OPS.fillStroke);
expect(result.argsArray[2][0]).toEqual(OPS.eoFill);
});
it("should handle glued operations and operands", async function () {
@ -160,7 +169,7 @@ describe("evaluator", function () {
);
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(2);
expect(result.fnArray[0]).toEqual(OPS.fill);
expect(result.fnArray[0]).toEqual(OPS.constructPath);
expect(result.fnArray[1]).toEqual(OPS.setTextRise);
expect(result.argsArray.length).toEqual(2);
expect(result.argsArray[1].length).toEqual(1);
@ -178,13 +187,13 @@ describe("evaluator", function () {
expect(result.fnArray.length).toEqual(3);
expect(result.fnArray[0]).toEqual(OPS.setFlatness);
expect(result.fnArray[1]).toEqual(OPS.setRenderingIntent);
expect(result.fnArray[2]).toEqual(OPS.endPath);
expect(result.fnArray[2]).toEqual(OPS.constructPath);
expect(result.argsArray.length).toEqual(3);
expect(result.argsArray[0].length).toEqual(1);
expect(result.argsArray[0][0]).toEqual(true);
expect(result.argsArray[1].length).toEqual(1);
expect(result.argsArray[1][0]).toEqual(false);
expect(result.argsArray[2]).toEqual(null);
expect(result.argsArray[2]).toEqual([OPS.endPath, [null], null]);
});
});