matplotlib/matplotlib · error · RuntimeError
unmatched closing token {token}
Error message
unmatched closing token {token} What it means
_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.
Source
Thrown at lib/matplotlib/_type1font.py:307
The token that triggered parsing a balanced expression.
tokens : iterator of _Token
Following tokens.
data : bytes
Underlying data that the token positions point to.
Returns
-------
_BalancedExpression
"""
delim_stack = []
token = initial
while True:
if token.is_delim():
if token.raw in ('[', '{'):
delim_stack.append(token)
elif token.raw in (']', '}'):
if not delim_stack:
raise RuntimeError(f"unmatched closing token {token}")
match = delim_stack.pop()
if match.raw != token.opposite():
raise RuntimeError(
f"opening token {match} closed by {token}"
)
if not delim_stack:
break
else:
raise RuntimeError(f'unknown delimiter {token}')
elif not delim_stack:
break
token = next(tokens)
return _BalancedExpression(
initial.pos,
data[initial.pos:token.endpos()].decode('ascii', 'replace')
)
View on GitHub (pinned to b379c1b69e)
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.
Defensive patterns
Strategy: try-catch
Validate before calling
def quick_ps_sanity(data: bytes) -> bool:
head = data[:65536]
return (data.startswith(b'\x80') or head.startswith(b'%!')) and b'eexec' in head Type guard
def is_loadable_type1font(path) -> bool:
from matplotlib.type1font import Type1Font
try:
Type1Font(str(path))
return True
except (ValueError, RuntimeError):
return False Try / catch
try:
font = Type1Font(path)
except RuntimeError as e:
if 'unmatched closing token' in str(e):
font = Type1Font(default_path)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unterminated string starting at {start}
- Unterminated hex string starting at {start}
- opening token {match} closed by {token}
- unknown delimiter {token}
- Token preceding subr must be {self._abbr['RD']}, was {token}
AI-assisted analysis of matplotlib/matplotlib@b379c1b69e (2026-08-21).
Data as JSON: /api/errors/3992e3f89de560a1.
Report an issue: GitHub.