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(