Skip to content

Commit a7a0dcf

Browse files
committed
Recent changes
1 parent 5a06e96 commit a7a0dcf

File tree

6 files changed

+108
-70
lines changed

6 files changed

+108
-70
lines changed

docs/user/adding-pdf-outlines.md

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,73 +15,86 @@ Adds an outline (bookmark) entry to the PDF document.
1515

1616
```python
1717
add_outline_item(
18+
self,
1819
title: str,
19-
page_number: int | None = None,
20-
parent: Any | None = None,
21-
color: tuple | None = None,
20+
page_number: Union[None, PageObject, IndirectObject, int],
21+
parent: Union[None, TreeObject, IndirectObject] = None,
22+
before: Union[None, TreeObject, IndirectObject] = None,
23+
color: Optional[Union[tuple[float, float, float], str]] = None,
2224
bold: bool = False,
2325
italic: bool = False,
26+
fit: Fit = PAGE_FIT,
2427
is_open: bool = True,
25-
fit: str | None = None,
26-
zoom: float | None = None
27-
) -> Any
28+
) -> IndirectObject:
2829
```
2930

3031

3132
## Parameters
3233

3334
The following parameters are available for `add_outline_item()`:
3435

35-
| Name | Type | Default | Description |
36-
|--------------|---------------------------|---------|-------------|
37-
| `title` | `str` || The visible text label shown in the PDF outline panel. |
38-
| `page_number`| `int`, optional | `None` | Zero-based target page index. If `None`, the item becomes a non-clickable parent/group header. |
39-
| `parent` | `Any`, optional | `None` | The parent outline item under which this one will be nested. If omitted, this becomes a top-level outline. |
40-
| `color` | `tuple`, optional | `None` | RGB color tuple with values between `0–1`. Example: `(1, 0, 0)` for red. |
41-
| `bold` | `bool` | `False` | If `True`, the outline title is displayed in bold. |
42-
| `italic` | `bool` | `False` | If `True`, the outline title is displayed in italic. |
43-
| `is_open` | `bool` | `True` | Whether the outline node is expanded when the PDF opens. |
44-
| `fit` | `str`, optional | `None` | Controls how the destination page is displayed (Fit, FitH, FitV, FitR, XYZ). |
45-
| `zoom` | `float`, optional | `None` | Used only when `fit="XYZ"`. Example: `1.0` = 100% zoom. |
36+
| Name | Type | Default | Description |
37+
| ------------- | ------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- |
38+
| `title` | `str` || The visible text label shown in the PDF outline panel. |
39+
| `page_number` | `None`, `int`, `PageObject`, or `IndirectObject` || Destination page for the outline item. May be set to `None`, making the entry non-clickable and usable as a parent/group node. |
40+
| `parent` | `None`, `TreeObject`, or `IndirectObject` | `None` | Makes the outline item a child of the given parent outline node. If omitted, it becomes a top-level entry. |
41+
| `before` | `None`, `TreeObject`, or `IndirectObject` | `None` | Inserts the outline item before another existing outline item at the same level. Used to control ordering. |
42+
| `color` | `tuple[float, float, float]` or `str`, optional | `None` | Sets the outline text color. Tuples must use 0–1 float values (e.g., `(1, 0, 0)` for red). Some named colors may also be accepted. |
43+
| `bold` | `bool` | `False` | Displays the outline title in bold. |
44+
| `italic` | `bool` | `False` | Displays the outline title in italic. |
45+
| `fit` | `Fit` | `PAGE_FIT` | Determines how the destination page is displayed (Fit, FitH, FitV, FitR, XYZ, etc.). |
46+
| `is_open` | `bool` | `True` | Controls whether the outline node appears expanded in the PDF viewer when opened. |
47+
4648

4749
### Fit Mode Options
4850

49-
| Value | Meaning |
50-
|--------|---------|
51-
| `"Fit"` | Display the entire page. |
52-
| `"FitH"` | Fit to width, aligned at the top. |
53-
| `"FitV"` | Fit to height. |
54-
| `"FitR"` | Fit a specific rectangle region. |
55-
| `"XYZ"` | Use a custom zoom level (`zoom=` required). |
51+
| Fit Method | Meaning |
52+
| --------------------------------------------- | ------------------------------------------ |
53+
| `Fit.fit()` | Display the entire page. |
54+
| `Fit.fit_horizontally(top)` | Fit page width, aligned at the given top. |
55+
| `Fit.fit_vertically(left)` | Fit page height, aligned at the given left.|
56+
| `Fit.fit_rectangle(left, bottom, right, top)` | Fit a specific rectangular region. |
57+
| `Fit.xyz(left, top, zoom)` | Custom position and zoom level. |
58+
5659

