Skip to content

Commit

Permalink
Fixing support for align= in FPDF.table() - close #1306 (#1308)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C authored Nov 20, 2024
1 parent 096d1c8 commit 056a2a5
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 121 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
- name: Statically checking code 🔎
if: matrix.python-version == '3.12' && matrix.platform == 'ubuntu-latest'
run: |
pip install --upgrade . -r test/linters-requirements.txt
black --check .
pylint fpdf test tutorial/tuto*.py
bandit -c .banditrc.yml -r contributors/ fpdf/ tutorial/
semgrep scan --config auto --error --strict --exclude-rule=python.lang.security.insecure-hash-function.insecure-hash-function fpdf
Expand All @@ -45,9 +47,6 @@ jobs:
with:
path: "."
fail-build: true
- name: Ensure code has been autoformatted with black 🖌️
if: matrix.python-version == '3.12' && matrix.platform == 'ubuntu-latest'
run: black --check .
- name: Checking all PDF samples ☑
if: matrix.python-version == '3.12' && matrix.platform == 'ubuntu-latest'
run: |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): now parses `<title>` tags to set the [document title](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.set_title). By default, it is added as PDF metadata, but not rendered in the document body. However, this can be enabled by passing `render_title_tag=True` to `FPDF.write_html()`.
* support for LZWDecode compression [issue #1271](https://github.com/py-pdf/fpdf2/issues/1271)
### Fixed
* support for `align=` in [`FPDF.table()`](https://py-pdf.github.io/fpdf2/Tables.html#setting-table-column-widths). Due to this correction, tables are now properly horizontally aligned on the page by default. This was always specified in the documentation, but was not in effect until now. You can revert to have left-aligned tables by passing `align="LEFT"` to `FPDF.table()`.
* `FPDF.set_text_shaping(False)` was broken since version 2.7.8 and is now working properly - [issue #1287](https://github.com/py-pdf/fpdf2/issues/1287)
* fixed bug where cells with `rowspan`, `colspan` > 1 and null text were not displayed properly - [issue #1293](https://github.com/py-pdf/fpdf2/issues/1293)
### Changed
Expand Down
5 changes: 5 additions & 0 deletions docs/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ black . # inside fpdf2 root directory
## Linting
We use [pylint](https://github.com/PyCQA/pylint/) as a static code analyzer
to detect potential issues in the code.
You can install & execute it by running those commands:
```
pip install pylint
pylint fpdf/ test/
```

In case of special "false positive" cases,
checks can be disabled locally with `#pylint disable=XXX` code comments,
Expand Down
2 changes: 1 addition & 1 deletion fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2533,7 +2533,7 @@ def ink_annotation(
pop-up window when open and active. This entry shall identify the user who added the annotation.
color (tuple): a tuple of numbers in the range 0.0 to 1.0, representing a colour used for
the title bar of the annotation’s pop-up window. Defaults to yellow.
border_width (int): thickness of the path stroke.
border_width (float): thickness of the path stroke.
"""
ink_list = sum(((x * self.k, (self.h - y) * self.k) for (x, y) in coords), ())
x_min = min(ink_list[0::2])
Expand Down
32 changes: 19 additions & 13 deletions fpdf/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def __init__(
repeat_headings (fpdf.enums.TableHeadingsDisplay): optional, indicates whether to print table headings on every page, default to 1.
"""
self._fpdf = fpdf
self._align = align
self._table_align = Align.coerce(align)
self._v_align = VAlign.coerce(v_align)
self._borders_layout = TableBordersLayout.coerce(borders_layout)
self._outer_border_width = outer_border_width
Expand All @@ -99,7 +99,7 @@ def __init__(
self._line_height = 2 * fpdf.font_size if line_height is None else line_height
self._markdown = markdown
self._text_align = text_align
self._width = fpdf.epw if width is None else width
self._width = width
self._wrapmode = wrapmode
self._num_heading_rows = num_heading_rows
self._repeat_headings = TableHeadingsDisplay.coerce(repeat_headings)
Expand Down Expand Up @@ -163,12 +163,22 @@ def row(self, cells=(), style=None):
def render(self):
"This is an internal method called by `fpdf.FPDF.table()` once the table is finished"
# Starting with some sanity checks:
self._cols_count = max(row.cols_count for row in self.rows) if self.rows else 0
if self._width is None:
if self._col_widths and isinstance(self._col_widths, Number):
self._width = self._cols_count * self._col_widths
else:
self._width = self._fpdf.epw
elif self._col_widths and isinstance(self._col_widths, Number):
if self._cols_count * self._col_widths != self._width:
raise ValueError(
f"Invalid value provided width={self._width} should be a multiple of col_widths={self._col_widths}"
)
if self._width > self._fpdf.epw:
raise ValueError(
f"Invalid value provided width={self._width}: effective page width is {self._fpdf.epw}"
)
table_align = Align.coerce(self._align)
if table_align == Align.J:
if self._table_align == Align.J:
raise ValueError(
"JUSTIFY is an invalid value for FPDF.table() 'align' parameter"
)
Expand All @@ -191,26 +201,23 @@ def render(self):

# Defining table global horizontal position:
prev_x, prev_y, prev_l_margin = self._fpdf.x, self._fpdf.y, self._fpdf.l_margin
if table_align == Align.C:
if self._table_align == Align.C:
self._fpdf.l_margin = (self._fpdf.w - self._width) / 2
self._fpdf.x = self._fpdf.l_margin
elif table_align == Align.R:
elif self._table_align == Align.R:
self._fpdf.l_margin = self._fpdf.w - self._fpdf.r_margin - self._width
self._fpdf.x = self._fpdf.l_margin
elif self._fpdf.x != self._fpdf.l_margin:
self._fpdf.l_margin = self._fpdf.x

# Pre-Compute the relative x-positions of the individual columns:
xx = self._outer_border_margin[0]
xx = self._fpdf.l_margin + self._outer_border_margin[0]
cell_x_positions = [xx]
if self.rows:
self._cols_count = max(row.cols_count for row in self.rows)
for i in range(self._cols_count):
xx += self._get_col_width(0, i)
xx += self._gutter_width
cell_x_positions.append(xx)
else:
self._cols_count = 0

# Process any rowspans
row_info = list(self._process_rowpans_entries())
Expand All @@ -220,7 +227,7 @@ def render(self):
self._repeat_headings is TableHeadingsDisplay.ON_TOP_OF_EVERY_PAGE
)
self._fpdf.y += self._outer_border_margin[1]
for i, row in enumerate(self.rows):
for i in range(len(self.rows)):
pagebreak_height = row_info[i].pagebreak_height
# pylint: disable=protected-access
page_break = self._fpdf._perform_page_break_if_need_be(pagebreak_height)
Expand Down Expand Up @@ -393,8 +400,7 @@ def _render_table_cell(
cell_x = 0
else:
cell_x = cell_x_positions[j]

self._fpdf.set_x(self._fpdf.l_margin + cell_x)
self._fpdf.set_x(cell_x)

# render cell border and background

Expand Down
Binary file modified test/embed_file_all_optionals.pdf
Binary file not shown.
Binary file modified test/embed_file_self.pdf
Binary file not shown.
Binary file modified test/file_attachment_annotation.pdf
Binary file not shown.
5 changes: 5 additions & 0 deletions test/linters-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bandit
black
pre-commit
pylint
semgrep
5 changes: 0 additions & 5 deletions test/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
bandit
black
# camelot-py[base] : does not work due to https://github.com/vinayak-mehta/pdftopng/issues/11
camelot-py
endesive
ghostscript # required by camelot-py[base]: https://github.com/camelot-dev/camelot/blob/master/setup.py#L27
numpy<2 # required by opencv-python, required for now to avoid an ImportError: numpy.core.multiarray failed to import, followed by an AttributeError: _ARRAY_API not found when importing cv2
opencv-python # required by camelot-py[base]: https://github.com/camelot-dev/camelot/blob/master/setup.py#L27
pre-commit
pylint
pytest
pytest-cov
qrcode
semgrep
tabula-py
uharfbuzz
Binary file modified test/table/table_with_fixed_col_width.pdf
Binary file not shown.
Binary file not shown.
Binary file added test/table/table_with_gutter_and_width.pdf
Binary file not shown.
Loading

0 comments on commit 056a2a5

Please sign in to comment.