Skip to content

Commit

Permalink
MAINT: Refactor Fit / Zoom parameters
Browse files Browse the repository at this point in the history
This is a breaking change that needs a major version bump
  • Loading branch information
MartinThoma committed Nov 19, 2022
1 parent 1842c21 commit e0e71b7
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 143 deletions.
37 changes: 27 additions & 10 deletions PyPDF2/_merger.py
Expand Up @@ -54,9 +54,11 @@
from .constants import PagesAttributes as PA
from .constants import TypArguments, TypFitArguments
from .generic import (
DEFAULT_FIT,
ArrayObject,
Destination,
DictionaryObject,
Fit,
FloatObject,
IndirectObject,
NameObject,
Expand Down Expand Up @@ -188,7 +190,7 @@ def merge(
outline_item_typ = OutlineItem(
TextStringObject(outline_item),
NumberObject(self.id_count),
NameObject(TypFitArguments.FIT),
Fit.fit(),
)
self.outline += [outline_item_typ, outline] # type: ignore
else:
Expand Down Expand Up @@ -628,8 +630,7 @@ def add_outline_item(
color: Optional[Tuple[float, float, float]] = None,
bold: bool = False,
italic: bool = False,
fit: FitType = "/Fit",
*args: ZoomArgType,
fit: Fit = DEFAULT_FIT,
) -> IndirectObject:
"""
Add an outline item (commonly referred to as a "Bookmark") to this PDF file.
Expand All @@ -642,14 +643,19 @@ def add_outline_item(
from 0.0 to 1.0
:param bool bold: Outline item font is bold
:param bool italic: Outline item font is italic
:param str fit: The fit of the destination page. See
:meth:`add_link()<add_link>` for details.
:param Fit fit: The fit of the destination page.
"""
writer = self.output
if writer is None:
raise RuntimeError(ERR_CLOSED_WRITER)
return writer.add_outline_item(
title, pagenum, parent, color, bold, italic, fit, *args
title,
pagenum,
parent,
color,
bold,
italic,
fit,
)

def addBookmark(
Expand All @@ -669,7 +675,13 @@ def addBookmark(
"""
deprecate_with_replacement("addBookmark", "add_outline_item")
return self.add_outline_item(
title, pagenum, parent, color, bold, italic, fit, *args
title,
pagenum,
parent,
color,
bold,
italic,
Fit(fit_type=fit, fit_args=args),
)

def add_bookmark(
Expand All @@ -689,7 +701,13 @@ def add_bookmark(
"""
deprecate_with_replacement("addBookmark", "add_outline_item")
return self.add_outline_item(
title, pagenum, parent, color, bold, italic, fit, *args
title,
pagenum,
parent,
color,
bold,
italic,
Fit(fit_type=fit, fit_args=args),
)

def addNamedDestination(self, title: str, pagenum: int) -> None: # pragma: no cover
Expand All @@ -710,8 +728,7 @@ def add_named_destination(self, title: str, pagenum: int) -> None:
dest = Destination(
TextStringObject(title),
NumberObject(pagenum),
NameObject(TypFitArguments.FIT_H),
NumberObject(826),
Fit.fit_horizontally(top=826),
)
self.named_dests.append(dest)

Expand Down
10 changes: 4 additions & 6 deletions PyPDF2/_reader.py
Expand Up @@ -85,6 +85,7 @@
DictionaryObject,
EncodedStreamObject,
Field,
Fit,
FloatObject,
IndirectObject,
NameObject,
Expand Down Expand Up @@ -874,23 +875,20 @@ def _build_destination(
):

page = NullObject()
typ = TextStringObject("/Fit")
return Destination(title, page, typ)
return Destination(title, page, Fit.fit())
else:
page, typ = array[0:2] # type: ignore
array = array[2:]
try:
return Destination(title, page, typ, *array) # type: ignore
return Destination(title, page, Fit(fit_type=typ, fit_args=array)) # type: ignore
except PdfReadError:
logger_warning(f"Unknown destination: {title} {array}", __name__)
if self.strict:
raise
# create a link to first Page
tmp = self.pages[0].indirect_ref
indirect_ref = NullObject() if tmp is None else tmp
return Destination(
title, indirect_ref, TextStringObject("/Fit") # type: ignore
)
return Destination(title, indirect_ref, Fit.fit()) # type: ignore

def _build_outline_item(self, node: DictionaryObject) -> Optional[Destination]:
dest, title, outline_item = None, None, None
Expand Down
37 changes: 21 additions & 16 deletions PyPDF2/_writer.py
Expand Up @@ -82,6 +82,7 @@
from .constants import TrailerKeys as TK
from .constants import TypFitArguments, UserAccessPermissions
from .generic import (
DEFAULT_FIT,
AnnotationBuilder,
ArrayObject,
BooleanObject,
Expand All @@ -90,6 +91,7 @@
DecodedStreamObject,
Destination,
DictionaryObject,
Fit,
FloatObject,
IndirectObject,
NameObject,
Expand All @@ -109,7 +111,6 @@
LayoutType,
OutlineItemType,
PagemodeType,
ZoomArgsType,
ZoomArgType,
)

Expand Down Expand Up @@ -1209,8 +1210,7 @@ def add_outline_item(
color: Optional[Union[Tuple[float, float, float], str]] = None,
bold: bool = False,
italic: bool = False,
fit: FitType = "/Fit",
*args: ZoomArgType,
fit: Fit = DEFAULT_FIT,
) -> IndirectObject:
"""
Add an outline item (commonly referred to as a "Bookmark") to this PDF file.
Expand All @@ -1223,18 +1223,13 @@ def add_outline_item(
from 0.0 to 1.0 or as a Hex String (#RRGGBB)
:param bool bold: Outline item font is bold
:param bool italic: Outline item font is italic
:param str fit: The fit of the destination page. See
:meth:`add_link()<add_link>` for details.
:param Fit fit: The fit of the destination page.
"""
page_ref = NumberObject(pagenum)
zoom_args: ZoomArgsType = [
NullObject() if a is None else NumberObject(a) for a in args
]
dest = Destination(
NameObject("/" + title + " outline item"),
page_ref,
NameObject(fit),
*zoom_args,
fit,
)

action_ref = self._add_object(
Expand Down Expand Up @@ -1269,7 +1264,13 @@ def add_bookmark(
"""
deprecate_with_replacement("add_bookmark", "add_outline_item")
return self.add_outline_item(
title, pagenum, parent, color, bold, italic, fit, *args
title,
pagenum,
parent,
color,
bold,
italic,
Fit(fit_type=fit, fit_args=args),
)

def addBookmark(
Expand All @@ -1290,7 +1291,13 @@ def addBookmark(
"""
deprecate_with_replacement("addBookmark", "add_outline_item")
return self.add_outline_item(
title, pagenum, parent, color, bold, italic, fit, *args
title,
pagenum,
parent,
color,
bold,
italic,
Fit(fit_type=fit, fit_args=args),
)

def add_outline(self) -> None:
Expand Down Expand Up @@ -1616,8 +1623,7 @@ def add_link(
rect=rect,
border=border,
target_page_index=pagedest,
fit=fit,
fit_args=args,
fit=Fit(fit_type=fit, fit_args=args),
)
return self.add_annotation(page_number=pagenum, annotation=annotation)

Expand Down Expand Up @@ -1869,8 +1875,7 @@ def add_annotation(self, page_number: int, annotation: Dict[str, Any]) -> None:
dest = Destination(
NameObject("/LinkName"),
tmp["target_page_index"],
tmp["fit"],
*tmp["fit_args"],
Fit(fit_type=tmp["fit"], fit_args=tmp["fit_args"]),
)
to_add[NameObject("/Dest")] = dest.dest_array

Expand Down
7 changes: 7 additions & 0 deletions PyPDF2/generic/__init__.py
Expand Up @@ -58,6 +58,7 @@
TreeObject,
read_object,
)
from ._fit import Fit
from ._outline import Bookmark, OutlineItem
from ._rectangle import RectangleObject
from ._utils import (
Expand Down Expand Up @@ -96,6 +97,9 @@ def createStringObject(
return create_string_object(string, forced_encoding)


DEFAULT_FIT = Fit.fit()


__all__ = [
# Base types
"BooleanObject",
Expand All @@ -109,6 +113,9 @@ def createStringObject(
"ByteStringObject",
# Annotations
"AnnotationBuilder",
# Fit
"Fit",
"DEFAULT_FIT",
# Data structures
"ArrayObject",
"DictionaryObject",
Expand Down
37 changes: 5 additions & 32 deletions PyPDF2/generic/_annotations.py
Expand Up @@ -4,11 +4,11 @@
BooleanObject,
FloatObject,
NameObject,
NullObject,
NumberObject,
TextStringObject,
)
from ._data_structures import ArrayObject, DictionaryObject
from ._fit import DEFAULT_FIT, Fit
from ._rectangle import RectangleObject
from ._utils import hex_to_rgb

Expand Down Expand Up @@ -198,8 +198,7 @@ def link(
border: Optional[ArrayObject] = None,
url: Optional[str] = None,
target_page_index: Optional[int] = None,
fit: FitType = "/Fit",
fit_args: Tuple[ZoomArgType, ...] = tuple(),
fit: Fit = DEFAULT_FIT,
) -> DictionaryObject:
"""
Add a link to the document.
Expand All @@ -223,30 +222,7 @@ def link(
:param str url: Link to a website (if you want to make an external link)
:param int target_page_index: index of the page to which the link should go
(if you want to make an internal link)
:param str fit: Page fit or 'zoom' option (see below). Additional arguments may need
to be supplied. Passing ``None`` will be read as a null value for that coordinate.
:param Tuple[int, ...] fit_args: Parameters for the fit argument.
.. list-table:: Valid ``fit`` arguments (see Table 8.2 of the PDF 1.7 reference for details)
:widths: 50 200
* - /Fit
- No additional arguments
* - /XYZ
- [left] [top] [zoomFactor]
* - /FitH
- [top]
* - /FitV
- [left]
* - /FitR
- [left] [bottom] [right] [top]
* - /FitB
- No additional arguments
* - /FitBH
- [top]
* - /FitBV
- [left]
:param Fit fit: Page fit or 'zoom' option.
"""
from ..types import BorderArrayType

Expand Down Expand Up @@ -287,15 +263,12 @@ def link(
}
)
if is_internal:
fit_arg_ready = [
NullObject() if a is None else NumberObject(a) for a in fit_args
]
# This needs to be updated later!
dest_deferred = DictionaryObject(
{
"target_page_index": NumberObject(target_page_index),
"fit": NameObject(fit),
"fit_args": ArrayObject(fit_arg_ready),
"fit": NameObject(fit.fit_type),
"fit_args": fit.fit_args,
}
)
link_obj[NameObject("/Dest")] = dest_deferred
Expand Down
30 changes: 7 additions & 23 deletions PyPDF2/generic/_data_structures.py
Expand Up @@ -65,6 +65,7 @@
PdfObject,
TextStringObject,
)
from ._fit import Fit
from ._utils import read_hex_string_from_stream, read_string_from_stream

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -1019,38 +1020,21 @@ class Destination(TreeObject):
:param str title: Title of this destination.
:param IndirectObject page: Reference to the page of this destination. Should
be an instance of :class:`IndirectObject<PyPDF2.generic.IndirectObject>`.
:param str typ: How the destination is displayed.
:param args: Additional arguments may be necessary depending on the type.
:param Fit fit: How the destination is displayed.
:raises PdfReadError: If destination type is invalid.
.. list-table:: Valid ``typ`` arguments (see PDF spec for details)
:widths: 50 50
* - /Fit
- No additional arguments
* - /XYZ
- [left] [top] [zoomFactor]
* - /FitH
- [top]
* - /FitV
- [left]
* - /FitR
- [left] [bottom] [right] [top]
* - /FitB
- No additional arguments
* - /FitBH
- [top]
* - /FitBV
- [left]
"""

def __init__(
self,
title: str,
page: Union[NumberObject, IndirectObject, NullObject, DictionaryObject],
typ: Union[str, NumberObject],
*args: Any, # ZoomArgType
fit: Fit,
) -> None:
typ = fit.fit_type
args = fit.fit_args

DictionaryObject.__init__(self)
self[NameObject("/Title")] = TextStringObject(title)
self[NameObject("/Page")] = page
Expand Down

0 comments on commit e0e71b7

Please sign in to comment.