Skip to content

Commit

Permalink
column bottom balancing
Browse files Browse the repository at this point in the history
  • Loading branch information
gmischler committed Sep 17, 2023
1 parent e6ba857 commit 3bb29d4
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 99 deletions.
27 changes: 21 additions & 6 deletions docs/TextRegion.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ But it is possible to use them intermittingly. This will probably most often mak

The `FPDF.text_column() and ``FPDF.text_columns()` methods allow to create columnar layouts, with one or several columns respectively. Columns will always be of equal width.

#### Single-column example
#### Single-Column Example ####

In this example an inserted paragraph is used in order to format its content with justified alignment, while the rest of the text uses the default left alignment.

Expand All @@ -47,12 +47,12 @@ In this example an inserted paragraph is used in order to format its content wit
cols.write(txt=LOREM_IPSUM)
```

#### Multi-column example
#### Multi-Column Example

Here we have a layout with three columns. Note that font type and text size can be varied within a text region, while still maintaining the justified (in this case) horizontal alignment.

```python
cols = pdf.text_columns(align="J", ncols=3, gap_width=5)
cols = pdf.text_columns(align="J", ncols=3, gutter=5)
with cols:
cols.write(txt=LOREM_IPSUM)
pdf.set_font("Times", "", 8)
Expand All @@ -62,19 +62,34 @@ Here we have a layout with three columns. Note that font type and text size can
pdf.set_font("Helvetica", "", 12)
```

#### Balanced Columns

Normally the columns will be filled left to right, and if the text ends before the page is full, the rightmost column will end up shorter than the others.
If you prefer that all columns on a page end on the same height, you can use the `balanced=True` argument. In that case a simple algorithm will be applied that attempts to approximately balance their bottoms.

```python
with pdf.text_columns(align="J", ncols=3, gutter=5, balanced=True) as cols:
pdf.set_font("Times", "", 14)
cols.write(txt=LOREM_IPSUM[:300])
```
Note that this only works reliably when the font size (specifically the line height) doesn't change. If parts of the text use a larger or smaller font than the rest, then the balancing will usually be out of whack. Contributions for a more refined balancing algorithm are welcome.

### Possible future extensions

* Balanced columns, which all end on the same hight. Currently columns are filled to the maximum height from left to right.
Those features are currently not supported, but Pull Requests are welcome to implement them:

* Columns with differing widths (no balancing possible in this case).


## Paragraphs ##

The primary purpose of paragraphs is simply to enable variations in horizontal text alignment, while the horizontal extents of the text are managed by the text region.
The primary purpose of paragraphs is to enable variations in horizontal text alignment, while the horizontal extents of the text are managed by the text region.

Other than text regions, paragraphs should alway be used as context managers and never be reused. Violating those rules may result in the entered text turning up on the page out of sequence.

### Possible future extensions

* Setting the spacing at the top/bottom of paragraphs
Those features are currently not supported, but Pull Requests are welcome to implement them:

* Setting the spacing between paragraphs
* first-line indent
1 change: 0 additions & 1 deletion fpdf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,4 @@
# FPDF Constants
"FPDF_VERSION",
"FPDF_FONT_DIR",
"TextColumns",
]
28 changes: 17 additions & 11 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,18 @@ class Image:
from .fonts import CoreFont, CORE_FONTS, FontFace, TTFFont
from .graphics_state import GraphicsStateMixin
from .html import HTML2FPDF
from .text_region import TextRegionMixin, TextColumns
from .image_parsing import SUPPORTED_IMAGE_FILTERS, get_img_info, load_image
from .linearization import LinearizedOutputProducer
from .output import OutputProducer, PDFPage, ZOOM_CONFIGS
from .line_break import Fragment, MultiLineBreak, TextLine
from .outline import OutlineSection # , serialize_outline
from .output import OutputProducer, PDFPage, ZOOM_CONFIGS
from .recorder import FPDFRecorder
from .structure_tree import StructureTreeBuilder
from .sign import Signature
from .structure_tree import StructureTreeBuilder
from .svg import Percent, SVGObject
from .syntax import DestinationXYZ, PDFDate
from .table import Table
from .text_region import TextRegionMixin, TextColumns
from .util import get_scale_factor

# Public global variables:
Expand Down Expand Up @@ -3641,14 +3641,10 @@ def write(
normalized_string = self.normalize_text(txt).replace("\r", "")
styled_text_fragments = self._preload_font_styles(normalized_string, False)

def _get_width(height): # pylint: disable=unused-argument
# Set the width dynamically, since the first line can have a different width.
return max_width

text_lines = []
multi_line_break = MultiLineBreak(
styled_text_fragments,
_get_width,
lambda h: max_width,
print_sh=print_sh,
wrapmode=wrapmode,
)
Expand Down Expand Up @@ -3694,13 +3690,16 @@ def text_column(
align: Union[Align, str] = "LEFT",
l_margin: float = None,
r_margin: float = None,
print_sh: bool = False,
):
"""Establish a layout with a single column to fill with text.
Args:
text (str, optional): A first piece of text to insert.
align (Align or str, optional): The alignment of the region, default "LEFT".
l_margin (float, optional): Override the current left page margin.
r_margin (float, optional): Override the current right page margin.
print_sh (bool, optional): Treat a soft-hyphen (\\u00ad) as a printable
character, instead of a line breaking opportunity. Default value: False
"""
return TextColumns(
self,
Expand All @@ -3709,35 +3708,42 @@ def text_column(
align=align,
l_margin=l_margin,
r_margin=r_margin,
print_sh=print_sh,
)

@check_page
def text_columns(
self,
text: Optional[str] = None,
ncols: int = 2,
gap_width: float = 10,
gutter: float = 10,
balance: bool = False,
align: Union[Align, str] = "LEFT",
l_margin: float = None,
r_margin: float = None,
print_sh: bool = False,
):
"""Establish a layout with multiple columns to fill with text.
Args:
text (str, optional): A first piece of text to insert.
ncols (int, optional): the number of columns to create, default 2.
gap_width (float, optional): The distance between the columns, default 10.
gutter (float, optional): The distance between the columns, default 10.
align (Align or str, optional): The alignment of the region, default "LEFT".
l_margin (float, optional): Override the current left page margin.
r_margin (float, optional): Override the current right page margin.
print_sh (bool, optional): Treat a soft-hyphen (\\u00ad) as a printable
character, instead of a line breaking opportunity. Default value: False
"""
return TextColumns(
self,
text=text,
ncols=ncols,
gap_width=gap_width,
gutter=gutter,
balance=balance,
align=align,
l_margin=l_margin,
r_margin=r_margin,
print_sh=print_sh,
)

@check_page
Expand Down
Loading

0 comments on commit 3bb29d4

Please sign in to comment.