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)