Skip to content

<IMG> width attribute behaviour seems wrong #4886

Closed

Description

@wiz21b

Description of the bug

Hello,

I'm trying to generate a PDF from an HTML. I use the Story mechanism much like explained. My html code is like this (there's not styling):

<table>
    <tr>
        <td><img src="aplot.png" width="10"/></td>
        <td>ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf sd
            ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf s</td>
    </tr>
</table>

If I don't specify the <img...>'s width attribute, the image is rendered in the PDF with a size proportional to the original PNG. In particular, the size of the table's cell containing the image more or less fits the size of the PNG image, which looks fine to me. Now, if I set the width attribute of the IMG tag to something very small, the image is rendered in the PDF as a very small image (which is expected). However, the size of the table cell containing the image remains the same as when the PNG is not resized. I would have expected that the size of the cell would be adjusted to the size of the re-sized <img...>.

I've read that pymupdf doesn't render tables quite like a browser but I don't know if this issue is related to that.

Thanks, S.

How to reproduce the bug

Here's the code I use to generate the failure:

    # Create a Story object with HTML (no archive needed with data URIs)

    story = pymupdf.Story(html=html_content, archive=archive)
    # Define page dimensions (A4)
    mediabox = pymupdf.paper_rect("A4")
    where = mediabox + (36, 36, -36, -36)  # 36pt margins

    # Create a DocumentWriter
    pdf_in_mem = BytesIO()
    writer = pymupdf.DocumentWriter(pdf_in_mem)

    # Place content across multiple pages
    more = 1
    while more:
        # Begin a new page and get the device
        device = writer.begin_page(mediabox)

        # Place content on this page
        more, filled = story.place(where)

        # Draw the content
        story.draw(device)

        # End the page
        writer.end_page()

    # Close the writer
    writer.close()

    # Write to file
    pdf_in_mem.seek(0)
    pdf_bytes = pdf_in_mem.read()

    if to_pdf:
        with open(to_pdf, "wb") as pdf_out:
            pdf_out.write(pdf_bytes)
            print(f"Wrote {to_pdf}")

PyMuPDF version

1.26.7

Operating system

Windows

Python version

3.11

