Skip to content

Commit

Permalink
Added support for Select fields as combo boxes
Browse files Browse the repository at this point in the history
I've decoded two PDFs, one with the combo box and another blank and extracted the binary info necessary to render it and applied to the `anchors.add_inputs` function.
  • Loading branch information
VagnerNico committed Nov 4, 2023
1 parent 4f322a2 commit cfaaf69
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 8 deletions.
4 changes: 2 additions & 2 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ The ``resize``, ``cursor``, ``caret-*`` and ``nav-*`` properties are **not**
supported.

The ``appearance`` property is supported. When set to ``auto``, it displays
form fields as PDF form fields (supported for text inputs, check boxes and
text areas only).
form fields as PDF form fields (supported for text inputs, check boxes, text
areas, and select only).

The ``accent-color`` property is **not** supported.
20 changes: 14 additions & 6 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,15 +482,23 @@ def test_partial_pdf_custom_metadata():
assert b'value' in stdout


@pytest.mark.parametrize('html, field', (
(b'<input>', b'/Tx'),
(b'<input type="checkbox">', b'/Btn'),
(b'<textarea></textarea>', b'/Tx'),
@pytest.mark.parametrize('html, fields', (
(b'<input>', [b'/Tx']),
(b'<input type="checkbox">', [b'/Btn']),
(b'<textarea></textarea>', [b'/Tx']),
(b'<select><option value="a">A</option></select>', [b'/Ch', b'/Opt']),
# The selected values will be (b) and (c) in the PDF.
(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)]']),
))
def test_pdf_inputs(html, field):
def test_pdf_inputs(html, fields):
stdout = _run('--pdf-forms --uncompressed-pdf - -', html)
assert b'AcroForm' in stdout
assert field in stdout
assert all(field in stdout for field in fields)
stdout = _run('--uncompressed-pdf - -', html)
assert b'AcroForm' not in stdout

Expand Down
12 changes: 12 additions & 0 deletions weasyprint/layout/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,18 @@ def block_container_layout(context, box, bottom_space, skip_stack,
if next_page['page'] is None:
next_page['page'] = new_box.page_values()[1]

if (
box.element and
box.element.tag == 'select' and
box.element.attrib.get('multiple') is not None and
not box.element.attrib.get('height') and
not box.element.attrib.get('max-height')
):
# Overriding the height when the select has the multiple attribute
# and no height attribute
options_number = len([child for child in box.element])
new_box.height = min(box.height * 4, box.height * options_number)

return (
new_box, resume_at, next_page, adjoining_margins, collapsing_through,
max_lines)
Expand Down
69 changes: 69 additions & 0 deletions weasyprint/pdf/anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,75 @@ 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
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',
'Ff': 2097152,
'I': pydyf.Array([3]),
'M': "(D:20231104123906+00'00')",
'MK': pydyf.Dictionary({
'BC': pydyf.Array([
0.627451, 0.627451, 0.643137
]),
'R': 0,
}),
'Opt': pydyf.Array(options),
'P': page.reference,
'Rect': pydyf.Array(rectangle),
'Subtype': '/Widget',
'T': pydyf.String(input_name),
'Type': '/Annot',
'V': pydyf.Array(selected_values)
if len(selected_values) > 1
else pydyf.String(element.attrib.get('value', '')),
})
else:
field = pydyf.Dictionary({
'DA': pydyf.String(b' '.join(field_stream.stream)),
'F': 2 ** (3 - 1), # Print flag
'FT': '/Ch',
'Ff': 131072,
'I': pydyf.Array([0]),
'M': "(D:20231103110832+00'00')",
'MK': pydyf.Dictionary({
'BC': pydyf.Array([
0.627451, 0.627451, 0.643137
]),
'R': 0,
}),
'Opt': pydyf.Array(options),
'P': page.reference,
'Rect': pydyf.Array(rectangle),
'Subtype': '/Widget',
'T': pydyf.String(input_name),
'Type': '/Annot',
'V': pydyf.String(element.attrib.get('value', '')),
})
else:
# Text, password, textarea, files, and unknown
font_description = get_font_description(style)
Expand Down

0 comments on commit cfaaf69

Please sign in to comment.