Socket
Socket
Sign inDemoInstall

opentype.js

Package Overview
Dependencies
Maintainers
2
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

opentype.js - npm Package Compare versions

Comparing version 0.7.3 to 0.8.0

dist/opentype.js.map

2

bower.json
{
"name": "opentype.js",
"version": "0.7.3",
"version": "0.8.0",
"main": "dist/opentype.js",

@@ -5,0 +5,0 @@ "keywords": [

{
"name": "opentype.js",
"description": "OpenType font parser",
"version": "0.7.3",
"version": "0.8.0",
"author": {

@@ -15,2 +15,3 @@ "name": "Frederik De Bleser",

"ttf",
"woff",
"type"

@@ -33,3 +34,3 @@ ],

"build": "rollup -c",
"minify": "uglifyjs ./dist/opentype.js > ./dist/opentype.min.js",
"minify": "uglifyjs --source-map \"url='opentype.min.js.map'\" --compress --mangle --output ./dist/opentype.min.js -- ./dist/opentype.js",
"dist": "npm run test && npm run build && npm run minify"

@@ -50,4 +51,3 @@ },

"rollup-watch": "^3.2.2",
"uglify-js": "^3.0.14",
"uglifyjs": "^2.4.11"
"uglify-js": "^3.3.10"
},

@@ -54,0 +54,0 @@ "browser": {

@@ -65,3 +65,9 @@ opentype.js

<script src="/bower_components/opentype.js/dist/opentype.js"></script>
### Using via a CDN
To use via a CDN, include the following code in your html:
<script src="https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js"></script>
API

@@ -68,0 +74,0 @@ ===

@@ -0,4 +1,18 @@

0.8.0 (March 6, 2018)
=====================
* Fix loading font file on Android devices (thanks @maoamid!).
* Fix loading fonts from a local source (file://data/... for Android for example (thanks @IntuilabGit!).
* Fixing 2 issues when hinting "mutlu.ttf" (thanks @axkibe!).
* Add some support for OpenType font variations (thanks @taylorb-monotype!).
* Make cmap table format 12 if needed (thanks @Jolg42!).
* Enable uglify's mangle and compress optimizations for a ~30% smaller minified file. (thanks @lojjic & @Jolg42!).
* Better parsing of NULL pointers (thanks @fpirsch!).
* Fix bad path init (empty glyphs) (thanks @fpirsch!).
* Rewrite GPOS parsing (thanks @fpirsch!).
* Roboto-Black.ttf updated (thanks @Jolg42!).
0.7.3 (July 18, 2017)
=====================
* Fix "Object x already has key" error in Safari (thanks @neiltron!).
* Fixed a bug where Font.getPaths() didn't pass options (thanks @keeslinp!).

@@ -5,0 +19,0 @@ 0.7.2 (June 7, 2017)

@@ -7,2 +7,3 @@ // The Font object

import glyphset from './glyphset';
import Position from './position';
import Substitution from './substitution';

@@ -91,2 +92,3 @@ import { isBrowser, checkArgument, arrayBufferToNodeBuffer } from './util';

this.encoding = new DefaultEncoding(this);
this.position = new Position(this);
this.substitution = new Substitution(this);

@@ -239,5 +241,3 @@ this.tables = this.tables || {};

rightGlyph = rightGlyph.index || rightGlyph;
const gposKerning = this.getGposKerningValue;
return gposKerning ? gposKerning(leftGlyph, rightGlyph) :
(this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0);
return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0;
};

@@ -281,2 +281,7 @@

const glyphs = this.stringToGlyphs(text, options);
let kerningLookups;
if (options.kerning) {
const script = options.script || this.position.getDefaultScriptName();
kerningLookups = this.position.getKerningTables(script, options.language);
}
for (let i = 0; i < glyphs.length; i += 1) {

@@ -290,3 +295,7 @@ const glyph = glyphs[i];

if (options.kerning && i < glyphs.length - 1) {
const kerningValue = this.getKerningValue(glyph, glyphs[i + 1]);
// We should apply position adjustment lookups in a more generic way.
// Here we only use the xAdvance value.
const kerningValue = kerningLookups ?
this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) :
this.getKerningValue(glyph, glyphs[i + 1]);
x += kerningValue * fontScale;

@@ -293,0 +302,0 @@ }

@@ -9,3 +9,3 @@ // The Glyph object

function getPathDefinition(glyph, path) {
let _path = path || {commands: []};
let _path = path || new Path();
return {

@@ -12,0 +12,0 @@ configurable: true,

@@ -40,2 +40,25 @@ // The Layout object is the prototype of Substitution objects, and provides

// binary search in a list of ranges (coverage, class definition)
function searchRange(ranges, value) {
// jshint bitwise: false
let range;
let imin = 0;
let imax = ranges.length - 1;
while (imin <= imax) {
const imid = (imin + imax) >>> 1;
range = ranges[imid];
const start = range.start;
if (start === value) {
return range;
} else if (start < value) {
imin = imid + 1;
} else { imax = imid - 1; }
}
if (imin > 0) {
range = ranges[imin - 1];
if (value > range.end) return 0;
return range;
}
}
/**

@@ -219,3 +242,3 @@ * @exports opentype.Layout

* @param {string} feature - 4-letter feature code
* @param {number} lookupType - 1 to 8
* @param {number} lookupType - 1 to 9
* @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables.

@@ -255,2 +278,40 @@ * @return {Object[]}

/**
* Find a glyph in a class definition table
* https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table
* @param {object} classDefTable - an OpenType Layout class definition table
* @param {number} glyphIndex - the index of the glyph to find
* @returns {number} -1 if not found
*/
getGlyphClass: function(classDefTable, glyphIndex) {
switch (classDefTable.format) {
case 1:
if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) {
return classDefTable.classes[glyphIndex - classDefTable.startGlyph];
}
return 0;
case 2:
const range = searchRange(classDefTable.ranges, glyphIndex);
return range ? range.classId : 0;
}
},
/**
* Find a glyph in a coverage table
* https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table
* @param {object} coverageTable - an OpenType Layout coverage table
* @param {number} glyphIndex - the index of the glyph to find
* @returns {number} -1 if not found
*/
getCoverageIndex: function(coverageTable, glyphIndex) {
switch (coverageTable.format) {
case 1:
const index = binSearch(coverageTable.glyphs, glyphIndex);
return index >= 0 ? index : -1;
case 2:
const range = searchRange(coverageTable.ranges, glyphIndex);
return range ? range.index + glyphIndex - range.start : -1;
}
},
/**
* Returns the list of glyph indexes of a coverage table.

@@ -257,0 +318,0 @@ * Format 1: the list is stored raw

@@ -67,7 +67,7 @@ // opentype.js

request.onload = function() {
if (request.status !== 200) {
if (request.response) {
return callback(null, request.response);
} else {
return callback('Font could not be loaded: ' + request.statusText);
}
return callback(null, request.response);
};

@@ -338,3 +338,3 @@

const gposTable = uncompressTable(data, gposTableEntry);
gpos.parse(gposTable.data, gposTable.offset, font);
font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset);
}

@@ -341,0 +341,0 @@

@@ -139,2 +139,4 @@ // Parsing utility functions

Parser.prototype.parseOffset32 = Parser.prototype.parseULong;
Parser.prototype.parseFixed = function() {

@@ -175,10 +177,12 @@ const v = getFixed(this.data, this.offset + this.relativeOffset);

Parser.prototype.parseVersion = function() {
Parser.prototype.parseVersion = function(minorBase) {
const major = getUShort(this.data, this.offset + this.relativeOffset);
// How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1
// This returns the correct number if minor = 0xN000 where N is 0-9
// Default returns the correct number if minor = 0xN000 where N is 0-9
// Set minorBase to 1 for tables that use minor = N where N is 0-9
const minor = getUShort(this.data, this.offset + this.relativeOffset + 2);
this.relativeOffset += 4;
return major + minor / 0x1000 / 10;
if (minorBase === undefined) minorBase = 0x1000;
return major + minor / minorBase / 10;
};

@@ -196,2 +200,17 @@

// Parse a list of 32 bit unsigned integers.
Parser.prototype.parseULongList = function(count) {
if (count === undefined) { count = this.parseULong(); }
const offsets = new Array(count);
const dataView = this.data;
let offset = this.offset + this.relativeOffset;
for (let i = 0; i < count; i++) {
offsets[i] = dataView.getUint32(offset);
offset += 4;
}
this.relativeOffset += count * 4;
return offsets;
};
// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream

@@ -258,2 +277,14 @@ // or provided as an argument.

Parser.prototype.parseList32 = function(count, itemCallback) {
if (!itemCallback) {
itemCallback = count;
count = this.parseULong();
}
const list = new Array(count);
for (let i = 0; i < count; i++) {
list[i] = itemCallback.call(this);
}
return list;
};
/**

@@ -284,2 +315,22 @@ * Parse a list of records.

Parser.prototype.parseRecordList32 = function(count, recordDescription) {
// If the count argument is absent, read it in the stream.
if (!recordDescription) {
recordDescription = count;
count = this.parseULong();
}
const records = new Array(count);
const fields = Object.keys(recordDescription);
for (let i = 0; i < count; i++) {
const rec = {};
for (let j = 0; j < fields.length; j++) {
const fieldName = fields[j];
const fieldType = recordDescription[fieldName];
rec[fieldName] = fieldType.call(this);
}
records[i] = rec;
}
return records;
};
// Parse a data structure into an object

@@ -302,5 +353,52 @@ // Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort }

/**
* Parse a GPOS valueRecord
* https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
* valueFormat is optional, if omitted it is read from the stream.
*/
Parser.prototype.parseValueRecord = function(valueFormat) {
if (valueFormat === undefined) {
valueFormat = this.parseUShort();
}
if (valueFormat === 0) {
// valueFormat2 in kerning pairs is most often 0
// in this case return undefined instead of an empty object, to save space
return;
}
const valueRecord = {};
if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); }
if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); }
if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); }
if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); }
// Device table (non-variable font) / VariationIndex table (variable font) not supported
// https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls
if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); }
if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); }
if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); }
if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); }
return valueRecord;
};
/**
* Parse a list of GPOS valueRecords
* https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
* valueFormat and valueCount are read from the stream.
*/
Parser.prototype.parseValueRecordList = function() {
const valueFormat = this.parseUShort();
const valueCount = this.parseUShort();
const values = new Array(valueCount);
for (let i = 0; i < valueCount; i++) {
values[i] = this.parseValueRecord(valueFormat);
}
return values;
};
Parser.prototype.parsePointer = function(description) {
const structOffset = this.parseOffset16();
if (structOffset > 0) { // NULL offset => return undefined
if (structOffset > 0) {
// NULL offset => return undefined
return new Parser(this.data, this.offset + structOffset).parseStruct(description);

@@ -311,2 +409,11 @@ }

Parser.prototype.parsePointer32 = function(description) {
const structOffset = this.parseOffset32();
if (structOffset > 0) {
// NULL offset => return undefined
return new Parser(this.data, this.offset + structOffset).parseStruct(description);
}
return undefined;
};
/**

@@ -326,4 +433,6 @@ * Parse a list of offsets to lists of 16-bit integers,

const start = offsets[i];
if (start === 0) { // NULL offset
list[i] = undefined; // Add i as owned property to list. Convenient with assert.
if (start === 0) {
// NULL offset
// Add i as owned property to list. Convenient with assert.
list[i] = undefined;
continue;

@@ -412,2 +521,8 @@ }

Parser.list32 = function(count, itemCallback) {
return function() {
return this.parseList32(count, itemCallback);
};
};
Parser.recordList = function(count, recordDescription) {

@@ -419,2 +534,8 @@ return function() {

Parser.recordList32 = function(count, recordDescription) {
return function() {
return this.parseRecordList32(count, recordDescription);
};
};
Parser.pointer = function(description) {

@@ -426,2 +547,8 @@ return function() {

Parser.pointer32 = function(description) {
return function() {
return this.parsePointer32(description);
};
};
Parser.tag = Parser.prototype.parseTag;

@@ -431,2 +558,4 @@ Parser.byte = Parser.prototype.parseByte;

Parser.uShortList = Parser.prototype.parseUShortList;
Parser.uLong = Parser.offset32 = Parser.prototype.parseULong;
Parser.uLongList = Parser.prototype.parseULongList;
Parser.struct = Parser.prototype.parseStruct;

@@ -455,3 +584,3 @@ Parser.coverage = Parser.prototype.parseCoverage;

})
}));
})) || [];
};

@@ -466,3 +595,3 @@

})
}));
})) || [];
};

@@ -473,3 +602,3 @@

const lookupType = this.parseUShort();
check.argument(1 <= lookupType && lookupType <= 8, 'GSUB lookup type ' + lookupType + ' unknown.');
check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.');
const lookupFlag = this.parseUShort();

@@ -483,5 +612,18 @@ const useMarkFilteringSet = lookupFlag & 0x10;

};
})));
}))) || [];
};
Parser.prototype.parseFeatureVariationsList = function() {
return this.parsePointer32(function() {
const majorVersion = this.parseUShort();
const minorVersion = this.parseUShort();
check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.');
const featureVariations = this.parseRecordList32({
conditionSetOffset: Parser.offset32,
featureTableSubstitutionOffset: Parser.offset32
});
return featureVariations;
}) || [];
};
export default {

@@ -488,0 +630,0 @@ getByte,

@@ -261,3 +261,4 @@ // The Substitution object provides utility methods to manipulate

Substitution.prototype.getFeature = function(feature, script, language) {
if (/ss\d\d/.test(feature)) { // ss01 - ss20
if (/ss\d\d/.test(feature)) {
// ss01 - ss20
return this.getSingle(feature, script, language);

@@ -285,3 +286,4 @@ }

Substitution.prototype.add = function(feature, sub, script, language) {
if (/ss\d\d/.test(feature)) { // ss01 - ss20
if (/ss\d\d/.test(feature)) {
// ss01 - ss20
return this.addSingle(feature, sub, script, language);

@@ -288,0 +290,0 @@ }

@@ -973,5 +973,7 @@ // The `CFF` table contains the glyph outlines in PostScript format.

const charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects);
if (topDict.encoding === 0) { // Standard encoding
if (topDict.encoding === 0) {
// Standard encoding
font.cffEncoding = new CffEncoding(cffStandardEncoding, charset);
} else if (topDict.encoding === 1) { // Expert encoding
} else if (topDict.encoding === 1) {
// Expert encoding
font.cffEncoding = new CffEncoding(cffExpertEncoding, charset);

@@ -978,0 +980,0 @@ } else {

@@ -126,3 +126,4 @@ // The `cmap` table stores the mappings from characters to glyphs.

delta: -(code - glyphIndex),
offset: 0
offset: 0,
glyphIndex: glyphIndex
});

@@ -140,11 +141,40 @@ }

// Make cmap table, format 4 by default, 12 if needed only
function makeCmapTable(glyphs) {
const t = new table.Table('cmap', [
// Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit)
let isPlan0Only = true;
let i;
// Check if we need to add cmap format 12 or if format 4 only is fine
for (i = glyphs.length - 1; i > 0; i -= 1) {
const g = glyphs.get(i);
if (g.unicode > 65535) {
console.log('Adding CMAP format 12 (needed!)');
isPlan0Only = false;
break;
}
}
let cmapTable = [
{name: 'version', type: 'USHORT', value: 0},
{name: 'numTables', type: 'USHORT', value: 1},
{name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2},
// CMAP 4 header
{name: 'platformID', type: 'USHORT', value: 3},
{name: 'encodingID', type: 'USHORT', value: 1},
{name: 'offset', type: 'ULONG', value: 12},
{name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)}
];
if (!isPlan0Only)
cmapTable = cmapTable.concat([
// CMAP 12 header
{name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere
{name: 'cmap12EncodingID', type: 'USHORT', value: 10},
{name: 'cmap12Offset', type: 'ULONG', value: 0}
]);
cmapTable = cmapTable.concat([
// CMAP 4 Subtable
{name: 'format', type: 'USHORT', value: 4},
{name: 'length', type: 'USHORT', value: 0},
{name: 'cmap4Length', type: 'USHORT', value: 0},
{name: 'language', type: 'USHORT', value: 0},

@@ -157,4 +187,6 @@ {name: 'segCountX2', type: 'USHORT', value: 0},

const t = new table.Table('cmap', cmapTable);
t.segments = [];
for (let i = 0; i < glyphs.length; i += 1) {
for (i = 0; i < glyphs.length; i += 1) {
const glyph = glyphs.get(i);

@@ -165,3 +197,3 @@ for (let j = 0; j < glyph.unicodes.length; j += 1) {

t.segments = t.segments.sort(function(a, b) {
t.segments = t.segments.sort(function (a, b) {
return a.start - b.start;

@@ -173,9 +205,6 @@ });

let segCount;
segCount = t.segments.length;
t.segCountX2 = segCount * 2;
t.searchRange = Math.pow(2, Math.floor(Math.log(segCount) / Math.log(2))) * 2;
t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2);
t.rangeShift = t.segCountX2 - t.searchRange;
const segCount = t.segments.length;
let segCountToRemove = 0;
// CMAP 4
// Set up parallel segment arrays.

@@ -188,13 +217,41 @@ let endCounts = [];

for (let i = 0; i < segCount; i += 1) {
// CMAP 12
let cmap12Groups = [];
// Reminder this loop is not following the specification at 100%
// The specification -> find suites of characters and make a group
// Here we're doing one group for each letter
// Doing as the spec can save 8 times (or more) space
for (i = 0; i < segCount; i += 1) {
const segment = t.segments[i];
endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end});
startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start});
idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta});
idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset});
if (segment.glyphId !== undefined) {
glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId});
// CMAP 4
if (segment.end <= 65535 && segment.start <= 65535) {
endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end});
startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start});
idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta});
idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset});
if (segment.glyphId !== undefined) {
glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId});
}
} else {
// Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12
segCountToRemove += 1;
}
// CMAP 12
// Skip Terminator Segment
if (!isPlan0Only && segment.glyphIndex !== undefined) {
cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start});
cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end});
cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex});
}
}
// CMAP 4 Subtable
t.segCountX2 = (segCount - segCountToRemove) * 2;
t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2;
t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2);
t.rangeShift = t.segCountX2 - t.searchRange;
t.fields = t.fields.concat(endCounts);

@@ -207,3 +264,3 @@ t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0});

t.length = 14 + // Subtable header
t.cmap4Length = 14 + // Subtable header
endCounts.length * 2 +

@@ -216,2 +273,19 @@ 2 + // reservedPad

if (!isPlan0Only) {
// CMAP 12 Subtable
const cmap12Length = 16 + // Subtable header
cmap12Groups.length * 4;
t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length;
t.fields = t.fields.concat([
{name: 'cmap12Format', type: 'USHORT', value: 12},
{name: 'cmap12Reserved', type: 'USHORT', value: 0},
{name: 'cmap12Length', type: 'ULONG', value: cmap12Length},
{name: 'cmap12Language', type: 'ULONG', value: 0},
{name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3}
]);
t.fields = t.fields.concat(cmap12Groups);
}
return t;

@@ -218,0 +292,0 @@ }

// The `GPOS` table contains kerning pairs, among other things.
// https://www.microsoft.com/typography/OTSPEC/gpos.htm
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
import check from '../check';
import parse from '../parse';
import { Parser } from '../parse';
import table from '../table';
// Parse ScriptList and FeatureList tables of GPOS, GSUB, GDEF, BASE, JSTF tables.
// These lists are unused by now, this function is just the basis for a real parsing.
function parseTaggedListTable(data, start) {
const p = new parse.Parser(data, start);
const n = p.parseUShort();
const list = [];
for (let i = 0; i < n; i++) {
list[p.parseTag()] = { offset: p.parseUShort() };
}
const subtableParsers = new Array(10); // subtableParsers[0] is unused
return list;
}
// Parse a coverage table in a GSUB, GPOS or GDEF table.
// Format 1 is a simple list of glyph ids,
// Format 2 is a list of ranges. It is expanded in a list of glyphs, maybe not the best idea.
function parseCoverageTable(data, start) {
const p = new parse.Parser(data, start);
const format = p.parseUShort();
let count = p.parseUShort();
if (format === 1) {
return p.parseUShortList(count);
} else if (format === 2) {
const coverage = [];
for (; count--;) {
const begin = p.parseUShort();
const end = p.parseUShort();
let index = p.parseUShort();
for (let i = begin; i <= end; i++) {
coverage[index++] = i;
}
}
return coverage;
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable
// this = Parser instance
subtableParsers[1] = function parseLookup1() {
const start = this.offset + this.relativeOffset;
const posformat = this.parseUShort();
if (posformat === 1) {
return {
posFormat: 1,
coverage: this.parsePointer(Parser.coverage),
value: this.parseValueRecord()
};
} else if (posformat === 2) {
return {
posFormat: 2,
coverage: this.parsePointer(Parser.coverage),
values: this.parseValueRecordList()
};
}
}
check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.');
};
// Parse a Class Definition Table in a GSUB, GPOS or GDEF table.
// Returns a function that gets a class value from a glyph ID.
function parseClassDefTable(data, start) {
const p = new parse.Parser(data, start);
const format = p.parseUShort();
if (format === 1) {
// Format 1 specifies a range of consecutive glyph indices, one class per glyph ID.
const startGlyph = p.parseUShort();
const glyphCount = p.parseUShort();
const classes = p.parseUShortList(glyphCount);
return function(glyphID) {
return classes[glyphID - startGlyph] || 0;
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable
subtableParsers[2] = function parseLookup2() {
const start = this.offset + this.relativeOffset;
const posFormat = this.parseUShort();
const coverage = this.parsePointer(Parser.coverage);
const valueFormat1 = this.parseUShort();
const valueFormat2 = this.parseUShort();
if (posFormat === 1) {
// Adjustments for Glyph Pairs
return {
posFormat: posFormat,
coverage: coverage,
valueFormat1: valueFormat1,
valueFormat2: valueFormat2,
pairSets: this.parseList(Parser.pointer(Parser.list(function() {
return { // pairValueRecord
secondGlyph: this.parseUShort(),
value1: this.parseValueRecord(valueFormat1),
value2: this.parseValueRecord(valueFormat2)
};
})))
};
} else if (format === 2) {
// Format 2 defines multiple groups of glyph indices that belong to the same class.
const rangeCount = p.parseUShort();
const startGlyphs = [];
const endGlyphs = [];
const classValues = [];
for (let i = 0; i < rangeCount; i++) {
startGlyphs[i] = p.parseUShort();
endGlyphs[i] = p.parseUShort();
classValues[i] = p.parseUShort();
}
return function(glyphID) {
let l = 0;
let r = startGlyphs.length - 1;
while (l < r) {
const c = (l + r + 1) >> 1;
if (glyphID < startGlyphs[c]) {
r = c - 1;
} else {
l = c;
}
}
if (startGlyphs[l] <= glyphID && glyphID <= endGlyphs[l]) {
return classValues[l] || 0;
}
return 0;
} else if (posFormat === 2) {
const classDef1 = this.parsePointer(Parser.classDef);
const classDef2 = this.parsePointer(Parser.classDef);
const class1Count = this.parseUShort();
const class2Count = this.parseUShort();
return {
// Class Pair Adjustment
posFormat: posFormat,
coverage: coverage,
valueFormat1: valueFormat1,
valueFormat2: valueFormat2,
classDef1: classDef1,
classDef2: classDef2,
class1Count: class1Count,
class2Count: class2Count,
classRecords: this.parseList(class1Count, Parser.list(class2Count, function() {
return {
value1: this.parseValueRecord(valueFormat1),
value2: this.parseValueRecord(valueFormat2)
};
}))
};
}
}
check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.');
};
// Parse a pair adjustment positioning subtable, format 1 or format 2
// The subtable is returned in the form of a lookup function.
function parsePairPosSubTable(data, start) {
const p = new parse.Parser(data, start);
// This part is common to format 1 and format 2 subtables
const format = p.parseUShort();
const coverageOffset = p.parseUShort();
const coverage = parseCoverageTable(data, start + coverageOffset);
// valueFormat 4: XAdvance only, 1: XPlacement only, 0: no ValueRecord for second glyph
// Only valueFormat1=4 and valueFormat2=0 is supported.
const valueFormat1 = p.parseUShort();
const valueFormat2 = p.parseUShort();
let value1;
let value2;
if (valueFormat1 !== 4 || valueFormat2 !== 0) return;
const sharedPairSets = {};
if (format === 1) {
// Pair Positioning Adjustment: Format 1
const pairSetCount = p.parseUShort();
const pairSet = [];
// Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index
const pairSetOffsets = p.parseOffset16List(pairSetCount);
for (let firstGlyph = 0; firstGlyph < pairSetCount; firstGlyph++) {
const pairSetOffset = pairSetOffsets[firstGlyph];
let sharedPairSet = sharedPairSets[pairSetOffset];
if (!sharedPairSet) {
// Parse a pairset table in a pair adjustment subtable format 1
sharedPairSet = {};
p.relativeOffset = pairSetOffset;
let pairValueCount = p.parseUShort();
for (; pairValueCount--;) {
const secondGlyph = p.parseUShort();
if (valueFormat1) value1 = p.parseShort();
if (valueFormat2) value2 = p.parseShort();
// We only support valueFormat1 = 4 and valueFormat2 = 0,
// so value1 is the XAdvance and value2 is empty.
sharedPairSet[secondGlyph] = value1;
}
}
subtableParsers[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; };
subtableParsers[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; };
subtableParsers[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; };
subtableParsers[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; };
subtableParsers[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; };
subtableParsers[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; };
subtableParsers[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; };
pairSet[coverage[firstGlyph]] = sharedPairSet;
}
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
function parseGposTable(data, start) {
start = start || 0;
const p = new Parser(data, start);
const tableVersion = p.parseVersion(1);
check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion);
return function(leftGlyph, rightGlyph) {
const pairs = pairSet[leftGlyph];
if (pairs) return pairs[rightGlyph];
if (tableVersion === 1) {
return {
version: tableVersion,
scripts: p.parseScriptList(),
features: p.parseFeatureList(),
lookups: p.parseLookupList(subtableParsers)
};
} else if (format === 2) {
// Pair Positioning Adjustment: Format 2
const classDef1Offset = p.parseUShort();
const classDef2Offset = p.parseUShort();
const class1Count = p.parseUShort();
const class2Count = p.parseUShort();
const getClass1 = parseClassDefTable(data, start + classDef1Offset);
const getClass2 = parseClassDefTable(data, start + classDef2Offset);
// Parse kerning values by class pair.
const kerningMatrix = [];
for (let i = 0; i < class1Count; i++) {
const kerningRow = kerningMatrix[i] = [];
for (let j = 0; j < class2Count; j++) {
if (valueFormat1) value1 = p.parseShort();
if (valueFormat2) value2 = p.parseShort();
// We only support valueFormat1 = 4 and valueFormat2 = 0,
// so value1 is the XAdvance and value2 is empty.
kerningRow[j] = value1;
}
}
// Convert coverage list to a hash
const covered = {};
for (let i = 0; i < coverage.length; i++) {
covered[coverage[i]] = 1;
}
// Get the kerning value for a specific glyph pair.
return function(leftGlyph, rightGlyph) {
if (!covered[leftGlyph]) return;
const class1 = getClass1(leftGlyph);
const class2 = getClass2(rightGlyph);
const kerningRow = kerningMatrix[class1];
if (kerningRow) {
return kerningRow[class2];
}
} else {
return {
version: tableVersion,
scripts: p.parseScriptList(),
features: p.parseFeatureList(),
lookups: p.parseLookupList(subtableParsers),
variations: p.parseFeatureVariationsList()
};
}
}
// Parse a LookupTable (present in of GPOS, GSUB, GDEF, BASE, JSTF tables).
function parseLookupTable(data, start) {
const p = new parse.Parser(data, start);
const lookupType = p.parseUShort();
const lookupFlag = p.parseUShort();
const useMarkFilteringSet = lookupFlag & 0x10;
const subTableCount = p.parseUShort();
const subTableOffsets = p.parseOffset16List(subTableCount);
const table = {
lookupType: lookupType,
lookupFlag: lookupFlag,
markFilteringSet: useMarkFilteringSet ? p.parseUShort() : -1
};
// LookupType 2, Pair adjustment
if (lookupType === 2) {
const subtables = [];
for (let i = 0; i < subTableCount; i++) {
const pairPosSubTable = parsePairPosSubTable(data, start + subTableOffsets[i]);
if (pairPosSubTable) subtables.push(pairPosSubTable);
}
// Return a function which finds the kerning values in the subtables.
table.getKerningValue = function(leftGlyph, rightGlyph) {
for (let i = subtables.length; i--;) {
const value = subtables[i](leftGlyph, rightGlyph);
if (value !== undefined) return value;
}
return 0;
};
}
return table;
}
// Parse the `GPOS` table which contains, among other things, kerning pairs.
// https://www.microsoft.com/typography/OTSPEC/gpos.htm
function parseGposTable(data, start, font) {
const p = new parse.Parser(data, start);
const tableVersion = p.parseFixed();
check.argument(tableVersion === 1, 'Unsupported GPOS table version.');
// GPOS Writing //////////////////////////////////////////////
// NOT SUPPORTED
const subtableMakers = new Array(10);
// ScriptList and FeatureList - ignored for now
parseTaggedListTable(data, start + p.parseUShort());
// 'kern' is the feature we are looking for.
parseTaggedListTable(data, start + p.parseUShort());
// LookupList
const lookupListOffset = p.parseUShort();
p.relativeOffset = lookupListOffset;
const lookupCount = p.parseUShort();
const lookupTableOffsets = p.parseOffset16List(lookupCount);
const lookupListAbsoluteOffset = start + lookupListOffset;
for (let i = 0; i < lookupCount; i++) {
const table = parseLookupTable(data, lookupListAbsoluteOffset + lookupTableOffsets[i]);
if (table.lookupType === 2 && !font.getGposKerningValue) font.getGposKerningValue = table.getKerningValue;
}
function makeGposTable(gpos) {
return new table.Table('GPOS', [
{name: 'version', type: 'ULONG', value: 0x10000},
{name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)},
{name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)},
{name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers)}
]);
}
export default { parse: parseGposTable };
export default { parse: parseGposTable, make: makeGposTable };

@@ -193,10 +193,21 @@ // The `GSUB` table contains ligatures, among other things.

const p = new Parser(data, start);
const tableVersion = p.parseVersion();
check.argument(tableVersion === 1, 'Unsupported GSUB table version.');
return {
version: tableVersion,
scripts: p.parseScriptList(),
features: p.parseFeatureList(),
lookups: p.parseLookupList(subtableParsers)
};
const tableVersion = p.parseVersion(1);
check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.');
if (tableVersion === 1) {
return {
version: tableVersion,
scripts: p.parseScriptList(),
features: p.parseFeatureList(),
lookups: p.parseLookupList(subtableParsers)
};
} else {
return {
version: tableVersion,
scripts: p.parseScriptList(),
features: p.parseFeatureList(),
lookups: p.parseLookupList(subtableParsers),
variations: p.parseFeatureVariationsList()
};
}
}

@@ -203,0 +214,0 @@

@@ -7,5 +7,5 @@ import assert from 'assert';

var font = opentype.loadSync('./fonts/Roboto-Black.ttf');
assert.deepEqual(font.names.fontFamily, {en: 'Roboto Bk'});
assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
assert.equal(font.unitsPerEm, 2048);
assert.equal(font.glyphs.length, 1037);
assert.equal(font.glyphs.length, 1294);
});

@@ -16,6 +16,6 @@

var font = opentype.loadSync('./fonts/Roboto-Black.ttf');
assert.deepEqual(font.names.fontFamily, {en: 'Roboto Bk'});
assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
assert.equal(font.unitsPerEm, 2048);
assert.equal(font.glyphs.length, 1037);
assert.equal(font.glyphs.length, 1294);
});
});

@@ -15,7 +15,7 @@ import assert from 'assert';

it('lazily loads xMin', function() {
assert.equal(glyph.xMin, 5);
assert.equal(glyph.xMin, -3);
});
it('lazily loads xMax', function() {
assert.equal(glyph.xMax, 1319);
assert.equal(glyph.xMax, 1399);
});

@@ -48,5 +48,5 @@

const box = glyph.getBoundingBox();
assert.equal(box.x1, 5);
assert.equal(box.x1, -3);
assert.equal(box.y1, 0);
assert.equal(box.x2, 1319);
assert.equal(box.x2, 1399);
assert.equal(box.y2, 1456);

@@ -58,6 +58,6 @@ });

const box = glyph.getBoundingBox();
assert.equal(box.x1, 89);
assert.equal(box.y1, -165);
assert.equal(box.x2, 1432);
assert.equal(box.y2, 1477);
assert.equal(box.x1, 72);
assert.equal(box.y1, -266);
assert.equal(box.x2, 1345);
assert.equal(box.y2, 1476);
});

@@ -64,0 +64,0 @@

@@ -65,2 +65,93 @@ import assert from 'assert';

});
describe('getGlyphClass', function() {
const classDef1 = {
format: 1,
startGlyph: 0x32,
classes: [
0, 1, 0, 1, 0, 1, 2, 1, 0, 2, 1, 1, 0,
0, 0, 2, 2, 0, 0, 1, 0, 0, 0, 0, 2, 1
]
};
const classDef2 = {
format: 2,
ranges: [
{ start: 0x46, end: 0x47, classId: 2 },
{ start: 0x49, end: 0x49, classId: 2 },
{ start: 0xd2, end: 0xd3, classId: 1 }
]
};
it('should find a glyph class in a format 1 class definition table', function() {
assert.equal(layout.getGlyphClass(classDef1, 0x32), 0);
assert.equal(layout.getGlyphClass(classDef1, 0x33), 1);
assert.equal(layout.getGlyphClass(classDef1, 0x34), 0);
assert.equal(layout.getGlyphClass(classDef1, 0x38), 2);
assert.equal(layout.getGlyphClass(classDef1, 0x4a), 2);
assert.equal(layout.getGlyphClass(classDef1, 0x4b), 1);
// Any glyph not included in the range of covered glyph IDs automatically belongs to Class 0.
assert.equal(layout.getGlyphClass(classDef1, 0x31), 0);
assert.equal(layout.getGlyphClass(classDef1, 0x50), 0);
});
it('should find a glyph class in a format 2 class definition table', function() {
assert.equal(layout.getGlyphClass(classDef2, 0x46), 2);
assert.equal(layout.getGlyphClass(classDef2, 0x47), 2);
assert.equal(layout.getGlyphClass(classDef2, 0x49), 2);
assert.equal(layout.getGlyphClass(classDef2, 0xd2), 1);
assert.equal(layout.getGlyphClass(classDef2, 0xd3), 1);
// Any glyph not covered by a ClassRangeRecord is assumed to belong to Class 0.
assert.equal(layout.getGlyphClass(classDef2, 0x45), 0);
assert.equal(layout.getGlyphClass(classDef2, 0x48), 0);
assert.equal(layout.getGlyphClass(classDef2, 0x4a), 0);
assert.equal(layout.getGlyphClass(classDef2, 0xd4), 0);
});
});
describe('getCoverageIndex', function() {
const cov1 = {
format: 1,
glyphs: [0x4f, 0x125, 0x129]
};
const cov2 = {
format: 2,
ranges: [
{ start: 6, end: 6, index: 0 },
{ start: 11, end: 11, index: 1 },
{ start: 16, end: 16, index: 2 },
{ start: 18, end: 18, index: 3 },
{ start: 37, end: 41, index: 4 },
{ start: 44, end: 52, index: 9 },
{ start: 56, end: 62, index: 18 }
]
};
it('should find a glyph in a format 1 coverage table', function() {
assert.equal(layout.getCoverageIndex(cov1, 0x4f), 0);
assert.equal(layout.getCoverageIndex(cov1, 0x125), 1);
assert.equal(layout.getCoverageIndex(cov1, 0x129), 2);
assert.equal(layout.getCoverageIndex(cov1, 0x33), -1);
assert.equal(layout.getCoverageIndex(cov1, 0x80), -1);
assert.equal(layout.getCoverageIndex(cov1, 0x200), -1);
});
it('should find a glyph in a format 2 coverage table', function() {
assert.equal(layout.getCoverageIndex(cov2, 6), 0);
assert.equal(layout.getCoverageIndex(cov2, 11), 1);
assert.equal(layout.getCoverageIndex(cov2, 37), 4);
assert.equal(layout.getCoverageIndex(cov2, 38), 5);
assert.equal(layout.getCoverageIndex(cov2, 56), 18);
assert.equal(layout.getCoverageIndex(cov2, 62), 24);
assert.equal(layout.getCoverageIndex(cov2, 5), -1);
assert.equal(layout.getCoverageIndex(cov2, 8), -1);
assert.equal(layout.getCoverageIndex(cov2, 55), -1);
assert.equal(layout.getCoverageIndex(cov2, 70), -1);
});
});
});

@@ -7,9 +7,8 @@ import assert from 'assert';

const font = loadSync('./fonts/Roboto-Black.ttf');
assert.deepEqual(font.names.fontFamily, {en: 'Roboto Bk'});
assert.deepEqual(font.names.fontFamily, {en: 'Roboto Black'});
assert.equal(font.unitsPerEm, 2048);
assert.equal(font.glyphs.length, 1037);
assert.equal(font.glyphs.length, 1294);
const aGlyph = font.charToGlyph('A');
assert.equal(aGlyph.name, 'A');
assert.equal(aGlyph.unicode, 65);
assert.equal(aGlyph.path.commands.length, 19);
assert.equal(aGlyph.path.commands.length, 15);
});

@@ -16,0 +15,0 @@

@@ -137,3 +137,3 @@ import assert from 'assert';

it('should parse a ClassDefFormat1 table', function() {
// https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 7
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#example-7-classdefformat1-table-class-array
const data = '0001 0032 001A' +

@@ -155,3 +155,3 @@ '0000 0001 0000 0001 0000 0001 0002 0001 0000 0002 0001 0001 0000' +

it('should parse a ClassDefFormat2 table', function() {
// https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 8
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#example-8-classdefformat2-table-class-ranges
const data = '0002 0003 0030 0031 0002 0040 0041 0003 00D2 00D3 0001';

@@ -158,0 +158,0 @@ const p = new Parser(unhex(data), 0);

@@ -41,2 +41,9 @@ import assert from 'assert';

it('can parse a GSUB header with null pointers', function() {
const data = unhex(
'00010000 0000 0000 0000'
);
assert.deepEqual(gsub.parse(data), { version: 1, scripts: [], features: [], lookups: [] });
});
//// Lookup type 1 ////////////////////////////////////////////////////////

@@ -43,0 +50,0 @@ it('can parse lookup1 substFormat 1', function() {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc