opentype.features.js
Version:
Library to list opentype fonts optional features.
318 lines (317 loc) • 16.1 kB
JavaScript
"use strict";
// The `GSUB` table contains ligatures, among other things.
// https://www.microsoft.com/typography/OTSPEC/gsub.htm
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var check_1 = __importDefault(require("../check"));
var parse_1 = require("../parse");
var table_1 = __importDefault(require("../table"));
var subtableParsers = new Array(9); // subtableParsers[0] is unused
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS
subtableParsers[1] = function parseLookup1() {
var start = this.offset + this.relativeOffset;
var substFormat = this.parseUShort();
if (substFormat === 1) {
return {
substFormat: 1,
coverage: this.parsePointer(parse_1.Parser.coverage),
deltaGlyphId: this.parseUShort()
};
}
else if (substFormat === 2) {
return {
substFormat: 2,
coverage: this.parsePointer(parse_1.Parser.coverage),
substitute: this.parseOffset16List()
};
}
check_1.default.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.');
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS
subtableParsers[2] = function parseLookup2() {
var substFormat = this.parseUShort();
check_1.default.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1');
return {
substFormat: substFormat,
coverage: this.parsePointer(parse_1.Parser.coverage),
sequences: this.parseListOfLists()
};
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS
subtableParsers[3] = function parseLookup3() {
var substFormat = this.parseUShort();
check_1.default.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1');
return {
substFormat: substFormat,
coverage: this.parsePointer(parse_1.Parser.coverage),
alternateSets: this.parseListOfLists()
};
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS
subtableParsers[4] = function parseLookup4() {
var substFormat = this.parseUShort();
check_1.default.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1');
return {
substFormat: substFormat,
coverage: this.parsePointer(parse_1.Parser.coverage),
ligatureSets: this.parseListOfLists(function () {
return {
ligGlyph: this.parseUShort(),
components: this.parseUShortList(this.parseUShort() - 1)
};
})
};
};
var lookupRecordDesc = {
sequenceIndex: parse_1.Parser.uShort,
lookupListIndex: parse_1.Parser.uShort
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF
subtableParsers[5] = function parseLookup5() {
var start = this.offset + this.relativeOffset;
var substFormat = this.parseUShort();
if (substFormat === 1) {
return {
substFormat: substFormat,
coverage: this.parsePointer(parse_1.Parser.coverage),
ruleSets: this.parseListOfLists(function () {
var glyphCount = this.parseUShort();
var substCount = this.parseUShort();
return {
input: this.parseUShortList(glyphCount - 1),
lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
};
})
};
}
else if (substFormat === 2) {
return {
substFormat: substFormat,
coverage: this.parsePointer(parse_1.Parser.coverage),
classDef: this.parsePointer(parse_1.Parser.classDef),
classSets: this.parseListOfLists(function () {
var glyphCount = this.parseUShort();
var substCount = this.parseUShort();
return {
classes: this.parseUShortList(glyphCount - 1),
lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
};
})
};
}
else if (substFormat === 3) {
var glyphCount = this.parseUShort();
var substCount = this.parseUShort();
return {
substFormat: substFormat,
coverages: this.parseList(glyphCount, parse_1.Parser.pointer(parse_1.Parser.coverage)),
lookupRecords: this.parseRecordList(substCount, lookupRecordDesc)
};
}
check_1.default.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.');
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC
subtableParsers[6] = function parseLookup6() {
var start = this.offset + this.relativeOffset;
var substFormat = this.parseUShort();
if (substFormat === 1) {
return {
substFormat: 1,
coverage: this.parsePointer(parse_1.Parser.coverage),
chainRuleSets: this.parseListOfLists(function () {
return {
backtrack: this.parseUShortList(),
input: this.parseUShortList(this.parseShort() - 1),
lookahead: this.parseUShortList(),
lookupRecords: this.parseRecordList(lookupRecordDesc)
};
})
};
}
else if (substFormat === 2) {
return {
substFormat: 2,
coverage: this.parsePointer(parse_1.Parser.coverage),
backtrackClassDef: this.parsePointer(parse_1.Parser.classDef),
inputClassDef: this.parsePointer(parse_1.Parser.classDef),
lookaheadClassDef: this.parsePointer(parse_1.Parser.classDef),
chainClassSet: this.parseListOfLists(function () {
return {
backtrack: this.parseUShortList(),
input: this.parseUShortList(this.parseShort() - 1),
lookahead: this.parseUShortList(),
lookupRecords: this.parseRecordList(lookupRecordDesc)
};
})
};
}
else if (substFormat === 3) {
return {
substFormat: 3,
backtrackCoverage: this.parseList(parse_1.Parser.pointer(parse_1.Parser.coverage)),
inputCoverage: this.parseList(parse_1.Parser.pointer(parse_1.Parser.coverage)),
lookaheadCoverage: this.parseList(parse_1.Parser.pointer(parse_1.Parser.coverage)),
lookupRecords: this.parseRecordList(lookupRecordDesc)
};
}
check_1.default.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.');
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES
subtableParsers[7] = function parseLookup7() {
// Extension Substitution subtable
var substFormat = this.parseUShort();
check_1.default.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1');
var extensionLookupType = this.parseUShort();
var extensionParser = new parse_1.Parser(this.data, this.offset + this.parseULong());
return {
substFormat: 1,
lookupType: extensionLookupType,
extension: subtableParsers[extensionLookupType].call(extensionParser)
};
};
// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS
subtableParsers[8] = function parseLookup8() {
var substFormat = this.parseUShort();
check_1.default.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1');
return {
substFormat: substFormat,
coverage: this.parsePointer(parse_1.Parser.coverage),
backtrackCoverage: this.parseList(parse_1.Parser.pointer(parse_1.Parser.coverage)),
lookaheadCoverage: this.parseList(parse_1.Parser.pointer(parse_1.Parser.coverage)),
substitutes: this.parseUShortList()
};
};
// https://www.microsoft.com/typography/OTSPEC/gsub.htm
function parseGsubTable(data, start) {
start = start || 0;
var p = new parse_1.Parser(data, start);
var tableVersion = p.parseVersion(1);
check_1.default.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()
};
}
}
// GSUB Writing //////////////////////////////////////////////
var subtableMakers = new Array(9);
subtableMakers[1] = function makeLookup1(subtable) {
if (subtable.substFormat === 1) {
return new table_1.default.Table('substitutionTable', [
{ name: 'substFormat', type: 'USHORT', value: 1 },
{ name: 'coverage', type: 'TABLE', value: new table_1.default.Coverage(subtable.coverage) },
{ name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId }
]);
}
else {
return new table_1.default.Table('substitutionTable', [
{ name: 'substFormat', type: 'USHORT', value: 2 },
{ name: 'coverage', type: 'TABLE', value: new table_1.default.Coverage(subtable.coverage) }
].concat(table_1.default.ushortList('substitute', subtable.substitute)));
}
check_1.default.fail('Lookup type 1 substFormat must be 1 or 2.');
};
subtableMakers[2] = function makeLookup2(subtable) {
check_1.default.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.');
return new table_1.default.Table('substitutionTable', [
{ name: 'substFormat', type: 'USHORT', value: 1 },
{ name: 'coverage', type: 'TABLE', value: new table_1.default.Coverage(subtable.coverage) }
].concat(table_1.default.tableList('seqSet', subtable.sequences, function (sequenceSet) {
return new table_1.default.Table('sequenceSetTable', table_1.default.ushortList('sequence', sequenceSet));
})));
};
subtableMakers[3] = function makeLookup3(subtable) {
check_1.default.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.');
return new table_1.default.Table('substitutionTable', [
{ name: 'substFormat', type: 'USHORT', value: 1 },
{ name: 'coverage', type: 'TABLE', value: new table_1.default.Coverage(subtable.coverage) }
].concat(table_1.default.tableList('altSet', subtable.alternateSets, function (alternateSet) {
return new table_1.default.Table('alternateSetTable', table_1.default.ushortList('alternate', alternateSet));
})));
};
subtableMakers[4] = function makeLookup4(subtable) {
check_1.default.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.');
return new table_1.default.Table('substitutionTable', [
{ name: 'substFormat', type: 'USHORT', value: 1 },
{ name: 'coverage', type: 'TABLE', value: new table_1.default.Coverage(subtable.coverage) }
].concat(table_1.default.tableList('ligSet', subtable.ligatureSets, function (ligatureSet) {
return new table_1.default.Table('ligatureSetTable', table_1.default.tableList('ligature', ligatureSet, function (ligature) {
return new table_1.default.Table('ligatureTable', [{ name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph }]
.concat(table_1.default.ushortList('component', ligature.components, ligature.components.length + 1)));
}));
})));
};
subtableMakers[6] = function makeLookup6(subtable) {
if (subtable.substFormat === 1) {
var returnTable = new table_1.default.Table('chainContextTable', [
{ name: 'substFormat', type: 'USHORT', value: subtable.substFormat },
{ name: 'coverage', type: 'TABLE', value: new table_1.default.Coverage(subtable.coverage) }
].concat(table_1.default.tableList('chainRuleSet', subtable.chainRuleSets, function (chainRuleSet) {
return new table_1.default.Table('chainRuleSetTable', table_1.default.tableList('chainRule', chainRuleSet, function (chainRule) {
var tableData = table_1.default.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length)
.concat(table_1.default.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1))
.concat(table_1.default.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length))
.concat(table_1.default.ushortList('substitution', [], chainRule.lookupRecords.length));
chainRule.lookupRecords.forEach(function (record, i) {
tableData = tableData
.concat({ name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex })
.concat({ name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex });
});
return new table_1.default.Table('chainRuleTable', tableData);
}));
})));
return returnTable;
}
else if (subtable.substFormat === 2) {
check_1.default.assert(false, 'lookup type 6 format 2 is not yet supported.');
}
else if (subtable.substFormat === 3) {
var tableData_1 = [
{ name: 'substFormat', type: 'USHORT', value: subtable.substFormat },
];
tableData_1.push({ name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length });
subtable.backtrackCoverage.forEach(function (coverage, i) {
tableData_1.push({ name: 'backtrackCoverage' + i, type: 'TABLE', value: new table_1.default.Coverage(coverage) });
});
tableData_1.push({ name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length });
subtable.inputCoverage.forEach(function (coverage, i) {
tableData_1.push({ name: 'inputCoverage' + i, type: 'TABLE', value: new table_1.default.Coverage(coverage) });
});
tableData_1.push({ name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length });
subtable.lookaheadCoverage.forEach(function (coverage, i) {
tableData_1.push({ name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table_1.default.Coverage(coverage) });
});
tableData_1.push({ name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length });
subtable.lookupRecords.forEach(function (record, i) {
tableData_1 = tableData_1
.concat({ name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex })
.concat({ name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex });
});
var returnTable = new table_1.default.Table('chainContextTable', tableData_1);
return returnTable;
}
check_1.default.assert(false, 'lookup type 6 format must be 1, 2 or 3.');
};
function makeGsubTable(gsub) {
return new table_1.default.Table('GSUB', [
{ name: 'version', type: 'ULONG', value: 0x10000 },
{ name: 'scripts', type: 'TABLE', value: new table_1.default.ScriptList(gsub.scripts) },
{ name: 'features', type: 'TABLE', value: new table_1.default.FeatureList(gsub.features) },
{ name: 'lookups', type: 'TABLE', value: new table_1.default.LookupList(gsub.lookups, subtableMakers) }
]);
}
exports.default = { parse: parseGsubTable, make: makeGsubTable };