Source code for pypdf.annotations._markup_annotations

import sys
from abc import ABC
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union

from ..generic import ArrayObject, DictionaryObject
from ..generic._base import (
    BooleanObject,
    FloatObject,
    NameObject,
    NumberObject,
    TextStringObject,
)
from ..generic._fit import DEFAULT_FIT, Fit
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


[docs]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)
[docs]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) super() 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)
[docs]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)] )
[docs]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), } )
[docs]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)), } )
[docs]class Rectangle(MarkupAnnotation): def __init__( self, rect: Union[RectangleObject, Tuple[float, float, float, float]], *, interiour_color: Optional[str] = None, **kwargs: Any, ): super().__init__(**kwargs) self.update( { NameObject("/Type"): NameObject("/Annot"), NameObject("/Subtype"): NameObject("/Square"), NameObject("/Rect"): RectangleObject(rect), } ) if interiour_color: self[NameObject("/IC")] = ArrayObject( [FloatObject(n) for n in hex_to_rgb(interiour_color)] )
[docs]class Highlight(MarkupAnnotation): def __init__( self, *, rect: Union[RectangleObject, Tuple[float, float, float, float]], quad_points: ArrayObject, highlight_color: str = "ff0000", **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)] ), } )
[docs]class Ellipse(MarkupAnnotation): def __init__( self, rect: Union[RectangleObject, Tuple[float, float, float, float]], *, interiour_color: Optional[str] = None, **kwargs: Any, ): super().__init__(**kwargs) self.update( { NameObject("/Type"): NameObject("/Annot"), NameObject("/Subtype"): NameObject("/Circle"), NameObject("/Rect"): RectangleObject(rect), } ) if interiour_color: self[NameObject("/IC")] = ArrayObject( [FloatObject(n) for n in hex_to_rgb(interiour_color)] )
[docs]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)), } )