Skip to content

Commit d009e4b

Browse files
authored
Merge pull request #19689 from calixteman/use_path2d
[api-minor] Use a Path2D when doing a path operation in the canvas (bug 1946953)
2 parents 72212a8 + be1f567 commit d009e4b

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)