opentype.js
Advanced tools
Comparing version 0.4.11 to 0.5.0
{ | ||
"name": "opentype.js", | ||
"version": "0.4.11", | ||
"version": "0.5.0", | ||
"main": "dist/opentype.js", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
{ | ||
"name": "opentype.js", | ||
"description": "OpenType font parser", | ||
"version": "0.4.11", | ||
"version": "0.5.0", | ||
"author": { | ||
@@ -47,3 +47,6 @@ "name": "Frederik De Bleser", | ||
"fs": false | ||
}, | ||
"dependencies": { | ||
"tiny-inflate": "^1.0.1" | ||
} | ||
} |
@@ -31,3 +31,3 @@ opentype.js | ||
* Support for composite glyphs (accented letters). | ||
* Support for OpenType (glyf) and PostScript (cff) shapes. | ||
* Support for WOFF, OTF, TTF (both with TrueType `glyf` and PostScript `cff` outlines) | ||
* Support for kerning (Using GPOS or the kern table). | ||
@@ -251,4 +251,5 @@ * Very efficient. | ||
* [CFF-glyphlet-fonts](https://pomax.github.io/CFF-glyphlet-fonts/): for a great explanation/implementation of CFF font writing. | ||
* [tiny-inflate](https://github.com/devongovett/tiny-inflate): for WOFF decompression. | ||
* [Microsoft Typography](https://www.microsoft.com/typography/OTSPEC/otff.htm): the go-to reference for all things OpenType. | ||
* [Adobe Compact Font Format spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf) and the [Adobe Type 2 Charstring spec](http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf): explains the data structures and commands for the CFF glyph format. | ||
* All contributing authors mentioned in the [AUTHORS](https://github.com/nodebox/opentype.js/blob/master/AUTHORS) file. |
@@ -0,1 +1,5 @@ | ||
0.5.0 (October 6, 2015) | ||
======================= | ||
* Read support for WOFF. | ||
0.4.11 (September 27, 2015) | ||
@@ -2,0 +6,0 @@ =========================== |
@@ -10,2 +10,4 @@ // opentype.js | ||
var inflate = require('tiny-inflate'); | ||
var encoding = require('./encoding'); | ||
@@ -72,2 +74,56 @@ var _font = require('./font'); | ||
// Table Directory Entries ////////////////////////////////////////////// | ||
function parseOpenTypeTableEntries(data, numTables) { | ||
var tableEntries = []; | ||
var p = 12; | ||
for (var i = 0; i < numTables; i += 1) { | ||
var tag = parse.getTag(data, p); | ||
var offset = parse.getULong(data, p + 8); | ||
tableEntries.push({tag: tag, offset: offset, compression: false}); | ||
p += 16; | ||
} | ||
return tableEntries; | ||
} | ||
function parseWOFFTableEntries(data, numTables) { | ||
var tableEntries = []; | ||
var p = 44; // offset to the first table directory entry. | ||
for (var i = 0; i < numTables; i += 1) { | ||
var tag = parse.getTag(data, p); | ||
var offset = parse.getULong(data, p + 4); | ||
var compLength = parse.getULong(data, p + 8); | ||
var origLength = parse.getULong(data, p + 12); | ||
var compression; | ||
if (compLength < origLength) { | ||
compression = 'WOFF'; | ||
} else { | ||
compression = false; | ||
} | ||
tableEntries.push({tag: tag, offset: offset, compression: compression, | ||
compressedLength: compLength, originalLength: origLength}); | ||
p += 20; | ||
} | ||
return tableEntries; | ||
} | ||
function uncompressTable(data, tableEntry) { | ||
if (tableEntry.compression === 'WOFF') { | ||
var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); | ||
var outBuffer = new Uint8Array(tableEntry.originalLength); | ||
inflate(inBuffer, outBuffer); | ||
if (outBuffer.byteLength !== tableEntry.originalLength) { | ||
throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); | ||
} | ||
var view = new DataView(outBuffer.buffer, 0); | ||
return {data: view, offset: 0}; | ||
} else { | ||
return {data: data, offset: tableEntry.offset}; | ||
} | ||
} | ||
// Public API /////////////////////////////////////////////////////////// | ||
@@ -81,11 +137,2 @@ | ||
var cffOffset; | ||
var fvarOffset; | ||
var glyfOffset; | ||
var gposOffset; | ||
var hmtxOffset; | ||
var kernOffset; | ||
var locaOffset; | ||
var nameOffset; | ||
// OpenType fonts use big endian byte ordering. | ||
@@ -97,32 +144,53 @@ // We can't rely on typed array view types, because they operate with the endianness of the host computer. | ||
var data = new DataView(buffer, 0); | ||
var version = parse.getFixed(data, 0); | ||
if (version === 1.0) { | ||
var numTables; | ||
var tableEntries = []; | ||
var signature = parse.getTag(data, 0); | ||
if (signature === String.fromCharCode(0, 1, 0, 0)) { | ||
font.outlinesFormat = 'truetype'; | ||
} else { | ||
version = parse.getTag(data, 0); | ||
if (version === 'OTTO') { | ||
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') { | ||
var 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 version ' + version); | ||
throw new Error('Unsupported OpenType flavor ' + signature); | ||
} | ||
numTables = parse.getUShort(data, 12); | ||
tableEntries = parseWOFFTableEntries(data, numTables); | ||
} else { | ||
throw new Error('Unsupported OpenType signature ' + signature); | ||
} | ||
var numTables = parse.getUShort(data, 4); | ||
var cffTableEntry; | ||
var fvarTableEntry; | ||
var glyfTableEntry; | ||
var gposTableEntry; | ||
var hmtxTableEntry; | ||
var kernTableEntry; | ||
var locaTableEntry; | ||
var nameTableEntry; | ||
// Offset into the table records. | ||
var p = 12; | ||
for (var i = 0; i < numTables; i += 1) { | ||
var tag = parse.getTag(data, p); | ||
var offset = parse.getULong(data, p + 8); | ||
switch (tag) { | ||
var tableEntry = tableEntries[i]; | ||
var table; | ||
switch (tableEntry.tag) { | ||
case 'cmap': | ||
font.tables.cmap = cmap.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
font.tables.cmap = cmap.parse(table.data, table.offset); | ||
font.encoding = new encoding.CmapEncoding(font.tables.cmap); | ||
break; | ||
case 'fvar': | ||
fvarOffset = offset; | ||
fvarTableEntry = tableEntry; | ||
break; | ||
case 'head': | ||
font.tables.head = head.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
font.tables.head = head.parse(table.data, table.offset); | ||
font.unitsPerEm = font.tables.head.unitsPerEm; | ||
@@ -132,3 +200,4 @@ indexToLocFormat = font.tables.head.indexToLocFormat; | ||
case 'hhea': | ||
font.tables.hhea = hhea.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
font.tables.hhea = hhea.parse(table.data, table.offset); | ||
font.ascender = font.tables.hhea.ascender; | ||
@@ -139,49 +208,56 @@ font.descender = font.tables.hhea.descender; | ||
case 'hmtx': | ||
hmtxOffset = offset; | ||
hmtxTableEntry = tableEntry; | ||
break; | ||
case 'ltag': | ||
ltagTable = ltag.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
ltagTable = ltag.parse(table.data, table.offset); | ||
break; | ||
case 'maxp': | ||
font.tables.maxp = maxp.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
font.tables.maxp = maxp.parse(table.data, table.offset); | ||
font.numGlyphs = font.tables.maxp.numGlyphs; | ||
break; | ||
case 'name': | ||
nameOffset = offset; | ||
nameTableEntry = tableEntry; | ||
break; | ||
case 'OS/2': | ||
font.tables.os2 = os2.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
font.tables.os2 = os2.parse(table.data, table.offset); | ||
break; | ||
case 'post': | ||
font.tables.post = post.parse(data, offset); | ||
table = uncompressTable(data, tableEntry); | ||
font.tables.post = post.parse(table.data, table.offset); | ||
font.glyphNames = new encoding.GlyphNames(font.tables.post); | ||
break; | ||
case 'glyf': | ||
glyfOffset = offset; | ||
glyfTableEntry = tableEntry; | ||
break; | ||
case 'loca': | ||
locaOffset = offset; | ||
locaTableEntry = tableEntry; | ||
break; | ||
case 'CFF ': | ||
cffOffset = offset; | ||
cffTableEntry = tableEntry; | ||
break; | ||
case 'kern': | ||
kernOffset = offset; | ||
kernTableEntry = tableEntry; | ||
break; | ||
case 'GPOS': | ||
gposOffset = offset; | ||
gposTableEntry = tableEntry; | ||
break; | ||
} | ||
p += 16; | ||
} | ||
font.tables.name = _name.parse(data, nameOffset, ltagTable); | ||
var nameTable = uncompressTable(data, nameTableEntry); | ||
font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); | ||
font.names = font.tables.name; | ||
if (glyfOffset && locaOffset) { | ||
if (glyfTableEntry && locaTableEntry) { | ||
var shortVersion = indexToLocFormat === 0; | ||
var locaTable = loca.parse(data, locaOffset, font.numGlyphs, shortVersion); | ||
font.glyphs = glyf.parse(data, glyfOffset, locaTable, font); | ||
} else if (cffOffset) { | ||
cff.parse(data, cffOffset, font); | ||
var locaTable = uncompressTable(data, locaTableEntry); | ||
var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); | ||
var glyfTable = uncompressTable(data, glyfTableEntry); | ||
font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font); | ||
} else if (cffTableEntry) { | ||
var cffTable = uncompressTable(data, cffTableEntry); | ||
cff.parse(cffTable.data, cffTable.offset, font); | ||
} else { | ||
@@ -191,7 +267,9 @@ throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); | ||
hmtx.parse(data, hmtxOffset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); | ||
var hmtxTable = uncompressTable(data, hmtxTableEntry); | ||
hmtx.parse(hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); | ||
encoding.addGlyphNames(font); | ||
if (kernOffset) { | ||
font.kerningPairs = kern.parse(data, kernOffset); | ||
if (kernTableEntry) { | ||
var kernTable = uncompressTable(data, kernTableEntry); | ||
font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); | ||
} else { | ||
@@ -201,8 +279,10 @@ font.kerningPairs = {}; | ||
if (gposOffset) { | ||
gpos.parse(data, gposOffset, font); | ||
if (gposTableEntry) { | ||
var gposTable = uncompressTable(data, gposTableEntry); | ||
gpos.parse(gposTable.data, gposTable.offset, font); | ||
} | ||
if (fvarOffset) { | ||
font.tables.fvar = fvar.parse(data, fvarOffset, font.names); | ||
if (fvarTableEntry) { | ||
var fvarTable = uncompressTable(data, fvarTableEntry); | ||
font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); | ||
} | ||
@@ -209,0 +289,0 @@ |
@@ -31,2 +31,14 @@ 'use strict'; | ||
}); | ||
it('can load a WOFF/CFF font', function() { | ||
var font = opentype.loadSync('./fonts/FiraSansMedium.woff'); | ||
assert.deepEqual(font.names.fontFamily, {en: 'Fira Sans OT'}); | ||
assert.equal(font.unitsPerEm, 1000); | ||
assert.equal(font.glyphs.length, 1147); | ||
var aGlyph = font.charToGlyph('A'); | ||
assert.equal(aGlyph.name, 'A'); | ||
assert.equal(aGlyph.unicode, 65); | ||
assert.equal(aGlyph.path.commands.length, 14); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
642325
60
12513
254
1
+ Addedtiny-inflate@^1.0.1
+ Addedtiny-inflate@1.0.3(transitive)