diff --git a/docs/api_reference.rst b/docs/api_reference.rst index 1e11c758e..377c21566 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -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. diff --git a/tests/test_api.py b/tests/test_api.py index 588731ac8..16def4b69 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -487,12 +487,21 @@ def test_partial_pdf_custom_metadata(): (b'', [b'/Tx', b'/V ()']), (b'', [b'/Btn']), (b'', [b'/Tx', b'/V ()']), + (b'', [b'/Ch', b'/Opt']), + (b'', [b'/Ch', b'/Opt', b'/V (b)']), + (b'', [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 diff --git a/weasyprint/css/html5_ua.css b/weasyprint/css/html5_ua.css index 96443ef54..9bc5b0c9e 100644 --- a/weasyprint/css/html5_ua.css +++ b/weasyprint/css/html5_ua.css @@ -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; } diff --git a/weasyprint/pdf/anchors.py b/weasyprint/pdf/anchors.py index 58e03d057..ac489957b 100644 --- a/weasyprint/pdf/anchors.py +++ b/weasyprint/pdf/anchors.py @@ -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', @@ -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) @@ -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)