From 1ca7a9d06e2637fe1bf64e194cecd070a96fbefc Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Sun, 12 Jun 2022 15:27:33 +0200 Subject: [PATCH] ENH: Add add_free_text_annotation to PdfWriter Full credit to the GitHub user snakemicro: https://github.com/py-pdf/PyPDF2/issues/107#issuecomment-480579234 Co-authored-by: snakemicro --- PyPDF2/_utils.py | 6 ++++- PyPDF2/_writer.py | 58 +++++++++++++++++++++++++++++++++++++++++++- tests/test_writer.py | 14 +++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/PyPDF2/_utils.py b/PyPDF2/_utils.py index 910bb6327..c1df4ddae 100644 --- a/PyPDF2/_utils.py +++ b/PyPDF2/_utils.py @@ -58,7 +58,7 @@ 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] @@ -66,6 +66,10 @@ 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. diff --git a/PyPDF2/_writer.py b/PyPDF2/_writer.py index c0528eeaa..8be23d0c3 100644 --- a/PyPDF2/_writer.py +++ b/PyPDF2/_writer.py @@ -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 @@ -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: diff --git a/tests/test_writer.py b/tests/test_writer.py index 5eb841a8d..763c9e76e 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -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)