ghiewa/freetype-go

Text extents

GoogleCodeExporter opened this issue · 16 comments

With freetype-go, is there a way to compute the text extents (width and height) 
of a string given a particular font and size *before* rendering the text?

I've looked around in the source code, and it *appears* that the right 
information is there, but I can't quite seem to figure out how to do it.

As of right now, if I want an image that "snugly" fits some string, I do the 
following:

Over estimate the extents by multiplying the pixel size of one em unit by the 
length of the string. I get the width/height this way (assuming one line of 
text).

I allocate an image with a rectangle of two points: (0, 0) and (width, height).

After rendering the text to the image using DrawText, I use the point returned 
from DrawText to take a sub-image of the initial image. (I convert this point 
to a (x, y) position by simply dividing the X and Y of the point by 256. It 
seems to work OK, but I have no idea if this is correct.)

--------------

And one last question: would the extents include the entire bounding box of the 
string? The (x,y) position returned by DrawText doesn't seem to cover the parts 
of the text the dip below the text's base line. Like the tail in the 'y'. My 
solution has just been to pad the 'y' with a few pixels, but I know this is not 
optimal since it will break if the font size changes too much.

(My apologies if my terminology is off. I don't have much experience in font 
rendering.)

I would happily submit a patch if I was nudged in the right direction :-)

Thanks!

Original issue reported on code.google.com by jamslam@gmail.com on 2 Jun 2012 at 10:44

It looks to me like DrawString in freetype.go needs to be factored out into 
drawOrMeasureString(string, point, measure bool), which checks if measure == 
true and skips the drawing. Then there would be two public entry points, 
DrawString and MeasureString (same args, same return).

Tell me if you want me to submit a patch doing this.

Original comment by jeff.al...@gmail.com on 7 Jun 2012 at 12:34

  • Added labels: ****
  • Removed labels: ****
A patch that implements what I mentioned is attached. See if it solves your 
problem.

Original comment by jeff.al...@gmail.com on 7 Jun 2012 at 1:38

  • Added labels: ****
  • Removed labels: ****

Attachments:

Oh, right, I got distracted and forgot about height. I'll take another try at 
it.

Original comment by jeff.al...@gmail.com on 7 Jun 2012 at 2:48

  • Added labels: ****
  • Removed labels: ****
Ah nice. I haven't tested it quite yet, but I understand what you did. Thanks!

So, the only caveat that remains is: how does one make sure the 'y' returned 
from MeasureString covers the bounding box of the text? (As of right now, the 
'y' seems to correspond to the base line of the text, so that if it's used as 
the Point.Max.Y for a bounding box, the box will cut off all letters that sink 
below that base line. Like the tail in 'y' or 'j'.)

I'm sure there's a technical word for the "base line of the text", but I'm not 
sure what it is. Am I making any sense?

(It is very possible that it isn't the job of freetype-go to do this 
computation, and that it's up to me to do it. I'm just not sure how.)

Once again, thanks! Your patch is appreciated.

Maintainers: is there any chance of getting this patch applied upstream?

Original comment by jamslam@gmail.com on 7 Jun 2012 at 2:19

  • Added labels: ****
  • Removed labels: ****
Well thinking some more, I see that I totally screwed this up, not even the 
signature is right. The correct signature would probably be 
MeasureString(string) (canvas image.Point, start raster.Point), so that a 
caller like you can use it to decide how big of a canvas to allocate and where 
to draw on it to ensure that all pixels are shown.

example/freetype/main.go shows an overly simplistic way of calculating the 
location of the next line of text, which is pt.Y += c.PointToFix32(*size * 
*spacing). For your purposes (allocating the minimal canvas) you don't want to 
fudge it with a spacing factor, but actually know the farthest down from your 
origin point that pixels will be drawn. That dimension is known to DrawString 
down inside of it's per rune loop, so calculating it in MeasureString should be 
possible as well.

Original comment by jeff.al...@gmail.com on 7 Jun 2012 at 3:30

  • Added labels: ****
  • Removed labels: ****
Don't hold your breath for an update to the patch; I'm not familiar enough with 
this code to make it work in the time I have available. Sorry.

Original comment by jeff.al...@gmail.com on 7 Jun 2012 at 4:24

  • Added labels: ****
  • Removed labels: ****
