Skip to content

Commit b7e9b11

Browse files
Lucas-Cdmail00
andauthored
[HTML] Introducing tag_indents & tag_styles (#1124)
Co-authored-by: dmail00 <dmail00@gmail.com>
1 parent 15af379 commit b7e9b11

21 files changed

+504
-129
lines changed

CHANGELOG.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
1919
## [2.7.9] - Not released yet
2020
### Added
2121
* support for overriding paragraph direction on bidirectional text
22-
* new optional `ul_bullet_color` parameter for `FPDF.write_html()`
23-
### Fixed
24-
22+
* new optional `li_prefix_color` parameter for `FPDF.write_html()`
23+
* support for `start` & `type` attributes of `<ol>` tags, and `type` attribute of `<ul>` tags, when using `FPDF.write_html()`
24+
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now accepts a `tag_styles` parameter to control the font, color & size of HTML elements: `<a>`, `<blockquote>`, `<li>`...
25+
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now accepts a `tag_indents` parameter to control, for example, the indent of `<blockquote>` elements
2526
### Changed
2627
* improved the performance of `FPDF.start_section()` - _cf._ [issue #1092](https://github.com/py-pdf/fpdf2/issues/1092)
27-
28+
### Deprecated
29+
* The `dd_tag_indent` & `li_tag_indent` parameters of `FPDF.write_html()` are replaced by the new `tag_indents` generic parameter.
30+
* The `heading_sizes` & `pre_code_font` parameters of `FPDF.write_html()` are replaced by the new `tag_styles` generic parameter.
2831

2932
## [2.7.8] - 2024-02-09
3033
### Added

docs/HTML.md

+49-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,16 @@ pdf.write_html("""
4040
</section>
4141
<section>
4242
<h2>Other section title</h2>
43-
<ul><li>unordered</li><li>list</li><li>items</li></ul>
44-
<ol><li>ordered</li><li>list</li><li>items</li></ol>
43+
<ul type="circle">
44+
<li>unordered</li>
45+
<li>list</li>
46+
<li>items</li>
47+
</ul>
48+
<ol start="3" type="i">
49+
<li>ordered</li>
50+
<li>list</li>
51+
<li>items</li>
52+
</ol>
4553
<br>
4654
<br>
4755
<pre>i am preformatted text.</pre>
@@ -71,6 +79,45 @@ pdf.output("html.pdf")
7179
```
7280

7381

82+
## Styling HTML tags globally
83+
84+
The style of several HTML tags (`<a>`, `<blockquote>`, `<code>`, `<pre>`, `<h1>`, `<h2>`, `<h3>`...) can be set globally, for the whole HTML document, by passing `tag_styles` to `FPDF.write_html()`:
85+
86+
```python
87+
from fpdf import FPDF, FontFace
88+
89+
pdf = FPDF()
90+
pdf.add_page()
91+
pdf.write_html("""
92+
<h1>Big title</h1>
93+
<section>
94+
<h2>Section title</h2>
95+
<p>Hello world!</p>
96+
</section>
97+
""", tag_styles={
98+
"h1": FontFace(color=(148, 139, 139), size_pt=32),
99+
"h2": FontFace(color=(148, 139, 139), size_pt=24),
100+
})
101+
pdf.output("html_styled.pdf")
102+
```
103+
104+
Similarly, the indentation of several HTML tags (`<blockquote>`, `<dd>`, `<li>`) can be set globally, for the whole HTML document, by passing `tag_indents` to `FPDF.write_html()`:
105+
106+
```python
107+
from fpdf import FPDF
108+
109+
pdf = FPDF()
110+
pdf.add_page()
111+
pdf.write_html("""
112+
<dl>
113+
<dt>Term</dt>
114+
<dd>Definition</dd>
115+
</dl>
116+
""", tag_indents={"dd": 5})
117+
pdf.output("html_dd_indented.pdf")
118+
```
119+
120+
74121
## Supported HTML features
75122

76123
* `<h1>` to `<h8>`: headings (and `align` attribute)

fpdf/drawing.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def __new__(cls, r, g, b, a=None):
214214

215215
@property
216216
def colors(self):
217-
"""The color components as a tuple in order `(r, g, b)` with alpha omitted."""
217+
"The color components as a tuple in order `(r, g, b)` with alpha omitted, in range 0-1."
218218
return self[:-1]
219219

220220
@property
@@ -262,16 +262,16 @@ def __new__(cls, g, a=None):
262262

263263
@property
264264
def colors(self):
265-
"""The color components as a tuple in order (g,) with alpha omitted."""
266-
return self[:-1]
265+
"The color components as a tuple in order (r, g, b) with alpha omitted, in range 0-1."
266+
return self.g, self.g, self.g
267267

268268
@property
269269
def colors255(self):
270270
"The color components as a tuple in order `(r, g, b)` with alpha omitted, in range 0-255."
271271
return tuple(255 * v for v in self.colors)
272272

273273
def serialize(self) -> str:
274-
return " ".join(number_to_str(val) for val in self.colors) + f" {self.OPERATOR}"
274+
return f"{number_to_str(self.g)} {self.OPERATOR}"
275275

276276

277277
__pdoc__["DeviceGray.OPERATOR"] = False
@@ -322,8 +322,7 @@ def __new__(cls, c, m, y, k, a=None):
322322

323323
@property
324324
def colors(self):
325-
"""The color components as a tuple in order (c, m, y, k) with alpha omitted."""
326-
325+
"The color components as a tuple in order (c, m, y, k) with alpha omitted, in range 0-1."
327326
return self[:-1]
328327

329328
def serialize(self) -> str:

fpdf/enums.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ class TextEmphasis(CoerciveIntFlag):
227227
style = B | I
228228
"""
229229

230+
NONE = 0
231+
"No emphasis"
232+
230233
B = 1
231234
"Bold"
232235

@@ -246,7 +249,7 @@ def style(self):
246249
def coerce(cls, value):
247250
if isinstance(value, str):
248251
if value == "":
249-
return 0
252+
return cls.NONE
250253
if value.upper() == "BOLD":
251254
return cls.B
252255
if value.upper() == "ITALICS":

fpdf/fonts.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ class FontFace:
5151
"fill_color",
5252
)
5353
family: Optional[str]
54-
emphasis: Optional[TextEmphasis] # can be a combination: B | U
54+
emphasis: Optional[TextEmphasis] # None means "no override"
55+
# Whereas "" means "no emphasis"
56+
# This can be a combination: B | U
5557
size_pt: Optional[int]
5658
# Colors are single number grey scales or (red, green, blue) tuples:
5759
color: Optional[Union[DeviceGray, DeviceRGB]]
@@ -61,7 +63,7 @@ def __init__(
6163
self, family=None, emphasis=None, size_pt=None, color=None, fill_color=None
6264
):
6365
self.family = family
64-
self.emphasis = TextEmphasis.coerce(emphasis) if emphasis else None
66+
self.emphasis = None if emphasis is None else TextEmphasis.coerce(emphasis)
6567
self.size_pt = size_pt
6668
self.color = None if color is None else convert_to_device_color(color)
6769
self.fill_color = (

fpdf/fpdf.py

+19-11
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ class Image:
144144
class TitleStyle(FontFace):
145145
def __init__(
146146
self,
147-
font_family: Optional[str] = None,
147+
font_family: Optional[str] = None, # None means "no override"
148+
# Whereas "" means "no emphasis"
148149
font_style: Optional[str] = None,
149150
font_size_pt: Optional[int] = None,
150151
color: Union[int, tuple] = None, # grey scale or (red, green, blue),
@@ -155,7 +156,7 @@ def __init__(
155156
):
156157
super().__init__(
157158
font_family,
158-
(font_style or "") + ("U" if underline else ""),
159+
((font_style or "") + "U") if underline else font_style,
159160
font_size_pt,
160161
color,
161162
)
@@ -401,14 +402,17 @@ def write_html(self, text, *args, **kwargs):
401402
text (str): HTML content to render
402403
image_map (function): an optional one-argument function that map <img> "src"
403404
to new image URLs
404-
li_tag_indent (int): numeric indentation of <li> elements
405-
dd_tag_indent (int): numeric indentation of <dd> elements
405+
li_tag_indent (int): [**DEPRECATED since v2.7.8**] numeric indentation of <li> elements - Set tag_indents instead
406+
dd_tag_indent (int): [**DEPRECATED since v2.7.8**] numeric indentation of <dd> elements - Set tag_indents instead
406407
table_line_separators (bool): enable horizontal line separators in <table>
407-
ul_bullet_char (str): bullet character for <ul> elements
408-
ul_bullet_color (tuple | str | drawing.Device* instance): color of the <ul> bullets
409-
heading_sizes (dict): font size per heading level names ("h1", "h2"...)
410-
pre_code_font (str): font to use for <pre> & <code> blocks
408+
ul_bullet_char (str): bullet character preceding <li> items in <ul> lists.
409+
li_prefix_color (tuple | str | drawing.Device* instance): color for bullets or numbers preceding <li> tags.
410+
This applies to both <ul> & <ol> lists.
411+
heading_sizes (dict): [**DEPRECATED since v2.7.8**] font size per heading level names ("h1", "h2"...) - Set tag_styles instead
412+
pre_code_font (str): [**DEPRECATED since v2.7.8**] font to use for <pre> & <code> blocks - Set tag_styles instead
411413
warn_on_tags_not_matching (bool): control warnings production for unmatched HTML tags
414+
tag_indents (dict): mapping of HTML tag names to numeric values representing their horizontal left identation
415+
tag_styles (dict): mapping of HTML tag names to colors
412416
"""
413417
kwargs2 = vars(self)
414418
# Method arguments must override class & instance attributes:
@@ -894,7 +898,7 @@ def add_page(
894898
if isinstance(self.page_background, tuple):
895899
self.set_fill_color(*self.page_background)
896900
self.rect(0, 0, self.w, self.h, style="F")
897-
self.set_fill_color(*(255 * v for v in fc.colors))
901+
self.set_fill_color(*fc.colors255)
898902
else:
899903
self.image(self.page_background, 0, 0, self.w, self.h)
900904

@@ -1104,7 +1108,7 @@ def set_page_background(self, background):
11041108
background, (str, io.BytesIO, Image, DeviceRGB, tuple, type(None))
11051109
):
11061110
if isinstance(background, DeviceRGB):
1107-
self.page_background = tuple(255 * v for v in background.colors)
1111+
self.page_background = background.colors255
11081112
else:
11091113
self.page_background = background
11101114
else:
@@ -4936,7 +4940,11 @@ def use_font_face(self, font_face: FontFace):
49364940
prev_font = (self.font_family, self.font_style, self.font_size_pt)
49374941
self.set_font(
49384942
font_face.family or self.font_family,
4939-
font_face.emphasis.style if font_face.emphasis is not None else "",
4943+
(
4944+
font_face.emphasis.style
4945+
if font_face.emphasis is not None
4946+
else self.font_style
4947+
),
49404948
font_face.size_pt or self.font_size_pt,
49414949
)
49424950
prev_text_color = self.text_color

0 commit comments

Comments
 (0)