Skip to content

Commit 9fb2c3b

Browse files
authored
Adding documentation pages on HTML, links, tables & page breaks (#96)
* Adding documentation pages on HTML, links, tables & page breaks + introducing FPDF.epw & FPDF.eph @Property methods + refactor: introduced FPDF._perform_page_break_if_need_be + improved reference doc for FPDF.accept_page_break + fixed column layout in tuto4.py + Introducing FPDF.unbreakable context-manager + raising an error when trying to add content on the PDF after closing it + forbid invalid values for `style` paramaters that were previously silently ignored + introduced DocumentState enum to improve code readibility + removed useless FPDF.color_flag & HTML2FPDF.font_face attributes
1 parent eb2354b commit 9fb2c3b

25 files changed

+730
-97
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ and [PEP 440](https://www.python.org/dev/peps/pep-0440/).
99

1010
## [2.2.1] - Not released yet
1111
### Added
12+
- `FPDF.unbreakable` : a new method providing a context-manager in which automatic page breaks are disabled.
13+
_cf._ https://pyfpdf.github.io/fpdf2/PageBreaks.html
14+
- `FPDF.epw` & `FPDF.eph` : new `@property` methods to retrieve the **effective page width / height**, that is the page width / height minus its horizontal / vertical margins.
1215
- `FPDF.image` now accepts also a `Pillow.Image.Image` as input
1316
- `FPDF.multi_cell` parameters evolve in order to generate tables with multiline text in cells:
1417
* its `ln` parameter now accepts a value of `3` that sets the new position to the right without altering vertical offset
1518
* a new optional `max_line_height` parameter sets a maximum height of each sub-cell generated
16-
- documentation on how to add content to existing PDFs
19+
- new documentation pages : how to add content to existing PDFs, HTML, links, tables, text styling & page breaks
1720
- all PDF samples are now validated using 3 different PDF checkers
1821
### Fixed
1922
- `FPDF.alias_nb_pages`: fixed this feature that was broken since v2.0.6

docs/FAQ.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ browser, or downloaded as PDF.
120120
Also, using web2py DAL, you can easily set up a templating engine for PDF
121121
documents.
122122

123-
Look at [Web2Py] (Web2Py.md) for examples. `# TODO fix link`
123+
Look at [Web2Py](Web2Py.html) for examples.
124124

125125
## What is the development status of this library? ##
126126

docs/HTML.md

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# HTML #
2+
3+
`fpdf2` can perform rendering from HTML.
4+
5+
This is implemented by using `html.parser.HTMLParser` from the Python standard library.
6+
The whole HTML 5 specification is very likely **not** supported,
7+
but bug reports & contributions are very welcome to improve this.
8+
9+
10+
## write_html usage example ##
11+
12+
HTML rendering require the use of `fpdf.HTMLMixin`,
13+
that provides a new `write_html` method:
14+
15+
```python
16+
from fpdf import FPDF, HTMLMixin
17+
18+
class PDF(FPDF, HTMLMixin):
19+
pass
20+
21+
pdf = PDF()
22+
pdf.add_page()
23+
pdf.write_html("""
24+
<h1>Big title</h1>
25+
<section>
26+
<h2>Section title</h2>
27+
<p><b>Hello</b> world. <u>I am</u> <i>tired</i>.</p>
28+
<p><a href="https://github.com/PyFPDF/fpdf2">PyFPDF/fpdf2 GitHub repo</a></p>
29+
<p align="right">right aligned text</p>
30+
<p>i am a paragraph <br />in two parts.</p>
31+
<font color="#00ff00"><p>hello in green</p></font>
32+
<font size="7"><p>hello small</p></font>
33+
<font face="helvetica"><p>hello helvetica</p></font>
34+
<font face="times"><p>hello times</p></font>
35+
</section>
36+
<section>
37+
<h2>Other section title</h2>
38+
<ul><li>unordered</li><li>list</li><li>items</li></ul>
39+
<ol><li>ordered</li><li>list</li><li>items</li></ol>
40+
<br>
41+
<br>
42+
<pre>i am preformatted text.</pre>
43+
<br>
44+
<blockquote>hello blockquote</blockquote>
45+
<table width="50%">
46+
<thead>
47+
<tr>
48+
<th width="30%">ID</th>
49+
<th width="70%">Name</th>
50+
</tr>
51+
</thead>
52+
<tbody>
53+
<tr>
54+
<td>1</td>
55+
<td>Alice</td>
56+
</tr>
57+
<tr>
58+
<td>2</td>
59+
<td>Bob</td>
60+
</tr>
61+
</tbody>
62+
</table>
63+
</section>
64+
""")
65+
pdf.output("html.pdf")
66+
```

docs/Links.md

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Links #
2+
3+
`fpdf2` can generate both **internal** links (to other pages in the document)
4+
& **hyperlinks** (links to external URLs that will be opened in a browser).
5+
6+
7+
## Hyperlink with FPDF.cell ##
8+
9+
This method makes the whole cell clickable (not only the text):
10+
11+
```python
12+
from fpdf import FPDF
13+
14+
pdf = FPDF()
15+
pdf.add_page()
16+
pdf.set_font("helvetica", size=24)
17+
pdf.cell(w=40, h=10, txt="Cell link", border=1, align="C", link="https://github.com/PyFPDF/fpdf2")
18+
pdf.output("hyperlink.pdf")
19+
```
20+
21+
22+
## Hyperlink with FPDF.link ##
23+
24+
The `FPDF.link` is a low-level method that defines a rectangular clickable area.
25+
26+
There is an example showing how to place such rectangular link over some text:
27+
28+
```python
29+
from fpdf import FPDF
30+
31+
pdf = FPDF()
32+
pdf.add_page()
33+
pdf.set_font("helvetica", size=36)
34+
line_height = 10
35+
text = "Text link"
36+
pdf.text(x=0, y=line_height, txt=text)
37+
width = pdf.get_string_width(text)
38+
pdf.link(x=0, y=0, w=width, h=line_height, link="https://github.com/PyFPDF/fpdf2")
39+
pdf.output("hyperlink.pdf")
40+
```
41+
42+
43+
## Hyperlink with write_html ##
44+
45+
An alternative method using [`fpdf.HTMLMixin`](HTML.html):
46+
47+
```python
48+
from fpdf import FPDF, HTMLMixin
49+
50+
class PDF(FPDF, HTMLMixin):
51+
pass
52+
53+
pdf = PDF()
54+
pdf.set_font_size(16)
55+
pdf.add_page()
56+
pdf.write_html('<a href="https://github.com/PyFPDF/fpdf2">Link defined as HTML</a>')
57+
pdf.output("hyperlink.pdf")
58+
```
59+
60+
The hyperlinks defined this way will be rendered in blue with underline.
61+
62+
63+
## Internal links ##
64+
65+
Using `FPDF.cell`:
66+
67+
```python
68+
from fpdf import FPDF
69+
70+
pdf = FPDF()
71+
pdf.set_font("helvetica", size=24)
72+
pdf.add_page()
73+
pdf.cell(w=pdf.epw, h=10, txt="Welcome on first page!", align="C")
74+
pdf.add_page()
75+
link = pdf.add_link()
76+
pdf.set_link(link, page=1)
77+
pdf.cell(w=100, h=10, txt="Internal link to first page", border=1, align="C", link=link)
78+
pdf.output("internal_link.pdf")
79+
```
80+
81+
Similarly, `FPDF.link` can be instead of `FPDF.cell`,
82+
however `write_html` does not allow to define internal links.

docs/PageBreaks.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Page breaks #
2+
3+
By default, `fpdf2` will automatically perform page breaks whenever a cell is rendered at the bottom of a page
4+
with a height greater than the page bottom margin.
5+
6+
This behaviour can be controlled using the
7+
[`set_auto_page_break`](https://pyfpdf.github.io/fpdf2/reference/set_auto_page_break.html)
8+
and
9+
[`accept_page_break`](https://pyfpdf.github.io/fpdf2/reference/accept_page_break.html)
10+
methods.
11+
12+
13+
## Unbreakable sections ##
14+
15+
In order to render content, like [tables](Tables.html),
16+
with the insurance that no page break will be performed in it,
17+
on the can use the `FPDF.unbreakable()` context-manager:
18+
19+
```python
20+
pdf = fpdf.FPDF()
21+
pdf.add_page()
22+
pdf.set_font("Times", size=16)
23+
line_height = pdf.font_size * 2
24+
col_width = pdf.epw / 4 # distribute content evenly
25+
for i in range(4): # repeat table 4 times
26+
with pdf.unbreakable() as pdf:
27+
for row in data: # data comes from snippets on the Tables documentation page
28+
for datum in row:
29+
pdf.cell(col_width, line_height, f"{datum} ({i})", border=1)
30+
pdf.ln(line_height)
31+
pdf.ln(line_height * 2)
32+
pdf.("unbreakable_tables.pdf")
33+
```

docs/Tables.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Tables #
2+
3+
Tables can be built either using **cells**
4+
or with [`write_html`](HTML.html).
5+
6+
7+
## Using cells ##
8+
9+
There is a method to build tables allowing for multilines content in cells:
10+
11+
```python
12+
from fpdf import FPDF
13+
14+
data = (
15+
("First name", "Last name", "Age", "City"),
16+
("Jules", "Smith", "34", "San Juan"),
17+
("Mary", "Ramos", "45", "Orlando"),
18+
("Carlson", "Banks", "19", "Los Angeles"),
19+
("Lucas", "Cimon", "31", "Saint-Mahturin-sur-Loire"),
20+
)
21+
22+
pdf = FPDF()
23+
pdf.add_page()
24+
pdf.set_font("Times", size=10)
25+
line_height = pdf.font_size * 2.5
26+
col_width = pdf.epw / 4 # distribute content evenly
27+
for row in data:
28+
for datum in row:
29+
pdf.multi_cell(col_width, line_height, datum, border=1, ln=3, max_line_height=pdf.font_size)
30+
pdf.ln(line_height)
31+
pdf.output('table_with_cells.pdf')
32+
```
33+
34+
35+
## Using write_html ##
36+
37+
An alternative method using [`fpdf.HTMLMixin`](HTML.html),
38+
with the same `data` as above, and column widths defined as percent of the effective width:
39+
40+
```python
41+
from fpdf import FPDF, HTMLMixin
42+
43+
class PDF(FPDF, HTMLMixin):
44+
pass
45+
46+
pdf = PDF()
47+
pdf.set_font_size(16)
48+
pdf.add_page()
49+
pdf.write_html(
50+
f"""<table border="1"><thead><tr>
51+
<th width="25%">{data[0][0]}</th>
52+
<th width="25%">{data[0][1]}</th>
53+
<th width="15%">{data[0][2]}</th>
54+
<th width="35%">{data[0][3]}</th>
55+
</tr></thead><tbody><tr>
56+
<td>{'</td><td>'.join(data[1])}</td>
57+
</tr><tr>
58+
<td>{'</td><td>'.join(data[2])}</td>
59+
</tr><tr>
60+
<td>{'</td><td>'.join(data[3])}</td>
61+
</tr><tr>
62+
<td>{'</td><td>'.join(data[4])}</td>
63+
</tr></tbody></table>""",
64+
table_line_separators=True,
65+
)
66+
pdf.output('table_html.pdf')
67+
```

docs/TextStyling.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Text styling #
2+
3+
`fpdf2` supports setting _emphasis_ on text : **bold**, __italics__ or <u>underlined</u>.
4+
5+
Bold & italics require using dedicated fonts for each style.
6+
7+
For the standard fonts (Courier, Helvetica & Times), those dedicated fonts are configured by default.
8+
Using other fonts means that their variants (bold, italics)
9+
must be registered using `add_font` (with `style="B"` and `style="I"`).
10+
11+
12+
## set_font ##
13+
14+
Setting emphasis on text can be controlled by using `set_font(style=...)`
15+
16+
```python
17+
from fpdf import FPDF
18+
19+
pdf = FPDF()
20+
pdf.add_page()
21+
pdf.set_font("Times", size=36)
22+
pdf.cell(w=50, txt="This")
23+
pdf.set_font(style="B")
24+
pdf.cell(w=50, txt="is")
25+
pdf.set_font(style="I")
26+
pdf.cell(w=50, txt="a")
27+
pdf.set_font(style="U")
28+
pdf.cell(w=50, txt="PDF")
29+
pdf.output("style.pdf")
30+
```
31+
32+
33+
## write_html ##
34+
35+
[`write_html`](HTML.html) allows to set emphasis on text through the `<b>`, `<i>` and `<u>` tags:
36+
37+
```python
38+
pdf.write_html("""<B>bold</B>
39+
<I>italic</I>
40+
<U>underlined</U>
41+
<B><I><U>all at once!</U></I></B>"""
42+
)
43+
```

docs/reference/accept_page_break.md

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,39 @@
11
## accept_page_break ##
22

33
```python
4-
fpdf.accept_page_break()
4+
fpdf.accept_page_break
55
```
66

77
### Description ###
88

9-
Whenever a page break condition is met, this method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by [set_auto_page_break](set_auto_page_break.md).
9+
`@property` method to determine whether to issue automatic page break.
10+
11+
Whenever a page break condition is met, this method is called, and the break is issued or not depending on the returned value.
12+
13+
The default implementation returns a value according to the mode selected by [set_auto_page_break](set_auto_page_break.md).
1014
This method is called automatically and should not be called directly by the application.
1115

16+
### Example ###
17+
18+
From `tutorials/tuto4.py`:
19+
20+
```python
21+
class PDF(FPDF):
22+
@property
23+
def accept_page_break(self):
24+
if self.col < 2:
25+
# Go to next column:
26+
self.set_col(self.col + 1)
27+
# Set ordinate to top:
28+
self.set_y(self.y0)
29+
# Stay on the same page:
30+
return False
31+
# Go back to first column:
32+
self.set_col(0)
33+
# Trigger a page break:
34+
return True
35+
```
36+
1237
### See also ###
1338

1439
[set_auto_page_break](set_auto_page_break.md).

docs/reference/write_html.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pdf.add_page()
8181
pdf.write_html(html)
8282
pdf.output('html.pdf')
8383
```
84-
See html.py or [Web2Py] (../Web2Py.md) for a complete example. `# TODO fix links`
84+
See html.py or [Web2Py](https://pyfpdf.github.io/fpdf2/Web2Py.html) for a complete example.
8585

8686
### See also ###
8787
[write](write.md), [add_font](add_font.md), [image](image.md).

0 commit comments

Comments
 (0)