Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
* `resource_access_policy` and [Security considerations](https://py-pdf.github.io/fpdf2/Security.html) documentation
* [`FPDF.optional_content()`](https://py-pdf.github.io/fpdf2/OptionalContent.html) context manager to mark content as visible on screen only or in print only, using PDF Optional Content Groups - _cf._ [issue #441](https://github.com/py-pdf/fpdf2/issues/441), based on a recipe by @digidigital
### Fixed
* visual gap in rendering subsequent text after `{nb}` page alias when text shaping is enabled - _cf._ [issue #1090](https://github.com/py-pdf/fpdf2/issues/1090)
* font state (family, style, size, current font, and the page-level "font is set" flag) no longer leaks back onto the `FPDF` instance after a `text_columns()` / `text_region()` context exits, so a subsequent `pdf.cell()` / `pdf.write()` renders at the caller's font instead of the last paragraph's - _cf._ [issue #1804](https://github.com/py-pdf/fpdf2/issues/1804)
* text rendering when the first text on a page starts with a fallback glyph - _cf._ [issue #1772](https://github.com/py-pdf/fpdf2/issues/1772)
* preserve boundary-neutral formatting during bidirectional text preprocessing - _cf._ [issue #1779](https://github.com/py-pdf/fpdf2/issues/1779)
Expand Down
1 change: 0 additions & 1 deletion docs/PageBreaks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ Simply call `.add_page()`.
The special string `{nb}` will be substituted by the total number of pages on document closure.
This special value can changed by calling [alias_nb_pages()](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.alias_nb_pages).

!!! warning "This is currently incompatible with [text shaping](./TextShaping.md).<br>_cf._ [GitHub issue #1090](https://github.com/py-pdf/fpdf2/issues/1090)"

## will_page_break ##

Expand Down
1 change: 0 additions & 1 deletion docs/TextShaping.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

_New in [:octicons-tag-24: 2.7.5](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)_

!!! warning "This is currently incompatible with [the special `{nb}` string](./PageBreaks.md) that inserts the number of pages.<br>_cf._ [GitHub issue #1090](https://github.com/py-pdf/fpdf2/issues/1090)"

## What is text shaping? ##
Text shaping is a fundamental process in typography and computer typesetting that influences the aesthetics and readability of text in various languages and scripts. It involves the transformation of Unicode text into glyphs, which are then positioned for display or print.
Expand Down
38 changes: 22 additions & 16 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4460,23 +4460,24 @@ def get_fallback_font(self, char: str, style: str = "") -> Optional[str]:

def _parse_chars(self, text: str, markdown: bool) -> Iterator[Fragment]:
"Split text into fragments"
if not markdown and not self.text_shaping and not self._fallback_font_ids:
if self.str_alias_nb_pages:
for seq, fragment_text in enumerate(
text.split(self.str_alias_nb_pages)
):
if seq > 0:
yield TotalPagesSubstitutionFragment(
self.str_alias_nb_pages,
self._get_current_graphics_state(),
self.k,
)
if fragment_text:
yield Fragment(
fragment_text, self._get_current_graphics_state(), self.k
)
return
if self.str_alias_nb_pages and self.str_alias_nb_pages in text:
for seq, fragment_text in enumerate(text.split(self.str_alias_nb_pages)):
if seq > 0:
yield TotalPagesSubstitutionFragment(
self.str_alias_nb_pages,
self._get_current_graphics_state(),
self.k,
dummy_width_string=(
str(self.page_no())
Comment thread
andersonhc marked this conversation as resolved.
Outdated
if self.text_shaping
else self.str_alias_nb_pages
),
)
if fragment_text:
yield from self._parse_chars(fragment_text, markdown)
return

if not markdown and not self.text_shaping and not self._fallback_font_ids:
yield Fragment(text, self._get_current_graphics_state(), self.k)
return
txt_frag: list[str] = []
Expand Down Expand Up @@ -4577,6 +4578,11 @@ def frag() -> Fragment:
self.str_alias_nb_pages,
gstate,
self.k,
dummy_width_string=(
str(self.page_no())
if self.text_shaping
else self.str_alias_nb_pages
),
)
text = text[len(self.str_alias_nb_pages) :]
continue
Expand Down
9 changes: 8 additions & 1 deletion fpdf/line_break.py
Comment thread
prateek-dagar marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,16 @@ class TotalPagesSubstitutionFragment(Fragment):
output is being produced.
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
def __init__(
self, *args: Any, dummy_width_string: str = "1", **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.uuid = uuid4()
self.dummy_width_string = dummy_width_string
# Use dummy_width_string for layout phase width calculation if characters are not empty (non-cloned)
# and text shaping is active.
if self.characters and self.graphics_state.text_shaping:
self.characters = list(dummy_width_string)

def get_placeholder_string(self) -> str:
"""
Expand Down
Binary file added test/alias_in_middle_with_shaping.pdf
Binary file not shown.
12 changes: 12 additions & 0 deletions test/test_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,15 @@ def test_alias_with_shaping(tmp_path):
pdf.write_html("<h1>{nb}</h1>")
pdf.multi_cell(w=pdf.epw, text="Number of pages: {nb}\nAgain:{nb}")
assert_pdf_equal(pdf, HERE / "alias_with_text_shaping.pdf", tmp_path)


def test_alias_in_middle_with_shaping(tmp_path):
pdf = fpdf.FPDF()
pdf.add_font("Quicksand", style="", fname=HERE / "fonts" / "Quicksand-Regular.otf")
pdf.add_page()
pdf.set_font("Quicksand", size=24)
pdf.set_text_shaping(True)
pdf.write(text="Pages {nb} with shaping")
pdf.ln()
pdf.write(text="Pages {nb} with {nb} shaping")
assert_pdf_equal(pdf, HERE / "alias_in_middle_with_shaping.pdf", tmp_path)