From 0579097951177ba61ac196c1f0765abea710cdb9 Mon Sep 17 00:00:00 2001
From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com>
Date: Mon, 27 Mar 2023 17:12:56 +0200
Subject: [PATCH] Implement FPDF.table() - close #701 & #723 (#703)
---
.../continuous-integration-workflow.yml | 12 +-
CHANGELOG.md | 10 +-
README.md | 2 +-
docs/Images.md | 2 +-
docs/Maths.md | 27 +-
docs/Tables.md | 295 +++++++++----
docs/Tutorial-de.md | 18 +-
docs/Tutorial-es.md | 30 +-
docs/Tutorial-fr.md | 17 +-
docs/Tutorial-gr.md | 13 +-
docs/Tutorial-he.md | 16 +-
docs/Tutorial-it.md | 13 +-
docs/Tutorial-pt.md | 16 +-
docs/Tutorial-ru.md | 14 +-
docs/Tutorial-zh.md | 23 +-
...77\340\244\202\340\244\246\340\245\200.md" | 20 +-
docs/Tutorial.md | 36 +-
docs/index.md | 2 +-
docs/table-pandas.png | Bin 12279 -> 23645 bytes
docs/table-simple.jpg | Bin 0 -> 24560 bytes
docs/table-styled.jpg | Bin 0 -> 24132 bytes
docs/table-with-cells-filled.jpg | Bin 0 -> 23937 bytes
docs/table-with-cells-filled2.jpg | Bin 0 -> 19463 bytes
docs/table-with-fixed-column-widths.jpg | Bin 0 -> 18152 bytes
docs/table_align.jpg | Bin 0 -> 39270 bytes
docs/table_with_images.jpg | Bin 0 -> 31466 bytes
docs/table_with_images_and_img_fill_width.jpg | Bin 0 -> 46561 bytes
docs/table_with_internal_layout.jpg | Bin 0 -> 18536 bytes
docs/table_with_minimal_layout.jpg | Bin 0 -> 14822 bytes
docs/table_with_single_top_line_layout.jpg | Bin 0 -> 13287 bytes
fpdf/enums.py | 84 +++-
fpdf/fonts.py | 59 ++-
fpdf/fpdf.py | 195 +++++----
fpdf/html.py | 386 +++++-------------
fpdf/image_parsing.py | 2 +-
fpdf/output.py | 1 +
fpdf/table.py | 346 ++++++++++++++++
fpdf/util.py | 10 +-
setup.cfg | 2 +
test/conftest.py | 33 +-
test/embed_file_all_optionals.pdf | Bin 1306 -> 1451 bytes
test/embed_file_self.pdf | Bin 1148 -> 1486 bytes
test/file_attachment_annotation.pdf | Bin 1321 -> 1658 bytes
test/fonts/test_set_font.py | 6 +-
test/html/bgcolor_in_table.pdf | Bin 1623 -> 0 bytes
...customize_ul.pdf => html_customize_ul.pdf} | Bin
test/html/html_features.pdf | Bin 5929 -> 5938 bytes
...pping.pdf => html_img_not_overlapping.pdf} | Bin
test/html/html_table_line_separators.pdf | Bin 1231 -> 1252 bytes
.../html_table_line_separators_issue_137.pdf | Bin 1223 -> 1250 bytes
test/html/html_table_with_border.pdf | Bin 1309 -> 1346 bytes
.../html_table_with_empty_cell_contents.pdf | Bin 1280 -> 1301 bytes
...dling.pdf => html_whitespace_handling.pdf} | Bin
test/html/test_html.py | 239 +----------
test/html/test_img_inside_html_table.pdf | Bin 14797 -> 0 bytes
.../test_img_inside_html_table_centered.pdf | Bin 14796 -> 0 bytes
..._inside_html_table_centered_with_align.pdf | Bin 14801 -> 0 bytes
...nside_html_table_centered_with_caption.pdf | Bin 22666 -> 0 bytes
...html_table_without_explicit_dimensions.pdf | Bin 14788 -> 0 bytes
test/image/test_load_image.py | 2 +-
test/image/test_oversized.py | 2 +-
test/requirements.txt | 5 +
test/table/table_align.pdf | Bin 0 -> 2124 bytes
test/table/table_simple.pdf | Bin 0 -> 1646 bytes
test/table/table_with_cell_fill.pdf | Bin 0 -> 2250 bytes
test/table/table_with_fixed_col_width.pdf | Bin 0 -> 1647 bytes
test/table/table_with_fixed_row_height.pdf | Bin 0 -> 1630 bytes
test/table/table_with_fixed_width.pdf | Bin 0 -> 1642 bytes
test/table/table_with_headings_styled.pdf | Bin 0 -> 1708 bytes
test/table/table_with_images.pdf | Bin 0 -> 79296 bytes
.../table_with_images_and_img_fill_width.pdf | Bin 0 -> 79299 bytes
test/table/table_with_internal_layout.pdf | Bin 0 -> 1572 bytes
test/table/table_with_minimal_layout.pdf | Bin 0 -> 1498 bytes
test/table/table_with_multiline_cells.pdf | Bin 0 -> 3050 bytes
...h_multiline_cells_and_fixed_row_height.pdf | Bin 0 -> 2974 bytes
.../table_with_multiline_cells_and_images.pdf | Bin 0 -> 80470 bytes
...multiline_cells_and_split_over_3_pages.pdf | Bin 0 -> 5254 bytes
...h_multiline_cells_and_without_headings.pdf | Bin 0 -> 4919 bytes
.../table_with_single_top_line_layout.pdf} | Bin 1221 -> 1360 bytes
test/table/table_with_varying_col_widths.pdf | Bin 0 -> 1643 bytes
test/table/table_without_headings.pdf | Bin 0 -> 1507 bytes
test/table/test_table.py | 291 +++++++++++++
test/table/test_table_extraction.py | 124 ++++++
test/table/test_table_with_image.py | 103 +++++
test/test_perfs.py | 2 +-
test/text/test_cell.py | 23 +-
test/text/test_multi_cell.py | 4 +-
test/text/test_multi_cell_markdown.py | 9 +-
tutorial/tuto1.htm | 91 -----
tutorial/tuto2.htm | 50 ---
tutorial/tuto3.htm | 43 --
tutorial/tuto4.htm | 34 --
tutorial/tuto5.htm | 43 --
tutorial/tuto5.pdf | Bin 8069 -> 7482 bytes
tutorial/tuto5.py | 95 ++---
tutorial/tuto6.htm | 68 ---
tutorial/tuto7.htm | 315 --------------
97 files changed, 1620 insertions(+), 1613 deletions(-)
create mode 100644 docs/table-simple.jpg
create mode 100644 docs/table-styled.jpg
create mode 100644 docs/table-with-cells-filled.jpg
create mode 100644 docs/table-with-cells-filled2.jpg
create mode 100644 docs/table-with-fixed-column-widths.jpg
create mode 100644 docs/table_align.jpg
create mode 100644 docs/table_with_images.jpg
create mode 100644 docs/table_with_images_and_img_fill_width.jpg
create mode 100644 docs/table_with_internal_layout.jpg
create mode 100644 docs/table_with_minimal_layout.jpg
create mode 100644 docs/table_with_single_top_line_layout.jpg
create mode 100644 fpdf/table.py
delete mode 100644 test/html/bgcolor_in_table.pdf
rename test/html/{test_customize_ul.pdf => html_customize_ul.pdf} (100%)
rename test/html/{test_img_not_overlapping.pdf => html_img_not_overlapping.pdf} (100%)
rename test/html/{test_html_whitespace_handling.pdf => html_whitespace_handling.pdf} (100%)
delete mode 100644 test/html/test_img_inside_html_table.pdf
delete mode 100644 test/html/test_img_inside_html_table_centered.pdf
delete mode 100644 test/html/test_img_inside_html_table_centered_with_align.pdf
delete mode 100644 test/html/test_img_inside_html_table_centered_with_caption.pdf
delete mode 100644 test/html/test_img_inside_html_table_without_explicit_dimensions.pdf
create mode 100644 test/table/table_align.pdf
create mode 100644 test/table/table_simple.pdf
create mode 100644 test/table/table_with_cell_fill.pdf
create mode 100644 test/table/table_with_fixed_col_width.pdf
create mode 100644 test/table/table_with_fixed_row_height.pdf
create mode 100644 test/table/table_with_fixed_width.pdf
create mode 100644 test/table/table_with_headings_styled.pdf
create mode 100644 test/table/table_with_images.pdf
create mode 100644 test/table/table_with_images_and_img_fill_width.pdf
create mode 100644 test/table/table_with_internal_layout.pdf
create mode 100644 test/table/table_with_minimal_layout.pdf
create mode 100644 test/table/table_with_multiline_cells.pdf
create mode 100644 test/table/table_with_multiline_cells_and_fixed_row_height.pdf
create mode 100644 test/table/table_with_multiline_cells_and_images.pdf
create mode 100644 test/table/table_with_multiline_cells_and_split_over_3_pages.pdf
create mode 100644 test/table/table_with_multiline_cells_and_without_headings.pdf
rename test/{html/html_simple_table.pdf => table/table_with_single_top_line_layout.pdf} (63%)
create mode 100644 test/table/table_with_varying_col_widths.pdf
create mode 100644 test/table/table_without_headings.pdf
create mode 100644 test/table/test_table.py
create mode 100644 test/table/test_table_extraction.py
create mode 100644 test/table/test_table_with_image.py
delete mode 100644 tutorial/tuto1.htm
delete mode 100644 tutorial/tuto2.htm
delete mode 100644 tutorial/tuto3.htm
delete mode 100644 tutorial/tuto4.htm
delete mode 100644 tutorial/tuto5.htm
delete mode 100644 tutorial/tuto6.htm
delete mode 100644 tutorial/tuto7.htm
diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml
index c4e6c53fb..29a8e499a 100644
--- a/.github/workflows/continuous-integration-workflow.yml
+++ b/.github/workflows/continuous-integration-workflow.yml
@@ -24,7 +24,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies ⚙️
if: matrix.platform == 'ubuntu-latest'
- run: sudo apt-get install libjpeg-dev
+ run: sudo apt-get install ghostscript libjpeg-dev
- name: Install qpdf ⚙️
if: matrix.platform == 'ubuntu-latest' && matrix.python-version != '3.9' # This allows us to run the unit tests WITHOUT qpdf in just one parallel execution
run: sudo apt-get install qpdf
@@ -35,9 +35,9 @@ jobs:
- name: Statically checking code 🔎
if: matrix.python-version == '3.10' && matrix.platform == 'ubuntu-latest'
run: |
- pylint fpdf test tutorial/tuto*.py
- bandit -c .banditrc.yml -r contributors/ fpdf/ tutorial/
- semgrep scan --config auto --lang=py --error --strict --exclude-rule=python.lang.security.insecure-hash-function.insecure-hash-function fpdf
+ pylint fpdf test tutorial/tuto*.py
+ bandit -c .banditrc.yml -r contributors/ fpdf/ tutorial/
+ semgrep scan --config auto --lang=py --error --strict --exclude-rule=python.lang.security.insecure-hash-function.insecure-hash-function fpdf
- name: Ensure code has been autoformatted with black 🖌️
if: matrix.python-version == '3.10' && matrix.platform == 'ubuntu-latest'
run: black --check .
@@ -59,7 +59,7 @@ jobs:
# Ensuring there is no `generate=True` left remaining in calls to assert_pdf_equal:
grep -IRF generate=True test/ && exit 1
# Executing all tests:
- pytest -vv --final-memory-usage
+ pytest -vv --trace-memory-usage
- name: Uploading coverage report to codecov.io ☑
if: matrix.python-version == '3.10' && matrix.platform == 'ubuntu-latest'
run: bash <(curl -s https://codecov.io/bash)
@@ -75,7 +75,7 @@ jobs:
sed -i "s/author:.*/author: v$(python setup.py -V 2>/dev/null)/" mkdocs.yml
cp tutorial/notebook.ipynb docs/
mkdocs build
- pdoc --html -o public/ fpdf
+ pdoc --html -o public/ fpdf --config "git_link_template='https://github.com/PyFPDF/fpdf2/blob/{commit}/{path}#L{start_line}-L{end_line}'"
cd contributors/ && PYTHONUNBUFFERED=1 ./build_contributors_html_page.py PyFPDF/fpdf2
cp -t ../public/ contributors.html contributors-map-small.png
- name: Deploy documentation 🚀
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33f8e2c11..a0f184ae7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,11 +16,12 @@ in order to get warned about deprecated features used in your code.
This can also be enabled programmatically with `warnings.simplefilter('default', DeprecationWarning)`.
-## [2.6.2] - Not released yet
+## [2.7.0] - Not released yet
### Added
-* [`FPDF.multi_cell()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) and [`FPDF.write()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write) now accept a `wrapmode` argument for word or character based line wrapping ("WORD"/"CHAR").
+- new method [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table): [documentation](https://pyfpdf.github.io/fpdf2/Tables.html)
- [`FPDF.image()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.image) has a new `keep_aspect_ratio` optional boolean parameter, to fit it inside a given rectangle: [documentation](https://pyfpdf.github.io/fpdf2/Images.html#fitting-an-image-inside-a-rectangle)
-- new method `FPDF.preload_image()`: [documentation](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.preload_image)
+- [`FPDF.multi_cell()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) and [`FPDF.write()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write) now accept a `wrapmode` argument for word or character based line wrapping ("WORD"/"CHAR").
+- new methods: [`FPDF.preload_image()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.preload_image) & [`FPDF.use_font_style()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.use_font_style)
- new translation of the tutorial in [简体中文](https://pyfpdf.github.io/fpdf2/Tutorial-zh.html) - thanks to @Bubbu0129
- new method [`FPDF.set_fallback_fonts()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.set_fallback_fonts) allow alternative fonts to be provided if a character on the text is not available on the currently set font - thanks to @andersonhc
- documentation on how to embed static [Plotly](https://plotly.com/python/) charts: [link to docs](https://pyfpdf.github.io/fpdf2/Maths.html)
@@ -32,10 +33,13 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
- all `TitleStyle` constructor parameters are now effectively optional
- memory usage was reduced by 10 MiB in some cases, thanks to a small optimization in using `fonttools`
### Changed
+* [`FPDF.write_html()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now uses the new [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/Tables.html) method to render `
` tags. As a consequence, vertical space before `` tags has sometimes been reduced.
- vector images parsing is now more robust: `fpdf2` can now embed SVG files without `viewPort` or no `height` / `width`
- bitonal images are now encoded using `CCITTFaxDecode`, reducing their size in the PDF document - thanks to @eroux
- when possible, JPG and group4 encoded TIFFs are now embedded directly without recompression - thanks to @eroux
- ICC Profiles of included images are now extracted and turned into PDF objects; they should now be taken into account by PDF viewers - thanks to @eroux
+### Removed
+* [`FPDF.write_html()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now uses the new [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/Tables.html) method to render `` tags. As a consequence, it does not support the `height` attribute defined on `` / ` | ` tags anymore, nor `height` / `width` attributes defined on `` tags inside cells, nor `width` attributes defined on `` / ` | ` tags.
## [2.6.1] - 2023-01-13
### Added
diff --git a/README.md b/README.md
index 31acd5527..5248a5064 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ pip install git+https://github.com/PyFPDF/fpdf2.git@master
* Embedding images, including transparency and alpha channel
* Arbitrary path drawing and basic [SVG](https://pyfpdf.github.io/fpdf2/SVG.html) import
* Embedding [barcodes](https://pyfpdf.github.io/fpdf2/Barcodes.html), [charts & graphs](https://pyfpdf.github.io/fpdf2/Maths.html), [emojis, symbols & dingbats](https://pyfpdf.github.io/fpdf2/EmojisSymbolsDingbats.html)
- * [Cell / multi-cell / plaintext writing](https://pyfpdf.github.io/fpdf2/Text.html), with [automatic page breaks](https://pyfpdf.github.io/fpdf2/PageBreaks.html), line break and text justification
+ * [Tables](https://pyfpdf.github.io/fpdf2/Tables.html) and also [cell / multi-cell / plaintext writing](https://pyfpdf.github.io/fpdf2/Text.html), with [automatic page breaks](https://pyfpdf.github.io/fpdf2/PageBreaks.html), line break and text justification
* Choice of measurement unit, page format & margins. Optional page header and footer
* Basic [conversion from HTML to PDF](https://pyfpdf.github.io/fpdf2/HTML.html)
* A [templating system](https://pyfpdf.github.io/fpdf2/Templates.html) to render PDFs in batchs
diff --git a/docs/Images.md b/docs/Images.md
index 925091e84..aa0d26982 100644
--- a/docs/Images.md
+++ b/docs/Images.md
@@ -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)
diff --git a/docs/Maths.md b/docs/Maths.md
index 831a65527..9beacb554 100644
--- a/docs/Maths.md
+++ b/docs/Maths.md
@@ -128,25 +128,20 @@ columns = [list(df)] # Get list of dataframe columns
rows = df.values.tolist() # Get list of dataframe rows
data = columns + rows # Combine columns and rows in one list
-# Start pdf creating
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=10)
-line_height = pdf.font_size * 2.5
-col_width = pdf.epw / 4 # distribute content evenly
-
-for row in data:
- for datum in row:
- pdf.multi_cell(
- col_width,
- line_height,
- datum,
- border=1,
- new_y="TOP",
- max_line_height=pdf.font_size,
- )
- pdf.ln(line_height)
-pdf.output("table_with_cells.pdf")
+with pdf.table(borders_layout="MINIMAL",
+ cell_fill_color=200, # grey
+ cell_fill_mode="ROWS",
+ line_height=pdf.font_size * 2.5,
+ text_align="CENTER",
+ width=160) as table:
+ for data_row in data:
+ row = table.row()
+ for datum in data_row:
+ row.cell(datum)
+pdf.output("table_from_pandas.pdf")
```
Result:
diff --git a/docs/Tables.md b/docs/Tables.md
index b7ba46343..1e7a1c79d 100644
--- a/docs/Tables.md
+++ b/docs/Tables.md
@@ -1,42 +1,224 @@
-# Tables #
+# Tables
-Tables can be built either using **cells**
-or with [`write_html`](HTML.md).
+_New in [:octicons-tag-24: 2.7.0](https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md)_
-
-## Using cells ##
-
-There is a method to build tables allowing for multilines content in cells:
+Tables can be built using the `table()` method.
+Here is a simple example:
```python
from fpdf import FPDF
-data = (
+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", "Saint-Mahturin-sur-Loire"),
)
+pdf = FPDF()
+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)
+pdf.output('table.pdf')
+```
+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 (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
+```python
+...
+with pdf.table(width=150, col_widths=(30, 30, 10, 30)) as table:
+ ...
+```
+Result:
+
+![](table-with-fixed-column-widths.jpg)
+
+`align` can be passed to `table()` 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(text_align="CENTER") as table:
+ ...
+pdf.ln()
+with pdf.table(text_align=("CENTER", "CENTER", "RIGHT", "LEFT")) as table:
+ ...
+```
+Result:
+
+![](table_align.jpg)
+
+## Setting row height
+```python
+...
+with pdf.table(line_height=2.5 * pdf.font_size) as table:
+ ...
+```
+
+## Disable table headings
+```python
+...
+with pdf.table(first_row_as_headings=False) as table:y
+ ...
+```
+
+## Style table headings
+```python
+...
+blue = (0, 0, 255)
+grey = (128, 128, 128)
+headings_style = FontStyle(emphasis="ITALICS", color=blue, fill_color=grey)
+with pdf.table(headings_styleheadings_style=headings_style) as table:
+ ...
+```
+Result:
+
+![](table-styled.jpg)
+
+## Set cells background
+```python
+...
+greyscale = 200
+with pdf.table(cell_fill_color=greyscale, cell_fill_mode="ROWS") as table:
+ ...
+```
+Result:
+
+![](table-with-cells-filled.jpg)
+
+```python
+...
+lightblue = (173, 216, 230)
+with pdf.table(cell_fill_color=lightblue, cell_fill_mode="COLUMNS") as table:
+ ...
+```
+Result:
+
+![](table-with-cells-filled2.jpg)
+
+## Set borders layout
+```python
+...
+with pdf.table(borders_layout="INTERNAL") as table:
+ ...
+```
+Result:
+
+![](table_with_internal_layout.jpg)
+
+```python
+...
+with pdf.table(borders_layout="MINIMAL") as table:
+ ...
+```
+Result:
+![](table_with_minimal_layout.jpg)
+
+```python
+...
+pdf.set_draw_color(50) # very dark grey
+pdf.set_line_width(.5)
+with pdf.table(borders_layout="SINGLE_TOP_LINE") as table:
+ ...
+```
+Result:
+
+![](table_with_single_top_line_layout.jpg)
+
+All the possible layout values are described there: [`TableBordersLayout`](https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.TableBordersLayout).
+
+## Insert images
+```python
+TABLE_DATA = (
+ ("First name", "Last name", "Image", "City"),
+ ("Jules", "Smith", "shirt.png", "San Juan"),
+ ("Mary", "Ramos", "joker.png", "Orlando"),
+ ("Carlson", "Banks", "socialist.png", "Los Angeles"),
+ ("Lucas", "Cimon", "circle.bmp", "Angers"),
+)
pdf = FPDF()
pdf.add_page()
-pdf.set_font("Times", size=10)
-line_height = pdf.font_size * 2.5
-col_width = pdf.epw / 4 # distribute content evenly
-for row in data:
- for datum in row:
- pdf.multi_cell(col_width, line_height, datum, border=1,
- new_x="RIGHT", new_y="TOP", max_line_height=pdf.font_size)
- pdf.ln(line_height)
-pdf.output('table_with_cells.pdf')
+pdf.set_font("Times", size=16)
+with pdf.table() as table:
+ for i, data_row in enumerate(TABLE_DATA):
+ row = table.row()
+ for j, datum in enumerate(data_row):
+ if j == 2 and i > 0:
+ row.cell(img=datum)
+ else:
+ row.cell(datum)
+pdf.output('table_with_images.pdf')
+```
+Result:
+
+![](table_with_images.jpg)
+
+By default, images height & width are constrained by the row height (based on text content)
+and the column width. To render bigger images, you can set the `line_height` to increase the row height, or pass `img_fill_width=True` to `.cell()`:
+
+```python
+ row.cell(img=datum, img_fill_width=True)
```
+Result:
+![](table_with_images_and_img_fill_width.jpg)
-## Using write_html ##
+## Syntactic sugar
-An alternative method using [`FPDF.write_html`](HTML.md),
-with the same `data` as above, and column widths defined as percent of the effective width:
+To simplify `table()` usage, shorter, alternative usage forms are allowed.
+
+This sample code:
+```python
+with pdf.table() as table:
+ for data_row in TABLE_DATA:
+ row = table.row()
+ for datum in data_row:
+ row.cell(datum)
+```
+
+Can be shortened to the followng code,
+by passing lists of strings as the `cells` optional argument of `.row()`:
+```python
+with pdf.table() as table:
+ for data_row in TABLE_DATA:
+ table.row(data_row)
+```
+
+And even shortened further to a single line,
+by passing lists of lists of strings as the `rows` optional argument of `.table()`:
+```python
+with pdf.table(TABLE_DATA):
+ pass
+```
+
+## Table from pandas DataFrame
+
+_cf._ https://pyfpdf.github.io/fpdf2/Maths.html#using-pandas
+
+## Using write_html
+
+Tables can also be defined in HTML using [`FPDF.write_html`](HTML.md).
+With the same `data` as above, and column widths defined as percent of the effective width:
```python
from fpdf import FPDF
@@ -46,18 +228,18 @@ pdf.set_font_size(16)
pdf.add_page()
pdf.write_html(
f"""
- {data[0][0]} |
- {data[0][1]} |
- {data[0][2]} |
- {data[0][3]} |
+ {TABLE_DATA[0][0]} |
+ {TABLE_DATA[0][1]} |
+ {TABLE_DATA[0][2]} |
+ {TABLE_DATA[0][3]} |
- {' | '.join(data[1])} |
+ {' | '.join(TABLE_DATA[1])} |
- {' | '.join(data[2])} |
+ {' | '.join(TABLE_DATA[2])} |
- {' | '.join(data[3])} |
+ {' | '.join(TABLE_DATA[3])} |
- {' | '.join(data[4])} |
+ {' | '.join(TABLE_DATA[4])} |
""",
table_line_separators=True,
)
@@ -66,55 +248,14 @@ pdf.output('table_html.pdf')
Note that `write_html` has [some limitations, notably regarding multi-lines cells](HTML.html#supported-html-features).
+## "Parsabilty" of the tables generated
-## Recipes ##
+The PDF file format is not designed to embed structured tables.
+Hence, it can be tricky to extract tables data from PDF documents.
-- our 5th tutorial provides examples on how to build tables: [Tuto 5 - Creating Tables](Tutorial.md#tuto-5-creating-tables)
-- `@bvalgard` wrote a custom `table()` method: [YouTube video](https://www.youtube.com/watch?v=euNvxWaRQMY) - [`create_table()` source code](https://github.com/bvalgard/create-pdf-with-python-fpdf2/blob/main/create_table_fpdf2.py)
-- [code snippet by @RubendeBruin to adapt row height to the highest cell](https://github.com/PyFPDF/fpdf2/issues/91#issuecomment-813033012)
-- detect if adding a table row will result in a page break: this can be done using [`.offset_rendering()`](https://pyfpdf.github.io/fpdf2/PageBreaks.html#unbreakable-sections)
+In our tests suite, we ensure that several PDF-tables parsing Python libraries can successfully extract tables in documents generated with `fpdf2`.
+Namely, we test [camelot-py](https://camelot-py.readthedocs.io) & [tabula-py](https://tabula-py.readthedocs.io): [test/table/test_table_extraction.py](https://github.com/PyFPDF/fpdf2/blob/master/test/table/test_table_extraction.py).
-
-## Repeat table header on each page ##
-
-The following recipe demonstrates a solution to handle this requirement:
-
-```python
-from fpdf import FPDF
-
-TABLE_COL_NAMES = ("First name", "Last name", "Age", "City")
-TABLE_DATA = (
- ("Jules", "Smith", "34", "San Juan"),
- ("Mary", "Ramos", "45", "Orlando"),
- ("Carlson", "Banks", "19", "Los Angeles"),
- ("Lucas", "Cimon", "31", "Angers"),
-)
-
-pdf = FPDF()
-pdf.add_page()
-pdf.set_font("Times", size=16)
-line_height = pdf.font_size * 2
-col_width = pdf.epw / 4 # distribute content evenly
-
-def render_table_header():
- pdf.set_font(style="B") # enabling bold text
- for col_name in TABLE_COL_NAMES:
- pdf.cell(col_width, line_height, col_name, border=1)
- pdf.ln(line_height)
- pdf.set_font(style="") # disabling bold text
-
-render_table_header()
-for _ in range(10): # repeat data rows
- for row in TABLE_DATA:
- if pdf.will_page_break(line_height):
- render_table_header()
- for datum in row:
- pdf.cell(col_width, line_height, datum, border=1)
- pdf.ln(line_height)
-
-pdf.output("table_with_headers_on_every_page.pdf")
-```
-
-Note that if you want to use [`multi_cell()`](fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) method instead of `cell()`,
-some extra code will be required: an initial call to `multi_cell` with `split_only=True`
-will be needed in order to compute the number of lines in the cell.
+Based on those tests, if you want to ease table extraction from the documents you produce, we recommend the following guidelines:
+* avoid splitting tables on several pages
+* avoid the `INTERNAL` / `MINIMAL` / `SINGLE_TOP_LINE` borders layouts
diff --git a/docs/Tutorial-de.md b/docs/Tutorial-de.md
index 56d4d3c32..0d0e0388a 100644
--- a/docs/Tutorial-de.md
+++ b/docs/Tutorial-de.md
@@ -144,10 +144,6 @@ Sobald det Text der dritten den oben beschriebenen Abstand zum Seitenende erreic
## Lektion 5 - Tabellen erstellen ##
-In dieser Lektion zeigen wir, wie man auf einfache Weise Tabellen erstellen kann.
-
-Der Code wird drei verschiedene Tabellen erstellen, um zu zeigen, welche Effekte wir mit einigen einfachen Anpassungen erzielen können.
-
```python
{% include "../tutorial/tuto5.py" %}
```
@@ -155,18 +151,12 @@ Der Code wird drei verschiedene Tabellen erstellen, um zu zeigen, welche Effekte
[Erzeugtes PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Länder](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-Da eine Tabelle lediglich eine Sammlung von Zellen darstellt, ist es naheliegend, eine Tabelle aus den bereits bekannten Zellen aufzubauen.
-
-Das erste Beispiel wird auf die einfachste Art und Weise realisiert. Einfach gerahmte Zellen, die alle die gleiche Größe haben und linksbündig ausgerichtet sind. Das Ergebnis ist rudimentär, aber sehr schnell zu erzielen.
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-Die zweite Tabelle bringt einige Verbesserungen: Jede Spalte hat ihre eigene Breite,
- die Überschriften sind zentriert und die Zahlen rechtsbündig ausgerichtet. Außerdem wurden die horizontalen Linien
- entfernt. Dies geschieht mit Hilfe des Randparameters der Methode `cell()`, der angibt, welche Seiten der Zelle gezeichnet werden müssen.
- Im Beispiel wählen wir die linke (L) und die rechte (R) Seite. Jetzt muss nur noch das Problem der horizontalen Linie
- zum Abschluss der Tabelle gelöst werden. Es gibt zwei Möglichkeiten, es zu lösen: In der Schleife prüfen, ob wir uns in der letzten Zeile befinden und dann "LRB" als Rahmenparameter übergeben oder, wie hier geschehen, eine abschließende Zelle separat nach dem Durchlaufen der Schleife einfügen.
+English versions:
-Die dritte Tabelle der zweiten sehr ähnlich, verwendet aber zusätzlich Farben. Füllung, Text und
- Linienfarben werden einfach mit den entsprechenden Methoden gesetzt. Eine wechselnde Färbung der Zeilen wird durch die abwechselnde Verwendung transparenter und gefüllter Zellen erreicht.
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Lektion 6 - Links erstellen und Textstile mischen ##
diff --git a/docs/Tutorial-es.md b/docs/Tutorial-es.md
index 7a17f1efc..20fc0c460 100644
--- a/docs/Tutorial-es.md
+++ b/docs/Tutorial-es.md
@@ -169,11 +169,6 @@ volverá a la primera columna, desencadenando un salto de página.
## Tutorial 5 - Creando tablas ##
-Este tutorial explicará cómo crear tablas fácilmente.
-
-El código creará tres tablas diferentes para explicar lo que
-puede lograrse con algunos cambios sencillos.
-
```python
{% include "../tutorial/tuto5.py" %}
```
@@ -181,25 +176,12 @@ puede lograrse con algunos cambios sencillos.
[PDF resultante](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Archivo de texto con países](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-Dado que una tabla es solo una colección de celdas, es natural construir una
-a partir de ellas.
-
-El primer ejemplo se logra de la forma más básica posible: simples celdas
-enmarcadas, todas del mismo tamaño y alineadas a la izquierda. El resultado es rudimentario pero
-muy rápido de obtener.
-
-La segunda tabla incluye algunas mejoras: cada columna tiene su propio ancho,
-los títulos están centrados y las figuras alineadas a la derecha. Es más, las líneas horizontales han
-sido removidas. Esto es hecho por medio del parámetro `border` del método
-`Cell()`, el cual especifica qué lados de la celda deben dibujarse. Aquí queremos
-el izquierdo (`L`) y el derecho (`R`). Ahora solo queda el problema de la línea horizontal
-para terminar la tabla. Hay dos posibilidades para resolverlo: encontrar
-la última línea en el ciclo, en cuyo caso usamos LRB para el parámetro
-`border`; o, como se hizo aquí, agregar la línea una vez el ciclo ha terminado.
-
-La tercera tabla es similar a la segunda, pero usa colores. Los colores de relleno,
-texto y línea son especificados de manera simple. Un coloreado alternante para las filas es obtenido
-usando de forma alternada celdas transparentes y rellenas.
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
+
+English versions:
+
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Tutorial 6 - Creando enlaces y combinando estilos de texto ##
diff --git a/docs/Tutorial-fr.md b/docs/Tutorial-fr.md
index c6687a679..c67d71f65 100644
--- a/docs/Tutorial-fr.md
+++ b/docs/Tutorial-fr.md
@@ -110,24 +110,21 @@ En utilisant la méthode [accept_page_break](fpdf/fpdf.html#fpdf.fpdf.FPDF.accep
Une fois que la limite inférieure de la troisième colonne est atteinte, la méthode [accept_page_break](fpdf/fpdf.html#fpdf.fpdf.FPDF.accept_page_break) sera réinitialisée et retournera à la première colonne. Cela déclenchera un saut de page.
## Tuto 5 - Créer des tables ##
-Ce tutoriel explique comment créer facilement des tableaux.
-
-Le code créera trois tableaux différents pour expliquer ce qui peut être réalisé avec quelques modifications.
+Ce tutoriel explique comment créer facilement des tableaux. Deux tableaux différents sont générés, pour illustrer ce qui peut être produit avec de très simples changements.
```python
{% include "../tutorial/tuto5.py" %}
```
[PDF généré](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
-[Liste de pays](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-
-Comme un tableau n'est qu'une collection de cellules, il est naturel d'en construire un à partir de celles-ci.
-
-Le premier exemple est réalisé de la manière la plus basique qui soit : de simples cellules encadrées, toutes de même taille et alignées à gauche. Le résultat est rudimentaire mais très rapide à obtenir.
+[Données CSV des pays](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-Le deuxième tableau apporte quelques améliorations : chaque colonne possède sa propre largeur, les titres sont centrés et les chiffres alignés à droite. De plus, les lignes horizontales ont été supprimées. C'est fait grâce au paramètre `border` de la méthode [`.cell()`](fpdf/fpdf.html#fpdf.fpdf.FPDF.cell) qui spécifie quels côtés de la cellule doivent être dessinés. Ici, nous voulons les côtés gauche (`L`) et droit (`R`). Il ne reste plus que le problème de la ligne horizontale pour terminer le tableau. Il y a deux possibilités pour le résoudre : vérifier la dernière ligne dans la boucle (dans ce cas nous utilisons `LRB` pour le paramètre de bordure) ou, comme fait ici, ajouter la ligne une fois la boucle terminée.
+Le premier exemple est généré de la façon la plus simple possible, en fournissant des données à [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/Tables.html). Le résultat est rudimentaire, mais très rapide à obtenir.
-Le troisième tableau est similaire au deuxième mais utilise des couleurs. Les couleurs de remplissage, de texte et de ligne sont simplement spécifiées. Une coloration alternative pour les lignes est obtenue en utilisant des cellules alternativement transparentes et remplies.
+Le second tableau introduit quelques améliorations : couleurs, largeur réduite de la table, moindre hauteur des lignes de texte, titres centrés, colonnes avec des largeurs propres, nombres alignés à droite...
+De plus, les lignes horizontales ont été supprimées.
+Cela grâce à la sélection d'un `borders_layout` parmi les valeurs disponibles :
+ [`TableBordersLayout`](https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.TableBordersLayout).
## Tuto 6 - Créer des liens et mélanger différents styles de textes ##
Ce tutoriel explique plusieurs façons d'insérer des liens à l'intérieur d'un document pdf, ainsi que l'ajout de liens vers des sources externes.
diff --git a/docs/Tutorial-gr.md b/docs/Tutorial-gr.md
index c80460a09..f25e80198 100644
--- a/docs/Tutorial-gr.md
+++ b/docs/Tutorial-gr.md
@@ -109,10 +109,6 @@ pdf.cell(60, 10, 'Powered by FPDF.', new_x="LMARGIN", new_y="NEXT", align='C')
## Μάθημα 5 - Δημιουργία Πινάκων ##
-Σε αυτό το μάθημα θα εξηγήσουμε πως να δημιουργούμε εύκολα πίνακες.
-
-Ο κώδικας θα δημιουργήσει τρεις διαφορετικούς πίνακες έτσι ώστε να παρουσιάσουμε τι μπορεί να επιτευχθεί με μερικές απλές προσαρμογές.
-
```python
{% include "../tutorial/tuto5.py" %}
```
@@ -120,13 +116,12 @@ pdf.cell(60, 10, 'Powered by FPDF.', new_x="LMARGIN", new_y="NEXT", align='C')
[Παραγόμενο PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Κείμενο Χωρών](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-Εφόσον ένας πίνακας αποτελεί μία συλλογή από κελιά, είναι φυσικό να τον κατασκευάσουμε από αυτά.
-
-Το πρώτο παράδειγμα επιτυγχάνεται με τον πιο απλό τρόπο: πλαισιωμένα κελιά, ίδιου μεγέθους και αριστερά στοιχισμένα. Το αποτέλεσμα είναι υποτυπώδες αλλά αποκτάται αρκετά εύκολα.
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-Ο δεύτερος πίνακας παρουσιάζει ορισμένες βελτιώσεις: κάθε στήλη έχει το δικό της πλάτος, οι τίτλοι είναι κεντραρισμένοι και οι αριθμοί δεξιά στοιχισμένοι. Επιπλέον, οριζόντιες γραμμές έχουν αφαιρεθεί. Αυτό επιτυγχάνεται με την παράμετρο border της μεθόδου Cell(), η οποία ορίζει ποιες πλευρές του κελιού χρειάζεται να σχεδιαστούν. Στη συγκεκριμένη περίπτωση θέλουμε τις αριστερές (L) και τις δεξιές (R). Τώρα απομένει μόνο το πρόβλημα των οριζόντιων γραμμών. Μπορούμε να το λύσουμε με δύο τρόπους: να ελέγξουμε την τελευταία γραμμή στο βρόχο επαναλήψεων, οπότε θα χρησιμοποιήσουμε LRB για την παράμετρο border ή, όπως πράξαμε εδώ, να προσθέσουμε την γραμμή όταν τελειώσει ο βρόχος επαναλήψεων.
+English versions:
-Ο τρίτος πίνακας είναι παρόμοιος με τον δεύτερο αλλά χρησιμοποιεί χρώματα. Τα χρώματα του γεμίσματος, του κειμένου και των γραμμών ορίζονται ξεχωριστά. Ο εναλλασσόμενος χρωματισμός των γραμμών του πίνακα επιτυγχάνεται με τη χρήση διαφανών και γεμισμένων κελιών.
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Μάθημα 6 - Δημιουργία συνδέσμων και μίξη στυλ κειμένου ##
diff --git a/docs/Tutorial-he.md b/docs/Tutorial-he.md
index 59d6e7308..f0476ec94 100644
--- a/docs/Tutorial-he.md
+++ b/docs/Tutorial-he.md
@@ -114,24 +114,16 @@ pdf.cell(60, 10, 'Powered by FPDF.', new_x="LMARGIN", new_y="NEXT", align='C')
## 5 - יצירת טבלאות ##
-דוגמא זו מסבירה איך ליצור טבלאות בקלות.
-
-הקוד ייצור שלוש טבלאות שונות על מנת להראות מה ניתן להשיג עם שינוים קלים.
-
```python
{% include "../tutorial/tuto5.py" %}
```
-[תוצר](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
-[טקסט מתמשך](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-
-מאחר וטבלה היא בסה"כ אוסף של תאים, טבעי שכך נבנה טבלאות.
-
-הדוגמא הראשונה נוצרת באופן הבסיסי ביותר שאפשר:תאים ממוסגרים, מיושרים לשמאל ובגדלים שווים. התוצאה היא בסיסית אבל קלה מאוד להשגה.
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-הטבלה השניה מציגה כמה שיפורים: לכל עמודה יש רוחב משלה, כותרות ממורכזות ותמונות מיושרות לימין. הוסרו קווים אופקיים. זה נעשה על ידי תכונות הגבול של המתודה Cell(), שמציינת איזה גבולות של התא להדפיס. כאן אנחנו רוצים את הגבול השמאלי (L) והימני (R). כעת נותרה הבעיה של הקווים האופקיים. ישנן שתי אפשרויות לפתור בעיה זו: לבדוק את הקו האחרון בלולאה, במקרה זה נשתמש בLRB עבור פרמטר הגבול; או לחלופין, כמו שעשינו כאן, להוסיף את הקו בסוף הלולאה.
+English versions:
-הטבלה השלישית דומה לשניה אבל עושה שימוש בצבעים. צבעי המילוי, טקסט והקווים מצויינים במפורש. שינוי הצבעים נעשה על ידי שימוש בתאים שקופים ומלאים לסירוגין.
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## 6 - יצירת קישורים וערבוב סגנונות טקסט ##
diff --git a/docs/Tutorial-it.md b/docs/Tutorial-it.md
index f19d0f6f9..bde3bfb9c 100644
--- a/docs/Tutorial-it.md
+++ b/docs/Tutorial-it.md
@@ -130,10 +130,6 @@ Una volta che il limite inferiore della terza colonna sarà raggiunto, [accept_p
## Tuto 5 - Creare tabelle ##
-Questo tutoria spiegherà come creare facilmente tabelle.
-
-Creeremo tre diverse tabelle per spiegare cosa si può ottenere con piccolo cambiamenti.
-
```python
{% include "../tutorial/tuto5.py" %}
```
@@ -141,13 +137,12 @@ Creeremo tre diverse tabelle per spiegare cosa si può ottenere con piccolo camb
[Risultato PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Testo delle nazioni](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-Dato che una tabella è un insieme di celle, viene natura crearne una partendo da loro.
-
-Il primo esempio è la via più elementare: semplici celle con cornice, tutte della stessa dimensione e allineate a sinistra. Il risultato è rudimentale ma molto veloce da ottenere.
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-La seconda tabella contiene dei miglioramenti: ogni colonna ha la propria larghezza, i titoli sono centrati e i numeri allineati a destra. Inoltre, le linee orizzontale sono state rimosse. Questo è stato possibile grazie al parametro border del metodo Cell(), che specifica quali lati della cella saranno disegnati. In questo caso vogliamo il sinistro (L) e il destro (R). Rimane il problema delle linee orizzontali. Ci sono due possibilità per risolverlo: controllare di essere nell'ultimo giro del ciclo, nel qual caso utilizziamo LRB per il parametro border; oppure, come fatto in questo esempio, aggiungiamo una linea dopo il completamento del ciclo.
+English versions:
-La terza tabella è molto simile alla seconda, ma utilizza i colori. Il colore di sfondo, testo e linee sono semplicemente specificati. L'alternanza dei colori delle righe è ottenuta utilizzando celle con sfondo colorato e trasparente alternativamente.
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Tuto 6 - Creare link e mescolare stili di testo ##
diff --git a/docs/Tutorial-pt.md b/docs/Tutorial-pt.md
index 6064beb42..6e66b2b0c 100644
--- a/docs/Tutorial-pt.md
+++ b/docs/Tutorial-pt.md
@@ -125,10 +125,6 @@ Quando o limite inferior da terceira coluna é alcançado, o método [accept_pag
## Tuto 5 - Criar Tabelas ##
-Este tutorial irá explicar como criar tabelas facilmente.
-
-O código seguinte cria três tabelas diferentes para explicar o que pode ser alcançado com alguns ajustes simples.
-
```python
{% include "../tutorial/tuto5.py"%}
```
@@ -136,16 +132,12 @@ O código seguinte cria três tabelas diferentes para explicar o que pode ser al
[PDF resultante](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Texto dos países](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-Uma vez que uma tabela é apenas uma coleção de células, é natural construir uma
-a partir delas.
-
-O primeiro exemplo é obtido da maneira mais básica possível: moldura simples
- células, todas do mesmo tamanho e alinhadas à esquerda. O resultado é rudimentar, mas
- muito rápido de obter.
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-A segunda tabela traz algumas melhorias: cada coluna tem sua largura própria, os títulos estão centrados e as figuras alinhadas à direita. Além disso, as linhas horizontais foram removidas. Isto é feito por meio do parâmetro border do método Cell(), que especifica quais lados da célula devem ser desenhados. Aqui nós queremos os esquerdo (L) e direito (R). Agora apenas o problema da linha horizontal para terminar a mesa permanece. Existem duas possibilidades para resolvê-lo: verificar para a última linha do loop, caso este em que usamos LRB para o parâmetro da borda; ou, como foi feito aqui, adicione a linha assim que o loop terminar.
+English versions:
-A terceira tabela é semelhante à segunda, mas usa cores. Preenchimento, texto e as cores das linhas são simplesmente especificadas. Coloração alternativa para linhas é obtida usando células alternativamente transparentes e preenchidas.
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Tuto 6 - Criar links e misturar estilos de texto ##
diff --git a/docs/Tutorial-ru.md b/docs/Tutorial-ru.md
index 1712f0f6c..0601a56d2 100644
--- a/docs/Tutorial-ru.md
+++ b/docs/Tutorial-ru.md
@@ -105,24 +105,18 @@ pdf.cell(60, 10, 'Powered by FPDF.', new_x="LMARGIN", new_y="NEXT", align='C')
## Руковдство 5 - Создание таблиц ##
-В этом уроке мы расскажем, как можно с легкостью создавать таблицы.
-
-Код создаст три различные таблицы, чтобы объяснить, чего можно достичь с помощью нескольких простых настроек.
-
```python
{% include "../tutorial/tuto5.py" %}
```
[Итоговый PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Список стран](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-Поскольку таблица - это просто набор ячеек, естественно построить таблицу из них.
-
-Первый пример достигается самым простым способом: простые ячейки в рамке, все одинакового размера и выровненные по левому краю. Результат элементарен, но достигается очень быстро.
-
-Вторая таблица имеет некоторые улучшения: каждый столбец имеет свою ширину, заголовки выровнены по центру, а цифры - по правому краю. Более того, горизонтальные линии были удалены. Это сделано с помощью параметра border метода Cell(), который указывает, какие стороны ячейки должны быть нарисованы. Здесь нам нужны левая (L) и правая (R). Теперь остается только проблема горизонтальной линии для завершения таблицы. Есть две возможности решить ее: проверить наличие последней строки в цикле, в этом случае мы используем LRB для параметра границы; или, как сделано здесь, добавить линию после завершения цикла.
+English versions:
-Третья таблица похожа на вторую, но в ней используются цвета. Цвета заливки, текста и линий просто задаются. Альтернативная окраска строк достигается путем использования поочередно прозрачных и заполненных ячеек.
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Руководство 6 - Создание ссылок и смешивание стилей текста ##
diff --git a/docs/Tutorial-zh.md b/docs/Tutorial-zh.md
index 55985fad1..fd85d992f 100644
--- a/docs/Tutorial-zh.md
+++ b/docs/Tutorial-zh.md
@@ -177,10 +177,6 @@ pdf.cell(60, 10, 'Powered by FPDF.', new_x="LMARGIN", new_y="NEXT", align='C')
## 教程五 - 创建表 ##
-本教程将演示如何创建表。
-
-该代码创建了三个不同的表,演示了几个简单的表格配置。
-
```python
{% include "../tutorial/tuto5.py" %}
```
@@ -188,23 +184,12 @@ pdf.cell(60, 10, 'Powered by FPDF.', new_x="LMARGIN", new_y="NEXT", align='C')
[生成的 PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[源文本](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-表格只是单元格的集合,因此由单元格便可构建一个表格。
-
-第一张表以最基本的方式实现:带框的单元格,大小相同且左对齐。
-虽然简陋,但操作便捷。
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-第二张表进行了一些改进:每列都设定了宽度,标题居中,数字右对齐。
-此外,水平线也被删除。
-上述设定由`Cell()` 的边界参数完成,在参数中指定需要绘制的
-单元格边框。
-在示例中,需要绘制左侧 (`L`) 和右侧 (`R`) 的边框。
-水平的边框可使用两种方法设置:
-对于循环中的最后一行,使用 `LRB` 作为边界参数;
-或者,如示例中,在循环结束后添加一行。
+English versions:
-第三张表与第二张表类似,但设置了颜色。
-填充、文本和线条的颜色直接指定即可。行间的间隔着色可以
-通过交替使用透明和填充的单元格实现。
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## 教程六 - 创建链接和混合文本样式 ##
diff --git "a/docs/Tutorial-\340\244\271\340\244\277\340\244\202\340\244\246\340\245\200.md" "b/docs/Tutorial-\340\244\271\340\244\277\340\244\202\340\244\246\340\245\200.md"
index f64e976bd..e481ccc0e 100644
--- "a/docs/Tutorial-\340\244\271\340\244\277\340\244\202\340\244\246\340\245\200.md"
+++ "b/docs/Tutorial-\340\244\271\340\244\277\340\244\202\340\244\246\340\245\200.md"
@@ -154,10 +154,6 @@ Logo को निर्दिष्ट करके [image](fpdf/fpdf.html#fpdf
## Tuto 5 - टेबल बनाना ##
-यह ट्यूटोरियल समझाएगा कि टेबल को आसानी से कैसे बनाया जाए।
-
-कुछ सरल समायोजनों के साथ क्या हासिल किया जा सकता है, यह समझाने के लिए कोड तीन अलग-अलग टेबल बनाएगा।
-
```python
{% include "../tutorial/tuto5.py" %}
```
@@ -165,20 +161,12 @@ Logo को निर्दिष्ट करके [image](fpdf/fpdf.html#fpdf
[Resulting PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
[Countries text](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-चूंकि तालिका (Table) केवल कोशिकाओं (Cells) का एक संग्रह (Collection) है, इसलिए उनमें से एक का निर्माण करना स्वाभाविक है।
+This section has changed a lot and requires a new translation: https://github.com/PyFPDF/fpdf2/issues/267
-पहला उदाहरण सबसे बुनियादी संभव तरीके से हासिल किया गया है: सरल फ़्रेमयुक्त कोशिकाएं (simple framed cells, सभी समान आकार कोशिकाएं (same sized cells) और बाएं संरेखित कोशिकाएं (left aligned cells)।
-परिणाम अल्पविकसित है लेकिन प्राप्त करने के लिए बहुत जल्दी है।
-
-दूसरी तालिका कुछ सुधार लाती है: प्रत्येक कॉलम की अपनी चौड़ाई होती है, शीर्षक केंद्रित होते हैं और आंकड़े सही संरेखित होते हैं। इसके अलावा, क्षैतिज रेखाओं को हटा दिया गया है।
-यह Cell() विधि के बॉर्डर पैरामीटर के माध्यम से किया जाता है, जो निर्दिष्ट करता है कि सेल के किन पक्षों को खींचा जाना चाहिए।
-यहां हम बाएं (L) और दाएं (R) वाले चाहते हैं।
-अब केवल क्षैतिज रेखा की तालिका (Table) को Finish करने की समस्या बनी हुई है।
-
- इसे हल करने की दो संभावनाएं हैं: लूप में अंतिम पंक्ति की जाँच करें, जिस स्थिति में हम सीमा पैरामीटर के लिए LRB का उपयोग करते हैं; या जैसा कि यहां किया गया है, लूप खत्म होने के बाद लाइन जोड़ें।
+English versions:
-तीसरी तालिका दूसरे के समान है लेकिन रंगों का उपयोग करती है। Fill, टेक्स्ट और लाइन रंग बस निर्दिष्ट हैं।
-वैकल्पिक(Alternate) रूप से पारदर्शी और भरी हुई कोशिकाओं का उपयोग करके पंक्तियों के लिए वैकल्पिक रंग प्राप्त किया जाता है।
+* [Tuto 5 - Creating Tables](https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables)
+* [Documentation on tables](https://pyfpdf.github.io/fpdf2/Tables.html)
## Tuto 6 - लिंक बनाना और टेक्स्ट शैलियों को मिलाना ##
diff --git a/docs/Tutorial.md b/docs/Tutorial.md
index 8142de057..8014802af 100644
--- a/docs/Tutorial.md
+++ b/docs/Tutorial.md
@@ -169,37 +169,23 @@ back to the first column and trigger a page break.
## Tuto 5 - Creating Tables ##
-This tutorial will explain how to create tables easily.
-
-The code will create three different tables to explain what
- can be achieved with some simple adjustments.
+This tutorial will explain how to create two different tables,
+ to demonstrate what can be achieved with some simple adjustments.
```python
{% include "../tutorial/tuto5.py" %}
```
[Resulting PDF](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/tuto5.pdf) -
-[Countries text](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
-
-Since a table is just a collection of cells, it is natural to build one
- from them.
-
-The first example is achieved in the most basic way possible: simple framed
- cells, all of the same size and left aligned. The result is rudimentary but
- very quick to obtain.
-
-The second table brings some improvements: each column has its own width,
- titles are centered and figures right aligned. Moreover, horizontal lines have
- been removed. This is done by means of the border parameter of the `Cell()`
- method, which specifies which sides of the cell must be drawn. Here we want
- the left (`L`) and right (`R`) ones. Now only the problem of the horizontal line
- to finish the table remains. There are two possibilities to solve it: check
- for the last line in the loop, in which case we use LRB for the border
- parameter; or, as done here, add the line once the loop is over.
-
-The third table is similar to the second one but uses colors. Fill, text and
- line colors are simply specified. Alternate coloring for rows is obtained by
- using alternatively transparent and filled cells.
+[Countries CSV data](https://github.com/PyFPDF/fpdf2/raw/master/tutorial/countries.txt)
+
+The first example is achieved in the most basic way possible, feeding data to [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/Tables.html). The result is rudimentary but very quick to obtain.
+
+The second table brings some improvements: colors, limited table width, reduced line height,
+ centered titles, columns with custom widths, figures right aligned...
+ Moreover, horizontal lines have been removed.
+ This was done by picking a `borders_layout` among the available values:
+ [`TableBordersLayout`](https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.TableBordersLayout).
## Tuto 6 - Creating links and mixing text styles ##
diff --git a/docs/index.md b/docs/index.md
index 57ba123ec..628463800 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -30,7 +30,7 @@ Go try it **now** online in a Jupyter notebook: [![Open In Colab](https://colab.
* Embedding images, including transparency and alpha channel, using [Pillow (Python Imaging Library)](https://pillow.readthedocs.io/en/stable/)
* Arbitrary path drawing and basic [SVG](SVG.md) import
* Embedding [barcodes](Barcodes.md), [charts & graphs](Maths.md), [emojis, symbols & dingbats](EmojisSymbolsDingbats.md)
-* [Cell / multi-cell / plaintext writing](Text.md), with [automatic page breaks](PageBreaks.md), line break and text justification
+* [Tables](Tables.md), and also [cell / multi-cell / plaintext writing](Text.md), with [automatic page breaks](PageBreaks.md), line break and text justification
* Choice of measurement unit, page format & margins. Optional page header and footer
* Basic [conversion from HTML to PDF](HTML.md)
* A [templating system](Templates.md) to render PDFs in batchs
diff --git a/docs/table-pandas.png b/docs/table-pandas.png
index ee350d41ee365ffca045ec23a41130c2750ac555..d0da1d064a17eef97bbe862179a7af4e56314483 100644
GIT binary patch
literal 23645
zcmeIac|4YF+cv5Z6`GX{O)5$vDA2+Pt)EBYkEy&po>*dv}-LTlc6T;M%K(0=u|W2kPF1`?
z)%ha-bMv`-Yc6a(@<_*{XJKH&)RoNraT_{G;jL3FoKs>#1>r{y4o1w5Q>h%ScnCVW
z(+@M~|NQ#sKfnI}WgXtHT9}<+VPq7u9|=@nwZfE=VUVO2e{0?D3#6GQby@GNHtXol
z(Uo%2vAF#D_Rz7@=F{?G&$5`v#wTkx?6^4?=_tNt{I3}sfVaG^+u>R32ITl`{^3RW4&NjQaINHQ5|5`g+{$Z+4-eeSc+-W2!
zF=j(2!==AiY0MnrP48^3C+eRyYl!>ez3p&BSH>0tez)n|W5zY?iSJY8C_Y8j#d8T4
zQ#9K$jav_Oy;6;x?5~_1s=q!v-nF8hHiU-?
AH-`v(ll=NO=@|r
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
literal 12279
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
diff --git a/docs/table-simple.jpg b/docs/table-simple.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cb0f0ae2c12f91d68153ca9e505ff900a7ecf0e2
GIT binary patch
literal 24560
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