Skip to content

Commit e8d3d19

Browse files
Merge pull request #21220 from Snuffleupagus/DataBuilder
Replace `TrueTypeTableBuilder` and `CompilerOutput` with a single class
2 parents a55cec4 + ac6a923 commit e8d3d19

3 files changed

Lines changed: 161 additions & 186 deletions

File tree

src/core/cff_parser.js

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
ISOAdobeCharset,
2929
} from "./charsets.js";
3030
import { ExpertEncoding, StandardEncoding } from "./encodings.js";
31+
import { DataBuilder } from "./data_builder.js";
3132

3233
// Maximum subroutine call depth of type 2 charstrings. Matches OTS.
3334
const MAX_SUBR_NESTING = 10;
@@ -1365,51 +1366,6 @@ class CFFOffsetTracker {
13651366
}
13661367
}
13671368

1368-
class CompilerOutput {
1369-
#buf;
1370-
1371-
#bufLength = 1024;
1372-
1373-
#pos = 0;
1374-
1375-
constructor(minLength) {
1376-
// Note: Usually the compiled size is smaller than the initial data,
1377-
// however in some cases it may increase slightly.
1378-
this.#initBuf(minLength);
1379-
}
1380-
1381-
#initBuf(minLength) {
1382-
// Compute the first power of two that is as big as the `minLength`.
1383-
while (this.#bufLength < minLength) {
1384-
this.#bufLength *= 2;
1385-
}
1386-
const newBuf = new Uint8Array(this.#bufLength);
1387-
1388-
if (this.#buf) {
1389-
newBuf.set(this.#buf, 0);
1390-
}
1391-
this.#buf = newBuf;
1392-
}
1393-
1394-
get data() {
1395-
return this.#buf.subarray(0, this.#pos);
1396-
}
1397-
1398-
get length() {
1399-
return this.#pos;
1400-
}
1401-
1402-
add(data) {
1403-
const newPos = this.#pos + data.length;
1404-
if (newPos > this.#bufLength) {
1405-
// It should be very rare that the buffer needs to grow.
1406-
this.#initBuf(newPos);
1407-
}
1408-
this.#buf.set(data, this.#pos);
1409-
this.#pos = newPos;
1410-
}
1411-
}
1412-
14131369
// Takes a CFF and converts it to the binary representation.
14141370
class CFFCompiler {
14151371
constructor(cff) {
@@ -1418,14 +1374,14 @@ class CFFCompiler {
14181374

14191375
compile() {
14201376
const cff = this.cff;
1421-
const output = new CompilerOutput(cff.rawFileLength);
1377+
const output = new DataBuilder({ minLength: cff.rawFileLength });
14221378

14231379
// Compile the five entries that must be in order.
14241380
const header = this.compileHeader(cff.header);
1425-
output.add(header);
1381+
output.setArray(header);
14261382

14271383
const nameIndex = this.compileNameIndex(cff.names);
1428-
output.add(nameIndex);
1384+
output.setArray(nameIndex);
14291385

14301386
if (cff.isCIDFont) {
14311387
// The spec is unclear on how font matrices should relate to each other
@@ -1465,14 +1421,14 @@ class CFFCompiler {
14651421
output.length,
14661422
cff.isCIDFont
14671423
);
1468-
output.add(compiled.output);
1424+
output.setArray(compiled.output);
14691425
const topDictTracker = compiled.trackers[0];
14701426

14711427
const stringIndex = this.compileStringIndex(cff.strings.strings);
1472-
output.add(stringIndex);
1428+
output.setArray(stringIndex);
14731429

14741430
const globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
1475-
output.add(globalSubrIndex);
1431+
output.setArray(globalSubrIndex);
14761432

14771433
// Now start on the other entries that have no specific order.
14781434
if (cff.encoding && cff.topDict.hasName("Encoding")) {
@@ -1485,7 +1441,7 @@ class CFFCompiler {
14851441
} else {
14861442
const encoding = this.compileEncoding(cff.encoding);
14871443
topDictTracker.setEntryLocation("Encoding", [output.length], output);
1488-
output.add(encoding);
1444+
output.setArray(encoding);
14891445
}
14901446
}
14911447
const charset = this.compileCharset(
@@ -1495,23 +1451,23 @@ class CFFCompiler {
14951451
cff.isCIDFont
14961452
);
14971453
topDictTracker.setEntryLocation("charset", [output.length], output);
1498-
output.add(charset);
1454+
output.setArray(charset);
14991455

15001456
const charStrings = this.compileCharStrings(cff.charStrings);
15011457
topDictTracker.setEntryLocation("CharStrings", [output.length], output);
1502-
output.add(charStrings);
1458+
output.setArray(charStrings);
15031459

15041460
if (cff.isCIDFont) {
15051461
// For some reason FDSelect must be in front of FDArray on windows. OSX
15061462
// and linux don't seem to care.
15071463
topDictTracker.setEntryLocation("FDSelect", [output.length], output);
15081464
const fdSelect = this.compileFDSelect(cff.fdSelect);
1509-
output.add(fdSelect);
1465+
output.setArray(fdSelect);
15101466
// It is unclear if the sub font dictionary can have CID related
15111467
// dictionary keys, but the sanitizer doesn't like them so remove them.
15121468
compiled = this.compileTopDicts(cff.fdArray, output.length, true);
15131469
topDictTracker.setEntryLocation("FDArray", [output.length], output);
1514-
output.add(compiled.output);
1470+
output.setArray(compiled.output);
15151471
const fontDictTrackers = compiled.trackers;
15161472

15171473
this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
@@ -1521,7 +1477,7 @@ class CFFCompiler {
15211477

15221478
// If the font data ends with INDEX whose object data is zero-length,
15231479
// the sanitizer will bail out. Add a dummy byte to avoid that.
1524-
output.add([0]);
1480+
output.setArray([0]);
15251481

15261482
return output.data;
15271483
}
@@ -1689,7 +1645,7 @@ class CFFCompiler {
16891645
[privateDictData.length, outputLength],
16901646
output
16911647
);
1692-
output.add(privateDictData);
1648+
output.setArray(privateDictData);
16931649

16941650
if (privateDict.subrsIndex && privateDict.hasName("Subrs")) {
16951651
const subrs = this.compileIndex(privateDict.subrsIndex);
@@ -1698,7 +1654,7 @@ class CFFCompiler {
16981654
[privateDictData.length],
16991655
output
17001656
);
1701-
output.add(subrs);
1657+
output.setArray(subrs);
17021658
}
17031659
}
17041660
}

src/core/data_builder.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/* Copyright 2026 Mozilla Foundation
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import { assert } from "../shared/util.js";
17+
import { MathClamp } from "../shared/math_clamp.js";
18+
19+
class DataBuilder {
20+
#buf;
21+
22+
#bufLength = 1024;
23+
24+
#hasExactLength = false;
25+
26+
#pos = 0;
27+
28+
#view;
29+
30+
constructor({ exactLength = 0, minLength = 0 }) {
31+
this.#hasExactLength = !!exactLength;
32+
this.#initBuf(exactLength || minLength);
33+
}
34+
35+
#initBuf(minLength) {
36+
if (this.#hasExactLength) {
37+
this.#bufLength = minLength;
38+
} else {
39+
// Compute the first power of two that is as big as the `minLength`.
40+
while (this.#bufLength < minLength) {
41+
this.#bufLength *= 2;
42+
}
43+
}
44+
const newBuf = new Uint8Array(this.#bufLength);
45+
46+
if (this.#buf) {
47+
newBuf.set(this.#buf, 0);
48+
}
49+
this.#buf = newBuf;
50+
this.#view = new DataView(newBuf.buffer);
51+
}
52+
53+
get data() {
54+
return this.#buf.subarray(0, this.#pos);
55+
}
56+
57+
get length() {
58+
return this.#pos;
59+
}
60+
61+
skip(n) {
62+
this.#pos += n;
63+
}
64+
65+
setArray(arr) {
66+
const newPos = this.#pos + arr.length;
67+
68+
if (!this.#hasExactLength && newPos > this.#bufLength) {
69+
this.#initBuf(newPos);
70+
}
71+
this.#buf.set(arr, this.#pos);
72+
this.#pos = newPos;
73+
}
74+
75+
setInt16(val) {
76+
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
77+
assert(
78+
typeof val === "number" && Math.abs(val) < 2 ** 16,
79+
`setInt16: Unexpected input "${val}".`
80+
);
81+
}
82+
const newPos = this.#pos + 2;
83+
84+
if (!this.#hasExactLength && newPos > this.#bufLength) {
85+
this.#initBuf(newPos);
86+
}
87+
this.#view.setInt16(this.#pos, val);
88+
this.#pos = newPos;
89+
}
90+
91+
setSafeInt16(val) {
92+
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
93+
assert(
94+
typeof val === "number" && !Number.isNaN(val),
95+
`safeString16: Unexpected input "${val}".`
96+
);
97+
}
98+
const newPos = this.#pos + 2;
99+
100+
if (!this.#hasExactLength && newPos > this.#bufLength) {
101+
this.#initBuf(newPos);
102+
}
103+
// clamp value to the 16-bit int range
104+
this.#view.setInt16(this.#pos, MathClamp(val, -0x8000, 0x7fff));
105+
this.#pos = newPos;
106+
}
107+
108+
setInt32(val) {
109+
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
110+
assert(
111+
typeof val === "number" && Math.abs(val) < 2 ** 32,
112+
`setInt32: Unexpected input "${val}".`
113+
);
114+
}
115+
const newPos = this.#pos + 4;
116+
117+
if (!this.#hasExactLength && newPos > this.#bufLength) {
118+
this.#initBuf(newPos);
119+
}
120+
this.#view.setInt32(this.#pos, val);
121+
this.#pos = newPos;
122+
}
123+
}
124+
125+
export { DataBuilder };

0 commit comments

Comments
 (0)