rougier / freetype-gl

OpenGL text using one vertex buffer, one texture and FreeType
Other
1.65k stars 266 forks source link

Do the text bounds for different font sizes scale linearly? #177

Open silverness2 opened 6 years ago

silverness2 commented 6 years ago

1 - Say I render multi-line text with the same exact font and font size for each glyph and then compute the text bounds of the text via text_buffer_get_bounds() to get BoundsA. 2 - Now, I want make the text smaller or bigger, so I scale the font size of each glyph by %x. 3 - I re-compute the text bounds of the newly scaled text via text_buffer_get_bounds() to get BoundsB. 4 - Is it true that, if nothing else has changed except for the font size of each glyph, BoundsB = BoundsA * %x? Or, is computing the text bounds not a simple linear relationship like this?

rougier commented 6 years ago

It depends if you use hinting or not. Without hinting, it'll scale linearly, with hinting, it is not the case.

wmamrak commented 6 years ago

From what I recall, not only hinting must be disabled, but you also need to use linear advance values, and you must not use FT_KERNING_DEFAULT if you apply kerning. That being said, most TrueType fonts require scaling values (x_scale [1]) to be based on integer ppem values (x_ppem [1]). If this is the case, any non-integer glyph sizes will cause rounding of scaling values, thus making the relationship non linear. Of course you should position your glyphs with subpixel accuracy as well.

[1] https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Size_Metrics

silverness2 commented 6 years ago

Thanks for your responses. In my tests, I was indeed finding that the bounds did not scale linearly, including if I turned hinting and kerning off. Can you expand on what you mean when you say one needs "to use linear advance values"?

What I am finding is the bounds scales pretty closely in the horizontal direction (bounds.width), but not in the vertical direction...in my tests using a baseline of font size 24 and scaling up or down, I can get anywhere from a 5 - 30 pixel difference in the height.

I forgot to mention this in the original post, but the text where I see the greatest difference in estimated height after scaling vs. actual height is with multi-line text whose height is greater than the width.

wmamrak commented 6 years ago

I don't know how this is implemented in freetype-gl, nor whether it is customizable in any way. It is a bit complicated because of presence of freetype glyph loading flags, but in general it boils down how freetype-gl obtains the advance values for each glyph. Is it FT_Glyph_Metrics::horiAdvance, which can be rounded by ft, or FT_GlyphSlotRec::linearHoriAdvance? The latter is required for precise layouting. Nick should know the answer :-)

For non-rotated fonts vertical advance is always zero, hence the reason for larger differences must be in inprecise ascender/descender values. Again, I didn't check how ft-gl computes this. See the note in [1] for precision improvement.

[1] https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Size_Metrics

rougier commented 6 years ago

Do you have a minimal example (+screenshot) showing the problem ?

silverness2 commented 6 years ago

Attached is a minimal example, directly adapted from the markup.c program. testFontScaling.cc.txt

In this example, there are 3 cases whose values are defined at the top of the code example. Each case uses the same text and rounds bounds values up (actual computed floating point values are written in the code in comments).

CASE 1: Set the font size of all text to 24.0. Run program. Get the bounds computed by Freetype-GL. Set the window width and height to the bounds, and the pen y origin to bounds.height. Run program again: actual-fontsize-24

CASE 2: Set the font size of all text to 96.0. Run program. Get the bounds computed by Freetype-GL. Set the window width and height to the bounds, and the pen y origin to bounds.height. Run program again: actual-fontsize-96-resized

CASE 3: Scale the font size and the computed bounds from CASE 1 by 4. Set the font size of all text to (24.0 4.0) = 96.0, the width to (71.22 4.0) = 284.88->285, and the height to (256.80 * 4.0) = 1027.88->1028. Set the pen y origin to the estimated height, 1028. Run program again: estimated-fontsize-96-resized

As you can see in the third image, the bounds estimated after linear scaling are incorrect. In this case, there is a ~27 pixel discrepancy.

rougier commented 6 years ago

Hum. Can know test if the vertical extent scales linearly if you set font size from 12 to 96 (on a single letter and on text "Tg")?

rougier commented 6 years ago

The advance computation is at: https://github.com/rougier/freetype-gl/blob/master/texture-font.c#L631. Maybe a FT_LOAD_NO_AUTOHINT flag is missing.

wmamrak commented 6 years ago

You mean 17px :-) I don't have font config installed hence can't test it by myself. Replace texture-font.c with texture-font.c.txt and report the result.

silverness2 commented 6 years ago

@rougier

Letter "T" CASE 1: Font size = 12, Actual Bounds = (8 x 14) letter-t-actual-fontsize-12 CASE 2: Font size = 96, Actual Bounds = (61 x 106) letter-t-actual-fontsize-96

CASE 3: Font size = 96, Scaling Factor = (96 / 12) = 8, Estimated Bounds = 8*(8 x 14) = (64 x 112) letter-t-estimated-fontsize-96

Letters "Tg" CASE 1: Font size = 12, Actual Bounds = (15 x 14) letters-tg-actual-fontsize-12

CASE 2: Font size = 96, Actual Bounds = (114 x 106) letters-tg-actual-fontsize-96

CASE 3: Font size = 96, Scaling Factor = (96 / 12) = 8, Estimated Bounds = 8*(15 x 14) = (120 x 112) letters-tg-estimated-fontsize-96

silverness2 commented 6 years ago

@wmamrak

I recompiled the library with the new texture-font.c source you provided. Unfortunately, the text is still getting cut off as before:

estimated-fontsize-96-newtexturefontsrc-resized

rougier commented 6 years ago

I think the text buffer bounding box computation is wrong because we need to round up the descender to the nearest integer to align the glyph to the pixel grid. More specifically, I think this line is responsible. Could you try to remove the int and check if it works?

silverness2 commented 6 years ago

@rougier Removal of the (int) appears to work for the bounds in my test case!!

CASE 1: For font size = 24, actual computed bounds is: (71.22 x 264.0) CASE 2: For font size = 96, actual computed bounds is: (284.83 x 1056.0) CASE 3: For font size = 96, estimated bounds using a scale factor of (96 / 24) = 4 is: 4*(72.22 x 264.0) = (284.88 x 1056.0)

newtextbuffsrc-estimated-fontsize-96-resized

rougier commented 6 years ago

Good ! Now I'm not sure how to handle it at the level of the library. Without the int, display might be wrong because it is not aligned vertically with the pixel grid. This would require at least to rewrite all the demos.