Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_markup_annotations.py352 linesDownload Raw Back to annotations
1import sys2import uuid3from abc import ABC4from typing import Any, Literal, Optional, Union5 6from ..constants import AnnotationFlag7from ..generic import ArrayObject, DictionaryObject8from ..generic._base import (9    BooleanObject,10    FloatObject,11    IndirectObject,12    NameObject,13    NumberObject,14    TextStringObject,15)16from ..generic._rectangle import RectangleObject17from ..generic._utils import hex_to_rgb18from ._base import NO_FLAGS, AnnotationDictionary19 20if sys.version_info[:2] >= (3, 10):21    from typing import TypeAlias22else:23    # PEP 613 introduced typing.TypeAlias with Python 3.1024    # For older Python versions, the backport typing_extensions is necessary:25    from typing_extensions import TypeAlias26 27 28Vertex: TypeAlias = tuple[float, float]29 30 31def _get_bounding_rectangle(vertices: list[Vertex]) -> RectangleObject:32    x_min, y_min = vertices[0][0], vertices[0][1]33    x_max, y_max = vertices[0][0], vertices[0][1]34    for x, y in vertices:35        x_min = min(x_min, x)36        y_min = min(y_min, y)37        x_max = max(x_max, x)38        y_max = max(y_max, y)39    return RectangleObject((x_min, y_min, x_max, y_max))40 41 42class MarkupAnnotation(AnnotationDictionary, ABC):43    """44    Base class for all markup annotations.45 46    Args:47        title_bar: Text to be displayed in the title bar of the annotation;48            by convention this is the name of the author49        in_reply_to: The annotation that this annotation is "in reply to"50            (PDF 1.5). Can be either an annotation (previously added using51            :meth:`~pypdf.PdfWriter.add_annotation`) or a reference to the52            target annotation.53        reply_type: The relationship between this annotation and the one54            specified by ``in_reply_to``. Either ``"R"`` (a reply, default)55            or ``"Group"`` (grouped with the parent annotation). Raises56            ``ValueError`` if a non-default value is provided without57            ``in_reply_to``.58        annotation_name: A text string uniquely identifying this annotation59            among all annotations on its page. Automatically generated when60            ``in_reply_to`` is set and no name is provided. Raises61            ``ValueError`` if provided without ``in_reply_to``.62 63    """64 65    def __init__(66        self,67        *,68        title_bar: Optional[str] = None,69        in_reply_to: Optional[Union[DictionaryObject, IndirectObject]] = None,70        reply_type: Literal["R", "Group"] = "R",71        annotation_name: Optional[str] = None,72    ) -> None:73        if title_bar is not None:74            self[NameObject("/T")] = TextStringObject(title_bar)75        if annotation_name is not None and in_reply_to is None:76            raise ValueError(77                "annotation_name is only supported when in_reply_to is set"78            )79        if reply_type != "R" and in_reply_to is None:80            raise ValueError(81                "reply_type is only meaningful when in_reply_to is set"82            )83        if in_reply_to is not None:84            if isinstance(in_reply_to, IndirectObject):85                ref: IndirectObject = in_reply_to86            else:87                indirect_ref = getattr(in_reply_to, "indirect_reference", None)88                if not isinstance(indirect_ref, IndirectObject):89                    raise ValueError(90                        "in_reply_to must be a registered annotation "91                        "(added via writer.add_annotation() first)"92                    )93                ref = indirect_ref94            self[NameObject("/IRT")] = ref95            self[NameObject("/RT")] = NameObject(f"/{reply_type}")96            if annotation_name is None:97                annotation_name = str(uuid.uuid4())98            self[NameObject("/NM")] = TextStringObject(annotation_name)99 100 101class Text(MarkupAnnotation):102    """103    A text annotation.104 105    Args:106        rect: array of four integers ``[xLL, yLL, xUR, yUR]``107            specifying the clickable rectangular area108        text: The text that is added to the document109        open:110        flags:111 112    """113 114    def __init__(115        self,116        *,117        rect: Union[RectangleObject, tuple[float, float, float, float]],118        text: str,119        open: bool = False,120        flags: int = NO_FLAGS,121        **kwargs: Any,122    ) -> None:123        super().__init__(**kwargs)124        self[NameObject("/Subtype")] = NameObject("/Text")125        self[NameObject("/Rect")] = RectangleObject(rect)126        self[NameObject("/Contents")] = TextStringObject(text)127        self[NameObject("/Open")] = BooleanObject(open)128        self[NameObject("/Flags")] = NumberObject(flags)129 130 131class FreeText(MarkupAnnotation):132    """A FreeText annotation"""133 134    def __init__(135        self,136        *,137        text: str,138        rect: Union[RectangleObject, tuple[float, float, float, float]],139        font: str = "Helvetica",140        bold: bool = False,141        italic: bool = False,142        font_size: str = "14pt",143        font_color: str = "000000",144        border_color: Optional[str] = "000000",145        background_color: Optional[str] = "ffffff",146        **kwargs: Any,147    ) -> None:148        super().__init__(**kwargs)149        self[NameObject("/Subtype")] = NameObject("/FreeText")150        self[NameObject("/Rect")] = RectangleObject(rect)151 152        # Table 225 of the 1.7 reference ("CSS2 style attributes used in rich text strings")153        font_str = "font: "154        if italic:155            font_str = f"{font_str}italic "156        else:157            font_str = f"{font_str}normal "158        if bold:159            font_str = f"{font_str}bold "160        else:161            font_str = f"{font_str}normal "162        font_str = f"{font_str}{font_size} {font}"163        font_str = f"{font_str};text-align:left;color:#{font_color}"164 165        default_appearance_string = ""166        if border_color:167            for st in hex_to_rgb(border_color):168                default_appearance_string = f"{default_appearance_string}{st} "169            default_appearance_string = f"{default_appearance_string}rg"170 171        self.update(172            {173                NameObject("/Subtype"): NameObject("/FreeText"),174                NameObject("/Rect"): RectangleObject(rect),175                NameObject("/Contents"): TextStringObject(text),176                # font size color177                NameObject("/DS"): TextStringObject(font_str),178                NameObject("/DA"): TextStringObject(default_appearance_string),179            }180        )181        if border_color is None:182            # Border Style183            self[NameObject("/BS")] = DictionaryObject(184                {185                    # width of 0 means no border186                    NameObject("/W"): NumberObject(0)187                }188            )189        if background_color is not None:190            self[NameObject("/C")] = ArrayObject(191                [FloatObject(n) for n in hex_to_rgb(background_color)]192            )193 194 195class Line(MarkupAnnotation):196    def __init__(197        self,198        p1: Vertex,199        p2: Vertex,200        rect: Union[RectangleObject, tuple[float, float, float, float]],201        text: str = "",202        **kwargs: Any,203    ) -> None:204        super().__init__(**kwargs)205        self.update(206            {207                NameObject("/Subtype"): NameObject("/Line"),208                NameObject("/Rect"): RectangleObject(rect),209                NameObject("/L"): ArrayObject(210                    [211                        FloatObject(p1[0]),212                        FloatObject(p1[1]),213                        FloatObject(p2[0]),214                        FloatObject(p2[1]),215                    ]216                ),217                NameObject("/LE"): ArrayObject(218                    [219                        NameObject("/None"),220                        NameObject("/None"),221                    ]222                ),223                NameObject("/IC"): ArrayObject(224                    [225                        FloatObject(0.5),226                        FloatObject(0.5),227                        FloatObject(0.5),228                    ]229                ),230                NameObject("/Contents"): TextStringObject(text),231            }232        )233 234 235class PolyLine(MarkupAnnotation):236    def __init__(237        self,238        vertices: list[Vertex],239        **kwargs: Any,240    ) -> None:241        super().__init__(**kwargs)242        if len(vertices) == 0:243            raise ValueError("A polyline needs at least 1 vertex with two coordinates")244        coord_list = []245        for x, y in vertices:246            coord_list.append(NumberObject(x))247            coord_list.append(NumberObject(y))248        self.update(249            {250                NameObject("/Subtype"): NameObject("/PolyLine"),251                NameObject("/Vertices"): ArrayObject(coord_list),252                NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),253            }254        )255 256 257class Rectangle(MarkupAnnotation):258    def __init__(259        self,260        rect: Union[RectangleObject, tuple[float, float, float, float]],261        *,262        interior_color: Optional[str] = None,263        **kwargs: Any,264    ) -> None:265        super().__init__(**kwargs)266        self.update(267            {268                NameObject("/Type"): NameObject("/Annot"),269                NameObject("/Subtype"): NameObject("/Square"),270                NameObject("/Rect"): RectangleObject(rect),271            }272        )273 274        if interior_color:275            self[NameObject("/IC")] = ArrayObject(276                [FloatObject(n) for n in hex_to_rgb(interior_color)]277            )278 279 280class Highlight(MarkupAnnotation):281    def __init__(282        self,283        *,284        rect: Union[RectangleObject, tuple[float, float, float, float]],285        quad_points: ArrayObject,286        highlight_color: str = "ff0000",287        printing: bool = False,288        **kwargs: Any,289    ) -> None:290        super().__init__(**kwargs)291        self.update(292            {293                NameObject("/Subtype"): NameObject("/Highlight"),294                NameObject("/Rect"): RectangleObject(rect),295                NameObject("/QuadPoints"): quad_points,296                NameObject("/C"): ArrayObject(297                    [FloatObject(n) for n in hex_to_rgb(highlight_color)]298                ),299            }300        )301        if printing:302            self.flags = AnnotationFlag.PRINT303 304 305class Ellipse(MarkupAnnotation):306    def __init__(307        self,308        rect: Union[RectangleObject, tuple[float, float, float, float]],309        *,310        interior_color: Optional[str] = None,311        **kwargs: Any,312    ) -> None:313        super().__init__(**kwargs)314 315        self.update(316            {317                NameObject("/Type"): NameObject("/Annot"),318                NameObject("/Subtype"): NameObject("/Circle"),319                NameObject("/Rect"): RectangleObject(rect),320            }321        )322 323        if interior_color:324            self[NameObject("/IC")] = ArrayObject(325                [FloatObject(n) for n in hex_to_rgb(interior_color)]326            )327 328 329class Polygon(MarkupAnnotation):330    def __init__(331        self,332        vertices: list[tuple[float, float]],333        **kwargs: Any,334    ) -> None:335        super().__init__(**kwargs)336        if len(vertices) == 0:337            raise ValueError("A polygon needs at least 1 vertex with two coordinates")338 339        coord_list = []340        for x, y in vertices:341            coord_list.append(NumberObject(x))342            coord_list.append(NumberObject(y))343        self.update(344            {345                NameObject("/Type"): NameObject("/Annot"),346                NameObject("/Subtype"): NameObject("/Polygon"),347                NameObject("/Vertices"): ArrayObject(coord_list),348                NameObject("/IT"): NameObject("/PolygonCloud"),349                NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),350            }351        )352 
codekingpro/portable-devtools · Team Ai