276 lines
9.8 KiB
Python
276 lines
9.8 KiB
Python
from typing import Optional, Tuple, Union
|
|
|
|
from ._base import (
|
|
BooleanObject,
|
|
FloatObject,
|
|
NameObject,
|
|
NumberObject,
|
|
TextStringObject,
|
|
)
|
|
from ._data_structures import ArrayObject, DictionaryObject
|
|
from ._fit import DEFAULT_FIT, Fit
|
|
from ._rectangle import RectangleObject
|
|
from ._utils import hex_to_rgb
|
|
|
|
|
|
class AnnotationBuilder:
|
|
"""
|
|
The AnnotationBuilder creates dictionaries representing PDF annotations.
|
|
|
|
Those dictionaries can be modified before they are added to a PdfWriter
|
|
instance via `writer.add_annotation`.
|
|
|
|
See `adding PDF annotations <../user/adding-pdf-annotations.html>`_ for
|
|
it's usage combined with PdfWriter.
|
|
"""
|
|
|
|
from ..types import FitType, ZoomArgType
|
|
|
|
@staticmethod
|
|
def text(
|
|
rect: Union[RectangleObject, Tuple[float, float, float, float]],
|
|
text: str,
|
|
open: bool = False,
|
|
flags: int = 0,
|
|
) -> DictionaryObject:
|
|
"""
|
|
Add text annotation.
|
|
|
|
:param Tuple[int, int, int, int] rect:
|
|
or array of four integers specifying the clickable rectangular area
|
|
``[xLL, yLL, xUR, yUR]``
|
|
:param bool open:
|
|
:param int flags:
|
|
"""
|
|
# TABLE 8.23 Additional entries specific to a text annotation
|
|
text_obj = DictionaryObject(
|
|
{
|
|
NameObject("/Type"): NameObject("/Annot"),
|
|
NameObject("/Subtype"): NameObject("/Text"),
|
|
NameObject("/Rect"): RectangleObject(rect),
|
|
NameObject("/Contents"): TextStringObject(text),
|
|
NameObject("/Open"): BooleanObject(open),
|
|
NameObject("/Flags"): NumberObject(flags),
|
|
}
|
|
)
|
|
return text_obj
|
|
|
|
@staticmethod
|
|
def free_text(
|
|
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: str = "000000",
|
|
background_color: str = "ffffff",
|
|
) -> DictionaryObject:
|
|
"""
|
|
Add text in a rectangle to a page.
|
|
|
|
:param str text: Text to be added
|
|
:param RectangleObject rect: or array of four integers
|
|
specifying the clickable rectangular area ``[xLL, yLL, xUR, yUR]``
|
|
:param str font: Name of the Font, e.g. 'Helvetica'
|
|
:param bool bold: Print the text in bold
|
|
:param bool italic: Print the text in italic
|
|
:param str font_size: How big the text will be, e.g. '14pt'
|
|
:param str font_color: Hex-string for the color
|
|
:param str border_color: Hex-string for the border color
|
|
:param str background_color: Hex-string for the background of the annotation
|
|
"""
|
|
font_str = "font: "
|
|
if bold is True:
|
|
font_str = font_str + "bold "
|
|
if italic is True:
|
|
font_str = font_str + "italic "
|
|
font_str = font_str + font + " " + font_size
|
|
font_str = font_str + ";text-align:left;color:#" + font_color
|
|
|
|
bg_color_str = ""
|
|
for st in hex_to_rgb(border_color):
|
|
bg_color_str = bg_color_str + str(st) + " "
|
|
bg_color_str = bg_color_str + "rg"
|
|
|
|
free_text = DictionaryObject()
|
|
free_text.update(
|
|
{
|
|
NameObject("/Type"): NameObject("/Annot"),
|
|
NameObject("/Subtype"): NameObject("/FreeText"),
|
|
NameObject("/Rect"): RectangleObject(rect),
|
|
NameObject("/Contents"): TextStringObject(text),
|
|
# font size color
|
|
NameObject("/DS"): TextStringObject(font_str),
|
|
# border color
|
|
NameObject("/DA"): TextStringObject(bg_color_str),
|
|
# background color
|
|
NameObject("/C"): ArrayObject(
|
|
[FloatObject(n) for n in hex_to_rgb(background_color)]
|
|
),
|
|
}
|
|
)
|
|
return free_text
|
|
|
|
@staticmethod
|
|
def line(
|
|
p1: Tuple[float, float],
|
|
p2: Tuple[float, float],
|
|
rect: Union[RectangleObject, Tuple[float, float, float, float]],
|
|
text: str = "",
|
|
title_bar: str = "",
|
|
) -> DictionaryObject:
|
|
"""
|
|
Draw a line on the PDF.
|
|
|
|
:param Tuple[float, float] p1: First point
|
|
:param Tuple[float, float] p2: Second point
|
|
:param RectangleObject rect: or array of four
|
|
integers specifying the clickable rectangular area
|
|
``[xLL, yLL, xUR, yUR]``
|
|
:param str text: Text to be displayed as the line annotation
|
|
:param str title_bar: Text to be displayed in the title bar of the
|
|
annotation; by convention this is the name of the author
|
|
"""
|
|
line_obj = DictionaryObject(
|
|
{
|
|
NameObject("/Type"): NameObject("/Annot"),
|
|
NameObject("/Subtype"): NameObject("/Line"),
|
|
NameObject("/Rect"): RectangleObject(rect),
|
|
NameObject("/T"): TextStringObject(title_bar),
|
|
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),
|
|
}
|
|
)
|
|
return line_obj
|
|
|
|
@staticmethod
|
|
def rectangle(
|
|
rect: Union[RectangleObject, Tuple[float, float, float, float]],
|
|
interiour_color: Optional[str] = None,
|
|
) -> DictionaryObject:
|
|
"""
|
|
Draw a rectangle on the PDF.
|
|
|
|
:param RectangleObject rect: or array of four
|
|
integers specifying the clickable rectangular area
|
|
``[xLL, yLL, xUR, yUR]``
|
|
"""
|
|
square_obj = DictionaryObject(
|
|
{
|
|
NameObject("/Type"): NameObject("/Annot"),
|
|
NameObject("/Subtype"): NameObject("/Square"),
|
|
NameObject("/Rect"): RectangleObject(rect),
|
|
}
|
|
)
|
|
|
|
if interiour_color:
|
|
square_obj[NameObject("/IC")] = ArrayObject(
|
|
[FloatObject(n) for n in hex_to_rgb(interiour_color)]
|
|
)
|
|
|
|
return square_obj
|
|
|
|
@staticmethod
|
|
def link(
|
|
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,
|
|
) -> DictionaryObject:
|
|
"""
|
|
Add a link to the document.
|
|
|
|
The link can either be an external link or an internal link.
|
|
|
|
An external link requires the URL parameter.
|
|
An internal link requires the target_page_index, fit, and fit args.
|
|
|
|
|
|
:param RectangleObject rect: or array of four
|
|
integers specifying the clickable rectangular area
|
|
``[xLL, yLL, xUR, yUR]``
|
|
:param border: if provided, an array describing border-drawing
|
|
properties. See the PDF spec for details. No border will be
|
|
drawn if this argument is omitted.
|
|
- horizontal corner radius,
|
|
- vertical corner radius, and
|
|
- border width
|
|
- Optionally: Dash
|
|
:param str url: Link to a website (if you want to make an external link)
|
|
:param int target_page_index: index of the page to which the link should go
|
|
(if you want to make an internal link)
|
|
:param Fit fit: Page fit or 'zoom' option.
|
|
"""
|
|
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(
|
|
f"Either 'url' or 'target_page_index' have to be provided. url={url}, target_page_index={target_page_index}"
|
|
)
|
|
|
|
border_arr: BorderArrayType
|
|
if border is not None:
|
|
border_arr = [NameObject(n) for n in border[:3]]
|
|
if len(border) == 4:
|
|
dash_pattern = ArrayObject([NameObject(n) for n in border[3]])
|
|
border_arr.append(dash_pattern)
|
|
else:
|
|
border_arr = [NumberObject(0)] * 3
|
|
|
|
link_obj = DictionaryObject(
|
|
{
|
|
NameObject("/Type"): NameObject("/Annot"),
|
|
NameObject("/Subtype"): NameObject("/Link"),
|
|
NameObject("/Rect"): RectangleObject(rect),
|
|
NameObject("/Border"): ArrayObject(border_arr),
|
|
}
|
|
)
|
|
if is_external:
|
|
link_obj[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,
|
|
}
|
|
)
|
|
link_obj[NameObject("/Dest")] = dest_deferred
|
|
return link_obj
|