diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/PyPDF2/_merger.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/PyPDF2/_merger.py | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/PyPDF2/_merger.py b/.venv/lib/python3.12/site-packages/PyPDF2/_merger.py new file mode 100644 index 00000000..4d7a659d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/PyPDF2/_merger.py @@ -0,0 +1,821 @@ +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import warnings +from io import BytesIO, FileIO, IOBase +from pathlib import Path +from types import TracebackType +from typing import ( + Any, + Dict, + Iterable, + List, + Optional, + Tuple, + Type, + Union, + cast, +) + +from ._encryption import Encryption +from ._page import PageObject +from ._reader import PdfReader +from ._utils import ( + StrByteType, + deprecation_bookmark, + deprecation_with_replacement, + str_, +) +from ._writer import PdfWriter +from .constants import GoToActionArguments +from .constants import PagesAttributes as PA +from .constants import TypArguments, TypFitArguments +from .generic import ( + PAGE_FIT, + ArrayObject, + Destination, + DictionaryObject, + Fit, + FloatObject, + IndirectObject, + NameObject, + NullObject, + NumberObject, + OutlineItem, + TextStringObject, + TreeObject, +) +from .pagerange import PageRange, PageRangeSpec +from .types import FitType, LayoutType, OutlineType, PagemodeType, ZoomArgType + +ERR_CLOSED_WRITER = "close() was called and thus the writer cannot be used anymore" + + +class _MergedPage: + """Collect necessary information on each page that is being merged.""" + + def __init__(self, pagedata: PageObject, src: PdfReader, id: int) -> None: + self.src = src + self.pagedata = pagedata + self.out_pagedata = None + self.id = id + + +class PdfMerger: + """ + Initialize a ``PdfMerger`` object. + + ``PdfMerger`` merges multiple PDFs into a single PDF. + It can concatenate, slice, insert, or any combination of the above. + + See the functions :meth:`merge()<merge>` (or :meth:`append()<append>`) + and :meth:`write()<write>` for usage information. + + :param bool strict: Determines whether user should be warned of all + problems and also causes some correctable problems to be fatal. + Defaults to ``False``. + :param fileobj: Output file. Can be a filename or any kind of + file-like object. + """ + + @deprecation_bookmark(bookmarks="outline") + def __init__( + self, strict: bool = False, fileobj: Union[Path, StrByteType] = "" + ) -> None: + self.inputs: List[Tuple[Any, PdfReader]] = [] + self.pages: List[Any] = [] + self.output: Optional[PdfWriter] = PdfWriter() + self.outline: OutlineType = [] + self.named_dests: List[Any] = [] + self.id_count = 0 + self.fileobj = fileobj + self.strict = strict + + def __enter__(self) -> "PdfMerger": + # There is nothing to do. + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + """Write to the fileobj and close the merger.""" + if self.fileobj: + self.write(self.fileobj) + self.close() + + @deprecation_bookmark(bookmark="outline_item", import_bookmarks="import_outline") + def merge( + self, + page_number: Optional[int] = None, + fileobj: Union[Path, StrByteType, PdfReader] = None, + outline_item: Optional[str] = None, + pages: Optional[PageRangeSpec] = None, + import_outline: bool = True, + position: Optional[int] = None, # deprecated + ) -> None: + """ + Merge the pages from the given file into the output file at the + specified page number. + + :param int page_number: The *page number* to insert this file. File will + be inserted after the given number. + + :param fileobj: A File Object or an object that supports the standard + read and seek methods similar to a File Object. Could also be a + string representing a path to a PDF file. + + :param str outline_item: Optionally, you may specify an outline item + (previously referred to as a 'bookmark') to be applied at the + beginning of the included file by supplying the text of the outline item. + + :param pages: can be a :class:`PageRange<PyPDF2.pagerange.PageRange>` + or a ``(start, stop[, step])`` tuple + to merge only the specified range of pages from the source + document into the output document. + Can also be a list of pages to merge. + + :param bool import_outline: You may prevent the source document's + outline (collection of outline items, previously referred to as + 'bookmarks') from being imported by specifying this as ``False``. + """ + if position is not None: # deprecated + if page_number is None: + page_number = position + old_term = "position" + new_term = "page_number" + warnings.warn( + ( + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2=4.0.0. Use {new_term} instead" + ), + DeprecationWarning, + ) + else: + raise ValueError( + "The argument position of merge is deprecated. Use page_number only." + ) + + if page_number is None: # deprecated + # The paremter is only marked as Optional as long as + # position is not fully deprecated + raise ValueError("page_number may not be None") + if fileobj is None: # deprecated + # The argument is only Optional due to the deprecated position + # argument + raise ValueError("fileobj may not be None") + + stream, encryption_obj = self._create_stream(fileobj) + + # Create a new PdfReader instance using the stream + # (either file or BytesIO or StringIO) created above + reader = PdfReader(stream, strict=self.strict) # type: ignore[arg-type] + self.inputs.append((stream, reader)) + if encryption_obj is not None: + reader._encryption = encryption_obj + + # Find the range of pages to merge. + if pages is None: + pages = (0, len(reader.pages)) + elif isinstance(pages, PageRange): + pages = pages.indices(len(reader.pages)) + elif isinstance(pages, list): + pass + elif not isinstance(pages, tuple): + raise TypeError('"pages" must be a tuple of (start, stop[, step])') + + srcpages = [] + + outline = [] + if import_outline: + outline = reader.outline + outline = self._trim_outline(reader, outline, pages) + + if outline_item: + outline_item_typ = OutlineItem( + TextStringObject(outline_item), + NumberObject(self.id_count), + Fit.fit(), + ) + self.outline += [outline_item_typ, outline] # type: ignore + else: + self.outline += outline + + dests = reader.named_destinations + trimmed_dests = self._trim_dests(reader, dests, pages) + self.named_dests += trimmed_dests + + # Gather all the pages that are going to be merged + for i in range(*pages): + page = reader.pages[i] + + id = self.id_count + self.id_count += 1 + + mp = _MergedPage(page, reader, id) + + srcpages.append(mp) + + self._associate_dests_to_pages(srcpages) + self._associate_outline_items_to_pages(srcpages) + + # Slice to insert the pages at the specified page_number + self.pages[page_number:page_number] = srcpages + + def _create_stream( + self, fileobj: Union[Path, StrByteType, PdfReader] + ) -> Tuple[IOBase, Optional[Encryption]]: + # If the fileobj parameter is a string, assume it is a path + # and create a file object at that location. If it is a file, + # copy the file's contents into a BytesIO stream object; if + # it is a PdfReader, copy that reader's stream into a + # BytesIO stream. + # If fileobj is none of the above types, it is not modified + encryption_obj = None + stream: IOBase + if isinstance(fileobj, (str, Path)): + stream = FileIO(fileobj, "rb") + elif isinstance(fileobj, PdfReader): + if fileobj._encryption: + encryption_obj = fileobj._encryption + orig_tell = fileobj.stream.tell() + fileobj.stream.seek(0) + stream = BytesIO(fileobj.stream.read()) + + # reset the stream to its original location + fileobj.stream.seek(orig_tell) + elif hasattr(fileobj, "seek") and hasattr(fileobj, "read"): + fileobj.seek(0) + filecontent = fileobj.read() + stream = BytesIO(filecontent) + else: + raise NotImplementedError( + "PdfMerger.merge requires an object that PdfReader can parse. " + "Typically, that is a Path or a string representing a Path, " + "a file object, or an object implementing .seek and .read. " + "Passing a PdfReader directly works as well." + ) + return stream, encryption_obj + + @deprecation_bookmark(bookmark="outline_item", import_bookmarks="import_outline") + def append( + self, + fileobj: Union[StrByteType, PdfReader, Path], + outline_item: Optional[str] = None, + pages: Union[ + None, PageRange, Tuple[int, int], Tuple[int, int, int], List[int] + ] = None, + import_outline: bool = True, + ) -> None: + """ + Identical to the :meth:`merge()<merge>` method, but assumes you want to + concatenate all pages onto the end of the file instead of specifying a + position. + + :param fileobj: A File Object or an object that supports the standard + read and seek methods similar to a File Object. Could also be a + string representing a path to a PDF file. + + :param str outline_item: Optionally, you may specify an outline item + (previously referred to as a 'bookmark') to be applied at the + beginning of the included file by supplying the text of the outline item. + + :param pages: can be a :class:`PageRange<PyPDF2.pagerange.PageRange>` + or a ``(start, stop[, step])`` tuple + to merge only the specified range of pages from the source + document into the output document. + Can also be a list of pages to append. + + :param bool import_outline: You may prevent the source document's + outline (collection of outline items, previously referred to as + 'bookmarks') from being imported by specifying this as ``False``. + """ + self.merge(len(self.pages), fileobj, outline_item, pages, import_outline) + + def write(self, fileobj: Union[Path, StrByteType]) -> None: + """ + Write all data that has been merged to the given output file. + + :param fileobj: Output file. Can be a filename or any kind of + file-like object. + """ + if self.output is None: + raise RuntimeError(ERR_CLOSED_WRITER) + + # Add pages to the PdfWriter + # The commented out line below was replaced with the two lines below it + # to allow PdfMerger to work with PyPdf 1.13 + for page in self.pages: + self.output.add_page(page.pagedata) + pages_obj = cast(Dict[str, Any], self.output._pages.get_object()) + page.out_pagedata = self.output.get_reference( + pages_obj[PA.KIDS][-1].get_object() + ) + # idnum = self.output._objects.index(self.output._pages.get_object()[PA.KIDS][-1].get_object()) + 1 + # page.out_pagedata = IndirectObject(idnum, 0, self.output) + + # Once all pages are added, create outline items to point at those pages + self._write_dests() + self._write_outline() + + # Write the output to the file + my_file, ret_fileobj = self.output.write(fileobj) + + if my_file: + ret_fileobj.close() + + def close(self) -> None: + """Shut all file descriptors (input and output) and clear all memory usage.""" + self.pages = [] + for fo, _reader in self.inputs: + fo.close() + + self.inputs = [] + self.output = None + + def add_metadata(self, infos: Dict[str, Any]) -> None: + """ + Add custom metadata to the output. + + :param dict infos: a Python dictionary where each key is a field + and each value is your new metadata. + Example: ``{u'/Title': u'My title'}`` + """ + if self.output is None: + raise RuntimeError(ERR_CLOSED_WRITER) + self.output.add_metadata(infos) + + def addMetadata(self, infos: Dict[str, Any]) -> None: # pragma: no cover + """ + .. deprecated:: 1.28.0 + + Use :meth:`add_metadata` instead. + """ + deprecation_with_replacement("addMetadata", "add_metadata") + self.add_metadata(infos) + + def setPageLayout(self, layout: LayoutType) -> None: # pragma: no cover + """ + .. deprecated:: 1.28.0 + + Use :meth:`set_page_layout` instead. + """ + deprecation_with_replacement("setPageLayout", "set_page_layout") + self.set_page_layout(layout) + + def set_page_layout(self, layout: LayoutType) -> None: + """ + Set the page layout. + + :param str layout: The page layout to be used + + .. list-table:: Valid ``layout`` arguments + :widths: 50 200 + + * - /NoLayout + - Layout explicitly not specified + * - /SinglePage + - Show one page at a time + * - /OneColumn + - Show one column at a time + * - /TwoColumnLeft + - Show pages in two columns, odd-numbered pages on the left + * - /TwoColumnRight + - Show pages in two columns, odd-numbered pages on the right + * - /TwoPageLeft + - Show two pages at a time, odd-numbered pages on the left + * - /TwoPageRight + - Show two pages at a time, odd-numbered pages on the right + """ + if self.output is None: + raise RuntimeError(ERR_CLOSED_WRITER) + self.output._set_page_layout(layout) + + def setPageMode(self, mode: PagemodeType) -> None: # pragma: no cover + """ + .. deprecated:: 1.28.0 + + Use :meth:`set_page_mode` instead. + """ + deprecation_with_replacement("setPageMode", "set_page_mode", "3.0.0") + self.set_page_mode(mode) + + def set_page_mode(self, mode: PagemodeType) -> None: + """ + Set the page mode. + + :param str mode: The page mode to use. + + .. list-table:: Valid ``mode`` arguments + :widths: 50 200 + + * - /UseNone + - Do not show outline or thumbnails panels + * - /UseOutlines + - Show outline (aka bookmarks) panel + * - /UseThumbs + - Show page thumbnails panel + * - /FullScreen + - Fullscreen view + * - /UseOC + - Show Optional Content Group (OCG) panel + * - /UseAttachments + - Show attachments panel + """ + if self.output is None: + raise RuntimeError(ERR_CLOSED_WRITER) + self.output.set_page_mode(mode) + + def _trim_dests( + self, + pdf: PdfReader, + dests: Dict[str, Dict[str, Any]], + pages: Union[Tuple[int, int], Tuple[int, int, int], List[int]], + ) -> List[Dict[str, Any]]: + """Remove named destinations that are not a part of the specified page set.""" + new_dests = [] + lst = pages if isinstance(pages, list) else list(range(*pages)) + for key, obj in dests.items(): + for j in lst: + if pdf.pages[j].get_object() == obj["/Page"].get_object(): + obj[NameObject("/Page")] = obj["/Page"].get_object() + assert str_(key) == str_(obj["/Title"]) + new_dests.append(obj) + break + return new_dests + + def _trim_outline( + self, + pdf: PdfReader, + outline: OutlineType, + pages: Union[Tuple[int, int], Tuple[int, int, int], List[int]], + ) -> OutlineType: + """Remove outline item entries that are not a part of the specified page set.""" + new_outline = [] + prev_header_added = True + lst = pages if isinstance(pages, list) else list(range(*pages)) + for i, outline_item in enumerate(outline): + if isinstance(outline_item, list): + sub = self._trim_outline(pdf, outline_item, lst) # type: ignore + if sub: + if not prev_header_added: + new_outline.append(outline[i - 1]) + new_outline.append(sub) # type: ignore + else: + prev_header_added = False + for j in lst: + if outline_item["/Page"] is None: + continue + if pdf.pages[j].get_object() == outline_item["/Page"].get_object(): + outline_item[NameObject("/Page")] = outline_item[ + "/Page" + ].get_object() + new_outline.append(outline_item) + prev_header_added = True + break + return new_outline + + def _write_dests(self) -> None: + if self.output is None: + raise RuntimeError(ERR_CLOSED_WRITER) + for named_dest in self.named_dests: + pageno = None + if "/Page" in named_dest: + for pageno, page in enumerate(self.pages): # noqa: B007 + if page.id == named_dest["/Page"]: + named_dest[NameObject("/Page")] = page.out_pagedata + break + + if pageno is not None: + self.output.add_named_destination_object(named_dest) + + @deprecation_bookmark(bookmarks="outline") + def _write_outline( + self, + outline: Optional[Iterable[OutlineItem]] = None, + parent: Optional[TreeObject] = None, + ) -> None: + if self.output is None: + raise RuntimeError(ERR_CLOSED_WRITER) + if outline is None: + outline = self.outline # type: ignore + assert outline is not None, "hint for mypy" # TODO: is that true? + + last_added = None + for outline_item in outline: + if isinstance(outline_item, list): + self._write_outline(outline_item, last_added) + continue + + page_no = None + if "/Page" in outline_item: + for page_no, page in enumerate(self.pages): # noqa: B007 + if page.id == outline_item["/Page"]: + self._write_outline_item_on_page(outline_item, page) + break + if page_no is not None: + del outline_item["/Page"], outline_item["/Type"] + last_added = self.output.add_outline_item_dict(outline_item, parent) + + @deprecation_bookmark(bookmark="outline_item") + def _write_outline_item_on_page( + self, outline_item: Union[OutlineItem, Destination], page: _MergedPage + ) -> None: + oi_type = cast(str, outline_item["/Type"]) + args = [NumberObject(page.id), NameObject(oi_type)] + fit2arg_keys: Dict[str, Tuple[str, ...]] = { + TypFitArguments.FIT_H: (TypArguments.TOP,), + TypFitArguments.FIT_BH: (TypArguments.TOP,), + TypFitArguments.FIT_V: (TypArguments.LEFT,), + TypFitArguments.FIT_BV: (TypArguments.LEFT,), + TypFitArguments.XYZ: (TypArguments.LEFT, TypArguments.TOP, "/Zoom"), + TypFitArguments.FIT_R: ( + TypArguments.LEFT, + TypArguments.BOTTOM, + TypArguments.RIGHT, + TypArguments.TOP, + ), + } + for arg_key in fit2arg_keys.get(oi_type, tuple()): + if arg_key in outline_item and not isinstance( + outline_item[arg_key], NullObject + ): + args.append(FloatObject(outline_item[arg_key])) + else: + args.append(FloatObject(0)) + del outline_item[arg_key] + + outline_item[NameObject("/A")] = DictionaryObject( + { + NameObject(GoToActionArguments.S): NameObject("/GoTo"), + NameObject(GoToActionArguments.D): ArrayObject(args), + } + ) + + def _associate_dests_to_pages(self, pages: List[_MergedPage]) -> None: + for named_dest in self.named_dests: + pageno = None + np = named_dest["/Page"] + + if isinstance(np, NumberObject): + continue + + for page in pages: + if np.get_object() == page.pagedata.get_object(): + pageno = page.id + + if pageno is None: + raise ValueError( + f"Unresolved named destination '{named_dest['/Title']}'" + ) + named_dest[NameObject("/Page")] = NumberObject(pageno) + + @deprecation_bookmark(bookmarks="outline") + def _associate_outline_items_to_pages( + self, pages: List[_MergedPage], outline: Optional[Iterable[OutlineItem]] = None + ) -> None: + if outline is None: + outline = self.outline # type: ignore # TODO: self.bookmarks can be None! + assert outline is not None, "hint for mypy" + for outline_item in outline: + if isinstance(outline_item, list): + self._associate_outline_items_to_pages(pages, outline_item) + continue + + pageno = None + outline_item_page = outline_item["/Page"] + + if isinstance(outline_item_page, NumberObject): + continue + + for p in pages: + if outline_item_page.get_object() == p.pagedata.get_object(): + pageno = p.id + + if pageno is not None: + outline_item[NameObject("/Page")] = NumberObject(pageno) + + @deprecation_bookmark(bookmark="outline_item") + def find_outline_item( + self, + outline_item: Dict[str, Any], + root: Optional[OutlineType] = None, + ) -> Optional[List[int]]: + if root is None: + root = self.outline + + for i, oi_enum in enumerate(root): + if isinstance(oi_enum, list): + # oi_enum is still an inner node + # (OutlineType, if recursive types were supported by mypy) + res = self.find_outline_item(outline_item, oi_enum) # type: ignore + if res: + return [i] + res + elif ( + oi_enum == outline_item + or cast(Dict[Any, Any], oi_enum["/Title"]) == outline_item + ): + # we found a leaf node + return [i] + + return None + + @deprecation_bookmark(bookmark="outline_item") + def find_bookmark( + self, + outline_item: Dict[str, Any], + root: Optional[OutlineType] = None, + ) -> Optional[List[int]]: # pragma: no cover + """ + .. deprecated:: 2.9.0 + Use :meth:`find_outline_item` instead. + """ + return self.find_outline_item(outline_item, root) + + def add_outline_item( + self, + title: str, + page_number: Optional[int] = None, + parent: Union[None, TreeObject, IndirectObject] = None, + color: Optional[Tuple[float, float, float]] = None, + bold: bool = False, + italic: bool = False, + fit: Fit = PAGE_FIT, + pagenum: Optional[int] = None, # deprecated + ) -> IndirectObject: + """ + Add an outline item (commonly referred to as a "Bookmark") to this PDF file. + + :param str title: Title to use for this outline item. + :param int page_number: Page number this outline item will point to. + :param parent: A reference to a parent outline item to create nested + outline items. + :param tuple color: Color of the outline item's font as a red, green, blue tuple + from 0.0 to 1.0 + :param bool bold: Outline item font is bold + :param bool italic: Outline item font is italic + :param Fit fit: The fit of the destination page. + """ + if page_number is not None and pagenum is not None: + raise ValueError( + "The argument pagenum of add_outline_item is deprecated. Use page_number only." + ) + if pagenum is not None: + old_term = "pagenum" + new_term = "page_number" + warnings.warn( + ( + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2==4.0.0. Use {new_term} instead" + ), + DeprecationWarning, + ) + page_number = pagenum + if page_number is None: + raise ValueError("page_number may not be None") + writer = self.output + if writer is None: + raise RuntimeError(ERR_CLOSED_WRITER) + return writer.add_outline_item( + title, + page_number, + parent, + None, + color, + bold, + italic, + fit, + ) + + def addBookmark( + self, + title: str, + pagenum: int, # deprecated, but the whole method is deprecated + parent: Union[None, TreeObject, IndirectObject] = None, + color: Optional[Tuple[float, float, float]] = None, + bold: bool = False, + italic: bool = False, + fit: FitType = "/Fit", + *args: ZoomArgType, + ) -> IndirectObject: # pragma: no cover + """ + .. deprecated:: 1.28.0 + Use :meth:`add_outline_item` instead. + """ + deprecation_with_replacement("addBookmark", "add_outline_item", "3.0.0") + return self.add_outline_item( + title, + pagenum, + parent, + color, + bold, + italic, + Fit(fit_type=fit, fit_args=args), + ) + + def add_bookmark( + self, + title: str, + pagenum: int, # deprecated, but the whole method is deprecated already + parent: Union[None, TreeObject, IndirectObject] = None, + color: Optional[Tuple[float, float, float]] = None, + bold: bool = False, + italic: bool = False, + fit: FitType = "/Fit", + *args: ZoomArgType, + ) -> IndirectObject: # pragma: no cover + """ + .. deprecated:: 2.9.0 + Use :meth:`add_outline_item` instead. + """ + deprecation_with_replacement("addBookmark", "add_outline_item", "3.0.0") + return self.add_outline_item( + title, + pagenum, + parent, + color, + bold, + italic, + Fit(fit_type=fit, fit_args=args), + ) + + def addNamedDestination(self, title: str, pagenum: int) -> None: # pragma: no cover + """ + .. deprecated:: 1.28.0 + Use :meth:`add_named_destination` instead. + """ + deprecation_with_replacement( + "addNamedDestination", "add_named_destination", "3.0.0" + ) + return self.add_named_destination(title, pagenum) + + def add_named_destination( + self, + title: str, + page_number: Optional[int] = None, + pagenum: Optional[int] = None, + ) -> None: + """ + Add a destination to the output. + + :param str title: Title to use + :param int page_number: Page number this destination points at. + """ + if page_number is not None and pagenum is not None: + raise ValueError( + "The argument pagenum of add_named_destination is deprecated. Use page_number only." + ) + if pagenum is not None: + old_term = "pagenum" + new_term = "page_number" + warnings.warn( + ( + f"{old_term} is deprecated as an argument and will be " + f"removed in PyPDF2==4.0.0. Use {new_term} instead" + ), + DeprecationWarning, + ) + page_number = pagenum + if page_number is None: + raise ValueError("page_number may not be None") + dest = Destination( + TextStringObject(title), + NumberObject(page_number), + Fit.fit_horizontally(top=826), + ) + self.named_dests.append(dest) + + +class PdfFileMerger(PdfMerger): # pragma: no cover + def __init__(self, *args: Any, **kwargs: Any) -> None: + deprecation_with_replacement("PdfFileMerger", "PdfMerger", "3.0.0") + + if "strict" not in kwargs and len(args) < 1: + kwargs["strict"] = True # maintain the default + super().__init__(*args, **kwargs) |