Skip to content

Commit 7c7f77e

Browse files
committed
html via text regions first round
1 parent 186521e commit 7c7f77e

23 files changed

+374
-226
lines changed

docs/TextRegion.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
# Text Flow Regions #
2+
_New in [:octicons-tag-24: 2.7.6](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)_
23

34
**Notice:** As of fpdf2 release 2.7.6, this is an experimental feature. Both the API and the functionality may change before it is finalized, without prior notice.
45

5-
Text regions are a hierarchy of classes that enable to flow text within a given outline. In the simplest case, it is just the running text column of a page. But it can also be a sequence of outlines, such as several parallel columns or the cells of a table. Other outlines may be combined by addition or subtraction to create more complex shapes.
6+
Text regions are a hierarchy of classes that enable to flow text within a given outline. In the simplest case, it is just the running text column of a page. But it can also be a sequence of outlines, such as several parallel columns or the cells of a table. Other outlines may be combined by addition or subtraction to create more complex shapes.
67

78
There are two general categories of regions. One defines boundaries for running text that will just continue in the same manner one the next page. Those include columns and tables. The second category are distinct shapes. Examples would be a circle, a rectangle, a polygon of individual shape or even an image. They may be used individually, in combination, or to modify the outline of a multipage column. Shape regions will typically not cause a page break when they are full. In the future, a possibility to chain them may be implemented, so that a new shape will continue with the text that didn't fit into the previous one.
89

910
## General Operation ##
1011

1112
Using the different region types and combination always follows the same pattern. The main difference to the normal `FPDF.write()` method is that all added text will first be buffered, and you need to explicitly trigger its rendering on the page. This is necessary so that text can be aligned within the given boundaries even if its font, style, or size are arbitrarily varied along the way.
1213

13-
* Create the region instance
14-
* If desired, add or subtract other shapes from it
15-
* Use the `.write()` method to feed text into its buffer
16-
* You can use the region instance as a context manager for filling, but you don't have to
17-
* When used as a context manager, you can change all text styling parameters within that context, and they will be used by the added text, but won't leak to the surroundings
18-
* For adding text with the already existing settings, just use the region instance as is.
14+
* Create the region instance with an `FPDF` method.
15+
* If desired, add or subtract other shapes from it (with geometric regions).
16+
* Use the `.write()` method to feed text into its buffer.
17+
* Best practise is to use the region instance as a context manager for filling.
18+
* Text will be rendered automatically after closing the context.
19+
* When used as a context manager, you can change all text styling parameters within that context, and they will be used by the added text, but won't leak to the surroundings
20+
* For adding text with the already existing settings, just use the region instance as is. In that case, you'll have to explicitly use the `render()` method.
1921
* Within a region, paragraphs can be inserted. The primary purpose of a paragraph is to apply a different horizontal alignment than the surrounding text.
20-
* Once all the desired text is collected to fill a shape or a set of columns, you can call its `.render()` method to actually do so.
21-
2222

2323
### Text Start Position ###
2424

@@ -30,6 +30,7 @@ When rendering, the vertical start position of the text will be at the lowest on
3030
Several region instances can exist at the same time. But only one of them can act as context manager at any given time. It is not currently possible to operate them recursively.
3131
But it is possible to use them intermittingly. This will probably most often make sense between a columnar region and a table. You may have some running text ending at a given height, then insert a table with data, and finally continue the running text at the new height below the table within the existing column.
3232

33+
3334
## Columns ##
3435

3536
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.
@@ -64,8 +65,8 @@ Here we have a layout with three columns. Note that font type and text size can
6465

6566
#### Balanced Columns
6667

67-
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.
68-
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.
68+
Normally the columns will be filled left to right, and if the text ends before the page is full, the rightmost column will be shorter than the others.
69+
If you prefer that all columns on a page end on the same height, you can use the `balance=True` argument. In that case a simple algorithm will be applied that attempts to approximately balance their bottoms.
6970

