{"record":{"id":"3992e3f89de560a1","repo":"matplotlib/matplotlib","slug":"unmatched-closing-token-token","errorCode":null,"errorMessage":"unmatched closing token {token}","messagePattern":"unmatched closing token (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/matplotlib/_type1font.py","lineNumber":307,"sourceCode":"        The token that triggered parsing a balanced expression.\n    tokens : iterator of _Token\n        Following tokens.\n    data : bytes\n        Underlying data that the token positions point to.\n\n    Returns\n    -------\n    _BalancedExpression\n    \"\"\"\n    delim_stack = []\n    token = initial\n    while True:\n        if token.is_delim():\n            if token.raw in ('[', '{'):\n                delim_stack.append(token)\n            elif token.raw in (']', '}'):\n                if not delim_stack:\n                    raise RuntimeError(f\"unmatched closing token {token}\")\n                match = delim_stack.pop()\n                if match.raw != token.opposite():\n                    raise RuntimeError(\n                        f\"opening token {match} closed by {token}\"\n                    )\n                if not delim_stack:\n                    break\n            else:\n                raise RuntimeError(f'unknown delimiter {token}')\n        elif not delim_stack:\n            break\n        token = next(tokens)\n    return _BalancedExpression(\n        initial.pos,\n        data[initial.pos:token.endpos()].decode('ascii', 'replace')\n    )\n\n","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/matplotlib/matplotlib/blob/b379c1b69e012b142c0f496a52bcb30513802d72/lib/matplotlib/_type1font.py#L289-L325","documentation":"_balanced_expression (lib/matplotlib/_type1font.py:307) walks the token stream of structural PostScript constructs (e.g. the /Subrs and /CharStrings arrays) keeping a delimiter stack. A closing ']' or '}' arriving when the stack is empty means a close with no matching open, and it raises RuntimeError('unmatched closing token {token}') showing the offending token and its position.","triggerScenarios":"Type1Font loading a font whose PostScript dict/array syntax is broken — an extra ']' or '}' before any opening bracket; hand-patched fonts; output of a broken Type-1 generator emitting mismatched delimiters; byte corruption that turns some byte into ']' inside a parsed region.","commonSituations":"Third-party or converted Type-1 fonts (old vendor .pfb, LaTeX-distro fonts) with sloppy PostScript that other renderers (Ghostscript) tolerate but matplotlib's strict tokenizer rejects; truncated files reassembled incorrectly.","solutions":["Round-trip the font through fontTools.t1Lib or FontForge (they re-emit canonical syntax) and use the result.","Replace the font with a known-good Type-1, or switch the figure's font stack to TrueType/OpenType, which matplotlib handles via FT2Font.","If you generate fonts programmatically, emit balanced delimiters and test-load each output.","Wrap Type1Font construction in try/except RuntimeError and fall back to a default font so export still succeeds."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"def quick_ps_sanity(data: bytes) -> bool:\n    head = data[:65536]\n    return (data.startswith(b'\\x80') or head.startswith(b'%!')) and b'eexec' in head","typeGuard":"def is_loadable_type1font(path) -> bool:\n    from matplotlib.type1font import Type1Font\n    try:\n        Type1Font(str(path))\n        return True\n    except (ValueError, RuntimeError):\n        return False","tryCatchPattern":"try:\n    font = Type1Font(path)\nexcept RuntimeError as e:\n    if 'unmatched closing token' in str(e):\n        font = Type1Font(default_path)\n    else:\n        raise","preventionTips":["Round-trip third-party Type-1 fonts through FontForge/fontTools before shipping them with an application.","Keep a validated font fixture set in CI so a bad upstream font fails the build, not production."],"tags":["matplotlib","fonts","type1","postscript","syntax"],"backgroundTag":"corrupt-font-file","analyzedSha":"b379c1b69e012b142c0f496a52bcb30513802d72","analyzedAt":"2026-08-21T23:31:55.468Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}