8000 Added getAscent and getDescent functions to Font by texus · Pull Request #3053 · SFML/SFML · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Added getAscent and getDescent functions to Font #3053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

texus
Copy link
Contributor
@texus texus commented May 30, 2024

Description

This PR adds functions to the Font class to retrieve the ascent and descent of the font.

I marked the PR as a draft because there are some implementation details that still need to be discussed.

image

Motive

SFML doesn't really provide good way to vertically position text. These functions will help with that.

If two text objects using the same font and character size are given the same Y position, then their baselines do end up at the same vertical position as intended (irrelevant of which characters each text contains). We also know the location of the baseline, as the Text class places it at y = character size.

There are however two issues:

We don't really know the full line height. If I render the letter x, it's bottom location will be at m_characterSize, and it's top position will be some offset that can be found with text.getLocalBounds().getPosition().y. However if I render the letter g, it extends below the baseline. I need to know the maximum amount that any text is going to go below the baseline to e.g. position text inside an edit box without the text being placed too low and be rendered outside the rectangle. Just knowing the position of the baseline isn't enough, I also need the font's descent, which is what the new getDescent() function will provide.

You could estimate the descent by subtracting the character size from the line spacing, but this could be off by a few pixels (and completely wrong for some broken fonts). The alternative that I used was to just get the information from the g glyph, but that only works if the font contains that character, and I learned today that _ is sometimes placed even lower than a g.

If you render text with Y position at 0 then there is a gap at the top. This is normal because you need to keep space for larger characters, it's normal for x to have a larger gap than B at the top. The problem is that the gap is too large. When using DejaVuSans with font size 16, the highest possible characters (e.g. Ê) are only 15 pixels tall). No matter what string you render, the top pixel will always be empty. Because the empty pixel (or more pixels for larger font sizes) is considered part of your line height, trying to vertically center text will always cause the text to be visually rendered too low. This is where the new getAscent() function comes in, it will return the real maximum character height (15 in this example) so that we don't have to wrongly assume that it equals the character size.

Discussion question 1: should sf::Text render the text higher? Instead of placing the baseline at getCharacterSize(), should it be located at getAscent() so that there is no empty space above the "Ê" character?

Example

In the following example a text is drawn with character size 160 (an intentionally large number to make the issues more visible).
A yellow background is rendered to show where the left, top, right and bottom of the text object are located.

SFMLFontAscentDescent

As you can see in the image, the baseline is located at top 160 (= character size). The tallest character however is only 149 pixels, the top 11 pixels are always blank no matter which string you use. Without the getDescent() function, it would also be hard to figure out that the text can extend at most 38 pixels below the baseline and thus 198 pixels from the Y position of the text.

With both the ascent and descent we know the maximum space occupied by any string (149 + 38 = 187), which can be used to better position the text vertically.

Here is the example code:

#include <SFML/Graphics.hpp>
#include <iostream>

int main()
{
    sf::RenderWindow window{sf::VideoMode{{800, 600}}, "SFML font test"};

    sf::Font font = sf::Font::loadFromFile("DejaVuSans.ttf").value();
    std::cerr << "Ascent: " << font.getAscent(160) << std::endl;
    std::cerr << "Descent: " << font.getDescent(160) << std::endl;

    sf::Text text = sf::Text(font);
    text.setCharacterSize(160);
    text.setFillColor(sf::Color::Black);
    text.setPosition({100, 100});
    text.setString(L"\\Êg_");

    sf::RectangleShape rect;
    rect.setPosition({100, 100});
    rect.setFillColor(sf::Color::Yellow);
    rect.setPosition(sf::Vector2f{100, 100});
    rect.setSize(text.getLocalBounds().getPosition() + text.getLocalBounds().getSize());

    while (window.isOpen())
    {
        while (const auto event = window.pollEvent())
        {
            if (event.is<sf::Event::Closed>())
                window.close();
        }

        window.clear({240, 240, 240});
        window.draw(rect);
        window.draw(text);
        window.display();
    }
}

The code has the following output:

Ascent: 148.516
Descent: 37.7344

Discussion question 2: should the values returned by getAscent and getDescent be rounded up to match what will be drawn instead of being the exact number returned by freetype?

Discussion question 3: FreeType and SDL_ttf return the descent as a negative value. The getDescent function currently returns a positive number. Which behavior should we use?

Note on testing: I did not test the if (!FT_IS_SCALABLE(face)) branches, I don't know what kind of font I would need for that.
Edit: I managed to test the non-scalable branch with a bitmap font from https://github.com/olikraus/u8g2/tree/master/tools/font/bdf

@eXpl0it3r
Copy link
Member

See also the related discussion on the forum: https://en.sfml-dev.org/forums/index.php?topic=29354.0

@coveralls
Copy link
Collaborator

Pull Request Test Coverage Report for Build 9309581106

Details

  • 1 of 15 (6.67%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.05%) to 55.586%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/SFML/Graphics/Font.cpp 1 15 6.67%
Totals Coverage Status
Change from base Build 9307707766: -0.05%
Covered Lines: 11521
Relevant Lines: 19602

💛 - Coveralls

@MarioLiebisch
Copy link
Member

Question 1: Using the ascent for this sounds very reasonable to me.
Question 2: Unsure, but I'd tend to want to have what's used in the end.
Question 3: I'd say it should be negative. Glyphs could in theory end above the baseline and in that case I'd prefer to see a positive value, too. (Simple vector geometry after all.)

@ChrisThrasher ChrisThrasher added this to the 3.1 milestone Sep 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Discussion
Development

Successfully merging this pull request may close these issues.

5 participants
0