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

Allowing to set padding in tables and multi_cell + vertical alignment in tables #797

Merged
merged 103 commits into from
Sep 23, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
b3ac854
added padding to multi_cell
RubendeBruin Jun 1, 2023
d42aa2a
fixed border for border=1,
RubendeBruin Jun 1, 2023
f4d13cf
implemented in table
RubendeBruin Jun 1, 2023
6cf7afb
decoupled drawing of borders/fill and text
RubendeBruin Jun 2, 2023
a575190
works
RubendeBruin Jun 2, 2023
18abfbc
fill now behind image
RubendeBruin Jun 2, 2023
56abc6c
only-images table works
RubendeBruin Jun 2, 2023
c63b4b4
and per-cell padding as well.
RubendeBruin Jun 2, 2023
88d21ab
Added v_align option and alignV enum
RubendeBruin Jun 2, 2023
c6e8994
Added docs in Tables.md
RubendeBruin Jun 2, 2023
bd86201
Blacked
RubendeBruin Jun 2, 2023
99d8d31
Added borders_outside_width option for tables
RubendeBruin Jun 5, 2023
7b122a3
Fixed padding
RubendeBruin Jun 5, 2023
0a65382
Update fpdf/fpdf.py
RubendeBruin Jun 6, 2023
cce511b
Update fpdf/fpdf.py
RubendeBruin Jun 6, 2023
1e98c3f
Update fpdf/fpdf.py
RubendeBruin Jun 6, 2023
544bc67
Update fpdf/fpdf.py
RubendeBruin Jun 6, 2023
64fdd93
Update fpdf/fpdf.py
RubendeBruin Jun 6, 2023
06745d7
Update fpdf/fpdf.py
RubendeBruin Jun 6, 2023
e34f5b4
Update fpdf/table.py
RubendeBruin Jun 6, 2023
9aa7405
Update fpdf/table.py
RubendeBruin Jun 6, 2023
b52ed25
Update fpdf/table.py
RubendeBruin Jun 6, 2023
ae23e6f
Incorporating comments
RubendeBruin Jun 6, 2023
3a78bbc
Merge remote-tracking branch 'fork/padding' into padding
RubendeBruin Jun 6, 2023
c1899fd
Merge branch 'padding_and_border' into padding
RubendeBruin Jun 6, 2023
ad0078b
Incorporating comments
RubendeBruin Jun 6, 2023
66a44dd
Incorporating comments
RubendeBruin Jun 6, 2023
9deb86c
Docs and tests for colspan
RubendeBruin Jun 7, 2023
8ba2899
Merge branch 'test_colspan' into padding
RubendeBruin Jun 7, 2023
6952bdf
wip
RubendeBruin Jun 7, 2023
e317928
Update fpdf/table.py
RubendeBruin Jun 7, 2023
c54e35b
Merge remote-tracking branch 'fork/padding' into padding
RubendeBruin Jun 7, 2023
611a8d2
Added c_margin and docs
RubendeBruin Jun 7, 2023
6446e15
Added c_margin and docs and docstrings
RubendeBruin Jun 7, 2023
90e6ad7
Merge branch 'master' into padding
RubendeBruin Jul 20, 2023
78ad49b
Merged with master
RubendeBruin Jul 20, 2023
d6cbb13
wip
RubendeBruin Jul 20, 2023
c61920b
wip
RubendeBruin Jul 20, 2023
29621df
wip
RubendeBruin Jul 20, 2023
7f419a7
Gutters and colspan now works
RubendeBruin Jul 20, 2023
8b6b693
Gutters and colspan now works
RubendeBruin Jul 21, 2023
d77e88a
fixed style bug
RubendeBruin Jul 21, 2023
11cb956
fixed draw_box borders
RubendeBruin Jul 21, 2023
ad6401b
Added tests for table_padding
RubendeBruin Jul 21, 2023
af2659c
Added rendered heigh to RowLayoutInfo to avoid having to call multice…
RubendeBruin Jul 21, 2023
21587ac
Added reference pdfs
RubendeBruin Jul 21, 2023
e408cf7
Renamed AlignV to VAlign (also affects test pdfs where name is printed)
RubendeBruin Jul 21, 2023
ca06b13
and docs
RubendeBruin Jul 21, 2023
c1c31a1
and docs
RubendeBruin Jul 21, 2023
780e56e
Removed a commented generate=True
RubendeBruin Jul 28, 2023
df484ee
Fixes for static code check
RubendeBruin Jul 28, 2023
b2e00c1
Fixes for static code check
RubendeBruin Jul 28, 2023
d78b0c6
Fixes for static code check
RubendeBruin Jul 28, 2023
5af74eb
Ok, I'm now starting to rewrite code in a BAD way just to pass the tests
RubendeBruin Jul 28, 2023
69fabe0
ok, nosemgrep
RubendeBruin Jul 28, 2023
7bb9cfc
trying nosemgrep again
RubendeBruin Jul 28, 2023
8fb862e
trying nosemgrep again
RubendeBruin Jul 28, 2023
b8d7a8e
black
RubendeBruin Jul 28, 2023
88b3295
# pylint: disable=protected-access
RubendeBruin Aug 1, 2023
8322730
Merge branch 'master' into padding
RubendeBruin Aug 15, 2023
8637289
Updated the reference PDFs for "table" tests
RubendeBruin Aug 15, 2023
4886777
Image size adjustments to fit in cell
RubendeBruin Aug 15, 2023
57548a6
Updated HTML table reference PDFs
RubendeBruin Aug 15, 2023
421e1ac
Fixed multi_cell
RubendeBruin Aug 15, 2023
be7ec89
pylint complaints
RubendeBruin Aug 15, 2023
ae97385
black complaints
RubendeBruin Aug 15, 2023
18c333f
changed vertical alignment implemented
RubendeBruin Aug 15, 2023
e56a3cd
Pre-computing the column widths for speedup
RubendeBruin Aug 21, 2023
2915bd9
Update docs/Tables.md
RubendeBruin Aug 21, 2023
82edd68
Update docs/Tables.md
RubendeBruin Aug 21, 2023
22863e7
Update docs/Tables.md
RubendeBruin Aug 21, 2023
6c3a6dc
Update docs/Tables.md
RubendeBruin Aug 21, 2023
3328cf4
Update docs/Tables.md
RubendeBruin Aug 21, 2023
2307264
Update docs/Tables.md
RubendeBruin Aug 21, 2023
089b337
Changelog
RubendeBruin Aug 21, 2023
5335fb9
Merge remote-tracking branch 'fork/padding' into padding
RubendeBruin Aug 21, 2023
00ce643
Merge branch 'master' into padding
RubendeBruin Aug 21, 2023
7849132
Update fpdf/fpdf.py
RubendeBruin Aug 21, 2023
102f8ce
removed padding=0 from tests
RubendeBruin Aug 21, 2023
3a4d88d
Merge remote-tracking branch 'fork/padding' into padding
RubendeBruin Aug 21, 2023
dbd4450
replaced local_c_margin with horizontal_margin as per earlier convers…
RubendeBruin Aug 21, 2023
11710dc
removed logging import (unused)
RubendeBruin Aug 21, 2023
2b51486
replaced local_c_margin with horizontal_margin as per earlier convers…
RubendeBruin Sep 12, 2023
d73aa19
Updated some of the reference pdfs
RubendeBruin Sep 12, 2023
4aff223
merged origin/master
RubendeBruin Sep 12, 2023
aeeaa17
Updated reference PDFs for HTML table tests - final render look ident…
RubendeBruin Sep 12, 2023
406c4d4
Black
RubendeBruin Sep 12, 2023
07b445a
Updated two more reference pdfs with tables (no visual changes)
RubendeBruin Sep 12, 2023
5aae1a9
Update fpdf/table.py
RubendeBruin Sep 12, 2023
db2bfd8
Update fpdf/table.py
RubendeBruin Sep 12, 2023
78b7ecd
Comments from Lucas' review
RubendeBruin Sep 12, 2023
5c4900d
Black
RubendeBruin Sep 12, 2023
c4c7c20
Merge branch 'py-pdf:master' into padding
RubendeBruin Sep 12, 2023
96c5897
Updated reference pdf
RubendeBruin Sep 12, 2023
80a2aa1
removed table_colspan_and_gutter.pdf
RubendeBruin Sep 12, 2023
c7b86bb
removed more obsolete reference pdfs
RubendeBruin Sep 12, 2023
f7aae69
Revert "removed more obsolete reference pdfs"
RubendeBruin Sep 12, 2023
66cdd7e
Replaced "Center" with "Middle" for vertical alignment in tables to b…
RubendeBruin Sep 17, 2023
6607dc3
And updated the default value as well
RubendeBruin Sep 17, 2023
01f8909
Merge remote-tracking branch 'origin/master' into padding
RubendeBruin Sep 22, 2023
ac069a3
And updated the default value as well
RubendeBruin Sep 22, 2023
1ffc9a3
Re-generated the pdfs of HTML tests with tables
RubendeBruin Sep 22, 2023
930cdae
Update Tables.md
RubendeBruin Sep 22, 2023
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
39 changes: 39 additions & 0 deletions docs/Tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,45 @@ Result:

