Skip to content

Commit

Permalink
Implement FPDF.table() - close #701
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C committed Feb 20, 2023
1 parent 0f007cf commit 9892367
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 18 deletions.
2 changes: 1 addition & 1 deletion docs/Images.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ When you want to scale an image to fill a rectangle, while keeping its aspect ra
and ensuring it does **not** overflow the rectangle width nor height in the process,
you can set `w` / `h` and also provide `keep_aspect_ratio=True` to the [`image()`](fpdf/fpdf.html#fpdf.fpdf.FPDF.image) method.

The following unit test illustrates that:
The following unit tests illustrate that:

* [test_image_fit.py](https://github.com/PyFPDF/fpdf2/blob/master/test/image/test_image_fit.py)
* resulting document: [image_fit_in_rect.pdf](https://github.com/PyFPDF/fpdf2/blob/master/test/image/image_fit_in_rect.pdf)
Expand Down
5 changes: 5 additions & 0 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class Image:
from .sign import Signature
from .svg import Percent, SVGObject
from .syntax import DestinationXYZ
from .table import Table
from .util import (
escape_parens,
format_date,
Expand Down Expand Up @@ -4532,6 +4533,10 @@ def _apply_style(self, title_style):
self.text_color = prev_text_color
self.underline = prev_underline

@check_page
def table(self):
return Table(self)

def output(
self, name="", dest="", linearize=False, output_producer_class=OutputProducer
):
Expand Down
56 changes: 56 additions & 0 deletions fpdf/table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from numbers import Number


class Table:
def __init__(self, fpdf):
self.fpdf = fpdf
self.set_col_widths = None
self.rows = []

def __enter__(self):
return self

def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
line_height = 2 * self.fpdf.font_size
for i, row in enumerate(self.rows):
for j, cell in enumerate(row.cells):
col_width = self._get_col_width(i, j)
self.fpdf.multi_cell(
col_width,
line_height,
cell,
border=1,
new_x="RIGHT",
new_y="TOP",
max_line_height=self.fpdf.font_size,
)
self.fpdf.ln(line_height)

def _get_col_width(self, i, _):
if not self.set_col_widths:
cols_count = len(self.rows[i].cells)
return self.fpdf.epw / cols_count
if isinstance(self.set_col_widths, Number):
return self.set_col_widths
# pylint: disable=unsubscriptable-object
return self.set_col_widths[i]

def row(self):
row = Row(self)
self.rows.append(row)
return row


class Row:
def __init__(self, table):
self.table = table
self.cells = []

def __enter__(self):
return self

def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
pass

def cell(self, text):
self.cells.append(text)
Binary file added test/table/simple_table.pdf
Binary file not shown.
29 changes: 29 additions & 0 deletions test/table/test_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pathlib import Path

from fpdf import FPDF
from test.conftest import assert_pdf_equal


HERE = Path(__file__).resolve().parent
FONTS_DIR = HERE.parent / "fonts"

TABLE_DATA = (
("First name", "Last name", "Age", "City"),
("Jules", "Smith", "34", "San Juan"),
("Mary", "Ramos", "45", "Orlando"),
("Carlson", "Banks", "19", "Los Angeles"),
("Lucas", "Cimon", "31", "Angers"),
)


def test_simple_table(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
with pdf.table() as table:
table.col_widths = pdf.epw / 4
for data_row in TABLE_DATA:
with table.row() as row:
for datum in data_row:
row.cell(datum)
assert_pdf_equal(pdf, HERE / "simple_table.pdf", tmp_path)
23 changes: 12 additions & 11 deletions test/text/test_cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from fpdf import FPDF, FPDFException
from test.conftest import assert_pdf_equal, LOREM_IPSUM

TEXT_SIZE, SPACING = 36, 1.15
LINE_HEIGHT = TEXT_SIZE * SPACING


HERE = Path(__file__).resolve().parent
FONTS_DIR = HERE.parent / "fonts"

TEXT_SIZE, SPACING = 36, 1.15
LINE_HEIGHT = TEXT_SIZE * SPACING

TABLE_DATA = (
("First name", "Last name", "Age", "City"),
Expand Down Expand Up @@ -155,9 +156,9 @@ def test_cell_markdown(tmp_path):
def test_cell_markdown_with_ttf_fonts(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.add_font("Roboto", "", HERE / "../fonts/Roboto-Regular.ttf")
pdf.add_font("Roboto", "B", HERE / "../fonts/Roboto-Bold.ttf")
pdf.add_font("Roboto", "I", HERE / "../fonts/Roboto-Italic.ttf")
pdf.add_font("Roboto", "", FONTS_DIR / "Roboto-Regular.ttf")
pdf.add_font("Roboto", "B", FONTS_DIR / "Roboto-Bold.ttf")
pdf.add_font("Roboto", "I", FONTS_DIR / "Roboto-Italic.ttf")
pdf.set_font("Roboto", size=60)
pdf.cell(txt="**Lorem** __Ipsum__ --dolor--", markdown=True)
assert_pdf_equal(pdf, HERE / "cell_markdown_with_ttf_fonts.pdf", tmp_path)
Expand All @@ -166,7 +167,7 @@ def test_cell_markdown_with_ttf_fonts(tmp_path):
def test_cell_markdown_missing_ttf_font():
pdf = FPDF()
pdf.add_page()
pdf.add_font(fname=HERE / "../fonts/Roboto-Regular.ttf")
pdf.add_font(fname=FONTS_DIR / "Roboto-Regular.ttf")
pdf.set_font("Roboto-Regular", size=60)
with pytest.raises(FPDFException) as error:
pdf.cell(txt="**Lorem Ipsum**", markdown=True)
Expand All @@ -190,8 +191,8 @@ def test_cell_markdown_bleeding(tmp_path): # issue 241
def test_cell_markdown_right_aligned(tmp_path): # issue 333
pdf = FPDF()
pdf.add_page()
pdf.add_font("Roboto", fname=HERE / "../fonts/Roboto-Regular.ttf")
pdf.add_font("Roboto", style="B", fname=HERE / "../fonts/Roboto-Bold.ttf")
pdf.add_font("Roboto", fname=FONTS_DIR / "Roboto-Regular.ttf")
pdf.add_font("Roboto", style="B", fname=FONTS_DIR / "Roboto-Bold.ttf")
pdf.set_font("Roboto", size=60)
pdf.cell(
0,
Expand Down Expand Up @@ -246,8 +247,8 @@ def test_cell_newpos_badinput():
def test_cell_curfont_leak(tmp_path): # issue #475
pdf = FPDF()
pdf.add_page()
pdf.add_font("Roboto", fname=HERE / "../fonts/Roboto-Regular.ttf")
pdf.add_font("Roboto", style="B", fname=HERE / "../fonts/Roboto-Bold.ttf")
pdf.add_font("Roboto", fname=FONTS_DIR / "Roboto-Regular.ttf")
pdf.add_font("Roboto", style="B", fname=FONTS_DIR / "Roboto-Bold.ttf")
with pdf.local_context():
pdf.set_font("Roboto", "B", 10)
pdf.cell(txt="ABCDEFGH", new_x="LEFT", new_y="NEXT")
Expand Down
4 changes: 2 additions & 2 deletions test/text/test_multi_cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def test_multi_cell_table_with_max_line_height(tmp_path): # issue 589
def test_multi_cell_justified_with_unicode_font(tmp_path): # issue 118
pdf = FPDF()
pdf.add_page()
pdf.add_font(fname=HERE / "../fonts/DejaVuSans.ttf")
pdf.add_font(fname=FONTS_DIR / "DejaVuSans.ttf")
pdf.set_font("DejaVuSans", size=14)
text = 'Justified line containing "()" that is long enough to trigger wrapping and a line jump'
pdf.multi_cell(w=0, h=8, txt=text, new_x="LMARGIN", new_y="NEXT")
Expand Down Expand Up @@ -280,7 +280,7 @@ def test_multicell_newpos_badinput():
def test_multi_cell_j_paragraphs(tmp_path): # issue 364
pdf = FPDF(format="A5")
pdf.add_page()
pdf.add_font(fname=HERE / "../fonts/DejaVuSans.ttf")
pdf.add_font(fname=FONTS_DIR / "DejaVuSans.ttf")
pdf.set_font("DejaVuSans", size=14)
pdf.set_margins(34, 55, 34)
pdf.set_auto_page_break(auto=True, margin=55)
Expand Down
9 changes: 5 additions & 4 deletions test/text/test_multi_cell_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

HERE = Path(__file__).resolve().parent
FONTS_DIR = HERE.parent / "fonts"


def test_multi_cell_markdown(tmp_path):
Expand All @@ -27,9 +28,9 @@ def test_multi_cell_markdown(tmp_path):
def test_multi_cell_markdown_with_ttf_fonts(tmp_path):
pdf = fpdf.FPDF()
pdf.add_page()
pdf.add_font("Roboto", "", HERE / "../fonts/Roboto-Regular.ttf")
pdf.add_font("Roboto", "B", HERE / "../fonts/Roboto-Bold.ttf")
pdf.add_font("Roboto", "I", HERE / "../fonts/Roboto-Italic.ttf")
pdf.add_font("Roboto", "", FONTS_DIR / "Roboto-Regular.ttf")
pdf.add_font("Roboto", "B", FONTS_DIR / "Roboto-Bold.ttf")
pdf.add_font("Roboto", "I", FONTS_DIR / "Roboto-Italic.ttf")
pdf.set_font("Roboto", size=32)
text = ( # Some text where styling occur over line breaks:
"Lorem ipsum dolor, **consectetur adipiscing** elit,"
Expand All @@ -46,7 +47,7 @@ def test_multi_cell_markdown_with_ttf_fonts(tmp_path):
def test_multi_cell_markdown_missing_ttf_font():
pdf = fpdf.FPDF()
pdf.add_page()
pdf.add_font(fname=HERE / "../fonts/Roboto-Regular.ttf")
pdf.add_font(fname=FONTS_DIR / "Roboto-Regular.ttf")
pdf.set_font("Roboto-Regular", size=60)
with pytest.raises(fpdf.FPDFException) as error:
pdf.multi_cell(w=pdf.epw, txt="**Lorem Ipsum**", markdown=True)
Expand Down

0 comments on commit 9892367

Please sign in to comment.