/ node_modules / opentype.js / .reify-cache / be8b097037bf57eaa7bb31fa1495e2f645cdfd2e.js
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 });