![](table_align.jpg)

## Setting cell padding

RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
Cell padding (the space between the cells content and the edge of the cell) can be set globally or on a per-cell basis.

Following the CCS standard the padding can be specified using 1,2 3 or 4 values.
- When one value is specified, it applies the same padding to all four sides.
- When two values are specified, the first padding applies to the top and bottom, the second to the left and right.
- When three values are specified, the first padding applies to the top, the second to the right and left, the third to the bottom.
- When four values are specified, the paddings apply to the top, right, bottom, and left in that order (clockwise)

```python
...
style = FontFace(color=black, fill_color=red)
with pdf.table(line_height = pdf.font_size,padding=2) as table:
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
for irow in range(5):
row = table.row()
for icol in range(5):
datum = "Circus"
if irow == 3 and icol %2 == 0:
row.cell("custom padding", style=style, padding = (2*icol,8,8,8))
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
else:
row.cell(datum)
```
(also an example of coloring individual cells)

![img.png](img_table_padding.png)

## Setting vertical alignment of text in cells

RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
Can be set globally or per cell.
Works the same way as padding, but with the `v_align` parameter.

```python

with pdf.table(v_align = AlignV.C) as table:
...
row.cell(f"custom v-align" v_align = AlignV.T) # <-- align to top
```

## Setting row height
```python
...
Expand Down
Binary file added docs/img_table_padding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,25 @@ def coerce(cls, value):
return cls.L
return super(cls, cls).coerce(value)

class AlignV(CoerciveEnum):
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
"""Defines how to vertically render text in a cell.
Default value is CENTER"""

C = intern("CENTER")
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
"Center text vertically"

T = intern("TOP")
"Place text at the top of the cell, but obey the cells padding"

B = intern("BOTTOM")
"Place text at the bottom of the cell, but obey the cells padding"

@classmethod
def coerce(cls, value):
if value == "":
return cls.C
return super(cls, cls).coerce(value)


class TextEmphasis(CoerciveIntFlag):
"""
Expand Down
127 changes: 101 additions & 26 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,22 @@ def get_page_format(format, k=None):
raise FPDFPageFormatException(f"Arguments must be numbers: {args}") from e


def get_padding_tuple(padding):
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
"""Return a 4-tuple of padding values from a single value or a 2, 3 or 4-tuple according to CSS rules"""
if isinstance(padding, (int, float)):
return (padding, padding, padding, padding)
elif len(padding) == 2:
return (padding[0], padding[1], padding[0], padding[1])
elif len(padding) == 3:
return (padding[0], padding[1], padding[2], padding[1])
elif len(padding) == 4:
return padding

raise ValueError(
f"padding shall be a number or a sequence of 2, 3 or 4 numbers, got {str(padding)}"
)


def check_page(fn):
"""Decorator to protect drawing methods"""

Expand Down Expand Up @@ -1413,6 +1429,42 @@ def _draw_rounded_rect(self, x, y, w, h, style, round_corners, r):
self.line(point_5[0], point_5[1], point_6[0], point_6[1])
self.line(point_7[0], point_7[1], point_8[0], point_8[1])

def _draw_box(self, x1, y1, x2, y2, border, fill):
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
"""Draws a box using the current style"""

sl = []

k = self.k

# y top to bottom instead of bottom to top
y1 = self.h - y1
y2 = self.h - y2

# scale
x1 *= k
x2 *= k
y2 *= k
y1 *= k

if fill:
op = "B" if border == 1 else "f"
sl.append(f"{x1:.2f} {y2:.2f} " f"{x2 - x1:.2f} {y1 - y2:.2f} re {op}")
elif border == 1:
sl.append(f"{x1:.2f} {y2:.2f} " f"{x2 - x1:.2f} {y1 - y2:.2f} re S")

if isinstance(border, str):
if "L" in border:
sl.append(f"{x1:.2f} {y2:.2f} m " f"{x1:.2f} {y1:.2f} l S")
if "T" in border:
sl.append(f"{x1:.2f} {y2:.2f} m " f"{x2:.2f} {y2:.2f} l S")
if "R" in border:
sl.append(f"{x2:.2f} {y2:.2f} m " f"{x2:.2f} {y1:.2f} l S")
if "B" in border:
sl.append(f"{x1:.2f} {y1:.2f} m " f"{x2:.2f} {y1:.2f} l S")

s = " ".join(sl)
self._out(s)

@check_page
def ellipse(self, x, y, w, h, style=None):
"""
Expand Down Expand Up @@ -2857,6 +2909,7 @@ def _render_styled_text_line(
fill: bool = False,
link: str = "",
center: bool = False,
padding=(0, 0, 0, 0),
):
"""
Prints a cell (rectangular area) with optional borders, background color and
Expand Down Expand Up @@ -2931,45 +2984,38 @@ def _render_styled_text_line(
self.x = self.l_margin + (self.epw - w) / 2
page_break_triggered = self._perform_page_break_if_need_be(h)
sl = []

k = self.k
# pylint: disable=invalid-unary-operand-type
# "h" can't actually be None

# pre-calc border edges with padding

left = (self.x - padding[3]) * k
right = (self.x + w + padding[1]) * k
top = (self.h - self.y + padding[0]) * k
bottom = (self.h - (self.y + h) - padding[2]) * k

if fill:
op = "B" if border == 1 else "f"
sl.append(
f"{self.x * k:.2f} {(self.h - self.y) * k:.2f} "
f"{w * k:.2f} {-h * k:.2f} re {op}"
f"{left:.2f} {top:.2f} " f"{right-left:.2f} {bottom-top:.2f} re {op}"
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
)
elif border == 1:
sl.append(
f"{self.x * k:.2f} {(self.h - self.y) * k:.2f} "
f"{w * k:.2f} {-h * k:.2f} re S"
f"{left:.2f} {top:.2f} " f"{right-left:.2f} {bottom-top:.2f} re S"
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
)
# pylint: enable=invalid-unary-operand-type

if isinstance(border, str):
x = self.x
y = self.y
if "L" in border:
sl.append(
f"{x * k:.2f} {(self.h - y) * k:.2f} m "
f"{x * k:.2f} {(self.h - (y + h)) * k:.2f} l S"
)
sl.append(f"{left:.2f} {top:.2f} m " f"{left:.2f} {bottom:.2f} l S")
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
if "T" in border:
sl.append(
f"{x * k:.2f} {(self.h - y) * k:.2f} m "
f"{(x + w) * k:.2f} {(self.h - y) * k:.2f} l S"
)
sl.append(f"{left:.2f} {top:.2f} m " f"{right:.2f} {top:.2f} l S")
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
if "R" in border:
sl.append(
f"{(x + w) * k:.2f} {(self.h - y) * k:.2f} m "
f"{(x + w) * k:.2f} {(self.h - (y + h)) * k:.2f} l S"
)
sl.append(f"{right:.2f} {top:.2f} m " f"{right:.2f} {bottom:.2f} l S")
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
if "B" in border:
sl.append(
f"{x * k:.2f} {(self.h - (y + h)) * k:.2f} m "
f"{(x + w) * k:.2f} {(self.h - (y + h)) * k:.2f} l S"
)
sl.append(f"{left:.2f} {bottom:.2f} m " f"{right:.2f} {bottom:.2f} l S")
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved

if self._record_text_quad_points:
self._add_quad_points(self.x, self.y, w, h)
Expand Down Expand Up @@ -3419,6 +3465,8 @@ def multi_cell(
wrapmode: WrapMode = WrapMode.WORD,
dry_run=False,
output=MethodReturnValue.PAGE_BREAK,
padding=0,
# cell_height = None,
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
):
"""
This method allows printing text with line breaks. They can be automatic
Expand All @@ -3430,7 +3478,7 @@ def multi_cell(

Args:
w (float): cell width. If 0, they extend up to the right margin of the page.
h (float): cell height. Default value: None, meaning to use the current font size.
h (float): row height. Default value: None, meaning to use the current font size.
RubendeBruin marked this conversation as resolved.
Show resolved Hide resolved
txt (str): string to print.
border: Indicates if borders must be drawn around the cell.
The value can be either a number (`0`: no border ; `1`: frame)
Expand Down Expand Up @@ -3461,12 +3509,21 @@ def multi_cell(
Can be useful when combined with `output`.
output (fpdf.enums.MethodReturnValue): defines what this method returns.
If several enum values are joined, the result will be a tuple.
padding (float or Sequence): padding to apply around the text. Default value: 0.
When one value is specified, it applies the same padding to all four sides.
When two values are specified, the first padding applies to the top and bottom, the second to the left and right.
When three values are specified, the first padding applies to the top, the second to the right and left, the third to the bottom.
When four values are specified, the paddings apply to the top, right, bottom, and left in that order (clockwise)


Using `new_x=XPos.RIGHT, new_y=XPos.TOP, maximum height=pdf.font_size` is
useful to build tables with multiline text in cells.

Returns: a single value or a tuple, depending on the `output` parameter value
"""

padding = get_padding_tuple(padding)

if split_only:
warnings.warn(
# pylint: disable=implicit-str-concat
Expand Down Expand Up @@ -3495,6 +3552,7 @@ def multi_cell(
dry_run=False,
split_only=False,
output=MethodReturnValue.LINES if split_only else output,
padding=padding,
)
wrapmode = WrapMode.coerce(wrapmode)
if isinstance(w, str) or isinstance(h, str):
Expand Down Expand Up @@ -3538,18 +3596,27 @@ def multi_cell(

if h is None:
h = self.font_size

# If width is 0, set width to available width between margins
if w == 0:
w = self.w - self.r_margin - self.x

prev_x, prev_y = self.x, self.y

# Apply padding to contents
# decrease maximum allowed width by padding
# shift the starting point by padding
w = w - padding[1] - padding[3]
maximum_allowed_width = w - 2 * self.c_margin
self.x += padding[3]
self.y += padding[0]

# Calculate text length
txt = self.normalize_text(txt)
normalized_string = txt.replace("\r", "")
styled_text_fragments = self._preload_font_styles(normalized_string, markdown)

prev_font_style, prev_underline = self.font_style, self.underline
prev_x, prev_y = self.x, self.y
total_height = 0

if not border:
Expand Down Expand Up @@ -3612,12 +3679,13 @@ def multi_cell(
align=Align.L if (align == Align.J and is_last_line) else align,
fill=fill,
link=link,
padding=padding,
)
page_break_triggered = page_break_triggered or new_page
total_height += current_cell_height
if not is_last_line and align == Align.X:
# prevent cumulative shift to the left
self.x = prev_x
self.x = prev_x + padding[3]
if should_render_bottom_blank_cell:
new_page = self._render_styled_text_line(
TextLine(
Expand All @@ -3640,6 +3708,7 @@ def multi_cell(
new_y=new_y,
fill=fill,
link=link,
padding=padding,
)
page_break_triggered = page_break_triggered or new_page
if new_page and new_y == YPos.TOP:
Expand All @@ -3654,13 +3723,18 @@ def multi_cell(

if new_y == YPos.TOP: # We may have jumped a few lines -> reset
self.y = prev_y
elif new_y == YPos.NEXT: # move down by bottom padding
self.y += padding[2]

if markdown:
if self.font_style != prev_font_style:
self.font_style = prev_font_style
self.current_font = self.fonts[self.font_family + self.font_style]
self.underline = prev_underline

# move right by right padding
self.x += padding[1]

output = MethodReturnValue.coerce(output)
return_value = ()
if output & MethodReturnValue.PAGE_BREAK:
Expand All @@ -3674,7 +3748,7 @@ def multi_cell(
output_lines.append("".join(characters))
return_value += (output_lines,)
if output & MethodReturnValue.HEIGHT:
return_value += (total_height,)
return_value += (total_height + padding[0] + padding[2],)
if len(return_value) == 1:
return return_value[0]
return return_value
Expand Down Expand Up @@ -4814,6 +4888,7 @@ def table(self, *args, **kwargs):
width (number): optional. Sets the table width
wrapmode (fpdf.enums.WrapMode): "WORD" for word based line wrapping (default),
"CHAR" for character based line wrapping.
padding (number, tuple): optional. Sets the cell padding. Can be a single number or a sequence of numbers (top, right, bottom, left) using CSS convention
"""
table = Table(self, *args, **kwargs)
yield table
Expand Down
Loading