pdf.js/src/core/font_renderer.js
Jonas Jenwald 1b4a7c5965 Introduce more optional chaining in the src/core/ folder
After PR 12563 we're now free to use optional chaining in the worker-thread as well. (This patch also fixes one previously "missed" case in the `web/` folder.)

For the MOZCENTRAL build-target this patch reduces the total bundle-size by `1.6` kilobytes.
2023-05-15 12:38:28 +02:00

903 lines
26 KiB
JavaScript

/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
bytesToString,
FONT_IDENTITY_MATRIX,
FormatError,
unreachable,
warn,
} from "../shared/util.js";
import { CFFParser } from "./cff_parser.js";
import { getGlyphsUnicode } from "./glyphlist.js";
import { StandardEncoding } from "./encodings.js";
import { Stream } from "./stream.js";
// TODO: use DataView and its methods.
function getUint32(data, offset) {
return (
((data[offset] << 24) |
(data[offset + 1] << 16) |
(data[offset + 2] << 8) |
data[offset + 3]) >>>
0
);
}
function getUint16(data, offset) {
return (data[offset] << 8) | data[offset + 1];
}
function getInt16(data, offset) {
return ((data[offset] << 24) | (data[offset + 1] << 16)) >> 16;
}
function getInt8(data, offset) {
return (data[offset] << 24) >> 24;
}
function getFloat214(data, offset) {
return getInt16(data, offset) / 16384;
}
function getSubroutineBias(subrs) {
const numSubrs = subrs.length;
let bias = 32768;
if (numSubrs < 1240) {
bias = 107;
} else if (numSubrs < 33900) {
bias = 1131;
}
return bias;
}
function parseCmap(data, start, end) {
const offset =
getUint16(data, start + 2) === 1
? getUint32(data, start + 8)
: getUint32(data, start + 16);
const format = getUint16(data, start + offset);
let ranges, p, i;
if (format === 4) {
getUint16(data, start + offset + 2); // length
const segCount = getUint16(data, start + offset + 6) >> 1;
p = start + offset + 14;
ranges = [];
for (i = 0; i < segCount; i++, p += 2) {
ranges[i] = { end: getUint16(data, p) };
}
p += 2;
for (i = 0; i < segCount; i++, p += 2) {
ranges[i].start = getUint16(data, p);
}
for (i = 0; i < segCount; i++, p += 2) {
ranges[i].idDelta = getUint16(data, p);
}
for (i = 0; i < segCount; i++, p += 2) {
let idOffset = getUint16(data, p);
if (idOffset === 0) {
continue;
}
ranges[i].ids = [];
for (let j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
ranges[i].ids[j] = getUint16(data, p + idOffset);
idOffset += 2;
}
}
return ranges;
} else if (format === 12) {
const groups = getUint32(data, start + offset + 12);
p = start + offset + 16;
ranges = [];
for (i = 0; i < groups; i++) {
start = getUint32(data, p);
ranges.push({
start,
end: getUint32(data, p + 4),
idDelta: getUint32(data, p + 8) - start,
});
p += 12;
}
return ranges;
}
throw new FormatError(`unsupported cmap: ${format}`);
}
function parseCff(data, start, end, seacAnalysisEnabled) {
const properties = {};
const parser = new CFFParser(
new Stream(data, start, end - start),
properties,
seacAnalysisEnabled
);
const cff = parser.parse();
return {
glyphs: cff.charStrings.objects,
subrs: cff.topDict.privateDict?.subrsIndex?.objects,
gsubrs: cff.globalSubrIndex?.objects,
isCFFCIDFont: cff.isCIDFont,
fdSelect: cff.fdSelect,
fdArray: cff.fdArray,
};
}
function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
let itemSize, itemDecode;
if (isGlyphLocationsLong) {
itemSize = 4;
itemDecode = getUint32;
} else {
itemSize = 2;
itemDecode = (data, offset) => 2 * getUint16(data, offset);
}
const glyphs = [];
let startOffset = itemDecode(loca, 0);
for (let j = itemSize; j < loca.length; j += itemSize) {
const endOffset = itemDecode(loca, j);
glyphs.push(glyf.subarray(startOffset, endOffset));
startOffset = endOffset;
}
return glyphs;
}
function lookupCmap(ranges, unicode) {
const code = unicode.codePointAt(0);
let gid = 0,
l = 0,
r = ranges.length - 1;
while (l < r) {
const c = (l + r + 1) >> 1;
if (code < ranges[c].start) {
r = c - 1;
} else {
l = c;
}
}
if (ranges[l].start <= code && code <= ranges[l].end) {
gid =
(ranges[l].idDelta +
(ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code)) &
0xffff;
}
return {
charCode: code,
glyphId: gid,
};
}
function compileGlyf(code, cmds, font) {
function moveTo(x, y) {
cmds.push({ cmd: "moveTo", args: [x, y] });
}
function lineTo(x, y) {
cmds.push({ cmd: "lineTo", args: [x, y] });
}
function quadraticCurveTo(xa, ya, x, y) {
cmds.push({ cmd: "quadraticCurveTo", args: [xa, ya, x, y] });
}
let i = 0;
const numberOfContours = getInt16(code, i);
let flags;
let x = 0,
y = 0;
i += 10;
if (numberOfContours < 0) {
// composite glyph
do {
flags = getUint16(code, i);
const glyphIndex = getUint16(code, i + 2);
i += 4;
let arg1, arg2;
if (flags & 0x01) {
if (flags & 0x02) {
arg1 = getInt16(code, i);
arg2 = getInt16(code, i + 2);
} else {
arg1 = getUint16(code, i);
arg2 = getUint16(code, i + 2);
}
i += 4;
} else {
if (flags & 0x02) {
arg1 = getInt8(code, i++);
arg2 = getInt8(code, i++);
} else {
arg1 = code[i++];
arg2 = code[i++];
}
}
if (flags & 0x02) {
x = arg1;
y = arg2;
} else {
x = 0;
y = 0;
}
let scaleX = 1,
scaleY = 1,
scale01 = 0,
scale10 = 0;
if (flags & 0x08) {
scaleX = scaleY = getFloat214(code, i);
i += 2;
} else if (flags & 0x40) {
scaleX = getFloat214(code, i);
scaleY = getFloat214(code, i + 2);
i += 4;
} else if (flags & 0x80) {
scaleX = getFloat214(code, i);
scale01 = getFloat214(code, i + 2);
scale10 = getFloat214(code, i + 4);
scaleY = getFloat214(code, i + 6);
i += 8;
}
const subglyph = font.glyphs[glyphIndex];
if (subglyph) {
// TODO: the transform should be applied only if there is a scale:
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205
cmds.push(
{ cmd: "save" },
{
cmd: "transform",
args: [scaleX, scale01, scale10, scaleY, x, y],
}
);
if (!(flags & 0x02)) {
// TODO: we must use arg1 and arg2 to make something similar to:
// https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209
}
compileGlyf(subglyph, cmds, font);
cmds.push({ cmd: "restore" });
}
} while (flags & 0x20);
} else {
// simple glyph
const endPtsOfContours = [];
let j, jj;
for (j = 0; j < numberOfContours; j++) {
endPtsOfContours.push(getUint16(code, i));
i += 2;
}
const instructionLength = getUint16(code, i);
i += 2 + instructionLength; // skipping the instructions
const numberOfPoints = endPtsOfContours.at(-1) + 1;
const points = [];
while (points.length < numberOfPoints) {
flags = code[i++];
let repeat = 1;
if (flags & 0x08) {
repeat += code[i++];
}
while (repeat-- > 0) {
points.push({ flags });
}
}
for (j = 0; j < numberOfPoints; j++) {
switch (points[j].flags & 0x12) {
case 0x00:
x += getInt16(code, i);
i += 2;
break;
case 0x02:
x -= code[i++];
break;
case 0x12:
x += code[i++];
break;
}
points[j].x = x;
}
for (j = 0; j < numberOfPoints; j++) {
switch (points[j].flags & 0x24) {
case 0x00:
y += getInt16(code, i);
i += 2;
break;
case 0x04:
y -= code[i++];
break;
case 0x24:
y += code[i++];
break;
}
points[j].y = y;
}
let startPoint = 0;
for (i = 0; i < numberOfContours; i++) {
const endPoint = endPtsOfContours[i];
// contours might have implicit points, which is located in the middle
// between two neighboring off-curve points
const contour = points.slice(startPoint, endPoint + 1);
if (contour[0].flags & 1) {
contour.push(contour[0]); // using start point at the contour end
} else if (contour.at(-1).flags & 1) {
// first is off-curve point, trying to use one from the end
contour.unshift(contour.at(-1));
} else {
// start and end are off-curve points, creating implicit one
const p = {
flags: 1,
x: (contour[0].x + contour.at(-1).x) / 2,
y: (contour[0].y + contour.at(-1).y) / 2,
};
contour.unshift(p);
contour.push(p);
}
moveTo(contour[0].x, contour[0].y);
for (j = 1, jj = contour.length; j < jj; j++) {
if (contour[j].flags & 1) {
lineTo(contour[j].x, contour[j].y);
} else if (contour[j + 1].flags & 1) {
quadraticCurveTo(
contour[j].x,
contour[j].y,
contour[j + 1].x,
contour[j + 1].y
);
j++;
} else {
quadraticCurveTo(
contour[j].x,
contour[j].y,
(contour[j].x + contour[j + 1].x) / 2,
(contour[j].y + contour[j + 1].y) / 2
);
}
}
startPoint = endPoint + 1;
}
}
}
function compileCharString(charStringCode, cmds, font, glyphId) {
function moveTo(x, y) {
cmds.push({ cmd: "moveTo", args: [x, y] });
}
function lineTo(x, y) {
cmds.push({ cmd: "lineTo", args: [x, y] });
}
function bezierCurveTo(x1, y1, x2, y2, x, y) {
cmds.push({ cmd: "bezierCurveTo", args: [x1, y1, x2, y2, x, y] });
}
const stack = [];
let x = 0,
y = 0;
let stems = 0;
function parse(code) {
let i = 0;
while (i < code.length) {
let stackClean = false;
let v = code[i++];
let xa, xb, ya, yb, y1, y2, y3, n, subrCode;
switch (v) {
case 1: // hstem
stems += stack.length >> 1;
stackClean = true;
break;
case 3: // vstem
stems += stack.length >> 1;
stackClean = true;
break;
case 4: // vmoveto
y += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 5: // rlineto
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
break;
case 6: // hlineto
while (stack.length > 0) {
x += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
y += stack.shift();
lineTo(x, y);
}
break;
case 7: // vlineto
while (stack.length > 0) {
y += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
x += stack.shift();
lineTo(x, y);
}
break;
case 8: // rrcurveto
while (stack.length > 0) {
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 10: // callsubr
n = stack.pop();
subrCode = null;
if (font.isCFFCIDFont) {
const fdIndex = font.fdSelect.getFDIndex(glyphId);
if (fdIndex >= 0 && fdIndex < font.fdArray.length) {
const fontDict = font.fdArray[fdIndex];
let subrs;
if (fontDict.privateDict?.subrsIndex) {
subrs = fontDict.privateDict.subrsIndex.objects;
}
if (subrs) {
// Add subroutine bias.
n += getSubroutineBias(subrs);
subrCode = subrs[n];
}
} else {
warn("Invalid fd index for glyph index.");
}
} else {
subrCode = font.subrs[n + font.subrsBias];
}
if (subrCode) {
parse(subrCode);
}
break;
case 11: // return
return;
case 12:
v = code[i++];
switch (v) {
case 34: // flex
xa = x + stack.shift();
xb = xa + stack.shift();
y1 = y + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y, xb, y1, x, y1);
xa = x + stack.shift();
xb = xa + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y, x, y);
break;
case 35: // flex
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
stack.pop(); // fd
break;
case 36: // hflex1
xa = x + stack.shift();
y1 = y + stack.shift();
xb = xa + stack.shift();
y2 = y1 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y2, x, y2);
xa = x + stack.shift();
xb = xa + stack.shift();
y3 = y2 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y2, xb, y3, x, y);
break;
case 37: // flex1
const x0 = x,
y0 = y;
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb;
y = yb;
if (Math.abs(x - x0) > Math.abs(y - y0)) {
x += stack.shift();
} else {
y += stack.shift();
}
bezierCurveTo(xa, ya, xb, yb, x, y);
break;
default:
throw new FormatError(`unknown operator: 12 ${v}`);
}
break;
case 14: // endchar
if (stack.length >= 4) {
const achar = stack.pop();
const bchar = stack.pop();
y = stack.pop();
x = stack.pop();
cmds.push({ cmd: "save" }, { cmd: "translate", args: [x, y] });
let cmap = lookupCmap(
font.cmap,
String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])
);
compileCharString(
font.glyphs[cmap.glyphId],
cmds,
font,
cmap.glyphId
);
cmds.push({ cmd: "restore" });
cmap = lookupCmap(
font.cmap,
String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]])
);
compileCharString(
font.glyphs[cmap.glyphId],
cmds,
font,
cmap.glyphId
);
}
return;
case 18: // hstemhm
stems += stack.length >> 1;
stackClean = true;
break;
case 19: // hintmask
stems += stack.length >> 1;
i += (stems + 7) >> 3;
stackClean = true;
break;
case 20: // cntrmask
stems += stack.length >> 1;
i += (stems + 7) >> 3;
stackClean = true;
break;
case 21: // rmoveto
y += stack.pop();
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 22: // hmoveto
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 23: // vstemhm
stems += stack.length >> 1;
stackClean = true;
break;
case 24: // rcurveline
while (stack.length > 2) {
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
x += stack.shift();
y += stack.shift();
lineTo(x, y);
break;
case 25: // rlinecurve
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
break;
case 26: // vvcurveto
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb;
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 27: // hhcurveto
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb;
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 28:
stack.push(((code[i] << 24) | (code[i + 1] << 16)) >> 16);
i += 2;
break;
case 29: // callgsubr
n = stack.pop() + font.gsubrsBias;
subrCode = font.gsubrs[n];
if (subrCode) {
parse(subrCode);
}
break;
case 30: // vhcurveto
while (stack.length > 0) {
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
if (stack.length === 0) {
break;
}
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
y = yb + stack.shift();
x = xb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 31: // hvcurveto
while (stack.length > 0) {
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
y = yb + stack.shift();
x = xb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
if (stack.length === 0) {
break;
}
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
default:
if (v < 32) {
throw new FormatError(`unknown operator: ${v}`);
}
if (v < 247) {
stack.push(v - 139);
} else if (v < 251) {
stack.push((v - 247) * 256 + code[i++] + 108);
} else if (v < 255) {
stack.push(-(v - 251) * 256 - code[i++] - 108);
} else {
stack.push(
((code[i] << 24) |
(code[i + 1] << 16) |
(code[i + 2] << 8) |
code[i + 3]) /
65536
);
i += 4;
}
break;
}
if (stackClean) {
stack.length = 0;
}
}
}
parse(charStringCode);
}
const NOOP = [];
class CompiledFont {
constructor(fontMatrix) {
if (this.constructor === CompiledFont) {
unreachable("Cannot initialize CompiledFont.");
}
this.fontMatrix = fontMatrix;
this.compiledGlyphs = Object.create(null);
this.compiledCharCodeToGlyphId = Object.create(null);
}
getPathJs(unicode) {
const { charCode, glyphId } = lookupCmap(this.cmap, unicode);
let fn = this.compiledGlyphs[glyphId];
if (!fn) {
try {
fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
this.compiledGlyphs[glyphId] = fn;
} catch (ex) {
// Avoid attempting to re-compile a corrupt glyph.
this.compiledGlyphs[glyphId] = NOOP;
if (this.compiledCharCodeToGlyphId[charCode] === undefined) {
this.compiledCharCodeToGlyphId[charCode] = glyphId;
}
throw ex;
}
}
if (this.compiledCharCodeToGlyphId[charCode] === undefined) {
this.compiledCharCodeToGlyphId[charCode] = glyphId;
}
return fn;
}
compileGlyph(code, glyphId) {
if (!code || code.length === 0 || code[0] === 14) {
return NOOP;
}
let fontMatrix = this.fontMatrix;
if (this.isCFFCIDFont) {
// Top DICT's FontMatrix can be ignored because CFFCompiler always
// removes it and copies to FDArray DICTs.
const fdIndex = this.fdSelect.getFDIndex(glyphId);
if (fdIndex >= 0 && fdIndex < this.fdArray.length) {
const fontDict = this.fdArray[fdIndex];
fontMatrix = fontDict.getByName("FontMatrix") || FONT_IDENTITY_MATRIX;
} else {
warn("Invalid fd index for glyph index.");
}
}
const cmds = [
{ cmd: "save" },
{ cmd: "transform", args: fontMatrix.slice() },
{ cmd: "scale", args: ["size", "-size"] },
];
this.compileGlyphImpl(code, cmds, glyphId);
cmds.push({ cmd: "restore" });
return cmds;
}
compileGlyphImpl() {
unreachable("Children classes should implement this.");
}
hasBuiltPath(unicode) {
const { charCode, glyphId } = lookupCmap(this.cmap, unicode);
return (
this.compiledGlyphs[glyphId] !== undefined &&
this.compiledCharCodeToGlyphId[charCode] !== undefined
);
}
}
class TrueTypeCompiled extends CompiledFont {
constructor(glyphs, cmap, fontMatrix) {
super(fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0]);
this.glyphs = glyphs;
this.cmap = cmap;
}
compileGlyphImpl(code, cmds) {
compileGlyf(code, cmds, this);
}
}
class Type2Compiled extends CompiledFont {
constructor(cffInfo, cmap, fontMatrix, glyphNameMap) {
super(fontMatrix || [0.001, 0, 0, 0.001, 0, 0]);
this.glyphs = cffInfo.glyphs;
this.gsubrs = cffInfo.gsubrs || [];
this.subrs = cffInfo.subrs || [];
this.cmap = cmap;
this.glyphNameMap = glyphNameMap || getGlyphsUnicode();
this.gsubrsBias = getSubroutineBias(this.gsubrs);
this.subrsBias = getSubroutineBias(this.subrs);
this.isCFFCIDFont = cffInfo.isCFFCIDFont;
this.fdSelect = cffInfo.fdSelect;
this.fdArray = cffInfo.fdArray;
}
compileGlyphImpl(code, cmds, glyphId) {
compileCharString(code, cmds, this, glyphId);
}
}
class FontRendererFactory {
static create(font, seacAnalysisEnabled) {
const data = new Uint8Array(font.data);
let cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
const numTables = getUint16(data, 4);
for (let i = 0, p = 12; i < numTables; i++, p += 16) {
const tag = bytesToString(data.subarray(p, p + 4));
const offset = getUint32(data, p + 8);
const length = getUint32(data, p + 12);
switch (tag) {
case "cmap":
cmap = parseCmap(data, offset, offset + length);
break;
case "glyf":
glyf = data.subarray(offset, offset + length);
break;
case "loca":
loca = data.subarray(offset, offset + length);
break;
case "head":
unitsPerEm = getUint16(data, offset + 18);
indexToLocFormat = getUint16(data, offset + 50);
break;
case "CFF ":
cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
break;
}
}
if (glyf) {
const fontMatrix = !unitsPerEm
? font.fontMatrix
: [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0];
return new TrueTypeCompiled(
parseGlyfTable(glyf, loca, indexToLocFormat),
cmap,
fontMatrix
);
}
return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
}
}
export { FontRendererFactory };