Skip to content

Commit

Permalink
Merge pull request #534 from chinapandaman/PPF-533
Browse files Browse the repository at this point in the history
PPF-533: calculates text wrap line by line
  • Loading branch information
chinapandaman authored Mar 22, 2024
2 parents 142f18b + a3eb98d commit 9d5cae9
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 66 deletions.
92 changes: 26 additions & 66 deletions PyPDFForm/template.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Contains helpers for generic template related processing."""

from sys import maxsize
from typing import Dict, List, Tuple, Union

from pypdf import PdfReader
Expand Down Expand Up @@ -132,10 +133,10 @@ def update_text_field_attributes(
if widgets[key].font_color is None:
widgets[key].font_color = get_text_field_font_color(_widget)
if is_text_multiline(_widget) and widgets[key].text_wrap_length is None:
widgets[key].text_lines = get_paragraph_lines(_widget, widgets[key])
widgets[key].text_wrap_length = get_paragraph_auto_wrap_length(
_widget, widgets[key]
widgets[key]
)
widgets[key].text_lines = get_paragraph_lines(_widget, widgets[key])


def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
Expand Down Expand Up @@ -295,35 +296,12 @@ def get_character_x_paddings(widget: dict, widget_middleware: Text) -> List[floa
return result


def calculate_wrap_length(widget: dict, widget_middleware: Text, v: str) -> int:
"""Increments the substring until reaching maximum horizontal width."""

width = abs(
float(widget[ANNOTATION_RECTANGLE_KEY][0])
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
)
value = widget_middleware.value or ""
value = value.replace(NEW_LINE_SYMBOL, " ")

counter = 0
_width = 0
while _width <= width and counter < len(value):
counter += 1
_width = stringWidth(
v[:counter],
widget_middleware.font,
widget_middleware.font_size,
)
return counter - 1


def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
"""Splits the paragraph field's text to a list of lines."""

# pylint: disable=R0912
lines = []
result = []
text_wrap_length = widget_middleware.text_wrap_length
value = widget_middleware.value or ""
if widget_middleware.max_length is not None:
value = value[: widget_middleware.max_length]
Expand All @@ -339,7 +317,9 @@ def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
current_line = ""
for each in characters:
line_extended = f"{current_line} {each}" if current_line else each
if len(line_extended) <= text_wrap_length:
if stringWidth(line_extended,
widget_middleware.font,
widget_middleware.font_size) <= width:
current_line = line_extended
else:
lines.append(current_line)
Expand All @@ -350,25 +330,25 @@ def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
else current_line
)

for line in lines:
while (
stringWidth(
line[:text_wrap_length],
widget_middleware.font,
widget_middleware.font_size,
)
> width
):
text_wrap_length -= 1

for each in lines:
while len(each) > text_wrap_length:
result.append(each[:text_wrap_length])
each = each[text_wrap_length:]
tracker = ""
for char in each:
check = tracker + char
if stringWidth(check,
widget_middleware.font,
widget_middleware.font_size) > width:
result.append(tracker)
tracker = char
else:
tracker = check

each = tracker
if each:
if (
result
and len(each) + 1 + len(result[-1]) <= text_wrap_length
and stringWidth(f"{each} {result[-1]}",
widget_middleware.font,
widget_middleware.font_size) <= width
and NEW_LINE_SYMBOL not in result[-1]
):
result[-1] = f"{result[-1]}{each} "
Expand All @@ -384,31 +364,11 @@ def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
return result


def get_paragraph_auto_wrap_length(widget: dict, widget_middleware: Text) -> int:
def get_paragraph_auto_wrap_length(widget_middleware: Text) -> int:
"""Calculates the text wrap length of a paragraph field."""

value = widget_middleware.value or ""
value = value.replace(NEW_LINE_SYMBOL, " ")
width = abs(
float(widget[ANNOTATION_RECTANGLE_KEY][0])
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
)
text_width = stringWidth(
value,
widget_middleware.font,
widget_middleware.font_size,
)
result = maxsize
for line in widget_middleware.text_lines:
result = min(result, len(line))

lines = text_width / width
if lines > 1:
current_min = 0
while len(value) and current_min < len(value):
result = calculate_wrap_length(widget, widget_middleware, value)
value = value[result:]
if current_min == 0:
current_min = result
elif result < current_min:
current_min = result
return current_min

return len(value) + 1
return result
Binary file not shown.
Binary file modified pdf_samples/paragraph/test_paragraph_complex.pdf
Binary file not shown.
Binary file modified pdf_samples/paragraph/test_paragraph_max_length.pdf
Binary file not shown.
Binary file added pdf_samples/scenario/issues/521-expected.pdf
Binary file not shown.
Binary file modified pdf_samples/scenario/issues/PPF-415-2-expected.pdf
Binary file not shown.
22 changes: 22 additions & 0 deletions tests/scenario/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ def test_pdf_form_with_paragraph_fields_new_line_symbol_text_overflow(
assert obj.read() == expected


def test_521(issue_pdf_directory, request):
expected_path = os.path.join(
issue_pdf_directory, "521-expected.pdf"
)
with open(expected_path, "rb+") as f:
obj = PdfWrapper(os.path.join(issue_pdf_directory, "521.pdf")).fill(
{
"Text1": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", # noqa
"Text2": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. NEMO ENIM IPSAM VOLUPTATEM QUIA VOLUPTAS SIT ASPERNATUR AUT ODIT AUT FUGIT, SED QUIA CONSEQUUNTUR MAGNI DOLORES EOS QUI RATIONE VOLUPTATEM SEQUI NESCIUNT. NEQUE PORRO QUISQUAM EST, QUI DOLOREM IPSUM QUIA DOLOR SIT AMET, CONSECTETUR, ADIPISCI VELIT, SED QUIA NON NUMQUAM EIUS MODI TEMPORA INCIDUNT UT LABORE ET DOLORE MAGNAM ALIQUAM QUAERAT VOLUPTATEM. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", # noqa
"Text3": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", # noqa
},
)

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

expected = f.read()

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


def test_pdf_form_with_paragraph_fields_new_line_symbol_short_text(
issue_pdf_directory, request
):
Expand Down

0 comments on commit 9d5cae9

Please sign in to comment.