memononen / nanovg

Antialiased 2D vector drawing library on top of OpenGL for UI and visualizations.
zlib License
5.17k stars 775 forks source link

Possible nvgTextBox and nvgTextBoxBounds inconsistency #206

Open SmilyOrg opened 9 years ago

SmilyOrg commented 9 years ago

I'm not entirely certain where the blame lies, but I'm implementing text rendering for a framework and it's measuring the text first before rendering it with the measured width.

The problem is that either nvgTextBoxBounds seems to return a narrower width than nvgTextBox uses internally or that nvgTextBox is using wider or offset widths. With the Droid Sans font at size 27 and text "Button" I'm seeing nvgTextBoxBounds return 70 as the width, but setting that as the width while rendering with nvgTextBox it seems to come up with a final width of 71, which makes it break at "n", so it looks like this:

2015-03-12_19-00-44

I've tried debugging it, but I haven't made much progress, however I did find that there are two different measurements width and minx / maxx. The former seems to match the 71 measurement and I think it's derived from FONStextIter measurements, while the latter seem to be derived from FONSquad ones, both provided by fonsTextIterNext.

I can fix this by rendering with a width 1 wider than the one measured:

2015-03-12_19-15-58

I'm not sure if that'll work for all cases and it doesn't seem like the right fix.

Any thoughts?

memononen commented 9 years ago

They both use the same code to break the text into lines, but different break width can potentially give different results. I think row width is the correct value to use, not min/max. Min/max gives exact row bounds, while width is based on the text advance, the same is used for line breaking.

What if you add just a little bit, like 0.001f? Are you using centered text?

SmilyOrg commented 9 years ago

Hmm, interesting. I'm using the default alignment (which is probably left aligned) and only adding a small epsilon doesn't work, since the conditional that ends up deciding the break

if (type == NVG_CHAR && nextWidth > breakRowWidth) {

ends up with nextWidth being 71 and breakRowWidth being 70 (the width that was measured) and presumably that makes it break at the last character.

memononen commented 9 years ago

If you have debugger handy, can you check where the rowWidth gets set when you calculate the box size?

SmilyOrg commented 9 years ago

First it's set in the if (rowStart == NULL) { if (type == NVG_CHAR) { ... conditional to 14.

Then iterates over chars and gets set in the if (type == NVG_CHAR) conditional after float nextWidth = ... to 28, 36, 44, 57 and finally 71.

Since this is while measuring text with an infinite width, it doesn't break lines and the row ends up being width = 71, minx = 1, maxx = 70.

memononen commented 9 years ago

Ok, so the problem actually is that nvgTextBounds() does not return the row width. Do you know how other APIs implement this? We could make nvgTextBounds() to return max of the row widths. I kinda hate that there are two "width" values in the first place, but they both have their uses.

SmilyOrg commented 9 years ago

I'm still not entirely clear on what the two width values are. I think that at least if nvgTextBoxBounds tells you that text is of a certain width, it shouldn't break into an additional line if you render it at that width.

memononen commented 9 years ago

Min/max tells you the bonding box of the glyphs, width tells you the advance during the line. For example glyph V might have width of 12 pixels, but advance of 11, or glyph of O might actually start at -1.

Maybe I could borrow from Cairo: http://cairographics.org/manual/cairo-cairo-scaled-font-t.html#cairo-text-extents-t width = x_advance min/max bounds = bearings & w/h

SmilyOrg commented 9 years ago

Ah, so if I understand it correctly, min/max are the graphical bounds of the glyph while width/advance is where you should put the next glyph so it looks right. So I assume that the break width is based on the advance while the *bounds functions report the glyph bounding boxes, which would explain the mismatch?

memononen commented 9 years ago

Yes, that is correct. Text has two bounds, the "logical" one which is used for layout, and the actual bounds of the glyph geometry.