Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: font.getLineSpacing not correct for first line  (Read 4941 times)

0 Members and 1 Guest are viewing this topic.

texus

  • Sr. Member
  • ****
  • Posts: 499
    • View Profile
    • TGUI
    • Email
font.getLineSpacing not correct for first line
« on: May 10, 2015, 04:58:21 pm »
The getLineSpacing apparently can't be used to determine the height of several lines of text. Suppose I have a TextBox widget which is given the size of 5*lineSpacing so that it could fit exactly 5 lines of text. The bottom part of the last line will be outside the text box because there is too much space on top. I'm not talking about the text.getLocalBounds().top here because the offset is slightly different.

The last line of the text looks like this:

The background rectangle (of which the height is calculated with getLineSpacing) is missing 9 pixels for a text size of 100.

It is a bit hard to explain so here is the full example code (I took the font from the pong example).
#include <SFML/Graphics.hpp>
#include <iostream>

int main()
{
    const unsigned int TEXT_SIZE = 100;

    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML test");
    window.setFramerateLimit(30);

    sf::Font font;
    font.loadFromFile("sansation.ttf");

    sf::Text text;
    text.setFont(font);
    text.setCharacterSize(TEXT_SIZE);
    text.setColor(sf::Color::Black);
    text.setString(L\ng\ng\ng\ng");

    sf::FloatRect bounds = text.getLocalBounds();

    sf::RectangleShape bg(sf::Vector2f(bounds.left + bounds.width, 5 * font.getLineSpacing(TEXT_SIZE)));
    bg.setPosition(text.getPosition());
    bg.setFillColor(sf::Color::Blue);

    std::cout << "bounds top = " << bounds.top << std::endl;
    std::cout << "bounds height = " << bounds.height << std::endl;
    std::cout << "full heigh = " << bounds.top + bounds.height << std::endl << std::endl;

    std::cout << "line spacing = " << font.getLineSpacing(TEXT_SIZE) << std::endl;
    std::cout << "5*lineSpacing = " << 5*font.getLineSpacing(TEXT_SIZE) << std::endl << std::endl;

    // lineHeight is character size + part of letter 'g' that lies below the baseline
    // When using this as height for the first line of text, the total height of the background rectangle is correct
    float lineHeight = text.getCharacterSize()
                       + font.getGlyph('g', TEXT_SIZE, false).bounds.height
                       + font.getGlyph('g', TEXT_SIZE, false).bounds.top;

    std::cout << "calculated line height = " << lineHeight << std::endl;
    std::cout << "lineHeight + 4*lineSpacing = " << lineHeight + 4*(font.getLineSpacing(TEXT_SIZE)) << std::endl;

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear(sf::Color(200, 200, 200));
        window.draw(bg);
        window.draw(text);
        window.display();
    }

    return EXIT_SUCCESS;
}

The following is being printed in the terminal:
Code: [Select]
bounds top = 11
bounds height = 543
full heigh = 554

line spacing = 109
5 * lines spacing = 545

calculated line height = 118
lineHeight + 4*lineSpacing = 554

Is this a bug in SFML?
If it isn't a bug, is there any way that I can know the height of several lines of text upfront? Because I either seem to need the bounds.top or the part of the letters beneath the baseline (for which I had to hardcode the 'g' character in this example to get it).
TGUI: C++ SFML GUI

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: font.getLineSpacing not correct for first line
« Reply #1 on: May 11, 2015, 02:01:01 am »
I'm speculating here but isn't the line spacing a measurement of the differences between baselines of each line? If so, the first line's "spacing" doesn't have a previous line to be spaced from so would probably be from the top of itself (to the baseline).
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

texus

  • Sr. Member
  • ****
  • Posts: 499
    • View Profile
    • TGUI
    • Email
Re: font.getLineSpacing not correct for first line
« Reply #2 on: May 11, 2015, 03:02:38 pm »
I think the top is just wrong.



If you look at the above image. The space between the bottom of the 'g' on the first line and the bottom of the 'g' on the second line is exactly 109 pixels. It equals the line spacing as expected.

The distance between the 'g' and the 'Ê' is 2 pixels. This might just be because there exists a bigger character than Ê which I just don't know. But in this case the biggest character would even have a bounds.top of 9, the exact amount of pixels that I am missing with getLineSpacing.
And these exact numbers are not a coincidence. When I found out about this issue some time ago (I just didn't have the time to report it yet), I was using a different font. Here the 'g' and 'Ê' even touched each other, so there wasn't a 2 pixel gap. The characters filled the line spacing exactly.

So I think to top should be 2 here. Then it all fits correctly.
« Last Edit: May 11, 2015, 03:09:12 pm by texus »
TGUI: C++ SFML GUI

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: font.getLineSpacing not correct for first line
« Reply #3 on: May 11, 2015, 03:17:41 pm »
The implementation of Text::getLocalBounds() may indeed need some rework. Unless that has been fixed meanwhile, its height depends on the concrete characters, which makes it not particularly useful for practical purposes (like aligning multiple text objects, or placing them relative to other objects, or centering them in a UI).

You removed relevant information in your edit: 2 instead of 11, which makes that 9 pixel difference.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: font.getLineSpacing not correct for first line
« Reply #4 on: May 11, 2015, 05:40:06 pm »
If you ask me, getLineSpacing does exactly what it says it does: get the spacing between 2 lines. This is also what is written in the documentation: "Line spacing is the vertical offset to apply between two consecutive lines of text."

The FreeType documentation is also very clear about its purpose:
Quote
This field is simply used to compute a default line spacing (i.e., the baseline-to-baseline distance) when writing text with this font. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance.

As you already noticed, this is the vertical distance between the same point within 2 identical glyphs on consecutive lines. Nothing more and nothing less. The fact that some people might end up using this metric as the line height as well isn't really getLineSpacing's fault.

I admit, there currently isn't a way to check for the true maximum height (in FreeType metrics, the global bbox) of a font without constructing a text string containing all the font's glyphs at the moment. Maybe this is what you want/need. But I have to remind you, even with such a possibility, you still rely on the font designer setting their font metrics up properly in the first place. I've seen many fonts with really broken measurements, and SFML will definitely not go out of its way to make up for all of those mistakes.

What Nexus said is also true, getLocalBounds() is the de facto way of doing what you want coupled with what I said about a string containing all supported characters within a font. But just remember, getLocalBounds() is not a Text-specific method. It works on geometry and not glyph data. It has no knowledge of a baseline/ascender/descender, so from that perspective it is also doing the right thing as it is currently implemented.

Yes... it is a bit non-trivial to get many mundane text rendering tasks done in the current state of SFML, but I am pretty sure they are all possible with a bit of thought and knowledge of the API.

aligning multiple text objects
You align multiple text objects via their baselines not the text local bounds, this is already supported albeit maybe not as trivial as one might think.
placing them relative to other objects
You can use either the baseline metric or local bounds metric here, depending on which fits your scenario best.
centering them in a UI
Since the focus is on having the same amount of empty space on all sides of the text in this case, one would use the local bounds instead of the baseline to position the text within another object.

Every now and then, people suggest exposing more font data through the API, and I'm open to features that end up being helpful in some way. Very often however, they overestimate the usefulness of some of the things they suggest because they either disregard the fact that they are not reliable 100% of the time anyway or that they simply misunderstood what the purpose of the specific metric truly is. Yes... the current "construct a string containing all glyphs to get the line height" workaround is a hack, and could be fixed by adding a method to get FreeType's global bbox value, but beyond that, I don't see anything else that really needs to be added that could be useful to users.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

texus

  • Sr. Member
  • ****
  • Posts: 499
    • View Profile
    • TGUI
    • Email
Re: font.getLineSpacing not correct for first line
« Reply #5 on: May 11, 2015, 09:25:24 pm »
Quote
If you ask me, getLineSpacing does exactly what it says it does
That is indeed possible/likely, but then I will still have to find an alternative to do what I want.
I was hoping that every line would have the same height so that it would be easy to know how much size to reserve upfront, but I guess its not going to be that easy.

Quote
there currently isn't a way to check for the true maximum height [...]. Maybe this is what you want/need.
I basically need to do two kinds of calculations with the fonts. One is deciding how much space several lines of text will take, the other is to center (a single line of) text inside an image. Exposing the maximum height would definitely allow me to do the latter in a more simple way.

Quote
you still rely on the font designer setting their font metrics up properly in the first place
I'm not sure if you can do much against that. If it works on most major fonts (the ones installed by default on the OS as opposed to those custom fonts you can download online), then it is good enough for me. And for the other fonts I can try to detect and work around some of the broken measurements but I don't really expect all fonts to work correctly.

Quote
Every now and then, people suggest exposing more font data through the API
I perfectly understand that you don't want to expose most of these things. But "construct a string containing all glyphs to get the line height" sounds like a serious hack, so IF this can be solved by exposing something small then I think it might be worth it. Is it even possible to figure out all the glyphs that the font contains?

Currently I am even centering my text based on the bounds of the string "kg" because that is the size that most text will have. But this code was written before I even knew there was an sf::Glyph class, so I was hoping to clean up these parts of the code. Right now I feel like calculating the lineHeight like in the code in my first post is even the best method. Altough I haven't tested the reliability of the method with different fonts yet.
TGUI: C++ SFML GUI

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: font.getLineSpacing not correct for first line
« Reply #6 on: May 11, 2015, 10:54:52 pm »
You align multiple text objects via their baselines not the text local bounds, this is already supported albeit maybe not as trivial as one might think.
Yes, that's the problem. It's currently quite difficult to align texts properly, I ended up writing my own functions for that. In order to do so, you have to assemble information from various methods. I don't like the idea that every user has to do this, as aligning two texts is something really basic...

Since the focus is on having the same amount of empty space on all sides of the text in this case, one would use the local bounds instead of the baseline to position the text within another object.
Not really. Consider two strings "hello" and "yes" that are centered in two adjacent buttons. I don't want both to have an equally spaced border, because that looks totally weird. When aligning texts, people almost always want to align the baseline, because that's what we're used to from all possible layouts on websites, newspapers and software UIs. My point is that sf::Text::getLocalBounds() is close to useless in its current implementation, I can't really imagine situations where that rectangle would be meaningful. Even for clicking/collision, it behaves too arbitrarily, because a single character in a long word changes the bounds completely.

The semantics are already relaxed for sf::Shape::getGlobalBounds(), which is not a bounding rectangle -- solely for performance reasons. I think it would not hurt if sf::Text::getLocal/GlobalBounds() weren't constrained by a minimal bounding rectangle, either, as this is not too useful for texts. I'm not even sure if the current implemention returns the minimal bounds.

By the way, this is not a new request at all. The first time I used a workaround was half a decade ago -- at a time when we still had SFML 1 and sf::Text::GetRect().
http://en.sfml-dev.org/forums/index.php?topic=2143
http://en.sfml-dev.org/forums/index.php?topic=5102
http://en.sfml-dev.org/forums/index.php?topic=7174

I think I could come up with something more consistent... These workarounds are all inefficient and tailored to specific cases. And it will be similar for many users needing such a functionality.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
Re: font.getLineSpacing not correct for first line
« Reply #7 on: May 12, 2015, 07:57:43 am »
Quote
Right now I feel like calculating the lineHeight like in the code in my first post is even the best method.
It is the right method, since it exactly matches what is done in the implementation:
- the first baseline is at characterSize, then subsequent lines are spaced by font.lineHeight pixels
- the space below the last baseline is g.top + g.height, g being the glyph of a character that extends below the baseline, like 'g'

Quote
Yes, that's the problem. It's currently quite difficult to align texts properly, I ended up writing my own functions for that. In order to do so, you have to assemble information from various methods. I don't like the idea that every user has to do this, as aligning two texts is something really basic...
Aligning multiple texts together shouldn't be difficult, all you have to know is that the first baseline is located at text.characterSize from the top of the text.

Quote
My point is that sf::Text::getLocalBounds() is close to useless in its current implementation, I can't really imagine situations where that rectangle would be meaningful.
When you want to align a single fixed string within its container.

I guess we should compile all the use cases and requested features, and see how we could improve the metrics API of sf::Font and sf::Text.
Laurent Gomila - SFML developer

 

anything