From e438d22dbaacf2b4e0df4109707a303ae0987ac3 Mon Sep 17 00:00:00 2001
From: Guillaume Ayoub <guillaume@courtbouillon.org>
Date: Sun, 21 Jul 2024 17:03:54 +0200
Subject: [PATCH] Clean and simplify PDF form generation

---
 weasyprint/pdf/anchors.py | 109 +++++++++++++++-----------------------
 1 file changed, 43 insertions(+), 66 deletions(-)

diff --git a/weasyprint/pdf/anchors.py b/weasyprint/pdf/anchors.py
index ad60d893e..859e7f2bf 100644
--- a/weasyprint/pdf/anchors.py
+++ b/weasyprint/pdf/anchors.py
@@ -130,6 +130,15 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
         font_size = style['font_size'] * 0.75
         field_stream = pydyf.Stream(compress=compress)
         field_stream.set_color_rgb(*style['color'][:3])
+        field = pydyf.Dictionary({
+            'Type': '/Annot',
+            'Subtype': '/Widget',
+            'Rect': pydyf.Array(rectangle),
+            'P': page.reference,
+            'F': 1 << (3 - 1),  # Print flag
+            'T': pydyf.String(input_name),
+        })
+
         if input_type in ('radio', 'checkbox'):
             if input_type == 'radio':
                 if input_name not in radio_groups[form]:
@@ -149,7 +158,7 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
             else:
                 character = '4'  # Check character in Dingbats
 
