Skip to content

FPDF.multi_cell(..., dry_run=True) leaves link-artifact #1807

@CoLa5

Description

@CoLa5

If I have already a link annotation on the current page and I run a dry-run of FPDF.multi_cell(...) with markdown active and a link in the text, the 2nd link remains permanently on the page although the dry-run should stop this behavior.

Error details
No error, just a clickable area as artifact where no clickable area should be!

Minimal code

import fpdf

pdf = fpdf.FPDF()
pdf.set_font("helvetica")

# Empty page, no annotations
pdf.add_page()
assert len(pdf.pages[1].annots) == 0, len(pdf.pages[1].annots)

# Dry run without former link
pdf.multi_cell(
    w=0,
    text="This is a [md link](www.example.com)",
    dry_run=True,
    markdown=True,
    new_x=fpdf.XPos.LEFT,
    new_y=fpdf.YPos.NEXT,
)
assert len(pdf.pages[1].annots) == 0, len(pdf.pages[1].annots)

# Create link
pdf.multi_cell(
    w=0,
    text="This is a [md link](www.example.com)",
    markdown=True,
    new_x=fpdf.XPos.LEFT,
    new_y=fpdf.YPos.NEXT,
)
assert len(pdf.pages[1].annots) == 1, len(pdf.pages[1].annots)

# Dry run with former link
pdf.multi_cell(
    w=0,
    text="This is a [md link](www.example.com)",
    dry_run=True,
    markdown=True,
    new_x=fpdf.XPos.LEFT,
    new_y=fpdf.YPos.NEXT,
)
assert len(pdf.pages[1].annots) == 1, len(pdf.pages[1].annots)  # ISSUE

pdf.output("bug.pdf")

Environment
Please provide the following information:

  • Operating System: Windows
  • Python version: 3.12
  • fpdf2 version used: Master

Solution
The error originates from the function FPDF. _disable_writing(...) L4689:
The code assigns the former PDFArray of annotations of the current page to the local var annots.
After executing the wrapped function FPDF.multi_cell(...), the local var annots is assigned "back" to the page.
Since the PDFArray is basically a list, the changes of annotations in FPDF.multi_cell(...) (in self.pages[self.page].annots) will end up also in the local variable annots since both refer to the same underlying PDFArray (list).
So, annots must create a copy of the original self.pages[self.page].annots-instance: Either by wrapping it in a new PDFArray-instance (easy solution) or by using proper type collections.UserList so that the PDFArray is a "real" subclass of a list and so that PDFArray.copy() returns a copy of a PDFArray and no list (more complex).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions