Skip to content

Commit

Permalink
Merge branch 'main' into Orientations
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinThoma authored Jul 30, 2022
2 parents ae9047f + 8c532a0 commit 03057ac
Show file tree
Hide file tree
Showing 15 changed files with 528 additions and 300 deletions.
255 changes: 151 additions & 104 deletions PyPDF2/_merger.py

Large diffs are not rendered by default.

103 changes: 57 additions & 46 deletions PyPDF2/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
TreeObject,
read_object,
)
from .types import OutlinesType, PagemodeType
from .types import OutlineType, PagemodeType
from .xmp import XmpInformation


Expand Down Expand Up @@ -677,19 +677,30 @@ def getNamedDestinations(
return self._get_named_destinations(tree, retval)

@property
def outlines(self) -> OutlinesType:
def outline(self) -> OutlineType:
"""
Read-only property for outlines present in the document.
Read-only property for the outline (i.e., a collection of 'outline items'
which are also known as 'bookmarks') present in the document.
:return: a nested list of :class:`Destinations<PyPDF2.generic.Destination>`.
"""
return self._get_outlines()
return self._get_outline()

def _get_outlines(
self, node: Optional[DictionaryObject] = None, outlines: Optional[Any] = None
) -> OutlinesType:
if outlines is None:
outlines = []
@property
def outlines(self) -> OutlineType:
"""
.. deprecated:: 2.9.0
Use :py:attr:`outline` instead.
"""
deprecate_with_replacement("outlines", "outline")
return self.outline

def _get_outline(
self, node: Optional[DictionaryObject] = None, outline: Optional[Any] = None
) -> OutlineType:
if outline is None:
outline = []
catalog = cast(DictionaryObject, self.trailer[TK.ROOT])

# get the outline dictionary and named destinations
Expand All @@ -699,49 +710,49 @@ def _get_outlines(
except PdfReadError:
# this occurs if the /Outlines object reference is incorrect
# for an example of such a file, see https://unglueit-files.s3.amazonaws.com/ebf/7552c42e9280b4476e59e77acc0bc812.pdf
# so continue to load the file without the Bookmarks
return outlines
# so continue to load the file without the Outlines
return outline

if isinstance(lines, NullObject):
return outlines
return outline

# TABLE 8.3 Entries in the outline dictionary
if lines is not None and "/First" in lines:
node = cast(DictionaryObject, lines["/First"])
self._namedDests = self._get_named_destinations()

if node is None:
return outlines
return outline

# see if there are any more outlines
# see if there are any more outline items
while True:
outline = self._build_outline(node)
if outline:
outlines.append(outline)
outline_obj = self._build_outline_item(node)
if outline_obj:
outline.append(outline_obj)

# check for sub-outlines
# check for sub-outline
if "/First" in node:
sub_outlines: List[Any] = []
self._get_outlines(cast(DictionaryObject, node["/First"]), sub_outlines)
if sub_outlines:
outlines.append(sub_outlines)
sub_outline: List[Any] = []
self._get_outline(cast(DictionaryObject, node["/First"]), sub_outline)
if sub_outline:
outline.append(sub_outline)

if "/Next" not in node:
break
node = cast(DictionaryObject, node["/Next"])

return outlines
return outline

def getOutlines(
self, node: Optional[DictionaryObject] = None, outlines: Optional[Any] = None
) -> OutlinesType: # pragma: no cover
self, node: Optional[DictionaryObject] = None, outline: Optional[Any] = None
) -> OutlineType: # pragma: no cover
"""
.. deprecated:: 1.28.0
Use :py:attr:`outlines` instead.
Use :py:attr:`outline` instead.
"""
deprecate_with_replacement("getOutlines", "outlines")
return self._get_outlines(node, outlines)
deprecate_with_replacement("getOutlines", "outline")
return self._get_outline(node, outline)

def _get_page_number_by_indirect(
self, indirect_ref: Union[None, int, NullObject, IndirectObject]
Expand Down Expand Up @@ -809,7 +820,7 @@ def _build_destination(
array: List[Union[NumberObject, IndirectObject, NullObject, DictionaryObject]],
) -> Destination:
page, typ = None, None
# handle outlines with missing or invalid destination
# handle outline items with missing or invalid destination
if (
isinstance(array, (type(None), NullObject))
or (isinstance(array, ArrayObject) and len(array) == 0)
Expand All @@ -835,8 +846,8 @@ def _build_destination(
title, indirect_ref, TextStringObject("/Fit") # type: ignore
)

def _build_outline(self, node: DictionaryObject) -> Optional[Destination]:
dest, title, outline = None, None, None
def _build_outline_item(self, node: DictionaryObject) -> Optional[Destination]:
dest, title, outline_item = None, None, None

# title required for valid outline
# PDF Reference 1.7: TABLE 8.4 Entries in an outline item dictionary
Expand All @@ -861,40 +872,40 @@ def _build_outline(self, node: DictionaryObject) -> Optional[Destination]:
dest = dest["/D"]

if isinstance(dest, ArrayObject):
outline = self._build_destination(title, dest) # type: ignore
outline_item = self._build_destination(title, dest) # type: ignore
elif isinstance(dest, str):
# named destination, addresses NameObject Issue #193
try:
outline = self._build_destination(
outline_item = self._build_destination(
title, self._namedDests[dest].dest_array
)
except KeyError:
# named destination not found in Name Dict
outline = self._build_destination(title, None)
outline_item = self._build_destination(title, None)
elif isinstance(dest, type(None)):
# outline not required to have destination or action
# outline item not required to have destination or action
# PDFv1.7 Table 153
outline = self._build_destination(title, dest) # type: ignore
outline_item = self._build_destination(title, dest) # type: ignore
else:
if self.strict:
raise PdfReadError(f"Unexpected destination {dest!r}")
outline = self._build_destination(title, None) # type: ignore
outline_item = self._build_destination(title, None) # type: ignore

# if outline created, add color, format, and child count if present
if outline:
# if outline item created, add color, format, and child count if present
if outline_item:
if "/C" in node:
# Color of outline in (R, G, B) with values ranging 0.0-1.0
outline[NameObject("/C")] = ArrayObject(FloatObject(c) for c in node["/C"]) # type: ignore
# Color of outline item font in (R, G, B) with values ranging 0.0-1.0
outline_item[NameObject("/C")] = ArrayObject(FloatObject(c) for c in node["/C"]) # type: ignore
if "/F" in node:
# specifies style characteristics bold and/or italic
# 1=italic, 2=bold, 3=both
outline[NameObject("/F")] = node["/F"]
outline_item[NameObject("/F")] = node["/F"]
if "/Count" in node:
# absolute value = num. visible children
# positive = open/unfolded, negative = closed/folded
outline[NameObject("/Count")] = node["/Count"]
outline_item[NameObject("/Count")] = node["/Count"]

return outline
return outline_item

@property
def pages(self) -> _VirtualList:
Expand Down Expand Up @@ -961,9 +972,9 @@ def page_mode(self) -> Optional[PagemodeType]:
:widths: 50 200
* - /UseNone
- Do not show outlines or thumbnails panels
- Do not show outline or thumbnails panels
* - /UseOutlines
- Show outlines (aka bookmarks) panel
- Show outline (aka bookmarks) panel
* - /UseThumbs
- Show page thumbnails panel
* - /FullScreen
Expand Down
44 changes: 43 additions & 1 deletion PyPDF2/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
__author__ = "Mathieu Fenniak"
__author_email__ = "[email protected]"

import functools
import logging
import warnings
from codecs import getencoder
Expand All @@ -40,7 +41,7 @@
FileIO,
)
from os import SEEK_CUR
from typing import Dict, Optional, Pattern, Tuple, Union, overload
from typing import Any, Callable, Dict, Optional, Pattern, Tuple, Union, overload

try:
# Python 3.10+: https://www.python.org/dev/peps/pep-0484/
Expand Down Expand Up @@ -362,3 +363,44 @@ def logger_warning(msg: str, src: str) -> None:
to strict=False mode.
"""
logging.getLogger(src).warning(msg)


def deprecate_bookmark(**aliases: str) -> Callable:
"""
Decorator for deprecated term "bookmark"
To be used for methods and function arguments
outline_item = a bookmark
outline = a collection of outline items
"""

def decoration(func: Callable): # type: ignore
@functools.wraps(func)
def wrapper(*args, **kwargs): # type: ignore
rename_kwargs(func.__name__, kwargs, aliases)
return func(*args, **kwargs)

return wrapper

return decoration


def rename_kwargs( # type: ignore
func_name: str, kwargs: Dict[str, Any], aliases: Dict[str, str]
):
"""
Helper function to deprecate arguments.
"""

for old_term, new_term in aliases.items():
if old_term in kwargs:
if new_term in kwargs:
raise TypeError(
f"{func_name} received both {old_term} and {new_term} as an argument."
f"{old_term} is deprecated. Use {new_term} instead."
)
kwargs[new_term] = kwargs.pop(old_term)
warnings.warn(
message=(
f"{old_term} is deprecated as an argument. Use {new_term} instead"
)
)
Loading

0 comments on commit 03057ac

Please sign in to comment.