Summary
When a UI2 Text / Button / TextField uses an AtlasFont (the TTF-backed, non-MSDF font path), every glyph quad is rendered shifted up by ~cellHeight/2 and left by ~cellWidth/2. For Latin text at typical sizes the shift is mostly absorbed by padding so it looks only slightly off, but for tall scripts (CJK, etc.) the top of every glyph is clipped by the text box.
Text using an MsdfFont (including the default font) is not affected, so the bug is specific to the AtlasFont rendering path.
Environment
- kool 0.19.0
- Backend:
RenderBackendGl (desktop/JVM, LWJGL), but the defect is in common code (MeshBuilder), not backend-specific.
Expected vs. actual
- Expected: an
AtlasFont glyph occupies the same box an MsdfFont glyph would, vertically centered like Latin/MSDF text.
- Actual: the glyph cell is offset by roughly
(-cellWidth/2, -cellHeight/2); the top of tall glyphs is clipped by the layout box and horizontal centering is slightly off.
Root cause
MeshBuilder.renderAtlasFont emits each glyph quad through rect(RectProps) but never sets RectProps.isCenteredOrigin, which defaults to true:
// MeshBuilder.kt
var isCenteredOrigin = true // RectProps, ~line 1134
With isCenteredOrigin = true, rect() treats props.origin as the center of the quad — it places the four vertices at origin ± halfSize and skips the translate(halfW, halfH) that the !isCenteredOrigin branch applies:
// MeshBuilder.kt fun rect(props: RectProps) ~line 445
val hx = props.size.x * 0.5f
val hy = props.size.y * 0.5f
if (!props.isCenteredOrigin) { // <- NOT taken: default is centered
transform.push()
translate(hx, hy, 0f)
}
// vertices placed at origin.x ± hx, origin.y ± hy (origin treated as center)
positionAttr?.set(props.origin.x - hx, props.origin.y - hy, props.origin.z)
...
But renderAtlasFont computes origin as if it were the corner of the cell, not the center:
// MeshBuilder.kt fun renderAtlasFont(...) ~line 1024
rect(rectProps.apply {
val x = advOffset - metrics.xOffset
val y = if (props.isYAxisUp) metrics.yBaseline - metrics.height else -metrics.yBaseline
origin.set(x, y, 0f) // origin used as a corner...
size.set(metrics.width, metrics.height)
...
}) // ...but rect() treats it as the center
Because the corner-style origin is fed into a center-style rect(), every glyph is displaced by (-width/2, -height/2). The contrast with renderMsdfFont, which places each vertex explicitly and is therefore correct, confirms the path-specific nature of the bug:
// MeshBuilder.kt fun renderMsdfFont(...) ~line 939
val yBot = if (props.isYAxisUp) g.planeBounds.bottom * s else -g.planeBounds.bottom * s
val h = yTop - yBot
val iBtLt = vertex { positionAttr?.set(lt, yBot, 0f); ... } // explicit corners,
val iBtRt = vertex { positionAttr?.set(lt + w, yBot, 0f); ... } // no centered-origin
val iTpLt = vertex { positionAttr?.set(lt - h * font.italic, yBot + h, 0f); ... }
...
text() dispatches the two paths, so the same string differs purely by font type:
// MeshBuilder.kt ~line 884
fun text(props: TextProps) {
when (val font = props.font) {
is AtlasFont -> renderAtlasFont(font, props) // buggy
is MsdfFont -> renderMsdfFont(font, props) // correct
}
}
Minimal reproduction
A UI2 panel with one CJK label using a TTF-backed AtlasFont and the same label using MsdfFont:
val cjk = "单位设置"
Text(cjk) { modifier.font(AtlasFont("Noto Sans CJK SC", 26f, chars = cjk)) } // top-clipped
Text(cjk) { modifier.font(myMsdfFont.derive(26f)) } // correct
The first renders with the top of each glyph cut off and slightly left of center; the second is correct.
Summary
When a UI2
Text/Button/TextFielduses anAtlasFont(the TTF-backed, non-MSDF font path), every glyph quad is rendered shifted up by ~cellHeight/2 and left by ~cellWidth/2. For Latin text at typical sizes the shift is mostly absorbed by padding so it looks only slightly off, but for tall scripts (CJK, etc.) the top of every glyph is clipped by the text box.Text using an
MsdfFont(including the default font) is not affected, so the bug is specific to theAtlasFontrendering path.Environment
RenderBackendGl(desktop/JVM, LWJGL), but the defect is in common code (MeshBuilder), not backend-specific.Expected vs. actual
AtlasFontglyph occupies the same box anMsdfFontglyph would, vertically centered like Latin/MSDF text.(-cellWidth/2, -cellHeight/2); the top of tall glyphs is clipped by the layout box and horizontal centering is slightly off.Root cause
MeshBuilder.renderAtlasFontemits each glyph quad throughrect(RectProps)but never setsRectProps.isCenteredOrigin, which defaults totrue:With
isCenteredOrigin = true,rect()treatsprops.originas the center of the quad — it places the four vertices atorigin ± halfSizeand skips thetranslate(halfW, halfH)that the!isCenteredOriginbranch applies:But
renderAtlasFontcomputesoriginas if it were the corner of the cell, not the center:Because the corner-style
originis fed into a center-stylerect(), every glyph is displaced by(-width/2, -height/2). The contrast withrenderMsdfFont, which places each vertex explicitly and is therefore correct, confirms the path-specific nature of the bug:text()dispatches the two paths, so the same string differs purely by font type:Minimal reproduction
A UI2 panel with one CJK label using a TTF-backed
AtlasFontand the same label usingMsdfFont:The first renders with the top of each glyph cut off and slightly left of center; the second is correct.