Pomax / lib-font

This library adds a new Font() object to the JavaScript toolbox, similar to new Image() for images
MIT License
730 stars 72 forks source link

GSUB bug when decoding Castoro #123

Closed RoelN closed 3 years ago

RoelN commented 3 years ago

Twitter thread where this was first raised: https://twitter.com/TiroTypeworks/status/1356325050782150656

Font can be downloaded here: https://github.com/TiroTypeworks/Castoro (I used this WOFF2)

Here's a reduced test case:

import { Font } from "./lib-font.js";

const font = new Font("test");
font.src = `./fonts/broken/Castoro-Regular.woff2`;

font.onerror = (evt) => console.error(evt);
font.onload = (evt) => {
  let font = evt.detail.font;

  const GSUB = font.opentype.tables["GSUB"];
  const scripts = GSUB.getSupportedScripts();
  let allGlyphs = {};

  scripts.forEach((script) => {
    let langsys = GSUB.getSupportedLangSys(script);

    allGlyphs[script] = {};

    langsys.forEach((lang) => {
      let langSysTable = GSUB.getLangSysTable(script, lang);
      let features = GSUB.getFeatures(langSysTable);

      allGlyphs[script][lang] = {};

      features.forEach((feature) => {
        const lookupIDs = feature.lookupListIndices;
        allGlyphs[script][lang][feature.featureTag] = {};
        allGlyphs[script][lang][feature.featureTag]["lookups"] = [];

        lookupIDs.forEach((id) => {
          const lookup = GSUB.getLookup(id);

          // Castoro bugs here
          if (lookup.lookupType === 6) {
            lookup.subtableOffsets.forEach((_, i) => {
              console.log(`+++ Getting subtable ${i}, lookup ${id}, feature ${feature.featureTag}, script ${script} , lang ${lang} +++`);
              const subtable = lookup.getSubTable(i);
            });
          }
        });
      });
    });
  });
};

Running this yields:

