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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
40 changes: 40 additions & 0 deletions fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,46 @@ class TableBordersLayout(CoerciveEnum):
"Draw only the top horizontal border, below the headings"


class CellBordersLayout(CoerciveIntFlag):
NONE = 0
LEFT = 1
RIGHT = 2
TOP = 4
BOTTOM = 8
ALL = LEFT | RIGHT | TOP | BOTTOM
INHERIT = 16

@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
13 changes: 13 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,21 @@ 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 hasattr(cell, "border"):
border = CellBordersLayout.coerce(cell.border)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed if you perform the call to .coerce() in Row.cell()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think that those hasattr & CellBordersLayout.coerce calls are needed.


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

if self._borders_layout == TableBordersLayout.ALL:
return 1
if self._borders_layout == TableBordersLayout.NONE:
Expand Down Expand Up @@ -770,6 +779,7 @@ def cell(
rowspan=1,
padding=None,
link=None,
border=CellBordersLayout.INHERIT,
):
"""
Adds a cell to the row.
Expand Down Expand Up @@ -819,6 +829,7 @@ def cell(
rowspan,
padding,
link,
CellBordersLayout.coerce(border),
)
self.cells.append(cell)
return cell
Expand All @@ -838,6 +849,7 @@ class Cell:
"rowspan",
"padding",
"link",
"border",
)
text: str
align: Optional[Union[str, Align]]
Expand All @@ -849,6 +861,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
237 changes: 236 additions & 1 deletion test/text/test_cell.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import gzip
from pathlib import Path

import pytest

from fpdf import FPDF, FPDFException
Expand Down Expand Up @@ -362,3 +361,239 @@ def test_cell_speed_with_long_text(): # issue #907
assert len(LONG_TEXT_LINES) == 26862
for line in LONG_TEXT_LINES:
pdf.cell(0, 3, line, new_x="LMARGIN", new_y="NEXT")


def test_cell_border_none(tmp_path):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you regroup all or most of those tests in a single one, with one case per page, in order to limit the number of reference PDF files, please?

All the right/left/top/bottom cases could form a single unit test I think.

inherit should have its own dedicated unit test & reference file, but I think more cases should be tested:

  • cell border="INHERIT" with various table borders_layout values
  • cell border="INHERIT" with table outer_border_width set

pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border="None")
assert_pdf_equal(pdf, HERE / "test_cell_border_none.pdf", tmp_path)


def test_cell_border_left(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()
for datum in data_row:
row.cell(datum, border="left")
assert_pdf_equal(pdf, HERE / "test_cell_border_left.pdf", tmp_path)


def test_cell_border_right(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border="right")
assert_pdf_equal(pdf, HERE / "test_cell_border_right.pdf", tmp_path)


def test_cell_border_top(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border="top")
assert_pdf_equal(pdf, HERE / "test_cell_border_top.pdf", tmp_path)


def test_cell_border_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border="bottom")
assert_pdf_equal(pdf, HERE / "test_cell_border_bottom.pdf", tmp_path)


def test_cell_border_all(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border="all")
assert_pdf_equal(pdf, HERE / "test_cell_border_all.pdf", tmp_path)


def test_cell_border_inherit(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border="inherit")
assert_pdf_equal(pdf, HERE / "test_cell_border_inherit.pdf", tmp_path)


def test_cell_border_left_right(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=3)
assert_pdf_equal(pdf, HERE / "test_cell_border_left_right.pdf", tmp_path)


def test_cell_border_left_top(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=5)
assert_pdf_equal(pdf, HERE / "test_cell_border_left_top.pdf", tmp_path)


def test_cell_border_left_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=9)
assert_pdf_equal(pdf, HERE / "test_cell_border_left_bottom.pdf", tmp_path)


def test_cell_border_right_top(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=6)
assert_pdf_equal(pdf, HERE / "test_cell_border_right_top.pdf", tmp_path)


def test_cell_border_right_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=10)
assert_pdf_equal(pdf, HERE / "test_cell_border_right_bottom.pdf", tmp_path)


def test_cell_border_left_right_top(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=7)
assert_pdf_equal(pdf, HERE / "test_cell_border_left_right_top.pdf", tmp_path)


def test_cell_border_left_right_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=11)
assert_pdf_equal(pdf, HERE / "test_cell_border_left_right_bottom.pdf", tmp_path)


def test_cell_border_left_top_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=13)
assert_pdf_equal(pdf, HERE / "test_cell_border_left_top_bottom.pdf", tmp_path)


def test_cell_border_right_top_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=13)
assert_pdf_equal(pdf, HERE / "test_cell_border_right_top_bottom.pdf", tmp_path)


def test_cell_border_top_bottom(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table(gutter_height=3, gutter_width=3) as table:
for data_row in TABLE_DATA:
row = table.row()

for datum in data_row:
print(datum, end=" ")
row.cell(datum, border=12)
assert_pdf_equal(pdf, HERE / "test_cell_border_top_bottom.pdf", tmp_path)
Binary file added test/text/test_cell_border_all.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_bottom.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_inherit.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_left.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_left_bottom.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_left_right.pdf
Binary file not shown.
Binary file not shown.
Binary file added test/text/test_cell_border_left_right_top.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_left_top.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_left_top_bottom.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_none.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_right.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_right_bottom.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_right_top.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_right_top_bottom.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_top.pdf
Binary file not shown.
Binary file added test/text/test_cell_border_top_bottom.pdf
Binary file not shown.
Loading