Skip to content

Commit

Permalink
Merge pull request #450 from chinapandaman/PPF-449
Browse files Browse the repository at this point in the history
PPF-449: implement create widget method
  • Loading branch information
chinapandaman authored Jan 19, 2024
2 parents 110d4f8 + 0d863ea commit 1d9d408
Show file tree
Hide file tree
Showing 16 changed files with 357 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[MESSAGES CONTROL]
disable=C0103, R0913, R0902, R0914, C0209
disable=C0103, R0913, R0902, R0903, R0914, C0209
7 changes: 7 additions & 0 deletions PyPDFForm/core/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from re import findall
from typing import Dict, Tuple, Union

from reportlab.pdfbase.acroform import AcroForm
from reportlab.pdfbase.pdfmetrics import registerFont, standardFonts
from reportlab.pdfbase.ttfonts import TTFError, TTFont

Expand Down Expand Up @@ -40,6 +41,7 @@ def register_font(font_name: str, ttf_stream: bytes) -> bool:
def auto_detect_font(widget: dict) -> str:
"""Returns the font of the text field if it is one of the standard fonts."""

# pylint: disable=R0912
result = DEFAULT_FONT

text_appearance = None
Expand All @@ -58,6 +60,11 @@ def auto_detect_font(widget: dict) -> str:
if each.startswith("/"):
text_segments = findall("[A-Z][^A-Z]*", each.replace("/", ""))

if len(text_segments) == 1:
for k, v in AcroForm.formFontNames.items():
if v == text_segments[0]:
return k

for font in standardFonts:
font_segments = findall("[A-Z][^A-Z]*", font.replace("-", ""))
if len(font_segments) != len(text_segments):
Expand Down
Empty file added PyPDFForm/widgets/__init__.py
Empty file.
78 changes: 78 additions & 0 deletions PyPDFForm/widgets/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""Contains base class for all widgets to create."""

from io import BytesIO
from typing import List

from pypdf import PdfReader
from reportlab.lib.colors import Color
from reportlab.pdfgen.canvas import Canvas

from ..core.utils import stream_to_io


class Widget:
"""Base class for all widgets to create."""

USER_PARAMS = []
COLOR_PARAMS = []
NONE_DEFAULTS = []
ACRO_FORM_FUNC = ""

def __init__(
self,
name: str,
page_number: int,
x: float,
y: float,
**kwargs,
) -> None:
"""Sets acro form parameters."""

self.page_number = page_number
self.acro_form_params = {
"name": name,
"x": x,
"y": y,
}

for each in self.USER_PARAMS:
user_input, param = each
if user_input in kwargs:
value = kwargs[user_input]
if user_input in self.COLOR_PARAMS:
value = Color(
value[0],
value[1],
value[2],
)
self.acro_form_params[param] = value
elif user_input in self.NONE_DEFAULTS:
self.acro_form_params[param] = None

def watermarks(self, stream: bytes) -> List[bytes]:
"""Returns a list of watermarks after creating the widget."""

pdf = PdfReader(stream_to_io(stream))
page_count = len(pdf.pages)
watermark = BytesIO()

canvas = Canvas(
watermark,
pagesize=(
float(pdf.pages[self.page_number - 1].mediabox[2]),
float(pdf.pages[self.page_number - 1].mediabox[3]),
),
)

getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**self.acro_form_params)

canvas.showPage()
canvas.save()
watermark.seek(0)

result = []
for i in range(page_count):
result.append(watermark.read() if i == self.page_number - 1 else b"")

return result
11 changes: 11 additions & 0 deletions PyPDFForm/widgets/checkbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""Contains checkbox widget to create."""

from .base import Widget


class CheckBoxWidget(Widget):
"""Checkbox widget to create."""

USER_PARAMS = [("size", "size")]
ACRO_FORM_FUNC = "checkbox"
15 changes: 15 additions & 0 deletions PyPDFForm/widgets/text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
"""Contains text field widget to create."""

from .base import Widget


class TextWidget(Widget):
"""Text field widget to create."""

USER_PARAMS = [("width", "width"), ("height", "height"),
("max_length", "maxlen"), ("font", "fontName"),
("font_size", "fontSize"), ("font_color", "textColor")]
COLOR_PARAMS = ["font_color"]
NONE_DEFAULTS = ["max_length"]
ACRO_FORM_FUNC = "textfield"
48 changes: 45 additions & 3 deletions PyPDFForm/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from .middleware.template import (build_widgets, dropdown_to_text,
set_character_x_paddings)
from .middleware.text import Text
from .widgets.text import TextWidget
from .widgets.checkbox import CheckBoxWidget


class PdfWrapper:
Expand All @@ -37,11 +39,15 @@ def __init__(
self.stream = fp_or_f_obj_or_stream_to_stream(template)
self.widgets = build_widgets(self.stream) if self.stream else {}

self.global_font = kwargs.get("global_font")
self.global_font_size = kwargs.get("global_font_size")
self.global_font_color = kwargs.get("global_font_color")

for each in self.widgets.values():
if isinstance(each, Text):
each.font = kwargs.get("global_font")
each.font_size = kwargs.get("global_font_size")
each.font_color = kwargs.get("global_font_color")
each.font = self.global_font
each.font_size = self.global_font_size
each.font_color = self.global_font_color

def read(self) -> bytes:
"""Reads the file stream of a PDF form."""
Expand Down Expand Up @@ -146,6 +152,42 @@ def fill(

return self

def create_widget(
self,
widget_type: str,
name: str,
page_number: int,
x: float,
y: float,
**kwargs,
) -> PdfWrapper:
"""Creates a new widget on a PDF form."""

_class = None
if widget_type == "text":
_class = TextWidget
if widget_type == "checkbox":
_class = CheckBoxWidget
if _class is None:
return self

watermarks = _class(
name=name,
page_number=page_number,
x=x,
y=y,
**kwargs
).watermarks(self.read())

self.stream = merge_watermarks_with_pdf(self.read(), watermarks)
self.widgets[name] = build_widgets(self.read())[name]
if widget_type == "text":
self.widgets[name].font = self.global_font
self.widgets[name].font_size = self.global_font_size
self.widgets[name].font_color = self.global_font_color

return self

def draw_text(
self,
text: str,
Expand Down
Binary file added pdf_samples/widget/create_checkbox_complex.pdf
Binary file not shown.
Binary file not shown.
Binary file added pdf_samples/widget/create_checkbox_default.pdf
Binary file not shown.
Binary file not shown.
Binary file added pdf_samples/widget/create_text_complex.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_text_complex_filled.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_text_default.pdf
Binary file not shown.
Binary file added pdf_samples/widget/create_text_default_filled.pdf
Binary file not shown.
Loading

0 comments on commit 1d9d408

Please sign in to comment.