Skip to content

Commit

Permalink
Merge pull request #464 from chinapandaman/PPF-463
Browse files Browse the repository at this point in the history
PPF-463: detect different button styles
  • Loading branch information
chinapandaman authored Jan 25, 2024
2 parents 5391649 + ed7aae1 commit ef8eb3e
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 13 deletions.
12 changes: 10 additions & 2 deletions PyPDFForm/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
TEXT_FIELD_ALIGNMENT_IDENTIFIER = "/Q"
CHOICE_FIELD_IDENTIFIER = "/Ch"
CHOICES_IDENTIFIER = "/Opt"
BUTTON_IDENTIFIER = "/MK"
BUTTON_STYLE_IDENTIFIER = "/CA"

# Field flag bits
MULTILINE = 1 << 12
Expand All @@ -27,6 +29,12 @@
DEFAULT_FONT_COLOR = (0, 0, 0)
PREVIEW_FONT_COLOR = (1, 0, 0)

CHECKBOX_TO_DRAW = "\u2713"
RADIO_TO_DRAW = "\u25CF"
NEW_LINE_SYMBOL = "\n"

DEFAULT_CHECKBOX_STYLE = "\u2713"
DEFAULT_RADIO_STYLE = "\u25CF"
BUTTON_STYLES = {
"4": "\u2713",
"5": "\u00D7",
"l": "\u25CF",
}
7 changes: 6 additions & 1 deletion PyPDFForm/core/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
TEXT_FIELD_ALIGNMENT_IDENTIFIER,
TEXT_FIELD_APPEARANCE_IDENTIFIER,
TEXT_FIELD_IDENTIFIER, WIDGET_SUBTYPE_KEY,
WIDGET_TYPE_KEY)
WIDGET_TYPE_KEY, BUTTON_STYLE_IDENTIFIER, BUTTON_IDENTIFIER)

WIDGET_TYPE_PATTERNS = [
(
Expand Down Expand Up @@ -71,3 +71,8 @@
{TEXT_FIELD_APPEARANCE_IDENTIFIER: True},
{PARENT_KEY: {TEXT_FIELD_APPEARANCE_IDENTIFIER: True}},
]

BUTTON_STYLE_PATTERNS = [
{BUTTON_IDENTIFIER: {BUTTON_STYLE_IDENTIFIER: True}},
{PARENT_KEY: {BUTTON_IDENTIFIER: {BUTTON_STYLE_IDENTIFIER: True}}}
]
13 changes: 12 additions & 1 deletion PyPDFForm/core/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
NEW_LINE_SYMBOL, TEXT_FIELD_MAX_LENGTH_KEY)
from .patterns import (DROPDOWN_CHOICE_PATTERNS, TEXT_FIELD_FLAG_PATTERNS,
WIDGET_ALIGNMENT_PATTERNS, WIDGET_KEY_PATTERNS,
WIDGET_TYPE_PATTERNS)
WIDGET_TYPE_PATTERNS, BUTTON_STYLE_PATTERNS)
from .utils import find_pattern_match, stream_to_io, traverse_pattern


Expand Down Expand Up @@ -132,6 +132,17 @@ def get_dropdown_choices(widget: dict) -> Union[Tuple[str], None]:
return result


def get_button_style(widget: dict) -> Union[str, None]:
"""Returns the button style of a checkbox or radiobutton."""

for pattern in BUTTON_STYLE_PATTERNS:
style = traverse_pattern(pattern, widget)
if style is not None:
return str(style)

return None


def get_char_rect_width(widget: dict, widget_middleware: Text) -> float:
"""Returns rectangular width of each character for combed text fields."""

Expand Down
12 changes: 5 additions & 7 deletions PyPDFForm/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from ..middleware.constants import WIDGET_TYPES
from ..middleware.radio import Radio
from ..middleware.text import Text
from .constants import (CHECKBOX_TO_DRAW, DEFAULT_FONT, DEFAULT_FONT_COLOR,
DEFAULT_FONT_SIZE, PREVIEW_FONT_COLOR, RADIO_TO_DRAW)
from .constants import (DEFAULT_CHECKBOX_STYLE, BUTTON_STYLES, DEFAULT_FONT, DEFAULT_FONT_COLOR,
DEFAULT_FONT_SIZE, PREVIEW_FONT_COLOR, DEFAULT_RADIO_STYLE)


