UNPKG

opentype.features.js

Version:

Library to list opentype fonts optional features.

318 lines (317 loc) 16.1 kB
"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 };