From c3ae7422fb6cac9f480de2d8be8f9b3da1983e7b Mon Sep 17 00:00:00 2001 From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:27:38 +0100 Subject: [PATCH] Adding optional title parameter to text_annotation() (#1302) --- .pylintrc | 2 +- CHANGELOG.md | 1 + docs/Development.md | 2 +- docs/Metadata.md | 6 ++- fpdf/enums.py | 22 ++++++++ fpdf/fpdf.py | 5 +- fpdf/prefs.py | 128 ++++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 157 insertions(+), 9 deletions(-) diff --git a/.pylintrc b/.pylintrc index dddab5dfc..c019d7dc1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,5 +1,5 @@ [MESSAGES CONTROL] -max-line-length=150 +max-line-length=155 # Disable superflous / noisy rules: disable = attribute-defined-outside-init, consider-using-max-builtin, consider-using-min-builtin, invalid-name, method-hidden, missing-docstring, multiple-imports, too-few-public-methods, too-many-arguments, too-many-instance-attributes, too-many-nested-blocks, too-many-branches, too-many-lines, too-many-locals, too-many-positional-arguments, too-many-public-methods, too-many-statements, use-dict-literal, wrong-import-order diff --git a/CHANGELOG.md b/CHANGELOG.md index 1904c64b4..4c4ae2c8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default', * new optional parameter `border` for table cells [issue #1192](https://github.com/py-pdf/fpdf2/issues/1192) users can define specific borders (left, right, top, bottom) for individual cells * [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): now parses `` tags to set the [document title](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.set_title). By default, it is added as PDF metadata, but not rendered in the document body. However, this can be enabled by passing `render_title_tag=True` to `FPDF.write_html()`. * support for LZWDecode compression [issue #1271](https://github.com/py-pdf/fpdf2/issues/1271) +* [text_annotation()](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.text_annotation) has a new optional `title` parameter ### Fixed * `FPDF.set_text_shaping(False)` was broken since version 2.7.8 and is now working properly - [issue #1287](https://github.com/py-pdf/fpdf2/issues/1287) * fixed bug where cells with `rowspan`, `colspan` > 1 and null text were not displayed properly - [issue #1293](https://github.com/py-pdf/fpdf2/issues/1293) diff --git a/docs/Development.md b/docs/Development.md index be277b197..45c43a239 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -237,7 +237,7 @@ There is a useful one-page example Python module with docstrings illustrating ho To preview the Markdown documentation, launch a local rendering server with: - mkdocs serve + mkdocs serve --open To preview the API documentation, launch a local rendering server with: diff --git a/docs/Metadata.md b/docs/Metadata.md index 1b4470f3f..2e920116c 100644 --- a/docs/Metadata.md +++ b/docs/Metadata.md @@ -30,11 +30,13 @@ from fpdf import FPDF_VERSION with pikepdf.open(sys.argv[1], allow_overwriting_input=True) as pdf: with pdf.open_metadata(set_pikepdf_as_editor=False) as meta: meta["dc:title"] = "Title" - meta["dc:description"] = "Description" + meta["dc:language"] = "en-US" meta["dc:creator"] = ["Author1", "Author2"] + meta["dc:description"] = "Description" + meta["dc:subject"] = "keyword1 keyword2 keyword3" meta["pdf:Keywords"] = "keyword1 keyword2 keyword3" meta["pdf:Producer"] = f"py-pdf/fpdf{FPDF_VERSION}" meta["xmp:CreatorTool"] = __file__ - meta["xmp:MetadataDate"] = datetime.now(datetime.utcnow().astimezone().tzinfo).isoformat() + meta["xmp:CreateDate"] = datetime.now(datetime.utcnow().astimezone().tzinfo).isoformat() pdf.save() ``` diff --git a/fpdf/enums.py b/fpdf/enums.py index d0188b951..cb70bdf09 100644 --- a/fpdf/enums.py +++ b/fpdf/enums.py @@ -995,6 +995,7 @@ class EncryptionMethod(Enum): class TextDirection(CoerciveEnum): "Text rendering direction for text shaping" + LTR = intern("LTR") "left to right" @@ -1006,3 +1007,24 @@ class TextDirection(CoerciveEnum): BTT = intern("BTT") "bottom to top" + + +class Duplex(CoerciveEnum): + "The paper handling option that shall be used when printing the file from the print dialog." + + Simplex = Name("Simplex") + "Print single-sided" + + DuplexFlipShortEdge = Name("DuplexFlipShortEdge") + "Duplex and flip on the short edge of the sheet" + + DuplexFlipLongEdge = Name("DuplexFlipLongEdge") + "Duplex and flip on the long edge of the sheet" + + +class PageBoundaries(CoerciveEnum): + ArtBox = Name("ArtBox") + BleedBox = Name("BleedBox") + CropBox = Name("CropBox") + MediaBox = Name("MediaBox") + TrimBox = Name("TrimBox") diff --git a/fpdf/fpdf.py b/fpdf/fpdf.py index 17bfba450..dbe857e66 100644 --- a/fpdf/fpdf.py +++ b/fpdf/fpdf.py @@ -2320,7 +2320,7 @@ def file_attachment_annotation( @check_page def text_annotation( - self, x, y, text, w=1, h=1, name=None, flags=DEFAULT_ANNOT_FLAGS + self, x, y, text, w=1, h=1, name=None, flags=DEFAULT_ANNOT_FLAGS, title="" ): """ Puts a text annotation on a rectangular area of the page. @@ -2333,6 +2333,8 @@ def text_annotation( h (float): optional height of the link rectangle name (fpdf.enums.AnnotationName, str): optional icon that shall be used in displaying the annotation flags (Tuple[fpdf.enums.AnnotationFlag], Tuple[str]): optional list of flags defining annotation properties + title (str): the text label that shall be displayed in the title bar of the annotation’s + pop-up window when open and active. This entry shall identify the user who added the annotation. """ annotation = AnnotationDict( "Text", @@ -2343,6 +2345,7 @@ def text_annotation( contents=text, name=AnnotationName.coerce(name) if name else None, flags=tuple(AnnotationFlag.coerce(flag) for flag in flags), + title=title, ) self.pages[self.page].annots.append(annotation) return annotation diff --git a/fpdf/prefs.py b/fpdf/prefs.py index e156325b6..17edb3abd 100644 --- a/fpdf/prefs.py +++ b/fpdf/prefs.py @@ -1,4 +1,4 @@ -from .enums import PageMode +from .enums import TextDirection, Duplex, PageBoundaries, PageMode from .syntax import build_obj_dict, create_dictionary_string @@ -14,6 +14,14 @@ def __init__( center_window=False, display_doc_title=False, non_full_screen_page_mode=PageMode.USE_NONE, + num_copies=None, + print_page_range=None, + direction=None, + duplex=None, + view_area=None, + view_clip=None, + print_area=None, + print_clip=None, ): self.hide_toolbar = hide_toolbar "A flag specifying whether to hide the conforming reader’s tool bars when the document is active" @@ -34,14 +42,56 @@ def __init__( taken from the Title entry of the document information dictionary. If false, the title bar should instead display the name of the PDF file containing the document. """ - self.non_full_screen_page_mode = PageMode.coerce(non_full_screen_page_mode) + self.non_full_screen_page_mode = non_full_screen_page_mode if self.non_full_screen_page_mode in ( PageMode.FULL_SCREEN, PageMode.USE_ATTACHMENTS, ): raise ValueError( - f"{self.non_full_screen_page_mode} is not a support value for NonFullScreenPageMode" + f"{self.non_full_screen_page_mode} is not a supported value for NonFullScreenPageMode" ) + self.num_copies = num_copies + """ + The number of copies that shall be printed when the print dialog is opened for this file. + Values outside this range shall be ignored. Default value: as defined by the conforming reader, but typically 1 + """ + self.print_page_range = print_page_range + """ + The page numbers used to initialize the print dialog box when the file is printed. + The array shall contain an even number of integers to be interpreted in pairs, + with each pair specifying the first and last pages in a sub-range of pages to be printed. + The first page of the PDF file shall be denoted by 1. + """ + self.direction = direction + """ + The predominant reading order for text. + _cf. `fpdf.enums.TextDirection` + """ + self.duplex = duplex + """ + The paper handling option that shall be used when printing the file from the print dialog. + _cf. `fpdf.enums.Duplex` + """ + self.view_area = view_area + """ + The name of the page boundary representing the area of a page that shall be displayed when viewing the document on the screen. + Default value: CropBox. + """ + self.view_clip = view_clip + """ + The name of the page boundary to which the contents of a page shall be clipped when viewing the document on the screen. + Default value: CropBox. + """ + self.print_area = print_area + """ + The name of the page boundary representing the area of a page that shall be rendered when printing the document. + Default value: CropBox. + """ + self.print_clip = print_clip + """ + The name of the page boundary to which the contents of a page shall be clipped when printing the document. + Default value: CropBox. + """ @property def non_full_screen_page_mode(self): @@ -50,7 +100,77 @@ def non_full_screen_page_mode(self): @non_full_screen_page_mode.setter def non_full_screen_page_mode(self, page_mode): - self._non_full_screen_page_mode = PageMode.coerce(page_mode) + self._non_full_screen_page_mode = ( + None if page_mode is None else PageMode.coerce(page_mode) + ) + + @property + def direction(self): + "(`fpdf.enums.TextDirection`) The predominant reading order for text" + return self._direction + + @direction.setter + def direction(self, direction): + self._direction = None if direction is None else TextDirection.coerce(direction) + + @property + def duplex(self): + "(`fpdf.enums.Duplexe`) The paper handling option that shall be used when printing the file from the print dialog" + return self._duplex + + @duplex.setter + def duplex(self, duplex): + self._duplex = None if duplex is None else Duplex.coerce(duplex) + + @property + def view_area(self): + """ + (`fpdf.enums.PageBoundaries`) The name of the page boundary representing the area of a page that shall be displayed + when viewing the document on the screen + """ + return self._view_area + + @view_area.setter + def view_area(self, view_area): + self._view_area = ( + None if view_area is None else PageBoundaries.coerce(view_area) + ) + + @property + def view_clip(self): + """ + (`fpdf.enums.PageBoundaries`) The name of the page boundary to which the contents of a page shall be clipped + when viewing the document on the screen. + """ + return self._view_clip + + @view_clip.setter + def view_clip(self, view_clip): + self._view_clip = ( + None if view_clip is None else PageBoundaries.coerce(view_clip) + ) + + @property + def print_area(self): + "(`fpdf.enums.PageBoundaries`) The name of the page boundary representing the area of a page that shall be rendered when printing the document" + return self._print_area + + @print_area.setter + def print_area(self, print_area): + self._print_area = ( + None if print_area is None else PageBoundaries.coerce(print_area) + ) + + @property + def print_clip(self): + "(`fpdf.enums.PageBoundaries`) The name of the page boundary to which the contents of a page shall be clipped when printing the document" + return self._print_clip + + @print_clip.setter + def print_clip(self, print_clip): + self._print_clip = ( + None if print_clip is None else PageBoundaries.coerce(print_clip) + ) def serialize(self, _security_handler=None, _obj_id=None): obj_dict = build_obj_dict(