Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions src/TTFFont.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,15 +518,28 @@ export default class TTFFont {
}
});

let stream = new r.DecodeStream(this.stream.buffer);
stream.pos = this._directoryPos;

let font = new TTFFont(stream, coords);
let font = this._createVariation(coords);
font._tables = this._tables;

return font;
}

/**
* Creates the font `getVariation` returns: this font, read again from the
* same data, at the given coordinates. A subclass whose data is not an
* sfnt at `_directoryPos` — a WOFF2, which decompresses it — overrides this.
*
* @private
*/
_createVariation(coords) {
let stream = new r.DecodeStream(this.stream.buffer);
stream.pos = this._directoryPos;

// The same class, so that a font in a container reads its directory,
// and its tables, the way the container holds them.
return new this.constructor(stream, coords);
}

@cache
get _variationProcessor() {
if (!this.fvar) {
Expand Down
69 changes: 67 additions & 2 deletions src/WOFF2Font.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,44 @@ export default class WOFF2Font extends TTFFont {
throw new Error('Error decoding compressed data in WOFF2');
}

// A variation reads the directory again (see _createVariation),
// so a font that has any keeps the file it came in.
if (this.directory.tables.fvar) {
this._file = this.stream;
}

this.stream = new r.DecodeStream(decompressed);
this._decompressed = true;
}
}

_createVariation(coords) {
// By now `this.stream` is the decompressed tables, with no directory in
// front of them. The directory is read from the file again, and the
// tables are not decompressed again: the variation reads this font's.
this._decompress();

let stream = new r.DecodeStream(this._file.buffer);
stream.pos = this._directoryPos;

let font = new this.constructor(stream, coords);
for (let tag in font.directory.tables) {
font.directory.tables[tag].offset = this.directory.tables[tag].offset;
}

font._file = this._file;
font.stream = new r.DecodeStream(this.stream.buffer);
font._decompressed = true;

// Nor are the glyphs decoded again: WOFF2Glyph moves a copy of each.
if (this.directory.tables.glyf && this.directory.tables.glyf.transformed) {
if (!this._transformedGlyphs) { this._transformGlyfTable(); }
font._transformedGlyphs = this._transformedGlyphs;
}

return font;
}

