lib-font
Version:
A JS based OpenType font inspector
215 lines (198 loc) • 5.38 kB
JavaScript
import { SimpleTable } from "./tables/simple-table.js";
import lazy from "../lazy.js";
const brotliDecode = globalThis.unbrotli;
let nativeBrotliDecode = undefined;
if (!brotliDecode) {
import("zlib").then((zlib) => {
nativeBrotliDecode = (buffer) => zlib.brotliDecompressSync(buffer);
});
}
/**
* The WOFF2 header
*
* See https://www.w3.org/TR/WOFF2 for WOFF2 information
* See https://docs.microsoft.com/en-us/typography/opentype/spec/overview for font information
*/
class WOFF2 extends SimpleTable {
constructor(font, dataview, createTable) {
const { p } = super({ offset: 0, length: 48 }, dataview, `woff2`);
this.signature = p.tag;
this.flavor = p.uint32;
this.length = p.uint32;
this.numTables = p.uint16;
p.uint16; // why woff2 even has any reserved bytes is a complete mystery. But it does.
this.totalSfntSize = p.uint32;
this.totalCompressedSize = p.uint32;
this.majorVersion = p.uint16;
this.minorVersion = p.uint16;
this.metaOffset = p.uint32;
this.metaLength = p.uint32;
this.metaOrigLength = p.uint32;
this.privOffset = p.uint32;
this.privLength = p.uint32;
p.verifyLength();
// parse the dictionary
this.directory = [...new Array(this.numTables)].map(
(_) => new Woff2TableDirectoryEntry(p)
);
let dictOffset = p.currentPosition; // = start of CompressedFontData block
// compute table byte offsets in the decompressed data
this.directory[0].offset = 0;
this.directory.forEach((e, i) => {
let next = this.directory[i + 1];
if (next) {
next.offset =
e.offset +
(e.transformLength !== undefined ? e.transformLength : e.origLength);
}
});
// then decompress the original data and lazy-bind
let decoded;
let buffer = dataview.buffer.slice(dictOffset);
if (brotliDecode) {
decoded = brotliDecode(new Uint8Array(buffer));
} else if (nativeBrotliDecode) {
decoded = new Uint8Array(nativeBrotliDecode(buffer));
} else {
const msg = `no brotli decoder available to decode WOFF2 font`;
if (font.onerror) font.onerror(msg);
throw new Error(msg);
}
buildWoff2LazyLookups(this, decoded, createTable);
}
}
/**
* WOFF2 Table Directory Entry
*/
class Woff2TableDirectoryEntry {
constructor(p) {
this.flags = p.uint8;
const tagNumber = (this.tagNumber = this.flags & 63);
if (tagNumber === 63) {
this.tag = p.tag;
} else {
this.tag = getWOFF2Tag(tagNumber);
}
/*
"Bits 6 and 7 indicate the preprocessing transformation version number (0-3)
that was applied to each table. For all tables in a font, except for 'glyf'
and 'loca' tables, transformation version 0 indicates the null transform
where the original table data is passed directly to the Brotli compressor
for inclusion in the compressed data stream. For 'glyf' and 'loca' tables,
transformation version 3 indicates the null transform"
*/
const transformVersion = (this.transformVersion = (this.flags & 192) >> 6);
let hasTransforms = transformVersion !== 0;
if (this.tag === `glyf` || this.tag === `loca`) {
hasTransforms = this.transformVersion !== 3;
}
this.origLength = p.uint128;
if (hasTransforms) {
this.transformLength = p.uint128;
}
}
}
/**
* Build late-evaluating properties for each table in a
* woff2 font, so that accessing a table via the
* font.opentype.tables.tableName property kicks off
* a table parse on first access.
*
* @param {*} woff2 the woff2 font object
* @param {decoded} the original (decompressed) SFNT data
* @param {createTable} the opentype table builder function
*/
function buildWoff2LazyLookups(woff2, decoded, createTable) {
woff2.tables = {};
woff2.directory.forEach((entry) => {
lazy(woff2.tables, entry.tag.trim(), () => {
const start = entry.offset;
const end =
start +
(entry.transformLength ? entry.transformLength : entry.origLength);
const data = new DataView(decoded.slice(start, end).buffer);
try {
return createTable(
woff2.tables,
{ tag: entry.tag, offset: 0, length: entry.origLength },
data
);
} catch (e) {
console.error(e);
}
});
});
}
/**
* WOFF2 uses a numbered tag registry, such that only unknown tables require a 4 byte tag
* in the WOFF directory entry struct. Everything else uses a uint8. Nice and tidy.
* @param {*} flag
*/
function getWOFF2Tag(flag) {
return [
`cmap`,
`head`,
`hhea`,
`hmtx`,
`maxp`,
`name`,
`OS/2`,
`post`,
`cvt `,
`fpgm`,
`glyf`,
`loca`,
`prep`,
`CFF `,
`VORG`,
`EBDT`,
`EBLC`,
`gasp`,
`hdmx`,
`kern`,
`LTSH`,
`PCLT`,
`VDMX`,
`vhea`,
`vmtx`,
`BASE`,
`GDEF`,
`GPOS`,
`GSUB`,
`EBSC`,
`JSTF`,
`MATH`,
`CBDT`,
`CBLC`,
`COLR`,
`CPAL`,
`SVG `,
`sbix`,
`acnt`,
`avar`,
`bdat`,
`bloc`,
`bsln`,
`cvar`,
`fdsc`,
`feat`,
`fmtx`,
`fvar`,
`gvar`,
`hsty`,
`just`,
`lcar`,
`mort`,
`morx`,
`opbd`,
`prop`,
`trak`,
`Zapf`,
`Silf`,
`Glat`,
`Gloc`,
`Feat`,
`Sill`,
][flag & 63];
}
export { WOFF2 };