From 128bb5454684caf8cf01554bba71b01883b7901c Mon Sep 17 00:00:00 2001 From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Date: Mon, 27 Mar 2023 19:23:11 +0200 Subject: [PATCH] Renaming FontStyle to FontFace (#737) --- CHANGELOG.md | 2 ++ docs/Tables.md | 3 +- fpdf/fonts.py | 10 ++++-- fpdf/fpdf.py | 40 +++++++++------------ fpdf/graphics_state.py | 20 ++++++++++- fpdf/html.py | 6 ++-- fpdf/table.py | 31 ++++++++++------ test/conftest.py | 2 +- test/table/table_capture_font_settings.pdf | Bin 0 -> 1833 bytes test/table/test_table.py | 22 ++++++++++-- tutorial/tuto5.py | 4 +-- 11 files changed, 94 insertions(+), 46 deletions(-) create mode 100644 test/table/table_capture_font_settings.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2e1e2a4..75f9c6b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ 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.1] - not released yet +### Changed +- renamed `fonts.FontStyle` to [`fonts.FontFace`](https://pyfpdf.github.io/fpdf2/fpdf/fonts.html#fpdf.fonts.FontFace), and `FPDF.use_font_style` to [`FPDF.use_font_face`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.FPDF.FPDF.use_font_face), to avoid confusions with `FPDF.font_style` ## [2.7.0] - 2023-03-27 ### Added diff --git a/docs/Tables.md b/docs/Tables.md index 1e7a1c79d..802b71832 100644 --- a/docs/Tables.md +++ b/docs/Tables.md @@ -83,10 +83,11 @@ with pdf.table(first_row_as_headings=False) as table:y ## Style table headings ```python +from fpdf.fonts import FontFace ... blue = (0, 0, 255) grey = (128, 128, 128) -headings_style = FontStyle(emphasis="ITALICS", color=blue, fill_color=grey) +headings_style = FontFace(emphasis="ITALICS", color=blue, fill_color=grey) with pdf.table(headings_styleheadings_style=headings_style) as table: ... ``` diff --git a/fpdf/fonts.py b/fpdf/fonts.py index db8ae97c8..3f20f1b62 100644 --- a/fpdf/fonts.py +++ b/fpdf/fonts.py @@ -1,5 +1,6 @@ """ -Definition of the character widths of all PDF standard fonts. +Font-related classes & constants. +Includes the definition of the character widths of all PDF standard fonts. """ from dataclasses import dataclass, replace from typing import Optional, Union @@ -9,7 +10,12 @@ @dataclass -class FontStyle: +class FontFace: + """ + Represent basic font styling properties. + This is a subset of `fpdf.graphics_state.GraphicState` properties. + """ + family: Optional[str] emphasis: Optional[TextEmphasis] size_pt: Optional[int] diff --git a/fpdf/fpdf.py b/fpdf/fpdf.py index 27bd899cb..c514db9d3 100644 --- a/fpdf/fpdf.py +++ b/fpdf/fpdf.py @@ -73,7 +73,7 @@ class Image: YPos, ) from .errors import FPDFException, FPDFPageFormatException, FPDFUnicodeEncodingException -from .fonts import CORE_FONTS_CHARWIDTHS, FontStyle +from .fonts import CORE_FONTS_CHARWIDTHS, FontFace from .graphics_state import GraphicsStateMixin from .html import HTML2FPDF from .image_parsing import SUPPORTED_IMAGE_FILTERS, get_img_info, load_image @@ -143,7 +143,7 @@ def __str__(self): return f"ImageInfo({d})" -class TitleStyle(FontStyle): +class TitleStyle(FontFace): def __init__( self, font_family: Optional[str] = None, @@ -4648,46 +4648,40 @@ def _use_title_style(self, title_style: TitleStyle): self.ln(title_style.t_margin) if title_style.l_margin: self.set_x(title_style.l_margin) - with self.use_font_style(title_style): + with self.use_font_face(title_style): yield if title_style.b_margin: self.ln(title_style.b_margin) @contextmanager - def use_font_style(self, font_style: FontStyle): + def use_font_face(self, font_face: FontFace): """ - Sets the provided `fpdf.font.FontStyle` in a local context, + Sets the provided `fpdf.fonts.FontFace` in a local context, then restore font settings back to they were initially. This method must be used as a context manager using `with`: - with pdf.use_font_style(FontStyle(emphasis="BOLD", color=255, size_pt=42)): + with pdf.use_font_face(FontFace(emphasis="BOLD", color=255, size_pt=42)): put_some_text() """ - if not font_style: + if not font_face: yield return prev_font = (self.font_family, self.font_style, self.font_size_pt) self.set_font( - font_style.family or self.font_family, - font_style.emphasis.style - if font_style.emphasis is not None + font_face.family or self.font_family, + font_face.emphasis.style + if font_face.emphasis is not None else self.font_style, - font_style.size_pt or self.font_size_pt, + font_face.size_pt or self.font_size_pt, ) prev_text_color = self.text_color - if font_style.color is not None and font_style.color != self.text_color: - self.set_text_color(font_style.color) + if font_face.color is not None and font_face.color != self.text_color: + self.set_text_color(font_face.color) prev_fill_color = self.fill_color - if ( - font_style.fill_color is not None - and font_style.fill_color != self.fill_color - ): - self.set_fill_color(font_style.fill_color) + if font_face.fill_color is not None and font_face.fill_color != self.fill_color: + self.set_fill_color(font_face.fill_color) yield - if ( - font_style.fill_color is not None - and font_style.fill_color != prev_fill_color - ): + if font_face.fill_color is not None and font_face.fill_color != prev_fill_color: self.set_fill_color(prev_fill_color) self.text_color = prev_text_color self.set_font(*prev_font) @@ -4710,7 +4704,7 @@ def table(self, *args, **kwargs): col_widths (int, tuple): optional. Sets column width. Can be a single number or a sequence of numbers first_row_as_headings (bool): optional, default to True. If False, the first row of the table is not styled differently from the others - headings_style (fpdf.fonts.FontStyle): optional, default to bold. + headings_style (fpdf.fonts.FontFace): optional, default to bold. Defines the visual style of the top headings row: size, color, emphasis... line_height (number): optional. Defines how much vertical space a line of text will occupy markdown (bool): optional, default to False. Enable markdown interpretation of cells textual content diff --git a/fpdf/graphics_state.py b/fpdf/graphics_state.py index 8dc55d92d..b2482da3d 100644 --- a/fpdf/graphics_state.py +++ b/fpdf/graphics_state.py @@ -1,5 +1,6 @@ from .drawing import DeviceGray -from .enums import TextMode, CharVPos +from .enums import CharVPos, TextEmphasis, TextMode +from .fonts import FontFace class GraphicsStateMixin: @@ -311,3 +312,20 @@ def denom_lift(self, v): ([docs](../TextStyling.html#subscript-superscript-and-fractional-numbers)) """ self.__statestack[-1]["denom_lift"] = float(v) + + def font_face(self): + """ + Return a `fpdf.fonts.FontFace` instance + representing a subset of properties of this GraphicsState. + """ + return FontFace( + family=self.font_family, + emphasis=TextEmphasis.coerce(self.font_style), + size_pt=self.font_size_pt, + color=self.text_color + if self.text_color != self.DEFAULT_TEXT_COLOR + else None, + fill_color=self.fill_color + if self.fill_color != self.DEFAULT_FILL_COLOR + else None, + ) diff --git a/fpdf/html.py b/fpdf/html.py index 48d957eea..106c86832 100644 --- a/fpdf/html.py +++ b/fpdf/html.py @@ -10,7 +10,7 @@ from html.parser import HTMLParser from .enums import TextEmphasis, XPos, YPos -from .fonts import FontStyle +from .fonts import FontFace from .table import Table import re @@ -281,7 +281,7 @@ def handle_data(self, data): emphasis |= TextEmphasis.U style = None if bgcolor or emphasis: - style = FontStyle(emphasis=emphasis, fill_color=bgcolor) + style = FontFace(emphasis=emphasis, fill_color=bgcolor) self.table_row.cell(text=data, align=align, style=style, colspan=colspan) self.td_th["inserted"] = True elif self.table is not None: @@ -588,7 +588,7 @@ def handle_endtag(self, tag): bgcolor = color_as_decimal( self.td_th.get("bgcolor", self.tr.get("bgcolor", None)) ) - style = FontStyle(fill_color=bgcolor) if bgcolor else None + style = FontFace(fill_color=bgcolor) if bgcolor else None self.table_row.cell(text="", style=style) self.td_th = None if tag == "font": diff --git a/fpdf/table.py b/fpdf/table.py index 0c77ff2be..adca89e6f 100644 --- a/fpdf/table.py +++ b/fpdf/table.py @@ -3,10 +3,10 @@ from typing import List, Union from .enums import Align, TableBordersLayout, TableCellFillMode -from .fonts import FontStyle +from .fonts import FontFace -DEFAULT_HEADINGS_STYLE = FontStyle(emphasis="BOLD") +DEFAULT_HEADINGS_STYLE = FontFace(emphasis="BOLD") class Table: @@ -45,7 +45,7 @@ def __init__( col_widths (int, tuple): optional. Sets column width. Can be a single number or a sequence of numbers first_row_as_headings (bool): optional, default to True. If False, the first row of the table is not styled differently from the others - headings_style (fpdf.fonts.FontStyle): optional, default to bold. + headings_style (fpdf.fonts.FontFace): optional, default to bold. Defines the visual style of the top headings row: size, color, emphasis... line_height (number): optional. Defines how much vertical space a line of text will occupy markdown (bool): optional, default to False. Enable markdown interpretation of cells textual content @@ -70,7 +70,7 @@ def __init__( def row(self, cells=()): "Adds a row to the table. Yields a `Row` object." - row = Row() + row = Row(self._fpdf) self.rows.append(row) for cell in cells: row.cell(cell) @@ -218,10 +218,12 @@ def _render_table_cell( text_align = cell.align or self._text_align if not isinstance(text_align, (Align, str)): text_align = text_align[j] - style = cell.style - if not style and i == 0 and self._first_row_as_headings: + if i == 0 and self._first_row_as_headings: 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 @@ -240,9 +242,9 @@ def _render_table_cell( style = ( style.replace(fill_color=self._cell_fill_color) if style - else FontStyle(fill_color=self._cell_fill_color) + else FontFace(fill_color=self._cell_fill_color) ) - with self._fpdf.use_font_style(style): + with self._fpdf.use_font_face(style): lines = self._fpdf.multi_cell( w=col_width, h=row_height, @@ -298,8 +300,10 @@ def _get_lines_heights_per_cell(self, i) -> List[List[int]]: class Row: "Object that `Table.row()` yields, used to build a row in a table" - def __init__(self): + def __init__(self, fpdf): + self._fpdf = fpdf self.cells = [] + self.style = fpdf.font_face() @property def cols_count(self): @@ -315,7 +319,7 @@ def cell( text (str): string content, can contain several lines. In that case, the row height will grow proportionally. align (str, fpdf.enums.Align): optional text alignment. - style (fpdf.fonts.FontStyle): optional text style. + style (fpdf.fonts.FontFace): optional text style. img: optional. Either a string representing a file path to an image, an URL to an image, an io.BytesIO, or a instance of `PIL.Image.Image`. img_fill_width (bool): optional, defaults to False. Indicates to render the image @@ -327,6 +331,11 @@ def cell( "fpdf2 currently does not support inserting text with an image in the same table cell." "Pull Requests are welcome to implement this 😊" ) + if not style: + # We capture the current font settings: + font_face = self._fpdf.font_face() + if font_face != self.style: + style = font_face cell = Cell(text, align, style, img, img_fill_width, colspan) self.cells.append(cell) return cell @@ -337,7 +346,7 @@ class Cell: "Internal representation of a table cell" text: str align: Union[str, Align] - style: FontStyle + style: FontFace img: str img_fill_width: bool colspan: int diff --git a/test/conftest.py b/test/conftest.py index f32d5e54a..15fda01ea 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -262,7 +262,7 @@ def handler(_, __): def ensure_rss_memory_below(max_in_mib): """ - Enure there is no unexpected / significant increase between + Ensure there is no unexpected / significant increase between the process RSS memory BEFORE executing the test, and AFTER. """ diff --git a/test/table/table_capture_font_settings.pdf b/test/table/table_capture_font_settings.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ba0cd6b38350ce97fa87707ccbc27ff4b798dc69 GIT binary patch literal 1833 zcmY!laBHDUpWF|W0 zS13dq07XnKP4$c{6f8^(^(-ubvLTfPsS5f5iRr1uTy}O`sd*_NmCPSDKRpGytU4C$TcWv_wJQKQGleKc_S|4xcdJLW%=c{C@-TyiSw&&@m6 zpIq+lHe*qw+bj7e#d|ID=N-JiTjzU9rRPo+)yE-RmnK!}c$xLOe{yPiKl4;(=*vo> zAL?$5I@6kGB*b(ZpYjR{vD9T*8Go|XSq+95!$~x(zH9RRayVKIq$27aLl`WXSdq_BN_|Z6PvnT&fC<}btrY$?f=TW z`#e`n@VeNw+vHzopaZ`%v*_cjCht>4)1N&os61S-QlUH!>_c}aSLq5KgR z$xv6#WBoxQIe%7cn)9W4d8F3mj@z#b&2@U7SpIqyu~G0|gMoilo17H4)}@GDG1Fcz zwT-EZTKBn&^}~sF!%N0rE(in^KF*tF7RjL(!}RB6#hJgts~hBIF=$<4+2wf7)I|1X z+-yCMh(#vPUOk^Ex>{Ji&Hem=B)bmI3zvHMxUWuMRv&$H<`<`mdxs5PN>*iPtvFzv zym6oD+suwqwY?1UzC&zh()w^Nff%zu^373e)@Er&ShP-u-^C zCyCMKbK$dC_gt5nD1QkfGIsQFI_=DJTuQRuQ(GXz@;Bt znp6VL?VvmlEG8gXA7lnn7Ba(Qg;Rb`3gtGKjZr6f|6{3@yzp4ULQq4J{3TAj;5E-M~Oy6G?MLQED2Ofr6m{ z7f1mFD43a=8k;JlDImmvc7T9F9zx6#=mRt{LrY9`M&=k|h6aYfa6r{-XlMv5JJH09 z41v`EnwT*bF%v^1J4%WYGjmdlz!g$(W>qT4Z~8&``6UXVIt3Wyo_T5c3P25Dwu?fv zjiZ~Bqltxyi?OS#g|oApqoaj^iMgwRxtp<(sf&S;lO16d;P@ymNh~S>2dJTmg#nkU Js;j>n7Xbf6eZK$z literal 0 HcmV?d00001 diff --git a/test/table/test_table.py b/test/table/test_table.py index f038c84f7..a19b50054 100644 --- a/test/table/test_table.py +++ b/test/table/test_table.py @@ -4,7 +4,7 @@ from fpdf import FPDF from fpdf.drawing import DeviceRGB -from fpdf.fonts import FontStyle +from fpdf.fonts import FontFace from test.conftest import assert_pdf_equal, LOREM_IPSUM @@ -190,7 +190,7 @@ def test_table_with_headings_styled(tmp_path): pdf.set_font("Times", size=16) blue = DeviceRGB(r=0, g=0, b=1) grey = 128 - headings_style = FontStyle(emphasis="ITALICS", color=blue, fill_color=grey) + headings_style = FontFace(emphasis="ITALICS", color=blue, fill_color=grey) with pdf.table(headings_style=headings_style) as table: for data_row in TABLE_DATA: row = table.row() @@ -289,3 +289,21 @@ def test_table_align(tmp_path): for datum in data_row: row.cell(datum) assert_pdf_equal(pdf, HERE / "table_align.pdf", tmp_path) + + +def test_table_capture_font_settings(tmp_path): + pdf = FPDF() + pdf.add_page() + pdf.set_font("Times", size=16) + lightblue = (173, 216, 230) + with pdf.table() as table: + for data_row in TABLE_DATA: + with pdf.local_context(text_color=lightblue): + row = table.row() + for i, datum in enumerate(data_row): + if i == 0: + pdf.font_style = "I" + else: + pdf.font_style = "" + row.cell(datum) + assert_pdf_equal(pdf, HERE / "table_capture_font_settings.pdf", tmp_path) diff --git a/tutorial/tuto5.py b/tutorial/tuto5.py index ceac95c56..7aba4387a 100644 --- a/tutorial/tuto5.py +++ b/tutorial/tuto5.py @@ -1,6 +1,6 @@ import csv from fpdf import FPDF -from fpdf.fonts import FontStyle +from fpdf.fonts import FontFace with open("countries.txt", encoding="utf8") as csv_file: @@ -21,7 +21,7 @@ pdf.add_page() pdf.set_draw_color(255, 0, 0) pdf.set_line_width(0.3) -headings_style = FontStyle(emphasis="BOLD", color=255, fill_color=(255, 100, 0)) +headings_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0)) with pdf.table( borders_layout="NO_HORIZONTAL_LINES", cell_fill_color=(224, 235, 255),