_decodeTable(table) {
this._decompress();
return super._decodeTable(table);
Expand All @@ -70,8 +103,12 @@ export default class WOFF2Font extends TTFFont {
let table = GlyfTable.decode(this.stream);
let glyphs = [];

// The bounding boxes stream starts with a bit for each glyph, set for
// the ones whose box is stored. The rest are computed from the points.
let bboxBitmap = table.bboxes.readBuffer(((table.numGlyphs + 31) >> 5) << 2);

for (let index = 0; index < table.numGlyphs; index++) {
let glyph = {};
let glyph = { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
let nContours = table.nContours.readInt16BE();
glyph.numberOfContours = nContours;

Expand All @@ -93,12 +130,23 @@ export default class WOFF2Font extends TTFFont {
var instructionSize = read255UInt16(table.glyphs);

} else if (nContours < 0) { // composite glyph
let haveInstructions = TTFGlyph.prototype._decodeComposite.call({ _font: this }, glyph, table.composites);
// Decoded as the file has it, whatever this font's coordinates:
// the glyphs are shared with its variations.
let haveInstructions = TTFGlyph.prototype._decodeComposite.call({ _font: {} }, glyph, table.composites);
if (haveInstructions) {
var instructionSize = read255UInt16(table.glyphs);
}
}

if (bboxBitmap[index >> 3] & (0x80 >> (index & 7))) {
glyph.xMin = table.bboxes.readInt16BE();
glyph.yMin = table.bboxes.readInt16BE();
glyph.xMax = table.bboxes.readInt16BE();
glyph.yMax = table.bboxes.readInt16BE();
} else if (nContours > 0) {
setBBox(glyph);
}

glyphs.push(glyph);
}

Expand Down Expand Up @@ -139,6 +187,23 @@ let GlyfTable = new r.Struct({
instructions: new Substream('instructionStreamSize')
});

// Computes the bounding box an encoder left out: that of a simple glyph's
// points, on and off the curve.
function setBBox(glyph) {
let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
for (let point of glyph.points) {
if (point.x < xMin) { xMin = point.x; }
if (point.x > xMax) { xMax = point.x; }
if (point.y < yMin) { yMin = point.y; }
if (point.y > yMax) { yMax = point.y; }
}

glyph.xMin = xMin;
glyph.yMin = yMin;
glyph.xMax = xMax;
glyph.yMax = yMax;
}

const WORD_CODE = 253;
const ONE_MORE_BYTE_CODE2 = 254;
const ONE_MORE_BYTE_CODE1 = 255;
Expand Down
24 changes: 19 additions & 5 deletions src/WOFFFont.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,32 @@ export default class WOFFFont extends TTFFont {
_getTableStream(tag) {
let table = this.directory.tables[tag];
if (table) {
this.stream.pos = table.offset;

if (table.compLength < table.length) {
this.stream.pos += 2; // skip deflate header
let outBuffer = new Uint8Array(table.length);
let buf = inflate(this.stream.readBuffer(table.compLength - 2), outBuffer);
// Inflated once, and kept: a glyph asks for `glyf`, and a variation
// for `gvar`, once for every glyph.
let inflated = this._inflated || (this._inflated = {});
let buf = inflated[tag];
if (!buf) {
this.stream.pos = table.offset + 2; // skip deflate header
let outBuffer = new Uint8Array(table.length);
buf = inflated[tag] = inflate(this.stream.readBuffer(table.compLength - 2), outBuffer);
}

return new r.DecodeStream(buf);
} else {
this.stream.pos = table.offset;
return this.stream;
}
}

return null;
}

_createVariation(coords) {
let font = super._createVariation(coords);

// The tables are the same bytes at any coordinates
font._inflated = this._inflated || (this._inflated = {});
return font;
}
}
20 changes: 10 additions & 10 deletions src/glyph/GlyphVariationProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ export default class GlyphVariationProcessor {
let offset = gvar.offsets[gid];
if (offset === gvar.offsets[gid + 1]) { return; }

// Read the gvar data for this glyph
let { stream } = this.font;
// Read the gvar data for this glyph, from the stream its table was
// decoded from, which the offsets are positions in. That is the font's
// own, unless the table is compressed by itself (WOFF).
let stream = this.font._getTableStream('gvar');
stream.pos = offset;
if (stream.pos >= stream.length) {
return;
Expand All @@ -85,7 +87,7 @@ export default class GlyphVariationProcessor {
if (tupleCount & TUPLES_SHARE_POINT_NUMBERS) {
var here = stream.pos;
stream.pos = offsetToData;
var sharedPoints = this.decodePoints();
var sharedPoints = this.decodePoints(stream);
offsetToData = stream.pos;
stream.pos = here;
}
Expand Down Expand Up @@ -134,15 +136,15 @@ export default class GlyphVariationProcessor {
stream.pos = offsetToData;

if (tupleIndex & PRIVATE_POINT_NUMBERS) {
var points = this.decodePoints();
var points = this.decodePoints(stream);
} else {
var points = sharedPoints;
}

// points.length = 0 means there are deltas for all points
let nPoints = points.length === 0 ? glyphPoints.length : points.length;
let xDeltas = this.decodeDeltas(nPoints);
let yDeltas = this.decodeDeltas(nPoints);
let xDeltas = this.decodeDeltas(stream, nPoints);
let yDeltas = this.decodeDeltas(stream, nPoints);

if (points.length === 0) { // all points
for (let i = 0; i < glyphPoints.length; i++) {
Expand Down Expand Up @@ -181,8 +183,7 @@ export default class GlyphVariationProcessor {
}
}

decodePoints() {
let stream = this.font.stream;
decodePoints(stream) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodePoints can be declared as a function in module scope

let count = stream.readUInt8();

if (count & POINTS_ARE_WORDS) {
Expand All @@ -206,8 +207,7 @@ export default class GlyphVariationProcessor {
return points;
}

decodeDeltas(count) {
let stream = this.font.stream;
decodeDeltas(stream, count) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeDeltas can be declared as a function in module scope

let i = 0;
let deltas = new Int16Array(count);

Expand Down
26 changes: 19 additions & 7 deletions src/glyph/TTFGlyph.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,7 @@ export default class TTFGlyph extends Glyph {
}

if (this._font._variationProcessor) {
let points = glyph.points.slice();
points.push(...this._getPhantomPoints(glyph));

this._font._variationProcessor.transformPoints(this.id, points);
glyph.phantomPoints = points.slice(-4);
this._applyVariation(glyph);
}

return;
Expand Down Expand Up @@ -224,6 +220,24 @@ export default class TTFGlyph extends Glyph {
}

if (this._font._variationProcessor) {
this._applyVariation(glyph);
}

return haveInstructions;
}

// Moves a decoded glyph to the font's variation coordinates: the points
// of a simple glyph, or the offsets of a composite glyph's components,
// and the phantom points its metrics are then measured from.
_applyVariation(glyph) {
if (glyph.numberOfContours > 0) {
let points = glyph.points.slice();
points.push(...this._getPhantomPoints(glyph));

this._font._variationProcessor.transformPoints(this.id, points);
glyph.phantomPoints = points.slice(-4);

} else if (glyph.numberOfContours < 0) {
let points = [];
for (let j = 0; j < glyph.components.length; j++) {
var component = glyph.components[j];
Expand All @@ -241,8 +255,6 @@ export default class TTFGlyph extends Glyph {
glyph.components[i].dy = point.y;
}
}

return haveInstructions;
}

_getPhantomPoints(glyph) {
Expand Down
28 changes: 26 additions & 2 deletions src/glyph/WOFF2Glyph.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import TTFGlyph from './TTFGlyph';
import BBox from './BBox';

/**
* Represents a TrueType glyph in the WOFF2 format, which compresses glyphs differently.
Expand All @@ -8,10 +9,33 @@ export default class WOFF2Glyph extends TTFGlyph {

_decode() {
// We have to decode in advance (in WOFF2Font), so just return the pre-decoded data.
return this._font._transformedGlyphs[this.id];
let glyph = this._font._transformedGlyphs[this.id];
if (!glyph || !this._font._variationProcessor || glyph.numberOfContours === 0) {
return glyph;
}

// A variation moves a copy of it: the decoded data is the font's, and
// that of every other variation of it.
let varied = Object.assign({}, glyph);
if (glyph.numberOfContours > 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glyph.numberOfContours > 0 is always true?

varied.points = glyph.points.map(point => point.copy());
} else {
varied.components = glyph.components.map(component => Object.assign({}, component));
}

this._applyVariation(varied);
return varied;
}

_getCBox() {
_getCBox(internal) {
// What a variation starts from is the box in the glyph's header, as it
// is for a TTFGlyph. The path's own would be the varied glyph's, and
// decoding that is what is asking.
if (internal && this._font._variationProcessor) {
let glyph = this._font._transformedGlyphs[this.id];
return Object.freeze(new BBox(glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax));
}

return this.path.bbox;
}
}
Binary file added test/data/Mada/Mada-VF.woff2
Binary file not shown.
Binary file added test/data/fonttest/AdobeVFPrototype-Subset.woff
Binary file not shown.
Binary file added test/data/fonttest/AdobeVFPrototype-Subset.woff2
Binary file not shown.
Binary file added test/data/fonttest/TestGVARFour.woff
Binary file not shown.
Binary file added test/data/fonttest/TestGVARFour.woff2
Binary file not shown.
Binary file not shown.
Binary file added test/data/fonttest/TestGVAROne.woff
Binary file not shown.
Binary file added test/data/fonttest/TestGVAROne.woff2
Binary file not shown.
Loading