7071
```python
7172
with pdf.text_columns(align="J", ncols=3, gutter=5, balanced=True) as cols:

fpdf/fpdf.py

+21-7
Original file line numberDiff line numberDiff line change
@@ -2922,6 +2922,8 @@ def _render_styled_text_line(
29222922
current_text_mode = self.text_mode
29232923
current_font_stretching = self.font_stretching
29242924
current_char_spacing = self.char_spacing
2925+
fill_color_changed = False
2926+
last_used_color = self.fill_color
29252927
if text_line.fragments:
29262928
if text_line.align == Align.R:
29272929
dx = w - self.c_margin - styled_txt_width
@@ -2930,10 +2932,6 @@ def _render_styled_text_line(
29302932
else:
29312933
dx = self.c_margin
29322934
s_start += dx
2933-
2934-
if self.fill_color != self.text_color:
2935-
sl.append(self.text_color.serialize().lower())
2936-
29372935
word_spacing = 0
29382936
if text_line.align == Align.J and text_line.number_of_spaces:
29392937
word_spacing = (
@@ -2944,6 +2942,11 @@ def _render_styled_text_line(
29442942
f"{(self.h - self.y - 0.5 * h - 0.3 * max_font_size) * k:.2f} Td"
29452943
)
29462944
for i, frag in enumerate(text_line.fragments):
2945+
if frag.graphics_state["text_color"] != last_used_color:
2946+
# allow to change color within the line of text.
2947+
last_used_color = frag.graphics_state["text_color"]
2948+
sl.append(last_used_color.serialize().lower())
2949+
fill_color_changed = True
29472950
if word_spacing and frag.font_stretching != 100:
29482951
# Space character is already stretched, extra spacing is absolute.
29492952
frag_ws = word_spacing * 100 / frag.font_stretching
@@ -3044,7 +3047,7 @@ def _render_styled_text_line(
30443047
or current_font != self.current_font
30453048
or current_font_size_pt != self.font_size_pt
30463049
or current_text_mode != self.text_mode
3047-
or self.fill_color != self.text_color
3050+
or fill_color_changed
30483051
or current_font_stretching != self.font_stretching
30493052
or current_char_spacing != self.char_spacing
30503053
):
@@ -3065,7 +3068,10 @@ def _render_styled_text_line(
30653068
elif new_x == XPos.END:
30663069
self.x = s_start + s_width
30673070
elif new_x == XPos.WCONT:
3068-
self.x = s_start + s_width - self.c_margin
3071+
if s_width:
3072+
self.x = s_start + s_width - self.c_margin
3073+
else:
3074+
self.x = s_start
30693075
elif new_x == XPos.CENTER:
30703076
self.x = s_start + s_width / 2.0
30713077
elif new_x == XPos.LMARGIN:
@@ -3708,6 +3714,7 @@ def text_column(
37083714
l_margin: float = None,
37093715
r_margin: float = None,
37103716
print_sh: bool = False,
3717+
skip_leading_spaces: bool = False,
37113718
):
37123719
"""Establish a layout with a single column to fill with text.
37133720
Args:
@@ -3719,6 +3726,8 @@ def text_column(
37193726
r_margin (float, optional): Override the current right page margin.
37203727
print_sh (bool, optional): Treat a soft-hyphen (\\u00ad) as a printable
37213728
character, instead of a line breaking opportunity. Default value: False
3729+
skip_leading_spaces (bool, optional): On each line, any space characters
3730+
at the beginning will be skipped. Default value: False.
37223731
"""
37233732
return TextColumns(
37243733
self,
@@ -3729,6 +3738,7 @@ def text_column(
37293738
l_margin=l_margin,
37303739
r_margin=r_margin,
37313740
print_sh=print_sh,
3741+
skip_leading_spaces=skip_leading_spaces,
37323742
)
37333743

37343744
@check_page
@@ -3743,19 +3753,22 @@ def text_columns(
37433753
l_margin: float = None,
37443754
r_margin: float = None,
37453755
print_sh: bool = False,
3756+
skip_leading_spaces: bool = False,
37463757
):
37473758
"""Establish a layout with multiple columns to fill with text.
37483759
Args:
37493760
text (str, optional): A first piece of text to insert.
37503761
ncols (int, optional): the number of columns to create, default 2.
3751-
gutter (float, optional): The distance between the columns, default 10.
3762+
gutter (float, optional): The distance between the columns, default 10 mm.
37523763
align (Align or str, optional): The alignment of the region, default "LEFT".
37533764
line_height (float, optional): A multiplier relative to the font
37543765
size changing the vertical space occupied by a line of text. Default 1.0.
37553766
l_margin (float, optional): Override the current left page margin.
37563767
r_margin (float, optional): Override the current right page margin.
37573768
print_sh (bool, optional): Treat a soft-hyphen (\\u00ad) as a printable
37583769
character, instead of a line breaking opportunity. Default value: False
3770+
skip_leading_spaces (bool, optional): On each line, any space characters
3771+
at the beginning will be skipped. Default value: False.
37593772
"""
37603773
return TextColumns(
37613774
self,
@@ -3768,6 +3781,7 @@ def text_columns(
37683781
l_margin=l_margin,
37693782
r_margin=r_margin,
37703783
print_sh=print_sh,
3784+
skip_leading_spaces=skip_leading_spaces,
37713785
)
37723786

37733787
@check_page

fpdf/graphics_state.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ def __init__(self, *args, **kwargs):
5757
]
5858
super().__init__(*args, **kwargs)
5959

60-
def _push_local_stack(self):
61-
self.__statestack.append(self.__statestack[-1].copy())
60+
def _push_local_stack(self, new=None):
61+
if new:
62+
self.__statestack.append(new)
63+
else:
64+
self.__statestack.append(self.__statestack[-1].copy())
6265

6366
def _pop_local_stack(self):
64-
del self.__statestack[-1]
67+
return self.__statestack.pop()
6568

6669
def _get_current_graphics_state(self):
6770
return self.__statestack[-1].copy()

0 commit comments

Comments
 (0)