// The `GSUB` table contains ligatures, among other things. // https://www.microsoft.com/typography/OTSPEC/gsub.htm import check from '../check'; import { Parser } from '../parse'; const subtableParsers = new Array(9); // subtableParsers[0] is unused // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS subtableParsers[1] = function parseLookup1() { const start = this.offset + this.relativeOffset; const substFormat = this.parseUShort(); if (substFormat === 1) { return { substFormat: 1, coverage: this.parsePointer(Parser.coverage), deltaGlyphId: this.parseUShort(), }; } else if (substFormat === 2) { return { substFormat: 2, coverage: this.parsePointer(Parser.coverage), substitute: this.parseOffset16List(), }; } check.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() { const substFormat = this.parseUShort(); check.argument( substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1' ); return { substFormat: substFormat, coverage: this.parsePointer(Parser.coverage), sequences: this.parseListOfLists(), }; }; // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS subtableParsers[3] = function parseLookup3() { const substFormat = this.parseUShort(); check.argument( substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1' ); return { substFormat: substFormat, coverage: this.parsePointer(Parser.coverage), alternateSets: this.parseListOfLists(), }; }; // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS subtableParsers[4] = function parseLookup4() { const substFormat = this.parseUShort(); check.argument( substFormat === 1, 'GSUB ligature table identifier-format must be 1' ); return { substFormat: substFormat, coverage: this.parsePointer(Parser.coverage), ligatureSets: this.parseListOfLists(function () { return { ligGlyph: this.parseUShort(), components: this.parseUShortList(this.parseUShort() - 1), }; }), }; }; const lookupRecordDesc = { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort, }; // https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF subtableParsers[5] = function parseLookup5() { const start = this.offset + this.relativeOffset; const substFormat = this.parseUShort(); if (substFormat === 1) { return { substFormat: substFormat, coverage: this.parsePointer(Parser.coverage), ruleSets: this.parseListOfLists(function () { const glyphCount = this.parseUShort(); const substCount = this.parseUShort(); return { input: this.parseUShortList(glyphCount - 1), lookupRecords: this.parseRecordList( substCount, lookupRecordDesc ), }; }), }; } else if (substFormat === 2) { return { substFormat: substFormat, coverage: this.parsePointer(Parser.coverage), classDef: this.parsePointer(Parser.classDef), classSets: this.parseListOfLists(function () { const glyphCount = this.parseUShort(); const substCount = this.parseUShort(); return { classes: this.parseUShortList(glyphCount - 1), lookupRecords: this.parseRecordList( substCount, lookupRecordDesc ), }; }), }; } else if (substFormat === 3) { const glyphCount = this.parseUShort(); const substCount = this.parseUShort(); return { substFormat: substFormat, coverages: this.parseList( glyphCount, Parser.pointer(Parser.coverage) ), lookupRecords: this.parseRecordList(substCount, lookupRecordDesc), }; } check.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() { const start = this.offset + this.relativeOffset; const substFormat = this.parseUShort(); if (substFormat === 1) { return { substFormat: 1, coverage: this.parsePointer(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(Parser.coverage), backtrackClassDef: this.parsePointer(Parser.classDef), inputClassDef: this.parsePointer(Parser.classDef), lookaheadClassDef: this.parsePointer(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(Parser.pointer(Parser.coverage)), inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), lookupRecords: this.parseRecordList(lookupRecordDesc), }; } check.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 const substFormat = this.parseUShort(); check.argument( substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1' ); const extensionLookupType = this.parseUShort(); const extensionParser = new 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() { const substFormat = this.parseUShort(); check.argument( substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1' ); return { substFormat: substFormat, coverage: this.parsePointer(Parser.coverage), backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), substitutes: this.parseUShortList(), }; }; // https://www.microsoft.com/typography/OTSPEC/gsub.htm function parseGsubTable(data, start) { start = start || 0; const p = new Parser(data, start); 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(), }; } } export default { parse: parseGsubTable };