Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#5122·PyMuPDF

`get_drawings()` reports a wrong start point for a subpath following a `re` with negative width/height

Author: tolot27Created Sep 15, 2026Updated Sep 15, 2026

Description of the bug

After the re operator, the PDF specification (ISO 32000-1, 8.5.2.1) leaves the current point at the operand origin (x, y). If a path continues with l or c without an intervening m, that segment must start at (x, y).

When w or h is negative, (x, y) is not a corner of the normalised rectangle. In that case get_drawings() reports the following segment as starting at the opposite corner of the normalised rectangle instead of at the re origin. The renderer is correct; only the extracted geometry is wrong.

The error equals the rectangle's height (or width), so an extracted line can be skewed, mispositioned, or both, while the rendered page looks fine. Anything built on get_drawings() — geometry extraction, the documented get_drawings() → Shape round-trip, table/line detection — silently inherits the wrong coordinates.

I hit this on a real CAD floor plan: 12 paths matched the pattern, and lines that are vertical in the PDF were extracted with a 0.97 mm skew. Pixel measurements on the rendered page confirmed the rendered geometry, not the extracted one.

How to reproduce the bug

Reproducer

This script writes a minimal PDF and prints the result — no external file needed. (The same PDF is attached as reproducer.pdf.)

reproducer.pdf

python
import pymupdf

content = b"1 w\n100 720 40 -20 re\n300 600 l\nS\n"

objs = [
    b"<< /Type /Catalog /Pages 2 0 R >>",
    b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
    b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 400 800] "
    b"/Contents 4 0 R /Resources << >> >>",
    b"<< /Length %d >>\nstream\n" % len(content) + content + b"endstream",
]
pdf, offsets = b"%PDF-1.4\n", []
for i, o in enumerate(objs, 1):
    offsets.append(len(pdf))
    pdf += b"%d 0 obj\n" % i + o + b"\nendobj\n"
xref = len(pdf)
pdf += b"xref\n0 %d\n" % (len(objs) + 1) + b"0000000000 65535 f \n"
for off in offsets:
    pdf += b"%010d 00000 n \n" % off
pdf += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (
    len(objs) + 1, xref)
open("reproducer.pdf", "wb").write(pdf)

page = pymupdf.open("reproducer.pdf")[0]
for d in page.get_drawings():
    for it in d["items"]:
        print(it)

Content stream

1 w
100 720 40 -20 re
300 600 l
S

re origin is (100, 720) with h = -20, so the rectangle spans PDF y 700..720. The current point after re is (100, 720), i.e. (100, 80) in page coordinates. There is no m, so the line must start there.

Actual output

('re', Rect(100.0, 80.0, 140.0, 100.0), -1)
('l', Point(100.0, 100.0), Point(300.0, 200.0))

Expected output

('re', Rect(100.0, 80.0, 140.0, 100.0), -1)
('l', Point(100.0, 80.0), Point(300.0, 200.0))

The reported start (100, 100) is the rectangle corner opposite to the re origin — off by |h| = 20.

Confirmation from rendering

MuPDF renders the line from (100, 80), not from (100, 100):

python
import pymupdf, numpy as np

page = pymupdf.open("reproducer.pdf")[0]
S = 4
pix = page.get_pixmap(matrix=pymupdf.Matrix(S, S), colorspace=pymupdf.csGRAY)
arr = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width)
col = arr[:, 200 * S]                       # sample the line at x = 200
print([d / S for d in np.where(col < 128)[0]])

Output is y ≈ 140. A line (100, 80) → (300, 200) passes through y = 140 at x = 200; the reported (100, 100) → (300, 200) would give y = 150.

Screenshots

  • pymupdf_bug_1_correct.png — the actual MuPDF rendering.
  • pymupdf_bug_2_wrong.png — the same page rebuilt from get_drawings() output via Shape (the round-trip described in the docs).
  • pymupdf_bug_3_overlay.png — both overlaid; the start points differ by the rectangle height while the end point matches.
ImageImageImage

Variants tested

Content stream re orientation Reported start Correct?
100 700 40 20 re + 100 600 m + 300 600 l +1 (100, 200) yes
100 720 40 -20 re + 100 600 m + 300 600 l -1 (100, 200) yes
100 720 40 -20 re + 300 600 l (no m) -1 (100, 100) no
140 720 -40 -20 re + 100 600 m + 300 600 l -1 (100, 200) yes

So the trigger is a negative w/h together with a following segment that has no explicit m. With an explicit m the start point is correct.

Additional note

get_drawings() does not expose the re operands, only the normalised Rect and an orientation flag. Since -1 can result from a negative width, a negative height, or both, callers cannot reconstruct the origin corner themselves and therefore cannot work around this reliably. It would help if the orientation flag were replaced (or complemented) by something that identifies the origin corner.

Environment

Reproduced in a clean container, PyMuPDF installed from PyPI via pip install pymupdf.

PyMuPDF version

1.28.2

Operating system

Linux

Python version

3.12

Source: pymupdf/PyMuPDF

View original on GitHubView discussion on GitHub