Skip to content

The bidi L1 pass reads 'type' off a line-break hyphen that was appended without one, and ADLaM Display stopped rendering #116

Description

@jakejackson1

A regression between 4388dc7 and 28aec23. Same font, same text, same settings:

4388dc7 28aec23
useOTL 0, kerning off clean Undefined array key "type" at Bidi.php:1095
useOTL 0, kerning on clean same
useOTL 0xFF, kerning off clean same
useOTL 0xFF, kerning on clean clean

Found by rendering the whole google/fonts catalogue — 3,328 faces across 1,942 families — at both commits. It is the only face of the 3,328 that warns anywhere in Bidi.php, and the only family whose verdict went backwards across the bump: ADLaM Display rendered at useOTL => 0xFF on the old commit and cannot be rendered at all on the new one, because the warning arrives at useOTL 0 where there is no fallback left.

What the loop reads

Bidi.php:1094, the L1 pass:

$numchars = count($bidiData);
…
for ($i = ($numchars - 1); $i > 0; $i--) {
    if ($bidiData[$i]['type'] == Ucdn::BIDI_CLASS_WS || (isset($bidiData[$i]['orig_type']) && $bidiData[$i]['orig_type'] == Ucdn::BIDI_CLASS_WS)) {

The loop starts at the last element, and for this document the last element has no type. Dumped at that point:

NOTYPE at 91 of 92: array (
  'level' => 0,
  'uni' => 45,
  'group' => 'C',
  'chunkid' => 0,
)

uni 45 is -. The array is contiguous — array_keys($bidiData) === range(0, count($bidiData) - 1) holds — so this is not an index off the end; it is an element that was appended with level, uni, group and chunkid and no type. The sample text does not end in a hyphen (it ends … Mn), and the line here is 92 characters of a 4,512-character paragraph, so the hyphen is one mPDF added breaking the line rather than one that was typed.

orig_type is isset()-guarded on the same line. type is not.

Reproducing

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

ofl/adlamdisplay/ADLaMDisplay-Regular.ttf from google/fonts, unmodified. It needs enough text to wrap and break a word — a few thousand characters of Adlam. Reproduces on the upstream bytes and on our dehinted build alike.

Under an E_ALL handler this throws. Without one it is a log line per render — but the comparison the loop is making is then against null, so the L1 reset for that element is decided on a value that was never set.

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