Skip to content

Commit

Permalink
Merge pull request #371 from chinapandaman/PPF-370
Browse files Browse the repository at this point in the history
PPF-370: implement smart line break
  • Loading branch information
chinapandaman authored Jul 15, 2023
2 parents 14cbdcd + cf18cff commit 3bc0206
Show file tree
Hide file tree
Showing 20 changed files with 96 additions and 155 deletions.
2 changes: 1 addition & 1 deletion PyPDFForm/core/filler.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def fill(
else:
elements[
key
].last_line_x_coordinate = template.get_last_line_x_coordinate(
].text_line_x_coordinates = template.get_text_line_x_coordinates(
_element, elements[key]
)
x, y = template.get_draw_text_coordinates(_element, elements[key])
Expand Down
16 changes: 10 additions & 6 deletions PyPDFForm/core/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,23 +327,27 @@ def get_draw_text_coordinates(
return x, y


def get_last_line_x_coordinate(
def get_text_line_x_coordinates(
element: pdfrw.PdfDict, element_middleware: Text
) -> Union[float, int, None]:
) -> Union[List[float], None]:
"""
Returns the x coordinate to draw the last line
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)
last_line_mark = -1 * (len(_ele.value) % _ele.text_wrap_length)
_ele.value = _ele.value[last_line_mark:]
for each in element_middleware.text_lines:
_ele.value = each
result.append(get_draw_text_coordinates(element, _ele)[0])

return get_draw_text_coordinates(element, _ele)[0]
return result

return None
44 changes: 42 additions & 2 deletions PyPDFForm/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Contains utility helpers."""

from io import BytesIO
from typing import Dict, Union
from typing import Dict, Union, List

import pdfrw
from reportlab.pdfbase.pdfmetrics import stringWidth
Expand Down Expand Up @@ -47,10 +47,50 @@ def update_text_field_attributes(
elements[key].font_size = template.get_text_field_font_size(
_element
) or font_size_core.text_field_font_size(_element)
if template.is_text_multiline(_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(
Expand Down
27 changes: 11 additions & 16 deletions PyPDFForm/core/watermark.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,17 @@ def draw_text(
)
else:
text_obj = canv.beginText(0, 0)

start = 0
end = element.text_wrap_length

while end < len(text_to_draw):
text_obj.textLine(text_to_draw[start:end])
start += element.text_wrap_length
end += element.text_wrap_length

if (
element.last_line_x_coordinate is not None
and element.last_line_x_coordinate - coordinate_x != 0
):
text_obj.moveCursor(element.last_line_x_coordinate - coordinate_x, 0)

text_obj.textLine(text_to_draw[start:])
for i, line in enumerate(element.text_lines):
cursor_moved = False
if (
element.text_line_x_coordinates is not None
and element.text_line_x_coordinates[i] - coordinate_x != 0
):
text_obj.moveCursor(element.text_line_x_coordinates[i] - coordinate_x, 0)
cursor_moved = True
text_obj.textLine(line)
if cursor_moved:
text_obj.moveCursor(-1 * (element.text_line_x_coordinates[i] - coordinate_x), 0)

canv.saveState()
canv.translate(
Expand Down
3 changes: 2 additions & 1 deletion PyPDFForm/middleware/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def __init__(
self.max_length = None
self.comb = None
self.character_paddings = None
self.last_line_x_coordinate = None
self.text_lines = None
self.text_line_x_coordinates = None

@property
def schema_definition(self) -> dict:
Expand Down
2 changes: 0 additions & 2 deletions PyPDFForm/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def __init__(
each.text_y_offset = kwargs.get(
"global_text_y_offset", constants.GLOBAL_TEXT_Y_OFFSET
)
each.text_wrap_length = kwargs.get("global_text_wrap_length")

def read(self) -> bytes:
"""Reads the file stream of a PDF form."""
Expand Down Expand Up @@ -109,7 +108,6 @@ def draw_text(
new_element.text_y_offset = kwargs.get(
"text_y_offset", constants.GLOBAL_TEXT_Y_OFFSET
)
new_element.text_wrap_length = kwargs.get("text_wrap_length")

watermarks = watermark_core.create_watermarks_and_draw(
self.stream,
Expand Down
37 changes: 0 additions & 37 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,41 +131,6 @@ with open(PATH_TO_FILLED_PDF_FORM, "wb+") as output:
)
```

## Wrap filled text with a length

Sometimes texts printed on the PDF form may be too lengthy. This example
demos globally wrapping texts with a given length.

```python
import os

from PyPDFForm import PyPDFForm

PATH_TO_DOWNLOADED_SAMPLE_PDF_FORM = os.path.join(
os.path.expanduser("~/Downloads"), "sample_template.pdf"
) # Change this to where you downloaded the sample PDF form

PATH_TO_FILLED_PDF_FORM = os.path.join(
os.path.expanduser("~"), "output.pdf"
) # Change this to where you wish to put your filled PDF form

with open(PATH_TO_FILLED_PDF_FORM, "wb+") as output:
output.write(
PyPDFForm(PATH_TO_DOWNLOADED_SAMPLE_PDF_FORM, global_text_wrap_length=2,)
.fill(
{
"test": "test_1",
"check": True,
"test_2": "test_2",
"check_2": False,
"test_3": "test_3",
"check_3": True,
},
)
.read()
)
```

## Offset texts globally

This example offsets all texts printed on the PDF form by 100 horizontally
Expand Down Expand Up @@ -237,10 +202,8 @@ with open(PATH_TO_FILLED_PDF_FORM, "wb+") as output:
pdf_form.elements["test"].font_color = (1, 0, 0)
pdf_form.elements["test_2"].text_x_offset = 50
pdf_form.elements["test_2"].text_y_offset = -50
pdf_form.elements["test_2"].text_wrap_length = 1
pdf_form.elements["test_2"].font_color = (0, 1, 0)
pdf_form.elements["test_3"].font = "LiberationSerif-Italic"
pdf_form.elements["test_3"].text_wrap_length = 2
pdf_form.elements["test_3"].font_color = (0, 0, 1)

pdf_form.fill(
Expand Down
Binary file modified pdf_samples/sample_filled_customized_elements.pdf
Binary file not shown.
Binary file not shown.
Binary file removed pdf_samples/sample_filled_text_wrap_2.pdf
Binary file not shown.
Binary file modified pdf_samples/sample_pdf_with_drawn_text.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified pdf_samples/test_paragraph_auto_font_auto_wrap.pdf
Binary file not shown.
Binary file modified pdf_samples/test_paragraph_auto_wrap.pdf
Binary file not shown.
Binary file modified pdf_samples/test_paragraph_complex.pdf
Binary file not shown.
Binary file added pdf_samples/test_paragraph_max_length.pdf
Binary file not shown.
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ def sample_template_paragraph_complex(pdf_samples):
return f.read()


@pytest.fixture
def sample_template_with_paragraph_max_length(pdf_samples):
with open(
os.path.join(pdf_samples, "sample_template_with_paragraph_max_length.pdf"), "rb+"
) as f:
return f.read()


@pytest.fixture
def sample_template_with_max_length_text_field(pdf_samples):
with open(
Expand Down
Loading

0 comments on commit 3bc0206

Please sign in to comment.