Skip to content

Commit be1f567

Browse files
committed
[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.
1 parent a229914 commit be1f567

10 files changed

Lines changed: 367 additions & 374 deletions

File tree

src/core/evaluator.js

Lines changed: 160 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import {
1717
AbortException,
1818
assert,
19+
DrawOPS,
1920
FONT_IDENTITY_MATRIX,
2021
FormatError,
2122
IDENTITY_MATRIX,
@@ -925,7 +926,7 @@ class PartialEvaluator {
925926
smaskOptions,
926927
operatorList,
927928
task,
928-
stateManager.state.clone(),
929+
stateManager.state.clone({ newPath: true }),
929930
localColorSpaceCache
930931
);
931932
}
@@ -1383,80 +1384,112 @@ class PartialEvaluator {
13831384
return promise;
13841385
}
13851386

1386-
buildPath(operatorList, fn, args, parsingText = false) {
1387-
const lastIndex = operatorList.length - 1;
1388-
if (!args) {
1389-
args = [];
1390-
}
1391-
if (
1392-
lastIndex < 0 ||
1393-
operatorList.fnArray[lastIndex] !== OPS.constructPath
1394-
) {
1395-
// Handle corrupt PDF documents that contains path operators inside of
1396-
// text objects, which may shift subsequent text, by enclosing the path
1397-
// operator in save/restore operators (fixes issue10542_reduced.pdf).
1398-
//
1399-
// Note that this will effectively disable the optimization in the
1400-
// `else` branch below, but given that this type of corruption is
1401-
// *extremely* rare that shouldn't really matter much in practice.
1402-
if (parsingText) {
1403-
warn(`Encountered path operator "${fn}" inside of a text object.`);
1404-
operatorList.addOp(OPS.save, null);
1387+
buildPath(fn, args, state) {
1388+
const { pathMinMax: minMax, pathBuffer } = state;
1389+
switch (fn | 0) {
1390+
case OPS.rectangle: {
1391+
const x = (state.currentPointX = args[0]);
1392+
const y = (state.currentPointY = args[1]);
1393+
const width = args[2];
1394+
const height = args[3];
1395+
const xw = x + width;
1396+
const yh = y + height;
1397+
if (width === 0 || height === 0) {
1398+
pathBuffer.push(
1399+
DrawOPS.moveTo,
1400+
x,
1401+
y,
1402+
DrawOPS.lineTo,
1403+
xw,
1404+
yh,
1405+
DrawOPS.closePath
1406+
);
1407+
} else {
1408+
pathBuffer.push(
1409+
DrawOPS.moveTo,
1410+
x,
1411+
y,
1412+
DrawOPS.lineTo,
1413+
xw,
1414+
y,
1415+
DrawOPS.lineTo,
1416+
xw,
1417+
yh,
1418+
DrawOPS.lineTo,
1419+
x,
1420+
yh,
1421+
DrawOPS.closePath
1422+
);
1423+
}
1424+
minMax[0] = Math.min(minMax[0], x, xw);
1425+
minMax[1] = Math.min(minMax[1], y, yh);
1426+
minMax[2] = Math.max(minMax[2], x, xw);
1427+
minMax[3] = Math.max(minMax[3], y, yh);
1428+
break;
14051429
}
1406-
1407-
let minMax;
1408-
switch (fn) {
1409-
case OPS.rectangle:
1410-
const x = args[0] + args[2];
1411-
const y = args[1] + args[3];
1412-
minMax = [
1413-
Math.min(args[0], x),
1414-
Math.min(args[1], y),
1415-
Math.max(args[0], x),
1416-
Math.max(args[1], y),
1417-
];
1418-
break;
1419-
case OPS.moveTo:
1420-
case OPS.lineTo:
1421-
minMax = [args[0], args[1], args[0], args[1]];
1422-
break;
1423-
default:
1424-
minMax = [Infinity, Infinity, -Infinity, -Infinity];
1425-
break;
1430+
case OPS.moveTo: {
1431+
const x = (state.currentPointX = args[0]);
1432+
const y = (state.currentPointY = args[1]);
1433+
pathBuffer.push(DrawOPS.moveTo, x, y);
1434+
minMax[0] = Math.min(minMax[0], x);
1435+
minMax[1] = Math.min(minMax[1], y);
1436+
minMax[2] = Math.max(minMax[2], x);
1437+
minMax[3] = Math.max(minMax[3], y);
1438+
break;
14261439
}
1427-
operatorList.addOp(OPS.constructPath, [[fn], args, minMax]);
1428-
1429-
if (parsingText) {
1430-
operatorList.addOp(OPS.restore, null);
1440+
case OPS.lineTo: {
1441+
const x = (state.currentPointX = args[0]);
1442+
const y = (state.currentPointY = args[1]);
1443+
pathBuffer.push(DrawOPS.lineTo, x, y);
1444+
minMax[0] = Math.min(minMax[0], x);
1445+
minMax[1] = Math.min(minMax[1], y);
1446+
minMax[2] = Math.max(minMax[2], x);
1447+
minMax[3] = Math.max(minMax[3], y);
1448+
break;
14311449
}
1432-
} else {
1433-
const opArgs = operatorList.argsArray[lastIndex];
1434-
opArgs[0].push(fn);
1435-
opArgs[1].push(...args);
1436-
const minMax = opArgs[2];
1437-
1438-
// Compute min/max in the worker instead of the main thread.
1439-
// If the current matrix (when drawing) is a scaling one
1440-
// then min/max can be easily computed in using those values.
1441-
// Only rectangle, lineTo and moveTo are handled here since
1442-
// Bezier stuff requires to have the starting point.
1443-
switch (fn) {
1444-
case OPS.rectangle:
1445-
const x = args[0] + args[2];
1446-
const y = args[1] + args[3];
1447-
minMax[0] = Math.min(minMax[0], args[0], x);
1448-
minMax[1] = Math.min(minMax[1], args[1], y);
1449-
minMax[2] = Math.max(minMax[2], args[0], x);
1450-
minMax[3] = Math.max(minMax[3], args[1], y);
1451-
break;
1452-
case OPS.moveTo:
1453-
case OPS.lineTo:
1454-
minMax[0] = Math.min(minMax[0], args[0]);
1455-
minMax[1] = Math.min(minMax[1], args[1]);
1456-
minMax[2] = Math.max(minMax[2], args[0]);
1457-
minMax[3] = Math.max(minMax[3], args[1]);
1458-
break;
1450+
case OPS.curveTo: {
1451+
const startX = state.currentPointX;
1452+
const startY = state.currentPointY;
1453+
const [x1, y1, x2, y2, x, y] = args;
1454+
state.currentPointX = x;
1455+
state.currentPointY = y;
1456+
pathBuffer.push(DrawOPS.curveTo, x1, y1, x2, y2, x, y);
1457+
Util.bezierBoundingBox(startX, startY, x1, y1, x2, y2, x, y, minMax);
1458+
break;
14591459
}
1460+
case OPS.curveTo2: {
1461+
const startX = state.currentPointX;
1462+
const startY = state.currentPointY;
1463+
const [x1, y1, x, y] = args;
1464+
state.currentPointX = x;
1465+
state.currentPointY = y;
1466+
pathBuffer.push(DrawOPS.curveTo, startX, startY, x1, y1, x, y);
1467+
Util.bezierBoundingBox(
1468+
startX,
1469+
startY,
1470+
startX,
1471+
startY,
1472+
x1,
1473+
y1,
1474+
x,
1475+
y,
1476+
minMax
1477+
);
1478+
break;
1479+
}
1480+
case OPS.curveTo3: {
1481+
const startX = state.currentPointX;
1482+
const startY = state.currentPointY;
1483+
const [x1, y1, x, y] = args;
1484+
state.currentPointX = x;
1485+
state.currentPointY = y;
1486+
pathBuffer.push(DrawOPS.curveTo, x1, y1, x, y, x, y);
1487+
Util.bezierBoundingBox(startX, startY, x1, y1, x, y, x, y, minMax);
1488+
break;
1489+
}
1490+
case OPS.closePath:
1491+
pathBuffer.push(DrawOPS.closePath);
1492+
break;
14601493
}
14611494
}
14621495

@@ -1731,7 +1764,6 @@ class PartialEvaluator {
17311764

17321765
const self = this;
17331766
const xref = this.xref;
1734-
let parsingText = false;
17351767
const localImageCache = new LocalImageCache();
17361768
const localColorSpaceCache = new LocalColorSpaceCache();
17371769
const localGStateCache = new LocalGStateCache();
@@ -1847,7 +1879,7 @@ class PartialEvaluator {
18471879
null,
18481880
operatorList,
18491881
task,
1850-
stateManager.state.clone(),
1882+
stateManager.state.clone({ newPath: true }),
18511883
localColorSpaceCache
18521884
)
18531885
.then(function () {
@@ -1909,12 +1941,6 @@ class PartialEvaluator {
19091941
})
19101942
);
19111943
return;
1912-
case OPS.beginText:
1913-
parsingText = true;
1914-
break;
1915-
case OPS.endText:
1916-
parsingText = false;
1917-
break;
19181944
case OPS.endInlineImage:
19191945
const cacheKey = args[0].cacheKey;
19201946
if (cacheKey) {
@@ -2237,8 +2263,40 @@ class PartialEvaluator {
22372263
case OPS.curveTo3:
22382264
case OPS.closePath:
22392265
case OPS.rectangle:
2240-
self.buildPath(operatorList, fn, args, parsingText);
2266+
self.buildPath(fn, args, stateManager.state);
2267+
continue;
2268+
case OPS.stroke:
2269+
case OPS.closeStroke:
2270+
case OPS.fill:
2271+
case OPS.eoFill:
2272+
case OPS.fillStroke:
2273+
case OPS.eoFillStroke:
2274+
case OPS.closeFillStroke:
2275+
case OPS.closeEOFillStroke:
2276+
case OPS.endPath: {
2277+
const {
2278+
state: { pathBuffer, pathMinMax },
2279+
} = stateManager;
2280+
if (
2281+
fn === OPS.closeStroke ||
2282+
fn === OPS.closeFillStroke ||
2283+
fn === OPS.closeEOFillStroke
2284+
) {
2285+
pathBuffer.push(DrawOPS.closePath);
2286+
}
2287+
if (pathBuffer.length === 0) {
2288+
operatorList.addOp(OPS.constructPath, [fn, [null], null]);
2289+
} else {
2290+
operatorList.addOp(OPS.constructPath, [
2291+
fn,
2292+
[new Float32Array(pathBuffer)],
2293+
pathMinMax.slice(),
2294+
]);
2295+
pathBuffer.length = 0;
2296+
pathMinMax.set([Infinity, Infinity, -Infinity, -Infinity], 0);
2297+
}
22412298
continue;
2299+
}
22422300
case OPS.markPoint:
22432301
case OPS.markPointProps:
22442302
case OPS.beginCompat:
@@ -4935,6 +4993,16 @@ class EvalState {
49354993
this._fillColorSpace = this._strokeColorSpace = ColorSpaceUtils.gray;
49364994
this.patternFillColorSpace = null;
49374995
this.patternStrokeColorSpace = null;
4996+
4997+
// Path stuff.
4998+
this.currentPointX = this.currentPointY = 0;
4999+
this.pathMinMax = new Float32Array([
5000+
Infinity,
5001+
Infinity,
5002+
-Infinity,
5003+
-Infinity,
5004+
]);
5005+
this.pathBuffer = [];
49385006
}
49395007

49405008
get fillColorSpace() {
@@ -4953,8 +5021,18 @@ class EvalState {
49535021
this._strokeColorSpace = this.patternStrokeColorSpace = colorSpace;
49545022
}
49555023

4956-
clone() {
4957-
return Object.create(this);
5024+
clone({ newPath = false } = {}) {
5025+
const clone = Object.create(this);
5026+
if (newPath) {
5027+
clone.pathBuffer = [];
5028+
clone.pathMinMax = new Float32Array([
5029+
Infinity,
5030+
Infinity,
5031+
-Infinity,
5032+
-Infinity,
5033+
]);
5034+
}
5035+
return clone;
49585036
}
49595037
}
49605038

src/core/operator_list.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,12 @@ class OperatorList {
703703
transfers.push(arg.data.buffer);
704704
}
705705
break;
706+
case OPS.constructPath:
707+
const [, [data], minMax] = argsArray[i];
708+
if (data) {
709+
transfers.push(data.buffer, minMax.buffer);
710+
}
711+
break;
706712
}
707713
}
708714
return transfers;

0 commit comments

Comments
 (0)