Skip to content

Commit

Permalink
Implementing table centering
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C committed Feb 24, 2023
1 parent 5cc7540 commit dff0ba2
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 15 deletions.
14 changes: 10 additions & 4 deletions docs/Tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ Result:
![](table-simple.jpg)

## Features
* support cells with content wrapping over several lines
* control over column & row sizes (automatically computed by default)
* allow to style table headings, or disable them
* allow to style table headings (top row), or disable them
* control over borders: color, width & where they are drawn
* handle splitting a table over page breaks, with headings repeated
* control over cell background color

* control table width & position
* control over text alignment in cells, globally or per row
* allow to embed images in cells

## Setting table & column widths
Expand All @@ -47,16 +50,19 @@ Result:

![](table-with-fixed-column-widths.jpg)

`table.align` can be used to set the table horizontal position relative to the page,
when it's not using the full page width. It's centered by default.

## Setting text alignment
This can be set globally, or on a per-column basis:
```python
...
with pdf.table() as table:
table.align = "CENTER"
table.text_align = "CENTER"
...
pdf.ln()
with pdf.table() as table:
table.align = ("CENTER", "CENTER", "RIGHT", "LEFT")
table.text_align = ("CENTER", "CENTER", "RIGHT", "LEFT")
...
```
Result:
Expand Down
Binary file modified docs/table-with-fixed-column-widths.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 36 additions & 4 deletions fpdf/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ class Table:
def __init__(self, fpdf):
self._fpdf = fpdf
self._rows = []
self.align = "LEFT"
"Control text alignment inside cells"
self.align = "CENTER"
"""
Sets the table horizontal position relative to the page,
when it's not using the full page width
"""
self.borders_layout = TableBordersLayout.ALL
"Control what cell borders are drawn"
self.cell_fill_color = None
Expand All @@ -31,6 +34,8 @@ def __init__(self, fpdf):
"Defines the visual style of the top headings row: size, color, emphasis..."
self.line_height = 2 * fpdf.font_size
"Defines how much vertical space a line of text will occupy"
self.text_align = "JUSTIFY"
"Control text alignment inside cells. Justify by default"
self.width = fpdf.epw
"Sets the table width"

Expand All @@ -43,6 +48,22 @@ def row(self):

def render(self):
"This is an internal method called by `FPDF.table()` once the table is finished"
if self.width > self._fpdf.epw:
raise ValueError(
f"Invalid value provided .width={self.width}: effective page width is {self._fpdf.epw}"
)
table_align = Align.coerce(self.align)
if table_align == Align.J:
raise ValueError("JUSTIFY is an invalid value for table .align")
prev_l_margin = self._fpdf.l_margin
if table_align == Align.C:
self._fpdf.l_margin = (self._fpdf.w - self.width) / 2
self._fpdf.x = self._fpdf.l_margin
elif table_align == Align.R:
self._fpdf.l_margin = self._fpdf.w - self.width
self._fpdf.x = self._fpdf.l_margin
elif self._fpdf.x != self._fpdf.l_margin:
self._fpdf.l_margin = self._fpdf.x
for i in range(len(self._rows)):
with self._fpdf.offset_rendering() as test:
self._render_table_row_styled(i)
Expand All @@ -59,6 +80,8 @@ def render(self):
self._render_table_row_styled(i)
if prev_fill_color:
self._fpdf.set_fill_color(prev_fill_color)
self._fpdf.l_margin = prev_l_margin
self._fpdf.x = self._fpdf.l_margin

