From 8763cad0c9f3bfc98e8d9ca544d1eee45c0b58db Mon Sep 17 00:00:00 2001 From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Date: Wed, 19 Apr 2023 21:47:33 +0200 Subject: [PATCH] Handling some edge cases with table() (#768) --- .../continuous-integration-workflow.yml | 4 +- CHANGELOG.md | 8 +- docs/Development.md | 2 +- docs/HTML.md | 2 +- docs/LineBreaks.md | 2 - docs/Text.md | 3 +- fpdf/enums.py | 23 ++++ fpdf/fpdf.py | 100 ++++++++++++---- fpdf/html.py | 2 + fpdf/table.py | 111 ++++++++---------- fpdf/template.py | 10 +- test/html/html_table_with_bgcolor.pdf | Bin 1654 -> 1644 bytes ...l_table_with_imgs_captions_and_colspan.pdf | Bin 22584 -> 22583 bytes test/html/html_table_with_width_and_align.pdf | Bin 0 -> 1235 bytes test/html/test_html_table.py | 16 +++ test/signing/sign_pkcs12.pdf | Bin 17542 -> 17542 bytes test/signing/sign_pkcs12_with_link.pdf | Bin 18174 -> 18174 bytes test/table/table_with_cell_fill.pdf | Bin 2250 -> 2231 bytes test/table/table_with_cell_overflow.pdf | Bin 0 -> 1407 bytes test/table/table_with_headings_styled.pdf | Bin 1708 -> 1700 bytes .../table_with_images_and_img_fill_width.pdf | Bin 79299 -> 79292 bytes .../table_with_multiline_cells_and_images.pdf | Bin 80470 -> 80357 bytes .../table_with_ttf_font_and_headings.pdf | Bin 15691 -> 15568 bytes test/table/test_table.py | 32 ++++- test/test_recorder.py | 13 +- test/text/test_multi_cell.py | 5 +- test/text/test_unbreakable.py | 66 ++++++----- 27 files changed, 266 insertions(+), 133 deletions(-) create mode 100644 test/html/html_table_with_width_and_align.pdf create mode 100644 test/table/table_with_cell_overflow.pdf diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 11aa083f4..70324c7cf 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -24,11 +24,11 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install system dependencies ⚙️ if: matrix.platform == 'ubuntu-latest' - run: sudo apt-get install ghostscript libjpeg-dev + run: sudo apt-get update && sudo apt-get install ghostscript libjpeg-dev - name: Install qpdf ⚙️ if: matrix.platform == 'ubuntu-latest' && matrix.python-version != '3.9' # We run the unit tests WITHOUT qpdf for a single parallel execution / Python version: - run: sudo apt-get install qpdf + run: sudo apt-get update && sudo apt-get install qpdf - name: Install Python dependencies ⚙️ run: | python -m pip install --upgrade pip setuptools wheel diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c984251..10cd073f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,15 @@ This can also be enabled programmatically with `warnings.simplefilter('default', ## [2.7.4] - Not released yet ### Added - documentation on how to embed `graphs` and `charts` generated using `Pygal` lib: [documentation section](https://pyfpdf.github.io/fpdf2/Maths.html#using-pygal) - thanks to @ssavi-ict -- Documentation on how to use `fpdf2` with [FastAPI](https://fastapi.tiangolo.com/): - thanks to @KamarulAdha +- documentation on how to use `fpdf2` with [FastAPI](https://fastapi.tiangolo.com/): - thanks to @KamarulAdha +- [`FPDF.write_html()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): `` elements can now be aligned left or right on the page using `align=` +### Fixed +- [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table): text overflow in the last cell of the header row is now properly handled +- [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table): when `align="RIGHT"` is provided, the page right margin is now properly taken in consideration ### Changed - [`FPDF.write_html()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html) does not render the top row as a header, in bold with a line below, when no `
` are used, in order to be more backward-compatible with earlier versions of `fpdf2` - _cf._ [#740](https://github.com/PyFPDF/fpdf2/issues/740) +### Deprecated +- the `split_only` optional parameter of [`FPDF.multi_cell()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell), which is replaced by two new distincts optional parameters: `dry_run` & `output` ## [2.7.3] - 2023-04-03 ### Fixed diff --git a/docs/Development.md b/docs/Development.md index 7c2c90eef..1ce382398 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -198,7 +198,7 @@ To preview the API documentation, launch a local rendering server with: ## PDF spec & new features The **PDF 1.7 spec** is available on Adobe website: -[PDF32000_2008.pdf](https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf). +[PDF32000_2008.pdf](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf). It may be intimidating at first, but while technical, it is usually quite clear and understandable. diff --git a/docs/HTML.md b/docs/HTML.md index 20189a1e0..f854d06a1 100644 --- a/docs/HTML.md +++ b/docs/HTML.md @@ -84,7 +84,7 @@ pdf.output("html.pdf") * `
    `, `
      `, `
    • `: ordered, unordered and list items (can be nested) * `
      `, `
      `, `
      `: description list, title, details (can be nested) * ``, ``: superscript and subscript text -* ``: (and `border`, `width` attributes) +* `
      `: (with `align`, `border`, `width` attributes) + ``: optional tag, wraps the table header row + ``: optional tag, wraps the table footer row + ``: optional tag, wraps the table rows with actual content diff --git a/docs/LineBreaks.md b/docs/LineBreaks.md index f0304a798..a83deadb4 100644 --- a/docs/LineBreaks.md +++ b/docs/LineBreaks.md @@ -10,5 +10,3 @@ An automatic break is performed at the location of the nearest space or soft-hyp A soft-hyphen will be replaced by a normal hyphen when triggering a line break, and ignored otherwise. If the parameter `print_sh=False` in `multi_cell()` or `write()` is set to `True`, then they will print the soft-hyphen character to the document (as a normal hyphen with most fonts) instead of using it as a line break opportunity. - -When using [multi_cell()](fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell), the parameter `split_only=True` will perform word-wrapping only and return the resulting multi-lines as a list of strings. This can be used in conjunction with the cursor position and document height to determine if inserting a [multi_cell()](fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) will result in a page break. \ No newline at end of file diff --git a/docs/Text.md b/docs/Text.md index 8d1ee5a0f..45c737269 100644 --- a/docs/Text.md +++ b/docs/Text.md @@ -90,8 +90,7 @@ the background painted. Using `new_x="RIGHT", new_y="TOP", maximum height=pdf.font_size` can be useful to build tables with multiline text in cells. -In normal operation, returns a boolean indicating if page break was triggered. -When `split_only == True`, returns `txt` split into lines in an array (with any markdown markup removed). +In normal operation, returns a boolean indicating if page break was triggered. The return value can be altered by specifying the `output` parameter. [Signature and parameters for.multi_cell()](fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) diff --git a/fpdf/enums.py b/fpdf/enums.py index 078c5391e..2c0e79197 100644 --- a/fpdf/enums.py +++ b/fpdf/enums.py @@ -129,6 +129,10 @@ def coerce(cls, value): return value if isinstance(value, str): + try: + return cls[value.upper()] + except KeyError: + pass try: flags = cls[value[0].upper()] for char in value[1:]: @@ -198,6 +202,7 @@ def coerce(cls, value): class TextEmphasis(CoerciveIntFlag): """ Indicates use of bold / italics / underline. + This enum values can be combined with & and | operators: style = B | I """ @@ -231,6 +236,24 @@ def coerce(cls, value): return super(cls, cls).coerce(value) +class MethodReturnValue(CoerciveIntFlag): + """ + Defines the return value(s) of a FPDF content-rendering method. + + This enum values can be combined with & and | operators: + PAGE_BREAK | LINES + """ + + PAGE_BREAK = 1 + "The method will return a boolean indicating if a page break occured" + + LINES = 2 + "The method will return a multi-lines array of strings, after performing word-wrapping" + + HEIGHT = 4 + "The method will return how much vertical space was used" + + class TableBordersLayout(CoerciveEnum): "Defines how to render table borders" diff --git a/fpdf/fpdf.py b/fpdf/fpdf.py index acdb42cd8..f0a2b9da8 100644 --- a/fpdf/fpdf.py +++ b/fpdf/fpdf.py @@ -60,6 +60,7 @@ class Image: EncryptionMethod, FontDescriptorFlags, FileAttachmentAnnotationName, + MethodReturnValue, PageLayout, PageMode, PathPaintRule, @@ -256,7 +257,7 @@ def check_page(fn): @wraps(fn) def wrapper(self, *args, **kwargs): - if not self.page and not kwargs.get("split_only"): + if not self.page and not (kwargs.get("dry_run") or kwargs.get("split_only")): raise FPDFException("No page open, you need to call add_page() first") return fn(self, *args, **kwargs) @@ -3342,6 +3343,19 @@ def _perform_page_break(self): def _has_next_page(self): return self.pages_count > self.page + @contextmanager + def _disable_writing(self): + self._out = lambda *args, **kwargs: None + self.add_page = lambda *args, **kwargs: None + self._perform_page_break = lambda *args, **kwargs: None + prev_x, prev_y = self.x, self.y + yield + # restore writing functions: + del self.add_page + del self._out + del self._perform_page_break + self.set_xy(prev_x, prev_y) # restore location + @check_page def multi_cell( self, @@ -3351,7 +3365,7 @@ def multi_cell( border=0, align=Align.J, fill=False, - split_only=False, + split_only=False, # DEPRECATED link="", ln="DEPRECATED", max_line_height=None, @@ -3360,6 +3374,8 @@ def multi_cell( new_x=XPos.RIGHT, new_y=YPos.NEXT, wrapmode: WrapMode = WrapMode.WORD, + dry_run=False, + output=MethodReturnValue.PAGE_BREAK, ): """ This method allows printing text with line breaks. They can be automatic @@ -3384,8 +3400,8 @@ def multi_cell( `C`: center; `X`: center around current x; `R`: right align fill (bool): Indicates if the cell background must be painted (`True`) or transparent (`False`). Default value: False. - split_only (bool): if `True`, does not output anything, only perform - word-wrapping and return the resulting multi-lines array of strings. + split_only (bool): **DEPRECATED since 2.7.4**: + Use `dry_run=True` and `output=("LINES",)` instead. link (str): optional link to add on the cell, internal (identifier returned by `add_link`) or external URL. new_x (fpdf.enums.XPos, str): New current position in x after the call. Default: RIGHT @@ -3398,13 +3414,46 @@ def multi_cell( character, instead of a line breaking opportunity. Default value: False wrapmode (fpdf.enums.WrapMode): "WORD" for word based line wrapping (default), "CHAR" for character based line wrapping. + dry_run (bool): if `True`, does not output anything in the document. + Can be useful when combined with `output`. + output (fpdf.enums.MethodReturnValue): defines what this method returns. + If several enum values are joined, the result will be a tuple. Using `new_x=XPos.RIGHT, new_y=XPos.TOP, maximum height=pdf.font_size` is useful to build tables with multiline text in cells. - Returns: a boolean indicating if page break was triggered, - or if `split_only == True`: `txt` splitted into lines in an array + Returns: a single value or a tuple, depending on the `output` parameter value """ + if split_only: + warnings.warn( + ( + 'The parameter "split_only" is deprecated.' + ' Use instead dry_run=True and output="LINES".' + ), + DeprecationWarning, + stacklevel=3, + ) + if dry_run or split_only: + with self._disable_writing(): + return self.multi_cell( + w=w, + h=h, + txt=txt, + border=border, + align=align, + fill=fill, + link=link, + ln=ln, + max_line_height=max_line_height, + markdown=markdown, + print_sh=print_sh, + new_x=new_x, + new_y=new_y, + wrapmode=wrapmode, + dry_run=False, + split_only=False, + output=MethodReturnValue.LINES if split_only else output, + ) wrapmode = WrapMode.coerce(wrapmode) if isinstance(w, str) or isinstance(h, str): raise ValueError( @@ -3443,10 +3492,6 @@ def multi_cell( align = Align.coerce(align) page_break_triggered = False - if split_only: - self._out = lambda *args, **kwargs: None - self.add_page = lambda *args, **kwargs: None - self._perform_page_break_if_need_be = lambda *args, **kwargs: None if h is None: h = self.font_size @@ -3462,6 +3507,7 @@ def multi_cell( prev_font_style, prev_underline = self.font_style, self.underline prev_x, prev_y = self.x, self.y + total_height = 0 if not border: border = "" @@ -3490,8 +3536,6 @@ def multi_cell( trailing_nl=False, ) ] - if align == Align.X: - prev_x = self.x should_render_bottom_blank_cell = False for text_line_index, text_line in enumerate(text_lines): is_last_line = text_line_index == len(text_lines) - 1 @@ -3527,6 +3571,7 @@ def multi_cell( link=link, ) page_break_triggered = page_break_triggered or new_page + total_height += current_cell_height if not is_last_line and align == Align.X: # prevent cumulative shift to the left self.x = prev_x @@ -3566,26 +3611,29 @@ def multi_cell( if new_y == YPos.TOP: # We may have jumped a few lines -> reset self.y = prev_y - if split_only: - # restore writing functions - del self.add_page - del self._out - del self._perform_page_break_if_need_be - self.set_xy(prev_x, prev_y) # restore location - result = [] - for text_line in text_lines: - characters = [] - for frag in text_line.fragments: - characters.extend(frag.characters) - result.append("".join(characters)) - return result if markdown: if self.font_style != prev_font_style: self.font_style = prev_font_style self.current_font = self.fonts[self.font_family + self.font_style] self.underline = prev_underline - return page_break_triggered + output = MethodReturnValue.coerce(output) + return_value = () + if output & MethodReturnValue.PAGE_BREAK: + return_value += (page_break_triggered,) + if output & MethodReturnValue.LINES: + output_lines = [] + for text_line in text_lines: + characters = [] + for frag in text_line.fragments: + characters.extend(frag.characters) + output_lines.append("".join(characters)) + return_value += (output_lines,) + if output & MethodReturnValue.HEIGHT: + return_value += (total_height,) + if len(return_value) == 1: + return return_value[0] + return return_value @check_page def write( diff --git a/fpdf/html.py b/fpdf/html.py index bf4eb4334..ceafd0eee 100644 --- a/fpdf/html.py +++ b/fpdf/html.py @@ -434,8 +434,10 @@ def handle_starttag(self, tag, attrs): if self.table_line_separators else "SINGLE_TOP_LINE" ) + align = attrs.get("align", "center").upper() self.table = Table( self.pdf, + align=align, borders_layout=borders_layout, line_height=self.h * 1.30, width=width, diff --git a/fpdf/table.py b/fpdf/table.py index ec132df88..96da9455b 100644 --- a/fpdf/table.py +++ b/fpdf/table.py @@ -1,8 +1,9 @@ from dataclasses import dataclass from numbers import Number -from typing import List, Optional, Union +from typing import Optional, Union from .enums import Align, TableBordersLayout, TableCellFillMode +from .enums import MethodReturnValue from .errors import FPDFException from .fonts import FontFace @@ -10,6 +11,12 @@ DEFAULT_HEADINGS_STYLE = FontFace(emphasis="BOLD") +@dataclass(frozen=True) +class RowLayoutInfo: + height: int + triggers_page_jump: bool + + class Table: """ Object that `fpdf.FPDF.table()` yields, used to build a table in the document. @@ -86,7 +93,9 @@ def render(self): ) table_align = Align.coerce(self._align) if table_align == Align.J: - raise ValueError("JUSTIFY is an invalid value for table .align") + raise ValueError( + "JUSTIFY is an invalid value for FPDF.table() 'align' parameter" + ) if self._first_row_as_headings: if not self._headings_style: raise ValueError( @@ -110,20 +119,19 @@ def render(self): self._fpdf.l_margin = (self._fpdf.w - self._width) / 2 self._fpdf.x = self._fpdf.l_margin elif table_align == Align.R: - self._fpdf.l_margin = self._fpdf.w - self._width + 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 # Starting the actual rows & cells rendering: for i in range(len(self.rows)): - with self._fpdf.offset_rendering() as test: - self._render_table_row(i) - if test.page_break_triggered: + row_layout_info = self._get_row_layout_info(i) + if row_layout_info.triggers_page_jump: # pylint: disable=protected-access self._fpdf._perform_page_break() if self._first_row_as_headings: # repeat headings on top: self._render_table_row(0) - self._render_table_row(i) + self._render_table_row(i, row_layout_info) # Restoring altered FPDF settings: self._fpdf.l_margin = prev_l_margin self._fpdf.x = self._fpdf.l_margin @@ -183,64 +191,43 @@ def get_cell_border(self, i, j): border.remove("B") return "".join(border) - def _render_table_row(self, i, fill=False, **kwargs): + def _render_table_row(self, i, row_layout_info=None, fill=False, **kwargs): + if not row_layout_info: + row_layout_info = self._get_row_layout_info(i) row = self.rows[i] - lines_heights_per_cell = self._get_lines_heights_per_cell(i) - row_height = ( - max(sum(lines_heights) for lines_heights in lines_heights_per_cell) - if lines_heights_per_cell - else 0 - ) j = 0 while j < len(row.cells): - cell_line_height = row_height / len(lines_heights_per_cell[j]) self._render_table_cell( i, j, - cell_line_height=cell_line_height, - row_height=row_height, + row_height=row_layout_info.height, fill=fill, **kwargs, ) j += row.cells[j].colspan - self._fpdf.ln(row_height) + self._fpdf.ln(row_layout_info.height) # pylint: disable=inconsistent-return-statements def _render_table_cell( self, i, j, - cell_line_height, row_height, fill=False, - lines_heights_only=False, **kwargs, ): - """ - If `lines_heights_only` is True, returns a list of lines (subcells) heights. - """ row = self.rows[i] cell = row.cells[j] col_width = self._get_col_width(i, j, cell.colspan) - lines_heights = [] if cell.img: - if lines_heights_only: - info = self._fpdf.preload_image(cell.img)[2] - img_ratio = info.width / info.height - if cell.img_fill_width or row_height * img_ratio > col_width: - img_height = col_width / img_ratio - else: - img_height = row_height - lines_heights += [img_height] - else: - x, y = self._fpdf.x, self._fpdf.y - self._fpdf.image( - cell.img, - w=col_width, - h=0 if cell.img_fill_width else row_height, - keep_aspect_ratio=True, - ) - self._fpdf.set_xy(x, y) + x, y = self._fpdf.x, self._fpdf.y + self._fpdf.image( + cell.img, + w=col_width, + h=0 if cell.img_fill_width else row_height, + keep_aspect_ratio=True, + ) + self._fpdf.set_xy(x, y) text_align = cell.align or self._text_align if not isinstance(text_align, (Align, str)): text_align = text_align[j] @@ -248,9 +235,6 @@ def _render_table_cell( style = self._headings_style else: style = cell.style or row.style - if lines_heights_only and style: - # Avoid to generate font-switching instructions: BT /F... Tf ET - style = style.replace(emphasis=None) if style and style.fill_color: fill = True elif ( @@ -271,24 +255,21 @@ def _render_table_cell( else FontFace(fill_color=self._cell_fill_color) ) with self._fpdf.use_font_face(style): - lines = self._fpdf.multi_cell( + page_break, height = self._fpdf.multi_cell( w=col_width, h=row_height, txt=cell.text, - max_line_height=cell_line_height, + max_line_height=self._line_height, border=self.get_cell_border(i, j), align=text_align, new_x="RIGHT", new_y="TOP", fill=fill, - split_only=lines_heights_only, markdown=self._markdown, + output=MethodReturnValue.PAGE_BREAK | MethodReturnValue.HEIGHT, **kwargs, ) - if lines_heights_only and not cell.img: - lines_heights += (len(lines) or 1) * [self._line_height] - if lines_heights_only: - return lines_heights + return page_break, height def _get_col_width(self, i, j, colspan=1): if not self._col_widths: @@ -307,20 +288,28 @@ def _get_col_width(self, i, j, colspan=1): col_width += col_ratio * self._width return col_width - def _get_lines_heights_per_cell(self, i) -> List[List[int]]: + def _get_row_layout_info(self, i): + """ + Uses FPDF.offset_rendering() to detect a potential page jump + and compute the cells heights. + """ row = self.rows[i] - lines_heights = [] - for j in range(len(row.cells)): - lines_heights.append( - self._render_table_cell( + heights_per_cell = [] + any_page_break = False + # pylint: disable=protected-access + with self._fpdf._disable_writing(): + for j in range(len(row.cells)): + page_break, height = self._render_table_cell( i, j, - cell_line_height=self._line_height, row_height=self._line_height, - lines_heights_only=True, ) - ) - return lines_heights + any_page_break = any_page_break or page_break + heights_per_cell.append(height) + row_height = ( + max(height for height in heights_per_cell) if heights_per_cell else 0 + ) + return RowLayoutInfo(row_height, any_page_break) class Row: @@ -367,7 +356,7 @@ def cell( return cell -@dataclass +@dataclass(frozen=True) class Cell: "Internal representation of a table cell" __slots__ = ( # RAM usage optimization diff --git a/fpdf/template.py b/fpdf/template.py index c59392bd3..9e04a49d7 100644 --- a/fpdf/template.py +++ b/fpdf/template.py @@ -300,7 +300,8 @@ def split_multicell(self, text, element_name): h=element["y2"] - element["y1"], txt=str(text), align=element.get("align", ""), - split_only=True, + dry_run=True, + output="LINES", ) def _text( @@ -358,7 +359,12 @@ def _text( ) else: # trim to fit exactly the space defined text = pdf.multi_cell( - w=width, h=height, txt=text, align=align, split_only=True + w=width, + h=height, + txt=text, + align=align, + dry_run=True, + output="LINES", )[0] pdf.cell(w=width, h=height, txt=text, border=0, align=align, fill=fill) diff --git a/test/html/html_table_with_bgcolor.pdf b/test/html/html_table_with_bgcolor.pdf index 76d738ad79a87932931df61ecc1854806280d1a9..b9da523275ca17537e197245b5b23d69db252c4c 100644 GIT binary patch delta 704 zcmeyy^M+@G17p3pDVLocS8+*EYGN)|#hj^A?&i-n5IK7PbNHO?WkJuAIO6+%zgXgX zdC4k|>at1PJ5-<+TYD^)G!fS^t6IpIV>m6orp9iqB7k)$jPXC~MI#=1s@1 zubS+1_*8aFpvt9$B_d~n1uJS-{$88=Qr=7E+UJY=uOEFQ*&Jasqg&&MyJXi1XWtWs zlDC};4OCiv1k@*TSlgE`yYNg#?z?f^&uQI>H+PEfSeTzIb?Rb!kJCh!TIER31&bx8 ztUB0q;*P^i(UQZ;*Isqrzag`9)Hgu(5Ba8{PX0BtovL3U*(>A!lY~SDP}?D1KukwV>-aKc`NBD=`{UPMyfN#x)c%<*cf+(LZYy2CBgb|A-S);i*J90W zZXM+~Ccm-5K8GtcF9jOWKvsTI7MH2QW;bS6ra&Vz1p^RJ$W!0~GYkwYjZD$S3=NEo zF~kh9h#47AzQbx8>*VZg=w@nS>Fna{V&ZDxZ0KTYX5{ANVr1%SPdvA=O`cWBH~yIDy94)+Z0rzHiBtGvS-(ltt|r(t@UP&_ z!`W9o7bZS6O%drdu?gmSzLI0_zAJC9ZTr$D)c%#}%llHp&jEXiA8|4Z)LDp6Jfa@o zb;8;Agdt~Fw!G4hL;eEl6DPzrNe5>XA8Nh5J%;D>xxE)`f0->al&k$QQN;6x`J|~V zRox4VEe?MX-)|Wk!j+nr0*Ug?M$G<9fyQPE1|Xo2r@#ef7#LU@o1%*u z8WJcjE#8$`>-k diff --git a/test/html/html_table_with_imgs_captions_and_colspan.pdf b/test/html/html_table_with_imgs_captions_and_colspan.pdf index dc352bb9e7713fa64df056f7f851fe7d50d6af82..f27a78585af630800cb1408fdf4a88f1ed72e9c5 100644 GIT binary patch delta 410 zcmdn7fpPl=#tl`B^=4dlc3j0JMX8CoTorS2`yBb23pc3dZ`bJ?&wAJ9G#Pcz&Q8sCscf>)n!V=4Yt1tsi+}yT&1$Q3LB%A(dVOfi z6VDT7Or1Y?%8wfAZ;)*N8l2PeZjG(yLiVK+FYo4i^{>Py=broQE`f5QA{ zn{V1?bEdZ;wk8S&AfS+^zy)R)7?@cYV~Cks7@~_A8kw3+P7D{;L^r|6$iTo9O|_Ac zp^@3-x#4!PE>0G%&W=t-Zf>qlPL{5w1{Q{<=B}nrE*6e1u8yX5Hco_8#6ki+v8be? TC^d}>Xtad|m#V6(zZ(|-(fhr#N@R?!_;SOj}L2UR?3%N?i731x=GnOEy(Tngp+S{QS%J?c8=Q0+Txq-l|-y zq~!0kP0(XW`VQ^B%FeSEGT)PwOVZzZcG-ByO`5Rx>&cXFZ%r?Jl3Z>e*HpUC{?A1I zpI?0=Hk&cM4XHO(FaQCCJOwT=!@$7I!URLi+`8ZiRkK# z42__ACoc-Oh;?&wvT(7murzXVHZwFecXTs!HgR)wb2Tt9G<9^gu(NR@q#_m)6NyD7 V6-B9OTt-HQ7M5J9s;>TSTmVc}llTAt diff --git a/test/html/html_table_with_width_and_align.pdf b/test/html/html_table_with_width_and_align.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eba36cceaf994387dd166a5bfafde6c6a4a22cdc GIT binary patch literal 1235 zcmbtUOK1~87?uhV7uyOVdQh3FwThVCO|xmnhPIn*R^ux%O(`^}Y?DddHkr}g3A7a- z2U`%8Dk?ru@rerJ3qiej@Svy{p%xDX^-vL{r`}p9IFpB6TF`@Y+1+pE|Nj5~{%^K6 zrsrXmPOj3U2?5A)4aEF zbhWB}{5tPV^ms+h!NV=a`@xE!Ih48nuIl62n>Svb&MX`r7)W_s=E=V9_D3(Q-I3)T zaN?9}@13L7(vE0IrFF$(@ zPgai%;lSY;9Y<0-6PLk?2ID&gElo2EV1-@Tgz^5qn3dn#N41&TIz%v+RW%K30 zm@DUCo`$)YqOglRE7s?od#=IDw3~I#?`3zSj8V%lv&bs^JX+0u#M%(!giye*KNB~C zGX!vX_`3#@0bSLDlB#N9zZz1*5XwHECN+2^uV0oR)Pha_K2cEKriMie(m_cPYHPz$ GUHA>&|8C^~ literal 0 HcmV?d00001 diff --git a/test/html/test_html_table.py b/test/html/test_html_table.py index d9c1723fe..23cc66b31 100644 --- a/test/html/test_html_table.py +++ b/test/html/test_html_table.py @@ -242,6 +242,22 @@ def test_html_table_with_multiline_cells_and_split_over_page(tmp_path): ) +def test_html_table_with_width_and_align(tmp_path): + pdf = FPDF() + pdf.set_font_size(24) + pdf.add_page() + pdf.write_html( + """
      + + + + + +
      leftcenterright
      123
      456
      """ + ) + assert_pdf_equal(pdf, HERE / "html_table_with_width_and_align.pdf", tmp_path) + + def test_html_table_invalid(caplog): pdf = FPDF() pdf.set_font_size(30) diff --git a/test/signing/sign_pkcs12.pdf b/test/signing/sign_pkcs12.pdf index af46fad04d570f33ffa57d565d014dd7d75b8bbb..b682df59433666e6d3ce95d0f55cdefba9934b2d 100644 GIT binary patch delta 732 zcmW-f&5D*W425wN7Yf3lpdfe$LjIa2TX$YY+BCRP)P>t`H!$lDybfV*%F8fgU%Dtw z&&m7b+}%I9yMOZM>&;UazJ9-X|I%3)jKCQle&5uq7q$PNvF!T%_S-YV*w@;$@xbJ`*cdyp6p( zkocQZ;39pvVVMvk@mdfO2N$c&S{ zq-(5A6fY4xVonXv5o40?<7NY@rce}h8B%Js-L~DJRx|}2Jf|e073(9|w%^&vwC6!1 zuwVB?7eqTcn$Z@wO~ZU^3>+F#vDGiKK)WG5VCh|*qig*nQ4bQaEpU&lL&3ofaE~&g z4$V9xs$b;k>(ve|F-L>0>KS!L_7PZC-k5kQ+R9|=cq};`r4~BO@N!efC%0BG%GL?pdHg)Rf87%ZdV?PO`S&T`yjuPL85bY7=O4E^2(TYd=ey(A z`NIkKHN6)?NHlO#8idq{Dq`hT?Yd+hf6jjVefR0I&_>oXtl>T9lo*q++hTOi(H&8G zXWP6QjwQPf2M{qv2t%elw{HStQx|5kO*vf|JAmPAvxGW~6VyVjX-g+uxjR+3_yAF# zIvP!DDds-OO25W*8$7$!#g5^#V-E#I=E(xmC~yyU9hh5T*2c**mUZN|7qw`-3P2ZS zbRnxE5jkVm-)0O6^GjPxlDoignXNhGTCjn+fQeaJ6LhiW+n0~0f5PvtHvj+t diff --git a/test/signing/sign_pkcs12_with_link.pdf b/test/signing/sign_pkcs12_with_link.pdf index bec60dd7412c780a40170142c14f920814ba5b00..261e50f669cf853bfbd5bbbbd7747eaa145e4b19 100644 GIT binary patch delta 765 zcmX9+JBnRL5Jlq&WDgiL4?8hRoC=BhS9R`8nKFl6-PNH%+Q3_ZqPG#j>_C}guf!V= zu94w3w?9;!Uw!-h;@jsJU*P_SA$&^?8Bg5)e*EiY z;Oq;GF8by6;qv;9&iC!t%loSyFi4tV5@E+-1HwQ6qQj`bAl0kuWr7gw*s?)@)>O-G#(-Gv2U!Rq@Wvng_fCfeRLW_cX7 z;#>;{O(BEbQWN9~IaceJsj=N=oZwSzl6x}^tj%rYJO|YTMO#}9Za%sKiA6`k06ai# z2-#*Zx&u?D!5UwuuV;W z=H-ob_7K!f%OE6BPI+}$bI;sOFRhj?l@^_PW~b1&q^Zg|SZ#5YAem#@8W%L|4 zC6yR+tkXtD8=Dd1bQxe`!cLbQ6>-+Y-2v}~whGzFkyZ~(4XsX-az0NA_lZO;1$Y+L k%5af>BhMiNR~z{1)Rg3K5b4lo#1sDd>Fv{-KmNJ<4{hnq&j0`b delta 664 zcmW-fO^OyU42E%>m7)vBp98oO5t1fN6I{6Q5-#)8EL`Xj<^+Q9UBiViCvfRb@IGRd zLX)=S?fZoM`Eu{imwVsg`jvKrfDKn+Rp|u<2n&XT4$ubrA9vn;xbDXfLjRvJ`113~ zrz^3`+Z(yUyu7=4_Hq(J<53_XBcwrec0Bn}`M0KDKHt>k{=*lSZ%-fHI&cY1UbVN` zOw}mM=}S3dnC9FnGiJ^sRRs&%*J_zoKsHM&&|!Cyfl!z7OeB_45Z#7ln_Z402Przm zj2qj+SE={9)r@^CignxUaAtMWF&70^W7Y6#^@%u_4BV`fJJVbzG~qZrC*xLCDhk-n z5)z{dO={o_lykK!BIKAkv7L;Kfi&>8S943T?;)rVYe;x@Xbp{nKN>tEr=%@+k6o&s z=>uS=?iie7qvf_i-qex|DQni6WAut6h-=Kv2Pe;ultt!ZyNgcuZY5=i1xz98?#;Ay zx$)*vGkQr7uQLsO$oh=I1PYHhuPe=%+l3KxuF$QmxB=X`G*~EskL~2zQ-bNVW%Kld dmy4Hq7W6gN*mpmke|&TK_5ADI>-WE}{sJd=x|9F_ diff --git a/test/table/table_with_cell_fill.pdf b/test/table/table_with_cell_fill.pdf index af6346c1303926b34ef2e9204a9f986d57d1848c..5ee1441359288b033522e07e63d4d9a7f780b5eb 100644 GIT binary patch delta 1390 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 literal 0 HcmV?d00001 diff --git a/test/table/table_with_headings_styled.pdf b/test/table/table_with_headings_styled.pdf index 1a3232048c7774cde3609738746376bd594f3985..a2202d713e6f674401c4dfdfa0252d2d3c92e5ad 100644 GIT binary patch delta 852 zcmZ3(yM%XxBV)ZGmz^C~aY<2XVlG$3oV8Q$=3RCWXnp@#q?l7&C+>a!+tb??i3bWc zJ!93G)#mVqLtXII?Z*@T&YZh(x8s7ivroR9xp_1HsnE1;rx{#zx_RLr{>3$IS)`Hh zl}A2odf3b@ZiRt|(mo2Giqc@-8geLOf){u8gSlGgFD3GxfBAl{cKzz8d8cJw5XeFC7nS$AdaC$zB0H^+IjDJ?eo$C!|&_6Pem}Rd~Oc)*0ZfLxYp(N(00k(1rOvB`C}Tt zFPJL0=}7gC+j8-nx)t7En7V7{uem{GwP$todNmzu9&Y>aaQgDQzaCuD|7840za!{r z_PdA8MXXgdhA)%1+tz*d{2ctLlu=C6i-+Oy1o3m+8;IyZzJh zEn9ca(yLm(E=_Emp(;nitz&Kc4)xY=_0eq7wr}J}1^U%&yz)Hb0PQqIT~t zDWj_G`_jbR&T=X(4{Kkse7)}9Yx|9q{U!EB#uxK1ekRg!X~vhl+eg1onLkOrCvM$( zwVmyO#au3#KC4s~ewiv*dFYZ~NC8Bl#Alxu0`LBrw+6}bZ+^x6hSAQ@NWlOE6!H|f zzzhRJAixka!XjpDU@=*Q%{tcE$jHdhz`(`Dz{1JJ*u})z+}y>)#nsWo$lTP@)WX!x ehM}OYgSpa;dYUe4P=@mJPPu#!m_3WUcYJY=R8{_0%YVRkcFA$p6?KFeypH5!*hmUbhTNY^~ zeC3%Rc6jolma~f__8HghpQhpN{ikh{*eM6ysRmL@E*+BkvEY>Bt@`y3Eo=9cCx)I@ zymL^wRxej`*0JNw3TIhZwI2#4H$D0^@k0KCA|ch*H4C%60y86Q5AOb*TW{ld{oVEV ziC;e#S^SXlpUd~|l|s;b<;f@HTeMCs6AF8rxMb#n2d0Nib};!nM=58u9{+gZ-}KBS zZpU|=nAf89<>~#@CbwCfTye@i_08HsW?fD%xcncdeeN&z(Cs^;SkJb~;98g4LraJ$ zGCP>gJ8CPZbUrWGZo4;y-SOQ*?o#95^McB1&v@-oJ;w5}?e?MB@$<@GFD~AH;`@pF z3SLwDD|z#$XkU2R?I-{J_GSL}CihkS&F}683cm*mAMNm)`pxXK?xzn+>u0WbV7kY5 zox`0)joV~(&gY7!&HD7%De~;IZoO;A*BpsX@(~c+>oNEI`P~0boOAOgF7M8}dVEjY z_miR>7c#y?@3xoGTzEP5Q_01nzh+$j;J1@WP--T=!?~3_)pJIWc zPZ6S1Ri?ycPE^+cxjpg96sAC_T)viXiQnrPd-&9}H$P&2!)Rw@pkM$33V8}#V1|LA zp`j&)m=P8+V?(paJZ#pnt|lf1=8lezmKLV2=8jHo2Iht)j*do7CN366ZjPp|b~Xf6 b#6ogTVo^y&QED2Op|QC+m#V6(zZ(|-aEXMn diff --git a/test/table/table_with_images_and_img_fill_width.pdf b/test/table/table_with_images_and_img_fill_width.pdf index b97b74c3e04e2ec7a955b229ad6afbf302fe9f03..391c8e31d35eba674101708c79b4b8c50fe6d1c0 100644 GIT binary patch delta 1017 zcmX^7nq|*xmJOAR^`>TAc6MCFB}J);xm*=<=1#ktciBOp_5ELwVovku_1AZA)6ZNO zv{;GdEccpQ0Zbc}CxxuY@9(w!ZCgg?6=w9ow|(X~4S&jFav!)pWU>V9W8SFIb?yd!?cJi)2mB z6yDI_j#VvRh4<{fE7zy~B4>9c|LggBHrD*|>Y*aOsy>o^BIR?Ms&>p=v8Z3IS8OX+ z`>yT(O@-&HhfY}~;JRhmj>jTb4xe=qJv{GI%#=`z%dy$>?_2QCR}IZbc3FI=5@=Zf zzd3*Cmy2FYTXhaq=51Sk-T1Ley@7w~%^05QF1~9=l5);){`s6#)U&thW^Uh%+4jxs z7DpMgUo|?eRI1>pxzt`({j1TlzBy#6fz+pe%DqC%+~k$D1H(VuICOPxOMmr`Ss!Os zbOh^cSSDv79+3){>w-0A?z4;_)lk@Tx(100}wU`oWFXV5#EC0Olt$9c`{~Ez%l8w4a3Db=? zZV==_*bxo4a{{EQkvIX-}ZZJLyjhU^GWycD3hNZYq}=Jo;&i$&0$8?tB22& zx_Z7pDD1ZObF_`&TlL`t+xP1I_J`WvRSNGZ7GAb2Xm$5Y@9!A8a&T+WqTO z#$|i9^m7jyy~Ep0zXF8?9bQkqmlOTJ;^>8EXFrs(-7;$W=A$NN(WJz5?TYhiR@Y`I9j+%n(D&+}I3F%*f2#+#FrZ!qVIlP0ZNH0%}J*!eV1%LsN81j7drJZfb~b zpt*&q1-f2y3v*-B=^CbtwsDS5hK?qVhQ^M@&IT5S&c+sQX2u2v=4P%2#xBmrj+S;d d1XaXBvUFlmNkvg=8W+%TOLHz&RabvEE&$GvkR|{C delta 1024 zcmdnLLGJLp>xbHBsB>3*H7tX|&}@V{_S=}&#asS8&x1)UJ; z^6ETv;FF}?owen^nYWZ~-qrNfUuXUu_1Jc+kWh~FLsK7dT2-*UJ0crv`f`m%>5ePT zf8(u}{tjrr=(Xa-ESTh9+owAXTz6cq4c`CfvE56(D~WT3r}BO8RDbb6atD8T$<>~1 zPOA#$-~ReAH%*r5v%=hZ9hb-$Hfz`8*2MR6~fDNsFGnf2gT+@a;VHK#Aly(AEHTXLw&O*vru;L z>+{>*|6F=+uV6yD+{CutxY(5h6^1eK(2Rb)ms+p;dfzZVnj^f)%BgHw zu#h=h6VM^wllpY#PCTYNZ$Wc<0{=USiM;2&C+=?Ve|uW%$5~Y$-k?lfu3E|BFRS*Z z3Dy~;I^=}jJo_f#>yMDj_H4%IJ(%6spUa-t=(;nx#Vn(0ciyq<+HKpcE7LvCaYhMi zI$B@W3aCQb&X28OPdhITdtRm4Jac4ARUMNw)Rm$`+7feDwYs;j>n7XU~6m=*v4 diff --git a/test/table/table_with_multiline_cells_and_images.pdf b/test/table/table_with_multiline_cells_and_images.pdf index 85210326b74995a96d9b08e09cfc5a006ff8362f..d4b1a85eb9a256c66069a0bfd5da42d35107ab44 100644 GIT binary patch delta 1924 zcmah}c|6qX8fPqxNg6Iq7!2JkBg{U(Q8+TjzAs&qZLFEHMn}T$Ln_Ol$w|evL>M{N zh{143HJ4LeR1(?7a#Z##S&plF&mZULkNf`fKF{~_eZJ50KA-1#-+mFDS`n>@7X~S0 zBFxYbb}=%-pAig;{%3l2PevX9zrMaB+wl1UQlMCqu71q_rxP^FWg*Bs>zJ;F_pSJC z?1nfgub!^2KlOWUJ9@wi3E4g6-DS10x;QT$x~p3Vhx#@dbZq5%sFfXeRPDZx)|`+O zuVJ{!oqhKt(0teHjyXnUA+g`m9`rlgbWJ;{+I;lm|CxA|j=Slk&{VNe0OS#4+_}ML*7fon6!ryHaLd}bgv=QXc z=Nj-lFl5T3Cu$8^aot~O)#_=u;sKd_1r3-xQjSfYU(zq`_1%x27hD#GS14uu*4mEEabSRZwc6(Xbm+smRwW*H(`J9HWGRECzi5z3kf4mM>^0r|1YKCF|rPEywbt+bm4P-Mte&rTE zbB%g9)GBYI*ybU`&T?|g(-2AeQm+S^J!5O zz2q^z^ScF8&aBW$J${hq1NEK`ueF#u8QHJOs*RwvbO@m|)TG19BmWCrC2hF4{#11R ziFNHzaiFc;+T}n4Z)9$!X|=G{a^z~r?hn>ZnY4topm~SxRx^pBrikns-kN`C%dy<3 zGBDi()thCdW$U3Jru&R*6*TTaN2zsXOb*41JPOI>9nYo)Oo;$vChC`RP^LYX8RHey?#7`>ToV(XA?eDPX!y4|=7 zY=DEZpAl0#9y#{p;Y*eKiC#6lS7>SvC113{+WE6(64=H5g&{}jKF>KybCIw)IHOWr z=V+lLUNcd>Bl@D?w=rKQ6@0L4l4DnDNp+MudW~3A%bQ@F=&^Ux$uszI+q6|M^Z5MT z!&Ymfhd$hT5sqflZ666uv+7ub8_3Z*F6ICL delta 2005 zcmah}do&biAO0{ChA@oFGBg;oMD5(nCF4HEEyndDYLsG^j9dpvV~}JTgxr-_!&Iw-n`bNfYD5mNfuN(@MSn4M@^|DtI)0#Pg+niq+H0X5`rWpuRZ zPh0tn)S_^=Av$f|)(IMy)SU*(^~}*kJj^$4oIA2fF`-^hh{^I?SvwDWomIn#0NW3O zx*v&ki7Jj+v~?_PU3EJ4*E*p|Fm5)Q?w=Pp)P=2{6<`uBH|y&vGXbcA5CE$gpW4Bb z^6xTAla}oFziD19zUdL!q}s@OFibc5X{Q(6Ss9o&01mhzmk|`~5_GHd?ENw=t@{JU zd^>G0H_X44Su^ryWq>CbAvQCTf3fEjv<^PyTUu&!G`#wo0pgTTOiw|fi56hkE0xKK! zILk)5YP;BZ4C!aerQoACo=(>nX`Y4?%qN#PT9Zf~Sb06j!NK*RyKErz_siz=x7EWr z#pANs;#Q4ps4e70vx%08Fe&xtGw4^_#sCm@w+Q=oYQCi$~B*5!~I4l9|pcDo%V7QTCN5Q!= zDVof_57k@g>8&rz>5DBkg>x0^I&Q52T%l;j)>BcircmTbU$pa;olMUwI>F%qt9vDOWV$|NeLhGK#pQ#w;G>{m^ju7ZH-KU!I-gY5NjtSCC0cR0{IUcDsJ~L;TWSDU9UptDW-iHs4%k=P{_HoPK#y zj*|o>Roc;Tf>~H-Ygm<~eR6GOL&TAJ1$aq$6E{q`74ArGt<|TkrpC$mjM45%*~@1t zF7|7TFW9!b{7U~m@9-&#-KvRY^96gT3*dP}=7p1qI%Ej2h&I_CI5iPfMxurlh-WA? zR=L(ZW`qyB!Sp>uugEgHaeEd6F}9wnA~7tE2S~OXr~@1|yZNJSK)joH^C^xXT(s>> zL$Uktz%e(D1ursvmLagoUU=uJ31qPowP5AMdp9VhNuj|RwZE4fb%@{bdgL6bAiZ|t zm$wgoo-09A4%LYv9`|%+zyAho;JYTORtLGG_nqL$T$-NpAh&^8F zUJdaeItq`&tN>C@h#~p*L!eK5#YO8h04r~bt;hu)Usos)UkB{Yl@3-}9A_i$<&;4q zB< zA@Wh~SZc(xSB|s^)%4S0YSE?8aO>kh7L%RWsZdTDeJ^!`&=(z;g7}jbd!1!`?v={- zY;u1~%k)^W?Af4TSxXchOHJ7Ka8__m)8OVWOZu@n#hsUUx)&#Qs}?}%w=_DE2ZaJg z_M;IgsV+U8(ik-Wj;8-dI#iNm&{Y@U|CBqZ}m@>#d6#LkcV#jeTk;dlId z^o7JLp+-HgO-rlosEG;^%?c7CNF)=D#=wyK2NnzieJ+qF3=Vk^L!*d{mPj&hVffo`^^N zvsZFo8c!tPF}Zd`5n~z}k0FpP309x(i?>8tSr}p|SSt+P!UBi2w8Wz*CZzw%IP>MN YITscY9p>i`!V`%YLy(S+6_o<|FV0n2`2YX_ diff --git a/test/table/table_with_ttf_font_and_headings.pdf b/test/table/table_with_ttf_font_and_headings.pdf index e2a76f4078cdb94684ddf48bdd2cc945493c329a..d7de387696097264027cae5d2f4fbe60d2f80e45 100644 GIT binary patch delta 7010 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 max_no_of_lines_in_cell: max_no_of_lines_in_cell = no_of_lines_in_cell @@ -169,17 +172,18 @@ def test_multi_cell_table_unbreakable_with_split_only(tmp_path): # issue 359 for row in data: max_no_of_lines_in_cell = 1 for cell in row: - result = doc.multi_cell( - cell_width, - l_height, - cell, - border=1, - align="L", - new_x="RIGHT", - new_y="TOP", - max_line_height=l_height, - split_only=True, - ) + with pytest.warns(DeprecationWarning, match=expected_warn): + result = doc.multi_cell( + cell_width, + l_height, + cell, + border=1, + align="L", + new_x="RIGHT", + new_y="TOP", + max_line_height=l_height, + split_only=True, + ) no_of_lines_in_cell = len(result) if no_of_lines_in_cell > max_no_of_lines_in_cell: max_no_of_lines_in_cell = no_of_lines_in_cell @@ -220,26 +224,26 @@ def test_multi_cell_table_unbreakable_with_split_only(tmp_path): # issue 359 def test_unbreakable_with_local_context(): # discussion 557 - pdf = fpdf.FPDF() + pdf = FPDF() pdf.set_font("Helvetica", "", 10) pdf.add_page() pdf.set_y(270) # Set position so that adding a cell triggers a page break - with pytest.raises(fpdf.FPDFException): + with pytest.raises(FPDFException): with pdf.unbreakable() as doc: with doc.local_context(fill_opacity=0.3): doc.cell(doc.epw, 10, "Cell text content", border=1, fill=True) pdf.set_y(270) # Set position so that adding a cell triggers a page break - with pytest.raises(fpdf.FPDFException): + with pytest.raises(FPDFException): with pdf.unbreakable() as doc: with doc.local_context(text_color=(255, 0, 0)): doc.cell(doc.epw, 10, "Cell text content", border=1) def test_unbreakable_with_get_y(): # discussion 557 - pdf = fpdf.FPDF() + pdf = FPDF() pdf.set_font("Helvetica", "", 10) pdf.add_page() pdf.set_y(270) # Set position so that adding a cell triggers a page break - with pytest.raises(fpdf.FPDFException): + with pytest.raises(FPDFException): with pdf.unbreakable() as doc: doc.cell(doc.epw, 10, f"doc.get_y(): {doc.get_y()}", border=1)