diff options
Diffstat (limited to 'env/lib/python3.10/site-packages/pikepdf/models/outlines.py')
-rw-r--r-- | env/lib/python3.10/site-packages/pikepdf/models/outlines.py | 421 |
1 files changed, 0 insertions, 421 deletions
diff --git a/env/lib/python3.10/site-packages/pikepdf/models/outlines.py b/env/lib/python3.10/site-packages/pikepdf/models/outlines.py deleted file mode 100644 index 1143de6..0000000 --- a/env/lib/python3.10/site-packages/pikepdf/models/outlines.py +++ /dev/null @@ -1,421 +0,0 @@ -# SPDX-FileCopyrightText: 2022 James R. Barlow, 2020 Matthias Erll - -# SPDX-License-Identifier: MPL-2.0 - -"""Support for document outlines (e.g. table of contents).""" - -from __future__ import annotations - -from enum import Enum -from itertools import chain -from typing import Iterable, List, cast - -from pikepdf import Array, Dictionary, Name, Object, Page, Pdf, String - - -class PageLocation(Enum): - """Page view location definitions, from PDF spec.""" - - XYZ = 1 - Fit = 2 - FitH = 3 - FitV = 4 - FitR = 5 - FitB = 6 - FitBH = 7 - FitBV = 8 - - -PAGE_LOCATION_ARGS = { - PageLocation.XYZ: ('left', 'top', 'zoom'), - PageLocation.FitH: ('top',), - PageLocation.FitV: ('left',), - PageLocation.FitR: ('left', 'bottom', 'right', 'top'), - PageLocation.FitBH: ('top',), - PageLocation.FitBV: ('left',), -} -ALL_PAGE_LOCATION_KWARGS = set(chain.from_iterable(PAGE_LOCATION_ARGS.values())) - - -def make_page_destination( - pdf: Pdf, - page_num: int, - page_location: PageLocation | str | None = None, - *, - left: float | None = None, - top: float | None = None, - right: float | None = None, - bottom: float | None = None, - zoom: float | None = None, -) -> Array: - """ - Create a destination ``Array`` with reference to a Pdf document's page number. - - Arguments: - pdf: PDF document object. - page_num: Page number (zero-based). - page_location: Optional page location, as a string or :enum:`PageLocation`. - left: Specify page viewport rectangle. - top: Specify page viewport rectangle. - right: Specify page viewport rectangle. - bottom: Specify page viewport rectangle. - zoom: Specify page viewport rectangle's zoom level. - - left, top, right, bottom, zoom are used in conjunction with the page fit style - specified by *page_location*. - """ - return _make_page_destination( - pdf, - page_num, - page_location=page_location, - left=left, - top=top, - right=right, - bottom=bottom, - zoom=zoom, - ) - - -def _make_page_destination( - pdf: Pdf, - page_num: int, - page_location: PageLocation | str | None = None, - **kwargs, -) -> Array: - kwargs = {k: v for k, v in kwargs.items() if v is not None} - - res: list[Dictionary | Name] = [pdf.pages[page_num].obj] - if page_location: - if isinstance(page_location, PageLocation): - loc_key = page_location - loc_str = loc_key.name - else: - loc_str = page_location - try: - loc_key = PageLocation[loc_str] - except KeyError: - raise ValueError( - f"Invalid or unsupported page location type {loc_str}" - ) from None - res.append(Name(f'/{loc_str}')) - dest_arg_names = PAGE_LOCATION_ARGS.get(loc_key) - if dest_arg_names: - res.extend(kwargs.get(k, 0) for k in dest_arg_names) - else: - res.append(Name.Fit) - return Array(res) - - -class OutlineStructureError(Exception): - """Indicates an error in the outline data structure.""" - - -class OutlineItem: - """Manage a single item in a PDF document outlines structure. - - Includes nested items. - - Arguments: - title: Title of the outlines item. - destination: Page number, destination name, or any other PDF object - to be used as a reference when clicking on the outlines entry. Note - this should be ``None`` if an action is used instead. If set to a - page number, it will be resolved to a reference at the time of - writing the outlines back to the document. - page_location: Supplemental page location for a page number - in ``destination``, e.g. ``PageLocation.Fit``. May also be - a simple string such as ``'FitH'``. - action: Action to perform when clicking on this item. Will be ignored - during writing if ``destination`` is also set. - obj: ``Dictionary`` object representing this outlines item in a ``Pdf``. - May be ``None`` for creating a new object. If present, an existing - object is modified in-place during writing and original attributes - are retained. - left, top, bottom, right, zoom: Describes the viewport position associated - with a destination. - - This object does not contain any information about higher-level or - neighboring elements. - - Valid destination arrays: - [page /XYZ left top zoom] - generally - [page, PageLocationEntry, 0 to 4 ints] - """ - - def __init__( - self, - title: str, - destination: Array | String | Name | int | None = None, - page_location: PageLocation | str | None = None, - action: Dictionary | None = None, - obj: Dictionary | None = None, - *, - left: float | None = None, - top: float | None = None, - right: float | None = None, - bottom: float | None = None, - zoom: float | None = None, - ): - self.title = title - self.destination = destination - self.page_location = page_location - self.page_location_kwargs = {} - self.action = action - if self.destination is not None and self.action is not None: - raise ValueError("Only one of destination and action may be set") - self.obj = obj - kwargs = dict(left=left, top=top, right=right, bottom=bottom, zoom=zoom) - self.page_location_kwargs = {k: v for k, v in kwargs.items() if v is not None} - self.is_closed = False - self.children: list[OutlineItem] = [] - - def __str__(self): - if self.children: - if self.is_closed: - oc_indicator = '[+]' - else: - oc_indicator = '[-]' - else: - oc_indicator = '[ ]' - if self.destination is not None: - if isinstance(self.destination, Array): - # 12.3.2.2 Explicit destination - # [raw_page, /PageLocation.SomeThing, integer parameters for viewport] - raw_page = self.destination[0] - page = Page(raw_page) - dest = page.label - elif isinstance(self.destination, String): - # 12.3.2.2 Named destination, byte string reference to Names - dest = f'<Named Destination in document .Root.Names dictionary: {self.destination}>' - elif isinstance(self.destination, Name): - # 12.3.2.2 Named destination, name object (PDF 1.1) - dest = f'<Named Destination in document .Root.Dests dictionary: {self.destination}>' - elif isinstance(self.destination, int): - # Page number - dest = f'<Page {self.destination}>' - else: - dest = '<Action>' - return f'{oc_indicator} {self.title} -> {dest}' - - def __repr__(self): - return f'<pikepdf.{self.__class__.__name__}: "{self.title}">' - - @classmethod - def from_dictionary_object(cls, obj: Dictionary): - """Creates a ``OutlineItem`` from a ``Dictionary``. - - Does not process nested items. - - Arguments: - obj: ``Dictionary`` object representing a single outline node. - """ - title = str(obj.Title) - destination = obj.get(Name.Dest) - if destination is not None and not isinstance( - destination, (Array, String, Name) - ): - # 12.3.3: /Dest may be a name, byte string or array - raise OutlineStructureError( - f"Unexpected object type in Outline's /Dest: {destination!r}" - ) - action = obj.get(Name.A) - if action is not None and not isinstance(action, Dictionary): - raise OutlineStructureError( - f"Unexpected object type in Outline's /A: {action!r}" - ) - return cls(title, destination=destination, action=action, obj=obj) - - def to_dictionary_object(self, pdf: Pdf, create_new: bool = False) -> Dictionary: - """Creates/updates a ``Dictionary`` object from this outline node. - - Page numbers are resolved to a page reference on the input - ``Pdf`` object. - - Arguments: - pdf: PDF document object. - create_new: If set to ``True``, creates a new object instead of - modifying an existing one in-place. - """ - if create_new or self.obj is None: - self.obj = obj = pdf.make_indirect(Dictionary()) - else: - obj = self.obj - obj.Title = self.title - if self.destination is not None: - if isinstance(self.destination, int): - self.destination = make_page_destination( - pdf, - self.destination, - self.page_location, - **self.page_location_kwargs, - ) - obj.Dest = self.destination - if Name.A in obj: - del obj.A - elif self.action is not None: - obj.A = self.action - if Name.Dest in obj: - del obj.Dest - return obj - - -class Outline: - """Maintains a intuitive interface for creating and editing PDF document outlines. - - See |pdfrm| section 12.3. - - Arguments: - pdf: PDF document object. - max_depth: Maximum recursion depth to consider when reading the outline. - strict: If set to ``False`` (default) silently ignores structural errors. - Setting it to ``True`` raises a - :class:`pikepdf.OutlineStructureError` - if any object references re-occur while the outline is being read or - written. - - See Also: - :meth:`pikepdf.Pdf.open_outline` - """ - - def __init__(self, pdf: Pdf, max_depth: int = 15, strict: bool = False): - self._root: list[OutlineItem] | None = None - self._pdf = pdf - self._max_depth = max_depth - self._strict = strict - self._updating = False - - def __str__(self): - return str(self.root) - - def __repr__(self): - return f'<pikepdf.{self.__class__.__name__}: {len(self.root)} items>' - - def __enter__(self): - self._updating = True - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - try: - if exc_type is not None: - return - self._save() - finally: - self._updating = False - - def _save_level_outline( - self, - parent: Dictionary, - outline_items: Iterable[OutlineItem], - level: int, - visited_objs: set[tuple[int, int]], - ): - count = 0 - prev: Dictionary | None = None - first: Dictionary | None = None - for item in outline_items: - out_obj = item.to_dictionary_object(self._pdf) - objgen = out_obj.objgen - if objgen in visited_objs: - if self._strict: - raise OutlineStructureError( - f"Outline object {objgen} reoccurred in structure" - ) - out_obj = item.to_dictionary_object(self._pdf, create_new=True) - else: - visited_objs.add(objgen) - - out_obj.Parent = parent - count += 1 - if prev is not None: - prev.Next = out_obj - out_obj.Prev = prev - else: - first = out_obj - if Name.Prev in out_obj: - del out_obj.Prev - prev = out_obj - if level < self._max_depth: - sub_items: Iterable[OutlineItem] = item.children - else: - sub_items = () - self._save_level_outline(out_obj, sub_items, level + 1, visited_objs) - if item.is_closed: - out_obj.Count = -cast(int, out_obj.Count) - else: - count += cast(int, out_obj.Count) - if count: - assert prev is not None and first is not None - if Name.Next in prev: - del prev.Next - parent.First = first - parent.Last = prev - else: - if Name.First in parent: - del parent.First - if Name.Last in parent: - del parent.Last - parent.Count = count - - def _load_level_outline( - self, - first_obj: Dictionary, - outline_items: list[Object], - level: int, - visited_objs: set[tuple[int, int]], - ): - current_obj: Dictionary | None = first_obj - while current_obj: - objgen = current_obj.objgen - if objgen in visited_objs: - if self._strict: - raise OutlineStructureError( - f"Outline object {objgen} reoccurred in structure" - ) - return - visited_objs.add(objgen) - - item = OutlineItem.from_dictionary_object(current_obj) - first_child = current_obj.get(Name.First) - if isinstance(first_child, Dictionary) and level < self._max_depth: - self._load_level_outline( - first_child, item.children, level + 1, visited_objs - ) - count = current_obj.get(Name.Count) - if isinstance(count, int) and count < 0: - item.is_closed = True - outline_items.append(item) - next_obj = current_obj.get(Name.Next) - if next_obj is None or isinstance(next_obj, Dictionary): - current_obj = next_obj - else: - raise OutlineStructureError( - f"Outline object {objgen} points to non-dictionary" - ) - - def _save(self): - if self._root is None: - return - if Name.Outlines in self._pdf.Root: - outlines = self._pdf.Root.Outlines - else: - self._pdf.Root.Outlines = outlines = self._pdf.make_indirect( - Dictionary(Type=Name.Outlines) - ) - self._save_level_outline(outlines, self._root, 0, set()) - - def _load(self): - self._root = root = [] - if Name.Outlines not in self._pdf.Root: - return - outlines = self._pdf.Root.Outlines or {} - first_obj = outlines.get(Name.First) - if first_obj: - self._load_level_outline(first_obj, root, 0, set()) - - @property - def root(self) -> list[OutlineItem]: - """Return the root node of the outline.""" - if self._root is None: - self._load() - return cast(List[OutlineItem], self._root) |