Skip to content

Commit bb2a112

Browse files
committed
Use a binary format for the glyph paths
We used a SVG string which can be pass to the Path2D ctor but it's a bit slower than building the path step by step. Having numerical data instead of a string will help the font data serialization.
1 parent 745e427 commit bb2a112

File tree

5 files changed

+74
-45
lines changed

5 files changed

+74
-45
lines changed

src/core/font_renderer.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import {
1717
assert,
1818
bytesToString,
19+
DrawOPS,
20+
FeatureTest,
1921
FONT_IDENTITY_MATRIX,
2022
FormatError,
2123
unreachable,
@@ -169,16 +171,16 @@ function compileGlyf(code, cmds, font) {
169171
function moveTo(x, y) {
170172
if (firstPoint) {
171173
// Close the current subpath in adding a straight line to the first point.
172-
cmds.add("L", firstPoint);
174+
cmds.add(DrawOPS.lineTo, firstPoint);
173175
}
174176
firstPoint = [x, y];
175-
cmds.add("M", [x, y]);
177+
cmds.add(DrawOPS.moveTo, [x, y]);
176178
}
177179
function lineTo(x, y) {
178-
cmds.add("L", [x, y]);
180+
cmds.add(DrawOPS.lineTo, [x, y]);
179181
}
180182
function quadraticCurveTo(xa, ya, x, y) {
181-
cmds.add("Q", [xa, ya, x, y]);
183+
cmds.add(DrawOPS.quadraticCurveTo, [xa, ya, x, y]);
182184
}
183185

184186
let i = 0;
@@ -355,16 +357,16 @@ function compileCharString(charStringCode, cmds, font, glyphId) {
355357
function moveTo(x, y) {
356358
if (firstPoint) {
357359
// Close the current subpath in adding a straight line to the first point.
358-
cmds.add("L", firstPoint);
360+
cmds.add(DrawOPS.lineTo, firstPoint);
359361
}
360362
firstPoint = [x, y];
361-
cmds.add("M", [x, y]);
363+
cmds.add(DrawOPS.moveTo, [x, y]);
362364
}
363365
function lineTo(x, y) {
364-
cmds.add("L", [x, y]);
366+
cmds.add(DrawOPS.lineTo, [x, y]);
365367
}
366368
function bezierCurveTo(x1, y1, x2, y2, x, y) {
367-
cmds.add("C", [x1, y1, x2, y2, x, y]);
369+
cmds.add(DrawOPS.curveTo, [x1, y1, x2, y2, x, y]);
368370
}
369371

370372
const stack = [];
@@ -749,7 +751,7 @@ class Commands {
749751
for (let i = 0, ii = args.length; i < ii; i += 2) {
750752
Util.applyTransform(args, currentTransform, i);
751753
}
752-
this.cmds.push(`${cmd}${args.join(" ")}`);
754+
this.cmds.push(cmd, ...args);
753755
} else {
754756
this.cmds.push(cmd);
755757
}
@@ -771,8 +773,13 @@ class Commands {
771773
this.currentTransform = this.transformStack.pop() || [1, 0, 0, 1, 0, 0];
772774
}
773775

774-
getSVG() {
775-
return this.cmds.join("");
776+
getPath() {
777+
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
778+
return new Float16Array(this.cmds);
779+
}
780+
return new (
781+
FeatureTest.isFloat16ArraySupported ? Float16Array : Float32Array
782+
)(this.cmds);
776783
}
777784
}
778785

@@ -834,9 +841,9 @@ class CompiledFont {
834841
const cmds = new Commands();
835842
cmds.transform(fontMatrix.slice());
836843
this.compileGlyphImpl(code, cmds, glyphId);
837-
cmds.add("Z");
844+
cmds.add(DrawOPS.closePath);
838845

839-
return cmds.getSVG();
846+
return cmds.getPath();
840847
}
841848

842849
compileGlyphImpl() {

src/display/canvas.js

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
Dependencies,
1919
} from "./canvas_dependency_tracker.js";
2020
import {
21-
DrawOPS,
2221
FeatureTest,
2322
FONT_IDENTITY_MATRIX,
2423
ImageKind,
@@ -33,6 +32,7 @@ import {
3332
import {
3433
getCurrentTransform,
3534
getCurrentTransformInverse,
35+
makePathFromDrawOPS,
3636
OutputScale,
3737
PixelsPerInch,
3838
} from "./display_utils.js";
@@ -1489,35 +1489,7 @@ class CanvasGraphics {
14891489
}
14901490

14911491
if (!(path instanceof Path2D)) {
1492-
// Using a SVG string is slightly slower than using the following loop.
1493-
const path2d = (data[0] = new Path2D());
1494-
for (let i = 0, ii = path.length; i < ii; ) {
1495-
switch (path[i++]) {
1496-
case DrawOPS.moveTo:
1497-
path2d.moveTo(path[i++], path[i++]);
1498-
break;
1499-
case DrawOPS.lineTo:
1500-
path2d.lineTo(path[i++], path[i++]);
1501-
break;
1502-
case DrawOPS.curveTo:
1503-
path2d.bezierCurveTo(
1504-
path[i++],
1505-
path[i++],
1506-
path[i++],
1507-
path[i++],
1508-
path[i++],
1509-
path[i++]
1510-
);
1511-
break;
1512-
case DrawOPS.closePath:
1513-
path2d.closePath();
1514-
break;
1515-
default:
1516-
warn(`Unrecognized drawing path operator: ${path[i - 1]}`);
1517-
break;
1518-
}
1519-
}
1520-
path = path2d;
1492+
path = data[0] = makePathFromDrawOPS(path);
15211493
}
15221494
Util.axialAlignedBoundingBox(
15231495
minMax,

src/display/display_utils.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import {
1717
BaseException,
18+
DrawOPS,
1819
FeatureTest,
1920
shadow,
2021
Util,
@@ -995,6 +996,44 @@ function renderRichText({ html, dir, className }, container) {
995996
container.append(fragment);
996997
}
997998

999+
function makePathFromDrawOPS(data) {
1000+
// Using a SVG string is slightly slower than using the following loop.
1001+
const path = new Path2D();
1002+
if (!data) {
1003+
return path;
1004+
}
1005+
for (let i = 0, ii = data.length; i < ii; ) {
1006+
switch (data[i++]) {
1007+
case DrawOPS.moveTo:
1008+
path.moveTo(data[i++], data[i++]);
1009+
break;
1010+
case DrawOPS.lineTo:
1011+
path.lineTo(data[i++], data[i++]);
1012+
break;
1013+
case DrawOPS.curveTo:
1014+
path.bezierCurveTo(
1015+
data[i++],
1016+
data[i++],
1017+
data[i++],
1018+
data[i++],
1019+
data[i++],
1020+
data[i++]
1021+
);
1022+
break;
1023+
case DrawOPS.quadraticCurveTo:
1024+
path.quadraticCurveTo(data[i++], data[i++], data[i++], data[i++]);
1025+
break;
1026+
case DrawOPS.closePath:
1027+
path.closePath();
1028+
break;
1029+
default:
1030+
warn(`Unrecognized drawing path operator: ${data[i - 1]}`);
1031+
break;
1032+
}
1033+
}
1034+
return path;
1035+
}
1036+
9981037
export {
9991038
applyOpacity,
10001039
ColorScheme,
@@ -1012,6 +1051,7 @@ export {
10121051
isDataScheme,
10131052
isPdfFile,
10141053
isValidFetchUrl,
1054+
makePathFromDrawOPS,
10151055
noContextMenu,
10161056
OutputScale,
10171057
PageViewport,

src/display/font_loader.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
unreachable,
2424
warn,
2525
} from "../shared/util.js";
26+
import { makePathFromDrawOPS } from "./display_utils.js";
2627

2728
class FontLoader {
2829
#systemFonts = new Set();
@@ -435,7 +436,7 @@ class FontFaceObject {
435436
} catch (ex) {
436437
warn(`getPathGenerator - ignoring character: "${ex}".`);
437438
}
438-
const path = new Path2D(cmds || "");
439+
const path = makePathFromDrawOPS(cmds);
439440

440441
if (!this.fontExtraProperties) {
441442
// Remove the raw path-string, since we don't need it anymore.

src/shared/util.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ const DrawOPS = {
354354
moveTo: 0,
355355
lineTo: 1,
356356
curveTo: 2,
357-
closePath: 3,
357+
quadraticCurveTo: 3,
358+
closePath: 4,
358359
};
359360

360361
const PasswordResponses = {
@@ -649,6 +650,14 @@ class FeatureTest {
649650
);
650651
}
651652

653+
static get isFloat16ArraySupported() {
654+
return shadow(
655+
this,
656+
"isFloat16ArraySupported",
657+
typeof Float16Array !== "undefined"
658+
);
659+
}
660+
652661
static get platform() {
653662
const { platform, userAgent } = navigator;
654663

0 commit comments

Comments
 (0)