Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text shaping #820

Merged
merged 40 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
331f6a0
draft of text shaping
andersonhc Jun 14, 2023
9a8c68a
Merge branch 'PyFPDF:master' into master
andersonhc Jun 14, 2023
df321a1
Fix font fallback, missing glyphs, and descriptor
andersonhc Jun 16, 2023
9701b32
fix pylint alarms
andersonhc Jun 16, 2023
c04caee
add uharfbuzz on test/requirements.txt
andersonhc Jun 16, 2023
8118d7f
disable pylint check on a function
andersonhc Jun 16, 2023
431aeea
regenerate reference PDFs - requirements.txt chang
andersonhc Jun 16, 2023
be06b8e
implement char stretching, char spacing
andersonhc Jun 17, 2023
de3e4ed
Update test_text_shaping.py
andersonhc Jun 17, 2023
714b7c7
fix not calling render with length 0
andersonhc Jun 18, 2023
cd2533e
Merge branch 'master' of https://github.com/andersonhc/fpdf2
andersonhc Jun 18, 2023
40721a5
fix glyph with different widths
andersonhc Jun 19, 2023
a84dbbb
Merge branch 'PyFPDF:master' into master
andersonhc Jun 21, 2023
9e4d0aa
add documentation and parameters
andersonhc Jun 24, 2023
9bc083c
add test for features
andersonhc Jun 28, 2023
23eadff
format with black
andersonhc Jun 28, 2023
ae5f9c3
remove space between ligature codes to please Vera
andersonhc Jun 29, 2023
23f68d6
fix surogate pair bug
andersonhc Jun 29, 2023
eb19226
fix test with bad surrogate pair
andersonhc Jun 29, 2023
cc29efe
update docs, changelog and vera ignore codes
andersonhc Jun 29, 2023
50d1b03
Update CHANGELOG.md
andersonhc Jun 29, 2023
3cac887
Create close() method on TTFFont class
andersonhc Jul 24, 2023
a3350af
Merge branch 'master' into master
andersonhc Jul 24, 2023
93f3a2d
add __repr__
andersonhc Jul 24, 2023
99be58b
check if "meta" table exclusion is breaking tests
andersonhc Jul 24, 2023
f0f1ab8
reinstate meta exclusion
andersonhc Jul 24, 2023
b3c6802
test if new fonttools is causing the otf problem
andersonhc Jul 24, 2023
251732d
.
andersonhc Jul 24, 2023
1900a01
'rebase'
Jul 25, 2023
32b4c57
undo setup.cfg change
andersonhc Jul 25, 2023
2c02bcb
update tests for new fonttools
andersonhc Jul 25, 2023
62f6ea5
-a
Jul 26, 2023
c383483
Merge branch 'PyFPDF-master'
Jul 26, 2023
29d01fa
update test file
andersonhc Jul 26, 2023
04a1807
Merge pull request #4 from PyFPDF/master
andersonhc Aug 2, 2023
ac53867
Update docs/TextShaping.md
andersonhc Aug 2, 2023
f42ee3e
Update fpdf/fonts.py
andersonhc Aug 2, 2023
64d5481
Update fpdf/fonts.py
andersonhc Aug 2, 2023
acdc78e
implement reviewer suggestions
andersonhc Aug 2, 2023
5bb3c5e
Merge branch 'master' of https://github.com/andersonhc/fpdf2
andersonhc Aug 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
- [`FPDF.mirror()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.mirror) - New method: [documentation page](https://pyfpdf.github.io/fpdf2/Transformations.html) - Contributed by @sebastiantia
- [`FPDF.table()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.table): new optional parameters `gutter_height`, `gutter_width` and `wrapmode`. Links can also be added to cells by passing a `link` parameter to [`Row.cell()`](https://pyfpdf.github.io/fpdf2/fpdf/table.html#fpdf.table.Row.cell)
- [`FPDF.multi_cell()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell): has a new optional `center` parameter to position the cell horizontally at the center of the page
- [`FPDF.set_text_shaping()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.set_text_shaping): new method to perform text shaping using Harfbuzz - [documentation](https://pyfpdf.github.io/fpdf2/TextShaping.html).
- Added tutorial in Khmer language: [ភាសខ្មែរ](https://pyfpdf.github.io/fpdf2/Tutorial-km.html) - thanks to @kuth-chi
- Added tutorial in [日本語](https://pyfpdf.github.io/fpdf2/Tutorial-ja.html) - thanks to @alcnaka
- Better documentation & errors when facing HTML rendering limitations for `<table>` tags: <https://pyfpdf.github.io/fpdf2/HTML.html>
Expand Down
84 changes: 84 additions & 0 deletions docs/TextShaping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Text Shaping #
andersonhc marked this conversation as resolved.
Show resolved Hide resolved

## 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.
andersonhc marked this conversation as resolved.
Show resolved Hide resolved


### 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)
Binary file added docs/text-shaping-kerning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/text-shaping-ligatures.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/text-shaping-substitution.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading