Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HTML] Introducing tag_indents & tag_styles #1124

Merged
merged 6 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading