376 lines
12 KiB
Plaintext
376 lines
12 KiB
Plaintext
// opentype.js
|
|
// https://github.com/opentypejs/opentype.js
|
|
// (c) 2015 Frederik De Bleser
|
|
// opentype.js may be freely distributed under the MIT license.
|
|
|
|
/* global DataView, Uint8Array */
|
|
|
|
import { inflateSync } from 'fflate';
|
|
import Font from './font';
|
|
import Glyph from './glyph';
|
|
import { CmapEncoding, addGlyphNames } from './encoding';
|
|
import parse from './parse';
|
|
import Path from './path';
|
|
import cmap from './tables/cmap';
|
|
import cff from './tables/cff';
|
|
import fvar from './tables/fvar';
|
|
import glyf from './tables/glyf';
|
|
import gdef from './tables/gdef';
|
|
import gpos from './tables/gpos';
|
|
import gsub from './tables/gsub';
|
|
import head from './tables/head';
|
|
import hhea from './tables/hhea';
|
|
import hmtx from './tables/hmtx';
|
|
import kern from './tables/kern';
|
|
import ltag from './tables/ltag';
|
|
import loca from './tables/loca';
|
|
import maxp from './tables/maxp';
|
|
import os2 from './tables/os2';
|
|
import post from './tables/post';
|
|
import meta from './tables/meta';
|
|
|
|
/**
|
|
* The opentype library.
|
|
* @namespace opentype
|
|
*/
|
|
|
|
// Table Directory Entries //////////////////////////////////////////////
|
|
/**
|
|
* Parses OpenType table entries.
|
|
* @param {DataView}
|
|
* @param {Number}
|
|
* @return {Object[]}
|
|
*/
|
|
function parseOpenTypeTableEntries(data, numTables) {
|
|
const tableEntries = [];
|
|
let p = 12;
|
|
for (let i = 0; i < numTables; i += 1) {
|
|
const tag = parse.getTag(data, p);
|
|
const checksum = parse.getULong(data, p + 4);
|
|
const offset = parse.getULong(data, p + 8);
|
|
const length = parse.getULong(data, p + 12);
|
|
tableEntries.push({
|
|
tag: tag,
|
|
checksum: checksum,
|
|
offset: offset,
|
|
length: length,
|
|
compression: false,
|
|
});
|
|
p += 16;
|
|
}
|
|
|
|
return tableEntries;
|
|
}
|
|
|
|
/**
|
|
* Parses WOFF table entries.
|
|
* @param {DataView}
|
|
* @param {Number}
|
|
* @return {Object[]}
|
|
*/
|
|
function parseWOFFTableEntries(data, numTables) {
|
|
const tableEntries = [];
|
|
let p = 44; // offset to the first table directory entry.
|
|
for (let i = 0; i < numTables; i += 1) {
|
|
const tag = parse.getTag(data, p);
|
|
const offset = parse.getULong(data, p + 4);
|
|
const compLength = parse.getULong(data, p + 8);
|
|
const origLength = parse.getULong(data, p + 12);
|
|
let compression;
|
|
if (compLength < origLength) {
|
|
compression = 'WOFF';
|
|
} else {
|
|
compression = false;
|
|
}
|
|
|
|
tableEntries.push({
|
|
tag: tag,
|
|
offset: offset,
|
|
compression: compression,
|
|
compressedLength: compLength,
|
|
length: origLength,
|
|
});
|
|
p += 20;
|
|
}
|
|
|
|
return tableEntries;
|
|
}
|
|
|
|
/**
|
|
* @typedef TableData
|
|
* @type Object
|
|
* @property {DataView} data - The DataView
|
|
* @property {number} offset - The data offset.
|
|
*/
|
|
|
|
/**
|
|
* @param {DataView}
|
|
* @param {Object}
|
|
* @return {TableData}
|
|
*/
|
|
function uncompressTable(data, tableEntry) {
|
|
if (tableEntry.compression === 'WOFF') {
|
|
const inBuffer = new Uint8Array(
|
|
data.buffer,
|
|
tableEntry.offset + 2,
|
|
tableEntry.compressedLength - 2
|
|
);
|
|
const outBuffer = new Uint8Array(tableEntry.length);
|
|
inflateSync(inBuffer, outBuffer);
|
|
if (outBuffer.byteLength !== tableEntry.length) {
|
|
throw new Error(
|
|
'Decompression error: ' +
|
|
tableEntry.tag +
|
|
` decompressed length doesn't match recorded length`
|
|
);
|
|
}
|
|
|
|
const view = new DataView(outBuffer.buffer, 0);
|
|
return { data: view, offset: 0 };
|
|
} else {
|
|
return { data: data, offset: tableEntry.offset };
|
|
}
|
|
}
|
|
|
|
// Public API ///////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Parse the OpenType file data (as an ArrayBuffer) and return a Font object.
|
|
* Throws an error if the font could not be parsed.
|
|
* @param {ArrayBuffer}
|
|
* @param {Object} opt - options for parsing
|
|
* @return {opentype.Font}
|
|
*/
|
|
function parseBuffer(buffer, opt) {
|
|
opt = opt === undefined || opt === null ? {} : opt;
|
|
|
|
let indexToLocFormat;
|
|
|
|
// Since the constructor can also be called to create new fonts from scratch, we indicate this
|
|
// should be an empty font that we'll fill with our own data.
|
|
const font = new Font({ empty: true });
|
|
|
|
// OpenType fonts use big endian byte ordering.
|
|
// We can't rely on typed array view types, because they operate with the endianness of the host computer.
|
|
// Instead we use DataViews where we can specify endianness.
|
|
const data = new DataView(buffer, 0);
|
|
let numTables;
|
|
let tableEntries = [];
|
|
const signature = parse.getTag(data, 0);
|
|
if (
|
|
signature === String.fromCharCode(0, 1, 0, 0) ||
|
|
signature === 'true' ||
|
|
signature === 'typ1'
|
|
) {
|
|
font.outlinesFormat = 'truetype';
|
|
numTables = parse.getUShort(data, 4);
|
|
tableEntries = parseOpenTypeTableEntries(data, numTables);
|
|
} else if (signature === 'OTTO') {
|
|
font.outlinesFormat = 'cff';
|
|
numTables = parse.getUShort(data, 4);
|
|
tableEntries = parseOpenTypeTableEntries(data, numTables);
|
|
} else if (signature === 'wOFF') {
|
|
const flavor = parse.getTag(data, 4);
|
|
if (flavor === String.fromCharCode(0, 1, 0, 0)) {
|
|
font.outlinesFormat = 'truetype';
|
|
} else if (flavor === 'OTTO') {
|
|
font.outlinesFormat = 'cff';
|
|
} else {
|
|
throw new Error('Unsupported OpenType flavor ' + signature);
|
|
}
|
|
|
|
numTables = parse.getUShort(data, 12);
|
|
tableEntries = parseWOFFTableEntries(data, numTables);
|
|
} else {
|
|
throw new Error('Unsupported OpenType signature ' + signature);
|
|
}
|
|
|
|
let cffTableEntry;
|
|
let fvarTableEntry;
|
|
let glyfTableEntry;
|
|
let gdefTableEntry;
|
|
let gposTableEntry;
|
|
let gsubTableEntry;
|
|
let hmtxTableEntry;
|
|
let kernTableEntry;
|
|
let locaTableEntry;
|
|
let metaTableEntry;
|
|
let p;
|
|
|
|
for (let i = 0; i < numTables; i += 1) {
|
|
const tableEntry = tableEntries[i];
|
|
let table;
|
|
switch (tableEntry.tag) {
|
|
case 'cmap':
|
|
table = uncompressTable(data, tableEntry);
|
|
font.tables.cmap = cmap.parse(table.data, table.offset);
|
|
font.encoding = new CmapEncoding(font.tables.cmap);
|
|
break;
|
|
case 'cvt ':
|
|
table = uncompressTable(data, tableEntry);
|
|
p = new parse.Parser(table.data, table.offset);
|
|
font.tables.cvt = p.parseShortList(tableEntry.length / 2);
|
|
break;
|
|
case 'fvar':
|
|
fvarTableEntry = tableEntry;
|
|
break;
|
|
case 'fpgm':
|
|
table = uncompressTable(data, tableEntry);
|
|
p = new parse.Parser(table.data, table.offset);
|
|
font.tables.fpgm = p.parseByteList(tableEntry.length);
|
|
break;
|
|
case 'head':
|
|
table = uncompressTable(data, tableEntry);
|
|
font.tables.head = head.parse(table.data, table.offset);
|
|
font.unitsPerEm = font.tables.head.unitsPerEm;
|
|
indexToLocFormat = font.tables.head.indexToLocFormat;
|
|
break;
|
|
case 'hhea':
|
|
table = uncompressTable(data, tableEntry);
|
|
font.tables.hhea = hhea.parse(table.data, table.offset);
|
|
font.ascender = font.tables.hhea.ascender;
|
|
font.descender = font.tables.hhea.descender;
|
|
font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics;
|
|
break;
|
|
case 'hmtx':
|
|
hmtxTableEntry = tableEntry;
|
|
break;
|
|
case 'ltag':
|
|
table = uncompressTable(data, tableEntry);
|
|
ltagTable = ltag.parse(table.data, table.offset);
|
|
break;
|
|
case 'maxp':
|
|
table = uncompressTable(data, tableEntry);
|
|
font.tables.maxp = maxp.parse(table.data, table.offset);
|
|
font.numGlyphs = font.tables.maxp.numGlyphs;
|
|
break;
|
|
case 'OS/2':
|
|
table = uncompressTable(data, tableEntry);
|
|
font.tables.os2 = os2.parse(table.data, table.offset);
|
|
break;
|
|
case 'post':
|
|
table = uncompressTable(data, tableEntry);
|
|
font.tables.post = post.parse(table.data, table.offset);
|
|
break;
|
|
case 'prep':
|
|
table = uncompressTable(data, tableEntry);
|
|
p = new parse.Parser(table.data, table.offset);
|
|
font.tables.prep = p.parseByteList(tableEntry.length);
|
|
break;
|
|
case 'glyf':
|
|
glyfTableEntry = tableEntry;
|
|
break;
|
|
case 'loca':
|
|
locaTableEntry = tableEntry;
|
|
break;
|
|
case 'CFF ':
|
|
cffTableEntry = tableEntry;
|
|
break;
|
|
case 'kern':
|
|
kernTableEntry = tableEntry;
|
|
break;
|
|
case 'GDEF':
|
|
gdefTableEntry = tableEntry;
|
|
break;
|
|
case 'GPOS':
|
|
gposTableEntry = tableEntry;
|
|
break;
|
|
case 'GSUB':
|
|
gsubTableEntry = tableEntry;
|
|
break;
|
|
case 'meta':
|
|
metaTableEntry = tableEntry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (glyfTableEntry && locaTableEntry) {
|
|
const shortVersion = indexToLocFormat === 0;
|
|
const locaTable = uncompressTable(data, locaTableEntry);
|
|
const locaOffsets = loca.parse(
|
|
locaTable.data,
|
|
locaTable.offset,
|
|
font.numGlyphs,
|
|
shortVersion
|
|
);
|
|
const glyfTable = uncompressTable(data, glyfTableEntry);
|
|
font.glyphs = glyf.parse(
|
|
glyfTable.data,
|
|
glyfTable.offset,
|
|
locaOffsets,
|
|
font,
|
|
opt
|
|
);
|
|
} else if (cffTableEntry) {
|
|
const cffTable = uncompressTable(data, cffTableEntry);
|
|
cff.parse(cffTable.data, cffTable.offset, font, opt);
|
|
} else {
|
|
throw new Error(`Font doesn't contain TrueType or CFF outlines.`);
|
|
}
|
|
|
|
const hmtxTable = uncompressTable(data, hmtxTableEntry);
|
|
hmtx.parse(
|
|
font,
|
|
hmtxTable.data,
|
|
hmtxTable.offset,
|
|
font.numberOfHMetrics,
|
|
font.numGlyphs,
|
|
font.glyphs,
|
|
opt
|
|
);
|
|
addGlyphNames(font, opt);
|
|
|
|
if (kernTableEntry) {
|
|
const kernTable = uncompressTable(data, kernTableEntry);
|
|
font.kerningPairs = kern.parse(kernTable.data, kernTable.offset);
|
|
} else {
|
|
font.kerningPairs = {};
|
|
}
|
|
|
|
if (gdefTableEntry) {
|
|
const gdefTable = uncompressTable(data, gdefTableEntry);
|
|
font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset);
|
|
}
|
|
|
|
if (gposTableEntry) {
|
|
const gposTable = uncompressTable(data, gposTableEntry);
|
|
font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset);
|
|
font.position.init();
|
|
}
|
|
|
|
if (gsubTableEntry) {
|
|
const gsubTable = uncompressTable(data, gsubTableEntry);
|
|
font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset);
|
|
}
|
|
|
|
if (fvarTableEntry) {
|
|
const fvarTable = uncompressTable(data, fvarTableEntry);
|
|
font.tables.fvar = fvar.parse(
|
|
fvarTable.data,
|
|
fvarTable.offset,
|
|
font.names
|
|
);
|
|
}
|
|
|
|
if (metaTableEntry) {
|
|
const metaTable = uncompressTable(data, metaTableEntry);
|
|
font.tables.meta = meta.parse(metaTable.data, metaTable.offset);
|
|
font.metas = font.tables.meta;
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
function load() {}
|
|
function loadSync() {}
|
|
|
|
export {
|
|
Font,
|
|
Glyph,
|
|
Path,
|
|
parse as _parse,
|
|
parseBuffer as parse,
|
|
load,
|
|
loadSync,
|
|
};
|