-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Font spacing changes #3977
Comments
Could you put together a simple script demonstrating the problem? |
@radarhere I've updated the first post with a simple example. |
What do you think of working from the end instead, subtracting the width of 'adise' from the width of 'paradise'? from PIL import ImageDraw, Image, ImageFont
img = Image.new('RGBA', (200, 60), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('arial.ttf', size=48)
first_font_color = (255, 0, 0)
second_font_color = (255, 255, 255)
draw.text((0, 0), 'paradise', first_font_color, font=font)
draw.text((0, 0), 'par', second_font_color, font=font)
par_width = draw.textsize('paradise', font=font)[0] - draw.textsize('adise', font=font)[0]
draw.text((0 + par_width, 0), 'adise', second_font_color, font=font)
img.save('example.png') As to why this works better, I think it's because not all gaps between letters are the same. If you look at the pixels, there is zero gap between 'r' and the following 'a', but there are 3 pixels between the second 'a' and the following 'd'. On an abstract level, I think this also better describes your goal - you're not writing 'adise' after 'par' - you're writing 'aside' at the end of 'paradise'. |
@radarhere Thanks for the suggestion, I'll give that a try. I didn't think to try that because I didn't know it would make a difference. I'll close the ticket for now and reopen it if it doesn't work for some reason (although I can see that for this example it does work). |
That proposed fix does not seem to work in every case. As an example, consider the string "Ayo", with the "A" and "yo" being drawn separately: from PIL import ImageDraw, Image, ImageFont
img = Image.new('RGBA', (200, 60), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('arial.ttf', size=48)
first_font_color = (255, 0, 0)
second_font_color = (255, 255, 255)
draw.text((0, 0), 'Ayo', first_font_color, font=font)
draw.text((0, 0), 'A', second_font_color, font=font)
par_width = draw.textsize('Ayo', font=font)[0] - draw.textsize('yo', font=font)[0]
draw.text((0 + par_width, 0), 'yo', second_font_color, font=font)
img.save('example.png') |
I'm not able to replicate this on my macOS machine. Does this happen for you on Ubuntu? While unlikely, I don't suppose that the problem is just that some of the pixels being drawn are translucent? |
@radarhere I just confirmed it's happening on my Digital Ocean (Ubuntu) machine. The issue seems to be the kerning between the letters 'A' and 'y'. I tried a fix where I would manually subtract from the preceding_text_width if the preceding_text_width + following_text_width was larger than the full_text_width (as recommended here), but that also does not work consistently, as different letter pairs seem to have different amounts of kerning even after making that adjustment. |
You may or may not be interested in this - as a workaround for your situation, instead of drawing 'Ayo' in red and then 'A' and 'yo' in white and finding they don't quite match, you could instead draw 'A' and 'yo' in red and then 'A' and 'yo' in white, always breaking the writing up into the final segments. |
@radarhere That workaround had occurred to me, and I'll keep it in mind, but I'm going to see if I can make it work with the kerning. I looked around and I think I found a way to extract the kerning values from the font's TTF file using the 'fontforge' library, and I'm going to see if I can use that to reliably adjust the distance between the two characters. |
Despite not being able to replicate, how's this for another idea? If the problem is that the 'y' in 'Ayo' is moved right by the presence of the 'A', then what if we always wrote 'Ayo', and cropped and pasted the result to get the effect? from PIL import ImageDraw, Image, ImageFont
img = Image.new('RGBA', (200, 220), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('Arial.ttf', size=48)
first_font_color = (255, 0, 0)
second_font_color = (255, 255, 255)
for i, parts in enumerate([
['Ayo',''],
['A','yo'],
['Ay','o'],
['','Ayo']
]):
first, second = parts
x = 0
y = i*50
if first and second:
draw.text((x, y), 'Ayo', first_font_color, font=font)
im2 = Image.new('RGBA', (200, 220))
draw2 = ImageDraw.Draw(im2)
draw2.text((0, 0), 'Ayo', second_font_color, font=font)
w = draw2.textsize('Ayo', font=font)[0] - draw2.textsize(second, font=font)[0]
cropped_im = im2.crop((w, 0, 220, 220))
img.paste(cropped_im, (x+w, y), cropped_im)
elif first:
draw.text((x, y), 'Ayo', first_font_color, font=font)
else:
draw.text((x, y), 'Ayo', second_font_color, font=font)
img.save('example.png') |
Thank you for the suggestion! One issue with this method is that sometimes one of the letters at the border will have a mix of the two colors (see the example below, I suspect the issue will be more dramatic in, for example, script-style fonts, which I'd like to be able to support): I've taken a short break from this problem but my current idea is still to try to use pangocairocffi to solve this, see the linked cairocffi thread to see the problem I'm currently dealing with with that library (not that it concerns you; just if you're interested). |
Ok. If you're looking into other libraries, can this issue be closed for Pillow, or are you still interested in seeing a solution here? |
@radarhere Yeah it's fine to close it, I'll reopen it if the other library doesn't work. But it would be nice if Pillow had this functionality. |
The problem, at least for me, is that I can't replicate this - for my machine, this can be done with Pillow. If you've moved on from using Pillow for this, then maybe you aren't interested in hearing my hit-and-miss ideas on how to address this - I don't currently have another way to figure out if this is solved apart from talking here. If you haven't moved on, then you can re-open this issue. |
@radarhere I would prefer to use Pillow since it would involve changing less of my code, but after learning more about kerning it seems to me that this problem/use-case is one that would require new code/features added to Pillow to get it working. When you say you can't replicate it, does that include the "VA" example I gave above? |
When I run my code with 'VA', there is a problem, but the pixels do not exactly match yours. However, that seems irrelevant, since that approach requires separation between the letters, and it now sounds like you want to use slanted text. |
Just to be clear: I don't want to solely use slanted text, but I do want to be able to support slanted text. I want to be able to support all of the Google free fonts. |
I'm pretty sure the cause of this issue is that sometimes glyphs extend outside their advance width. I'll use the terms from the following diagram to explain this: The origin refers to the position at which a glyph is supposed to be rendered. For the first glyph, this refers to the xy parameter of the
Kerning could also affect this spacing, but the current implementation looks buggy to me. Specifically, kerning is being scaled twice, once in the basic layout function and a second time in the getsize and render functions. The effect is that the delta is scaled to 1/64 of the intended offset. The PIXEL macro call on the following lines looks unnecessary and wrong (added in #2576 with #2284): Lines 568 to 569 in 394f7a0
Note that there are further complications if you use Raqm layout instead of basic layout, see #4483 (comment). |
@radarhere Thank you for letting me know about |
I've figured out the last piece, creating PR #6722 to resolve this. Using |
What did you do?
What did you expect to happen?
I expected that the current syllable would change color without any other visible changes.
What actually happened?
The text spacing always doesn't seem to work totally right, so the lyrics sometimes get spaced out.
You can see it:
What are your OS, Python and Pillow versions?
Example code:
There's first a red 'paradise' created, then a white 'paradise' created in the same spot. If you zoom in you'll notice that the red one shows up underneath the white one starting with the 'a' and then that continues for the 'dise':
The text was updated successfully, but these errors were encountered: