Pomax / lib-font

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

Access to all GSUB lookup type 3 variants. #102

Closed Pomax closed 3 years ago

Pomax commented 3 years ago

This prompted me to drop in the Athena Ruby font (large number of cvXX features), and I noticed that the UI doesn't have provision for enumerated variants in type 3 GSUB lookups, so only shows the first variant option. -- Tiro Typeworks

I grabbed a copy of the font and have stubbed a new test file for it, but if anyone has additional details they want to drop into this issue to make sure the right things come out of that test: please do =)

Pomax commented 3 years ago

The current code technically gives access to all alternatesets, but it doesn't "pregenerate" them:

class LookupType3 extends LookupType {
  constructor(p) {
    super(p);
    this.alternateSetCount = p.uint16;
    this.alternateSetOffsets = [...new Array(this.alternateSetCount)].map(
      (_) => p.Offset16
    );
  }
  getAlternateSet(index) {
    let p = this.parser;
    p.currentPosition = this.start + this.alternateSetOffsets[index];
    return new AlternateSetTable(p);
  }
}

class AlternateSetTable {
  constructor(p) {
    this.glyphCount = p.uint16;
    this.alternateGlyphIDs = [...new Array(this.glyphCount)].map(
      (_) => p.uint16
    );
  }
}

(https://github.com/Pomax/Font.js/blob/master/src/opentype/tables/advanced/shared/subtables/gsub.js#L54-L76)

So the "immediately" option is to go "check which format it is, the know you need to access alternateSetCount and for-loop your way to the data" but that's kind of crummy, and all GSUB formats should have some kind of same-named function to get the "payloads" that each points to.

I've set up https://github.com/Pomax/Font.js/blob/master/testing/node/athena.ruby.test.js as a convenient file to do Athena Ruby related testing.

Pomax commented 3 years ago

Looking at what code would currently be required, we see this:

/**
 * Given a script/lang/feature/lookup combination that is type 3:
 */
function lookup3Alternates(font, script, lang, feature, lookupId, lookup) {
  if (lookup.lookupType !== 3) return;

  // for argument's sake, let's only focus on lookupid=15 used by cv01
  if (lookupId !== 15) return;

  // get the subtable (we happen to know there's only 1 but we could
  // run this iteratively using lookup.subTableCount, too)
  const subtable = lookup.getSubTable(0);

  // get the first (and only) alternates set and coverage it applies to.
  // again, we know there's only 1 but we could run this iteratively
  // using subtable.alternateSetCount
  const altset = subtable.getAlternateSet(0);
  const coverage = subtable.getCoverageTable(0);

  console.log(subtable, coverage, altset);
}

This yields:

LookupType3 {
  substFormat: 1,
  coverageOffset: 64,
  alternateSetCount: 1,
  alternateSetOffsets: [ 8 ]
}

CoverageTable {
  coverageFormat: 1,
  glyphCount: 1,
  glyphArray: [ 4 ]
}

AlternateSetTable {
  glyphCount: 27,
  alternateGlyphIDs: [
    5,  6,  7,  8,  9, 10, 11, 12,
    13, 14, 15, 16, 17, 18, 19, 20,
    21, 22, 23, 24, 25, 26, 27, 28,
    29, 30, 31
  ]
}

So it's not a matter of "the data is inaccessible", but really more "how low level vs. high level do we make the API"?

Pomax commented 3 years ago

Updating the post table to yield glyph names, the following code in lookup3Alternates:

  const getGlyphName = id => font.opentype.tables.post.getGlyphName(id);
  console.log(getGlyphName(coverage.glyphArray[0]), `⇒`, altset.alternateGlyphIDs.map(getGlyphName));

yields

      Alpha ⇒ [
        'Alpha.1',  'Alpha.2',  'Alpha.3',
        'Alpha.4',  'Alpha.5',  'Alpha.6',
        'Alpha.7',  'Alpha.8',  'Alpha.9',
        'Alpha.10', 'Alpha.11', 'Alpha.12',
        'Alpha.13', 'Alpha.14', 'Alpha.15',
        'Alpha.16', 'Alpha.17', 'Alpha.18',
        'Alpha.19', 'Alpha.20', 'Alpha.21',
        'Alpha.22', 'Alpha.23', 'Alpha.24',
        'Alpha.25', 'Alpha.26', 'Alpha.27'
      ]
RoelN commented 3 years ago

Awesome work, thanks!

I don't need the individual glyphs for Wakamai Fondue, as its modus operandi is to take the input character and then let the browser show all variants through applying the proper CSS.

Relevant WF PR: https://github.com/Wakamai-Fondue/wakamai-fondue-site/pull/91 Works like advertised, thanks again! My regular test fonts are all shown correctly.

Pomax commented 3 years ago

nice, in that case I'll close this but mark it as some code that might work as a recipe.

RoelN commented 3 years ago

The snippet above was very helpful, so a great candidate for a recipe!