[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Monospaced fonts are meant to be mono-spaced, right?



I've looked into Sam's monospaced font size problems and followed up
in the Ubuntu bug. I think we've discovered unusual behaviour in
FreeType that may or may not be a bug, and hopefully someone on the
FreeType mailing list will respond to my questions over there. I'm
posting back here now to discuss how best to actually achieve the
intended cursor placement using Pygame.

Over in the bug report, Sam says:
> My solution for the labels, which works fine, is to find the rendered length of the first character, then the length of the first two characters, then the length of the first 3 characters and so on. Storing all these values allows me to work out the correct position.

> Because there's quite a bit of repetition, this can take a little bit of CPU time. My only concern with the input box, is that it needs to recalculate all of this, everytime the user types something, and it is designed for use in games where a fraction of a millisecond can still be important to keep the game running smoothly.

In all the examples so far, you've been using
font.render("blah"...).get_size(). Do you know there's a
font.size("blah") too? That should do the calculations quickly without
actually doing the rendering. I think that should be fast enough for
your needs. (See the end of this email for a short example.)

I've done some experiments to try to understand how the font metrics
relate to the dimensions of a rendered piece of text in the general
case, not just for monospaced fonts. I thought it might be useful to
others.

Firstly, any text string rendered by a font will always have a height
of font.get_height(). It seems that this is always
1+font.get_ascent()+font.get_ascent(). It doesn't matter what text you
render, even it it's entirely whitespace, it will always be the full
height.

For the width, you need to look at the metrics of each character in
the string, as returned by font.metrics(). These are xmin, xmax, ymin,
ymax and advance, as shown in the diagram here:
http://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html#SEC38

The advance is the easiest to understand. It's the horizontal distance
the text cursor moves from one side of the character to the other.
xmin is the distance from the left cursor position to the leftmost
part of the outline. xmax is the distance from the left cursor
position to the rightmost part of the outline.

Most of the time, the width of a string of text is the sum of the
advance widths of the characters. But some characters can overhang,
which happens a lot in italic fonts. If the first or last characters
overhang, the width of the string will be longer. Here's a diagram:

https://lh3.googleusercontent.com/-hxXPX7kxCbs/T8q0jF08loI/AAAAAAAADJk/72dsWzp7ATw/s877/font_rendering.png

The black bars under the letters are their advance widths. The red
highlighted areas are where characters overhang - they have parts
wider than their advance width. (The blue highlighted areas are where
the characters are narrower than their advance width - they don't
matter much.) The width of the string of text is the sum of the
advance widths *plus* the overhang of the first and last characters.

(Actually, that's not *entirely* true. I think long strings can also
vary a few pixels due to accumulated rounding and/or kerning, but
that's a much smaller effect than the overhang.)

Suppose you have rendered the word "offer" as shown in the diagram,
and you want to draw a cursor between the two "f"s. If you calculate
the cursor position based on the width of the text on the left - "of"
- you will find that the cursor is too far to the right. It will end
up right in the middle of the second "f" because the first "f"
overhangs it so much. What you actually want to do is let the font
library calculate the width of "of", then subtract the right overhang
of the last letter, "f". The right overhang is "xmax - advance".

To get the very best positioning of a text cursor, I think you want to
calculate the width of the string on its left, then *subtract* the
overhang of the character immediately to the left, if any. Here's a
demo: http://pastebin.com/PKm8ChxV

Hope this information is useful to someone!