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

Added Cell level control for Table Borders #1285

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',

## [2.8.2] - Not released yet
### Added
* new optional parameter `border` for table cells [issue #1192](https://github.com/py-pdf/fpdf2/issues/1192) users can define specific borders (left, right, top, bottom) for individual cells
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): now parses `<title>` tags to set the [document title](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.set_title). By default, it is added as PDF metadata, but not rendered in the document body. However, this can be enabled by passing `render_title_tag=True` to `FPDF.write_html()`.
* support for LZWDecode compression [issue #1271](https://github.com/py-pdf/fpdf2/issues/1271)
### Fixed
Expand Down
63 changes: 63 additions & 0 deletions docs/Tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Result:
* 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 over cell borders
* control table width & position
* control over text alignment in cells, globally or per row
* allow to embed images in cells
Expand Down Expand Up @@ -277,6 +278,68 @@ Result:
All the possible layout values are described
there: [`TableBordersLayout`](https://py-pdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.TableBordersLayout).

## Set cell borders

_New in [:octicons-tag-24: 2.8.2](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)_

```python
from fpdf import FPDF

pdf = FPDF()
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table() as table:
for data_row in TABLE_DATA:
row = table.row()
for datum in data_row:
row.cell(datum, border="LEFT")
pdf.output('table.pdf')
```

Result:

![](table_with_cell_border_left.jpg)

```python
from fpdf import FPDF

pdf = FPDF()
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table() as table:
for data_row in TABLE_DATA:
row = table.row()
for datum in data_row:
row.cell(datum, border="TOP")
pdf.output('table.pdf')
```

Result:

![](table_with_cell_border_top.jpg)

```python
from fpdf import FPDF
from fpdf.enums import CellBordersLayout

pdf = FPDF()
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table() as table:
for data_row in TABLE_DATA:
row = table.row()
for datum in data_row:
row.cell(datum, border=CellBordersLayout.TOP | CellBordersLayout.LEFT)
pdf.output('table.pdf')
```

Result:

![](table_with_cell_border_left_top.jpg)

All the possible borders values are described there: [`CellBordersLayout`](https://py-pdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.CellBordersLayout).


## Insert images

```python
Expand Down
Binary file added docs/table_with_cell_border_left.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/table_with_cell_border_left_top.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/table_with_cell_border_top.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,74 @@ class TableBordersLayout(CoerciveEnum):
"Draw only the top horizontal border, below the headings"


class CellBordersLayout(CoerciveIntFlag):
"""Defines how to render cell borders in table

The integer value of `border` determines which borders are applied. Below are some common examples:

- border=1 (LEFT): Only the left border is enabled.
- border=3 (LEFT | RIGHT): Both the left and right borders are enabled.
- border=5 (LEFT | TOP): The left and top borders are enabled.
- border=12 (TOP | BOTTOM): The top and bottom borders are enabled.
- border=15 (ALL): All borders (left, right, top, bottom) are enabled.
- border=16 (INHERIT): Inherit the border settings from the parent element.

Using `border=3` will combine LEFT and RIGHT borders, as it represents the
bitwise OR of `LEFT (1)` and `RIGHT (2)`.
"""

NONE = 0
"Draw no border on any side of cell"

LEFT = 1
"Draw border on the left side of the cell"

RIGHT = 2
"Draw border on the right side of the cell"

TOP = 4
"Draw border on the top side of the cell"

BOTTOM = 8
"Draw border on the bottom side of the cell"

ALL = LEFT | RIGHT | TOP | BOTTOM
"Draw border on all side of the cell"

INHERIT = 16
"Inherits the border layout from the table borders layout"

@classmethod
def coerce(cls, value):
if isinstance(value, int) and value > 16:
raise ValueError("INHERIT cannot be combined with other values")
return super().coerce(value)

def __and__(self, value):
value = super().__and__(value)
if value > 16:
raise ValueError("INHERIT cannot be combined with other values")
return value

def __or__(self, value):
value = super().__or__(value)
if value > 16:
raise ValueError("INHERIT cannot be combined with other values")
return value

def __str__(self):
border_str = []
if self & CellBordersLayout.LEFT:
border_str.append("L")
if self & CellBordersLayout.RIGHT:
border_str.append("R")
if self & CellBordersLayout.TOP:
border_str.append("T")
if self & CellBordersLayout.BOTTOM:
border_str.append("B")
return "".join(border_str) if border_str else "NONE"


class TableCellFillMode(CoerciveEnum):
"Defines which table cells to fill"

Expand Down
11 changes: 11 additions & 0 deletions fpdf/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
WrapMode,
VAlign,
TableSpan,
CellBordersLayout,
)
from .errors import FPDFException
from .fonts import CORE_FONTS, FontFace
Expand Down Expand Up @@ -251,13 +252,18 @@ def render(self):
self._fpdf.l_margin = prev_l_margin
self._fpdf.x = self._fpdf.l_margin

# pylint: disable=too-many-return-statements
def get_cell_border(self, i, j, cell):
"""
Defines which cell borders should be drawn.
Returns a string containing some or all of the letters L/R/T/B,
to be passed to `fpdf.FPDF.multi_cell()`.
Can be overriden to customize this logic
"""

if cell.border != CellBordersLayout.INHERIT:
return str(cell.border)

if self._borders_layout == TableBordersLayout.ALL:
return 1
if self._borders_layout == TableBordersLayout.NONE:
Expand Down Expand Up @@ -770,6 +776,7 @@ def cell(
rowspan=1,
padding=None,
link=None,
border=CellBordersLayout.INHERIT,
):
"""
Adds a cell to the row.
Expand All @@ -788,6 +795,7 @@ def cell(
rowspan (int): optional number of rows this cell should span.
padding (tuple): optional padding (left, top, right, bottom) for the cell.
link (str, int): optional link, either an URL or an integer returned by `FPDF.add_link`, defining an internal link to a page
border (fpdf.enums.CellBordersLayout): optional cell borders, defaults to `CellBordersLayout.INHERIT`

"""
if text and img:
Expand Down Expand Up @@ -819,6 +827,7 @@ def cell(
rowspan,
padding,
link,
CellBordersLayout.coerce(border),
)
self.cells.append(cell)
return cell
Expand All @@ -838,6 +847,7 @@ class Cell:
"rowspan",
"padding",
"link",
"border",
)
text: str
align: Optional[Union[str, Align]]
Expand All @@ -849,6 +859,7 @@ class Cell:
rowspan: int
padding: Optional[Union[int, tuple, type(None)]]
link: Optional[Union[str, int]]
border: Optional[CellBordersLayout]

def write(self, text, align=None):
raise NotImplementedError("Not implemented yet")
Expand Down
Loading
Loading