-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Perform font fallback #6926
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
base: main
Are you sure you want to change the base?
Perform font fallback #6926
Conversation
| switch (anchor[1]) { | ||
| case 'a': // ascender | ||
| y_anchor = PIXEL(self->face->size->metrics.ascender); | ||
| y_anchor = PIXEL(family->faces[0]->size->metrics.ascender); | ||
| break; | ||
| case 't': // top | ||
| y_anchor = y_max; | ||
| break; | ||
| case 'm': // middle (ascender + descender) / 2 | ||
| y_anchor = PIXEL( | ||
| (self->face->size->metrics.ascender + | ||
| self->face->size->metrics.descender) / | ||
| (family->faces[0]->size->metrics.ascender + | ||
| family->faces[0]->size->metrics.descender) / | ||
| 2); | ||
| break; | ||
| case 's': // horizontal baseline |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here you would need to get the ascenders and descenders of every font used on this line, not just the first one, and use the greatest value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps. I don't think looking just at the fonts used in a given line would be good - that could create an inconsistent layout for the text_multiline functions.
This should use either the first font's metrics or the greatest value across all fonts in a family object, and it should be consistent with family.getmetrics() (not yet implemented).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just tested what MS Edge (i.e. Chromium) does - it uses the metrics of all fonts up to the last one that is in use for a given line, even if it causes uneven line spacing.
For example, with three fonts, a paragraph that only uses the second font will have line spacing computed from the first two fonts. If one word in the paragraph then uses the third font, that line will include the third font's line spacing, but the other lines are unaffected.
This is incompatible with how Pillow currently calculates line spacing for multiline text (see #6469 (comment)), so I think I'll just use the maximum over all fonts (i.e. assume that if a user creates a FontFamily object they actually want to use all of the fonts), and leave fixing it for #1646 (+document the limitation).
| found = 1; | ||
| } | ||
| } | ||
| /* prefer first font's missing glyph if no font support this codepoint */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /* prefer first font's missing glyph if no font support this codepoint */ | |
| /* prefer first font's missing glyph if no font supports this codepoint */ |
|
|
||
| static PyObject * | ||
| getfamily(PyObject *self_, PyObject *args, PyObject *kw) { | ||
| /* create a font family object from a list of file names and a sizes (in pixels) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /* create a font family object from a list of file names and a sizes (in pixels) */ | |
| /* create a font family object from a list of file names and sizes (in pixels) */ |
| (*glyph_info)[i].x_offset = 0; | ||
| (*glyph_info)[i].y_offset = 0; | ||
|
|
||
| /* This has been broken and had no effect for many years now... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you expand on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
How to download the module or the build with below module? AttributeError: module 'PIL.ImageFont' has no attribute 'FreeTypeFontFamily' |
|
You can use a source install from the branch https://github.com/nulano/Pillow/archive/refs/heads/font-fallback.zip using the installation instructions: https://pillow.readthedocs.io/en/stable/installation.html#building-from-source The previous Windows dev build has expired so I've re-run the CI for this branch here: https://github.com/nulano/Pillow/actions/runs/5568846705 |
|
Thank you. |
|
If I want to use below method to draw text for each char. is it slower than fontfamily? Iterate all chacters in a string, |
@Mitchell-kw-Lee Looks like this is still a WIP, are you able to test the branch and report results? That may help … |
|
This needs a pretty large rebase before it can cleanly merge with the main branch. Currently, I see #6926 (comment) as the biggest unresolved issue with this PR. I think I have an idea that could work, but I've not had time to work on this. However, if you are able to test this PR as is (even though it is made for an older version of Pillow) and report whether it works / causes issues, it could perhaps be helpful. |
|
As I wrote above, this branch has a non-trivial merge conflict I haven't had time to resolve, even though I would like to get to it at some point. If you'd like (keep in mind this is based on Pillow from a year ago), I can re-run the CI to generate new Windows wheels for easier installation. If you are on Linux/macOS, you'll need to figure out source installation (hint: https://pillow.readthedocs.io/en/stable/installation.html#building-from-source) |
|
I've rerun the CI: https://github.com/nulano/Pillow/actions/runs/7765749734 Example usage is at the top of this page (click on |
|
FreeTypeFontFamily doesn't have |
This function does not call |
|
Ah right, looks like multiline text is not working for this branch. If you want to use multiline text, you'll have to wait for someone to rebase this branch to main (the current |
@Mitchell-kw-Lee You can follow related issues and provide comments, questions and feedback just as you did here to find the answers to those questions. Pillow is released quarterly, and your fix may or may not be in the next release, based on the answers to those questions. I suspect we already know most of the involved parties from the discussion in this thread, and I don't think we can give any meaningful answer in this case to the the question of how long you'll have to wait for a new feature, developed in large part by volunteers, or underpaid developers. Sometimes we can answer! But I don't see anything definitive here yet … more than @nulano has already provided at least. |
|
fontfamily (font1 (include some chars)+font2(include most chars)) cannot show some characters font1: see attachment. font2: Arial Unicode MS.ttf or any other fonts |
|
Your code is incomplete. What is |
No, except multiline text seems to work now.
Go to https://github.com/nulano/Pillow/actions/runs/8583137967 and scroll down: |
Saw it. Thank you. There is still below problem from yesterday. font_file0: ballpen.zip. font_file1: tianshiyanti2.0.ttf.zip str = 'をも資资儲储議议歷历權权個个TextInAaBbCcDdEeFfGgHhIi family PILLOW' image = Image.new('RGB', (image_width=1000, image_height=25), color='white')
draw = ImageDraw.Draw(image)
font0 = ImageFont.truetype(font_file0, size=20)
font1 = ImageFont.truetype(font_file1, size=20)
font_family = ImageFont.FreeTypeFontFamily(font0, font1)
draw.text((0, 0), textstr, fill='black', font=font_family) |
Same as for regular text: draw.text((0, 0), "line1\nline2", font=family, ...)
I'll take a look at it when I have the time. |
I wonder whether there is any function to import html to pillow canvas, like rendering a web page. |
This is off-topic for this discussion of multiple fonts, but no, Pillow doesn't have any functionality for rendering HTML to an image. |
|
Would it be possible to get a |
Sure, I didn't bother for a WIP PR when I wasn't sure it would be useful, but it's quite simple (literally just copy-pasted from You should be able to download wheels from this workflow in a few hours: https://github.com/nulano/Pillow/actions/runs/8774721560 |
Hi, the builds are expired. Could you please re run? I'm fairly new to this, so I don't know if there's another way to get the build |
…-missing glyph in a single cluster
… instead of last font's missing glyph
for more information, see https://pre-commit.ci
|
I've rebased the PR and triggered a build, you should see the wheels here in a few hours: https://github.com/nulano/Pillow/actions/runs/10547427520 |
|
This would be amazing to see completion, I have a project now where I could really use this! |
|
Hi there, I’m curious if there’s any update on this PR, thanks! |




Fixes #4808.
Add a new type,
ImageFont.FreeTypeFontFamily(font1, font2, ..., layout_engine=layout_engine), that can be used withImageDraw.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:
ImageFont.truetype(...), perhapsImageFont.truetype_family(...)?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:
Combining Latin, symbols, and an emoji:
Combining Arabic, Greek, Latin, and a symbol:
Combining characters are treated as part of a single cluster (with Raqm layout):
Raqm layout:

Basic layout:

The string
scontains: