be8b097037bf57eaa7bb31fa1495e2f645cdfd2e.js
1 "use strict";var check;module.watch(require('../check'),{default(v){check=v}},0);var parse;module.watch(require('../parse'),{default(v){parse=v}},1);// The `GPOS` table contains kerning pairs, among other things. 2 // https://www.microsoft.com/typography/OTSPEC/gpos.htm 3 4 5 6 7 // Parse ScriptList and FeatureList tables of GPOS, GSUB, GDEF, BASE, JSTF tables. 8 // These lists are unused by now, this function is just the basis for a real parsing. 9 function parseTaggedListTable(data, start) { 10 const p = new parse.Parser(data, start); 11 const n = p.parseUShort(); 12 const list = []; 13 for (let i = 0; i < n; i++) { 14 list[p.parseTag()] = { offset: p.parseUShort() }; 15 } 16 17 return list; 18 } 19 20 // Parse a coverage table in a GSUB, GPOS or GDEF table. 21 // Format 1 is a simple list of glyph ids, 22 // Format 2 is a list of ranges. It is expanded in a list of glyphs, maybe not the best idea. 23 function parseCoverageTable(data, start) { 24 const p = new parse.Parser(data, start); 25 const format = p.parseUShort(); 26 let count = p.parseUShort(); 27 if (format === 1) { 28 return p.parseUShortList(count); 29 } else if (format === 2) { 30 const coverage = []; 31 for (; count--;) { 32 const begin = p.parseUShort(); 33 const end = p.parseUShort(); 34 let index = p.parseUShort(); 35 for (let i = begin; i <= end; i++) { 36 coverage[index++] = i; 37 } 38 } 39 40 return coverage; 41 } 42 } 43 44 // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. 45 // Returns a function that gets a class value from a glyph ID. 46 function parseClassDefTable(data, start) { 47 const p = new parse.Parser(data, start); 48 const format = p.parseUShort(); 49 if (format === 1) { 50 // Format 1 specifies a range of consecutive glyph indices, one class per glyph ID. 51 const startGlyph = p.parseUShort(); 52 const glyphCount = p.parseUShort(); 53 const classes = p.parseUShortList(glyphCount); 54 return function(glyphID) { 55 return classes[glyphID - startGlyph] || 0; 56 }; 57 } else if (format === 2) { 58 // Format 2 defines multiple groups of glyph indices that belong to the same class. 59 const rangeCount = p.parseUShort(); 60 const startGlyphs = []; 61 const endGlyphs = []; 62 const classValues = []; 63 for (let i = 0; i < rangeCount; i++) { 64 startGlyphs[i] = p.parseUShort(); 65 endGlyphs[i] = p.parseUShort(); 66 classValues[i] = p.parseUShort(); 67 } 68 69 return function(glyphID) { 70 let l = 0; 71 let r = startGlyphs.length - 1; 72 while (l < r) { 73 const c = (l + r + 1) >> 1; 74 if (glyphID < startGlyphs[c]) { 75 r = c - 1; 76 } else { 77 l = c; 78 } 79 } 80 81 if (startGlyphs[l] <= glyphID && glyphID <= endGlyphs[l]) { 82 return classValues[l] || 0; 83 } 84 85 return 0; 86 }; 87 } 88 } 89 90 // Parse a pair adjustment positioning subtable, format 1 or format 2 91 // The subtable is returned in the form of a lookup function. 92 function parsePairPosSubTable(data, start) { 93 const p = new parse.Parser(data, start); 94 // This part is common to format 1 and format 2 subtables 95 const format = p.parseUShort(); 96 const coverageOffset = p.parseUShort(); 97 const coverage = parseCoverageTable(data, start + coverageOffset); 98 // valueFormat 4: XAdvance only, 1: XPlacement only, 0: no ValueRecord for second glyph 99 // Only valueFormat1=4 and valueFormat2=0 is supported. 100 const valueFormat1 = p.parseUShort(); 101 const valueFormat2 = p.parseUShort(); 102 let value1; 103 let value2; 104 if (valueFormat1 !== 4 || valueFormat2 !== 0) return; 105 const sharedPairSets = {}; 106 if (format === 1) { 107 // Pair Positioning Adjustment: Format 1 108 const pairSetCount = p.parseUShort(); 109 const pairSet = []; 110 // Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index 111 const pairSetOffsets = p.parseOffset16List(pairSetCount); 112 for (let firstGlyph = 0; firstGlyph < pairSetCount; firstGlyph++) { 113 const pairSetOffset = pairSetOffsets[firstGlyph]; 114 let sharedPairSet = sharedPairSets[pairSetOffset]; 115 if (!sharedPairSet) { 116 // Parse a pairset table in a pair adjustment subtable format 1 117 sharedPairSet = {}; 118 p.relativeOffset = pairSetOffset; 119 let pairValueCount = p.parseUShort(); 120 for (; pairValueCount--;) { 121 const secondGlyph = p.parseUShort(); 122 if (valueFormat1) value1 = p.parseShort(); 123 if (valueFormat2) value2 = p.parseShort(); 124 // We only support valueFormat1 = 4 and valueFormat2 = 0, 125 // so value1 is the XAdvance and value2 is empty. 126 sharedPairSet[secondGlyph] = value1; 127 } 128 } 129 130 pairSet[coverage[firstGlyph]] = sharedPairSet; 131 } 132 133 return function(leftGlyph, rightGlyph) { 134 const pairs = pairSet[leftGlyph]; 135 if (pairs) return pairs[rightGlyph]; 136 }; 137 } else if (format === 2) { 138 // Pair Positioning Adjustment: Format 2 139 const classDef1Offset = p.parseUShort(); 140 const classDef2Offset = p.parseUShort(); 141 const class1Count = p.parseUShort(); 142 const class2Count = p.parseUShort(); 143 const getClass1 = parseClassDefTable(data, start + classDef1Offset); 144 const getClass2 = parseClassDefTable(data, start + classDef2Offset); 145 146 // Parse kerning values by class pair. 147 const kerningMatrix = []; 148 for (let i = 0; i < class1Count; i++) { 149 const kerningRow = kerningMatrix[i] = []; 150 for (let j = 0; j < class2Count; j++) { 151 if (valueFormat1) value1 = p.parseShort(); 152 if (valueFormat2) value2 = p.parseShort(); 153 // We only support valueFormat1 = 4 and valueFormat2 = 0, 154 // so value1 is the XAdvance and value2 is empty. 155 kerningRow[j] = value1; 156 } 157 } 158 159 // Convert coverage list to a hash 160 const covered = {}; 161 for (let i = 0; i < coverage.length; i++) { 162 covered[coverage[i]] = 1; 163 } 164 165 // Get the kerning value for a specific glyph pair. 166 return function(leftGlyph, rightGlyph) { 167 if (!covered[leftGlyph]) return; 168 const class1 = getClass1(leftGlyph); 169 const class2 = getClass2(rightGlyph); 170 const kerningRow = kerningMatrix[class1]; 171 172 if (kerningRow) { 173 return kerningRow[class2]; 174 } 175 }; 176 } 177 } 178 179 // Parse a LookupTable (present in of GPOS, GSUB, GDEF, BASE, JSTF tables). 180 function parseLookupTable(data, start) { 181 const p = new parse.Parser(data, start); 182 const lookupType = p.parseUShort(); 183 const lookupFlag = p.parseUShort(); 184 const useMarkFilteringSet = lookupFlag & 0x10; 185 const subTableCount = p.parseUShort(); 186 const subTableOffsets = p.parseOffset16List(subTableCount); 187 const table = { 188 lookupType: lookupType, 189 lookupFlag: lookupFlag, 190 markFilteringSet: useMarkFilteringSet ? p.parseUShort() : -1 191 }; 192 // LookupType 2, Pair adjustment 193 if (lookupType === 2) { 194 const subtables = []; 195 for (let i = 0; i < subTableCount; i++) { 196 const pairPosSubTable = parsePairPosSubTable(data, start + subTableOffsets[i]); 197 if (pairPosSubTable) subtables.push(pairPosSubTable); 198 } 199 // Return a function which finds the kerning values in the subtables. 200 table.getKerningValue = function(leftGlyph, rightGlyph) { 201 for (let i = subtables.length; i--;) { 202 const value = subtables[i](leftGlyph, rightGlyph); 203 if (value !== undefined) return value; 204 } 205 206 return 0; 207 }; 208 } 209 210 return table; 211 } 212 213 // Parse the `GPOS` table which contains, among other things, kerning pairs. 214 // https://www.microsoft.com/typography/OTSPEC/gpos.htm 215 function parseGposTable(data, start, font) { 216 const p = new parse.Parser(data, start); 217 const tableVersion = p.parseFixed(); 218 check.argument(tableVersion === 1, 'Unsupported GPOS table version.'); 219 220 // ScriptList and FeatureList - ignored for now 221 parseTaggedListTable(data, start + p.parseUShort()); 222 // 'kern' is the feature we are looking for. 223 parseTaggedListTable(data, start + p.parseUShort()); 224 225 // LookupList 226 const lookupListOffset = p.parseUShort(); 227 p.relativeOffset = lookupListOffset; 228 const lookupCount = p.parseUShort(); 229 const lookupTableOffsets = p.parseOffset16List(lookupCount); 230 const lookupListAbsoluteOffset = start + lookupListOffset; 231 for (let i = 0; i < lookupCount; i++) { 232 const table = parseLookupTable(data, lookupListAbsoluteOffset + lookupTableOffsets[i]); 233 if (table.lookupType === 2 && !font.getGposKerningValue) font.getGposKerningValue = table.getKerningValue; 234 } 235 } 236 237 module.exportDefault({ parse: parseGposTable });