Spaces:
Runtime error
Runtime error
| import sys | |
| import uuid | |
| from abc import ABC | |
| from typing import Any, Literal, Optional, Union | |
| from ..constants import AnnotationFlag | |
| from ..generic import ArrayObject, DictionaryObject | |
| from ..generic._base import ( | |
| BooleanObject, | |
| FloatObject, | |
| IndirectObject, | |
| 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) | |
| return RectangleObject((x_min, y_min, x_max, y_max)) | |
| 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 | |
| in_reply_to: The annotation that this annotation is "in reply to" | |
| (PDF 1.5). Can be either an annotation (previously added using | |
| :meth:`~pypdf.PdfWriter.add_annotation`) or a reference to the | |
| target annotation. | |
| reply_type: The relationship between this annotation and the one | |
| specified by ``in_reply_to``. Either ``"R"`` (a reply, default) | |
| or ``"Group"`` (grouped with the parent annotation). Raises | |
| ``ValueError`` if a non-default value is provided without | |
| ``in_reply_to``. | |
| annotation_name: A text string uniquely identifying this annotation | |
| among all annotations on its page. Automatically generated when | |
| ``in_reply_to`` is set and no name is provided. Raises | |
| ``ValueError`` if provided without ``in_reply_to``. | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| title_bar: Optional[str] = None, | |
| in_reply_to: Optional[Union[DictionaryObject, IndirectObject]] = None, | |
| reply_type: Literal["R", "Group"] = "R", | |
| annotation_name: Optional[str] = None, | |
| ) -> None: | |
| if title_bar is not None: | |
| self[NameObject("/T")] = TextStringObject(title_bar) | |
| if annotation_name is not None and in_reply_to is None: | |
| raise ValueError( | |
| "annotation_name is only supported when in_reply_to is set" | |
| ) | |
| if reply_type != "R" and in_reply_to is None: | |
| raise ValueError( | |
| "reply_type is only meaningful when in_reply_to is set" | |
| ) | |
| if in_reply_to is not None: | |
| if isinstance(in_reply_to, IndirectObject): | |
| ref: IndirectObject = in_reply_to | |
| else: | |
| indirect_ref = getattr(in_reply_to, "indirect_reference", None) | |
| if not isinstance(indirect_ref, IndirectObject): | |
| raise ValueError( | |
| "in_reply_to must be a registered annotation " | |
| "(added via writer.add_annotation() first)" | |
| ) | |
| ref = indirect_ref | |
| self[NameObject("/IRT")] = ref | |
| self[NameObject("/RT")] = NameObject(f"/{reply_type}") | |
| if annotation_name is None: | |
| annotation_name = str(uuid.uuid4()) | |
| self[NameObject("/NM")] = TextStringObject(annotation_name) | |
| 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, | |
| ) -> None: | |
| 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, | |
| ) -> None: | |
| super().__init__(**kwargs) | |
| self[NameObject("/Subtype")] = NameObject("/FreeText") | |
| self[NameObject("/Rect")] = RectangleObject(rect) | |
| # Table 225 of the 1.7 reference ("CSS2 style attributes used in rich text strings") | |
| font_str = "font: " | |
| if italic: | |
| font_str = f"{font_str}italic " | |
| else: | |
| font_str = f"{font_str}normal " | |
| if bold: | |
| font_str = f"{font_str}bold " | |
| else: | |
| font_str = f"{font_str}normal " | |
| font_str = f"{font_str}{font_size} {font}" | |
| 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, | |
| ) -> None: | |
| 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, | |
| ) -> None: | |
| super().__init__(**kwargs) | |
| if len(vertices) == 0: | |
| raise ValueError("A polyline 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, | |
| ) -> None: | |
| 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, | |
| ) -> None: | |
| 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, | |
| ) -> None: | |
| 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, | |
| ) -> None: | |
| 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)), | |
| } | |
| ) | |