From fde89581299c8390585c5877742659e5186e7231 Mon Sep 17 00:00:00 2001 From: jingeli Date: Sun, 19 Nov 2023 14:28:15 -0600 Subject: [PATCH] PPF-405: removed circular imports --- PyPDFForm/core/font.py | 40 +++++++++++++- PyPDFForm/core/template.py | 69 ++++++++++++++++++++++++ PyPDFForm/core/utils.py | 108 +------------------------------------ PyPDFForm/wrapper.py | 3 +- 4 files changed, 111 insertions(+), 109 deletions(-) diff --git a/PyPDFForm/core/font.py b/PyPDFForm/core/font.py index 6d357300..a2c5814b 100644 --- a/PyPDFForm/core/font.py +++ b/PyPDFForm/core/font.py @@ -4,7 +4,7 @@ import re from io import BytesIO from math import sqrt -from typing import Union +from typing import Dict, Union import pdfrw from reportlab.pdfbase import pdfmetrics @@ -13,7 +13,10 @@ from . import constants from .constants import DEFAULT_FONT_SIZE from .patterns import TEXT_FIELD_APPEARANCE_PATTERNS -from .template import is_text_multiline, traverse_pattern +from .template import is_text_multiline, traverse_pattern, get_elements_by_page, get_element_key, \ + get_text_field_font_size, get_text_field_font_color, get_paragraph_auto_wrap_length, get_paragraph_lines +from ..middleware.constants import ELEMENT_TYPES +from ..middleware.text import Text def register_font(font_name: str, ttf_stream: bytes) -> bool: @@ -102,3 +105,36 @@ def checkbox_radio_font_size(element: pdfrw.PdfDict) -> Union[float, int]: ) return sqrt(area) * 72 / 96 + + +def update_text_field_attributes( + template_stream: bytes, + elements: Dict[str, ELEMENT_TYPES], +) -> None: + """Auto updates text fields' attributes.""" + + template_pdf = pdfrw.PdfReader(fdata=template_stream) + + for _, _elements in get_elements_by_page(template_pdf).items(): + for _element in _elements: + key = get_element_key(_element) + + if isinstance(elements[key], Text): + if elements[key].font is None: + elements[key].font = auto_detect_font(_element) + if elements[key].font_size is None: + elements[key].font_size = get_text_field_font_size( + _element + ) or text_field_font_size(_element) + if elements[key].font_color is None: + elements[key].font_color = get_text_field_font_color( + _element + ) + if ( + is_text_multiline(_element) + and elements[key].text_wrap_length is None + ): + elements[key].text_wrap_length = get_paragraph_auto_wrap_length( + _element, elements[key] + ) + elements[key].text_lines = get_paragraph_lines(elements[key]) diff --git a/PyPDFForm/core/template.py b/PyPDFForm/core/template.py index a1b5dc6e..be020040 100644 --- a/PyPDFForm/core/template.py +++ b/PyPDFForm/core/template.py @@ -384,3 +384,72 @@ def get_text_line_x_coordinates( return result return None + + +def get_paragraph_lines(element_middleware: Text) -> List[str]: + """Splits the paragraph field's text to a list of lines.""" + + lines = [] + result = [] + text_wrap_length = element_middleware.text_wrap_length + value = element_middleware.value or "" + if element_middleware.max_length is not None: + value = value[: element_middleware.max_length] + characters = value.split(" ") + current_line = "" + for each in characters: + line_extended = f"{current_line} {each}" if current_line else each + if len(line_extended) <= text_wrap_length: + current_line = line_extended + else: + lines.append(current_line) + current_line = each + lines.append(current_line) + + for each in lines: + while len(each) > text_wrap_length: + last_index = text_wrap_length - 1 + result.append(each[:last_index]) + each = each[last_index:] + if each: + if result and len(each) + 1 + len(result[-1]) <= text_wrap_length: + result[-1] = f"{result[-1]}{each} " + else: + result.append(f"{each} ") + + if result: + result[-1] = result[-1][:-1] + + return result + + +def get_paragraph_auto_wrap_length( + element: pdfrw.PdfDict, element_middleware: Text +) -> int: + """Calculates the text wrap length of a paragraph field.""" + + value = element_middleware.value or "" + width = abs( + float(element[constants.ANNOTATION_RECTANGLE_KEY][0]) + - float(element[constants.ANNOTATION_RECTANGLE_KEY][2]) + ) + text_width = stringWidth( + value, + element_middleware.font, + element_middleware.font_size, + ) + + lines = text_width / width + if lines > 1: + counter = 0 + _width = 0 + while _width <= width: + counter += 1 + _width = stringWidth( + value[:counter], + element_middleware.font, + element_middleware.font_size, + ) + return counter - 1 + + return len(value) + 1 diff --git a/PyPDFForm/core/utils.py b/PyPDFForm/core/utils.py index 88a4486a..96f65369 100644 --- a/PyPDFForm/core/utils.py +++ b/PyPDFForm/core/utils.py @@ -2,17 +2,15 @@ """Contains utility helpers.""" from io import BytesIO -from typing import Dict, List, Union +from typing import Union import pdfrw -from reportlab.pdfbase.pdfmetrics import stringWidth from ..middleware.checkbox import Checkbox from ..middleware.constants import ELEMENT_TYPES from ..middleware.radio import Radio from ..middleware.text import Text -from . import constants, template -from .font import auto_detect_font, text_field_font_size +from . import constants def generate_stream(pdf: pdfrw.PdfReader) -> bytes: @@ -29,108 +27,6 @@ def generate_stream(pdf: pdfrw.PdfReader) -> bytes: return result -def update_text_field_attributes( - template_stream: bytes, - elements: Dict[str, ELEMENT_TYPES], -) -> None: - """Auto updates text fields' attributes.""" - - template_pdf = pdfrw.PdfReader(fdata=template_stream) - - for _, _elements in template.get_elements_by_page(template_pdf).items(): - for _element in _elements: - key = template.get_element_key(_element) - - if isinstance(elements[key], Text): - if elements[key].font is None: - elements[key].font = auto_detect_font(_element) - if elements[key].font_size is None: - elements[key].font_size = template.get_text_field_font_size( - _element - ) or text_field_font_size(_element) - if elements[key].font_color is None: - elements[key].font_color = template.get_text_field_font_color( - _element - ) - if ( - template.is_text_multiline(_element) - and elements[key].text_wrap_length is None - ): - elements[key].text_wrap_length = get_paragraph_auto_wrap_length( - _element, elements[key] - ) - elements[key].text_lines = get_paragraph_lines(elements[key]) - - -def get_paragraph_lines(element_middleware: Text) -> List[str]: - """Splits the paragraph field's text to a list of lines.""" - - lines = [] - result = [] - text_wrap_length = element_middleware.text_wrap_length - value = element_middleware.value or "" - if element_middleware.max_length is not None: - value = value[: element_middleware.max_length] - characters = value.split(" ") - current_line = "" - for each in characters: - line_extended = f"{current_line} {each}" if current_line else each - if len(line_extended) <= text_wrap_length: - current_line = line_extended - else: - lines.append(current_line) - current_line = each - lines.append(current_line) - - for each in lines: - while len(each) > text_wrap_length: - last_index = text_wrap_length - 1 - result.append(each[:last_index]) - each = each[last_index:] - if each: - if result and len(each) + 1 + len(result[-1]) <= text_wrap_length: - result[-1] = f"{result[-1]}{each} " - else: - result.append(f"{each} ") - - if result: - result[-1] = result[-1][:-1] - - return result - - -def get_paragraph_auto_wrap_length( - element: pdfrw.PdfDict, element_middleware: Text -) -> int: - """Calculates the text wrap length of a paragraph field.""" - - value = element_middleware.value or "" - width = abs( - float(element[constants.ANNOTATION_RECTANGLE_KEY][0]) - - float(element[constants.ANNOTATION_RECTANGLE_KEY][2]) - ) - text_width = stringWidth( - value, - element_middleware.font, - element_middleware.font_size, - ) - - lines = text_width / width - if lines > 1: - counter = 0 - _width = 0 - while _width <= width: - counter += 1 - _width = stringWidth( - value[:counter], - element_middleware.font, - element_middleware.font_size, - ) - return counter - 1 - - return len(value) + 1 - - def checkbox_radio_to_draw( element: Union[Checkbox, Radio], font_size: Union[float, int] ) -> Text: diff --git a/PyPDFForm/wrapper.py b/PyPDFForm/wrapper.py index 36cc25a1..74d6c714 100644 --- a/PyPDFForm/wrapper.py +++ b/PyPDFForm/wrapper.py @@ -10,6 +10,7 @@ from .core import image as image_core from .core import utils from .core import watermark as watermark_core +from .core.font import update_text_field_attributes from .middleware import adapter, constants from .middleware import template as template_middleware from .middleware.dropdown import Dropdown @@ -109,7 +110,7 @@ def fill( if isinstance(value, Dropdown): self.elements[key] = template_middleware.dropdown_to_text(value) - utils.update_text_field_attributes(self.stream, self.elements) + update_text_field_attributes(self.stream, self.elements) if self.read(): self.elements = template_middleware.set_character_x_paddings( self.stream, self.elements