Skip to content

Commit

Permalink
Clean and simplify code for select fields
Browse files Browse the repository at this point in the history
  • Loading branch information
liZe committed Dec 6, 2023
1 parent 7a0bc53 commit 2a7ced5
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 78 deletions.
7 changes: 2 additions & 5 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,15 +491,12 @@ def test_partial_pdf_custom_metadata():
(b'<select>'
b'<option value="a">A</option>'
b'<option value="b" selected>B</option>'
b'</select>'
, [b'/Ch', b'/Opt', b'/V (b)']),
# The selected values will be (b) and (c) in the PDF.
b'</select>', [b'/Ch', b'/Opt', b'/V (b)']),
(b'<select multiple>'
b'<option value="a">A</option>'
b'<option value="b" selected>B</option>'
b'<option value="c" selected>C</option>'
b'</select>'
, [b'/Ch', b'/Opt', b'[(b) (c)]']),
b'</select>', [b'/Ch', b'/Opt', b'[(b) (c)]']),
))
def test_pdf_inputs(html, fields):
stdout = _run('--pdf-forms --uncompressed-pdf - -', html)
Expand Down
104 changes: 31 additions & 73 deletions weasyprint/pdf/anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def add_inputs(inputs, matrix, pdf, page, resources, stream, font_map,
'Subtype': '/Widget',
'Rect': pydyf.Array(rectangle),
'FT': '/Btn',
'F': 2 ** (3 - 1), # Print flag
'F': 1 << (3 - 1), # Print flag
'P': page.reference,
'T': pydyf.String(input_name),
'V': '/Yes' if checked else '/Off',
Expand All @@ -160,79 +160,43 @@ def add_inputs(inputs, matrix, pdf, page, resources, stream, font_map,
'AS': '/Yes' if checked else '/Off',
'DA': pydyf.String(b' '.join(field_stream.stream)),
})
elif element.tag == "select":
# Text, password, textarea, files, and unknown
elif element.tag == 'select':
# Select fields
font_description = get_font_description(style)
font = pango.pango_font_map_load_font(
font_map, context, font_description)
font = stream.add_font(font)
font.used_in_forms = True

field_stream.set_font_size(font.hash, font_size)
multiple = element.attrib.get("multiple") is not None
options = []
selected_values = []
for option in element:
options.append(
pydyf.Array(
[f'({option.attrib.get("value")})', f'({option.text})']
)
)
if option.attrib.get("selected") is not None:
selected_values.append(
pydyf.String(option.attrib.get("value"))
)

if multiple:
field = pydyf.Dictionary({
'DA': pydyf.String(b' '.join(field_stream.stream)),
'F': 2 ** (3 - 1), # Print flag
'FT': '/Ch',
# To be a multiselect list we need to set the 21st bit to 1
# outputing 2097152:
# https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf # noqa: E501
# This specification can be found on the link above on page
# 445.
'Ff': (1 << 21),
'Opt': pydyf.Array(options),
'P': page.reference,
'Rect': pydyf.Array(rectangle),
'Subtype': '/Widget',
'T': pydyf.String(input_name),
'Type': '/Annot',
# The select value is a list of selected values that come
# from the options with the selected property. If there are
# no selected values, then the value is an empty string.
'V': pydyf.Array(selected_values)
if len(selected_values)
else pydyf.String(''),
})
value = pydyf.String(option.attrib.get('value', ''))
text = pydyf.String(option.text)
options.append(pydyf.Array([value, text]))
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',
})
if 'multiple' in element.attrib:
field['Ff'] = 1 << (22 - 1)
field['V'] = pydyf.Array(selected_values)
else:
field = pydyf.Dictionary({
'DA': pydyf.String(b' '.join(field_stream.stream)),
'F': 2 ** (3 - 1), # Print flag
'FT': '/Ch',
# To be a combo box we need to set the 17th bit to 1
# outputing 131072:
# https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf # noqa: E501
# This specification can be found on the link above on page
# 444.
'Ff': (1 << 17),
'Opt': pydyf.Array(options),
'P': page.reference,
'Rect': pydyf.Array(rectangle),
'Subtype': '/Widget',
'T': pydyf.String(input_name),
'Type': '/Annot',
# Get the last value in the list when there are multiple
# selected values in a single select box (this is the same
# approach used by browsers).
# If there are no selected values, then the value is an
# empty string.
'V': selected_values[-1]
if len(selected_values)
else pydyf.String(''),
})
field['Ff'] = 1 << (18 - 1)
field['V'] = (
selected_values[-1] if selected_values
else pydyf.String(''))
else:
# Text, password, textarea, files, and unknown
font_description = get_font_description(style)
Expand All @@ -250,24 +214,18 @@ def add_inputs(inputs, matrix, pdf, page, resources, stream, font_map,
'Subtype': '/Widget',
'Rect': pydyf.Array(rectangle),
'FT': '/Tx',
'F': 2 ** (3 - 1), # Print flag
'F': 1 << (3 - 1), # Print flag
'P': page.reference,
'T': pydyf.String(input_name),
# Previously if the input had no value or the value was an
# empty string, the V key was filled with a pydyf.String(None)
# object. This caused the PDF input/textarea to be filled with
# the string "None". Now if the input has no value or the
# value is an empty string, the V key is filled with a
# pydyf.String('') object.
'V': pydyf.String(value or ''),
'DA': pydyf.String(b' '.join(field_stream.stream)),
})
if element.tag == 'textarea':
field['Ff'] = 2 ** (13 - 1)
field['Ff'] = 1 << (13 - 1)
elif input_type == 'password':
field['Ff'] = 2 ** (14 - 1)
field['Ff'] = 1 << (14 - 1)
elif input_type == 'file':
field['Ff'] = 2 ** (21 - 1)
field['Ff'] = 1 << (21 - 1)

pdf.add_object(field)
page['Annots'].append(field.reference)
Expand Down

0 comments on commit 2a7ced5

Please sign in to comment.