diff --git a/pypdf/_writer.py b/pypdf/_writer.py index 0545e8200..51b92b0e1 100644 --- a/pypdf/_writer.py +++ b/pypdf/_writer.py @@ -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, @@ -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 @@ -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, @@ -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( @@ -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() diff --git a/pypdf/generic/_data_structures.py b/pypdf/generic/_data_structures.py index 9b87bd061..94f1f2425 100644 --- a/pypdf/generic/_data_structures.py +++ b/pypdf/generic/_data_structures.py @@ -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") diff --git a/tests/test_writer.py b/tests/test_writer.py index 00d3e513f..ca5492b43 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -4,6 +4,7 @@ import subprocess from io import BytesIO from pathlib import Path +from typing import Any import pytest @@ -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