mozilla/pdf.js · error · FormatError

Required "hhea" table is not found

Error message

Required "hhea" table is not found

What it means

Thrown when the font's table directory lacks an 'hhea' (Horizontal Header) table. The hhea table is mandatory — it carries ascent, descent, lineGap, maxAdvanceWidth, and the numberOfHMetrics used by the hmtx table. Without it, horizontal metrics cannot be parsed and the font's vertical layout is undefined.

Source

Thrown at src/core/fonts.js:2987

        tables.loca,
        tables.glyf,
        numGlyphs,
        isGlyphLocationsLong,
        hintsValid,
        dupFirstEntry,
        maxSizeOfInstructions
      );
      missingGlyphs = glyphsInfo.missingGlyphs;

      // Some fonts have incorrect maxSizeOfInstructions values, so we use
      // the computed value instead.
      if (version >= 0x00010000 && tables.maxp.length >= 32) {
        tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8;
        tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255;
      }
    }
    if (!tables.hhea) {
      throw new FormatError('Required "hhea" table is not found');
    }

    // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
    // Sometimes it's 0. That needs to be fixed
    if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {
      tables.hhea.data[10] = 0xff;
      tables.hhea.data[11] = 0xff;
    }

    // Extract some more font properties from the OpenType head and
    // hhea tables; yMin and descent value are always negative.
    const metricsOverride = {
      unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),
      yMax: signedInt16(tables.head.data[42], tables.head.data[43]),
      yMin: signedInt16(tables.head.data[38], tables.head.data[39]),
      ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]),
      descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]),
      lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9]),

View on GitHub (pinned to 5903d58d58)

Solutions

  1. Replace the font with a complete copy that includes the hhea table.
  2. Rebuild the font with fonttools to regenerate the hhea table from glyph metrics.
  3. Validate with ots-sanitize — it flags missing hhea as a critical error.
  4. If subsetting fonts, use a reliable tool (pyftsubset) that preserves mandatory tables.

Example fix

# rebuild font to ensure hhea is present and valid
from fontTools import ttLib
font = ttLib.TTFont('broken.ttf')
hhea = font['hhea']  # fontTools rebuilds on save if missing/corrupt
font.save('fixed.ttf')
ots-sanitize fixed.ttf
Defensive patterns

Strategy: validation

Validate before calling

// Server-side: verify font has hhea table
// from fontTools import ttLib
// font = ttLib.TTFont('font.ttf')
// assert 'hhea' in font, 'Font missing hhea table'

Try / catch

try {
  await page.render(renderParams).promise;
} catch (err) {
  if (err.message?.includes('Required "hhea" table is not found')) {
    console.error('Font missing hhea table; replace embedded font.');
  } else { throw err; }
}

Prevention

When it happens

Trigger: After the TrueType glyph location sanitization block (or after the CFF path rejoins), the check if (!tables.hhea) at line 2986 throws. Note that sanitizeMetrics was already called with tables.hhea earlier (it handles a null hhea gracefully), but subsequent code at line 2992 reads tables.hhea.data directly.

Common situations: A corrupt font missing the hhea table due to truncation or stripping. Fonts produced by defective subsetting tools that dropped hhea. Non-standard fonts that only provide vertical metrics (vhea) without horizontal. Damaged embedded font streams in PDFs.

Related errors


AI-assisted analysis of mozilla/pdf.js@5903d58d58 (2026-08-13). Data as JSON: /api/errors/eb56679e5d8ea26e. Report an issue: GitHub.