From c6e230dd2288a35ed8fc87dfd1b1c0c4480b095c Mon Sep 17 00:00:00 2001 From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:16:01 +0200 Subject: [PATCH] Restoring ability to disable text shaping - fix #1287 --- CHANGELOG.md | 2 ++ fpdf/fpdf.py | 24 ++++++------- fpdf/graphics_state.py | 3 +- test/text_shaping/disabling_text_shaping.pdf | Bin 0 -> 5898 bytes test/text_shaping/test_text_shaping.py | 34 +++++++++++++------ 5 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 test/text_shaping/disabling_text_shaping.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf2bdf55..b5df632a5 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.8.2] - Not released yet +### Fixed +* `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) ## [2.8.1] - 2024-10-04 ### Added diff --git a/fpdf/fpdf.py b/fpdf/fpdf.py index 326c1db95..de36896a4 100644 --- a/fpdf/fpdf.py +++ b/fpdf/fpdf.py @@ -576,8 +576,6 @@ def set_display_mode(self, zoom, layout="continuous"): raise FPDFException(f"Incorrect zoom display mode: {zoom}") self.page_layout = LAYOUT_ALIASES.get(layout, layout) - # Disabling this check - importing outside toplevel to check module is present - # pylint: disable=import-outside-toplevel, unused-import def set_text_shaping( self, use_shaping_engine: bool = True, @@ -601,16 +599,18 @@ def set_text_shaping( script: a valid OpenType script tag like "arab" or "latn" language: a valid OpenType language tag like "eng" or "fra" """ - if use_shaping_engine: - try: - import uharfbuzz - except ImportError as exc: - raise FPDFException( - "The uharfbuzz package could not be imported, but is required for text shaping. Try: pip install uharfbuzz" - ) from exc - else: + if not use_shaping_engine: self.text_shaping = None return + + try: + # pylint: disable=import-outside-toplevel, unused-import + import uharfbuzz + except ImportError as exc: + raise FPDFException( + "The uharfbuzz package could not be imported, but is required for text shaping. Try: pip install uharfbuzz" + ) from exc + # # Features must be a dictionary contaning opentype features and a boolean flag # stating whether the feature should be enabled or disabled. @@ -4149,8 +4149,8 @@ def image( bytes, an io.BytesIO, or a instance of `PIL.Image.Image` x (float, fpdf.enums.Align): optional horizontal position where to put the image on the page. If not specified or equal to None, the current abscissa is used. - `Align.C` can also be passed to center the image horizontally; - and `Align.R` to place it along the right page margin + `fpdf.enums.Align.C` can also be passed to center the image horizontally; + and `fpdf.enums.Align.R` to place it along the right page margin y (float): optional vertical position where to put the image on the page. If not specified or equal to None, the current ordinate is used. After the call, the current ordinate is moved to the bottom of the image diff --git a/fpdf/graphics_state.py b/fpdf/graphics_state.py index 6a08bcd86..43dce75d0 100644 --- a/fpdf/graphics_state.py +++ b/fpdf/graphics_state.py @@ -341,8 +341,7 @@ def text_shaping(self): @text_shaping.setter def text_shaping(self, v): - if v: - self.__statestack[-1]["text_shaping"] = v + self.__statestack[-1]["text_shaping"] = v def font_face(self): """ diff --git a/test/text_shaping/disabling_text_shaping.pdf b/test/text_shaping/disabling_text_shaping.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dac193f5013b46dbc4d7900c5436835cb91a1c3e GIT binary patch literal 5898 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 b|Gq-tVJ?r#;R!s1$APP>meyJ)E5!c*b`R5m literal 0 HcmV?d00001 diff --git a/test/text_shaping/test_text_shaping.py b/test/text_shaping/test_text_shaping.py index be9333521..d68f4a3b6 100644 --- a/test/text_shaping/test_text_shaping.py +++ b/test/text_shaping/test_text_shaping.py @@ -8,8 +8,7 @@ FONTS_DIR = HERE.parent / "fonts" -def test_indi_text(tmp_path): - # issue #365 +def test_indi_text(tmp_path): # issue #365 pdf = FPDF() pdf.add_page() pdf.add_font(family="Mangal", fname=HERE / "Mangal 400.ttf") @@ -55,8 +54,7 @@ def test_text_replacement(tmp_path): assert_pdf_equal(pdf, HERE / "text_replacement.pdf", tmp_path) -def test_kerning(tmp_path): - # issue #812 +def test_kerning(tmp_path): # issue #812 pdf = FPDF() pdf.add_page() pdf.add_font(family="Dumbledor3Thin", fname=HERE / "Dumbledor3Thin.ttf") @@ -70,8 +68,7 @@ def test_kerning(tmp_path): assert_pdf_equal(pdf, HERE / "kerning.pdf", tmp_path) -def test_hebrew_diacritics(tmp_path): - # issue #549 +def test_hebrew_diacritics(tmp_path): # issue #549 pdf = FPDF() pdf.add_page() pdf.add_font(family="SBL_Hbrw", fname=HERE / "SBL_Hbrw.ttf") @@ -99,8 +96,7 @@ def test_ligatures(tmp_path): assert_pdf_equal(pdf, HERE / "ligatures.pdf", tmp_path) -def test_arabic_right_to_left(tmp_path): - # issue #549 +def test_arabic_right_to_left(tmp_path): # issue #549 pdf = FPDF() pdf.add_page() pdf.add_font( @@ -171,8 +167,7 @@ def test_text_with_parentheses(tmp_path): assert_pdf_equal(pdf, HERE / "text_with_parentheses.pdf", tmp_path) -def test_text_shaping_and_offset_rendering(tmp_path): - # issue #1075 +def test_text_shaping_and_offset_rendering(tmp_path): # issue #1075 pdf = FPDF() pdf.add_font("Garuda", fname=FONTS_DIR / "Garuda.ttf") pdf.set_font("Garuda", size=16) @@ -365,3 +360,22 @@ def test_unicode_script_codes(): for index, char in enumerate(char_list): assert get_unicode_script(char) == UnicodeScript(index) get_unicode_script.cache_clear() + + +def test_disabling_text_shaping(tmp_path): # issue #1287 + pdf = FPDF() + pdf.add_font(fname=FONTS_DIR / "Garuda.ttf") + pdf.set_font("Garuda", size=24) + pdf.add_page() + assert not pdf.text_shaping + pdf.write(text="Pages {nb}") + pdf.ln() + pdf.set_text_shaping(True) + assert pdf.text_shaping + pdf.write(text="Pages {nb}") + pdf.ln() + pdf.set_text_shaping(False) + assert not pdf.text_shaping + print(f"{pdf.text_shaping=}") + pdf.write(text="Pages {nb}") + assert_pdf_equal(pdf, HERE / "disabling_text_shaping.pdf", tmp_path)