You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The title is so vague, because there are multiple issues with the string width calculations. I wanted to document them in one place. Feel free to change it to a more appropriate name.
Prompted by the recent discussion on discord about the Label wrap not calculating the text width correctly, I delved a bit into the source to look for the issue.
It turns out the MonoGame.Extended.BitmapFonts.BitmapFont.MeasureString(..) on it's own works mostly as intended.
It uses the StringGlyphEnumerable to calculate the glyph (characters) positions based on the provided FontRegion and the LetterSpacing. For the width it chooses the highest value of the glyphs width added to their respective positions. The height is calculated by multiplying the amount of lines by the LineHeight.
SpriteBatch.DrawStringOnCtrl() calls MonoGame.Extended.BitmapFonts.BitmapFontExtensions.DrawString(..), which uses the same StringGlyphEnumerable to draw the glyphs onto the SpriteBatch. If you measure the whole string you want to draw, the MeasureWidth(..) gives the exact result.
The only thing that MeasureString(..) is missing: Most, if not all, first characters in a line start at a negative x position. Since MeasureString(..) only calculates position + width, the calculated width is missing the offset to the left.
This on it's own is not a cause for the wrap issue, because the calculation fits the visual width of the rendered text (relative to the Labels (0, 0)), because it's also rendered offset to the left.
DrawUtil.WrapText(..)
The WrapText(..) method (more specifically the WrapTextSegment(..) method) that is used all over Blish HUD separates the string into words, calculates their respective width and then glues them back together.
It calculates the width of the space and the words using the MeasureString(..) method mentioned above.
Now the small issue with MeasureString(..) is amplified a lot. Every single word and every space is 'losing' the negative offset from the first character.
Adding the calculated width of the words and spaces together is in most cases not the same as calculating the width of the whole string.
Note
Example DefaultFont14
text: "mm mm mm mm mm mm mm mm mm mm mm mm mm"
measured width as a whole (MeasureString(..)): 373
measured width as single words and then glued together (WrapTextSegment(..)): 361
Default Fonts
There are some issues with the way the default fonts were generated.
Space
No matter the font size, the width is always set to 5. The actual spacing is accomplished by making the xadvance bigger for bigger fonts. This works visually well, but since using MeasureString(..) for a single character only cares about a glyphs width, the aforementioned issue is amplified again by a lot.
For some reason the xoffset for space is -2 which seems to be a lot and rather unneeded.
This (the small green rectangle) is where the space character is actually rendered (and measured).
Other Characters
This probably applies to other special characters, though I have not tested that.
Solutions
WrapText(..)
Regardless of other modifications, WrapText(..) need to be fixed to measure the resulting line as a whole and not word by word. As far as I can tell, this is the only way to ensure that the measured value is always the same as the rendered text.
Caution
Although this is possible to fix and a much needed fix at that, I would advise against a quick fix like #997 or even a proper fix until other PRs are merged that affect this area (namely #984).
Default Fonts
It would be possible to rebuild the default fonts with values that are chosen to mitigate the issue with the implementation of MeasureString(..). I'm not sure if that would be desireable, or otherwise useful.
MeasureString(..)
It would be possible to write an internal MeasureString(..) method that takes the way the default fonts are built into account. How that would affect other non-default fonts I have no idea.
The text was updated successfully, but these errors were encountered:
No issue with calc space width, but word calculation not perfect. If the sum of all lines is greater than the original, the text is moved a little earlier. No visual issues.
The title is so vague, because there are multiple issues with the string width calculations. I wanted to document them in one place. Feel free to change it to a more appropriate name.
Prompted by the recent discussion on discord about the
Label
wrap not calculating the text width correctly, I delved a bit into the source to look for the issue.Latest discussion: https://discord.com/channels/531175899588984842/599270434642460753/1327399531393978440
Previous discussion: https://discord.com/channels/531175899588984842/536970543736291346/1152261761467162848
Problems
MeasureString(..)
It turns out the
MonoGame.Extended.BitmapFonts.BitmapFont.MeasureString(..)
on it's own works mostly as intended.It uses the
StringGlyphEnumerable
to calculate the glyph (characters) positions based on the providedFontRegion
and theLetterSpacing
. For the width it chooses the highest value of the glyphs width added to their respective positions. The height is calculated by multiplying the amount of lines by theLineHeight
.SpriteBatch.DrawStringOnCtrl()
callsMonoGame.Extended.BitmapFonts.BitmapFontExtensions.DrawString(..)
, which uses the sameStringGlyphEnumerable
to draw the glyphs onto theSpriteBatch
. If you measure the whole string you want to draw, theMeasureWidth(..)
gives the exact result.The only thing that
MeasureString(..)
is missing: Most, if not all, first characters in a line start at a negative x position. SinceMeasureString(..)
only calculatesposition + width
, the calculated width is missing the offset to the left.This on it's own is not a cause for the wrap issue, because the calculation fits the visual width of the rendered text (relative to the
Label
s(0, 0)
), because it's also rendered offset to the left.DrawUtil.WrapText(..)
The
WrapText(..)
method (more specifically theWrapTextSegment(..)
method) that is used all over Blish HUD separates the string into words, calculates their respective width and then glues them back together.It calculates the width of the space and the words using the
MeasureString(..)
method mentioned above.Now the small issue with
MeasureString(..)
is amplified a lot. Every single word and every space is 'losing' the negative offset from the first character.Adding the calculated width of the words and spaces together is in most cases not the same as calculating the width of the whole string.
Note
Example DefaultFont14
text: "mm mm mm mm mm mm mm mm mm mm mm mm mm"
measured width as a whole (
MeasureString(..)
): 373measured width as single words and then glued together (
WrapTextSegment(..)
): 361Default Fonts
There are some issues with the way the default fonts were generated.
Space
No matter the font size, the
width
is always set to 5. The actual spacing is accomplished by making thexadvance
bigger for bigger fonts. This works visually well, but since usingMeasureString(..)
for a single character only cares about a glyphs width, the aforementioned issue is amplified again by a lot.For some reason the
xoffset
for space is -2 which seems to be a lot and rather unneeded.This (the small green rectangle) is where the space character is actually rendered (and measured).
Other Characters
This probably applies to other special characters, though I have not tested that.
Solutions
WrapText(..)
Regardless of other modifications,
WrapText(..)
need to be fixed to measure the resulting line as a whole and not word by word. As far as I can tell, this is the only way to ensure that the measured value is always the same as the rendered text.Caution
Although this is possible to fix and a much needed fix at that, I would advise against a quick fix like #997 or even a proper fix until other PRs are merged that affect this area (namely #984).
Default Fonts
It would be possible to rebuild the default fonts with values that are chosen to mitigate the issue with the implementation of
MeasureString(..)
. I'm not sure if that would be desireable, or otherwise useful.MeasureString(..)
It would be possible to write an internal
MeasureString(..)
method that takes the way the default fonts are built into account. How that would affect other non-default fonts I have no idea.The text was updated successfully, but these errors were encountered: