Skip to content

Commit

Permalink
[HTML] Introducing tag_indents & tag_styles (#1124)
Browse files Browse the repository at this point in the history
Co-authored-by: dmail00 <[email protected]>
  • Loading branch information
Lucas-C and dmail00 authored Feb 28, 2024
1 parent 15af379 commit b7e9b11
Show file tree
Hide file tree
Showing 21 changed files with 504 additions and 129 deletions.
11 changes: 7 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
## [2.7.9] - Not released yet
### Added
* support for overriding paragraph direction on bidirectional text
* new optional `ul_bullet_color` parameter for `FPDF.write_html()`
### Fixed

* new optional `li_prefix_color` parameter for `FPDF.write_html()`
* support for `start` & `type` attributes of `<ol>` tags, and `type` attribute of `<ul>` tags, when using `FPDF.write_html()`
* [`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>`...
* [`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
### Changed
* improved the performance of `FPDF.start_section()` - _cf._ [issue #1092](https://github.com/py-pdf/fpdf2/issues/1092)

### Deprecated
* The `dd_tag_indent` & `li_tag_indent` parameters of `FPDF.write_html()` are replaced by the new `tag_indents` generic parameter.
* The `heading_sizes` & `pre_code_font` parameters of `FPDF.write_html()` are replaced by the new `tag_styles` generic parameter.

## [2.7.8] - 2024-02-09
### Added
Expand Down
51 changes: 49 additions & 2 deletions docs/HTML.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ pdf.write_html("""
</section>
<section>
<h2>Other section title</h2>
<ul><li>unordered</li><li>list</li><li>items</li></ul>
<ol><li>ordered</li><li>list</li><li>items</li></ol>
<ul type="circle">
<li>unordered</li>
<li>list</li>
<li>items</li>
</ul>
<ol start="3" type="i">
<li>ordered</li>
<li>list</li>
<li>items</li>
</ol>
<br>
<br>
<pre>i am preformatted text.</pre>
Expand Down Expand Up @@ -71,6 +79,45 @@ pdf.output("html.pdf")
```


## Styling HTML tags globally

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()`:

```python
from fpdf import FPDF, FontFace

pdf = FPDF()
pdf.add_page()
pdf.write_html("""
<h1>Big title</h1>
<section>
<h2>Section title</h2>
<p>Hello world!</p>
</section>
""", tag_styles={
"h1": FontFace(color=(148, 139, 139), size_pt=32),
"h2": FontFace(color=(148, 139, 139), size_pt=24),
})
pdf.output("html_styled.pdf")
```

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()`:

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.write_html("""
<dl>
<dt>Term</dt>
<dd>Definition</dd>
</dl>
""", tag_indents={"dd": 5})
pdf.output("html_dd_indented.pdf")
```


## Supported HTML features

* `<h1>` to `<h8>`: headings (and `align` attribute)
Expand Down
11 changes: 5 additions & 6 deletions fpdf/drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def __new__(cls, r, g, b, a=None):

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

@property
Expand Down Expand Up @@ -262,16 +262,16 @@ def __new__(cls, g, a=None):

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

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

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


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

@property
def colors(self):
"""The color components as a tuple in order (c, m, y, k) with alpha omitted."""

"The color components as a tuple in order (c, m, y, k) with alpha omitted, in range 0-1."
return self[:-1]

def serialize(self) -> str:
Expand Down
5 changes: 4 additions & 1 deletion fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ class TextEmphasis(CoerciveIntFlag):
style = B | I
"""

NONE = 0
"No emphasis"

B = 1
"Bold"

Expand All @@ -246,7 +249,7 @@ def style(self):
def coerce(cls, value):
if isinstance(value, str):
if value == "":
return 0
return cls.NONE
if value.upper() == "BOLD":
return cls.B
if value.upper() == "ITALICS":
Expand Down
6 changes: 4 additions & 2 deletions fpdf/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ class FontFace:
"fill_color",
)
family: Optional[str]
emphasis: Optional[TextEmphasis] # can be a combination: B | U
emphasis: Optional[TextEmphasis] # None means "no override"
# Whereas "" means "no emphasis"
# This can be a combination: B | U
size_pt: Optional[int]
# Colors are single number grey scales or (red, green, blue) tuples:
color: Optional[Union[DeviceGray, DeviceRGB]]
Expand All @@ -61,7 +63,7 @@ def __init__(
self, family=None, emphasis=None, size_pt=None, color=None, fill_color=None
):
self.family = family
self.emphasis = TextEmphasis.coerce(emphasis) if emphasis else None
self.emphasis = None if emphasis is None else TextEmphasis.coerce(emphasis)
self.size_pt = size_pt
self.color = None if color is None else convert_to_device_color(color)
self.fill_color = (
Expand Down
30 changes: 19 additions & 11 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ class Image:
class TitleStyle(FontFace):
def __init__(
self,
font_family: Optional[str] = None,
font_family: Optional[str] = None, # None means "no override"
# Whereas "" means "no emphasis"
font_style: Optional[str] = None,
font_size_pt: Optional[int] = None,
color: Union[int, tuple] = None, # grey scale or (red, green, blue),
Expand All @@ -155,7 +156,7 @@ def __init__(
):
super().__init__(
font_family,
(font_style or "") + ("U" if underline else ""),
((font_style or "") + "U") if underline else font_style,
font_size_pt,
color,
)
Expand Down Expand Up @@ -401,14 +402,17 @@ def write_html(self, text, *args, **kwargs):
text (str): HTML content to render
image_map (function): an optional one-argument function that map <img> "src"
to new image URLs
li_tag_indent (int): numeric indentation of <li> elements
dd_tag_indent (int): numeric indentation of <dd> elements
li_tag_indent (int): [**DEPRECATED since v2.7.8**] numeric indentation of <li> elements - Set tag_indents instead
dd_tag_indent (int): [**DEPRECATED since v2.7.8**] numeric indentation of <dd> elements - Set tag_indents instead
table_line_separators (bool): enable horizontal line separators in <table>
ul_bullet_char (str): bullet character for <ul> elements
ul_bullet_color (tuple | str | drawing.Device* instance): color of the <ul> bullets
heading_sizes (dict): font size per heading level names ("h1", "h2"...)
pre_code_font (str): font to use for <pre> & <code> blocks
ul_bullet_char (str): bullet character preceding <li> items in <ul> lists.
li_prefix_color (tuple | str | drawing.Device* instance): color for bullets or numbers preceding <li> tags.
This applies to both <ul> & <ol> lists.
heading_sizes (dict): [**DEPRECATED since v2.7.8**] font size per heading level names ("h1", "h2"...) - Set tag_styles instead
pre_code_font (str): [**DEPRECATED since v2.7.8**] font to use for <pre> & <code> blocks - Set tag_styles instead
warn_on_tags_not_matching (bool): control warnings production for unmatched HTML tags
tag_indents (dict): mapping of HTML tag names to numeric values representing their horizontal left identation
tag_styles (dict): mapping of HTML tag names to colors
"""
kwargs2 = vars(self)
# Method arguments must override class & instance attributes:
Expand Down Expand Up @@ -894,7 +898,7 @@ def add_page(
if isinstance(self.page_background, tuple):
self.set_fill_color(*self.page_background)
self.rect(0, 0, self.w, self.h, style="F")
self.set_fill_color(*(255 * v for v in fc.colors))
self.set_fill_color(*fc.colors255)
else:
self.image(self.page_background, 0, 0, self.w, self.h)

Expand Down Expand Up @@ -1104,7 +1108,7 @@ def set_page_background(self, background):
background, (str, io.BytesIO, Image, DeviceRGB, tuple, type(None))
):
if isinstance(background, DeviceRGB):
self.page_background = tuple(255 * v for v in background.colors)
self.page_background = background.colors255
else:
self.page_background = background
else:
Expand Down Expand Up @@ -4936,7 +4940,11 @@ def use_font_face(self, font_face: FontFace):
prev_font = (self.font_family, self.font_style, self.font_size_pt)
self.set_font(
font_face.family or self.font_family,
font_face.emphasis.style if font_face.emphasis is not None else "",
(
font_face.emphasis.style
if font_face.emphasis is not None
else self.font_style
),
font_face.size_pt or self.font_size_pt,
)
prev_text_color = self.text_color
Expand Down
Loading

0 comments on commit b7e9b11

Please sign in to comment.