Skip to content

Commit

Permalink
Merge pull request #411 from chinapandaman/PPF-404
Browse files Browse the repository at this point in the history
PPF-404: refactor template core functions to appropriate locations
  • Loading branch information
chinapandaman authored Nov 19, 2023
2 parents 429e1aa + dfaab91 commit e18dfb5
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 224 deletions.
153 changes: 153 additions & 0 deletions PyPDFForm/core/coordinate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
"""Contains helpers for coordinates calculations."""

from typing import Tuple, Union, List
from copy import deepcopy
import pdfrw
from reportlab.pdfbase.pdfmetrics import stringWidth

from ..middleware.text import Text
from .constants import ANNOTATION_RECTANGLE_KEY
from .template import get_element_alignment, get_char_rect_width, is_text_multiline


def get_draw_checkbox_radio_coordinates(
element: pdfrw.PdfDict,
element_middleware: Text,
) -> Tuple[Union[float, int], Union[float, int]]:
"""Returns coordinates to draw at given a PDF form checkbox/radio element."""

string_height = element_middleware.font_size * 96 / 72
width_mid_point = (
float(element[ANNOTATION_RECTANGLE_KEY][0])
+ float(element[ANNOTATION_RECTANGLE_KEY][2])
) / 2
height_mid_point = (
float(element[ANNOTATION_RECTANGLE_KEY][1])
+ float(element[ANNOTATION_RECTANGLE_KEY][3])
) / 2

return (
width_mid_point
- stringWidth(
element_middleware.value,
element_middleware.font,
element_middleware.font_size,
)
/ 2,
(height_mid_point - string_height / 2 + height_mid_point) / 2,
)


def get_draw_text_coordinates(
element: pdfrw.PdfDict, element_middleware: Text
) -> Tuple[Union[float, int], Union[float, int]]:
"""Returns coordinates to draw text at given a PDF form text element."""

if element_middleware.preview:
return (
float(element[ANNOTATION_RECTANGLE_KEY][0]),
float(element[ANNOTATION_RECTANGLE_KEY][3]) + 5,
)

element_value = element_middleware.value or ""
length = (
min(len(element_value), element_middleware.max_length)
if element_middleware.max_length is not None
else len(element_value)
)
element_value = element_value[:length]

if element_middleware.text_wrap_length is not None:
element_value = element_value[: element_middleware.text_wrap_length]

character_paddings = (
element_middleware.character_paddings[:length]
if element_middleware.character_paddings is not None
else element_middleware.character_paddings
)

alignment = get_element_alignment(element) or 0
x = float(element[ANNOTATION_RECTANGLE_KEY][0])

if int(alignment) != 0:
width_mid_point = (
float(element[ANNOTATION_RECTANGLE_KEY][0])
+ float(element[ANNOTATION_RECTANGLE_KEY][2])
) / 2
string_width = stringWidth(
element_value,
element_middleware.font,
element_middleware.font_size,
)
if element_middleware.comb is True and length:
string_width = character_paddings[-1] + stringWidth(
element_value[-1],
element_middleware.font,
element_middleware.font_size,
)

if int(alignment) == 1:
x = width_mid_point - string_width / 2
elif int(alignment) == 2:
x = float(element[ANNOTATION_RECTANGLE_KEY][2]) - string_width
if length > 0 and element_middleware.comb is True:
x -= (
get_char_rect_width(element, element_middleware)
- stringWidth(
element_value[-1],
element_middleware.font,
element_middleware.font_size,
)
) / 2

string_height = element_middleware.font_size * 96 / 72
height_mid_point = (
float(element[ANNOTATION_RECTANGLE_KEY][1])
+ float(element[ANNOTATION_RECTANGLE_KEY][3])
) / 2
y = (height_mid_point - string_height / 2 + height_mid_point) / 2
if is_text_multiline(element):
y = float(element[ANNOTATION_RECTANGLE_KEY][3]) - string_height / 1.5

if int(alignment) == 1 and element_middleware.comb is True and length != 0:
x -= character_paddings[0] / 2
if length % 2 == 0:
x -= (
character_paddings[0]
+ stringWidth(
element_value[:1],
element_middleware.font,
element_middleware.font_size,
)
/ 2
)

return x, y


