ocrmypdf/OCRmyPDF · warning

PDF graphics stack overflowed spec limit

Error message

PDF graphics stack overflowed spec limit

What it means

While interpreting a PDF content stream, more than 32 'q' (save-state) operators were nested without matching 'Q' (restore) — exceeding the PDF spec's graphics state stack limit. The library tolerates up to 128 before a hard RuntimeError; this warning means ink coverage analysis may be imprecise.

Source

Thrown at src/ocrmypdf/pdfinfo/_contentstream.py:192

    found_text = False
    vector_ops = set(['S', 's', 'f', 'F', 'f*', 'B', 'B*', 'b', 'b*'])
    text_showing_ops = set(["TJ", "Tj", '"', "'"])
    image_ops = set(['BI', 'ID', 'EI', 'q', 'Q', 'Do', 'cm'])
    color_ops = set(['g', 'rg', 'k', 'cs', 'sc', 'scn'])
    operator_whitelist = ' '.join(vector_ops | text_showing_ops | image_ops | color_ops)

    for n, graphobj in enumerate(
        _normalize_stack(parse_content_stream(contentstream, operator_whitelist))
    ):
        operands, operator = graphobj
        if operator == 'q':
            stack.append((ctm, fill_ink, fill_space))
            if len(stack) > 32:  # See docstring
                if len(stack) > 128:
                    raise RuntimeError(
                        f"PDF graphics stack overflowed hard limit at operator {n}"
                    )
                warn("PDF graphics stack overflowed spec limit")
        elif operator == 'Q':
            try:
                ctm, fill_ink, fill_space = stack.pop()
            except IndexError:
                # Keeping the state the same seems to be the only sensible thing
                # to do. Just pretend nothing happened, keep calm and carry on.
                warn("PDF graphics stack underflowed - PDF may be malformed")
        elif operator == 'cm':
            try:
                ctm = Matrix(operands) @ ctm
            except ValueError as e:
                raise InputFileError(
                    "PDF content stream is corrupt - this PDF is malformed. "
                    "Use a PDF editor that is capable of visually inspecting the PDF."
                ) from e
        elif operator == 'g':
            if vals := _operand_floats(operands):
                fill_ink = _ink_from_components('gray', vals)

View on GitHub (pinned to 5074a0b0e1)

Solutions

  1. The warning is non-fatal — processing continues; no action strictly required
  2. If ink analysis misbehaves, flatten/repair the PDF first (qpdf --stream-data=uncompress, pikepdf)
  3. Regenerate the offending PDF from its source if you control the generator

Example fix

# before
info = PdfInfo.from_path(path)  # may warn on abusive PDFs
# after
import pikepdf
with pikepdf.open(path, allow_overwriting_input=True) as pdf:
    pdf.save(path)  # normalized copy first
info = PdfInfo.from_path(path)
Defensive patterns

Strategy: fallback

Validate before calling

import pikepdf\nwith pikepdf.open(path) as pdf:\n    pdf.save(normalized_path)  # rebalances/normalizes streams

Try / catch

with warnings.catch_warnings():\n    warnings.simplefilter('ignore', UserWarning, message='PDF graphics stack')\n    process(path)

Prevention

When it happens

Trigger: Interpreting content streams (used for preprocessing decisions / page ink analysis) of PDFs that stack many q operators — typically machine-generated or malicious/abusive PDFs.

Common situations: PDF generators that emit long q runs; fuzzed/adversarial PDFs; the accompanying test test_stack_abuse exercises exactly this.

Related errors


AI-assisted analysis of ocrmypdf/OCRmyPDF@5074a0b0e1 (2026-08-27). Data as JSON: /api/errors/785876040c4da944. Report an issue: GitHub.