Current File : /home/tradevaly/www/node_modules/fontkit/src/WOFF2Font.js |
import r from 'restructure';
import brotli from 'brotli/decompress';
import TTFFont from './TTFFont';
import TTFGlyph, { Point } from './glyph/TTFGlyph';
import WOFF2Glyph from './glyph/WOFF2Glyph';
import WOFF2Directory from './tables/WOFF2Directory';
/**
* Subclass of TTFFont that represents a TTF/OTF font compressed by WOFF2
* See spec here: http://www.w3.org/TR/WOFF2/
*/
export default class WOFF2Font extends TTFFont {
static probe(buffer) {
return buffer.toString('ascii', 0, 4) === 'wOF2';
}
_decodeDirectory() {
this.directory = WOFF2Directory.decode(this.stream);
this._dataPos = this.stream.pos;
}
_decompress() {
// decompress data and setup table offsets if we haven't already
if (!this._decompressed) {
this.stream.pos = this._dataPos;
let buffer = this.stream.readBuffer(this.directory.totalCompressedSize);
let decompressedSize = 0;
for (let tag in this.directory.tables) {
let entry = this.directory.tables[tag];
entry.offset = decompressedSize;
decompressedSize += (entry.transformLength != null) ? entry.transformLength : entry.length;
}
let decompressed = brotli(buffer, decompressedSize);
if (!decompressed) {
throw new Error('Error decoding compressed data in WOFF2');
}
this.stream = new r.DecodeStream(new Buffer(decompressed));
this._decompressed = true;
}
}
_decodeTable(table) {
this._decompress();
return super._decodeTable(table);
}
// Override this method to get a glyph and return our
// custom subclass if there is a glyf table.
_getBaseGlyph(glyph, characters = []) {
if (!this._glyphs[glyph]) {
if (this.directory.tables.glyf && this.directory.tables.glyf.transformed) {
if (!this._transformedGlyphs) { this._transformGlyfTable(); }
return this._glyphs[glyph] = new WOFF2Glyph(glyph, characters, this);
} else {
return super._getBaseGlyph(glyph, characters);
}
}
}
_transformGlyfTable() {
this._decompress();
this.stream.pos = this.directory.tables.glyf.offset;
let table = GlyfTable.decode(this.stream);
let glyphs = [];
for (let index = 0; index < table.numGlyphs; index++) {
let glyph = {};
let nContours = table.nContours.readInt16BE();
glyph.numberOfContours = nContours;
if (nContours > 0) { // simple glyph
let nPoints = [];
let totalPoints = 0;
for (let i = 0; i < nContours; i++) {
let r = read255UInt16(table.nPoints);
totalPoints += r;
nPoints.push(totalPoints);
}
glyph.points = decodeTriplet(table.flags, table.glyphs, totalPoints);
for (let i = 0; i < nContours; i++) {
glyph.points[nPoints[i] - 1].endContour = true;
}
var instructionSize = read255UInt16(table.glyphs);
} else if (nContours < 0) { // composite glyph
let haveInstructions = TTFGlyph.prototype._decodeComposite.call({ _font: this }, glyph, table.composites);
if (haveInstructions) {
var instructionSize = read255UInt16(table.glyphs);
}
}
glyphs.push(glyph);
}
this._transformedGlyphs = glyphs;
}
}
// Special class that accepts a length and returns a sub-stream for that data
class Substream {
constructor(length) {
this.length = length;
this._buf = new r.Buffer(length);
}
decode(stream, parent) {
return new r.DecodeStream(this._buf.decode(stream, parent));
}
}
// This struct represents the entire glyf table
let GlyfTable = new r.Struct({
version: r.uint32,
numGlyphs: r.uint16,
indexFormat: r.uint16,
nContourStreamSize: r.uint32,
nPointsStreamSize: r.uint32,
flagStreamSize: r.uint32,
glyphStreamSize: r.uint32,
compositeStreamSize: r.uint32,
bboxStreamSize: r.uint32,
instructionStreamSize: r.uint32,
nContours: new Substream('nContourStreamSize'),
nPoints: new Substream('nPointsStreamSize'),
flags: new Substream('flagStreamSize'),
glyphs: new Substream('glyphStreamSize'),
composites: new Substream('compositeStreamSize'),
bboxes: new Substream('bboxStreamSize'),
instructions: new Substream('instructionStreamSize')
});
const WORD_CODE = 253;
const ONE_MORE_BYTE_CODE2 = 254;
const ONE_MORE_BYTE_CODE1 = 255;
const LOWEST_U_CODE = 253;
function read255UInt16(stream) {
let code = stream.readUInt8();
if (code === WORD_CODE) {
return stream.readUInt16BE();
}
if (code === ONE_MORE_BYTE_CODE1) {
return stream.readUInt8() + LOWEST_U_CODE;
}
if (code === ONE_MORE_BYTE_CODE2) {
return stream.readUInt8() + LOWEST_U_CODE * 2;
}
return code;
}
function withSign(flag, baseval) {
return flag & 1 ? baseval : -baseval;
}
function decodeTriplet(flags, glyphs, nPoints) {
let y;
let x = y = 0;
let res = [];
for (let i = 0; i < nPoints; i++) {
let dx = 0, dy = 0;
let flag = flags.readUInt8();
let onCurve = !(flag >> 7);
flag &= 0x7f;
if (flag < 10) {
dx = 0;
dy = withSign(flag, ((flag & 14) << 7) + glyphs.readUInt8());
} else if (flag < 20) {
dx = withSign(flag, (((flag - 10) & 14) << 7) + glyphs.readUInt8());
dy = 0;
} else if (flag < 84) {
var b0 = flag - 20;
var b1 = glyphs.readUInt8();
dx = withSign(flag, 1 + (b0 & 0x30) + (b1 >> 4));
dy = withSign(flag >> 1, 1 + ((b0 & 0x0c) << 2) + (b1 & 0x0f));
} else if (flag < 120) {
var b0 = flag - 84;
dx = withSign(flag, 1 + ((b0 / 12) << 8) + glyphs.readUInt8());
dy = withSign(flag >> 1, 1 + (((b0 % 12) >> 2) << 8) + glyphs.readUInt8());
} else if (flag < 124) {
var b1 = glyphs.readUInt8();
let b2 = glyphs.readUInt8();
dx = withSign(flag, (b1 << 4) + (b2 >> 4));
dy = withSign(flag >> 1, ((b2 & 0x0f) << 8) + glyphs.readUInt8());
} else {
dx = withSign(flag, glyphs.readUInt16BE());
dy = withSign(flag >> 1, glyphs.readUInt16BE());
}
x += dx;
y += dy;
res.push(new Point(onCurve, false, x, y));
}
return res;
}