5760

5861

59-
## **Return Type:** `Any`
62+
## **Return Type:** `IndirectObject`
6063

61-
The method returns a reference to the created outline item.
62-
This reference is typically used when creating nested (child) outline items.
64+
Returns a reference to the created outline item, which can be used when adding nested children.
6365

6466
### Example
6567
```python
66-
parent = writer.add_outline_item("Chapter 1", page_number=0)
67-
writer.add_outline_item("Section 1.1", page_number=1, parent=parent)
68+
chapter = writer.add_outline_item("Chapter 1", page_number=0)
69+
writer.add_outline_item("Section 1.1", page_number=1, parent=chapter)
6870
```
6971

7072

7173
## Exceptions
7274

7375
The `add_outline_item()` method may raise the following exceptions:
7476

75-
| Exception | When it occurs |
76-
|-----------------|----------------|
77-
| `ValueError` | Raised when `page_number` is out of range, `fit` is invalid, or `color` is not a valid `(r, g, b)` tuple (each value must be a float between 0–1). |
78-
| Internal errors | Occur if an invalid `parent` reference is passed, or if the outline tree becomes corrupted internally. |
77+
| Exception | When it occurs |
78+
|---------------|----------------|
79+
| `ValueError` | Raised when `page_number` is out of range, the `fit` argument is invalid, or when the `color` tuple contains values outside the 0–1 float range. |
80+
| `TypeError` | Raised when arguments such as `parent`, `before`, or `color` are provided using unsupported types. |
81+
| `IndexError` | May occur if a referenced page index is not available in the document. |
82+
7983

8084

8185

8286
## Example: Full PDF Outline with All Parameters
8387

