Skip to content

Four more unguarded reads warn under E_ALL, on nine families mPDF otherwise shapes correctly #113

Description

@jakejackson1

Measured on gravitypdf at 28aec23, over the whole google/fonts catalogue — 3,328 faces across 1,942 families, each rendered at useOTL 0 and 0xFF with warnings promoted to exceptions. 63 families shape correctly and then warn; 53 of them stopped warning between 4388dc7 and head, which is #104, #96, #97, #99 and #83 landing. Nine are left, at four reads, and all four are the same shape as #104: a key the font never set, which PHP 7 answered with a notice and null and PHP 8 answers with a warning and null. The behaviour was always "treat missing as absent"; only the log changed.

Every font named below reproduces from its upstream bytes, not from anything this pipeline produced.

1. A GSUB 1/1 delta lands on a glyph the cmap never mapped

TTFontFile.php:1816, in the LookupType 1 Format 1 branch:

$substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]);

DeltaGlyphID is added to a glyph id and the sum is looked up in glyphToChar, which only holds glyphs the cmap reaches. Measured keys: Undefined array key 66822, and -7000 and -28279 where the delta wraps negative. The missing key gives null, [0] is then Trying to access array offset on null, and the null travels into unicode_hex() — so dechex(): Passing null to parameter #1 ($num) of type int is deprecated at TTFontFile.php:37 is the same bug one frame down, not a second one.

Five families, and four of them are among the ten largest in google/fonts:

family upstream
Cactus Classical Serif ofl/cactusclassicalserif/CactusClassicalSerif-Regular.ttf
Noto Sans SignWriting ofl/notosanssignwriting/NotoSansSignWriting-Regular.ttf
Chiron Sung HK, Chiron Hei HK, Chiron GoRound TC variable upstream; statics from the family's fonts.google.com download

2. A lookup flag names a mark attachment class the parser never built

$this->MarkAttachmentType[$flag >> 8], read in four places:

  • TTFontFile.php:2951strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)
  • TTFontFile.php:3024$str = $this->MarkAttachmentType[$MarkAttachmentType];
  • Otl.php:4649 — the same strpos() call as 2951
  • Otl.php:4594 — the same assignment as 3024

Undefined array key 1, then the null reaches strpos(): Passing null to parameter #1 ($haystack) at 2951 and 4649, and Otl.php:4566explode('|', $this->buildGCOMignoreList(...)) — gets the null that 4594 returned.

Two families: Carlito (ofl/carlito/Carlito-Regular.ttf, all four faces, 33 warnings each) and NATS (ofl/nats/NATS-Regular.ttf, 6,853 warnings on one page).

3. Context class 0 is the one index without the guard

TTFontFile.php:2519, GSUB context format 2:

$inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass];

Class 0 is the implicit "every glyph not named by another class" and is not in InputClasses. Three lines further down the identical read for every index above 0 already handles exactly this, with the comment saying so:

} // if class[0] = all glyphs excluding those specified in all other classes
// set to blank '' for now
else {
    $inputGlyphs[$gcl] = '';
}

Index 0 does not get that else. The null then reaches strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) at TTFontFile.php:2554.

One family: Molengo (ofl/molengo/Molengo-Regular.ttf), 32 warnings.

4. A feature with an empty lookup list indexes by null

TTFontFile.php:1437:

$lg[$ft['LookupListIndex'][0]] = $ft;

Undefined array key 0 when the feature's LookupListIndex is empty, so the key is null and Using null as an array offset is deprecated puts the row at $lg[''] — where ksort() then orders it ahead of every real lookup index.

One family: Sedan SC (ofl/sedansc/SedanSC-Regular.ttf), 20 warnings.

Reproducing

$mpdf = new \Mpdf\Mpdf([
    'mode' => 'UTF-8',
    'fontDir' => [$dir],
    'fontdata' => ['probe' => ['R' => 'Carlito-Regular.ttf', 'useOTL' => 0xFF]],
    'default_font' => 'probe',
]);
$mpdf->WriteHTML('<div>' . $text . '</div>');

with an E_ALL error handler and text that reaches the font's own lookups. The sample text here is derived per font from its GSUB and GPOS coverage rather than written by hand, so it fires the rules that are actually there.

What is not measured

Whether guarding these four leaves the nine families rendering byte-identically. That is the evidence #104's three had before they were changed, and none of these has it yet — "it is the same class of bug" is a claim about a fix, not a finding about this one.

Two other bugs came out of the same run and are filed separately: a multi-glyph substitute reaching hexdec() in the Arabic shaper, and Output() costing ~140 s per font independent of the page.

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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions