Repository: python-pillow/Pillow

Issue:
TrueType fonts rendered from top pixel instead of top of font
As of [this commit in Pillow 2.1.0](https://github.com/python-imaging/Pillow/commit/efd70fb295c94936abb099256411f52a46a5c483), TrueType fonts are drawn from the top pixel down, instead of from the max height of the font. This means that lines containing only half-height lowercase characters, for example, are rendered at a different height than full-height characters. Because of this, it's basically impossible to evenly space text over multiple lines.

```
from PIL import Image, ImageDraw, ImageFont

im = Image.new(mode='RGB', size=(150,100))
font = ImageFont.truetype("Arial.ttf", 16, encoding='unic')
draw = ImageDraw.Draw(im)
line_spacing = draw.textsize('A', font=font)[1] + 5
lines = ['hey you', 'you are awesome', 'this looks awkward']
y = 0
for line in lines:
    draw.text((0, y), line, font=font)
    y += line_spacing
im.save('test.png', 'PNG')
```

![test](https://f.cloud.github.com/assets/1280949/788339/779837ae-eaf9-11e2-8afb-0f60f023c0b5.png)



Discussion:
Can you send a PR to fix this issue please? I remember some confusion at the time about it… but I thought we had it figured out. Thanks

IIRC, we've already applied and reversed one patch for this or very similar behavior. I think that perhaps it's time to define exactly what behavior we want from draw.text, then test the hell out of it. 

I'd propose that there should be a consistent distance from the starting point to the baseline of the text, based on the font/size. Specifically, I'd think that it should draw based on the Cap Height, and that ascender height should potentially be over that. (see http://en.wikipedia.org/wiki/Typeface#Font_metrics) There are some fonts where there's significant difference between the ascender height and the capital height (especially in script fonts, like Zapfino). 

Alternately, we could add something in that would draw fonts based on the baseline position, which would be the ideal reference for getting rid of all of this, as it's the most stable point of any. 

I was going to raise an issue on this as I am wanting to switch pi3d to Pillow and its behaviour is different (and wrong!) It's especially noticeable for us as we draw each character individually to a texture then uv map them onto 3D surfaces.

There is an 'anchor=None' keyword argument to the text() method maybe this could be used to vary which font metric to use to anchor the x,y location. Default behaviour should be to draw the text base line ascent (value from hhea table in truetype font) distance below the x,y location.

@paddywwoof Thanks for the feedback! Hopefully a "fix" (something everyone agrees is the right fix) for this will go into 2.2.0

@aclark4life Sounds good. Any idea when 2.2.0 is scheduled for?

I've not done more than look at source on github but my vote would be for
default behaviour to replace L338 in_imagingft.c with something like

ascender = PIXEL(self->face->ascender);

Three months: https://github.com/python-imaging/Pillow/issues/milestones

I don't know if the current master branch will just become 2.2.0 or otherwise how to get a revised version to test it out. Do I have to make my own branch then submit a pull request? If someone is already set up and working on this project then they could try it for me (and put in the pull request too). What I would try is to change
_imagingft.c line 338
from
 ascender = 0;
to
 ascender = PIXEL(self->face->size->metrics.ascender);
hopefully that would allow the user to line up separate text strings arranged horizontally, which is currently impossible! Ideally I would take out the block of code that also checks the maximum size of characters in the current string. This should be done externally, in my opinion, but at least for normal fonts the max size should fall within the ascender so give a consistent base line.

Master is 2.2.0

OK, not so simple as my suggestion. Obviously more changes impact on this subsequent to efd70fb295. I will carry on looking at it when I have time but for the moment I have added a hack to (our project) pi3d code to prepend a '|' onto the start of each string then not display it (yes it's horrible!).

Also I noticed with this new code that supplying color for text to draw onto an rgba image seems to require an rgba tuple. This means that existing code that worked with PIL specifying text color like "#ffff00" or "cornsilk" will crash. Maybe ImageColor.py line 102+ should be

```
if mode == "RGB":
    return color
if mode == "RGBA":
    if len(color) == 3:
      color = (color + (255,))
    r, g, b, a = color
    return r, g, b, a
```

this lets me use my existing code


Resolving patch:
diff --git a/PIL/ImageColor.py b/PIL/ImageColor.py
--- a/PIL/ImageColor.py
+++ b/PIL/ImageColor.py
@@ -102,6 +102,8 @@ def getcolor(color, mode):
     if mode == "RGB":
         return color
     if mode == "RGBA":
+        if len(color) == 3:
+          color = (color + (255,))
         r, g, b, a = color
         return r, g, b, a
     if Image.getmodebase(mode) == "L":
diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py
--- a/PIL/ImageFont.py
+++ b/PIL/ImageFont.py
@@ -157,6 +157,9 @@ def getmetrics(self):
     def getsize(self, text):
         return self.font.getsize(text)[0]
 
+    def getoffset(self, text):
+        return self.font.getsize(text)[1]
+
     def getmask(self, text, mode=""):
         return self.getmask2(text, mode)[0]
 
@@ -183,7 +186,7 @@ def __init__(self, font, orientation=None):
         self.orientation = orientation # any 'transpose' argument, or None
 
     def getsize(self, text):
-        w, h = self.font.getsize(text)
+        w, h = self.font.getsize(text)[0]
         if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
             return h, w
         return w, h


Test patch:
diff --git a/Tests/images/multiline_text.png b/Tests/images/multiline_text.png
new file mode 100644
Binary files /dev/null and b/Tests/images/multiline_text.png differ
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -35,7 +35,7 @@ def test_font_with_filelike():
     #shared_bytes = _font_as_bytes()
     #assert_no_exception(lambda: _render(shared_bytes))
     #assert_exception(Exception, lambda: _render(shared_bytes))
-    _clean
+    _clean()
 
 def test_font_with_open_file():
     with open(font_path, 'rb') as f:
@@ -67,3 +67,21 @@ def test_render_equal():
 
     assert_image_equal(img_path, img_filelike)
     _clean()
+
+
+def test_render_multiline():
+    im = Image.new(mode='RGB', size=(300,100))
+    ttf = ImageFont.truetype(font_path, font_size)
+    draw = ImageDraw.Draw(im)
+    line_spacing = draw.textsize('A', font=ttf)[1] + 8
+    lines = ['hey you', 'you are awesome', 'this looks awkward']
+    y = 0
+    for line in lines:
+        draw.text((0, y), line, font=ttf)
+        y += line_spacing
+
+    target = 'Tests/images/multiline_text.png'
+    target_img = Image.open(target)
+
+    assert_image_equal(im, target_img)
+