Skip to content

Commit

Permalink
BUG: Handle empty root outline (#2239)
Browse files Browse the repository at this point in the history
Closes #2233
  • Loading branch information
pubpub-zz authored Oct 14, 2023
1 parent 4be07d0 commit 448c379
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 7 deletions.
19 changes: 13 additions & 6 deletions pypdf/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,13 @@ def _replace_object(
obj: PdfObject,
) -> PdfObject:
if isinstance(indirect_reference, IndirectObject):
assert indirect_reference.pdf == self
if indirect_reference.pdf != self:
raise ValueError("pdf must be self")
indirect_reference = indirect_reference.idnum
gen = self._objects[indirect_reference - 1].indirect_reference.generation # type: ignore
self._objects[indirect_reference - 1] = obj
obj.indirect_reference = IndirectObject(indirect_reference, gen, self)
return self._objects[indirect_reference - 1]
if indirect_reference.pdf != self:
raise ValueError("pdf must be self")
return self._objects[indirect_reference.idnum - 1]

def _add_page(
self,
Expand Down Expand Up @@ -1623,6 +1623,10 @@ def get_outline_root(self) -> TreeObject:
if CO.OUTLINES in self._root_object:
# TABLE 3.25 Entries in the catalog dictionary
outline = cast(TreeObject, self._root_object[CO.OUTLINES])
if not isinstance(outline, TreeObject):
t = TreeObject(outline)
self._replace_object(outline.indirect_reference.idnum, t)
outline = t
idnum = self._objects.index(outline) + 1
outline_ref = IndirectObject(idnum, 0, self)
assert outline_ref.get_object() == outline
Expand Down Expand Up @@ -1718,7 +1722,7 @@ def getNamedDestRoot(self) -> ArrayObject: # deprecated

def add_outline_item_destination(
self,
page_destination: Union[None, PageObject, TreeObject] = None,
page_destination: Union[None, IndirectObject, PageObject, TreeObject] = None,
parent: Union[None, TreeObject, IndirectObject] = None,
before: Union[None, TreeObject, IndirectObject] = None,
is_open: bool = True,
Expand All @@ -1744,6 +1748,7 @@ def add_outline_item_destination(
# argument is only Optional due to deprecated argument.
raise ValueError("page_destination may not be None")

page_destination = cast(PageObject, page_destination.get_object())
if isinstance(page_destination, PageObject):
return self.add_outline_item_destination(
Destination(
Expand Down Expand Up @@ -1928,7 +1933,9 @@ def add_outline_item(
}
)
)
outline_item = _create_outline_item(action_ref, title, color, italic, bold)
outline_item = self._add_object(
_create_outline_item(action_ref, title, color, italic, bold)
)

if parent is None:
parent = self.get_outline_root()
Expand Down
4 changes: 3 additions & 1 deletion pypdf/generic/_data_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,10 @@ def readFromStream(


class TreeObject(DictionaryObject):
def __init__(self) -> None:
def __init__(self, dct: Optional[DictionaryObject] = None) -> None:
DictionaryObject.__init__(self)
if dct:
self.update(dct)

def hasChildren(self) -> bool: # deprecated
deprecate_with_replacement("hasChildren", "has_children", "4.0.0")
Expand Down
81 changes: 81 additions & 0 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
from io import BytesIO
from pathlib import Path
from typing import Any

import pytest

Expand Down Expand Up @@ -1861,6 +1862,86 @@ def test_object_contains_indirect_reference_to_self():
writer.append(reader)


@pytest.mark.enable_socket()
def test_add_outlines_on_empty_dict():
"""Cf #2233"""

def _get_parent_bookmark(current_indent, history_indent, bookmarks) -> Any:
"""The parent of A is the nearest bookmark whose indent is smaller than A's"""
assert len(history_indent) == len(bookmarks)
if current_indent == 0:
return None
for i in range(len(history_indent) - 1, -1, -1):
# len(history_indent) - 1 ===> 0
if history_indent[i] < current_indent:
return bookmarks[i]
return None

bookmark_lines = """1 FUNDAMENTALS OF RADIATIVE TRANSFER 1
1.1 The Electromagnetic Spectrum; Elementary Properties of Radiation 1
1.2 Radiative Flux 2
Macroscopic Description of the Propagation of Radiation 2
Flux from an Isotropic Source-The Inverse Square Law 2
1.3 The Specific Intensity and Its Moments 3
Definition of Specific Intensity or Brightness 3
Net Flux and Momentum Flux 4
Radiative Energy Density 5
Radiation Pressure in an Enclosure Containing an Isotropic Radiation Field 6
Constancy of Specific Zntensiw Along Rays in Free Space 7
Proof of the Inverse Square Law for a Uniformly Bright Sphere 7
1.4 Radiative Transfer 8
Emission 9
Absorption 9
The Radiative Transfer Equation 11
Optical Depth and Source Function 12
Mean Free Path 14
Radiation Force 15
1.5 Thermal Radiation 15
Blackbody Radiation 15
Kirchhof's Law for Thermal Emission 16
Thermodynamics of Blackbody Radiation 17
The Planck Spectrum 20
Properties of the Planck Law 23
Characteristic Temperatures Related to Planck Spectrum 25
1.6 The Einstein Coefficients 27
Definition of Coefficients 27
Relations between Einstein Coefficients 29
Absorption and Emission Coefficients in Terms of Einstein Coefficients 30
1.7 Scattering Effects; Random Walks 33
Pure Scattering 33
Combined Scattering and Absorption 36
1.8 Radiative Diffusion 39
The Rosseland Approximation 39
The Eddington Approximation; Two-Stream Approximation 42
PROBLEMS 45
REFERENCES 50
2 BASIC THEORY OF RADIATION FIELDS 51
2.1 Review of Maxwell’s Equations 51
2.2 Plane Electromagnetic Waves 55
2.3 The Radiation Spectrum 58
2.4 Polarization and Stokes Parameters 62
Monochromatic Waves 62
Quasi-monochromatic Waves 65
2.5 Electromagnetic Potentials 69
2.6 Applicability of Transfer Theory and the Geometrical Optics Limit 72
PROBLEMS 74
REFERENCES 76"""
url = "https://github.com/py-pdf/pypdf/files/12797067/test-12.pdf"
name = "iss2233.pdf"
reader = PdfReader(BytesIO(get_data_from_url(url, name=name)))
writer = PdfWriter(clone_from=reader)

bookmarks, history_indent = [], []
for line in bookmark_lines.split("\n"):
line2 = re.split(r"\s+", line.strip())
indent_size = len(line) - len(line.lstrip())
parent = _get_parent_bookmark(indent_size, history_indent, bookmarks)
history_indent.append(indent_size)
title, page = " ".join(line2[:-1]), int(line2[-1]) - 1
new_bookmark = writer.add_outline_item(title, page, parent=parent)
bookmarks.append(new_bookmark)


def test_merging_many_temporary_files():
def create_number_pdf(n) -> BytesIO:
from fpdf import FPDF
Expand Down

0 comments on commit 448c379

Please sign in to comment.