Skip to content

Commit

Permalink
[HTML] Introducing tag_indents & tag_styles
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C committed Feb 27, 2024
1 parent 2244a18 commit edeb0b3
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 138 deletions.
34 changes: 31 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,38 @@ 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
### Fixed
* When adding a link on a table cell, an extra link was added erroneously on the left. Moreover, now `FPDF._disable_writing()` properly disable link writing.
### Added
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now accepts a dictionary of colors for HTML elements (link, blockquote, li and headings)
* [`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
### 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
* support for `<path>` elements in SVG `<clipPath>` elements
* support for `bidirectional` text shaping - thanks to @andersonhc
* documentation on how to combine `fpdf2` with [mistletoe](https://pypi.org/project/kaleido/) in order to [generate PDF documents from Markdown (link)](https://py-pdf.github.io/fpdf2/CombineWithMistletoeoToUseMarkdown.html)
* tutorial in Dutch: [Handleiding](https://py-pdf.github.io/fpdf2/Tutorial-nl.md) - thanks to @Polderrider
* support for `Table` cells that span multiple rows via the `rowspan` attribute, which can be combined with `colspan` - thanks to @mjasperse
* `TableSpan.COL` and `TableSpan.ROW` enums that can be used as placeholder table entries to identify span extents - thanks to @mjasperse
### Fixed
* when adding a link on a table cell, an extra link was added erroneously on the left. Moreover, now `FPDF._disable_writing()` properly disable link writing.
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now handles linking directly to other pages - thanks to @mjasperse
* non-bold `TitleStyle` is now rendered as non-bold even when the current font is bold
* calling `.table()` inside the `render_toc_function`
* using `.set_text_shaping(True)` & `.offset_rendering()`
* fixed gutter handing when a pagebreak occurs within a table with header rows - thanks to @mjasperse
* fixed handling of `border=0` in HTML table - thanks to @mjasperse
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) now properly honors `align=` attributes in `<th>` tags
* fixed problem using bold italic standard fonts in markdown - thanks to @Alan-Collins
### Changed
* refactored [`FPDF.multi_cell()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) to generate fewer PDF component objects - thanks to @mjasperse
* outer table borders are now drawn continuously for nonzero `gutter_width`/`gutter_height`, with spacing applied inside the border similar to HTML tables - thanks to @mjasperse - cf. [#1071](https://github.com/py-pdf/fpdf2/issues/1071)
* removed the requirement that all rows in a `Table` have the same number of columns - thanks to @mjasperse
### Deprecated
- font aliases (`Arial``Helvetica`, `CourierNew``Courer`, `TimesNewRoman``Times`). They will be removed in a later release.

## [2.7.7] - 2023-12-10
### Added
* Basic support for `<image>` elements in SVG vector graphics inserted
Expand Down
39 changes: 39 additions & 0 deletions docs/HTML.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,45 @@ pdf.output("html.pdf")
```


## Styling HTML tags globally

The style of several HTML tags (`<a>`, `<blockquote>`, `<code>`, `<pre>`, `<li>`, `<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
21 changes: 15 additions & 6 deletions fpdf/drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,14 @@ 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
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}"

Expand Down Expand Up @@ -257,11 +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"{0 if self.g == 0 else self.g} {self.OPERATOR}"


__pdoc__["DeviceGray.OPERATOR"] = False
Expand Down Expand Up @@ -312,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
27 changes: 13 additions & 14 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,20 +395,15 @@ 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
heading_sizes (dict): font size per heading level names ("h1", "h2"...)
pre_code_font (str): font to use for <pre> & <code> blocks
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
element_colors (dict): dictionary of colors for elements. Possible keys:
* "link" sets the link color for a href tag
* "li" sets the bullet or number for <li> tags
* "blockquote" sets the color for <blockquote> tags
* "headings" sets the color is not specified in a h1, h2 etc. heading tag
The values for the dictionary are a tuple of three ints, being the r,g and b values
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 +889,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 +1099,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 @@ -4926,7 +4921,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 edeb0b3

Please sign in to comment.