Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add capability to set font and size in fields #2636

Merged
merged 15 commits into from
May 20, 2024
53 changes: 41 additions & 12 deletions pypdf/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,11 @@ def append_pages_from_reader(
after_page_append(writer_page)

def _update_field_annotation(
self, field: DictionaryObject, anno: DictionaryObject
self,
field: DictionaryObject,
anno: DictionaryObject,
font_name: str = "",
font_size: float = -1,
) -> None:
# Calculate rectangle dimensions
_rct = cast(RectangleObject, anno[AA.Rect])
Expand All @@ -799,12 +803,22 @@ def _update_field_annotation(
da = da.get_object()
font_properties = da.replace("\n", " ").replace("\r", " ").split(" ")
font_properties = [x for x in font_properties if x != ""]
font_name = font_properties[font_properties.index("Tf") - 2]
font_height = float(font_properties[font_properties.index("Tf") - 1])
if font_name:
font_properties[font_properties.index("Tf") - 2] = font_name
else:
font_name = font_properties[font_properties.index("Tf") - 2]
font_height = (
font_size
if font_size >= 0
else float(font_properties[font_properties.index("Tf") - 1])
)
if font_height == 0:
font_height = rct.height - 2
font_properties[font_properties.index("Tf") - 1] = str(font_height)
da = " ".join(font_properties)
if field.get("/Ff", 0) & InteractiveFormDictEntries.Ff_Multiline:
font_height = 12
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
else:
font_height = rct.height - 2
font_properties[font_properties.index("Tf") - 1] = str(font_height)
da = " ".join(font_properties)
y_offset = rct.height - 1 - font_height

# Retrieve font information from local DR ...
Expand Down Expand Up @@ -944,11 +958,17 @@ def update_page_form_field_values(
annotations and field data will be updated.
`List[Pageobject]` - provides list of pages to be processed.
`None` - all pages.
fields: a Python dictionary of field names (/T) and text
values (/V).
flags: An integer (0 to 7). The first bit sets ReadOnly, the
second bit sets Required, the third bit sets NoExport. See
PDF Reference Table 8.70 for details.
fields: a Python dictionary of:

* field names (/T) as keys and text values (/V) as value
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
* field names (/T) as keys and list of text values (/V) for multiple choice list
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
* field names (/T) as keys and tuple of :
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
* text values (/V)
* font id (e.g. /F1, the font id must exist)
* font size (0 for autosize)
flags: An integer. You can build it with InteractiveFormDictEntries:
ex: InteractiveFormDictEntries.Ff_ReadOnly ^ InteractiveFormDictEntries.Ff_Multiline
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved

auto_regenerate: set/unset the need_appearances flag ;
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
the flag is unchanged if auto_regenerate is None.
"""
Expand Down Expand Up @@ -997,6 +1017,10 @@ def update_page_form_field_values(
if isinstance(value, list):
lst = ArrayObject(TextStringObject(v) for v in value)
writer_parent_annot[NameObject(FA.V)] = lst
elif isinstance(value, tuple):
writer_annot[NameObject(FA.V)] = TextStringObject(
value[0],
)
else:
writer_parent_annot[NameObject(FA.V)] = TextStringObject(value)
if writer_parent_annot.get(FA.FT) in ("/Btn"):
Expand All @@ -1011,7 +1035,12 @@ def update_page_form_field_values(
or writer_parent_annot.get(FA.FT) == "/Ch"
):
# textbox
self._update_field_annotation(writer_parent_annot, writer_annot)
if isinstance(value, tuple):
self._update_field_annotation(
writer_parent_annot, writer_annot, value[1], value[2]
)
else:
self._update_field_annotation(writer_parent_annot, writer_annot)
elif (
writer_annot.get(FA.FT) == "/Sig"
): # deprecated # not implemented yet
Expand Down
25 changes: 25 additions & 0 deletions pypdf/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,31 @@ class InteractiveFormDictEntries:
Q = "/Q"
XFA = "/XFA"

# Common Field Flags
Ff_ReadOnly = 1
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
Ff_Required = 2
Ff_NoExport = 4
# Text Field Flags
Ff_Multiline = 1 << (13 - 1)
Ff_Password = 1 << (14 - 1)
Ff_FileSelect = 1 << (21 - 1)
Ff_DoNotSpellCheck = 1 << (23 - 1)
Ff_DoNotScroll = 1 << (24 - 1)
Ff_Comb = 1 << (25 - 1)
Ff_RichText = 1 << (26 - 1)
# Button Field Flags
Ff_NoToggleToOff = 1 << (15 - 1)
Ff_Radio = 1 << (16 - 1)
Ff_Pushbutton = 1 << (17 - 1)
Ff_RadiosInUnison = 1 << (26 - 1)
# Choice Field FlagsZ
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
Ff_Combo = 1 << (18 - 1)
Ff_Edit = 1 << (19 - 1)
Ff_Sort = 1 << (20 - 1)
Ff_MultiSelect = 1 << (22 - 1)
# Ff_DoNotSpellCheck
pubpub-zz marked this conversation as resolved.
Show resolved Hide resolved
Ff_CommitOnSelChange = 1 << (27 - 1)


class FieldDictionaryAttributes:
"""Table 8.69 Entries common to all field dictionaries (PDF 1.7 reference)."""
Expand Down
23 changes: 23 additions & 0 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2230,3 +2230,26 @@ def test_i_in_choice_fields():
writer.pages[0], {"State": "NY"}, auto_regenerate=False
)
assert "/I" not in writer.get_fields()["State"].indirect_reference.get_object()


def test_selfont():
writer = PdfWriter(clone_from=RESOURCE_ROOT / "FormTestFromOo.pdf")
writer.update_page_form_field_values(
writer.pages[0],
{"Text1": ("Text_1", "", 5), "Text2": ("Text_2", "/F3", 0)},
auto_regenerate=False,
)
assert (
b"/F3 5 Tf"
in writer.pages[0]["/Annots"][1].get_object()["/AP"]["/N"].get_data()
)
assert (
b"Text_1" in writer.pages[0]["/Annots"][1].get_object()["/AP"]["/N"].get_data()
)
assert (
b"/F3 12 Tf"
in writer.pages[0]["/Annots"][2].get_object()["/AP"]["/N"].get_data()
)
assert (
b"Text_2" in writer.pages[0]["/Annots"][2].get_object()["/AP"]["/N"].get_data()
)
Loading