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

Added support for Select fields as combo boxes #2000

Merged
merged 3 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
13 changes: 11 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,12 +487,21 @@ def test_partial_pdf_custom_metadata():
(b'<input value="">', [b'/Tx', b'/V ()']),
(b'<input type="checkbox">', [b'/Btn']),
(b'<textarea></textarea>', [b'/Tx', b'/V ()']),
(b'<select><option value="a">A</option></select>', [b'/Ch', b'/Opt']),
(b'<select>'
b'<option value="a">A</option>'
b'<option value="b" selected>B</option>'
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)]']),
))
def test_pdf_inputs(html, fields):
stdout = _run('--pdf-forms --uncompressed-pdf - -', html)
assert b'AcroForm' in stdout
for field in fields:
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
16 changes: 11 additions & 5 deletions weasyprint/css/html5_ua.css
Original file line number Diff line number Diff line change
Expand Up @@ -363,23 +363,29 @@ input[value=""]::before {
select {
background: lightgrey;
border-radius: 0.25em 0.25em;
padding-right: 1.5em;
position: relative;
white-space: normal;
}
select::before {
select[multiple] {
height: 3.6em;
}
select:not([multiple])::before {
content: "˅";
position: absolute;
right: 0;
text-align: center;
width: 1.5em;
}
option {
display: none;
select option {
padding-right: 1.5em;
white-space: nowrap;
}
select:not([multiple]) option {
display: none;
}
select[multiple] option,
select:not(:has(option[selected])) option:first-of-type,
option[selected]:not(option[selected] ~ option[selected]) {
select option[selected]:not(option[selected] ~ option[selected]) {
display: block;
overflow: hidden;
}
Expand Down
53 changes: 42 additions & 11 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,6 +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':
# 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)
options = []
selected_values = []
for option in element:
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['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 @@ -177,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