mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-11 17:45:49 +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() {
|
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