{"record":{"id":"cd90ece590a8a024","repo":"subframe7536/maple-font","slug":"font-does-not-have-an-os-2-table","errorCode":null,"errorMessage":"Font does not have an OS/2 table.","messagePattern":"Font does not have an OS/2 table\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"source/py/task/merge_font/utils.py","lineNumber":179,"sourceCode":"\ndef _get_glyph_bounds(font, glyph_name):\n    \"\"\"Get (yMin, yMax) of a glyph by name from 'glyf' table.\"\"\"\n    glyph_order = font.getGlyphOrder()\n    if glyph_name not in glyph_order:\n        raise ValueError(f\"Glyph '{glyph_name}' not found in font.\")\n    glyf = font[\"glyf\"]\n    glyph = glyf[glyph_name]\n    if glyph.numberOfContours == 0:\n        # Empty glyph\n        return (0, 0)\n    y_min = glyph.yMin\n    y_max = glyph.yMax\n    return (y_min, y_max)\n\n\ndef auto_xheight_capheight(font: TTFont):\n    if \"OS/2\" not in font:\n        raise ValueError(\"Font does not have an OS/2 table.\")\n    if \"glyf\" not in font:\n        raise ValueError(\n            \"This script only supports TrueType (glyf) fonts. CFF support not implemented.\"\n        )\n\n    try:\n        # Get bounds\n        z_ymin, z_ymax = _get_glyph_bounds(font, \"z\")\n        Z_ymin, Z_ymax = _get_glyph_bounds(font, \"Z\")\n\n        x_height = round(z_ymax)  # xHeight = top of lowercase 'z'\n        cap_height = round(Z_ymax)  # capHeight = top of uppercase 'Z'\n\n        # Update OS/2 table\n        os2 = font[\"OS/2\"]\n        os2.sxHeight = x_height  # type: ignore\n        os2.sCapHeight = cap_height  # type: ignore\n    except Exception as e:","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/subframe7536/maple-font/blob/c08fda97fef73d68c1755219852150770f6e6578/source/py/task/merge_font/utils.py#L161-L197","documentation":"auto_xheight_capheight() needs the OS/2 table to write sxHeight and sCapHeight. If the TTFont has no 'OS/2' table it raises this ValueError immediately. Fonts produced by very old or minimal toolchains, or some icon/bitmap-derived fonts, may omit OS/2.","triggerScenarios":"Calling polish() with a line_height/metrics step that triggers auto_xheight_capheight on a font whose table directory lacks 'OS/2' (font['OS/2'] would KeyError, so the explicit check raises first).","commonSituations":"Legacy fonts predating OS/2; fonts converted from formats that don't carry OS/2; minimal test/icon fonts; fonts heavily stripped by subsetting tools that dropped the table.","solutions":["Add an OS/2 table with fontTools (e.g. copy from a base font or use fontTools.ttLib.newTable('OS/2') and set required fields).","Regenerate the font from its source with a modern compiler (fontmake/glyphs) so OS/2 is emitted.","Skip the auto x-height step for such fonts and keep existing OS/2 metrics (or guard the call with `if 'OS/2' in font`)."],"exampleFix":"# before\nauto_xheight_capheight(font)\n\n# after\nif \"OS/2\" in font:\n    auto_xheight_capheight(font)","handlingStrategy":"type-guard","validationCode":"if \"OS/2\" not in font:\n    raise RuntimeError(\"input font lacks OS/2 table; regenerate or add table before polish()\")","typeGuard":"def has_required_tables(font, tables=(\"OS/2\", \"glyf\", \"hhea\")) -> bool:\n    return all(t in font for t in tables)","tryCatchPattern":"try:\n    auto_xheight_capheight(font)\nexcept ValueError as e:\n    if \"OS/2\" in str(e):\n        font[\"OS/2\"] = newTable(\"OS/2\")  # or skip step\n    else:\n        raise","preventionTips":["Sanity-check required tables (OS/2, glyf, hhea, head) on every input font at pipeline start.","Avoid aggressively stripped fonts; keep standard tables in subsetting.","Prefer fonts built with modern toolchains that always emit OS/2."],"tags":["python","fonts","fonttools","os2-table"],"backgroundTag":"missing-font-table","analyzedSha":"c08fda97fef73d68c1755219852150770f6e6578","analyzedAt":"2026-08-28T22:07:29.503Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}