forked from reingart/pyfpdf
-
Notifications
You must be signed in to change notification settings - Fork 259
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Co-authored-by: Anderson HerzogenrathDaCosta <anderson.costa@cn.ca>
1 parent
dface79
commit b671cb6
Showing
71 changed files
with
761 additions
and
187 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Text Shaping # | ||
|
||
## 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. | ||
|
||
For texts in latin script, text shaping can improve the aesthetics by replacing characters that would colide or overlap by a single glyph specially crafted to look harmonious. | ||
|
||
![](text-shaping-ligatures.png) | ||
|
||
This process is especially important for scripts that require complex layout, such as Arabic or Indic scripts, where characters change shape depending on their context. | ||
|
||
There are three primary aspects of text shaping that contribute to the overall appearance of the text: kerning, ligatures, and glyph substitution. | ||
|
||
|
||
### Kerning ### | ||
Kerning refers to the adjustment of space between individual letter pairs in a font. This process is essential to avoid awkward gaps or overlaps that may occur due to the default spacing of the font. By manually or programmatically modifying the kerning, we can ensure an even and visually pleasing distribution of letters, which significantly improves the readability and aesthetic quality of the text. | ||
|
||
![](text-shaping-kerning.png) | ||
|
||
|
||
### Ligatures ### | ||
Ligatures are special characters that are created by combining two or more glyphs. This is frequently used to avoid collision between characters or to adhere to the typographic traditions. For instance, in English typography, the most common ligatures are "fi" and "fl", which are often fused into single characters to provide a more seamless reading experience. | ||
|
||
|
||
### Glyph Substitution ### | ||
Glyph substitution is a mechanism that replaces one glyph or a set of glyphs with one or more alternative glyphs. This is a crucial aspect of text shaping, especially for complex scripts where the representation of a character can significantly vary based on its surrounding characters. For example, in Arabic script, a letter can have different forms depending on whether it's at the beginning, middle, or end of a word. | ||
|
||
Another common use of glyph substitution is to replace a sequence of characters by a symbol that better represent the meaning of those characters on a specialized context (mathematical, programming, etc.). | ||
|
||
![](text-shaping-substitution.png) | ||
|
||
|
||
|
||
|
||
## Usage ## | ||
Text shaping is disabled by default to keep backwards compatibility, reduce resource requirements and not make uharfbuzz a hard dependency. | ||
|
||
If you want to use text shaping, the first step is installing the uharfbuzz package via pip. | ||
|
||
```python | ||
pip install uharfbuzz | ||
``` | ||
|
||
⚠️ Text shaping is *not* available for type 1 fonts. | ||
|
||
### Basic usage ### | ||
The method `set_text_shaping()` is used to control text shaping on a document. The only mandatory argument, `use_shaping_engine` can be set to `True` to enable the shaping mechaning or `False` to disable it. | ||
|
||
```python | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="ViaodaLibre", fname=HERE / "ViaodaLibre-Regular.ttf") | ||
pdf.set_font("ViaodaLibre", size=40) | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="final soft stuff") | ||
pdf.output("Example.pdf") | ||
``` | ||
|
||
### Features ### | ||
On most languages, Harfbuzz enables all features by default. If you want to enable or disable a specific feature you can pass a dictionary containing the 4 digit OpenType feature code as key and a boolean value to indicate if it should be enabled or disable. | ||
|
||
Example: | ||
```python | ||
pdf.set_text_shaping(use_shaping_engine=True, features={"kern": False, "liga": False}) | ||
``` | ||
|
||
The full list of OpenType feature codes can be found [here](https://learn.microsoft.com/en-us/typography/opentype/spec/featuretags) | ||
|
||
### Additional options ### | ||
To perform the text shaping, harfbuzz needs to know some information like the language and the direction (right-to-left, left-to-right, etc) in order to apply the correct rules. Those information can be guessed based on the text being shaped, but you can also set the information to make sure the correct rules will be applied. | ||
|
||
Examples: | ||
```python | ||
pdf.set_text_shaping(use_shaping_engine=True, direction="rtl", script="arab", language="ara") | ||
``` | ||
```python | ||
pdf.set_text_shaping(use_shaping_engine=True, direction="ltr", script="latn", language="eng") | ||
``` | ||
|
||
Direction can be `ltr` (left to right) or `rtl` (right to left). The `ttb` (top to bottom) and `btt` (bottom to top) directions are not supported by fpdf2 for now. | ||
|
||
[Valid OpenType script tags](https://learn.microsoft.com/en-us/typography/opentype/spec/scripttags) | ||
|
||
[Valid OpenType language codes](https://learn.microsoft.com/en-us/typography/opentype/spec/languagetags) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified
BIN
-128 Bytes
(100%)
test/fonts/fallback_font_with_overriden_get_fallback_font.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,4 @@ pytest-cov | |
qrcode | ||
semgrep | ||
tabula-py | ||
uharfbuzz |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
from pathlib import Path | ||
|
||
from fpdf import FPDF | ||
from test.conftest import assert_pdf_equal | ||
|
||
HERE = Path(__file__).resolve().parent | ||
FONTS_DIR = HERE.parent / "fonts" | ||
|
||
|
||
def test_indi_text(tmp_path): | ||
# issue #365 | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="Mangal", fname=HERE / "Mangal 400.ttf") | ||
pdf.set_font("Mangal", size=40) | ||
pdf.set_text_shaping(False) | ||
pdf.cell(txt="इण्टरनेट पर हिन्दी के साधन", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="इण्टरनेट पर हिन्दी के साधन", new_x="LEFT", new_y="NEXT") | ||
|
||
assert_pdf_equal(pdf, HERE / "shaping_hindi.pdf", tmp_path) | ||
|
||
|
||
def test_text_replacement(tmp_path): | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="FiraCode", fname=HERE / "FiraCode-Regular.ttf") | ||
pdf.set_font("FiraCode", size=40) | ||
pdf.set_text_shaping(False) | ||
pdf.cell(txt="http://www 3 >= 2 != 1", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="http://www 3 >= 2 != 1", new_x="LEFT", new_y="NEXT") | ||
|
||
assert_pdf_equal(pdf, HERE / "text_replacement.pdf", tmp_path) | ||
|
||
|
||
def test_kerning(tmp_path): | ||
# issue #812 | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="Dumbledor3Thin", fname=HERE / "Dumbledor3Thin.ttf") | ||
pdf.set_font("Dumbledor3Thin", size=40) | ||
pdf.set_text_shaping(False) | ||
pdf.cell(txt="Ты То Тф Та Тт Ти", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="Ты То Тф Та Тт Ти", new_x="LEFT", new_y="NEXT") | ||
|
||
assert_pdf_equal(pdf, HERE / "kerning.pdf", tmp_path) | ||
|
||
|
||
def test_hebrew_diacritics(tmp_path): | ||
# issue #549 | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="SBL_Hbrw", fname=HERE / "SBL_Hbrw.ttf") | ||
pdf.set_font("SBL_Hbrw", size=40) | ||
pdf.set_text_shaping(False) | ||
pdf.cell(txt="בּ", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="בּ", new_x="LEFT", new_y="NEXT") | ||
|
||
assert_pdf_equal(pdf, HERE / "hebrew_diacritics.pdf", tmp_path) | ||
|
||
|
||
def test_ligatures(tmp_path): | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="ViaodaLibre", fname=HERE / "ViaodaLibre-Regular.ttf") | ||
pdf.set_font("ViaodaLibre", size=40) | ||
pdf.set_text_shaping(False) | ||
pdf.cell(txt="final soft stuff", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="final soft stuff", new_x="LEFT", new_y="NEXT") | ||
|
||
assert_pdf_equal(pdf, HERE / "ligatures.pdf", tmp_path) | ||
|
||
|
||
def test_arabic_right_to_left(tmp_path): | ||
# issue #549 | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font( | ||
family="KFGQPC", fname=HERE / "KFGQPC Uthmanic Script HAFS Regular.otf" | ||
) | ||
pdf.set_font("KFGQPC", size=36) | ||
pdf.set_text_shaping(False) | ||
pdf.cell(txt="مثال على اللغة العربية. محاذاة لليمين.", new_x="LEFT", new_y="NEXT") | ||
pdf.ln(36) | ||
pdf.set_text_shaping(True) | ||
pdf.cell(txt="مثال على اللغة العربية. محاذاة لليمين.", new_x="LEFT", new_y="NEXT") | ||
|
||
assert_pdf_equal(pdf, HERE / "arabic.pdf", tmp_path) | ||
|
||
|
||
def test_multi_cell_markdown_with_shaping(tmp_path): | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font("Roboto", "", FONTS_DIR / "Roboto-Regular.ttf") | ||
pdf.add_font("Roboto", "B", FONTS_DIR / "Roboto-Bold.ttf") | ||
pdf.add_font("Roboto", "I", FONTS_DIR / "Roboto-Italic.ttf") | ||
pdf.set_font("Roboto", size=32) | ||
pdf.set_text_shaping(True) | ||
text = ( # Some text where styling occur over line breaks: | ||
# pylint: disable=implicit-str-concat | ||
"Lorem ipsum dolor, **consectetur adipiscing** elit," | ||
" eiusmod __tempor incididunt__ ut labore et dolore --magna aliqua--." | ||
) | ||
pdf.multi_cell( | ||
w=pdf.epw, txt=text, markdown=True | ||
) # This is tricky to get working well | ||
pdf.ln() | ||
pdf.multi_cell(w=pdf.epw, txt=text, markdown=True, align="L") | ||
assert_pdf_equal(pdf, HERE / "multi_cell_markdown_with_styling.pdf", tmp_path) | ||
|
||
|
||
def test_features(tmp_path): | ||
pdf = FPDF() | ||
pdf.add_page() | ||
pdf.add_font(family="ViaodaLibre", fname=HERE / "ViaodaLibre-Regular.ttf") | ||
pdf.set_font("ViaodaLibre", size=40) | ||
pdf.set_text_shaping(use_shaping_engine=True) | ||
pdf.cell(txt="final soft stuff", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(use_shaping_engine=True, features={"liga": False}) | ||
pdf.cell(txt="final soft stuff", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping(use_shaping_engine=True, features={"kern": False}) | ||
pdf.cell(txt="final soft stuff", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
pdf.set_text_shaping( | ||
use_shaping_engine=True, direction="rtl", script="Latn", language="en-us" | ||
) | ||
pdf.cell(txt="final soft stuff", new_x="LEFT", new_y="NEXT") | ||
pdf.ln() | ||
|
||
assert_pdf_equal(pdf, HERE / "features.pdf", tmp_path) |
Binary file not shown.