juce::Font::getStringWidthFloat() has one extra kerning

Hi! first time posting to the JUCE forum.

juce::Font::getStringWidthFloat() calculates Kerning Size as follows.

w += font->getKerning() * (float) text.length();

this has one extra kerning.
It would be great if you could fix as below.

w += font->getKerning() * (float) (text.length() - 1);

4 Likes

This is a great find, thank you for sharing!

1 Like

OOI is this consistent with other tools like Figma?

Something tells me it may be intentional to have the extra space on the end, but I don’t know enough about typefaces to be sure.

3 Likes

I’m using Adobe XD. extra space on the end is not included in bounds.


Then, I tested as below.
        auto xdFontSize = 20.0f;            // unit is pt
        auto xdTracking = 300.0f / 1000.0f; // unit is em * 1000
        
        auto font = juce::Font();
        font.setTypefaceName("Arial");
        font.setTypefaceStyle("Bold");
        font = font.withPointHeight( xdFontSize );
        font.setExtraKerningFactor( xdTracking );

        DBG("f: " << font.getStringWidthFloat("TEST"));
        DBG("i: " << font.getStringWidth("TEST"));
        
//         [ xd ]
//         69
//
//         [ text.length() ]
//         f: 77.9258
//         i: 78
//
//         [ text.length() - 1 ]
//         f: 71.2227
//         i: 72

It doesn’t match exactly, but I think “text.length() - 1” is nearer than “text.length()”.

5 Likes

Yes!

Definitely a bug with with getStringWidth*, should be a nice small UI win for the JUCE team.

I’m grateful for @migizo for pointing it out. I do a lot of horizontal centering (buttons, etc) of fonts with letter-spacing and things often feel not quite right…


Not to muddy the waters, but I believe another issue here is JUCE conflates kerning with letter spacing.

Kerning is spacing between letters. It’s something that type designers meticulously take care of. The amount of space differs for every character combo.

Tracking or letter spacing (see: css and every design tool) adds/removes a constant amount of spacing between glyphs— while keeping the carefully designed kerning intact. This is so designers/implementers (like us!) can tighten up a font or make it feel more spacious without ruining the font’s internal knowledge of how characters relate to each other. The kerning is not touched or altered.

I’m not 100% on this, but it looks like JUCE’s withExtraKerningFactor does something different, it’s a multiplier that overrides the kerning of the font’s glyphs at a low level, for example via kCTKernAttributeName on macOS. As far as I can tell this replaces the kerning between glyphs with a constant amount of space — getting rid of the meticulous work done by the type designer. I haven’t “proven” this with a demo, but this would explain the discrepancy @migizo is seeing in their test as well as some issues I’ve noticed when going adjusting (what I assumed was) letter-spacing.

Edit: I should have tested this before jumping to conclusions, sorry about that, see below.

5 Likes

This seems like a necessary fix, but I bet it causes problems with many pre-existing laid-out GUIs. :thinking:

Better to embrace breaking changes to align JUCE with other tools and well-defined standards. Any projects relying on bugs should expect issues when those bugs are fixed!

8 Likes

Or finally perfectly center-aligns a bunch of people’s buttons that they didn’t manage to catch themselves! :laughing:

9 Likes

JUCE’s centering algorithm does not use that function, though, so the centering issue demonstrated here is still present.

But @migizo pointed in the right direction (thanks!). The following modification to juce_GlyphArrangment.cpp (line 482) could be a fix for the issue:

if (justification.testFlags (Justification::horizontallyCentred))  
{
    auto font = glyphs.getLast().font;
    auto kerningOfLastGlyph = font.getExtraKerningFactor() * font.getHeight() * font.getHorizontalScale();
    moveRangeOfGlyphs (startIndex, num, deltaX + kerningOfLastGlyph * 0.5f, deltaY);
}
else
{
    moveRangeOfGlyphs (startIndex, num, deltaX, deltaY);
}
3 Likes

For Justification::right a change hast to be made as well and kerningOfLastGlyph added to deltaX.

1 Like

But wouldn’t this be very obvious? Isn’t it something you would immediately see in the font demo, as soon as you moved the kerning slider?

Also, the proposed change to Font::getStringWidthFloat() messes up the spacing in the textEditor when a kerning factor is applied. Screenshot from the demoRunner:

1 Like

Sorry about that, I jumped to conclusions based on the code and apple docs without first verifying behavior. The Apple docs seemed to imply that it was replacing/specifying the kerning. The docs for the sibling kCTTrackingAttributeName are clearer that it’s additive, and more modern (also named correctly). This is interesting too:

it treats tracking as trailing whitespace and a nonzero amount disables nonessential ligatures, unless overridden by the presence of kCTLigatureAttributeName.

Testing things out in the font demo (good call!) it looks that withExtraKerningFactor behaves identically to Figma’s letter-spacing. It’s correctly adding a constant factor (%) of the font’s height in points.

tail between legs (though perhaps the JUCE function should still be renamed for clarity)

1 Like

I find it kind of odd that in the screenshot it looks like:

L    o    r    e    m i    p    s    u    m d    o    l    o    r s    i    t a    m    e    t

i.e. the spaces are not having their width adjusted by the extra kerning.

Is this expected?

No, not expected, that’s a bug introduced by changing juce::Font::getStringWidthFloat() as described in the first post.

1 Like

It looks like getStringWidthFloat is being used internally in a manner which expects the trailing tracking space.

When laying out text (in buttons, etc) the trailing tracking space is undesirable (impedes horizontal centering).

Exactly. It’s very good to know that the function includes a trailing tracking space, if one wants to use it directly! But when centering text JUCE does not seem to use that function.

The change I posted above to juce_GlyphArrangment.cpp apparently solves the centering issue by itself, but I have no idea if it breaks something somewhere else.

1 Like

I think this isn’t fixed yet? Just ran into the same problem. No centering because of last space

1 Like

+1