def get_cell_border(self, i, j):
"""
Expand Down Expand Up @@ -159,14 +182,18 @@ def _render_table_cell(
self._fpdf.set_xy(x, y)
if not fill:
fill = self.cell_fill_color and self.cell_fill_logic(i, j)
align = self.align if isinstance(self.align, (Align, str)) else self.align[j]
text_align = (
self.text_align
if isinstance(self.text_align, (Align, str))
else self.text_align[j]
)
lines = self._fpdf.multi_cell(
w=col_width,
h=row_height,
txt=cell.text or "",
max_line_height=cell_line_height,
border=self.get_cell_border(i, j),
align=align,
align=text_align,
new_x="RIGHT",
new_y="TOP",
fill=fill,
Expand Down Expand Up @@ -224,6 +251,11 @@ def cell(self, text=None, img=None, img_fill_width=False):
img_fill_width (bool): optional, defaults to False. Indicates to render the image
using the full width of the current table column.
"""
if text and img:
raise NotImplementedError(
"fpdf2 currently does not support inserting text with an image in the same table cell."
"Pull Requests are welcome to implement this 😊"
)
self.cells.append(Cell(text, img, img_fill_width))


Expand Down
Binary file modified test/table/table_with_fixed_width.pdf
Binary file not shown.
File renamed without changes.
17 changes: 15 additions & 2 deletions test/table/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@ def test_table_with_fixed_width(tmp_path):
assert_pdf_equal(pdf, HERE / "table_with_fixed_width.pdf", tmp_path)


def test_table_with_invalid_width():
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pytest.raises(ValueError):
with pdf.table() as table:
table.width = 200
for data_row in TABLE_DATA:
with table.row() as row:
for datum in data_row:
row.cell(datum)


def test_table_without_headings(tmp_path):
pdf = FPDF()
pdf.add_page()
Expand Down Expand Up @@ -267,14 +280,14 @@ def test_table_align(tmp_path):
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table() as table:
table.align = "CENTER"
table.text_align = "CENTER"
for data_row in TABLE_DATA:
with table.row() as row:
for datum in data_row:
row.cell(datum)
pdf.ln()
with pdf.table() as table:
table.align = ("CENTER", "CENTER", "RIGHT", "LEFT")
table.text_align = ("CENTER", "CENTER", "RIGHT", "LEFT")
for data_row in TABLE_DATA:
with table.row() as row:
for datum in data_row:
Expand Down
26 changes: 21 additions & 5 deletions test/table/test_table_with_image.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from pathlib import Path

import pytest

from fpdf import FPDF
from test.conftest import assert_pdf_equal, LOREM_IPSUM


HERE = Path(__file__).resolve().parent
IMG_DIR = HERE.parent / "image"

Expand Down Expand Up @@ -38,7 +39,7 @@
)


def test_table_with_an_image(tmp_path):
def test_table_with_images(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
Expand All @@ -50,10 +51,10 @@ def test_table_with_an_image(tmp_path):
row.cell(img=datum)
else:
row.cell(datum)
assert_pdf_equal(pdf, HERE / "table_with_an_image.pdf", tmp_path)
assert_pdf_equal(pdf, HERE / "table_with_images.pdf", tmp_path)


def test_table_with_an_image_and_img_fill_width(tmp_path):
def test_table_with_images_and_img_fill_width(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
Expand All @@ -67,7 +68,7 @@ def test_table_with_an_image_and_img_fill_width(tmp_path):
row.cell(datum)
assert_pdf_equal(
pdf,
HERE / "table_with_an_image_and_img_fill_width.pdf",
HERE / "table_with_images_and_img_fill_width.pdf",
tmp_path,
)

Expand All @@ -85,3 +86,18 @@ def test_table_with_multiline_cells_and_images(tmp_path):
else:
row.cell(datum)
assert_pdf_equal(pdf, HERE / "table_with_multiline_cells_and_images.pdf", tmp_path)


def test_table_with_images_and_text():
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pytest.raises(NotImplementedError):
with pdf.table() as table:
for i, data_row in enumerate(TABLE_DATA):
with table.row() as row:
for j, datum in enumerate(data_row):
if j == 2 and i > 0:
row.cell(datum.name, img=datum)
else:
row.cell(datum)

0 comments on commit dff0ba2

Please sign in to comment.