about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pypdf/annotations
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pypdf/annotations')
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py45
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py27
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py308
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py109
4 files changed, 489 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py
new file mode 100644
index 00000000..3ddf9856
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py
@@ -0,0 +1,45 @@
+"""
+PDF specifies several annotation types which pypdf makes available here.
+
+The names of the annotations and their attributes do not reflect the names in
+the specification in all cases. For example, the PDF standard defines a
+'Square' annotation that does not actually need to be square. For this reason,
+pypdf calls it 'Rectangle'.
+
+At their core, all annotation types are DictionaryObjects. That means if pypdf
+does not implement a feature, users can easily extend the given functionality.
+"""
+
+
+from ._base import NO_FLAGS, AnnotationDictionary
+from ._markup_annotations import (
+    Ellipse,
+    FreeText,
+    Highlight,
+    Line,
+    MarkupAnnotation,
+    Polygon,
+    PolyLine,
+    Rectangle,
+    Text,
+)
+from ._non_markup_annotations import Link, Popup
+
+__all__ = [
+    "NO_FLAGS",
+    # Export abstract base classes so that they are shown in the docs
+    "AnnotationDictionary",
+    "MarkupAnnotation",
+    # markup annotations
+    "Ellipse",
+    "FreeText",
+    "Highlight",
+    "Line",
+    "Link",
+    "Polygon",
+    "PolyLine",
+    "Rectangle",
+    "Text",
+    # Non-markup annotations
+    "Popup",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py
new file mode 100644
index 00000000..f235acf3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py
@@ -0,0 +1,27 @@
+from abc import ABC
+
+from ..constants import AnnotationFlag
+from ..generic import NameObject, NumberObject
+from ..generic._data_structures import DictionaryObject
+
+
+class AnnotationDictionary(DictionaryObject, ABC):
+    def __init__(self) -> None:
+        from ..generic._base import NameObject
+
+        # "rect" should not be added here as PolyLine can automatically set it
+        self[NameObject("/Type")] = NameObject("/Annot")
+        # The flags was NOT added to the constructor on purpose: We expect that
+        #   most users don't want to change the default. If they want, they
+        #   can use the property. The default is 0.
+
+    @property
+    def flags(self) -> AnnotationFlag:
+        return self.get(NameObject("/F"), AnnotationFlag(0))
+
+    @flags.setter
+    def flags(self, value: AnnotationFlag) -> None:
+        self[NameObject("/F")] = NumberObject(value)
+
+
+NO_FLAGS = AnnotationFlag(0)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py
new file mode 100644
index 00000000..4db8dfdb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py
@@ -0,0 +1,308 @@
+import sys
+from abc import ABC
+from typing import Any, List, Optional, Tuple, Union
+
+from .._utils import deprecate_with_replacement
+from ..constants import AnnotationFlag
+from ..generic import ArrayObject, DictionaryObject
+from ..generic._base import (
+    BooleanObject,
+    FloatObject,
+    NameObject,
+    NumberObject,
+    TextStringObject,
+)
+from ..generic._rectangle import RectangleObject
+from ..generic._utils import hex_to_rgb
+from ._base import NO_FLAGS, AnnotationDictionary
+
+if sys.version_info[:2] >= (3, 10):
+    from typing import TypeAlias
+else:
+    # PEP 613 introduced typing.TypeAlias with Python 3.10
+    # For older Python versions, the backport typing_extensions is necessary:
+    from typing_extensions import TypeAlias
+
+
+Vertex: TypeAlias = Tuple[float, float]
+
+
+def _get_bounding_rectangle(vertices: List[Vertex]) -> RectangleObject:
+    x_min, y_min = vertices[0][0], vertices[0][1]
+    x_max, y_max = vertices[0][0], vertices[0][1]
+    for x, y in vertices:
+        x_min = min(x_min, x)
+        y_min = min(y_min, y)
+        x_max = max(x_max, x)
+        y_max = max(y_max, y)
+    rect = RectangleObject((x_min, y_min, x_max, y_max))
+    return rect
+
+
+class MarkupAnnotation(AnnotationDictionary, ABC):
+    """
+    Base class for all markup annotations.
+
+    Args:
+        title_bar: Text to be displayed in the title bar of the annotation;
+            by convention this is the name of the author
+    """
+
+    def __init__(self, *, title_bar: Optional[str] = None):
+        if title_bar is not None:
+            self[NameObject("T")] = TextStringObject(title_bar)
+
+
+class Text(MarkupAnnotation):
+    """
+    A text annotation.
+
+    Args:
+        rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+            specifying the clickable rectangular area
+        text: The text that is added to the document
+        open:
+        flags:
+    """
+
+    def __init__(
+        self,
+        *,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        text: str,
+        open: bool = False,
+        flags: int = NO_FLAGS,
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        self[NameObject("/Subtype")] = NameObject("/Text")
+        self[NameObject("/Rect")] = RectangleObject(rect)
+        self[NameObject("/Contents")] = TextStringObject(text)
+        self[NameObject("/Open")] = BooleanObject(open)
+        self[NameObject("/Flags")] = NumberObject(flags)
+
+
+class FreeText(MarkupAnnotation):
+    """A FreeText annotation"""
+
+    def __init__(
+        self,
+        *,
+        text: str,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        font: str = "Helvetica",
+        bold: bool = False,
+        italic: bool = False,
+        font_size: str = "14pt",
+        font_color: str = "000000",
+        border_color: Optional[str] = "000000",
+        background_color: Optional[str] = "ffffff",
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        self[NameObject("/Subtype")] = NameObject("/FreeText")
+        self[NameObject("/Rect")] = RectangleObject(rect)
+
+        font_str = "font: "
+        if bold is True:
+            font_str = f"{font_str}bold "
+        if italic is True:
+            font_str = f"{font_str}italic "
+        font_str = f"{font_str}{font} {font_size}"
+        font_str = f"{font_str};text-align:left;color:#{font_color}"
+
+        default_appearance_string = ""
+        if border_color:
+            for st in hex_to_rgb(border_color):
+                default_appearance_string = f"{default_appearance_string}{st} "
+            default_appearance_string = f"{default_appearance_string}rg"
+
+        self.update(
+            {
+                NameObject("/Subtype"): NameObject("/FreeText"),
+                NameObject("/Rect"): RectangleObject(rect),
+                NameObject("/Contents"): TextStringObject(text),
+                # font size color
+                NameObject("/DS"): TextStringObject(font_str),
+                NameObject("/DA"): TextStringObject(default_appearance_string),
+            }
+        )
+        if border_color is None:
+            # Border Style
+            self[NameObject("/BS")] = DictionaryObject(
+                {
+                    # width of 0 means no border
+                    NameObject("/W"): NumberObject(0)
+                }
+            )
+        if background_color is not None:
+            self[NameObject("/C")] = ArrayObject(
+                [FloatObject(n) for n in hex_to_rgb(background_color)]
+            )
+
+
+class Line(MarkupAnnotation):
+    def __init__(
+        self,
+        p1: Vertex,
+        p2: Vertex,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        text: str = "",
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        self.update(
+            {
+                NameObject("/Subtype"): NameObject("/Line"),
+                NameObject("/Rect"): RectangleObject(rect),
+                NameObject("/L"): ArrayObject(
+                    [
+                        FloatObject(p1[0]),
+                        FloatObject(p1[1]),
+                        FloatObject(p2[0]),
+                        FloatObject(p2[1]),
+                    ]
+                ),
+                NameObject("/LE"): ArrayObject(
+                    [
+                        NameObject("/None"),
+                        NameObject("/None"),
+                    ]
+                ),
+                NameObject("/IC"): ArrayObject(
+                    [
+                        FloatObject(0.5),
+                        FloatObject(0.5),
+                        FloatObject(0.5),
+                    ]
+                ),
+                NameObject("/Contents"): TextStringObject(text),
+            }
+        )
+
+
+class PolyLine(MarkupAnnotation):
+    def __init__(
+        self,
+        vertices: List[Vertex],
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        if len(vertices) == 0:
+            raise ValueError("A polygon needs at least 1 vertex with two coordinates")
+        coord_list = []
+        for x, y in vertices:
+            coord_list.append(NumberObject(x))
+            coord_list.append(NumberObject(y))
+        self.update(
+            {
+                NameObject("/Subtype"): NameObject("/PolyLine"),
+                NameObject("/Vertices"): ArrayObject(coord_list),
+                NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),
+            }
+        )
+
+
+class Rectangle(MarkupAnnotation):
+    def __init__(
+        self,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        *,
+        interior_color: Optional[str] = None,
+        **kwargs: Any,
+    ):
+        if "interiour_color" in kwargs:
+            deprecate_with_replacement("interiour_color", "interior_color", "6.0.0")
+            interior_color = kwargs["interiour_color"]
+            del kwargs["interiour_color"]
+        super().__init__(**kwargs)
+        self.update(
+            {
+                NameObject("/Type"): NameObject("/Annot"),
+                NameObject("/Subtype"): NameObject("/Square"),
+                NameObject("/Rect"): RectangleObject(rect),
+            }
+        )
+
+        if interior_color:
+            self[NameObject("/IC")] = ArrayObject(
+                [FloatObject(n) for n in hex_to_rgb(interior_color)]
+            )
+
+
+class Highlight(MarkupAnnotation):
+    def __init__(
+        self,
+        *,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        quad_points: ArrayObject,
+        highlight_color: str = "ff0000",
+        printing: bool = False,
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        self.update(
+            {
+                NameObject("/Subtype"): NameObject("/Highlight"),
+                NameObject("/Rect"): RectangleObject(rect),
+                NameObject("/QuadPoints"): quad_points,
+                NameObject("/C"): ArrayObject(
+                    [FloatObject(n) for n in hex_to_rgb(highlight_color)]
+                ),
+            }
+        )
+        if printing:
+            self.flags = AnnotationFlag.PRINT
+
+
+class Ellipse(MarkupAnnotation):
+    def __init__(
+        self,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        *,
+        interior_color: Optional[str] = None,
+        **kwargs: Any,
+    ):
+        if "interiour_color" in kwargs:
+            deprecate_with_replacement("interiour_color", "interior_color", "6.0.0")
+            interior_color = kwargs["interiour_color"]
+            del kwargs["interiour_color"]
+        super().__init__(**kwargs)
+
+        self.update(
+            {
+                NameObject("/Type"): NameObject("/Annot"),
+                NameObject("/Subtype"): NameObject("/Circle"),
+                NameObject("/Rect"): RectangleObject(rect),
+            }
+        )
+
+        if interior_color:
+            self[NameObject("/IC")] = ArrayObject(
+                [FloatObject(n) for n in hex_to_rgb(interior_color)]
+            )
+
+
+class Polygon(MarkupAnnotation):
+    def __init__(
+        self,
+        vertices: List[Tuple[float, float]],
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        if len(vertices) == 0:
+            raise ValueError("A polygon needs at least 1 vertex with two coordinates")
+
+        coord_list = []
+        for x, y in vertices:
+            coord_list.append(NumberObject(x))
+            coord_list.append(NumberObject(y))
+        self.update(
+            {
+                NameObject("/Type"): NameObject("/Annot"),
+                NameObject("/Subtype"): NameObject("/Polygon"),
+                NameObject("/Vertices"): ArrayObject(coord_list),
+                NameObject("/IT"): NameObject("/PolygonCloud"),
+                NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),
+            }
+        )
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py
new file mode 100644
index 00000000..dcdb3b0f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py
@@ -0,0 +1,109 @@
+from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
+
+from ..constants import AnnotationFlag
+from ..generic._base import (
+    BooleanObject,
+    NameObject,
+    NumberObject,
+    TextStringObject,
+)
+from ..generic._data_structures import ArrayObject, DictionaryObject
+from ..generic._fit import DEFAULT_FIT, Fit
+from ..generic._rectangle import RectangleObject
+from ._base import AnnotationDictionary
+
+DEFAULT_ANNOTATION_FLAG = AnnotationFlag(0)
+
+
+class Link(AnnotationDictionary):
+    def __init__(
+        self,
+        *,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        border: Optional[ArrayObject] = None,
+        url: Optional[str] = None,
+        target_page_index: Optional[int] = None,
+        fit: Fit = DEFAULT_FIT,
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        if TYPE_CHECKING:
+            from ..types import BorderArrayType
+
+        is_external = url is not None
+        is_internal = target_page_index is not None
+        if not is_external and not is_internal:
+            raise ValueError(
+                "Either 'url' or 'target_page_index' have to be provided. Both were None."
+            )
+        if is_external and is_internal:
+            raise ValueError(
+                "Either 'url' or 'target_page_index' have to be provided. "
+                f"url={url}, target_page_index={target_page_index}"
+            )
+
+        border_arr: BorderArrayType
+        if border is not None:
+            border_arr = [NumberObject(n) for n in border[:3]]
+            if len(border) == 4:
+                dash_pattern = ArrayObject([NumberObject(n) for n in border[3]])
+                border_arr.append(dash_pattern)
+        else:
+            border_arr = [NumberObject(0)] * 3
+
+        self.update(
+            {
+                NameObject("/Type"): NameObject("/Annot"),
+                NameObject("/Subtype"): NameObject("/Link"),
+                NameObject("/Rect"): RectangleObject(rect),
+                NameObject("/Border"): ArrayObject(border_arr),
+            }
+        )
+        if is_external:
+            self[NameObject("/A")] = DictionaryObject(
+                {
+                    NameObject("/S"): NameObject("/URI"),
+                    NameObject("/Type"): NameObject("/Action"),
+                    NameObject("/URI"): TextStringObject(url),
+                }
+            )
+        if is_internal:
+            # This needs to be updated later!
+            dest_deferred = DictionaryObject(
+                {
+                    "target_page_index": NumberObject(target_page_index),
+                    "fit": NameObject(fit.fit_type),
+                    "fit_args": fit.fit_args,
+                }
+            )
+            self[NameObject("/Dest")] = dest_deferred
+
+
+class Popup(AnnotationDictionary):
+    def __init__(
+        self,
+        *,
+        rect: Union[RectangleObject, Tuple[float, float, float, float]],
+        parent: Optional[DictionaryObject] = None,
+        open: bool = False,
+        **kwargs: Any,
+    ):
+        super().__init__(**kwargs)
+        self.update(
+            {
+                NameObject("/Subtype"): NameObject("/Popup"),
+                NameObject("/Rect"): RectangleObject(rect),
+                NameObject("/Open"): BooleanObject(open),
+            }
+        )
+        if parent:
+            # This needs to be an indirect object
+            try:
+                self[NameObject("/Parent")] = parent.indirect_reference
+            except AttributeError:
+                from .._utils import logger_warning
+
+                logger_warning(
+                    "Unregistered Parent object : No Parent field set",
+                    __name__,
+                )