mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-29 10:27:21 +02:00
Reduce allocations when compiling CFF fonts
Currently the `CFFCompiler.prototype.compile` implementation seem a bit inefficient, since the data is stored in a plain Array that needs to grow (a lot) during compilation. Additionally, adding a lot of entries isn't very efficient either and requires special handling of the "too many elements" case. Some of the "helper" methods that use TypedArrays internally currently need to convert their return data to plain Arrays, via the `compileTypedArray` method, which adds even more intermediate allocations. Note also that the `OpenTypeFileBuilder` has a special-case for writing plain Array data, which is only needed because of how the CFF compilation is implemented. To improve this situation the `CFFCompiler.prototype.compile` method is re-factored to store its data in a TypedArray, whose initial size is estimated from the "raw" file size. This removes the need for most intermediate allocations, and it also handles adding of "many elements" more efficiently.
This commit is contained in:
parent
959701d8e7
commit
6f0431456c
@ -228,7 +228,7 @@ class CFFParser {
|
|||||||
|
|
||||||
parse() {
|
parse() {
|
||||||
const properties = this.properties;
|
const properties = this.properties;
|
||||||
const cff = new CFF();
|
const cff = new CFF(this.bytes.length);
|
||||||
this.cff = cff;
|
this.cff = cff;
|
||||||
|
|
||||||
// The first five sections must be in order, all the others are reached
|
// The first five sections must be in order, all the others are reached
|
||||||
@ -1017,6 +1017,10 @@ class CFF {
|
|||||||
|
|
||||||
charStringCount = 0;
|
charStringCount = 0;
|
||||||
|
|
||||||
|
constructor(rawFileLength = 0) {
|
||||||
|
this.rawFileLength = rawFileLength;
|
||||||
|
}
|
||||||
|
|
||||||
duplicateFirstGlyph() {
|
duplicateFirstGlyph() {
|
||||||
// Browsers will not display a glyph at position 0. Typically glyph 0 is
|
// Browsers will not display a glyph at position 0. Typically glyph 0 is
|
||||||
// notdef, but a number of fonts put a valid glyph there so it must be
|
// notdef, but a number of fonts put a valid glyph there so it must be
|
||||||
@ -1372,6 +1376,57 @@ class CFFOffsetTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CompilerOutput {
|
||||||
|
#buf;
|
||||||
|
|
||||||
|
#bufLength = 1024;
|
||||||
|
|
||||||
|
#pos = 0;
|
||||||
|
|
||||||
|
constructor(minLength) {
|
||||||
|
// Note: Usually the compiled size is smaller than the initial data,
|
||||||
|
// however in some cases it may increase slightly.
|
||||||
|
this.#initBuf(minLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
#initBuf(minLength) {
|
||||||
|
// Compute the first power of two that is as big as the `minLength`.
|
||||||
|
while (this.#bufLength < minLength) {
|
||||||
|
this.#bufLength *= 2;
|
||||||
|
}
|
||||||
|
const newBuf = new Uint8Array(this.#bufLength);
|
||||||
|
|
||||||
|
if (this.#buf) {
|
||||||
|
newBuf.set(this.#buf, 0);
|
||||||
|
}
|
||||||
|
this.#buf = newBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this.#buf.subarray(0, this.#pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
get finalData() {
|
||||||
|
const data = this.#buf.slice(0, this.#pos);
|
||||||
|
this.#buf = null;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
get length() {
|
||||||
|
return this.#pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(data) {
|
||||||
|
const newPos = this.#pos + data.length;
|
||||||
|
if (newPos > this.#bufLength) {
|
||||||
|
// It should be very rare that the buffer needs to grow.
|
||||||
|
this.#initBuf(newPos);
|
||||||
|
}
|
||||||
|
this.#buf.set(data, this.#pos);
|
||||||
|
this.#pos = newPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Takes a CFF and converts it to the binary representation.
|
// Takes a CFF and converts it to the binary representation.
|
||||||
class CFFCompiler {
|
class CFFCompiler {
|
||||||
constructor(cff) {
|
constructor(cff) {
|
||||||
@ -1380,21 +1435,7 @@ class CFFCompiler {
|
|||||||
|
|
||||||
compile() {
|
compile() {
|
||||||
const cff = this.cff;
|
const cff = this.cff;
|
||||||
const output = {
|
const output = new CompilerOutput(cff.rawFileLength);
|
||||||
data: [],
|
|
||||||
length: 0,
|
|
||||||
add(data) {
|
|
||||||
try {
|
|
||||||
// It's possible to exceed the call stack maximum size when trying
|
|
||||||
// to push too much elements.
|
|
||||||
// In case of failure, we fallback to the `concat` method.
|
|
||||||
this.data.push(...data);
|
|
||||||
} catch {
|
|
||||||
this.data = this.data.concat(data);
|
|
||||||
}
|
|
||||||
this.length = this.data.length;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compile the five entries that must be in order.
|
// Compile the five entries that must be in order.
|
||||||
const header = this.compileHeader(cff.header);
|
const header = this.compileHeader(cff.header);
|
||||||
@ -1499,7 +1540,7 @@ class CFFCompiler {
|
|||||||
// the sanitizer will bail out. Add a dummy byte to avoid that.
|
// the sanitizer will bail out. Add a dummy byte to avoid that.
|
||||||
output.add([0]);
|
output.add([0]);
|
||||||
|
|
||||||
return output.data;
|
return output.finalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeNumber(value) {
|
encodeNumber(value) {
|
||||||
@ -1781,7 +1822,7 @@ class CFFCompiler {
|
|||||||
} else {
|
} else {
|
||||||
const length = 1 + numGlyphsLessNotDef * 2;
|
const length = 1 + numGlyphsLessNotDef * 2;
|
||||||
out = new Uint8Array(length);
|
out = new Uint8Array(length);
|
||||||
out[0] = 0; // format 0
|
// format 0, skip redundant `out[0] = 0;` assignment.
|
||||||
let charsetIndex = 0;
|
let charsetIndex = 0;
|
||||||
const numCharsets = charset.charset.length;
|
const numCharsets = charset.charset.length;
|
||||||
let warned = false;
|
let warned = false;
|
||||||
@ -1802,11 +1843,11 @@ class CFFCompiler {
|
|||||||
out[i + 1] = sid & 0xff;
|
out[i + 1] = sid & 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.compileTypedArray(out);
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
compileEncoding(encoding) {
|
compileEncoding(encoding) {
|
||||||
return this.compileTypedArray(encoding.raw);
|
return encoding.raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
compileFDSelect(fdSelect) {
|
compileFDSelect(fdSelect) {
|
||||||
@ -1847,11 +1888,7 @@ class CFFCompiler {
|
|||||||
out = new Uint8Array(ranges);
|
out = new Uint8Array(ranges);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return this.compileTypedArray(out);
|
return out;
|
||||||
}
|
|
||||||
|
|
||||||
compileTypedArray(data) {
|
|
||||||
return Array.from(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileIndex(index, trackers = []) {
|
compileIndex(index, trackers = []) {
|
||||||
@ -1915,9 +1952,8 @@ class CFFCompiler {
|
|||||||
|
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
// Notify the tracker where the object will be offset in the data.
|
// Notify the tracker where the object will be offset in the data.
|
||||||
if (trackers[i]) {
|
trackers[i]?.offset(data.length);
|
||||||
trackers[i].offset(data.length);
|
|
||||||
}
|
|
||||||
data.push(...objects[i]);
|
data.push(...objects[i]);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@ -35,11 +35,6 @@ function writeData(dest, offset, data) {
|
|||||||
for (let i = 0, ii = data.length; i < ii; i++) {
|
for (let i = 0, ii = data.length; i < ii; i++) {
|
||||||
dest[offset++] = data.charCodeAt(i) & 0xff;
|
dest[offset++] = data.charCodeAt(i) & 0xff;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// treating everything else as array
|
|
||||||
for (const num of data) {
|
|
||||||
dest[offset++] = num & 0xff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -153,6 +153,8 @@ function getEexecBlock(stream, suggestedLength) {
|
|||||||
* Type1Font is also a CIDFontType0.
|
* Type1Font is also a CIDFontType0.
|
||||||
*/
|
*/
|
||||||
class Type1Font {
|
class Type1Font {
|
||||||
|
#rawFileLength;
|
||||||
|
|
||||||
constructor(name, file, properties) {
|
constructor(name, file, properties) {
|
||||||
// Some bad generators embed pfb file as is, we have to strip 6-byte header.
|
// Some bad generators embed pfb file as is, we have to strip 6-byte header.
|
||||||
// Also, length1 and length2 might be off by 6 bytes as well.
|
// Also, length1 and length2 might be off by 6 bytes as well.
|
||||||
@ -200,6 +202,7 @@ class Type1Font {
|
|||||||
for (const key in data.properties) {
|
for (const key in data.properties) {
|
||||||
properties[key] = data.properties[key];
|
properties[key] = data.properties[key];
|
||||||
}
|
}
|
||||||
|
this.#rawFileLength = headerBlock.length + eexecBlock.length;
|
||||||
|
|
||||||
const charstrings = data.charstrings;
|
const charstrings = data.charstrings;
|
||||||
const type2Charstrings = this.getType2Charstrings(charstrings);
|
const type2Charstrings = this.getType2Charstrings(charstrings);
|
||||||
@ -323,7 +326,7 @@ class Type1Font {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wrap(name, glyphs, charstrings, subrs, properties) {
|
wrap(name, glyphs, charstrings, subrs, properties) {
|
||||||
const cff = new CFF();
|
const cff = new CFF(this.#rawFileLength);
|
||||||
cff.header = new CFFHeader(1, 0, 4, 4);
|
cff.header = new CFFHeader(1, 0, 4, 4);
|
||||||
|
|
||||||
cff.names = [name];
|
cff.names = [name];
|
||||||
|
|||||||
@ -430,47 +430,53 @@ describe("CFFCompiler", function () {
|
|||||||
const fdSelect = new CFFFDSelect(0, [3, 2, 1]);
|
const fdSelect = new CFFFDSelect(0, [3, 2, 1]);
|
||||||
const c = new CFFCompiler();
|
const c = new CFFCompiler();
|
||||||
const out = c.compileFDSelect(fdSelect);
|
const out = c.compileFDSelect(fdSelect);
|
||||||
expect(out).toEqual([
|
expect(out).toEqual(
|
||||||
0, // format
|
new Uint8Array([
|
||||||
3, // gid: 0 fd 3
|
0, // format
|
||||||
2, // gid: 1 fd 3
|
3, // gid: 0 fd 3
|
||||||
1, // gid: 2 fd 3
|
2, // gid: 1 fd 3
|
||||||
]);
|
1, // gid: 2 fd 3
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiles fdselect format 3", function () {
|
it("compiles fdselect format 3", function () {
|
||||||
const fdSelect = new CFFFDSelect(3, [0, 0, 1, 1]);
|
const fdSelect = new CFFFDSelect(3, [0, 0, 1, 1]);
|
||||||
const c = new CFFCompiler();
|
const c = new CFFCompiler();
|
||||||
const out = c.compileFDSelect(fdSelect);
|
const out = c.compileFDSelect(fdSelect);
|
||||||
expect(out).toEqual([
|
expect(out).toEqual(
|
||||||
3, // format
|
new Uint8Array([
|
||||||
0, // nRanges (high)
|
3, // format
|
||||||
2, // nRanges (low)
|
0, // nRanges (high)
|
||||||
0, // range struct 0 - first (high)
|
2, // nRanges (low)
|
||||||
0, // range struct 0 - first (low)
|
0, // range struct 0 - first (high)
|
||||||
0, // range struct 0 - fd
|
0, // range struct 0 - first (low)
|
||||||
0, // range struct 0 - first (high)
|
0, // range struct 0 - fd
|
||||||
2, // range struct 0 - first (low)
|
0, // range struct 0 - first (high)
|
||||||
1, // range struct 0 - fd
|
2, // range struct 0 - first (low)
|
||||||
0, // sentinel (high)
|
1, // range struct 0 - fd
|
||||||
4, // sentinel (low)
|
0, // sentinel (high)
|
||||||
]);
|
4, // sentinel (low)
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiles fdselect format 3, single range", function () {
|
it("compiles fdselect format 3, single range", function () {
|
||||||
const fdSelect = new CFFFDSelect(3, [0, 0]);
|
const fdSelect = new CFFFDSelect(3, [0, 0]);
|
||||||
const c = new CFFCompiler();
|
const c = new CFFCompiler();
|
||||||
const out = c.compileFDSelect(fdSelect);
|
const out = c.compileFDSelect(fdSelect);
|
||||||
expect(out).toEqual([
|
expect(out).toEqual(
|
||||||
3, // format
|
new Uint8Array([
|
||||||
0, // nRanges (high)
|
3, // format
|
||||||
1, // nRanges (low)
|
0, // nRanges (high)
|
||||||
0, // range struct 0 - first (high)
|
1, // nRanges (low)
|
||||||
0, // range struct 0 - first (low)
|
0, // range struct 0 - first (high)
|
||||||
0, // range struct 0 - fd
|
0, // range struct 0 - first (low)
|
||||||
0, // sentinel (high)
|
0, // range struct 0 - fd
|
||||||
2, // sentinel (low)
|
0, // sentinel (high)
|
||||||
]);
|
2, // sentinel (low)
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiles charset of CID font", function () {
|
it("compiles charset of CID font", function () {
|
||||||
@ -479,13 +485,15 @@ describe("CFFCompiler", function () {
|
|||||||
const numGlyphs = 7;
|
const numGlyphs = 7;
|
||||||
const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), true);
|
const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), true);
|
||||||
// All CID charsets get turned into a simple format 2.
|
// All CID charsets get turned into a simple format 2.
|
||||||
expect(out).toEqual([
|
expect(out).toEqual(
|
||||||
2, // format
|
new Uint8Array([
|
||||||
0, // cid (high)
|
2, // format
|
||||||
1, // cid (low)
|
0, // cid (high)
|
||||||
0, // nLeft (high)
|
1, // cid (low)
|
||||||
numGlyphs - 2, // nLeft (low)
|
0, // nLeft (high)
|
||||||
]);
|
numGlyphs - 2, // nLeft (low)
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiles charset of non CID font", function () {
|
it("compiles charset of non CID font", function () {
|
||||||
@ -494,13 +502,15 @@ describe("CFFCompiler", function () {
|
|||||||
const numGlyphs = 3;
|
const numGlyphs = 3;
|
||||||
const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), false);
|
const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), false);
|
||||||
// All non-CID fonts use a format 0 charset.
|
// All non-CID fonts use a format 0 charset.
|
||||||
expect(out).toEqual([
|
expect(out).toEqual(
|
||||||
0, // format
|
new Uint8Array([
|
||||||
0, // sid of 'space' (high)
|
0, // format
|
||||||
1, // sid of 'space' (low)
|
0, // sid of 'space' (high)
|
||||||
0, // sid of 'exclam' (high)
|
1, // sid of 'space' (low)
|
||||||
2, // sid of 'exclam' (low)
|
0, // sid of 'exclam' (high)
|
||||||
]);
|
2, // sid of 'exclam' (low)
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO a lot more compiler tests
|
// TODO a lot more compiler tests
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user