+++ Getting subtable 0, lookup 6, feature ccmp, script latn , lang dflt +++
parser getUint16 2 Parser {
  name: 'GSUB',
  length: 6524,
  start: 4554,
  offset: 1972,
  data: DataView {
    byteLength: 6524,
    byteOffset: 0,
    buffer: ArrayBuffer {
      [Uint8Contents]: <00 01 00 00 00 0a 00 b4 01 66 00 01 6c 61 74 6e 00 08 00 22 00 05 43 41 54 20 00 3a 4d 4f 4c 20 00 6e 4e 4c 44 20 00 54 52 4f 4d 20 00 6e 54 52 4b 20 00 88 00 00 ff ff 00 09 00 00 00 01 00 02 00 03 00 04 00 05 00 0a 00 0b 00 0c 00 00 ff ff 00 0a 00 00 00 01 00 02 00 03 00 04 00 05 00 06 00 0a 00 0b ... 6424 more bytes>,
      byteLength: 6524
    }
  }
}
parser 4554 1972
Event {
  type: 'error',
  detail: RangeError: Offset is outside the bounds of the DataView
      at DataView.getUint16 (<anonymous>)
      at Parser.getValue (file:///Users/roel/code/lib-font/src/parser.js:60:29)
      at Parser.get (file:///Users/roel/code/lib-font/src/parser.js:38:25)
      at Parser.get Offset16 [as Offset16] (file:///Users/roel/code/lib-font/src/parser.js:130:17)
      at file:///Users/roel/code/lib-font/src/opentype/tables/advanced/shared/subtables/gsub.js:283:22
      at Array.map (<anonymous>)
      at new LookupType6 (file:///Users/roel/code/lib-font/src/opentype/tables/advanced/shared/subtables/gsub.js:283:9)
      at Object.buildSubtable (file:///Users/roel/code/lib-font/src/opentype/tables/advanced/shared/subtables/gsub.js:475:16)
      at LookupTable.getSubTable (file:///Users/roel/code/lib-font/src/opentype/tables/advanced/shared/lookup.js:53:20)
      at file:///Users/roel/code/lib-font/test.js:37:39,
  msg: 'Failed to load font at ./fonts/broken/Castoro-Regular.woff2'
}
RoelN commented 3 years ago

Possibly related to https://github.com/Pomax/lib-font/issues/105 ?

Pomax commented 3 years ago

I've slightly changed the test code to not single out lookup type 6:

function testFont(font) {
  const { GSUB } = font.opentype.tables;
  const scripts = GSUB.getSupportedScripts();
  let allGlyphs = {};

  scripts.forEach((script) => {
    let langsys = GSUB.getSupportedLangSys(script);

    allGlyphs[script] = {};

    langsys.forEach((lang) => {
      let langSysTable = GSUB.getLangSysTable(script, lang);
      let features = GSUB.getFeatures(langSysTable);

      allGlyphs[script][lang] = {};

      features.forEach((feature) => {
        const lookupIDs = feature.lookupListIndices;
        allGlyphs[script][lang][feature.featureTag] = {};
        allGlyphs[script][lang][feature.featureTag]["lookups"] = [];

        lookupIDs.forEach((id) => {
          const lookup = GSUB.getLookup(id);

          lookup.subtableOffsets.forEach((_, i) => {
            console.log(
              `Getting subtable ${i}, lookup ${id}, feature ${feature.featureTag}, script ${script} , lang ${lang}`
            );
            const subtable = lookup.getSubTable(i);
          });
        });
      });
    });
  });
}
Pomax commented 3 years ago

This yields:

Getting subtable 0, lookup 14, feature c2sc, script latn , lang dflt
Getting subtable 0, lookup 15, feature c2sc, script latn , lang dflt
Getting subtable 0, lookup 17, feature case, script latn , lang dflt
Getting subtable 0, lookup 18, feature case, script latn , lang dflt
Getting subtable 0, lookup 4, feature ccmp, script latn , lang dflt
Getting subtable 0, lookup 5, feature ccmp, script latn , lang dflt
Getting subtable 0, lookup 6, feature ccmp, script latn , lang dflt
parser getUint16 2 Parser {
  name: 'GSUB',
  length: 6524,
  start: 4554,
  offset: 1972,
  data: DataView {
    byteLength: 6524,
    byteOffset: 0,
    buffer: ArrayBuffer {
      [Uint8Contents]: <00 01 00 00 00 0a 00 b4 01 66 00 01 6c 61 74 6e 00 08 00 22 00 05 43 41 54 20 00 3a 4d 4f 4c 20 00 6e 4e 4c 44 20 00 54 52 4f 4d 20 00 6e 54 52 4b 20 00 88 00 00 ff ff 00 09 00 00 00 01 00 02 00 03 00 04 00 05 00 0a 00 0b 00 0c 00 00 ff ff 00 0a 00 00 00 01 00 02 00 03 00 04 00 05 00 06 00 0a 00 0b ... 6424 more bytes>,
      byteLength: 6524
    }
  }
}
parser 4554 1972
RangeError: Offset is outside the bounds of the DataView
    at DataView.getUint16 (<anonymous>)
    at Parser.getValue (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/parser.js:60:29)
    at Parser.get (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/parser.js:38:25)
    at Parser.get Offset16 [as Offset16] (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/parser.js:130:17) 
    at file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/shared/subtables/lookuptypes/gsub-6.js:26:22
    at Array.map (<anonymous>)
    at new LookupType6 (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/shared/subtables/lookuptypes/gsub-6.js:26:9)
    at Object.buildSubtable (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/shared/subtables/gsub.js:24:16)
    at LookupTable.getSubTable (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/shared/lookup.js:53:20)
    at file:///C:/Users/Mike/Documents/Git/released/lib-font/testing/manual/custom/castoro-parsing.js:42:37
Pomax commented 3 years ago

Looking at getSubTable() and adding a try/catch:

  getSubTable(index) {
    const builder = this.ctType === `GSUB` ? GSUBtables : GPOStables;
    this.parser.currentPosition = this.start + this.subtableOffsets[index];
    console.log(`getSubTable(${index}): this.parser.currentPosition = ${this.start} + ${this.subtableOffsets[index]}`)
    try {
      return builder.buildSubtable(this.lookupType, this.parser);
    } catch (e) {
      console.log({
        ...this,
        start: this.start
      });
      throw e;
    }
  }

Yields:

Getting subtable 0, lookup 14, feature c2sc, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 5002 + 8
Getting subtable 0, lookup 15, feature c2sc, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 5462 + 8
Getting subtable 0, lookup 17, feature case, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 5982 + 8
Getting subtable 0, lookup 18, feature case, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 6060 + 8
Getting subtable 0, lookup 4, feature ccmp, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 620 + 8
Getting subtable 0, lookup 5, feature ccmp, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 3926 + 8
Getting subtable 0, lookup 6, feature ccmp, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 4546 + 8
going
parser getUint16 2 Parser {
  name: 'GSUB',
  length: 6524,
  start: 4554,
  offset: 1972,
  data: DataView {
    byteLength: 6524,
    byteOffset: 0,
    buffer: ArrayBuffer {
      [Uint8Contents]: <00 01 00 00 00 0a 00 b4 01 66 00 01 6c 61 74 6e 00 08 00 22 00 05 43 41 54 20 00 3a 4d 4f 4c 20 00 6e 4e 4c 44 20 00 54 52 4f 4d 20 00 6e 54 52 4b 20 00 88 00 00 ff ff 00 09 00 00 00 01 00 02 00 03 00 04 00 05 00 0a 00 0b 00 0c 00 00 ff ff 00 0a 00 00 00 01 00 02 00 03 00 04 00 05 00 06 00 0a 00 0b ... 6424 more bytes>,
      byteLength: 6524
    }
  }
}
parser 4554 1972
{
  ctType: 'GSUB',
  lookupType: 6,
  lookupFlag: 0,
  subTableCount: 1,
  subtableOffsets: [ 8 ],
  markFilteringSet: 2,
  start: 4546
}
RangeError: Offset is outside the bounds of the DataView
    at DataView.getUint16 (<anonymous>)
    at Parser.getValue (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/parser.js:60:29)
    at Parser.get (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/parser.js:38:25)
    at Parser.get Offset16 [as Offset16] (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/parser.js:130:17) 
    at file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/lookups/gsub/lookup-type-6.js:32:22
    at Array.map (<anonymous>)
    at new LookupType6 (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/lookups/gsub/lookup-type-6.js:32:9)
    at Object.buildSubtable (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/shared/subtables/gsub.js:24:16)
    at LookupTable.getSubTable (file:///C:/Users/Mike/Documents/Git/released/lib-font/src/opentype/tables/advanced/shared/lookup.js:55:22)
    at file:///C:/Users/Mike/Documents/Git/released/lib-font/testing/manual/custom/castoro-parsing.js:42:37
Pomax commented 3 years ago

Updating the GSUB lookup6 class:

class LookupType6 extends LookupType {
  constructor(p) {
    super(p);

    console.log(`building GSUB lookup type 6 subtable, with substFormat ${this.substFormat}`);

Shows:

...
Getting subtable 0, lookup 6, feature ccmp, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 4546 + 8
building GSUB lookup type 6 subtable, with substFormat 2

So let's investigate the code specific to 6.2

Pomax commented 3 years ago
...
    if (this.substFormat === 2) {
      this.coverageOffset = p.Offset16;
      this.backtrackClassDefOffset = p.Offset16;
      this.inputClassDefOffset = p.Offset16;
      this.lookaheadClassDefOffset = p.Offset16;
      this.chainSubClassSetCount = p.uint16;

      console.log(`Data found prior to binding this.chainSubClassSetOffsets:`, {
        coverageOffset: this.coverageOffset,
        backtrackClassDefOffset: this.backtrackClassDefOffset,
        inputClassDefOffset: this.inputClassDefOffset,
        lookaheadClassDefOffset: this.lookaheadClassDefOffset,
        chainSubClassSetCount: this.chainSubClassSetCount,
      });

      this.chainSubClassSetOffsets = [
        ...new Array(this.chainSubClassSetCount),
      ].map((_) => p.Offset16);
    }
...

Yields:

Getting subtable 0, lookup 6, feature ccmp, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 4546 + 8
building GSUB lookup type 6 subtable, with substFormat 2
Data found prior to binding this.chainSubClassSetOffsets: {
  coverageOffset: 42,
  backtrackClassDefOffset: 130,
  inputClassDefOffset: 1556,
  lookaheadClassDefOffset: 15,
  chainSubClassSetCount: 1634
}

And that might be a valid chainSubClassSetCount but it looks terribly suspicious

Pomax commented 3 years ago
...
      console.log(`Trying to build this.chainSubClassSetOffsets...`)
      try {
        this.chainSubClassSetOffsets = [
          ...new Array(this.chainSubClassSetCount),
        ].map((_) => p.Offset16);
      } catch (e) {
        console.error(`yeah that failed.`);
        throw e;
      }

Shows

Getting subtable 0, lookup 6, feature ccmp, script latn , lang dflt
getSubTable(0): this.parser.currentPosition = 4546 + 8
building GSUB lookup type 6 subtable, with substFormat 2
Data found prior to binding this.chainSubClassSetOffsets: {
  coverageOffset: 42,
  backtrackClassDefOffset: 130,
  inputClassDefOffset: 1556,
  lookaheadClassDefOffset: 15,
  chainSubClassSetCount: 1634
}
Trying to build this.chainSubClassSetOffsets...
yeah that failed.
Pomax commented 3 years ago

Time to break out the documentation: https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#lookuptype-6-chained-contexts-substitution-subtable tells us that we also need to look at https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#chseqctxt2

Our table format should be:

Type Name Description
uint16 format Format identifier: format = 2
Offset16 coverageOffset Offset to Coverage table, from beginning of ChainedSequenceContextFormat2 table
Offset16 backtrackClassDefOffset Offset to ClassDef table containing backtrack sequence context, from beginning of ChainedSequenceContextFormat2 table
Offset16 inputClassDefOffset Offset to ClassDef table containing input sequence context, from beginning of ChainedSequenceContextFormat2 table
Offset16 lookaheadClassDefOffset Offset to ClassDef table containing lookahead sequence context, from beginning of ChainedSequenceContextFormat2 table
uint16 chainedClassSeqRuleSetCount Number of ChainedClassSequenceRuleSet tables
Offset16 chainedClassSeqRuleSetOffsets[chainedClassSeqRuleSetCount] Array of offsets to ChainedClassSequenceRuleSet tables, from beginning of ChainedSequenceContextFormat2 table (may be NULL)
Pomax commented 3 years ago

In the subtable we see:

class LookupType6 extends LookupType {
  constructor(p) {
    super(p);

    ...

    if (this.substFormat === 2) {
      this.coverageOffset = p.Offset16;
      this.backtrackClassDefOffset = p.Offset16;
      this.inputClassDefOffset = p.Offset16;
      this.lookaheadClassDefOffset = p.Offset16;
      this.chainSubClassSetCount = p.uint16;

But if we look at the super class, we see:

class LookupType extends ParsedData {
  constructor(p) {
    super(p);
    this.substFormat = p.uint16;
    this.coverageOffset = p.Offset16;
  }

So there's our bug: we're double-parsing this.coverageOffset, once in the super class, and then again in the specific class

Pomax commented 3 years ago

Removing the erroneous double parse from the LookupType6 class:

class LookupType6 extends LookupType {
  constructor(p) {
    super(p);

    ...

    if (this.substFormat === 2) {
      this.backtrackClassDefOffset = p.Offset16;
      this.inputClassDefOffset = p.Offset16;
      this.lookaheadClassDefOffset = p.Offset16;
      this.chainSubClassSetCount = p.uint16;
      this.chainSubClassSetOffsets = [
        ...new Array(this.chainSubClassSetCount),
      ].map((_) => p.Offset16);
    }

    ...

With updated test:

...
          lookup.subtableOffsets.forEach((_, i) => {
            const subtable = lookup.getSubTable(i);
            console.log(
              `Getting lookup ${`${id}`.padStart(2, ' ')}, subtable ${i}, lookuptype ${subtable.type}, format ${subtable.substFormat}, feature ${feature.featureTag}, script ${script}, lang ${lang}`
            );
          });
...

Now gives us:

Getting lookup 14, subtable 0, lookuptype 1, format 2, feature c2sc, script latn, lang dflt
Getting lookup 15, subtable 0, lookuptype 1, format 1, feature c2sc, script latn, lang dflt
Getting lookup 17, subtable 0, lookuptype 1, format 2, feature case, script latn, lang dflt
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature case, script latn, lang dflt
Getting lookup  4, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang dflt
Getting lookup  5, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang dflt
Getting lookup  6, subtable 0, lookuptype 6, format 2, feature ccmp, script latn, lang dflt
Getting lookup 10, subtable 0, lookuptype 1, format 2, feature frac, script latn, lang dflt
Getting lookup 11, subtable 0, lookuptype 1, format 1, feature frac, script latn, lang dflt
Getting lookup 12, subtable 0, lookuptype 6, format 2, feature frac, script latn, lang dflt
Getting lookup 19, subtable 0, lookuptype 6, format 2, feature liga, script latn, lang dflt
Getting lookup 21, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang dflt
Getting lookup 21, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang dflt
Getting lookup 23, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang dflt
Getting lookup 23, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang dflt
Getting lookup 25, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang dflt
Getting lookup 27, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang dflt
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature lnum, script latn, lang dflt
Getting lookup 16, subtable 0, lookuptype 1, format 2, feature smcp, script latn, lang dflt
Getting lookup  9, subtable 0, lookuptype 1, format 1, feature subs, script latn, lang dflt
Getting lookup  8, subtable 0, lookuptype 1, format 1, feature sups, script latn, lang dflt
Getting lookup 14, subtable 0, lookuptype 1, format 2, feature c2sc, script latn, lang CAT
Getting lookup 15, subtable 0, lookuptype 1, format 1, feature c2sc, script latn, lang CAT
Getting lookup 17, subtable 0, lookuptype 1, format 2, feature case, script latn, lang CAT
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature case, script latn, lang CAT
Getting lookup  4, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang CAT
Getting lookup  5, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang CAT
Getting lookup  6, subtable 0, lookuptype 6, format 2, feature ccmp, script latn, lang CAT
Getting lookup 10, subtable 0, lookuptype 1, format 2, feature frac, script latn, lang CAT
Getting lookup 11, subtable 0, lookuptype 1, format 1, feature frac, script latn, lang CAT
Getting lookup 12, subtable 0, lookuptype 6, format 2, feature frac, script latn, lang CAT
Getting lookup 19, subtable 0, lookuptype 6, format 2, feature liga, script latn, lang CAT
Getting lookup 21, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang CAT
Getting lookup 21, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang CAT
Getting lookup 23, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang CAT
Getting lookup 23, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang CAT
Getting lookup 25, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang CAT
Getting lookup 27, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang CAT
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature lnum, script latn, lang CAT
Getting lookup  0, subtable 0, lookuptype 4, format 1, feature locl, script latn, lang CAT
Getting lookup 16, subtable 0, lookuptype 1, format 2, feature smcp, script latn, lang CAT
Getting lookup  9, subtable 0, lookuptype 1, format 1, feature subs, script latn, lang CAT
Getting lookup  8, subtable 0, lookuptype 1, format 1, feature sups, script latn, lang CAT
Getting lookup 14, subtable 0, lookuptype 1, format 2, feature c2sc, script latn, lang MOL
Getting lookup 15, subtable 0, lookuptype 1, format 1, feature c2sc, script latn, lang MOL
Getting lookup 17, subtable 0, lookuptype 1, format 2, feature case, script latn, lang MOL
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature case, script latn, lang MOL
Getting lookup  4, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang MOL
Getting lookup  5, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang MOL
Getting lookup  6, subtable 0, lookuptype 6, format 2, feature ccmp, script latn, lang MOL
Getting lookup 10, subtable 0, lookuptype 1, format 2, feature frac, script latn, lang MOL
Getting lookup 11, subtable 0, lookuptype 1, format 1, feature frac, script latn, lang MOL
Getting lookup 12, subtable 0, lookuptype 6, format 2, feature frac, script latn, lang MOL
Getting lookup 19, subtable 0, lookuptype 6, format 2, feature liga, script latn, lang MOL
Getting lookup 21, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang MOL
Getting lookup 21, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang MOL
Getting lookup 23, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang MOL
Getting lookup 23, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang MOL
Getting lookup 25, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang MOL
Getting lookup 27, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang MOL
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature lnum, script latn, lang MOL
Getting lookup  2, subtable 0, lookuptype 1, format 1, feature locl, script latn, lang MOL
Getting lookup 16, subtable 0, lookuptype 1, format 2, feature smcp, script latn, lang MOL
Getting lookup  9, subtable 0, lookuptype 1, format 1, feature subs, script latn, lang MOL
Getting lookup  8, subtable 0, lookuptype 1, format 1, feature sups, script latn, lang MOL
Getting lookup 14, subtable 0, lookuptype 1, format 2, feature c2sc, script latn, lang NLD
Getting lookup 15, subtable 0, lookuptype 1, format 1, feature c2sc, script latn, lang NLD
Getting lookup 17, subtable 0, lookuptype 1, format 2, feature case, script latn, lang NLD
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature case, script latn, lang NLD
Getting lookup  4, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang NLD
Getting lookup  5, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang NLD
Getting lookup  6, subtable 0, lookuptype 6, format 2, feature ccmp, script latn, lang NLD
Getting lookup 10, subtable 0, lookuptype 1, format 2, feature frac, script latn, lang NLD
Getting lookup 11, subtable 0, lookuptype 1, format 1, feature frac, script latn, lang NLD
Getting lookup 12, subtable 0, lookuptype 6, format 2, feature frac, script latn, lang NLD
Getting lookup 19, subtable 0, lookuptype 6, format 2, feature liga, script latn, lang NLD
Getting lookup 21, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang NLD
Getting lookup 21, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang NLD
Getting lookup 23, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang NLD
Getting lookup 23, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang NLD
Getting lookup 25, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang NLD
Getting lookup 27, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang NLD
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature lnum, script latn, lang NLD
Getting lookup  1, subtable 0, lookuptype 4, format 1, feature locl, script latn, lang NLD
Getting lookup 16, subtable 0, lookuptype 1, format 2, feature smcp, script latn, lang NLD
Getting lookup  9, subtable 0, lookuptype 1, format 1, feature subs, script latn, lang NLD
Getting lookup  8, subtable 0, lookuptype 1, format 1, feature sups, script latn, lang NLD
Getting lookup 14, subtable 0, lookuptype 1, format 2, feature c2sc, script latn, lang ROM
Getting lookup 15, subtable 0, lookuptype 1, format 1, feature c2sc, script latn, lang ROM
Getting lookup 17, subtable 0, lookuptype 1, format 2, feature case, script latn, lang ROM
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature case, script latn, lang ROM
Getting lookup  4, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang ROM
Getting lookup  5, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang ROM
Getting lookup  6, subtable 0, lookuptype 6, format 2, feature ccmp, script latn, lang ROM
Getting lookup 10, subtable 0, lookuptype 1, format 2, feature frac, script latn, lang ROM
Getting lookup 11, subtable 0, lookuptype 1, format 1, feature frac, script latn, lang ROM
Getting lookup 12, subtable 0, lookuptype 6, format 2, feature frac, script latn, lang ROM
Getting lookup 19, subtable 0, lookuptype 6, format 2, feature liga, script latn, lang ROM
Getting lookup 21, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang ROM
Getting lookup 21, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang ROM
Getting lookup 23, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang ROM
Getting lookup 23, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang ROM 
Getting lookup 25, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang ROM
Getting lookup 27, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang ROM
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature lnum, script latn, lang ROM
Getting lookup  2, subtable 0, lookuptype 1, format 1, feature locl, script latn, lang ROM
Getting lookup 16, subtable 0, lookuptype 1, format 2, feature smcp, script latn, lang ROM
Getting lookup  9, subtable 0, lookuptype 1, format 1, feature subs, script latn, lang ROM
Getting lookup  8, subtable 0, lookuptype 1, format 1, feature sups, script latn, lang ROM
Getting lookup 14, subtable 0, lookuptype 1, format 2, feature c2sc, script latn, lang TRK
Getting lookup 15, subtable 0, lookuptype 1, format 1, feature c2sc, script latn, lang TRK
Getting lookup 17, subtable 0, lookuptype 1, format 2, feature case, script latn, lang TRK
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature case, script latn, lang TRK
Getting lookup  4, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang TRK
Getting lookup  5, subtable 0, lookuptype 4, format 1, feature ccmp, script latn, lang TRK
Getting lookup  6, subtable 0, lookuptype 6, format 2, feature ccmp, script latn, lang TRK
Getting lookup 10, subtable 0, lookuptype 1, format 2, feature frac, script latn, lang TRK
Getting lookup 11, subtable 0, lookuptype 1, format 1, feature frac, script latn, lang TRK
Getting lookup 12, subtable 0, lookuptype 6, format 2, feature frac, script latn, lang TRK
Getting lookup 19, subtable 0, lookuptype 6, format 2, feature liga, script latn, lang TRK
Getting lookup 21, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang TRK
Getting lookup 21, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang TRK
Getting lookup 23, subtable 0, lookuptype 6, format 3, feature liga, script latn, lang TRK
Getting lookup 23, subtable 1, lookuptype 6, format 3, feature liga, script latn, lang TRK
Getting lookup 25, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang TRK
Getting lookup 27, subtable 0, lookuptype 6, format 1, feature liga, script latn, lang TRK
Getting lookup 18, subtable 0, lookuptype 1, format 1, feature lnum, script latn, lang TRK
Getting lookup  3, subtable 0, lookuptype 1, format 1, feature locl, script latn, lang TRK
Getting lookup 16, subtable 0, lookuptype 1, format 2, feature smcp, script latn, lang TRK
Getting lookup  9, subtable 0, lookuptype 1, format 1, feature subs, script latn, lang TRK
Getting lookup  8, subtable 0, lookuptype 1, format 1, feature sups, script latn, lang TRK

PR incoming

Pomax commented 3 years ago

PR #125 merged, v2.3.3 published to npm