bevy
bevy copied to clipboard
Vertically-centered text is too low
Bevy version
0.15.0-dev, although this bug has been around a long time.
What you did
In a flexbox or grid, place a text element with vertical centering.
What went wrong
The text appears 1 or 2 pixels lower than it should, as seen in the following example screenshot:
Additional information
I had hoped that the migration to cosmic-text would fix this, but apparently not.
@UkoeHB
Is the text itself not centered, or the text node not centered in the parent? I assume you're seeing the problem with JustifyContent::Center?
This particular example is using align, not justify (it's a flex row, not a column) - but I think the type of alignment doesn't matter. I've seen it in a bunch of different situations. I suspect it's a problem in the way the text is being measured, or perhaps a rounding issue.
This isn't a layout problem but the text not centered I think, though when I've seen it was too high, not too low. You can tell for certain by setting a background color on the text itself.
Which font are you using? Try a different font, see if you get a different result?
I'm using OpenSans.
I also had problems with OpenSans and switched to FiraSans. Maybe the font extraction is bugged.
Here's what it looks like with Ubuntu:
This makes it clear, definitely malpositioned:
The gray rect is the text node, the red is the parent it's vertically centered within.
It does look better with FiraSans:
Does anyone know if there is an API in Bevy or its dependencies that helps visualize how text is laid out that would be helpful in investigating this type of problem? (Something like Paint.FontMetrics or Paint.getTextBounds() in Android).
I thought it might be some problem with my layout rounding hacks but there's no difference after removing them. And nothing changes with different font sizes or UiScales.
Should have checked this first, you can see the same problem with Text2d:
The y glyph is overflowing in the same way as with the UI examples.
(The maroon rectangle is a sprite set to the same size as the computed text bounds from TextLayoutInfo,)
In main with ab_glyph the vertical positions are correct and I can't see anything in bevy_text that could be responsible, so it's probably an issue with comic text. Tried updating bevy to their main branch but didn't help.
use bevy::color::palettes::css::MAROON;
use bevy::prelude::*;
use bevy::text::TextLayoutInfo;
use bevy::window::WindowResolution;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::default().with_scale_factor_override(1.),
..Default::default()
}),
..Default::default()
}))
.add_systems(Startup, setup)
.add_systems(Update, update_sprite)
.run();
}
fn setup(mut commands: Commands<'_, '_>, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands
.spawn((
Text2d::new("Primary"),
TextFont {
font: asset_server.load("OpenSans-Regular.ttf"),
font_size: 100.,
font_smoothing: bevy::text::FontSmoothing::None,
},
))
.with_child((
Sprite {
color: MAROON.into(),
..Default::default()
},
Transform::from_translation(-Vec3::Z),
));
}
fn update_sprite(text_query: Query<&TextLayoutInfo>, mut sprite_query: Query<&mut Sprite>) {
let size = text_query.single().size;
sprite_query.single_mut().custom_size = Some(size);
}
I'm not sure if it is really malpositioned. I opened OpenSans-Regular.ttf on FontForge and it seems that glyph y originally reached below the descent line.
I don't think we should be looking at the descender height, but the ascender height. In your example, the distance between the top of the P and the top line is less than the width of the strokes. In the earlier screenshot, however, the distance is more like 3 stroke widths.
This is what I see in Firefox:
- Open Sans Regular
- First pink box:
font-size: 60px, line-height: 0.5 (30px) - Second pink box:
font-size: 60px, line-height: 1 (60px) - Rectangle:
60x60px
~~I think it defines the text box based on the ascenders height (not the uppercase) and the baseline (red lines), then centers vertically the text based on the line-height (pink boxes). It can overflow in both directions.~~ (Edit: "em squre" is used?)
I just realized I don't understand what font-size: 60px means. I knew different fonts have different cap-height/x-height… but what's 60px here? 😅
Edit: this comment is probably noise.
the number of pixels in height
(the following might not be true, as I'm reading contradictory (?) definitions of "em square")
@BenjaminBrienen yes, but height of what? Because it's not of the caps, or from descenders to ascenders…
I forgot to answer myself, font-size refers to the height of the "em square" as it's noted in the web specs:
The font size corresponds to the em square, a concept used in typography. Note that certain glyphs may bleed outside their em squares.
What's interesting is the note, "glyphs may bleed outside"… and I suppose that's what I see in my screenshot. The em-square is 60px, but some diacritics (Ñ) or descenders (y) bleed out. (I tried to check this in FontForge but I have no idea how I can visualize it).
~~Which leads me to think that either Bevy or cosmic-text it's not considering the "em-square"? Why? It's just a hunch after seeing the Firefox/Chrome screenshot, which clearly bleeds from both top and bottom which suggest that "Open Sans Regular" em square bleeds? ickshonpe's screenshots have way too much space both at the top and bottom, in one of the screenshots even the y doesn't overflow. (btw… why the inconsistency? maybe the screenshots have different line-height?)~~ Nope, Bevy uses 1.2 line-height by default, I was using 1
Ignore my previous comments (🙈), I just realized that Bevy uses 1.2 hard-coded line-height, which gives the same result as in Firefox. The good part is that I learned few new things xD
Also, probably there isn't any problem with the overflowing y, it's OK if a glyph bleeds outside of the "em square". If you change Bevy's line-height to 1 you'll probably see even more overflowing.
For what is worth… bevy-reactor vs Firefox: 1px offset.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
rel="stylesheet"
/>
<style>
.button-wrapper {
display: inline-flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-content: center;
padding: 0 10.5px;
border: 0;
color: #ececec;
cursor: pointer;
min-height: 24px;
min-width: 24px;
position: relative;
}
.button-bg {
display: grid;
position: absolute;
inset: 0;
border-radius: var(--border-radius);
background-color: #39393e;
}
.open-sans {
font-family: "Open Sans", sans-serif;
font-optical-sizing: auto;
font-weight: normal;
font-style: normal;
font-variation-settings: "wdth" 100;
font-size: var(--size);
line-height: var(--line-height);
}
.text {
position: relative;
}
</style>
</head>
<body class="flex-center">
<div style="background-color: #666; padding: 16px">
<div
class="button-wrapper open-sans"
style="--line-height: 1.2; --size: 14px; --border-radius: 3px"
>
<div class="button-bg"></div>
<div class="text">Default</div>
</div>
<div
class="button-wrapper open-sans"
style="--line-height: 1; --size: 14px; --border-radius: 3px"
>
<div class="button-bg"></div>
<div class="text">Default</div>
</div>
</div>
</body>
</html>
But which is correct? 🤔
IMO, OP is correct, text looks too low in Bevy. Md/Xs/Xxs look particularly bad in the first screenshot, or the first two rows (which are Md size).
Also note that between the two screenshots there is some anti-aliasing difference. But no idea if that's relevant. It could be because FF uses another anti-aliasing? Or the FF text is not pixel grid aligned and it's 0.5px higher or whatever? 🤷♂
I had the same issue, but the opposite (the text is too high): https://github.com/bevyengine/bevy/issues/16627
My solution was to edit the font using FontForge, go to Element -> Font Info... -> OS/2 -> Metrics and increase the Win Ascent, Typo Ascent and HHead Ascent. Not sure if there's any nasty hidden side-effects, but it works as a quick hack.