Update ashboard, dashboard, memory +1 more (+2 ~3)
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
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",
|
||||
"AnnotationDictionary",
|
||||
"Ellipse",
|
||||
"FreeText",
|
||||
"Highlight",
|
||||
"Line",
|
||||
"Link",
|
||||
"MarkupAnnotation",
|
||||
"PolyLine",
|
||||
"Polygon",
|
||||
"Popup",
|
||||
"Rectangle",
|
||||
"Text",
|
||||
]
|
||||
29
venv/lib/python3.12/site-packages/pypdf/annotations/_base.py
Normal file
29
venv/lib/python3.12/site-packages/pypdf/annotations/_base.py
Normal file
@@ -0,0 +1,29 @@
|
||||
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:
|
||||
super().__init__()
|
||||
|
||||
from ..generic._base import NameObject # noqa: PLC0415
|
||||
|
||||
# /Rect should not be added here as Polygon and PolyLine can automatically set it
|
||||
self[NameObject("/Type")] = NameObject("/Annot")
|
||||
# The flags were NOT added to the constructor on purpose:
|
||||
# We expect that most users don't want to change the default.
|
||||
# If they do, 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)
|
||||
@@ -0,0 +1,305 @@
|
||||
import sys
|
||||
from abc import ABC
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *, title_bar: Optional[str] = None) -> 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,
|
||||
) -> 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)),
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,106 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
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
|
||||
|
||||
|
||||
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,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
if TYPE_CHECKING:
|
||||
from ..types import BorderArrayType # noqa: PLC0415
|
||||
|
||||
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=}, {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,
|
||||
) -> None:
|
||||
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 # noqa: PLC0415
|
||||
|
||||
logger_warning(
|
||||
"Unregistered Parent object : No Parent field set",
|
||||
__name__,
|
||||
)
|
||||
Reference in New Issue
Block a user