parse.js
  1  import assert from 'assert';
  2  import { unhex } from './testutil';
  3  import { Parser } from '../src/parse';
  4  
  5  describe('parse.js', function() {
  6      describe('parseUShortList', function() {
  7          it('can parse an empty list', function() {
  8              const p = new Parser(unhex('0000'), 0);
  9              assert.deepEqual(p.parseUShortList(), []);
 10          });
 11  
 12          it('can parse a list', function() {
 13              const p = new Parser(unhex('0003 1234 DEAD BEEF'), 0);
 14              assert.deepEqual(p.parseUShortList(), [0x1234, 0xdead, 0xbeef]);
 15          });
 16  
 17          it('can parse a list of predefined length', function() {
 18              const p = new Parser(unhex('1234 DEAD BEEF 5678 9ABC'), 2);
 19              assert.deepEqual(p.parseUShortList(3), [0xdead, 0xbeef, 0x5678]);
 20          });
 21      });
 22  
 23      describe('parseList', function() {
 24          it('can parse a list of values', function() {
 25              const data = '0003 12 34 56 78 9A BC';
 26              const p = new Parser(unhex(data), 0);
 27              assert.deepEqual(p.parseList(Parser.uShort), [0x1234, 0x5678, 0x9abc]);
 28              assert.equal(p.relativeOffset, 8);
 29          });
 30  
 31          it('can parse a list of values of predefined length', function() {
 32              const data = '12 34 56 78 9A BC';
 33              const p = new Parser(unhex(data), 0);
 34              assert.deepEqual(p.parseList(3, Parser.uShort), [0x1234, 0x5678, 0x9abc]);
 35              assert.equal(p.relativeOffset, 6);
 36          });
 37      });
 38  
 39      describe('parseRecordList', function() {
 40          it('can parse a list of records', function() {
 41              const data = '0002 12 34 56 78 9A BC';
 42              const p = new Parser(unhex(data), 0);
 43              assert.deepEqual(p.parseRecordList({ a: Parser.byte, b: Parser.uShort }), [
 44                  { a: 0x12, b: 0x3456 },
 45                  { a: 0x78, b: 0x9abc }
 46              ]);
 47              assert.equal(p.relativeOffset, 8);
 48          });
 49  
 50          it('can parse an empty list of records', function() {
 51              const data = '0000';
 52              const p = new Parser(unhex(data), 0);
 53              assert.deepEqual(p.parseRecordList({ a: Parser.byte, b: Parser.uShort }), []);
 54              assert.equal(p.relativeOffset, 2);
 55          });
 56  
 57          it('can parse a list of records of predefined length', function() {
 58              const data = '12 34 56 78 9A BC';
 59              const p = new Parser(unhex(data), 0);
 60              assert.deepEqual(p.parseRecordList(2, { a: Parser.byte, b: Parser.uShort }), [
 61                  { a: 0x12, b: 0x3456 },
 62                  { a: 0x78, b: 0x9abc }
 63              ]);
 64              assert.equal(p.relativeOffset, 6);
 65          });
 66      });
 67  
 68      describe('parseListOfLists', function() {
 69          it('can parse a list of lists of 16-bit integers', function() {
 70              const data = '0003 0008 000E 0016' +      // 3 lists
 71                  '0002 1234 5678' +                  // list 1
 72                  '0003 DEAD BEEF FADE' +             // list 2
 73                  '0001 9876';                        // list 3
 74              const p = new Parser(unhex(data), 0);
 75              assert.deepEqual(p.parseListOfLists(), [
 76                  [0x1234, 0x5678],
 77                  [0xdead, 0xbeef, 0xfade],
 78                  [0x9876]
 79              ]);
 80          });
 81  
 82          it('can parse an empty list of lists', function() {
 83              const p = new Parser(unhex('0000'), 0);
 84              assert.deepEqual(p.parseListOfLists(), []);
 85          });
 86  
 87          it('can parse list of empty lists', function() {
 88              const p = new Parser(unhex('0001 0004 0000'), 0);
 89              assert.deepEqual(p.parseListOfLists(), [[]]);
 90          });
 91  
 92          it('can parse a list of lists of records', function() {
 93              const data = '0002 0006 0012' +                   // 2 lists
 94                  '0002 0006 0009 12 34 56 78 9A BC' +        // list 1
 95                  '0001 0004 DE F0 12';                       // list 2
 96  
 97              const p = new Parser(unhex(data), 0);
 98              function parseRecord() {
 99                  return { a: p.parseByte(), b: p.parseUShort() };
100              }
101  
102              assert.deepEqual(p.parseListOfLists(parseRecord), [
103                  [{ a: 0x12, b: 0x3456 }, { a: 0x78, b: 0x9abc }],
104                  [{ a: 0xde, b: 0xf012 }]
105              ]);
106          });
107      });
108  
109      describe('parseCoverage', function() {
110          it('should parse a CoverageFormat1 table', function() {
111              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 5
112              const data = '0004 1234' +                // coverageOffset + filler
113                  '0001 0005 0038 003B 0041 0042 004A';
114              const p = new Parser(unhex(data), 4);
115              assert.deepEqual(p.parseCoverage(), {
116                  format: 1,
117                  glyphs: [0x38, 0x3b, 0x41, 0x42, 0x4a]
118              });
119              assert.equal(p.relativeOffset, 14);
120          });
121  
122          it('should parse a CoverageFormat2 table', function() {
123              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 6
124              const data = '0004 1234' +                // coverageOffset + filler
125                  '0002 0001 004E 0057 0000';
126              const p = new Parser(unhex(data), 4);
127              assert.deepEqual(p.parseCoverage(), {
128                  format: 2,
129                  ranges: [{ start: 0x4e, end: 0x57, index: 0 }]
130              });
131              assert.equal(p.relativeOffset, 10);
132          });
133      });
134  
135      describe('parseClassDef', function() {
136          it('should parse a ClassDefFormat1 table', function() {
137              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 7
138              const data = '0001 0032 001A' +
139                  '0000 0001 0000 0001 0000 0001 0002 0001 0000 0002 0001 0001 0000' +
140                  '0000 0000 0002 0002 0000 0000 0001 0000 0000 0000 0000 0002 0001';
141              const p = new Parser(unhex(data), 0);
142              assert.deepEqual(p.parseClassDef(), {
143                  format: 1,
144                  startGlyph: 0x32,
145                  classes: [
146                      0, 1, 0, 1, 0, 1, 2, 1, 0, 2, 1, 1, 0,
147                      0, 0, 2, 2, 0, 0, 1, 0, 0, 0, 0, 2, 1
148                  ]
149              });
150              assert.equal(p.relativeOffset, 58);
151          });
152  
153          it('should parse a ClassDefFormat2 table', function() {
154              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 8
155              const data = '0002 0003 0030 0031 0002 0040 0041 0003 00D2 00D3 0001';
156              const p = new Parser(unhex(data), 0);
157              assert.deepEqual(p.parseClassDef(), {
158                  format: 2,
159                  ranges: [
160                      { start: 0x30, end: 0x31, classId: 2 },
161                      { start: 0x40, end: 0x41, classId: 3 },
162                      { start: 0xd2, end: 0xd3, classId: 1 }
163                  ]
164              });
165              assert.equal(p.relativeOffset, 22);
166          });
167      });
168  
169      describe('parseScriptList', function() {
170          it('should parse a ScriptList table', function() {
171              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Examples 1 & 2
172              const data = '0004 1234' +                // coverageOffset + filler
173                  '0003 68616E69 0014 6B616E61 0018 6C61746E 001C' +  // Example 1
174                  '0000 0000 0000 0000' +                             // 2 empty Script Tables
175                  '000A 0001 55524420 0016' +                         // Example 2
176                  '0000 FFFF 0003 0000 0001 0002' +                   // DefLangSys
177                  '0000 0003 0003 0000 0001 0002';                    // UrduLangSys
178              const p = new Parser(unhex(data), 0);
179              assert.deepEqual(p.parseScriptList(), [
180                  { tag: 'hani', script: { defaultLangSys: undefined, langSysRecords: [] } },
181                  { tag: 'kana', script: { defaultLangSys: undefined, langSysRecords: [] } },
182                  { tag: 'latn', script: {
183                      defaultLangSys: {
184                          reserved: 0,
185                          reqFeatureIndex: 0xffff,
186                          featureIndexes: [0, 1, 2]
187                      },
188                      langSysRecords: [{
189                          tag: 'URD ',
190                          langSys: {
191                              reserved: 0,
192                              reqFeatureIndex: 3,
193                              featureIndexes: [0, 1, 2]
194                          }
195                      }]
196                  } },
197              ]);
198              assert.equal(p.relativeOffset, 2);
199          });
200      });
201  
202      describe('parseFeatureList', function() {
203          it('should parse a FeatureList table', function() {
204              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 3
205              const data = '0004 0000' +                                // table offset + filler
206                  '0003 6C696761 0014 6C696761 001A 6C696761 0022' +  // feature list
207                  // There is an error in the online example, count is 3 for the 3rd feature.
208                  '0000 0001 0000   0000 0002 0000 0001   0000 0003 0000 0001 0002';
209              const p = new Parser(unhex(data), 0);
210              assert.deepEqual(p.parseFeatureList(), [
211                  { tag: 'liga', feature: { featureParams: 0, lookupListIndexes: [0] } },
212                  { tag: 'liga', feature: { featureParams: 0, lookupListIndexes: [0, 1] } },
213                  { tag: 'liga', feature: { featureParams: 0, lookupListIndexes: [0, 1, 2] } }
214              ]);
215              assert.equal(p.relativeOffset, 2);
216          });
217      });
218  
219      describe('parseLookupList', function() {
220          it('should parse a LookupList table', function() {
221              // https://www.microsoft.com/typography/OTSPEC/chapter2.htm Example 4
222              const data = '0004 0000' +                    // table offset + filler
223                  '0003 0008 0010 0018' +                 // lookup list
224                  '0004 000C 0001 0018' +                 // FfiFi lookup
225                  '0004 000C 0001 0028' +                 // FflFlFf lookup
226                  '0004 000C 0001 0038' +                 // Eszet lookup
227                  '1234 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000' +
228                  '5678 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000' +
229                  '9ABC';
230              const lookupTableParsers = [0, 0, 0, 0, Parser.uShort];
231              const p = new Parser(unhex(data), 0);
232              assert.deepEqual(p.parseLookupList(lookupTableParsers), [
233                  { lookupType: 4, lookupFlag: 0x000c, subtables: [0x1234], markFilteringSet: undefined },
234                  { lookupType: 4, lookupFlag: 0x000c, subtables: [0x5678], markFilteringSet: undefined },
235                  { lookupType: 4, lookupFlag: 0x000c, subtables: [0x9abc], markFilteringSet: undefined },
236              ]);
237              assert.equal(p.relativeOffset, 2);
238          });
239      });
240  });