fontjs
Version:
FontJS (Font.js) is a packages for TrueType font parsing and manipulation
1,244 lines (1,228 loc) • 225 kB
JavaScript
/*!
* BSD 3-Clause License
*
* Copyright (c) 2021, PeculiarVentures
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
import { SeqStream } from 'bytestreamjs';
import * as asn1js from 'asn1js';
import * as pkijs from 'pkijs';
import { Convert } from 'pvtsutils';
function unicodePointsToCodePoints(unicodePoints, utf16 = true, be = true, includeBOM = true) {
const codePoints = [];
const dataView = new DataView((new Uint8Array(unicodePoints)).buffer);
const step = (utf16) ? 2 : 4;
const flag = !be;
let start = 0;
if (includeBOM)
start = (utf16) ? 2 : 4;
for (let i = start; i < dataView.byteLength; i += step) {
if (utf16) {
codePoints.push(dataView.getUint16(i, flag));
}
else {
codePoints.push(dataView.getUint32(i, flag));
}
}
return codePoints;
}
function stringToCodePoints(str, utf16 = true, be = true, includeBOM = true) {
const result = [];
if (includeBOM) {
switch (true) {
case (utf16 && be):
result.push(0xFE);
result.push(0xFF);
break;
case (utf16 && !be):
result.push(0xFF);
result.push(0xFE);
break;
case (!utf16 && be):
result.push(0x00);
result.push(0x00);
result.push(0xFE);
result.push(0xFF);
break;
case (!utf16 && !be):
result.push(0xFF);
result.push(0xFE);
result.push(0x00);
result.push(0x00);
break;
}
}
for (const ch of str) {
for (let i = 0; i < ch.length; i++) {
const codeView = new Uint8Array((new Uint32Array([ch.codePointAt(i) || 0])).buffer);
if (!utf16 && !be)
result.push(...codeView);
else {
if (!utf16) {
result.push(codeView[3]);
result.push(codeView[2]);
}
if (be) {
result.push(codeView[1]);
result.push(codeView[0]);
}
else {
result.push(codeView[0]);
result.push(codeView[1]);
}
}
}
}
return result;
}
function stringToUnicode(str, utf16 = true, be = true, includeBOM = true) {
return String.fromCodePoint(...stringToCodePoints(str, utf16, be, includeBOM));
}
function unicodeToString(unicode, utf16 = true, be = true, includeBOM = true) {
const unicodePoints = [];
for (let i = 0; i < unicode.length; i++) {
unicodePoints.push(unicode.charCodeAt(i));
}
return String.fromCodePoint(...unicodePointsToCodePoints(unicodePoints, utf16, be, includeBOM));
}
function stringToUnicodeHex(str, utf16 = true, be = true, includeBOM = true) {
return Array
.from(stringToCodePoints(str, utf16, be, includeBOM), element => `0${element.toString(16)}`.slice(-2))
.join("")
.toUpperCase();
}
function unicodeHexToString(unicode, utf16 = true, be = true, includeBOM = true) {
const unicodePoints = [];
for (let i = 0; i < unicode.length; i += 2)
unicodePoints.push(parseInt(unicode.slice(i, i + 2), 16));
return String.fromCodePoint(...unicodePointsToCodePoints(unicodePoints, utf16, be, includeBOM));
}
function getLongDateTime(stream) {
stream.getUint32();
const part2 = stream.getUint32();
return new Date((part2 - 2082844800) * 1000);
}
function appendLongDateTime(date, stream) {
stream.appendUint32(0);
stream.appendUint32((date.getTime() / 1000) + 2082844800);
}
function getF2Dot14(stream) {
const value = stream.getInt16();
return (value / 16384);
}
function appendF2Dot14(value, stream) {
stream.appendInt16(value * 16384);
}
function checkFlag(flag, mask) {
return ((flag & mask) === mask);
}
const point = 1 << (32 >> 1);
function getFixed(stream) {
return stream.getInt32() / point;
}
function appendFixed(value, stream) {
stream.appendInt32(value * point);
}
const zero = 0;
const one = 1;
class Matrix {
constructor(parameters = {}) {
this._array = [
one, zero, zero,
zero, one, zero,
zero, zero, one
];
this.setValues(parameters);
}
static makeTranslation(tx, ty) {
return new Matrix({
e: tx,
f: ty
});
}
setValues(parameters = {}) {
this.a = parameters.a || one;
this.b = parameters.b || zero;
this.c = parameters.c || zero;
this.d = parameters.d || one;
this.e = parameters.e || zero;
this.f = parameters.f || zero;
this.j = parameters.j || zero;
this.k = parameters.k || zero;
this.l = parameters.l || one;
}
multiply(other) {
return new Matrix({
a: ((this.a * other.a) + (this.b * other.c) + (this.j * other.e)),
b: ((this.a * other.b) + (this.b * other.d) + (this.j * other.f)),
j: ((this.a * other.j) + (this.b * other.k) + (this.j * other.l)),
c: ((this.c * other.a) + (this.d * other.c) + (this.k * other.e)),
d: ((this.c * other.b) + (this.d * other.d) + (this.k * other.f)),
k: ((this.c * other.j) + (this.d * other.k) + (this.k * other.l)),
e: ((this.e * other.a) + (this.f * other.c) + (this.l * other.e)),
f: ((this.e * other.b) + (this.f * other.d) + (this.l * other.f)),
l: ((this.e * other.j) + (this.f * other.k) + (this.l * other.l))
});
}
add(other) {
return new Matrix({
a: (this.a + other.a),
b: (this.b + other.b),
c: (this.c + other.c),
d: (this.d + other.d),
e: (this.e + other.e),
f: (this.f + other.f),
j: (this.j + other.j),
k: (this.k + other.k),
l: (this.l + other.l)
});
}
sub(other) {
return new Matrix({
a: (this.a - other.a),
b: (this.b - other.b),
c: (this.c - other.c),
d: (this.d - other.d),
e: (this.e - other.e),
f: (this.f - other.f),
j: (this.j - other.j),
k: (this.k - other.k),
l: (this.l - other.l),
});
}
get det() {
return ((this.a * this.d * this.l) -
(this.a * this.k * this.f) -
(this.b * this.c * this.l) +
(this.b * this.k * this.e) +
(this.j * this.c * this.f) -
(this.j * this.d * this.e));
}
get a() {
return this._array[0];
}
set a(value) {
this._array[0] = value;
}
get b() {
return this._array[1];
}
set b(value) {
this._array[1] = value;
}
get c() {
return this._array[3];
}
set c(value) {
this._array[3] = value;
}
get d() {
return this._array[4];
}
set d(value) {
this._array[4] = value;
}
get e() {
return this._array[6];
}
set e(value) {
this._array[6] = value;
}
get f() {
return this._array[7];
}
set f(value) {
this._array[7] = value;
}
get j() {
return this._array[2];
}
set j(value) {
this._array[2] = value;
}
get k() {
return this._array[5];
}
set k(value) {
this._array[5] = value;
}
get l() {
return this._array[8];
}
set l(value) {
this._array[8] = value;
}
}
class BaseClass {
static get className() {
return "BaseClass";
}
toStream(stream, ...args) {
return true;
}
static fromStream(stream, ...args) {
return new BaseClass();
}
}
class FontTable extends BaseClass {
static get tag() {
return 0;
}
}
const StandardStrings = [
".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright",
"parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater",
"question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
"T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
"quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
"u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling",
"fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft",
"guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph",
"bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand",
"questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring",
"cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu",
"trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright",
"Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex",
"Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex",
"Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute",
"Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute",
"ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave",
"yacute", "ydieresis", "zcaron", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior",
"ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "266 ff", "onedotenleader",
"zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
"sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", "threequartersemdash", "periodsuperior",
"questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior",
"msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "ffi", "ffl",
"parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall",
"Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall",
"Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall",
"Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall",
"centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
"Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
"questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
"zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior",
"seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior",
"commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall",
"Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall",
"Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall",
"Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall",
"Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000",
"001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold"
];
class INDEX extends BaseClass {
constructor(parameters = {}) {
super();
this.data = parameters.data || [];
}
static fromStream(stream, version = 1) {
let count = 0;
switch (version) {
case 1:
count = stream.getUint16();
break;
default:
count = stream.getUint32();
}
const offSize = (stream.getBlock(1))[0];
const offsets = [];
for (let i = 0; i <= count; i++) {
let offset = null;
switch (offSize) {
case 1:
offset = (stream.getBlock(1))[0];
break;
case 2:
offset = stream.getUint16();
break;
case 3:
offset = stream.getUint24();
break;
case 4:
offset = stream.getUint32();
break;
}
if (offset !== null)
offsets.push(offset);
}
const data = [];
for (let i = 0; i < (offsets.length - 1); i++) {
const value = stream.stream.buffer.slice(stream.start - 1 + offsets[i], stream.start - 1 + offsets[i + 1]);
data.push(value);
}
stream.start = stream.start - 1 + offsets[offsets.length - 1];
return new INDEX({ data });
}
}
class StringIndex extends INDEX {
constructor(params = {}) {
super(params);
}
static fromStream(stream, version = 1) {
const index = INDEX.fromStream(stream, version);
const data = index.data.map(o => String.fromCharCode(...new Uint8Array(o)));
return new StringIndex({ data });
}
}
class CFFCharset extends BaseClass {
constructor(parameters = {}) {
super();
this.format = parameters.format || 0;
if ("charset" in parameters)
this.charset = parameters.charset;
}
static fromStream(stream, numGlyphs = 0, stringIndex = new StringIndex()) {
const parameters = {};
parameters.charset = [".notdef"];
parameters.format = (stream.getBlock(1))[0];
switch (parameters.format) {
case 0:
{
for (let i = 0; i < (numGlyphs - 1); i++) {
const value = stream.getUint16();
parameters.charset.push((value <= 390) ? StandardStrings[value] : stringIndex.data[value - 391]);
}
}
break;
case 1:
{
while (parameters.charset.length <= (numGlyphs - 1)) {
let value = stream.getUint16();
const count = (stream.getBlock(1))[0];
for (let j = 0; j <= count; j++, value++)
parameters.charset.push((value <= 390) ? StandardStrings[value] : stringIndex.data[value - 391]);
}
}
break;
case 2:
{
while (parameters.charset.length <= (numGlyphs - 1)) {
let value = stream.getUint16();
const count = stream.getUint16();
for (let j = 0; j <= count; j++, value++)
parameters.charset.push((value <= 390) ? StandardStrings[value] : stringIndex.data[value - 391]);
}
}
break;
}
return new CFFCharset(parameters);
}
}
class CFFEncoding extends BaseClass {
constructor(parameters = {}) {
super();
this.format = parameters.format || 0;
this.encoding = parameters.encoding || {};
}
static fromStream(stream) {
const parameters = {};
parameters.encoding = {};
parameters.format = (stream.getBlock(1))[0];
switch (parameters.format) {
case 0:
{
const nCodes = (stream.getBlock(1))[0];
for (let i = 0; i < nCodes; i++) {
const code = (stream.getBlock(1))[0];
parameters.encoding[code] = i;
}
}
break;
case 1:
{
const nRanges = (stream.getBlock(1))[0];
for (let i = 0; i < nRanges; i++) {
const first = (stream.getBlock(1))[0];
const nLeft = (stream.getBlock(1))[0];
for (let j = first, code = 1; j <= (first + nLeft); j++, code++)
parameters.encoding[j] = code;
}
}
break;
}
return new CFFEncoding(parameters);
}
}
const represents = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "E", "E-", "", "-"];
class DICT {
constructor(parameters = {}) {
this.entries = parameters.entries || [];
}
static fromBuffer(buffer) {
const view = new DataView(buffer);
const entries = [];
let values = [];
let current = 0;
while (current < buffer.byteLength) {
let value = null;
const b0 = view.getUint8(current);
current++;
switch (true) {
case (b0 <= 27):
{
let key = b0;
if (key === 12) {
key = 1200 + view.getUint8(current);
current++;
}
entries.push({ key, values });
values = [];
}
break;
case (b0 === 28):
{
const b1 = view.getUint8(current);
current++;
const b2 = view.getUint8(current);
current++;
value = (b1 << 8 | b2);
}
break;
case (b0 === 29):
{
const b1 = view.getUint8(current);
current++;
const b2 = view.getUint8(current);
current++;
const b3 = view.getUint8(current);
current++;
const b4 = view.getUint8(current);
current++;
value = (b1 << 24 | b2 << 16 | b3 << 8 | b4);
}
break;
case (b0 === 30):
{
let real = "";
while (true) {
const nibbles = view.getUint8(current);
current++;
const n1 = nibbles >> 4;
if (n1 === 0xF)
break;
real += represents[n1];
const n2 = nibbles & 0xF;
if (n2 === 0xF)
break;
real += represents[n2];
}
value = parseFloat(real);
}
break;
case ((b0 >= 32) && (b0 <= 246)):
value = (b0 - 139);
break;
case ((b0 >= 247) && (b0 <= 250)):
{
const b1 = view.getUint8(current);
current++;
value = ((b0 - 247) * 256 + b1 + 108);
}
break;
case ((b0 >= 251) && (b0 <= 254)):
{
const b1 = view.getUint8(current);
current++;
value = (-(b0 - 251) * 256 - b1 - 108);
}
break;
}
if (value !== null)
values.push(value);
}
return new DICT({ entries });
}
}
class CFFPrivateDICT extends DICT {
constructor(parameters = {}) {
super(parameters);
if ("BlueValues" in parameters) {
this.BlueValues = parameters.BlueValues;
}
if ("OtherBlues" in parameters) {
this.OtherBlues = parameters.OtherBlues;
}
if ("FamilyBlues" in parameters) {
this.FamilyBlues = parameters.FamilyBlues;
}
if ("FamilyOtherBlues" in parameters) {
this.FamilyOtherBlues = parameters.FamilyOtherBlues;
}
this.BlueScale = parameters.BlueScale || 0.039625;
this.BlueShift = parameters.BlueShift || 7;
this.BlueFuzz = parameters.BlueFuzz || 1;
if ("StdHW" in parameters) {
this.StdHW = parameters.StdHW;
}
if ("StdVW" in parameters) {
this.StdVW = parameters.StdVW;
}
if ("SteamSnapH" in parameters) {
this.SteamSnapH = parameters.SteamSnapH;
}
if ("SteamSnapV" in parameters) {
this.SteamSnapV = parameters.SteamSnapV;
}
this.ForceBold = parameters.ForceBold || false;
this.LanguageGroup = parameters.LanguageGroup || 0;
this.ExpansionFactor = parameters.ExpansionFactor || 0.06;
this.initialRandomSeed = parameters.initialRandomSeed || 0;
if ("Subrs" in parameters) {
this.Subrs = parameters.Subrs;
}
this.defaultWidthX = parameters.defaultWidthX || 0;
this.nominalWidthX = parameters.nominalWidthX || 0;
}
static fromBuffer(buffer) {
const dict = DICT.fromBuffer(buffer);
const parameters = { entries: dict.entries };
for (const entry of dict.entries) {
switch (entry.key) {
case 6:
parameters.BlueValues = entry.values;
break;
case 7:
parameters.OtherBlues = entry.values;
break;
case 8:
parameters.FamilyBlues = entry.values;
break;
case 9:
parameters.FamilyOtherBlues = entry.values;
break;
case 1209:
parameters.BlueScale = entry.values[0];
break;
case 1210:
parameters.BlueShift = entry.values[0];
break;
case 1211:
parameters.BlueFuzz = entry.values[0];
break;
case 10:
parameters.StdHW = entry.values[0];
break;
case 11:
parameters.StdVW = entry.values[0];
break;
case 1212:
parameters.SteamSnapH = entry.values;
break;
case 1213:
parameters.SteamSnapV = entry.values;
break;
case 1214:
parameters.ForceBold = !!(entry.values[0]);
break;
case 1217:
parameters.LanguageGroup = entry.values[0];
break;
case 1218:
parameters.ExpansionFactor = entry.values[0];
break;
case 1219:
parameters.initialRandomSeed = entry.values[0];
break;
case 19:
parameters.Subrs = entry.values[0];
break;
case 20:
parameters.defaultWidthX = entry.values[0];
break;
case 21:
parameters.nominalWidthX = entry.values[0];
break;
}
}
return new CFFPrivateDICT(parameters);
}
}
class CFFTopDICT extends DICT {
constructor(parameters = {}) {
super(parameters);
if ("version" in parameters) {
this.version = parameters.version;
}
if ("Notice" in parameters) {
this.Notice = parameters.Notice;
}
if ("Copyright" in parameters) {
this.Copyright = parameters.Copyright;
}
if ("FullName" in parameters) {
this.FullName = parameters.FullName;
}
if ("FamilyName" in parameters) {
this.FamilyName = parameters.FamilyName;
}
if ("Weight" in parameters) {
this.Weight = parameters.Weight;
}
this.isFixedPitch = parameters.isFixedPitch || false;
this.ItalicAngle = parameters.ItalicAngle || 0;
this.UnderlinePosition = parameters.UnderlinePosition || (-100);
this.UnderlineThickness = parameters.UnderlineThickness || 50;
this.PaintType = parameters.PaintType || 0;
this.CharstringType = parameters.CharstringType || 2;
this.FontMatrix = parameters.FontMatrix || [0.001, 0, 0, 0.001, 0, 0];
if ("UniqueID" in parameters) {
this.UniqueID = parameters.UniqueID;
}
this.FontBBox = parameters.FontBBox || [0, 0, 0, 0];
this.StrokeWidth = parameters.StrokeWidth || 0;
if ("XUID" in parameters) {
this.XUID = parameters.XUID;
}
this.charset = parameters.charset || 0;
this.Encoding = parameters.Encoding || 0;
if ("CharStrings" in parameters) {
this.CharStrings = parameters.CharStrings;
}
if ("Private" in parameters) {
this.Private = parameters.Private;
}
if ("SyntheticBase" in parameters) {
this.SyntheticBase = parameters.SyntheticBase;
}
if ("PostScript" in parameters) {
this.PostScript = parameters.PostScript;
}
if ("BaseFontName" in parameters) {
this.BaseFontName = parameters.BaseFontName;
}
if ("BaseFontBlend" in parameters) {
this.BaseFontBlend = parameters.BaseFontBlend;
}
}
static fromBuffer(buffer, stringIndex = new StringIndex()) {
const dict = DICT.fromBuffer(buffer);
const parameters = { entries: dict.entries };
for (const entry of dict.entries) {
switch (entry.key) {
case 0:
parameters.version = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 1:
parameters.Notice = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 1200:
parameters.Copyright = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 2:
parameters.FullName = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 3:
parameters.FamilyName = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 4:
parameters.Weight = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 1201:
parameters.isFixedPitch = !!(entry.values[0]);
break;
case 1202:
parameters.ItalicAngle = entry.values[0];
break;
case 1203:
parameters.UnderlinePosition = entry.values[0];
break;
case 1204:
parameters.UnderlineThickness = entry.values[0];
break;
case 1205:
parameters.PaintType = entry.values[0];
break;
case 1206:
parameters.CharstringType = entry.values[0];
break;
case 1207:
parameters.FontMatrix = entry.values;
break;
case 13:
parameters.UniqueID = entry.values[0];
break;
case 5:
parameters.FontBBox = entry.values;
break;
case 1208:
parameters.StrokeWidth = entry.values[0];
break;
case 14:
parameters.XUID = entry.values;
break;
case 15:
parameters.charset = entry.values[0];
break;
case 16:
parameters.Encoding = entry.values[0];
break;
case 17:
parameters.CharStrings = entry.values[0];
break;
case 18:
parameters.Private = {
size: entry.values[0],
offset: entry.values[1]
};
break;
case 1220:
parameters.SyntheticBase = entry.values[0];
break;
case 1221:
parameters.PostScript = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 1222:
parameters.BaseFontName = (entry.values[0] <= 390) ? StandardStrings[entry.values[0]] : stringIndex.data[entry.values[0] - 391];
break;
case 1223:
parameters.BaseFontBlend = entry.values;
break;
}
}
return new CFFTopDICT(parameters);
}
}
class CFF extends FontTable {
constructor(parameters = {}) {
super();
this.dicts = parameters.dicts || [];
}
static get tag() {
return 0x43464620;
}
static fromStream(stream) {
const headerBlock = stream.getBlock(4);
const header = {
major: headerBlock[0],
minor: headerBlock[1],
hdrSize: headerBlock[2],
offSize: headerBlock[3]
};
stream.getBlock(header.hdrSize - 4);
INDEX.fromStream(stream);
const topDICTIndex = INDEX.fromStream(stream);
const stringIndex = StringIndex.fromStream(stream);
INDEX.fromStream(stream);
const dicts = [];
for (const data of topDICTIndex.data) {
const dict = CFFTopDICT.fromBuffer(data, stringIndex);
if (dict.Private) {
const buffer = stream.stream.buffer.slice(dict.Private.offset, dict.Private.offset + dict.Private.size);
dict.PrivateDICT = CFFPrivateDICT.fromBuffer(buffer);
}
if ("CharStrings" in dict) {
dict.CharStringsINDEX = INDEX.fromStream(new SeqStream({ stream: stream.stream.slice(dict.CharStrings) }));
dict.CharsetParsed = CFFCharset.fromStream(new SeqStream({ stream: stream.stream.slice(dict.charset) }), dict.CharStringsINDEX.data.length, stringIndex);
switch (dict.Encoding) {
case 0:
break;
case 1:
break;
case 2:
dict.EncodingParsed = CFFEncoding.fromStream(new SeqStream({ stream: stream.stream.slice(dict.Encoding) }));
break;
}
}
dicts.push(dict);
}
return new CFF({ dicts });
}
}
class CFF2CharstringINDEX extends INDEX {
constructor(parameters = {}) {
super(parameters);
}
static fromStream(stream) {
const index = INDEX.fromStream(stream, 2);
const data = CFF2CharstringINDEX.decode(index.data);
return new CFF2CharstringINDEX({ data });
}
static decode(data2) {
const res = [];
let stack = [];
for (const data of data2) {
const view = new DataView(data);
for (let i = 0; i < view.byteLength; i++) {
let code = view.getUint8(i);
switch (true) {
case (code === 1):
{
res.push({
operator: "hstem",
operands: stack
});
stack = [];
}
break;
case (code === 3):
{
res.push({
operator: "vstem",
operands: stack
});
stack = [];
}
break;
case (code === 4):
{
res.push({
operator: "vmoveto",
operands: stack
});
stack = [];
}
break;
case (code === 5):
{
res.push({
operator: "rlineto",
operands: stack
});
stack = [];
}
break;
case (code === 6):
{
res.push({
operator: "hlineto",
operands: stack
});
stack = [];
}
break;
case (code === 7):
{
res.push({
operator: "vlineto",
operands: stack
});
stack = [];
}
break;
case (code === 8):
{
res.push({
operator: "rrcurveto",
operands: stack
});
stack = [];
}
break;
case (code === 10):
{
res.push({
operator: "callsubr",
operands: stack
});
stack = [];
}
break;
case (code === 12):
{
i++;
code = view.getUint8(i);
switch (true) {
case ((code >= 0) && (code <= 33)):
break;
case (code === 34):
{
res.push({
operator: "hflex",
operands: stack
});
stack = [];
}
break;
case (code === 35):
{
res.push({
operator: "flex",
operands: stack
});
stack = [];
}
break;
case (code === 36):
{
res.push({
operator: "hflex1",
operands: stack
});
stack = [];
}
break;
case (code === 37):
{
res.push({
operator: "flex1",
operands: stack
});
stack = [];
}
break;
}
}
break;
case (code === 15):
{
res.push({
operator: "vsindex",
operands: stack
});
stack = [];
}
break;
case (code === 16):
{
res.push({
operator: "blend",
operands: stack
});
stack = [];
}
break;
case (code === 18):
{
res.push({
operator: "hstemhm",
operands: stack
});
stack = [];
}
break;
case (code === 19):
{
res.push({
operator: "hintmask",
operands: stack
});
stack = [];
}
break;
case (code === 20):
{
res.push({
operator: "cntrmask",
operands: stack
});
stack = [];
}
break;
case (code === 21):
{
res.push({
operator: "rmoveto",
operands: stack
});
stack = [];
}
break;
case (code === 22):
{
res.push({
operator: "hmoveto",
operands: stack
});
stack = [];
}
break;
case (code === 23):
{
res.push({
operator: "vstemhm",
operands: stack
});
stack = [];
}
break;
case (code === 24):
{
res.push({
operator: "rcurveline",
operands: stack
});
stack = [];
}
break;
case (code === 25):
{
res.push({
operator: "rlinecurve",
operands: stack
});
stack = [];
}
break;
case (code === 26):
{
res.push({
operator: "vvcurveto",
operands: stack
});
stack = [];
}
break;
case (code === 27):
{
res.push({
operator: "hhcurveto",
operands: stack
});
stack = [];
}
break;
case (code === 28):
{
i++;
const code2 = view.getUint8(i);
stack.push(((code << 24) | (code2 << 16)) >> 16);
}
break;
case (code === 29):
{
res.push({
operator: "callgsubr",
operands: stack
});
stack = [];
}
break;
case (code === 30):
{
res.push({
operator: "vhcurveto",
operands: stack
});
stack = [];
}
break;
case (code === 31):
{
res.push({
operator: "hvcurveto",
operands: stack
});
stack = [];
}
break;
case ((code >= 32) && (code <= 246)):
stack.push(code - 139);
break;
case ((code >= 247) && (code <= 250)):
{
i++;
const code2 = view.getUint8(i);
stack.push((code - 247) * 256 + code2 + 108);
}
break;
case ((code >= 251) && (code <= 254)):
{
i++;
const code2 = view.getUint8(i);
stack.push(-((code - 251) * 256) - code2 - 108);
}
break;
case (code === 255):
{
i++;
code = view.getInt32(i, false);
stack.push(code / 65536);
i += 4;
}
break;
}
}
}
return res;
}
}
class CFF2FDSelect extends BaseClass {
constructor(parameters = {}) {
super();
this.format = parameters.format || 0;
this.fds = parameters.fds || new Uint8Array();
}
static fromStream(stream, nGlyphs = 0) {
const parameters = {};
parameters.fds = new Uint8Array();
parameters.format = (stream.getBlock(1))[0];
switch (parameters.format) {
case 0:
parameters.fds = stream.getBlock(nGlyphs);
break;
case 3:
{
const nRanges = stream.getUint16();
const range3 = [];
for (let i = 0; i < nRanges; i++) {
const range = {
first: stream.getUint16(),
fd: (stream.getBlock(1))[0],
};
range3.push(range);
}
const sentinel = stream.getUint16();
for (let i = 0; i < (range3.length - 1); i++) {
for (let j = range3[i].first; j < range3[i + 1].first; j++)
parameters.fds[j] = range3[i].fd;
}
for (let j = range3[range3.length - 1].first; j < sentinel; j++)
parameters.fds[j] = range3[range3.length - 1].fd;
}
break;
case 4:
{
const nRanges = stream.getUint32();
const range4 = [];
for (let i = 0; i < nRanges; i++) {
const range = {
first: stream.getUint32(),
fd: stream.getUint16(),
};
range4.push(range);
}
const sentinel = stream.getUint32();
for (let i = 0; i < (range4.length - 1); i++) {
for (let j = range4[i].first; j < range4[i + 1].first; j++)
parameters.fds[j] = range4[i].fd;
}
for (let j = range4[range4.length - 1].first; j < sentinel; j++)
parameters.fds[j] = range4[range4.length - 1].fd;
}
break;
}
return new CFF2FDSelect(parameters);
}
}
class CFF2PrivateDICT extends DICT {
constructo