Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAINT: Refactor Fit / Zoom parameters #1437

Merged
merged 12 commits into from Dec 10, 2022
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,
MartinThoma marked this conversation as resolved.
Show resolved Hide resolved
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,
MartinThoma marked this conversation as resolved.
Show resolved Hide resolved
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
39 changes: 23 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,9 @@ 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=dict(tmp)["fit_args"]
), # I have no clue why this dict-hack is necessary
)
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