84-
```python
88+
```{testsetup}
89+
pypdf_test_setup("user/adding-pdf-outlines", {
90+
"output.pdf": "../resources/output.pdf",
91+
"example1.pdf": "../resources/example1.pdf",
92+
"input.pdf": "../resources/input.pdf"
93+
94+
})
95+
```
96+
97+
```{testcode}
8598
from pypdf import PdfReader, PdfWriter
8699
from pypdf.generic._fit import Fit # Use Fit only
87100
@@ -105,7 +118,7 @@ chapter1 = writer.add_outline_item(
105118
106119
# Section under Chapter 1 (dark green, italic, collapsed)
107120
section1_1 = writer.add_outline_item(
108-
title="Section 1.1: Overview",
121+
title="Section 1.1: Getting Started",
109122
page_number=1,
110123
parent=chapter1,
111124
color=(0, 0.5, 0),
@@ -117,7 +130,7 @@ section1_1 = writer.add_outline_item(
117130
118131
# Section with custom zoom
119132
section1_2 = writer.add_outline_item(
120-
title="Section 1.2: Details",
133+
title="Section 1.2: Printing a Test Page",
121134
page_number=2,
122135
parent=chapter1,
123136
color=(1, 0, 0),
@@ -153,8 +166,6 @@ writer.add_outline_item(
153166
output_path = "output.pdf"
154167
with open(output_path, "wb") as f:
155168
writer.write(f)
156-
157-
print(f"PDF with outlines created successfully: {output_path}")
158169
```
159170

160171
### What this code demonstrates
@@ -169,7 +180,8 @@ print(f"PDF with outlines created successfully: {output_path}")
169180
## Adding a Simple Outline
170181

171182
Use this when you want a single top-level bookmark pointing to a page.
172-
```python
183+
184+
```{testcode}
173185
from pypdf import PdfReader, PdfWriter
174186
175187
reader = PdfReader("input.pdf")
@@ -189,6 +201,7 @@ with open("output.pdf", "wb") as f:
189201
writer.write(f)
190202
```
191203

204+
192205
### What the simple outline code does
193206

194207
* Loads the original PDF
@@ -204,12 +217,12 @@ with open("output.pdf", "wb") as f:
204217
Nested outlines create structures like:
205218

206219
```text
207-
Chapter 1
220+
Introduction
208221
├── Section 1.1
209222
└── Section 1.2
210223
```
211224

212-
```python
225+
```{testcode}
213226
from pypdf import PdfReader, PdfWriter
214227
215228
reader = PdfReader("input.pdf")
@@ -219,35 +232,36 @@ writer = PdfWriter()
219232
for page in reader.pages:
220233
writer.add_page(page)
221234
222-
# Add parent (chapter)
223-
chapter = writer.add_outline_item(
224-
title="Chapter 1",
235+
# Add parent (Introduction)
236+
introduction = writer.add_outline_item(
237+
title="Introduction",
225238
page_number=0
226239
)
227240
228241
# Add children (sections)
229242
writer.add_outline_item(
230-
title="Section 1.1",
243+
title="Section 1",
231244
page_number=1,
232-
parent=chapter
245+
parent=introduction
233246
)
234247
235248
writer.add_outline_item(
236-
title="Section 1.2",
249+
title="Section 2",
237250
page_number=2,
238-
parent=chapter
251+
parent=introduction
239252
)
240253
241254
with open("output.pdf", "wb") as f:
242255
writer.write(f)
243256
```
244257

258+
245259
### What the nested outline code does
246260

247261
* Copies all pages into the new PDF
248-
* Creates a top-level outline called Chapter 1
249-
* Adds Section 1.1 under that chapter
250-
* Adds Section 1.2 under the same chapter
262+
* Creates a top-level outline called Introduction
263+
* Adds Section 1 under that Introduction
264+
* Adds Section 2 under the same Introduction
251265
* Produces an outline tree like:
252266

253267
![PDF outline output](nested-outline.png)

docs/user/nested-outline.png

100 KB
Loading

docs/user/reading-pdf-outlines.md

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
```{testsetup}
2-
pypdf_test_setup("user/reading-pdf-outlines", {
3-
"example.pdf": "../resources/example.pdf",
4-
})
5-
```
6-
71
# Reading PDF Outlines
82

93
PDF outlines (also called *bookmarks*) help users navigate through a document.
@@ -71,17 +65,29 @@ This is how pypdf internally represents nested outlines:
7165

7266
Use this method if your PDF has a flat list of outlines.
7367

74-
7568
```{testsetup}
69+
pypdf_test_setup("user/reading-pdf-outlines", {
70+
"example1.pdf": "../resources/example1.pdf",
71+
"output.pdf": "../resources/output.pdf"
72+
})
73+
```
74+
75+
```{testcode}
7676
from pypdf import PdfReader
7777
78-
reader = PdfReader("example.pdf")
78+
reader = PdfReader("example1.pdf")
7979
8080
for outline in reader.outline:
8181
page_num = reader.get_destination_page_number(outline)
8282
print(f"{outline.title} -> page {page_num + 1}")
8383
```
8484

85+
```{testoutput}
86+
:hide:
87+
88+
Introduction -> page 1
89+
```
90+
8591
### What this simple code does:
8692
* Loops through each top-level outline item
8793
* Gets the page number for that outline
@@ -92,26 +98,44 @@ for outline in reader.outline:
9298

9399
Use this method when your PDF contains parent → child outline items
94100

95-
```python
101+
```{testcode}
96102
from pypdf import PdfReader
97103
98-
def print_outline(outlines, level=0):
104+
def print_outline(outlines, reader, level=0):
99105
"""Recursively print all outline items with indentation."""
100106
for item in outlines:
101-
if isinstance(item, list): # This is a nested list of bookmarks
102-
print_outline(item, level + 1)
107+
if isinstance(item, list): # nested children
108+
print_outline(item, reader, level + 1)
103109
else:
104-
page_num = reader.get_destination_page_number(item)
110+
try:
111+
page_num = reader.get_destination_page_number(item)
112+
except Exception:
113+
page_num = None
114+
105115
indent = " " * level
106-
print(f"{indent}- {item.title} (Page {page_num + 1})")
107116
108-
reader = PdfReader("example.pdf")
109-
print_outline(reader.outline)
117+
if page_num is None:
118+
print(f"{indent}- {item.title} (No page destination)")
119+
else:
120+
print(f"{indent}- {item.title} (Page {page_num + 1})")
121+
122+
reader = PdfReader("output.pdf")
123+
print_outline(reader.outline, reader)
110124
```
111125

126+
```{testoutput}
127+
:hide:
128+
129+
- Introduction (Page 1)
130+
- Section 1 (Page 2)
131+
- Section 2 (Page 3)
132+
```
133+
134+
112135
### What this nested code does:
113-
* Recursively handles nested outline structures
114-
* Adds indentation for each depth level
115-
* Prints a clean hierarchical view with page numbers
136+
* Recursively handles hierarchical outline structures
137+
* Indents output according to nesting depth
138+
* Prints page numbers when available
139+
* Safely handles undefined or invalid destinations
116140

117141

resources/example1.pdf

7.91 MB
Binary file not shown.

resources/input.pdf

7.91 MB
Binary file not shown.

resources/output.pdf

7.91 MB
Binary file not shown.

0 commit comments

Comments
 (0)