Story: a background block that ENDS on one page repaints its padding at the top of the next page
Description of the bug
A block-level element with a background-color and padding that ends on
one page has its padding repainted as a filled bar at the top of the next
page.
The bar is padding-top + padding-bottom tall, starts padding-top above the
frame that was passed to Story.place(), and is emitted before the page's
real content — so the first line of text on that page is printed on top of it.
With a dark background and dark body text, that line is unreadable.
The element is finished. It contributes nothing else to the following page, and nothing in the markup asks for a background there.
This is not the same thing as a background that genuinely continues across a page break. A real continuation starts at the same y but is as tall as the content that continues; this bar has zero content height.
How to reproduce the bug
import pymupdf
MEDIABOX = pymupdf.paper_rect("letter")
FRAME = MEDIABOX + (54, 54, -54, -54)
HTML = (
'<html><body>'
'<div style="background-color:#1f3a5f;color:#ffffff;padding:12pt">BANNER</div>'
"<p>" + ("filler words " * 400) + "</p>"
"</body></html>"
)
story = pymupdf.Story(html=HTML)
writer = pymupdf.DocumentWriter("out.pdf")
story.write(writer, lambda rect_num, filled: (MEDIABOX, FRAME, None))
writer.close()
doc = pymupdf.open("out.pdf")
for n in range(1, doc.page_count):
for d in doc[n].get_drawings():
r = d["rect"]
if r.y0 < FRAME.y0:
print(f"page {n + 1}: fill at y {r.y0}..{r.y1} "
f"(frame starts at {FRAME.y0}), height {r.y1 - r.y0}")Output on 1.28.2 and on 1.27.2.3:
page 2: fill at y 42.0..66.0 (frame starts at 54.0), height 24.042 = 54 - 12 (one padding-top above the frame) and 24 = 12 + 12
(padding-top + padding-bottom). The first line of body text on page 2 has its
baseline at y 64.8, inside that bar.
The page content stream shows the ordering — the bar is painted immediately
after the clip and before the first BT:
1 0 0 -1 -0 792 cm
q
54 54 504 684 re
W n
.12156863 .22745098 .37254904 rg
66 42 480 24 re
f
0 0 0 rg
BT
/F0 12 Tf
1 0 0 -1 66 64.8 Tm
...MuPDF's own clip removes the part above y 54, so what survives is a 12pt bar lying under the first line of text.
Expected: no background is painted on page 2 for an element that ended on page 1.
Notes
- Reproduced with
Story.write()above so that no user-written place/draw loop is involved. A hand-writtenplace()/draw()loop produces byte-identical stray rects. paddingis required — withpadding: 0there is no bar. The block must also be followed by enough content to paginate; the block alone fits on one page and there is no second page to repaint onto.- A background block taller than one page behaves correctly: on the continuation page it starts at the same y but is as tall as the remaining content (measured: 700.8pt against this bar's 24pt), so the two cases are distinguishable by height.
- Found in the wild in a 14-page generated report whose page 2 opened with a dark bar painted across its first table row. The reduction above is the smallest markup we could get it down to; we did not isolate which of that document's several padded, coloured blocks produced the bar.
PyMuPDF version
1.28.2 (also reproduced on 1.27.2.3)
Python version / OS
Python 3.12, Linux x86-64
Source: pymupdf/PyMuPDF