Skip to content

Commit

Permalink
Override heading style with supplied cell style
Browse files Browse the repository at this point in the history
Add combine method to FontFace in fonts.py, allowing for partial overrides
  • Loading branch information
TedBrookings committed Nov 1, 2023
1 parent 99e4d6d commit ba92f32
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 4 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ in order to get warned about deprecated features used in your code.
This can also be enabled programmatically with `warnings.simplefilter('default', DeprecationWarning)`.

## [2.7.7] - Not released yet

### Added
* [`FPDF.fonts.FontFace`](https://py-pdf.github.io/fpdf2/fpdf/fonts.html#fpdf.fonts.FontFace): Now has a static `combine` method that allows overriding a default FontFace (e.g. for specific cells in a table). Unspecified properties of the override FontFace retain the values of the default.
### Changed
* [`FPDF.table()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table): If cell styles are provided for cells in heading rows, combine the cell style as an override with the overall heading style.

## [2.7.6] - 2023-10-11
This release is the first performed from the [@py-pdf GitHub org](https://github.com/py-pdf), where `fpdf2` migrated.
Expand All @@ -34,7 +37,7 @@ This release also marks the arrival of two new maintainers: Georg Mischler ([@gm
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): the formatting output has changed in some aspects. Vertical spacing around headings and paragraphs may be slightly different, and elements at the top of the page don't have any extra spacing above anymore.
* [`FPDF.table()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table): If the height of a row is governed by an image, then the default vertical alignment of the other cells is "center". This was "top".
* variable-width non-breaking space (NBSP) support [issue #834](https://github.com/PyFPDF/fpdf2/issues/834)
This change was made for consistency between row-height governed by text or images. The old behaviour can be enforced using the new vertical alignment parameter.
This change was made for consistency between row-height governed by text or images. The old behaviour can be enforced using the new vertical alignment parameter.
### Fixed
* [`FPDF.table()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table) & [`FPDF.multi_cell()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell): when some horizontal padding was set, the text was not given quite enough space - thanks to @gmischler
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) can now handle formatting tags within paragraphs without adding extra line breaks (except in table cells for now) - thanks to @gmischler
Expand Down
19 changes: 19 additions & 0 deletions docs/Tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@ Result:

![](table-styled.jpg)

It's possible to override the style of individual cells in the heading. The overriding style will take
precedence for any specified values, while retaining the default style for unspecified values:
```python
...
headings_style = FontFace(emphasis="ITALICS", color=blue, fill_color=grey)
override_style = FontFace(emphasis="BOLD")
with pdf.table(headings_style=headings_style) as table:
headings = table.row()
headings.cell("First name", style=override_style)
headings.cell("Last name", style=override_style)
headings.cell("Age")
headings.cell("City")
...
```

Result:

![](table-styled-override.jpg)

## Set cells background

```python
Expand Down
Binary file added docs/table-styled-override.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions fpdf/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ def __init__(

replace = replace

@staticmethod
def _override(override_value, current_value):
"""Override the current value if an override value is provided"""
return current_value if override_value is None else override_value

@staticmethod
def combine(override_style, default_style):
"""
Create a combined FontFace with all the supplied features of the two styles. When both
the default and override styles provide a feature, prefer the override style.
Override specified FontFace style features
Override this FontFace's values with the values of `other`.
Values of `other` that are None in this FontFace will be kept unchanged.
"""
if override_style is None:
return default_style
elif default_style is None:
return override_style
if not isinstance(override_style, FontFace):
raise TypeError(f"Cannot combine FontFace with {type(override_style)}")
if not isinstance(default_style, FontFace):
raise TypeError(f"Cannot combine FontFace with {type(default_style)}")
return FontFace(
family=FontFace._override(override_style.family, default_style.family),
emphasis=FontFace._override(override_style.emphasis, default_style.emphasis),
size_pt=FontFace._override(override_style.size_pt, default_style.size_pt),
color=FontFace._override(override_style.color, default_style.color),
fill_color=FontFace._override(override_style.fill_color, default_style.fill_color),
)


class CoreFont:
# RAM usage optimization:
Expand Down
6 changes: 4 additions & 2 deletions fpdf/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,11 @@ def _render_table_cell(
if not isinstance(text_align, (Align, str)):
text_align = text_align[j]
if i < self._num_heading_rows:
style = self._headings_style
# Get the style for this cell by overriding the row style with any provided
# headings style, and overriding that with any provided cell style
style = FontFace.combine(cell.style, FontFace.combine(self._headings_style, row.style))
else:
style = cell.style or row.style
style = FontFace.combine(cell.style, row.style)
if style and style.fill_color:
fill = True
elif (
Expand Down
15 changes: 15 additions & 0 deletions test/fonts/test_combine_fontface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pathlib import Path
from fpdf.fonts import FontFace

def test_combine_fontface():
font1 = FontFace(family="helvetica", size_pt=12)
# providing None override should return the default style
assert FontFace.combine(override_style=None, default_style=font1) == font1
# overriding a None style should return the override
assert FontFace.combine(override_style=font1, default_style=None) == font1
font2 = FontFace(size_pt=14)
combined = FontFace.combine(override_style=font2, default_style=font1)
assert isinstance(combined, FontFace)
assert combined.family == "helvetica" # wasn't overridden
assert combined.emphasis is None # wasn't specified by either
assert combined.size_pt == 14 # was overridden
Binary file added test/table/table_with_heading_style_overrides.pdf
Binary file not shown.
23 changes: 23 additions & 0 deletions test/table/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,3 +631,26 @@ def test_table_with_no_horizontal_lines_layout(tmp_path):
HERE / "table_with_no_horizontal_lines_layout.pdf",
tmp_path,
)

def test_table_with_heading_style_overrides(tmp_path):
pdf = FPDF()
pdf.set_font(family="helvetica", size=10)
pdf.add_page()

with pdf.table(
headings_style=FontFace(emphasis="B", size_pt=18), num_heading_rows=2
) as table:
# should be Helvetica bold size 18
table.row().cell("Big Heading", colspan=3)
second_header = table.row()
# should be Helvetica bold size 14:
second_header_style_1 = FontFace(size_pt=14)
second_header.cell("First", style=second_header_style_1)
# should be Times italic size 14
second_header_style_2_3 = FontFace(family="times", emphasis="I", size_pt=14)
second_header.cell("Second", style=second_header_style_2_3)
second_header.cell("Third", style=second_header_style_2_3)
# should be helvetica normal size 10
table.row(("Some", "Normal", "Data"))

assert_pdf_equal(pdf, HERE / "table_with_heading_style_overrides.pdf", tmp_path)

0 comments on commit ba92f32

Please sign in to comment.