def stream_to_io(stream: bytes) -> BinaryIO:
Expand All @@ -37,11 +37,9 @@ def checkbox_radio_to_draw(
new_widget.font = DEFAULT_FONT
new_widget.font_size = font_size
new_widget.font_color = DEFAULT_FONT_COLOR

if isinstance(widget, Checkbox):
new_widget.value = CHECKBOX_TO_DRAW
elif isinstance(widget, Radio):
new_widget.value = RADIO_TO_DRAW
new_widget.value = BUTTON_STYLES.get(widget.button_style) or (
DEFAULT_CHECKBOX_STYLE if isinstance(widget, Checkbox) else DEFAULT_RADIO_STYLE
)

return new_widget

Expand Down
2 changes: 2 additions & 0 deletions PyPDFForm/middleware/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def __init__(

super().__init__(name, value)

self.button_style = None

@property
def schema_definition(self) -> dict:
"""Json schema definition of the checkbox."""
Expand Down
1 change: 1 addition & 0 deletions PyPDFForm/middleware/radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def __init__(

super().__init__(name, value)

self.button_style = None
self.number_of_options = 0

@property
Expand Down
6 changes: 5 additions & 1 deletion PyPDFForm/middleware/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from ..core.template import (construct_widget, get_character_x_paddings,
get_dropdown_choices, get_text_field_max_length,
get_widget_key, get_widgets_by_page,
is_text_field_comb)
is_text_field_comb, get_button_style)
from .constants import WIDGET_TYPES
from .checkbox import Checkbox
from .dropdown import Dropdown
from .radio import Radio
from .text import Text
Expand Down Expand Up @@ -46,6 +47,9 @@ def build_widgets(pdf_stream: bytes) -> Dict[str, WIDGET_TYPES]:
if _widget.max_length is not None and is_text_field_comb(widget):
_widget.comb = True

if isinstance(_widget, (Checkbox, Radio)):
_widget.button_style = get_button_style(widget)

if isinstance(_widget, Dropdown):
_widget.choices = get_dropdown_choices(widget)

Expand Down
2 changes: 1 addition & 1 deletion PyPDFForm/widgets/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
class CheckBoxWidget(Widget):
"""Checkbox widget to create."""

USER_PARAMS = [("size", "size")]
USER_PARAMS = [("size", "size"), ("button_style", "buttonStyle")]
ACRO_FORM_FUNC = "checkbox"
Binary file added pdf_samples/widget/create_checkbox_check.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_checkbox_circle.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_checkbox_cross.pdf
Binary file not shown.
72 changes: 72 additions & 0 deletions tests/test_create_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,78 @@ def test_create_checkbox_complex(template_stream, pdf_samples, request):
assert obj.stream == expected


def test_create_checkbox_check(template_stream, pdf_samples, request):
expected_path = os.path.join(
pdf_samples, "widget", "create_checkbox_check.pdf"
)
with open(expected_path, "rb+") as f:
obj = PdfWrapper(template_stream).create_widget(
"checkbox",
"foo",
1,
100,
100,
button_style="check",
)
obj.fill(obj.sample_data)

request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()

expected = f.read()

assert len(obj.stream) == len(expected)
assert obj.stream == expected


def test_create_checkbox_circle(template_stream, pdf_samples, request):
expected_path = os.path.join(
pdf_samples, "widget", "create_checkbox_circle.pdf"
)
with open(expected_path, "rb+") as f:
obj = PdfWrapper(template_stream).create_widget(
"checkbox",
"foo",
1,
100,
100,
button_style="circle",
)
obj.fill(obj.sample_data)

request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()

expected = f.read()

assert len(obj.stream) == len(expected)
assert obj.stream == expected


def test_create_checkbox_cross(template_stream, pdf_samples, request):
expected_path = os.path.join(
pdf_samples, "widget", "create_checkbox_cross.pdf"
)
with open(expected_path, "rb+") as f:
obj = PdfWrapper(template_stream).create_widget(
"checkbox",
"foo",
1,
100,
100,
button_style="cross",
)
obj.fill(obj.sample_data)

request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()

expected = f.read()

assert len(obj.stream) == len(expected)
assert obj.stream == expected


def test_create_checkbox_complex_filled(template_stream, pdf_samples, request):
expected_path = os.path.join(
pdf_samples, "widget", "create_checkbox_complex_filled.pdf"
Expand Down

0 comments on commit ef8eb3e

Please sign in to comment.