Activity

  1. JorjMcKie commented on Feb 5, 2026

    @JorjMcKie
    Collaborator

    I've #3041 (comment) that pymupdf doesn't render tables quite like a browser but I don't know if this issue is related to that.

    This expectation is unfounded, because that's not the goal of the Story feature. Its purpose is to create PDFs using HTML/CSS syntax as directives - not to faithfully replicate browser behavior.

    You didn't bother to provide your image example, so I took an arbitrary one from my stock:

    <table>
        <tr>
            <td><img src="nur-ruhig.jpg" width="10" /></td>
            <td>ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf sd
                ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf s</td>
        </tr>
    </table>

    After modifying your example code:

    import io
    from pathlib import Path
    import pymupdf
    
    # Create a Story object with HTML (no archive needed with data URIs)
    HTML = Path("html.html").read_text()
    archive = pymupdf.Archive(".")
    story = pymupdf.Story(html=HTML, archive=archive)
    # Define page dimensions (A4)
    mediabox = pymupdf.paper_rect("A4")
    where = mediabox + (36, 36, -36, -36)  # 36pt margins
    
    # Create a DocumentWriter
    pdf_in_mem = io.BytesIO()
    writer = pymupdf.DocumentWriter(pdf_in_mem)
    
    # Place content across multiple pages
    more = 1
    while more:
        # Begin a new page and get the device
        device = writer.begin_page(mediabox)
    
        # Place content on this page
        more, filled = story.place(where)
    
        # Draw the content
        story.draw(device)
    
        # End the page
        writer.end_page()
    
    # Close the writer
    writer.close()
    
    # Write to file
    pdf_in_mem.seek(0)
    pdf_bytes = pdf_in_mem.read()
    Path("output.pdf").write_bytes(pdf_bytes)

    I am getting this PDF - as I would have expected

    output.pdf

    After changing width to some other value - 50 - I'm getting again an apparently reasoonable result:

    output.pdf

    So, summing up I cannot confirm a problem.

  2. wiz21b commented on Feb 5, 2026

    @wiz21b
    Author

    Thank you for your quick answer. really strange. Just for the record I'm going to assemble a minimal working program. As you have guessed, I gave you an extract of a bigger code. Given your code it's quite likely I'll feel ashamed in a few minutes :-)

  3. wiz21b commented on Feb 5, 2026

    @wiz21b
    Author

    So here's my code:

    import pymupdf
    from matplotlib import pyplot as plt
    from io import BytesIO
    
    def test_bug(html, pdf_name):
    
        archive = pymupdf.Archive()
    
        fig, ax = plt.subplots(1,1)
        ax.plot([1,2,4],[5,-1,3])
        fig.set_layout_engine("tight")
        fig.set_size_inches(4,4)
    
        buf = BytesIO()
        fig.savefig(buf, format='png', dpi=72)
        buf.seek(0)
        archive.add((buf.read(), "aplot.png"))
    
        story = pymupdf.Story(html=html, archive=archive)
        mediabox = pymupdf.paper_rect("A4")
        where = mediabox + (36, 36, -36, -36)  # 36pt margins
    
        pdf_in_mem = BytesIO()
        writer = pymupdf.DocumentWriter(pdf_in_mem)
    
        more = 1
        while more:
            device = writer.begin_page(mediabox)
            more, filled = story.place(where)
            story.draw(device)
            writer.end_page()
    
        writer.close()
    
        # This comes from some other code so it's a bit overkill here
        pdf_in_mem.seek(0)
        pdf_bytes = pdf_in_mem.read()
        with open(pdf_name, "wb") as pdf_out:
            pdf_out.write(pdf_bytes)
    
    
    
    if __name__ == "__main__":
        test_bug("""<table>
        <tr>
            <td><img src="aplot.png" width="72"/></td>
            <td>ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf sd
                ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf s</td>
        </tr>
    </table>""", pdf_name="test1.pdf")
    
        test_bug("""<table>
        <tr>
            <td><img src="aplot.png"/></td>
            <td>ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf sd
                ldkjf dsflksdf sdlfjdlsfjsdlfds fdlskfjdslf s</td>
        </tr>
    </table>""", pdf_name="test2.pdf")

    and here are the two generated PDF's:

    test1.pdf
    test2.pdf

    As you can see the space taken by the first cell doesn't change although the image size changes.

  4. wiz21b commented on Feb 5, 2026

    @wiz21b
    Author

    Again, I've read the web and it is definitely said that pymupdf is not like a browser. My question is more like "are there guarantees concerning how a table cell is laid out" ? I'm surprised by the way it's rendered but it may just be my expectations which are wrong.

  5. JorjMcKie commented on Feb 6, 2026

    @JorjMcKie
    Collaborator

    Thanks for the extended information. You have spotted an issue in the base library MuPDF (which unsurprisingly does the Story stuff behind the curtain).
    This issue is present in MuPDF 1.26.12 but has been resolved in the 1.27.x.
    We expect to release the next PyMuPDF version based on 1.27.x very soon.
    Here are the results based on either version.

    test1-('1.26.7', '1.26.12', None).pdf
    test1-('1.26.7', '1.27.0', None).pdf

  6. JorjMcKie commented on Feb 6, 2026

    @JorjMcKie
    Collaborator

    Sorry: I should have thought of this earlier and tested with your communicated PyMuPDF version ... 😒

  7. wiz21b commented on Feb 9, 2026

    @wiz21b
    Author

    no worries, the simple fact you answered already made me quite happy!

  8. julian-smith-artifex-com commented on Feb 11, 2026

    @julian-smith-artifex-com
    Collaborator

    Fixed in PyMuPDF-1.27.1.

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