Skip to content

Page.clip_to_rect() drops 'sh' (shading) operators #5108

Closed

Description

@BenVosper

Description of the bug

We are noticing that Page.clip_to_rect() incorrectly deletes certain graphic elements, even if they fall within the clip box. I've had this for PDFs authored in Adobe Illustrator containing certain kinds of gradients on curved lines - which are expressed in the PDF as Shading elements. I've created a minimal PDF example below.

Image

I don't know enough about the PDF spec internals to be able to debug any further, so let me know if I'm missing anything. clip_to_rect seems to perform perfectly in all other scenarios, it's just these types of graphics.

Thanks for your help!

How to reproduce the bug

Using the minimal PDF attached below, we can see that any clip_to_rect operation that does not include the origin of the page (top left) results in the gradient shading being deleted, even if the gradient is visually in the clipped area:

import pymupdf


def clipped_content(clip):
    doc = pymupdf.open("shading-repro.pdf")
    doc[0].clip_to_rect(clip)
    doc2 = pymupdf.open(stream=doc.tobytes(garbage=3, clean=True))
    return b"".join(doc2.xref_stream(x) for x in doc2[0].get_contents())


print("pymupdf", pymupdf.version)
for label, clip in [
    ("whole page                 ", pymupdf.Rect(0, 0, 400, 400)),
    ("right half (holds shading) ", pymupdf.Rect(200, 0, 400, 400)),
    ("page inset by 1pt          ", pymupdf.Rect(1, 1, 399, 399)),
    ("1x1 at PDF origin, no shading in it", pymupdf.Rect(0, 399, 1, 400)),
]:
    content = clipped_content(clip)
    print(f"  {label} -> sh kept: {b'sh' in content}   {content!r}")

This prints:

pymupdf ('1.28.2', '1.28.2', None)
  whole page                  -> sh kept: True
  right half (holds shading)  -> sh kept: False
  page inset by 1pt           -> sh kept: False
  1x1 at PDF origin, no shading in it -> sh kept: True

The final test also shows that the shading graphic is not deleted when clipping to the origin where the graphic is not present.

shading-repro.pdf

PyMuPDF version

1.28.2

Operating system

Linux

Python version

3.14

Activity

  1. JorjMcKie commented on Sep 4, 2026

    @JorjMcKie
    Collaborator

    I am transferring this to the MuPDF team.
    I am not sure whether this is even to be considered as a bug.

  2. JorjMcKie commented on Sep 4, 2026

    @JorjMcKie
    Collaborator
  3. JorjMcKie commented on Sep 4, 2026

    @JorjMcKie
    Collaborator

    @BenVosper I think there is no bug, specifically no MuPDF bug. The shading axis is (210, 0) → (390, 0).
    The clip rectangle in your page's /Contents is (210, 10) → (390, 390).
    Result:

    • The shading axis lies below the clipping rectangle.
    • MuPDF discards the shading because the axis is outside the global clip applied by pdf_clip_page(rect).
    • The object that invokes /Sh0 sh receives an “empty” shading (effectively no fill).
    • Therefore, the shading disappears whenever the page‑clip rectangle is smaller than the full page.

    But let's wait for the MuPDF team's response.

  4. BenVosper commented on Sep 4, 2026

    @BenVosper
    Author

    @JorjMcKie Thanks for the quick response!

    Messing with my example a little, I'm still seeing the shading get removed if the axis does lie inside the clip box. I get the same results as above with this version where the shading axis is (210, 50) → (390, 50) which has an identical visual appearance when applied to the box:

    shading-repro-2.pdf

    This might be a totally contrived example, but we are seeing the issue on real PDFs created in Illustrator. I'm not sure if the user can control where the shading axis itself is placed when creating gradients there.

    Thanks for passing this on 👍

  5. added a commit that references this issue on Sep 10, 2026
  6. julian-smith-artifex-com commented on Sep 22, 2026

    @julian-smith-artifex-com
    Collaborator
  7. julian-smith-artifex-com commented on Oct 19, 2026

    @julian-smith-artifex-com
    Collaborator

    Fixed in PyMuPDF-2.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions