Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write() refactor to use new line wrapping code #346

Merged
merged 87 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
71bd53d
Fix parsing of csv template files
gmischler Sep 18, 2021
d70bc37
fixes suggested by static code check
gmischler Sep 18, 2021
6ed9686
Update template.py
gmischler Sep 19, 2021
fa62a8d
now it's dark.
gmischler Sep 20, 2021
f1d7802
do some hardcoded template tests without multiline
gmischler Sep 21, 2021
ec69b8f
first round Splitting Template() into FlexTemplate()
gmischler Sep 25, 2021
6771592
offset and rotate for render(), first test
gmischler Sep 25, 2021
536e819
small fixes and cleanup
gmischler Sep 25, 2021
42e0d27
removing mistaken checkin
gmischler Sep 25, 2021
5b1d889
test for multipage Template(); Template.code39 with standard template…
gmischler Sep 26, 2021
92c9e28
refer defaults to type handlers, x2 optional for barcodes
gmischler Sep 26, 2021
0195db4
more template and flextemplate tests
gmischler Sep 26, 2021
bb97a63
Merge remote-tracking branch 'upstream/master'
gmischler Sep 26, 2021
e5ab09c
static check fixes
gmischler Sep 26, 2021
fdb03de
more pylint
gmischler Sep 26, 2021
56f639e
blackity-black
gmischler Sep 26, 2021
bef03d1
even blacker
gmischler Sep 26, 2021
cad0264
Expand docstrings, update help, hide private methods.
gmischler Sep 29, 2021
0f99984
Issues from PR review
gmischler Sep 29, 2021
1af4365
Merge remote-tracking branch 'upstream/master'
gmischler Sep 29, 2021
bba1ca1
Issue #226 solved: Rotate anything anywhere
gmischler Sep 30, 2021
b89147a
Issue #238 solved - split_multicell doesn't modify target document
gmischler Sep 30, 2021
13e9739
Documentation details and corrections
gmischler Sep 30, 2021
058f7ea
breaking up long line
gmischler Sep 30, 2021
5807548
rotation fix slightly changed barcode output
gmischler Sep 30, 2021
557148a
Update CHANGELOG.md
gmischler Sep 30, 2021
ddba2ce
Include _write() in template rotation test
gmischler Sep 30, 2021
4847792
FlexTemplate.render() with scaling
gmischler Oct 1, 2021
6f2c98f
empty text field - consistency between T and W
gmischler Oct 2, 2021
2b2d82e
Enforce user input types as early as possible
gmischler Oct 2, 2021
b00666d
Merge remote-tracking branch 'upstream/master'
gmischler Oct 2, 2021
ba9ab80
Fix to make sure deprecated code39 arguments still work
gmischler Oct 2, 2021
a746ef7
sync to upstream
gmischler Oct 2, 2021
7c9fcdb
some more test coverage
gmischler Oct 2, 2021
03814bb
pylint asking for style points...
gmischler Oct 2, 2021
d09688c
picky black...
gmischler Oct 2, 2021
f8febaa
Merge branch 'PyFPDF:master' into master
gmischler Oct 3, 2021
546d5b2
Merge remote-tracking branch 'upstream/master'
gmischler Oct 8, 2021
294287c
Change background default to transparent
gmischler Oct 8, 2021
431d918
Add ellipse element to templates
gmischler Oct 8, 2021
753c246
Bugfix skipping check for x2 with barcods
gmischler Oct 8, 2021
112f496
More template tests
gmischler Oct 8, 2021
d5de0e2
code cleanup
gmischler Oct 8, 2021
605acb1
list template changes to log
gmischler Oct 9, 2021
a256212
expose FlexTemplate through __init__.__all__
gmischler Oct 9, 2021
de230b8
Merge remote-tracking branch 'upstream/master'
gmischler Oct 11, 2021
2582afb
bugfix: Keep track of nested rotation contexts
gmischler Oct 11, 2021
a6501f3
test fix: text file loaded in binary mode
gmischler Oct 13, 2021
f26a873
new set_dash_pattern(); dashed_line() retired
gmischler Oct 14, 2021
325566c
Merge remote-tracking branch 'upstream/master'
gmischler Oct 14, 2021
bae8b76
test update for deprecated dashed_line()
gmischler Oct 14, 2021
007e00b
mask unused argument in test
gmischler Oct 14, 2021
a96bcc3
error message fix, expand test coverage
gmischler Oct 14, 2021
b04c879
updates based on PR review
gmischler Oct 15, 2021
41672c5
Merge remote-tracking branch 'upstream/master'
gmischler Oct 16, 2021
f5573ef
Graphics state stack implemented, rotation fixed
gmischler Oct 18, 2021
fec5188
Simplify Templates again, making use of flexible rotation
gmischler Oct 31, 2021
c201180
code cleanup
gmischler Oct 31, 2021
cdcdf37
Merge remote-tracking branch 'upstream/master'
gmischler Oct 31, 2021
f4f2dd0
merging updates from upstream
gmischler Oct 31, 2021
35df15b
Update flextemplate_rotation.pdf
gmischler Oct 31, 2021
6c46a5f
Merge remote-tracking branch 'upstream/master'
gmischler Nov 2, 2021
5ebb144
Updating changelog
gmischler Nov 2, 2021
e1299a2
Merge remote-tracking branch 'upstream/master'
gmischler Nov 10, 2021
604a6b0
include line_width in graphics context stack
gmischler Nov 10, 2021
29c4266
hopefully fixing line endings
gmischler Nov 10, 2021
516be96
changes discussed in PR review
gmischler Nov 12, 2021
18bd976
migrating test_regular_polygon.py to standard fixture
gmischler Nov 12, 2021
5747acc
Merge branch 'PyFPDF:master' into master
gmischler Nov 13, 2021
e8507ec
Merge branch 'PyFPDF:master' into master
gmischler Feb 12, 2022
f24a4cf
Merge branch 'PyFPDF:master' into master
gmischler Feb 20, 2022
99a03a8
initial working write() refactor
gmischler Feb 24, 2022
0d90739
replace ln=0 internally with newpos_x/newpos_y
gmischler Feb 25, 2022
6e68780
renaming test/cells to test/text, moving rext related tests there
gmischler Feb 25, 2022
2c85c61
test cases for write() and _render_styled_cell_text()
gmischler Feb 26, 2022
427b61b
Move word spacing code to _render_styled_cell_text()
gmischler Mar 2, 2022
27b85f4
Merge branch 'PyFPDF:master' into master
gmischler Mar 3, 2022
fb7dfac
Merge branch 'PyFPDF:master' into write_refactor
gmischler Mar 3, 2022
c7d911b
print_sh option for write() and multi_cell()
gmischler Mar 3, 2022
2774b24
Merge branch 'write_refactor' of https://github.com/gmischler/fpdf2 i…
gmischler Mar 3, 2022
ff3b19a
tabs to spaces
gmischler Mar 3, 2022
5884cc8
Apply PR review
gmischler Mar 4, 2022
c99ac7d
Merge branch 'PyFPDF:master' into master
gmischler Mar 5, 2022
155f758
Merge branch 'master' into write_refactor
gmischler Mar 5, 2022
baee4b7
merge origin updates
gmischler Mar 5, 2022
ce2fc7c
newpos_[xy] to new[xy], annotations, docstring fixes
gmischler Mar 5, 2022
3a72364
revert drawing.py, after black 22.1 made up its mind
gmischler Mar 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ and [PEP 440](https://www.python.org/dev/peps/pep-0440/).

## [2.5.1] - not released yet
### Added
- support for soft-hyphen (`\u00ad`) break in `cell()` & `multi_cell()` calls - thanks @oleksii-shyman & @gmischler!
- support for soft-hyphen (`\u00ad`) break in `write()`, `cell()` & `multi_cell()` calls - thanks @oleksii-shyman & @gmischler!
- new documentation page on [Emojis, Symbols & Dingbats](https://pyfpdf.github.io/fpdf2/EmojisSymbolsDingbats.html)
- documentation on combining `borb` & `fpdf2`: [Creating a borb.pdf.document.Document from a FPDF instance](https://pyfpdf.github.io/fpdf2/ExistingPDFs.html)

### Changed
- `write()` now supports soft hyphen characters, thanks to @gmischler
- `fname` is now a required parameter for `FPDF.add_font()`
- `image()` method now insert `.svg` images as PDF paths
- the [defusedxml](https://pypi.org/project/defusedxml/) package was added as dependency in order to make SVG parsing safer
Expand Down
5 changes: 3 additions & 2 deletions docs/PageBreaks.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Page breaks #

By default, `fpdf2` will automatically perform page breaks whenever a cell is rendered at the bottom of a page
with a height greater than the page bottom margin.
By default, `fpdf2` will automatically perform page breaks whenever a cell or
the text from a `write()` is rendered at the bottom of a page with a height
greater than the page bottom margin.

This behaviour can be controlled using the
[`set_auto_page_break`](fpdf/fpdf.html#fpdf.fpdf.FPDF.set_auto_page_break)
Expand Down
4 changes: 2 additions & 2 deletions docs/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ to 1 mm (against 0.2 by default) with
[set_line_width](fpdf/fpdf.html#fpdf.fpdf.FPDF.set_line_width). Finally, we output the cell (the
last parameter to true indicates that the background must be filled).

The method used to print the paragraphs is [multi_cell](fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell).
The method used to print the paragraphs is [multi_cell](fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell). Text is justified by default.
Each time a line reaches the right extremity of the cell or a carriage return
character is met, a line break is issued and a new cell automatically created
under the current one. Text is justified by default.
under the current one. An automatic break is performed at the location of the nearest space or soft-hyphen (\u00ad) character before the right limit. A soft-hyphen will be replaced by a normal hyphen when triggering a line break, and ignored otherwise.

Two document properties are defined: the title
([set_title](fpdf/fpdf.html#fpdf.fpdf.FPDF.set_title)) and the author
Expand Down
4 changes: 4 additions & 0 deletions fpdf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from .fpdf import (
FPDF,
XPos,
YPos,
FPDFException,
TitleStyle,
FPDF_FONT_DIR as _FPDF_FONT_DIR,
Expand Down Expand Up @@ -39,6 +41,8 @@
"__license__",
# Classes
"FPDF",
"XPos",
"YPos",
"Template",
"FlexTemplate",
"TitleStyle",
Expand Down
582 changes: 335 additions & 247 deletions fpdf/fpdf.py

Large diffs are not rendered by default.

131 changes: 78 additions & 53 deletions fpdf/line_break.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections import namedtuple
from typing import NamedTuple, Any, Union, Sequence

SOFT_HYPHEN = "\u00ad"
HYPHEN = "\u002d"
Expand All @@ -7,61 +7,68 @@


class Fragment:
def __init__(self, style, underlined, characters=None):
def __init__(self, style: str, underlined: bool, characters: str = None):
self.characters = [] if characters is None else characters
self.style = style
self.underline = underlined

@classmethod
def from_string(cls, string, style, underlined):
def from_string(cls, string: str, style: str, underlined: bool):
return cls(style, underlined, list(string))

def trim(self, index):
def trim(self, index: int):
self.characters = self.characters[:index]

@property
def string(self):
return "".join(self.characters)

def __eq__(self, other):
def __eq__(self, other: Any):
return (
self.characters == other.characters
and self.style == other.style
and self.underline == other.underline
)


TextLine = namedtuple(
"TextLine",
("fragments", "text_width", "number_of_spaces_between_words", "justify"),
)

SpaceHint = namedtuple(
"SpaceHint",
(
"original_fragment_index",
"original_character_index",
"current_line_fragment_index",
"current_line_character_index",
"width",
"number_of_spaces",
),
)

HyphenHint = namedtuple(
"HyphenHint",
SpaceHint._fields
+ (
"character_to_append",
"character_to_append_width",
"character_to_append_style",
"character_to_append_underline",
),
)
class TextLine(NamedTuple):
fragments: tuple
text_width: float
number_of_spaces_between_words: int
justify: bool


class SpaceHint(NamedTuple):
original_fragment_index: int
original_character_index: int
current_line_fragment_index: int
current_line_character_index: int
width: float
number_of_spaces: int


class HyphenHint(NamedTuple):
original_fragment_index: int
original_character_index: int
current_line_fragment_index: int
current_line_character_index: int
width: float
number_of_spaces: int
character_to_append: str
character_to_append_width: float
character_to_append_style: str
character_to_append_underline: bool


class CurrentLine:
def __init__(self):
def __init__(self, print_sh: bool = False):
"""
Per-line text fragment management for use by MultiLineBreak.
Args:
print_sh (bool): If true, a soft-hyphen will be rendered
normally, instead of triggering a line break. Default: False
"""
self.print_sh = print_sh
self.fragments = []
self.width = 0
self.number_of_spaces = 0
Expand All @@ -82,12 +89,12 @@ def __init__(self):

def add_character(
self,
character,
character_width,
style,
underline,
original_fragment_index,
original_character_index,
character: str,
character_width: float,
style: str,
underline: bool,
original_fragment_index: int,
original_character_index: int,
):
assert character != NEWLINE

Expand Down Expand Up @@ -115,7 +122,7 @@ def add_character(
self.number_of_spaces,
)
self.number_of_spaces += 1
elif character == SOFT_HYPHEN:
elif character == SOFT_HYPHEN and not self.print_sh:
self.hyphen_break_hint = HyphenHint(
original_fragment_index,
original_character_index,
Expand All @@ -129,11 +136,11 @@ def add_character(
underline,
)

if character != SOFT_HYPHEN:
if character != SOFT_HYPHEN or self.print_sh:
self.width += character_width
active_fragment.characters.append(character)

def _apply_automatic_hint(self, break_hint):
def _apply_automatic_hint(self, break_hint: Union[SpaceHint, HyphenHint]):
"""
This function mutates the current_line, applying one of the states
observed in the past and stored in
Expand All @@ -145,7 +152,7 @@ def _apply_automatic_hint(self, break_hint):
self.number_of_spaces = break_hint.number_of_spaces
self.width = break_hint.width

def manual_break(self, justify=False):
def manual_break(self, justify: bool = False):
return TextLine(
fragments=self.fragments,
text_width=self.width,
Expand All @@ -156,7 +163,7 @@ def manual_break(self, justify=False):
def automatic_break_possible(self):
return self.hyphen_break_hint is not None or self.space_break_hint is not None

def automatic_break(self, justify):
def automatic_break(self, justify: bool):
assert self.automatic_break_possible()
if self.hyphen_break_hint is not None and (
self.space_break_hint is None
Expand Down Expand Up @@ -185,28 +192,37 @@ def automatic_break(self, justify):


class MultiLineBreak:
def __init__(self, styled_text_fragments, size_by_style, justify=False):

def __init__(
self,
styled_text_fragments: Sequence,
size_by_style: Sequence,
justify: bool = False,
print_sh: bool = False,
):
self.styled_text_fragments = styled_text_fragments

self.size_by_style = size_by_style
self.justify = justify

self.print_sh = print_sh
self.fragment_index = 0
self.character_index = 0

def _get_character_width(self, character, style=""):
if character == SOFT_HYPHEN:
def _get_character_width(self, character: str, style: str = ""):
if character == SOFT_HYPHEN and not self.print_sh:
# HYPHEN is inserted instead of SOFT_HYPHEN
character = HYPHEN
return self.size_by_style(character, style)

def get_line_of_given_width(self, maximum_width):
# pylint: disable=too-many-return-statements
def get_line_of_given_width(self, maximum_width: float, wordsplit: bool = True):

if self.fragment_index == len(self.styled_text_fragments):
return None

current_line = CurrentLine()
last_fragment_index = self.fragment_index
last_character_index = self.character_index
line_full = False

current_line = CurrentLine(print_sh=self.print_sh)
while self.fragment_index < len(self.styled_text_fragments):

current_fragment = self.styled_text_fragments[self.fragment_index]
Expand Down Expand Up @@ -237,7 +253,10 @@ def get_line_of_given_width(self, maximum_width):
) = current_line.automatic_break(self.justify)
self.character_index += 1
return line
return current_line.manual_break()
if not wordsplit:
line_full = True
break
return current_line.manual_break(self.justify)

current_line.add_character(
character,
Expand All @@ -250,5 +269,11 @@ def get_line_of_given_width(self, maximum_width):

self.character_index += 1

if line_full and not wordsplit:
# roll back and return empty line to trigger continuation
# on the next line.
self.fragment_index = last_fragment_index
self.character_index = last_character_index
return CurrentLine().manual_break(self.justify)
if current_line.width:
return current_line.manual_break()
2 changes: 1 addition & 1 deletion test/end_to_end_legacy/charmap/test_charmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_first_999_chars(font_filename, tmp_path):

# Create a PDF with the first 999 charters defined in the font:
for counter, character in enumerate(ttf.saveChar, 0):
pdf.write(8, f"{counter:03}) {character:03x} - {character:c}")
pdf.write(8, f"{counter:03}) {character:03x} - {character:c}", print_sh=True)
pdf.ln()
if counter >= 999:
break
Expand Down
Binary file modified test/outline/2_pages_outline.pdf
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
Binary file added test/text/render_styled_newpos.pdf
Binary file not shown.
5 changes: 4 additions & 1 deletion test/cells/test_cell.py → test/text/test_cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ def test_cell_missing_text_or_width(tmp_path):
pdf.set_font("Times", size=16)
with pytest.raises(ValueError) as error:
pdf.cell()
assert str(error.value) == "A 'txt' parameter must be provided if 'w' is None"
assert (
str(error.value)
== "A 'text_line' parameter with fragments must be provided if 'w' is None"
)


def test_cell_centering(tmp_path):
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
Loading