Actually, I don't think you screwed it up. raster.Point seems to be what 
freetype-go wants to return, and that it's up to the caller to convert them. 
(I've been doing that.) Maybe that should be changed, but I'll leave that up to 
the maintainer's discretion.

As for discovering point.Max.Y, perhaps I can use 'glyphRect' to compute it? 
i.e., in the per rune loop:

p.Y = max(p.Y, orig.Y + c.FUnitToFix32(glyphRect.Dy()))

Where 'orig' is a copy of the original point passed to DrawString.

Finally, a question for the maintainer: *should* this calculation of Y be the 
value returned by DrawString, or is this an entirely separate computation? As 
far as I can tell, the Y value returned is the same as the Y value put in, so 
perhaps this computation is desirable.

When I get home, I'll work on another patch that tries to incorporate these 
changes.

Original comment by jamslam@gmail.com on 7 Jun 2012 at 4:27

  • Added labels: ****
  • Removed labels: ****
Yeah, package freetype needs to expose font metrics in some way. I'd rather 
hold off on that until the hinting code lands, though, as hinting affects 
metrics. Hinting has recently pushed the scale computation out of package 
freetype and into package truetype.

Also, golang-dev mail from long, long ago notes that ideally the API would let 
you e.g. change font colors while preserving kerning. Thus, the actual API 
design might need some deep thought.

Sorry for the late response, but I had a typo in my mail setup and wasn't 
getting freetype-go bug mail.

Original comment by nigel...@golang.org on 2 Aug 2012 at 1:39

  • Changed state: Accepted
  • Added labels: ****
  • Removed labels: ****
No worries Jeff, thanks a lot for your help! You shoved me in the appropriate 
direction :-)

Original comment by jamslam@gmail.com on 7 Jun 2012 at 4:28

  • Added labels: ****
  • Removed labels: ****
As far as I know, yes. I'm still actively using my fork (jamslam-freetype-go) 
in my window manager until it gets fixed upstream.

Original comment by jamslam@gmail.com on 17 Dec 2013 at 12:31

  • Added labels: ****
  • Removed labels: ****
Okay, I've finally cobbled something together that appears to work. I've added 
a separate MeasureString method, which I hope returns the width/height of the 
bounding box containing the text provided.

I know next to nothing about font rendering and hacked this together based on 
other code in the package. It's likely I've done something wrong, but I thought 
it'd be nice to get this issue rolling. It's really awkward to render text 
without it.

I'm hoping that this, or something similar, will be added to freetype-go. For 
now, I have a clone: http://code.google.com/r/jamslam-freetype-go/

Nigel: comments are welcome!

Original comment by jamslam@gmail.com on 16 Dec 2012 at 5:47

  • Added labels: ****
  • Removed labels: ****

Attachments:

Hello All, I'm curious if this is still holding on additional code before being 
added to the package. Is the patch provided the only means of getting this sort 
of functionality added?

Original comment by prat...@condorcapital.com on 16 Dec 2013 at 4:53

  • Added labels: ****
  • Removed labels: ****
Yes, hinting isn't completely implemented yet. It's a lot closer than when 
comment #9 was written, but it's still not complete.

Original comment by nigel...@golang.org on 17 Dec 2013 at 11:42

  • Added labels: ****
  • Removed labels: ****
In fact, I'll just open a separate issue for it.

Original comment by burns.ethan@gmail.com on 16 Feb 2015 at 8:10

  • Added labels: ****
  • Removed labels: ****
The vg package from gonum/plot has code for computing these metrics.
You can find it here: https://github.com/gonum/plot/blob/master/vg/font.go.

It works OK, but it's not exactly right. For one, the scaling is a hack that I 
mostly made up. Everything else I tried didn't seem to work. I am also 
suspicious of the bounding boxes that freetype-go computes. They seem to be too 
big in the Y direction. At least, visually, they seem larger than line heights 
for the same fonts by other programs. This isn't a big problem unless you want 
to draw highlighted text with a shaded background.

Original comment by burns.ethan@gmail.com on 16 Feb 2015 at 4:39

  • Added labels: ****
  • Removed labels: ****
I think that the bounding boxes are "too tall" for use as ascent and descent 
because they are computed from the bounds in the head table. Ascent and 
descent, for horizontal fonts should come from the hhea table. Would it be 
possible to expose this information?

Original comment by burns.ethan@gmail.com on 16 Feb 2015 at 6:02

  • Added labels: ****
  • Removed labels: ****