mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-09 14:54:04 +02:00
Merge pull request #21053 from Snuffleupagus/CFFCompiler-TypedArray
Reduce allocations when compiling CFF fonts
This commit is contained in:
commit
1bf1ef2939
@ -228,7 +228,7 @@ class CFFParser {
|
||||
|
||||
parse() {
|
||||
const properties = this.properties;
|
||||
const cff = new CFF();
|
||||
const cff = new CFF(this.bytes.length);
|
||||
this.cff = cff;
|
||||
|
||||
// The first five sections must be in order, all the others are reached
|
||||
@ -1017,6 +1017,10 @@ class CFF {
|
||||
|
||||
charStringCount = 0;
|
||||
|
||||
constructor(rawFileLength = 0) {
|
||||
this.rawFileLength = rawFileLength;
|
||||
}
|
||||
|
||||
duplicateFirstGlyph() {
|
||||
// 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
|
||||
@ -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.
|
||||
class CFFCompiler {
|
||||
constructor(cff) {
|
||||
@ -1380,21 +1435,7 @@ class CFFCompiler {
|
||||
|
||||
compile() {
|
||||
const cff = this.cff;
|
||||
const output = {
|
||||
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;
|
||||
},
|
||||
};
|
||||
const output = new CompilerOutput(cff.rawFileLength);
|
||||
|
||||
// Compile the five entries that must be in order.
|
||||
const header = this.compileHeader(cff.header);
|
||||
@ -1499,7 +1540,7 @@ class CFFCompiler {
|
||||
// the sanitizer will bail out. Add a dummy byte to avoid that.
|
||||
output.add([0]);
|
||||
|
||||
return output.data;
|
||||
return output.finalData;
|
||||
}
|
||||
|
||||
encodeNumber(value) {
|
||||
@ -1781,7 +1822,7 @@ class CFFCompiler {
|
||||
} else {
|
||||
const length = 1 + numGlyphsLessNotDef * 2;
|
||||
out = new Uint8Array(length);
|
||||
out[0] = 0; // format 0
|
||||
// format 0, skip redundant `out[0] = 0;` assignment.
|
||||
let charsetIndex = 0;
|
||||
const numCharsets = charset.charset.length;
|
||||
let warned = false;
|
||||
@ -1802,11 +1843,11 @@ class CFFCompiler {
|
||||
out[i + 1] = sid & 0xff;
|
||||
}
|
||||
}
|
||||
return this.compileTypedArray(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
compileEncoding(encoding) {
|
||||
return this.compileTypedArray(encoding.raw);
|
||||
return encoding.raw;
|
||||
}
|
||||
|
||||
compileFDSelect(fdSelect) {
|
||||
@ -1847,11 +1888,7 @@ class CFFCompiler {
|
||||
out = new Uint8Array(ranges);
|
||||
break;
|
||||
}
|
||||
return this.compileTypedArray(out);
|
||||
}
|
||||
|
||||
compileTypedArray(data) {
|
||||
return Array.from(data);
|
||||
return out;
|
||||
}
|
||||
|
||||
compileIndex(index, trackers = []) {
|
||||
@ -1915,9 +1952,8 @@ class CFFCompiler {
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
// 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]);
|
||||
}
|
||||
return data;
|
||||
|
||||
@ -35,11 +35,6 @@ function writeData(dest, offset, data) {
|
||||
for (let i = 0, ii = data.length; i < ii; i++) {
|
||||
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.
|
||||
*/
|
||||
class Type1Font {
|
||||
#rawFileLength;
|
||||
|
||||
constructor(name, file, properties) {
|
||||
// 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.
|
||||
@ -200,6 +202,7 @@ class Type1Font {
|
||||
for (const key in data.properties) {
|
||||
properties[key] = data.properties[key];
|
||||
}
|
||||
this.#rawFileLength = headerBlock.length + eexecBlock.length;
|
||||
|
||||
const charstrings = data.charstrings;
|
||||
const type2Charstrings = this.getType2Charstrings(charstrings);
|
||||
@ -323,7 +326,7 @@ class Type1Font {
|
||||
}
|
||||
|
||||
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.names = [name];
|
||||
|
||||
@ -430,47 +430,53 @@ describe("CFFCompiler", function () {
|
||||
const fdSelect = new CFFFDSelect(0, [3, 2, 1]);
|
||||
const c = new CFFCompiler();
|
||||
const out = c.compileFDSelect(fdSelect);
|
||||
expect(out).toEqual([
|
||||
0, // format
|
||||
3, // gid: 0 fd 3
|
||||
2, // gid: 1 fd 3
|
||||
1, // gid: 2 fd 3
|
||||
]);
|
||||
expect(out).toEqual(
|
||||
new Uint8Array([
|
||||
0, // format
|
||||
3, // gid: 0 fd 3
|
||||
2, // gid: 1 fd 3
|
||||
1, // gid: 2 fd 3
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("compiles fdselect format 3", function () {
|
||||
const fdSelect = new CFFFDSelect(3, [0, 0, 1, 1]);
|
||||
const c = new CFFCompiler();
|
||||
const out = c.compileFDSelect(fdSelect);
|
||||
expect(out).toEqual([
|
||||
3, // format
|
||||
0, // nRanges (high)
|
||||
2, // nRanges (low)
|
||||
0, // range struct 0 - first (high)
|
||||
0, // range struct 0 - first (low)
|
||||
0, // range struct 0 - fd
|
||||
0, // range struct 0 - first (high)
|
||||
2, // range struct 0 - first (low)
|
||||
1, // range struct 0 - fd
|
||||
0, // sentinel (high)
|
||||
4, // sentinel (low)
|
||||
]);
|
||||
expect(out).toEqual(
|
||||
new Uint8Array([
|
||||
3, // format
|
||||
0, // nRanges (high)
|
||||
2, // nRanges (low)
|
||||
0, // range struct 0 - first (high)
|
||||
0, // range struct 0 - first (low)
|
||||
0, // range struct 0 - fd
|
||||
0, // range struct 0 - first (high)
|
||||
2, // range struct 0 - first (low)
|
||||
1, // range struct 0 - fd
|
||||
0, // sentinel (high)
|
||||
4, // sentinel (low)
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("compiles fdselect format 3, single range", function () {
|
||||
const fdSelect = new CFFFDSelect(3, [0, 0]);
|
||||
const c = new CFFCompiler();
|
||||
const out = c.compileFDSelect(fdSelect);
|
||||
expect(out).toEqual([
|
||||
3, // format
|
||||
0, // nRanges (high)
|
||||
1, // nRanges (low)
|
||||
0, // range struct 0 - first (high)
|
||||
0, // range struct 0 - first (low)
|
||||
0, // range struct 0 - fd
|
||||
0, // sentinel (high)
|
||||
2, // sentinel (low)
|
||||
]);
|
||||
expect(out).toEqual(
|
||||
new Uint8Array([
|
||||
3, // format
|
||||
0, // nRanges (high)
|
||||
1, // nRanges (low)
|
||||
0, // range struct 0 - first (high)
|
||||
0, // range struct 0 - first (low)
|
||||
0, // range struct 0 - fd
|
||||
0, // sentinel (high)
|
||||
2, // sentinel (low)
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("compiles charset of CID font", function () {
|
||||
@ -479,13 +485,15 @@ describe("CFFCompiler", function () {
|
||||
const numGlyphs = 7;
|
||||
const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), true);
|
||||
// All CID charsets get turned into a simple format 2.
|
||||
expect(out).toEqual([
|
||||
2, // format
|
||||
0, // cid (high)
|
||||
1, // cid (low)
|
||||
0, // nLeft (high)
|
||||
numGlyphs - 2, // nLeft (low)
|
||||
]);
|
||||
expect(out).toEqual(
|
||||
new Uint8Array([
|
||||
2, // format
|
||||
0, // cid (high)
|
||||
1, // cid (low)
|
||||
0, // nLeft (high)
|
||||
numGlyphs - 2, // nLeft (low)
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("compiles charset of non CID font", function () {
|
||||
@ -494,13 +502,15 @@ describe("CFFCompiler", function () {
|
||||
const numGlyphs = 3;
|
||||
const out = c.compileCharset(charset, numGlyphs, new CFFStrings(), false);
|
||||
// All non-CID fonts use a format 0 charset.
|
||||
expect(out).toEqual([
|
||||
0, // format
|
||||
0, // sid of 'space' (high)
|
||||
1, // sid of 'space' (low)
|
||||
0, // sid of 'exclam' (high)
|
||||
2, // sid of 'exclam' (low)
|
||||
]);
|
||||
expect(out).toEqual(
|
||||
new Uint8Array([
|
||||
0, // format
|
||||
0, // sid of 'space' (high)
|
||||
1, // sid of 'space' (low)
|
||||
0, // sid of 'exclam' (high)
|
||||
2, // sid of 'exclam' (low)
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
// TODO a lot more compiler tests
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user