File: test_named_destinations.py

package info (click to toggle)
fpdf2 2.8.7-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 114,352 kB
  • sloc: python: 50,410; sh: 133; makefile: 12
file content (214 lines) | stat: -rw-r--r-- 6,669 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
from pathlib import Path

from fpdf import FPDF
from fpdf.enums import XPos, YPos
from test.conftest import assert_pdf_equal

import pytest

HERE = Path(__file__).resolve().parent


def test_named_destinations(tmp_path):
    """Test PDF generation with named destinations and links.

    This test demonstrates how to create a hierarchical structure of named destinations
    by using named destinations directly and binding them to pages later.
    """
    # Create PDF
    pdf = FPDF()

    # Create the first page first
    pdf.add_page()

    # Create the main TOC destination directly
    pdf.set_link(name="dests/main")

    # For backward compatibility, you can still use add_link with named destinations
    # We only need to create a link for subsection1 this way for testing
    sub1_link = pdf.add_link(name="dests/subsection1", page=2)

    # Subsections 2 and 3 will be referenced directly by name
    # without pre-creating them

    # First page is already created
    pdf.set_font("Helvetica", size=16)
    # Main TOC destination is already bound to this location
    pdf.cell(0, 10, "Table of Contents", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
    pdf.ln(10)
    pdf.set_font("Helvetica", size=12)

    # Add links to subsections in the TOC
    pdf.cell(
        0,
        10,
        "Subsection 1: Concepts",
        link=sub1_link,
        new_x=XPos.LMARGIN,
        new_y=YPos.NEXT,
    )

    # You can directly reference a destination by name
    # Even if it hasn't been set yet
    pdf.cell(
        0,
        10,
        "Subsection 2: Implementation",
        link="#dests/subsection2",  # Using the '#' prefix to reference the named destination
        new_x=XPos.LMARGIN,
        new_y=YPos.NEXT,
    )

    # Use direct named reference for subsection 3 too
    pdf.cell(
        0,
        10,
        "Subsection 3: Advanced Usage",
        link="#dests/subsection3",  # Named destinations automatically created when referenced
        new_x=XPos.LMARGIN,
        new_y=YPos.NEXT,
    )

    # Subsection 1 page
    pdf.add_page()
    pdf.set_font("Helvetica", size=16)
    # Bind subsection1 destination to this location
    pdf.set_link(sub1_link)
    pdf.cell(0, 10, "Subsection 1: Concepts", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
    pdf.set_font("Helvetica", size=12)
    pdf.multi_cell(
        0,
        10,
        (
            "This page demonstrates binding a previously created named destination. "
            "The destination 'dests/subsection1' was created before this page existed "
            "and is now bound to this location.\n\n"
            "Click on the table of contents link to return."
        ),
    )
    # Add a link back to TOC
    pdf.cell(
        0,
        10,
        "Return to Table of Contents",
        link="#dests/main",
        new_x=XPos.LMARGIN,
        new_y=YPos.NEXT,
    )

    # Subsection 2 page
    pdf.add_page()
    pdf.set_font("Helvetica", size=16)
    # Bind subsection2 destination to this location using name directly
    pdf.set_link(name="dests/subsection2")
    pdf.cell(0, 10, "Subsection 2: Implementation", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
    pdf.set_font("Helvetica", size=12)
    pdf.multi_cell(
        0,
        10,
        (
            "This page shows how named destinations maintain stability when page numbers "
            "change. Even if pages are added or removed, the named destination 'dests/subsection2' "
            "will always point to this content.\n\n"
            "Click on the table of contents link to return."
        ),
    )
    # Add a link back to TOC
    pdf.cell(
        0,
        10,
        "Return to Table of Contents",
        link="#dests/main",
        new_x=XPos.LMARGIN,
        new_y=YPos.NEXT,
    )

    # Subsection 3 page
    pdf.add_page()
    pdf.set_font("Helvetica", size=16)
    # Bind subsection3 destination to this location using name directly
    pdf.set_link(name="dests/subsection3")
    pdf.cell(0, 10, "Subsection 3: Advanced Usage", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
    pdf.set_font("Helvetica", size=12)
    pdf.multi_cell(
        0,
        10,
        (
            "Named destinations can be referenced anywhere in the document, including in "
            "bookmarks, outlines, and from external documents. The hierarchical naming scheme "
            "'dests/subsection3' allows for organized structure.\n\n"
            "Click on the table of contents link to return."
        ),
    )
    # Add a link back to TOC using direct named reference
    pdf.cell(
        0,
        10,
        "Return to Table of Contents",
        link="#dests/main",
        new_x=XPos.LMARGIN,
        new_y=YPos.NEXT,
    )

    # Compare generated PDF to reference using assert_pdf_equal
    assert_pdf_equal(pdf, HERE / "named_destinations.pdf", tmp_path)


def test_invalid_destination():
    """Test that using non-existent index still raises a KeyError."""
    pdf = FPDF()
    pdf.add_page()
    with pytest.raises(KeyError):
        pdf.set_link(99999)


def test_duplicate_destinations():
    """Test that creating multiple destinations with the same name works."""
    pdf = FPDF()
    pdf.add_page()
    link1 = pdf.add_link(name="test")
    link2 = pdf.add_link(name="test")
    # Should not raise any error when setting either link
    pdf.set_link(link1)
    pdf.set_link(link2)


def test_set_link_with_name():
    """Test setting a named destination using set_link with name parameter."""
    pdf = FPDF()
    pdf.add_page()

    # Create a named destination with set_link
    name = pdf.set_link(name="direct_destination")

    # Verify the name was returned
    assert name == "direct_destination"

    # Verify the destination exists
    assert "direct_destination" in pdf.named_destinations

    # Create another page and reference the destination
    pdf.add_page()
    pdf.set_font("Helvetica", size=12)
    pdf.cell(0, 10, "Link to first page", link="#direct_destination")

    # This should not raise an error
    dest_link = pdf.get_named_destination("direct_destination")
    assert dest_link is not None


def test_unset_destinations_error():
    """Test that referencing but not setting a destination raises an error."""
    pdf = FPDF()
    pdf.add_page()
    pdf.set_font("Helvetica", size=12)

    # Reference a destination but don't set it
    pdf.cell(0, 10, "Link to nowhere", link="#missing-destination")

    # Trying to output should raise an exception
    with pytest.raises(Exception) as excinfo:
        pdf.output(HERE / "should_not_be_created.pdf")

    # Verify error message contains the missing destination name
    assert "missing-destination" in str(excinfo.value)