def get_text_line_x_coordinates(
element: pdfrw.PdfDict, element_middleware: Text
) -> Union[List[float], None]:
"""
Returns the x coordinates to draw lines
of the text at given a PDF form paragraph element.
"""

if (
element_middleware.text_wrap_length is not None
and element_middleware.text_lines is not None
and len(element_middleware.text_lines)
and isinstance(element_middleware.value, str)
and len(element_middleware.value) > element_middleware.text_wrap_length
):
result = []
_ele = deepcopy(element_middleware)
for each in element_middleware.text_lines:
_ele.value = each
_ele.text_wrap_length = None
result.append(get_draw_text_coordinates(element, _ele)[0])

return result

return None
8 changes: 5 additions & 3 deletions PyPDFForm/core/filler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from . import template, utils
from . import watermark as watermark_core
from .font import checkbox_radio_font_size
from .coordinate import get_draw_checkbox_radio_coordinates, \
get_draw_text_coordinates, get_text_line_x_coordinates


def fill(
Expand All @@ -36,7 +38,7 @@ def fill(
if isinstance(elements[key], (Checkbox, Radio)):
font_size = checkbox_radio_font_size(_element)
_to_draw = utils.checkbox_radio_to_draw(elements[key], font_size)
x, y = template.get_draw_checkbox_radio_coordinates(_element, _to_draw)
x, y = get_draw_checkbox_radio_coordinates(_element, _to_draw)
if isinstance(elements[key], Checkbox) and elements[key].value:
needs_to_be_drawn = True
elif isinstance(elements[key], Radio):
Expand All @@ -48,10 +50,10 @@ def fill(
else:
elements[
key
].text_line_x_coordinates = template.get_text_line_x_coordinates(
].text_line_x_coordinates = get_text_line_x_coordinates(
_element, elements[key]
)
x, y = template.get_draw_text_coordinates(_element, elements[key])
x, y = get_draw_text_coordinates(_element, elements[key])
_to_draw = elements[key]
needs_to_be_drawn = True

Expand Down
49 changes: 46 additions & 3 deletions PyPDFForm/core/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
from io import BytesIO
from math import sqrt
from typing import Dict, Union
from typing import Dict, Union, Tuple

import pdfrw
from reportlab.pdfbase import pdfmetrics
Expand All @@ -17,8 +17,8 @@
from .patterns import TEXT_FIELD_APPEARANCE_PATTERNS
from .template import (get_element_key, get_elements_by_page,
get_paragraph_auto_wrap_length, get_paragraph_lines,
get_text_field_font_color, get_text_field_font_size,
is_text_multiline, traverse_pattern)
is_text_multiline)
from .utils import traverse_pattern


def register_font(font_name: str, ttf_stream: bytes) -> bool:
Expand Down Expand Up @@ -109,6 +109,49 @@ def checkbox_radio_font_size(element: pdfrw.PdfDict) -> Union[float, int]:
return sqrt(area) * 72 / 96


def get_text_field_font_size(element: pdfrw.PdfDict) -> Union[float, int]:
"""Returns the font size of the text field if presented or zero."""

result = 0
for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
text_appearance = traverse_pattern(pattern, element)
if text_appearance:
text_appearance = text_appearance.replace("(", "").replace(")", "")
properties = text_appearance.split(" ")
for i, val in enumerate(properties):
if val == constants.FONT_SIZE_IDENTIFIER:
return float(properties[i - 1])

return result


def get_text_field_font_color(
element: pdfrw.PdfDict,
) -> Union[Tuple[float, float, float], None]:
"""Returns the font color tuple of the text field if presented or black."""

result = (0, 0, 0)
for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
text_appearance = traverse_pattern(pattern, element)
if text_appearance:
if constants.FONT_COLOR_IDENTIFIER not in text_appearance:
return result

text_appearance = (
text_appearance.replace("(", "").replace(")", "").split(" ")
)
for i, val in enumerate(text_appearance):
if val == constants.FONT_COLOR_IDENTIFIER.replace(" ", ""):
result = (
float(text_appearance[i - 3]),
float(text_appearance[i - 2]),
float(text_appearance[i - 1]),
)
break

return result


def update_text_field_attributes(
template_stream: bytes,
elements: Dict[str, ELEMENT_TYPES],
Expand Down
Loading

0 comments on commit e18dfb5

Please sign in to comment.