Skip to content

Commit

Permalink
ENH: Add add_free_text_annotation to PdfWriter
Browse files Browse the repository at this point in the history
Full credit to the GitHub user snakemicro:
#107 (comment)

Co-authored-by: snakemicro
  • Loading branch information
MartinThoma committed Jun 12, 2022
1 parent 41eff2a commit 1ca7a9d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 2 deletions.
6 changes: 5 additions & 1 deletion PyPDF2/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@
float, float, float, float, float, float
]

bytes_type = type(bytes()) # Works the same in Python 2.X and 3.X
bytes_type = bytes # Works the same in Python 2.X and 3.X
StreamType = Union[BytesIO, BufferedReader, BufferedWriter, FileIO]
StrByteType = Union[str, StreamType]

DEPR_MSG_NO_REPLACEMENT = "{} is deprecated and will be removed in PyPDF2 {}."
DEPR_MSG = "{} is deprecated and will be removed in PyPDF2 3.0.0. Use {} instead."


def hex_to_rgb(value: str) -> Tuple[int, int, int]:
return tuple(int(value[i : i + 2], 16) / 255.0 for i in (0, 2, 4)) # type: ignore


def read_until_whitespace(stream: StreamType, maxchars: Optional[int] = None) -> bytes:
"""
Reads non-whitespace characters and returns them.
Expand Down
58 changes: 57 additions & 1 deletion PyPDF2/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from ._page import PageObject, _VirtualList
from ._reader import PdfReader
from ._security import _alg33, _alg34, _alg35
from ._utils import StreamType, b_, deprecate_with_replacement
from ._utils import StreamType, b_, deprecate_with_replacement, hex_to_rgb
from .constants import CatalogAttributes as CA
from .constants import Core as CO
from .constants import EncryptionDictAttributes as ED
Expand Down Expand Up @@ -143,6 +143,62 @@ def getObject(self, ido: IndirectObject) -> PdfObject: # pragma: no cover
deprecate_with_replacement("getObject", "get_object")
return self.get_object(ido)

def add_free_text_annotation(
self,
page_number: int,
text: str,
rect: Tuple[float, float, float, float],
font: str = "Helvetica",
bold: bool = False,
italic: bool = False,
font_size: str = "14pt",
font_color: str = "ff0000",
border_color: str = "ff0000",
bg_color: str = "ffffff",
) -> None:
"""Add text in a rectangle to a page."""
page_link = self.get_object(self._pages)["/Kids"][page_number] # type: ignore
page_ref = cast(DictionaryObject, self.get_object(page_link))

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("/P"): page_link,
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(bg_color)]
),
}
)

link_ref = self._add_object(free_text)

if "/Annots" in page_ref:
page_ref["/Annots"].append(link_ref) # type: ignore
else:
page_ref[NameObject("/Annots")] = ArrayObject([link_ref])

def _add_page(
self, page: PageObject, action: Callable[[Any, IndirectObject], None]
) -> None:
Expand Down
14 changes: 14 additions & 0 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,17 @@ def test_issue301():
writer.append_pages_from_reader(reader)
o = BytesIO()
writer.write(o)


def test_add_free_text_annotation():
filepath = os.path.join(RESOURCE_ROOT, "crazyones.pdf")

writer = PdfWriter()
reader = PdfReader(filepath)

writer.add_page(reader.pages[0])

writer.add_free_text_annotation(0, "Hello World", rect=(0, 0, 100, 100))

with open("foo.pdf", "wb") as fh:
writer.write(fh)

0 comments on commit 1ca7a9d

Please sign in to comment.