Pillow
Pillow copied to clipboard
Perform font fallback
Fixes #4808.
Add a new type, ImageFont.FreeTypeFontFamily(font1, font2, ..., layout_engine=layout_engine)
, that can be used with ImageDraw.text*(...)
functions performing font fallback. Font fallback is done per cluster with Raqm layout (similar to Chromium) and per codepoint with basic layout.
This PR is far from complete, several TODOs:
- [ ] Font families have only a minimal API so far, e.g. retrieving metrics or setting font variations should be supported
- [ ] Maybe add a wrapper similar to
ImageFont.truetype(...)
, perhapsImageFont.truetype_family(...)
? - [ ] Lots of tests
- [ ] Documentation
I would like to get some feedback, both on the API and the implementation, before working on the TODOs above. A dev build for Windows is available from the artifact here: https://github.com/nulano/Pillow/actions/runs/8583137967
A few examples (click to expand):
All examples use this helper block:
from PIL import Image, ImageDraw, ImageFont
im = Image.new("RGBA", (500, 200), "white")
draw = ImageDraw.Draw(im)
def line(y, string, font, name, **kwargs):
draw.text((10, y), name, fill="black", font=font, **kwargs)
draw.text((300, y), string, fill="black", font=font, **kwargs)
example()
im.show()
Combining Latin, symbols, and an emoji:
def example():
s = "smile ⊗ 😀"
times = ImageFont.truetype("times.ttf", 24)
segoe_ui_emoji = ImageFont.truetype("seguiemj.ttf", 24)
segoe_ui_symbol = ImageFont.truetype("seguisym.ttf", 24)
family = ImageFont.FreeTypeFontFamily(times, segoe_ui_emoji, segoe_ui_symbol)
line(30, s, times, "Times New Roman", anchor="ls", embedded_color=True)
line(80, s, segoe_ui_emoji, "Segoe UI Emoji", anchor="ls", embedded_color=True)
line(130, s, segoe_ui_symbol, "Segoe UI Symbol", anchor="ls", embedded_color=True)
line(180, s, family, "Font Family", anchor="ls", embedded_color=True)
Combining Arabic, Greek, Latin, and a symbol:
def example():
s = "ية↦α,abc"
scriptin = ImageFont.truetype(r"C:\Users\Nulano\AppData\Local\Microsoft\Windows\Fonts\SCRIPTIN.ttf", 24)
segoe_ui = ImageFont.truetype("segoeui.ttf", 24)
segoe_ui_symbol = ImageFont.truetype("seguisym.ttf", 24)
family = ImageFont.FreeTypeFontFamily(scriptin, segoe_ui, segoe_ui_symbol)
line(30, s, scriptin, "Scriptina", direction="ltr", anchor="ls")
line(80, s, segoe_ui, "Segoe UI", direction="ltr", anchor="ls")
line(130, s, segoe_ui_symbol, "Segoe UI Symbol", direction="ltr", anchor="ls")
line(180, s, family, "Font Family", direction="ltr", anchor="ls")
Combining characters are treated as part of a single cluster (with Raqm layout):
def example():
import unicodedata
s = " ̌,ῶ,ω̃,ώ,ώ, ́,á,č,č"
for c in s:
print(unicodedata.name(c))
le = ImageFont.Layout.RAQM # or ImageFont.Layout.BASIC
scriptin = ImageFont.truetype(r"C:\Users\Nulano\AppData\Local\Microsoft\Windows\Fonts\SCRIPTIN.ttf", 24, layout_engine=le)
dubai = ImageFont.truetype(r"DUBAI-REGULAR.TTF", 24, layout_engine=le)
gentium = ImageFont.truetype(r"C:\Users\Nulano\AppData\Local\Microsoft\Windows\Fonts\GentiumPlus-Regular.ttf", 24, layout_engine=le)
family = ImageFont.FreeTypeFontFamily(scriptin, dubai, gentium, layout_engine=le)
line(30, s, scriptin, "Scriptina", anchor="ls")
line(80, s, dubai, "Dubai", anchor="ls")
line(130, s, gentium, "GentiumPlus", anchor="ls")
line(180, s, family, "Font Family", anchor="ls")
Raqm layout:
Basic layout:
The string s
contains:
SPACE
COMBINING CARON
COMMA
GREEK SMALL LETTER OMEGA WITH PERISPOMENI
COMMA
GREEK SMALL LETTER OMEGA
COMBINING TILDE
COMMA
GREEK SMALL LETTER OMEGA WITH TONOS
COMMA
GREEK SMALL LETTER OMEGA
COMBINING ACUTE ACCENT
COMMA
SPACE
COMBINING ACUTE ACCENT
COMMA
LATIN SMALL LETTER A
COMBINING ACUTE ACCENT
COMMA
LATIN SMALL LETTER C WITH CARON
COMMA
LATIN SMALL LETTER C
COMBINING CARON