-            # Create stream when input is checked
+            # Create stream when input is checked.
             width = rectangle[2] - rectangle[0]
             height = rectangle[1] - rectangle[3]
             checked_stream = pydyf.Stream(extra={
@@ -162,7 +171,7 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
             checked_stream.begin_text()
             checked_stream.set_color_rgb(*style['color'][:3])
             checked_stream.set_font_size('ZaDb', font_size)
-            # Center (let’s assume that Dingbat’s characters have a 0.75em size)
+            # Center (assuming that Dingbat’s characters have a 0.75em size).
             x = (width - font_size * 0.75) / 2
             y = (height - font_size * 0.75) / 2
             checked_stream.move_text_to(x, y)
@@ -171,22 +180,16 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
             checked_stream.pop_state()
             pdf.add_object(checked_stream)
 
-            checked = 'checked' in element.attrib
             field_stream.set_font_size('ZaDb', font_size)
+
+            checked = 'checked' in element.attrib
             key = len(group['Kids']) if input_type == 'radio' else 'on'
-            field = pydyf.Dictionary({
-                'Type': '/Annot',
-                'Subtype': '/Widget',
-                'Rect': pydyf.Array(rectangle),
-                'FT': '/Btn',
-                'F': 1 << (3 - 1),  # Print flag
-                'P': page.reference,
-                'AS': f'/{key}' if checked else '/Off',
-                'AP': pydyf.Dictionary({'N': pydyf.Dictionary({
-                    key: checked_stream.reference})}),
-                'MK': pydyf.Dictionary({'CA': pydyf.String(character)}),
-                'DA': pydyf.String(b' '.join(field_stream.stream)),
-            })
+            appearance = pydyf.Dictionary({key: checked_stream.reference})
+            field['FT'] = '/Btn'
+            field['DA'] = pydyf.String(b' '.join(field_stream.stream))
+            field['AS'] = f'/{key}' if checked else '/Off'
+            field['AP'] = pydyf.Dictionary({'N': appearance})
+            field['MK'] = pydyf.Dictionary({'CA': pydyf.String(character)})
             pdf.add_object(field)
             if input_type == 'radio':
                 field['Parent'] = group.reference
@@ -197,8 +200,8 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
             else:
                 field['T'] = pydyf.String(input_name)
                 field['V'] = field['AS']
+
         elif element.tag == 'select':
-            # Select fields
             font_description = get_font_description(style)
             font = pango.pango_font_map_load_font(
                 font_map, context, font_description)
@@ -215,17 +218,9 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
                 if 'selected' in option.attrib:
                     selected_values.append(value)
 
-            field = pydyf.Dictionary({
-                'DA': pydyf.String(b' '.join(field_stream.stream)),
-                'F': 1 << (3 - 1),  # Print flag
-                'FT': '/Ch',
-                'Opt': pydyf.Array(options),
-                'P': page.reference,
-                'Rect': pydyf.Array(rectangle),
-                'Subtype': '/Widget',
-                'T': pydyf.String(input_name),
-                'Type': '/Annot',
-            })
+            field['FT'] = '/Ch'
+            field['DA'] = pydyf.String(b' '.join(field_stream.stream))
+            field['Opt'] = pydyf.Array(options)
             if 'multiple' in element.attrib:
                 field['Ff'] = 1 << (22 - 1)
                 field['V'] = pydyf.Array(selected_values)
@@ -235,33 +230,27 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
                     selected_values[-1] if selected_values
                     else pydyf.String(''))
             pdf.add_object(field)
+
         elif input_type == 'submit' or element.tag == 'button':
             flags = 1 << (3 - 1)  # HTML form format
             if form.attrib.get('method', '').lower() != 'post':
                 flags += 1 << (4 - 1)  # GET method
-            field = pydyf.Dictionary({
-                'Type': '/Annot',
-                'Subtype': '/Widget',
-                'Rect': pydyf.Array(rectangle),
-                'FT': '/Btn',
-                'T': pydyf.String(input_name),
-                'F': 1 << (3 - 1),  # Print flag
-                'Ff': 1 << (17 - 1),  # Push-button
-                'P': page.reference,
-                'DA': pydyf.String(b' '.join(field_stream.stream)),
-                'V': pydyf.String(form.attrib.get('value', '')),
-                'A': pydyf.Dictionary({
-                    'Type': '/Action',
-                    'S': '/SubmitForm',
-                    'F': pydyf.String(form.attrib.get('action')),
-                    'Fields': pydyf.Array((
-                        field.reference for field in forms[form].values())),
-                    'Flags': flags,
-                }),
+            fields = pydyf.Array((field.reference for field in forms[form].values()))
+            field['FT'] = '/Btn'
+            field['DA'] = pydyf.String(b' '.join(field_stream.stream))
+            field['V'] = pydyf.String(form.attrib.get('value', ''))
+            field['Ff'] = 1 << (17 - 1)  # Push-button
+            field['A'] = pydyf.Dictionary({
+                'Type': '/Action',
+                'S': '/SubmitForm',
+                'F': pydyf.String(form.attrib.get('action')),
+                'Fields': fields,
+                'Flags': flags,
             })
             pdf.add_object(field)
+
         else:
-            # Text, password, textarea, files, and unknown
+            # Text, password, textarea, files, and other unknown fields.
             font_description = get_font_description(style)
             font = pango.pango_font_map_load_font(
                 font_map, context, font_description)
@@ -269,30 +258,18 @@ def add_forms(forms, matrix, pdf, page, resources, stream, font_map,
             font.used_in_forms = True
 
             field_stream.set_font_size(font.hash, font_size)
-            value = (
-                element.text if element.tag == 'textarea'
-                else element.attrib.get('value', ''))
-            field = pydyf.Dictionary({
-                'Type': '/Annot',
-                'Subtype': '/Widget',
-                'Rect': pydyf.Array(rectangle),
-                'FT': '/Tx',
-                'F': 1 << (3 - 1),  # Print flag
-                'P': page.reference,
-                'T': pydyf.String(input_name),
-                'V': pydyf.String(value or ''),
-                'DA': pydyf.String(b' '.join(field_stream.stream)),
-            })
+            field['FT'] = '/Tx'
+            field['DA'] = pydyf.String(b' '.join(field_stream.stream))
+            field['V'] = pydyf.String(element.attrib.get('value', ''))
             if element.tag == 'textarea':
                 field['Ff'] = 1 << (13 - 1)
+                field['V'] = pydyf.String(element.text or '')
             elif input_type == 'password':
                 field['Ff'] = 1 << (14 - 1)
             elif input_type == 'file':
                 field['Ff'] = 1 << (21 - 1)
-
-            maxlength = element.get('maxlength')
-            if maxlength and maxlength.isdigit():
-                field['MaxLen'] = element.get('maxlength')
+            if (max_length := element.get('maxlength', '')).isdigit():
+                field['MaxLen'] = max_length
             pdf.add_object(field)
 
         page['Annots'].append(field.reference)