diff --git a/.gitattributes b/.gitattributes
index da9b47531..30f8bc4b1 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -5,6 +5,7 @@
*.gz binary
*.pdf binary
*.p12 binary
+*.ods binary
*.otf binary
*.ttf binary
*.TTF binary
@@ -13,3 +14,4 @@
*.gif binary
*.png binary
*.webp binary
+*.xlsx binary
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0bbf715dc..a8a3a345e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): now parses `
` 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)
* support for [page labels](https://py-pdf.github.io/fpdf2/PageLabels.html) and created a [reference table of contents](https://py-pdf.github.io/fpdf2/DocumentOutlineAndTableOfContents.html) implementation
+* documentation on how to: [render spreadsheets as PDF tables](https://py-pdf.github.io/fpdf2/RenderingSpreadsheetsAsPDFTables.html)
### Fixed
* support for `align=` in [`FPDF.table()`](https://py-pdf.github.io/fpdf2/Tables.html#setting-table-column-widths). Due to this correction, tables are now properly horizontally aligned on the page by default. This was always specified in the documentation, but was not in effect until now. You can revert to have left-aligned tables by passing `align="LEFT"` to `FPDF.table()`.
* `FPDF.set_text_shaping(False)` was broken since version 2.7.8 and is now working properly - [issue #1287](https://github.com/py-pdf/fpdf2/issues/1287)
diff --git a/docs/RenderingSpreadsheetsAsPDFTables.md b/docs/RenderingSpreadsheetsAsPDFTables.md
new file mode 100644
index 000000000..f1cf1c507
--- /dev/null
+++ b/docs/RenderingSpreadsheetsAsPDFTables.md
@@ -0,0 +1,21 @@
+# Rendering spreadsheets as PDF tables
+
+
+
+## From a .csv spreadsheet
+Example input file: [color_srgb.csv](../tutorial/color_srgb.csv)
+```python
+{% include "../tutorial/csv2table.py" %}
+```
+
+## From a .xlsx spreadsheet
+Example input file: [color_srgb.xlsx](../tutorial/color_srgb.xlsx)
+```python
+{% include "../tutorial/xlsx2table.py" %}
+```
+
+## From an .ods spreadsheet
+Example input file: [color_srgb.ods](../tutorial/color_srgb.ods)
+```python
+{% include "../tutorial/ods2table.py" %}
+```
diff --git a/docs/table-from-spreadsheet.png b/docs/table-from-spreadsheet.png
new file mode 100644
index 000000000..c5448d906
Binary files /dev/null and b/docs/table-from-spreadsheet.png differ
diff --git a/mkdocs.yml b/mkdocs.yml
index dbd8664af..c83d8dca5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -173,9 +173,10 @@ nav:
- 'Combine with pypdf': 'CombineWithPypdf.md'
- 'Combine with pdfrw': 'CombineWithPdfrw.md'
- 'Matplotlib, Pandas, Plotly, Pygal': 'CombineWithChartingLibs.md'
+ - 'Usage in web APIs': 'UsageInWebAPI.md'
+ - 'Rendering spreadsheets as PDF tables': 'RenderingSpreadsheetsAsPDFTables.md'
- 'Combine with Rough.js': 'CombineWithRoughJS.md'
- 'Templating with Jinja': 'TemplatingWithJinja.md'
- - 'Usage in web APIs': 'UsageInWebAPI.md'
- 'Database storage': 'DatabaseStorage.md'
- 'Development':
- 'Development guidelines': 'Development.md'
diff --git a/tutorial/color_srgb.csv b/tutorial/color_srgb.csv
new file mode 100644
index 000000000..4d4259d56
--- /dev/null
+++ b/tutorial/color_srgb.csv
@@ -0,0 +1,17 @@
+Name,HEX,RGB
+White,#FFFFFF,"rgb(100,100,100)"
+Silver,#C0C0C0,"rgb(75,75,75)"
+Gray,#808080,"rgb(50,50,50)"
+Black,#000000,"rgb(0,0,0)"
+Red,#FF0000,"rgb(100,0,0)"
+Maroon,#800000,"rgb(50,0,0)"
+Yellow,#FFFF00,"rgb(100,100,0)"
+Olive,#808000,"rgb(50,50,0)"
+Lime,#00FF00,"rgb(0,100,0)"
+Green,#008000,"rgb(0,50,0)"
+Aqua,#00FFFF,"rgb(0,100,100)"
+Teal,#008080,"rgb(0,50,50)"
+Blue,#0000FF,"rgb(0,0,100)"
+Navy,#000080,"rgb(0,0,50)"
+Fuchsia,#FF00FF,"rgb(100,0,100)"
+Purple,#800080,"rgb(50,0,50)"
\ No newline at end of file
diff --git a/tutorial/color_srgb.ods b/tutorial/color_srgb.ods
new file mode 100644
index 000000000..4735611ec
Binary files /dev/null and b/tutorial/color_srgb.ods differ
diff --git a/tutorial/color_srgb.xlsx b/tutorial/color_srgb.xlsx
new file mode 100644
index 000000000..9e53ee27b
Binary files /dev/null and b/tutorial/color_srgb.xlsx differ
diff --git a/tutorial/csv2table.py b/tutorial/csv2table.py
new file mode 100755
index 000000000..b713ddfec
--- /dev/null
+++ b/tutorial/csv2table.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+# USAGE: ./csv2table.py spreadsheet.csv
+import csv, sys
+from fpdf import FPDF, FontFace
+from fpdf.drawing import color_from_hex_string
+
+pdf = FPDF()
+pdf.add_page()
+pdf.set_font("Times", size=22)
+with pdf.table() as table:
+ with open(sys.argv[1], encoding="utf-8") as csv_file:
+ reader = csv.reader(csv_file, delimiter=",")
+ for i, row in enumerate(reader):
+ # We color the row based on the hexadecimal code in the 2nd column:
+ style = FontFace(fill_color=color_from_hex_string(row[1])) if i > 0 else None
+ table.row(row, style=style)
+pdf.output("from-csv.pdf")
diff --git a/tutorial/ods2table.py b/tutorial/ods2table.py
new file mode 100755
index 000000000..576390caf
--- /dev/null
+++ b/tutorial/ods2table.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+# Script Dependencies:
+# odfpy
+# USAGE: ./ods2table.py spreadsheet.ods
+import sys
+from fpdf import FPDF, FontFace
+from fpdf.drawing import color_from_hex_string
+from odf.opendocument import load
+from odf.table import Table, TableCell, TableRow
+
+pdf = FPDF()
+pdf.add_page()
+pdf.set_font("Times", size=22)
+ods = load(sys.argv[1])
+for sheet in ods.getElementsByType(Table):
+ with pdf.table() as table:
+ for i, row in enumerate(sheet.getElementsByType(TableRow)):
+ row = [str(cell) for cell in row.getElementsByType(TableCell)]
+ # We color the row based on the hexadecimal code in the 2nd column:
+ style = FontFace(fill_color=color_from_hex_string(row[1])) if i > 0 else None
+ table.row(row, style=style)
+pdf.output("from-ods.pdf")
diff --git a/tutorial/xlsx2table.py b/tutorial/xlsx2table.py
new file mode 100755
index 000000000..db59beba7
--- /dev/null
+++ b/tutorial/xlsx2table.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# Script Dependencies:
+# openxlsx
+# USAGE: ./xlsx2table.py spreadsheet.xlsx
+import sys
+from fpdf import FPDF, FontFace
+from fpdf.drawing import color_from_hex_string
+from openpyxl import load_workbook
+
+pdf = FPDF()
+pdf.add_page()
+pdf.set_font("Times", size=22)
+wb = load_workbook(sys.argv[1])
+ws = wb.active
+with pdf.table() as table:
+ for i, row in enumerate(ws.rows):
+ # We color the row based on the hexadecimal code in the 2nd column:
+ style = FontFace(fill_color=color_from_hex_string(row[1].value)) if i > 0 else None
+ table.row([cell.value for cell in row], style=style)
+pdf.output("from-xlsx.pdf")