Skip to content

The font parser keeps glyphs a ClassDef lists as class 0, which the shaper drops #415

Description

@jakejackson1

TTFontFile::_getClasses() keeps glyphs that a ClassDef explicitly lists as class 0. Otl::_getClasses() drops them. The parser and the dump therefore read class 0 differently from the shaper.

src/TTFontFile.php:

foreach (ClassDef::pairsAt($this->reader, $offset) as $pair) {
	list($glyphID, $class) = $pair;

	if (isset($this->glyphToChar[$glyphID][0])) {
		$GlyphByClass[$class][] = GlyphString::of($this->glyphToChar[$glyphID][0]);
	}
}

src/Otl.php:

if ($class > 0 && $uni) {
	$GlyphByClass[$class][$uni] = 1;
}

A Format 1 ClassDef gives a class value to every glyph in its range, so glyphs in class 0 can appear in it explicitly. TTFontFile::classGlyphs() assumes that never happens. Its docblock says: "Class 0 is every glyph the ClassDef does not mention, so a ClassDef never lists it and _getClasses() never returns a key for it."

What follows

  • Class 0 exclusions include class 0 glyphs. The class0excl strings built in TTFontFile (implode('|', $subtable['InputClasses']) and the backtrack and lookahead equivalents) include glyphs that are really in class 0. So class 0 is read as "not these", where "these" wrongly includes some class 0 glyphs.
  • A rule naming class 0 gets a glyph list. classGlyphs($classes, 0) returns the listed class 0 glyphs instead of ''. That list is written into the context-rule data the parser caches and into the OTL dump.

Scope

Found by the #394 survey. Bundled fonts with Format 1 ClassDefs that list class 0 explicitly:

  • FreeSansBold: 4 of 12
  • FreeSerif: 1 of 52
  • Garuda: all four styles
  • tests/data/ttf/NotoSansSinhala-Subset: 2 of 3

It has not been established whether the parser's cached data changes shaping, or only what the dump and caches say. A fix needs to answer that, and to compare shaping against hb-shape for any run that changes.

Expected

TTFontFile::_getClasses() drops class 0 as Otl::_getClasses() does, so the parser, the dump and the shaper agree, and the classGlyphs() docblock becomes true. Font caches and golden masters for the fonts above will change, and each change needs explaining.

mpdf/mpdf development's _getClasses() has no class 0 filter either, so this belongs upstream as well.

Found while doing #394.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions