lib-font
Version:
A JS based OpenType font inspector
197 lines (171 loc) • 4.57 kB
JavaScript
const startDate = new Date(`1904-01-01T00:00:00+0000`).getTime();
/**
* Convert an array of uint8 char into a proper string.
*
* @param {uint8[]} data
*/
function asText(data) {
return Array.from(data)
.map((v) => String.fromCharCode(v))
.join(``);
}
/**
* A data parser for table data, with auto-advancing pointer.
*/
class Parser {
constructor(dict, dataview, name) {
this.name = (name || dict.tag || ``).trim();
this.length = dict.length;
this.start = dict.offset;
this.offset = 0;
this.data = dataview;
[
`getInt8`,
`getUint8`,
`getInt16`,
`getUint16`,
`getInt32`,
`getUint32`,
`getBigInt64`,
`getBigUint64`,
].forEach((name) => {
let fn = name.replace(/get(Big)?/, "").toLowerCase();
let increment = parseInt(name.replace(/[^\d]/g, "")) / 8;
Object.defineProperty(this, fn, {
get: () => this.getValue(name, increment),
});
});
}
get currentPosition() {
return this.start + this.offset;
}
set currentPosition(position) {
this.start = position;
this.offset = 0;
}
skip(n = 0, bits = 8) {
this.offset += (n * bits) / 8;
}
getValue(type, increment) {
let pos = this.start + this.offset;
this.offset += increment;
try {
return this.data[type](pos);
} catch (e) {
console.error(`parser`, type, increment, this);
console.error(`parser`, this.start, this.offset);
throw e;
}
}
flags(n) {
if (n === 8 || n === 16 || n === 32 || n === 64) {
return this[`uint${n}`]
.toString(2)
.padStart(n, 0)
.split(``)
.map((v) => v === "1");
}
console.error(
`Error parsing flags: flag types can only be 1, 2, 4, or 8 bytes long`
);
console.trace();
}
get tag() {
const t = this.uint32;
return asText([(t >> 24) & 255, (t >> 16) & 255, (t >> 8) & 255, t & 255]);
}
get fixed() {
let major = this.int16;
let minor = Math.round((1000 * this.uint16) / 65356);
return major + minor / 1000;
}
get legacyFixed() {
// Only used in the `maxp`, `post`, and `vhea` tables.
let major = this.uint16;
let minor = this.uint16.toString(16).padStart(4, 0);
return parseFloat(`${major}.${minor}`);
}
get uint24() {
// Why does DataView not have a 24 bit value getters?
return (this.uint8 << 16) + (this.uint8 << 8) + this.uint8;
}
get uint128() {
// I have no idea why the variable uint128 was chosen over a
// fixed-width uint32, but it was, and so we need to decode it.
let value = 0;
for (let i = 0; i < 5; i++) {
let byte = this.uint8;
value = value * 128 + (byte & 127);
if (byte < 128) break;
}
return value;
}
get longdatetime() {
return new Date(startDate + 1000 * parseInt(this.int64.toString()));
}
// alias datatypes
get fword() {
return this.int16;
}
get ufword() {
return this.uint16;
}
get Offset16() {
return this.uint16;
}
get Offset32() {
return this.uint32;
}
// "that weird datatype"
get F2DOT14() {
const bits = p.uint16;
const integer = [0, 1, -2, -1][bits >> 14];
const fraction = bits & 0x3fff;
return integer + fraction / 16384;
}
verifyLength() {
if (this.offset != this.length) {
console.error(
`unexpected parsed table size (${this.offset}) for "${this.name}" (expected ${this.length})`
);
}
}
/**
* Read an entire data block.
*/
readBytes(n = 0, position = 0, bits = 8, signed = false) {
n = n || this.length;
if (n === 0) return [];
if (position) this.currentPosition = position;
const fn = `${signed ? `` : `u`}int${bits}`,
slice = [];
while (n--) slice.push(this[fn]);
return slice;
}
}
/**
* ... docs go here ...
*/
class ParsedData {
constructor(parser) {
const pGetter = { enumerable: false, get: () => parser };
Object.defineProperty(this, `parser`, pGetter);
const start = parser.currentPosition;
const startGetter = { enumerable: false, get: () => start };
Object.defineProperty(this, `start`, startGetter);
}
load(struct) {
Object.keys(struct).forEach((p) => {
let props = Object.getOwnPropertyDescriptor(struct, p);
if (props.get) {
this[p] = props.get.bind(this);
} else if (props.value !== undefined) {
this[p] = props.value;
}
});
if (this.parser.length) {
this.parser.verifyLength();
}
}
}
export { Parser, ParsedData };