# HG changeset patch # User sam # Date 1734368315 -25200 # Node ID 4ecb004ee7f827a9e0b2e95236187b92fc12b0ff # Parent 20602878744e0b3348e775cb7d53184f0b85628c did: add horizontal advancment for glyph rendering diff -r 20602878744e -r 4ecb004ee7f8 semicongine/text.nim --- a/semicongine/text.nim Mon Dec 16 00:27:40 2024 +0700 +++ b/semicongine/text.nim Mon Dec 16 23:58:35 2024 +0700 @@ -31,7 +31,6 @@ FontObj*[N: static int] = object advance*: Table[Rune, float32] kerning*: Table[(Rune, Rune), float32] - lineHeight*: float32 lineAdvance*: float32 descriptorSet*: DescriptorSetData[GlyphDescriptorSet[N]] descriptorGlyphIndex: Table[Rune, uint16] @@ -73,11 +72,16 @@ void main() { int vertexI = indices[gl_VertexIndex]; vec3 pos = vec3( - glyphquads.pos[glyphIndex][i_x[vertexI]] * scale, - glyphquads.pos[glyphIndex][i_y[vertexI]] * scale * textRendering.aspectRatio, + glyphquads.pos[glyphIndex][i_x[vertexI]] * scale / textRendering.aspectRatio, + glyphquads.pos[glyphIndex][i_y[vertexI]] * scale, 1 - (gl_InstanceIndex + 1) * epsilon // allows overlapping glyphs to make proper depth test ); - gl_Position = vec4(pos + position, 1.0); + vec3 offset = vec3( + (position.x - textRendering.aspectRatio + 1) / textRendering.aspectRatio, + position.y, + position.z + ); + gl_Position = vec4(pos + offset, 1.0); vec2 uv = vec2(glyphquads.uv[glyphIndex][i_x[vertexI]], glyphquads.uv[glyphIndex][i_y[vertexI]]); fragmentUv = uv; fragmentColor = color; @@ -113,30 +117,35 @@ ) = assert text.len <= glyphs.position.len, &"Set {text.len} but Glyphs-object only supports {glyphs.position.len}" - var cursorPos = position + var origin = + vec3(position.x * 2'f32 - 1'f32, -(position.y * 2'f32 - 1'f32), position.z) + let s = scale * glyphs.baseScale + var cursorPos = origin for i in 0 ..< text.len: - if not text[i].isWhitespace(): - glyphs.position[glyphs.cursor] = cursorPos - glyphs.scale[glyphs.cursor] = scale * glyphs.baseScale - glyphs.color[glyphs.cursor] = color - if text[i] in glyphs.font.descriptorGlyphIndex: - glyphs.glyphIndex[glyphs.cursor] = glyphs.font.descriptorGlyphIndex[text[i]] + if text[i] == Rune('\n'): + cursorPos.x = origin.x + cursorPos.y = cursorPos.y - glyphs.font.lineAdvance * s + else: + if not text[i].isWhitespace(): + glyphs.position[glyphs.cursor] = cursorPos + glyphs.scale[glyphs.cursor] = s + glyphs.color[glyphs.cursor] = color + if text[i] in glyphs.font.descriptorGlyphIndex: + glyphs.glyphIndex[glyphs.cursor] = glyphs.font.descriptorGlyphIndex[text[i]] + else: + glyphs.glyphIndex[glyphs.cursor] = + glyphs.font.descriptorGlyphIndex[glyphs.font.fallbackCharacter] + inc glyphs.cursor + + if text[i] in glyphs.font.advance: + cursorPos.x = cursorPos.x + glyphs.font.advance[text[i]] * s else: - glyphs.glyphIndex[glyphs.cursor] = - glyphs.font.descriptorGlyphIndex[glyphs.font.fallbackCharacter] - inc glyphs.cursor + cursorPos.x = + cursorPos.x + glyphs.font.advance[glyphs.font.fallbackCharacter] * s - if text[i] in glyphs.font.advance: - cursorPos.x = - cursorPos.x + glyphs.font.advance[text[i]] * scale * glyphs.baseScale - else: - cursorPos.x = - cursorPos.x + - glyphs.font.advance[glyphs.font.fallbackCharacter] * scale * glyphs.baseScale - - if i < text.len - 1: - cursorPos.x = - cursorPos.x + glyphs.font.kerning.getOrDefault((text[i], text[i + 1]), 0) * scale + if i < text.len - 1: + cursorPos.x = + cursorPos.x + glyphs.font.kerning.getOrDefault((text[i], text[i + 1]), 0) * s proc reset*(glyphs: var Glyphs) = glyphs.cursor = 0 diff -r 20602878744e -r 4ecb004ee7f8 semicongine/text/font.nim --- a/semicongine/text/font.nim Mon Dec 16 00:27:40 2024 +0700 +++ b/semicongine/text/font.nim Mon Dec 16 23:58:35 2024 +0700 @@ -54,18 +54,18 @@ var indata = stream.readAll() - fontinfo: stbtt_fontinfo - if stbtt_InitFont(addr fontinfo, indata.ToCPointer, 0) == 0: + fi: stbtt_fontinfo + if stbtt_InitFont(addr fi, indata.ToCPointer, 0) == 0: raise newException(Exception, "An error occured while loading font file") - var ascent, descent, lineGap: cint - stbtt_GetFontVMetrics(addr fontinfo, addr ascent, addr descent, addr lineGap) + let + glyph2bitmapScale = + float32(stbtt_ScaleForPixelHeight(addr fi, cfloat(lineHeightPixels))) + glyph2QuadScale = glyph2bitmapScale / lineHeightPixels - let fscale = - float32(stbtt_ScaleForPixelHeight(addr fontinfo, cfloat(lineHeightPixels))) # ensure all codepoints are available in the font for codePoint in codePoints: - if stbtt_FindGlyphIndex(addr fontinfo, cint(codePoint)) == 0: + if stbtt_FindGlyphIndex(addr fi, cint(codePoint)) == 0: warn &"Loading font {name}: Codepoint '{codePoint}' ({cint(codePoint)}) has no glyph" var @@ -79,9 +79,9 @@ offsetY[codePoint] = 0 var width, height: cint let data = stbtt_GetCodepointBitmap( - addr fontinfo, - fscale, - fscale, + addr fi, + glyph2bitmapScale, + glyph2bitmapScale, cint(codePoint), addr width, addr height, @@ -106,11 +106,13 @@ # generate quad-information for use in shader for i in 0 ..< codePoints.len: let codePoint = codePoints[i] - var advance, leftBearing: cint # is in glyph-space, needs to be scaled to pixel-space + var advanceUnscaled, leftBearingUnscaled: cint + # is in glyph-space, needs to be scaled to pixel-space stbtt_GetCodepointHMetrics( - addr fontinfo, cint(codePoint), addr advance, addr leftBearing + addr fi, cint(codePoint), addr advanceUnscaled, addr leftBearingUnscaled ) - result.advance[codePoint] = float32(advance) * fscale * (1 / lineHeightPixels) + var leftBearing = leftBearingUnscaled.float32 * glyph2QuadScale + result.advance[codePoint] = advanceUnscaled.float32 * glyph2QuadScale let atlasW = float32(result.descriptorSet.data.fontAtlas.width) @@ -118,15 +120,16 @@ uv = vec2(packed.coords[i].x, packed.coords[i].y) bitmapW = float32(bitmaps[i].width) bitmapH = float32(bitmaps[i].height) - left = float32(leftBearing) * fscale + float32(offsetX[codePoint]) - right = left + bitmapW - top = -float32(offsetY[codePoint]) - bottom = top - bitmapH + # divide by lineHeightPixels to get from pixel-space to quad-geometry-space + left = leftBearing + offsetX[codePoint].float32 / lineHeightPixels + right = left + bitmapW / lineHeightPixels + top = -offsetY[codePoint].float32 / lineHeightPixels + bottom = top - bitmapH / lineHeightPixels template glyphquads(): untyped = result.descriptorSet.data.glyphquads.data - glyphquads.pos[i] = vec4(left, bottom, right, top) * (1 / lineHeightPixels) + glyphquads.pos[i] = vec4(left, bottom, right, top) glyphquads.uv[i] = vec4( (uv.x + 0.5) / atlasW, # left (uv.y + bitmapH - 0.5) / atlasH, # bottom @@ -141,14 +144,13 @@ for codePointAfter in codePoints: result.kerning[(codePoint, codePointAfter)] = float32( - stbtt_GetCodepointKernAdvance( - addr fontinfo, cint(codePoint), cint(codePointAfter) - ) - ) * fscale + stbtt_GetCodepointKernAdvance(addr fi, cint(codePoint), cint(codePointAfter)) + ) * glyph2QuadScale # line spacing - result.lineHeight = float32(ascent - descent) * fscale - result.lineAdvance = float32(ascent - descent + lineGap) * fscale + var ascent, descent, lineGap: cint + stbtt_GetFontVMetrics(addr fi, addr ascent, addr descent, addr lineGap) + result.lineAdvance = float32(ascent - descent + lineGap) * glyph2QuadScale proc loadFont*[N: static int]( path: string, diff -r 20602878744e -r 4ecb004ee7f8 tests/test_text.nim --- a/tests/test_text.nim Mon Dec 16 00:27:40 2024 +0700 +++ b/tests/test_text.nim Mon Dec 16 23:58:35 2024 +0700 @@ -19,10 +19,11 @@ const MAX_GLYPHS = 200 proc test_01_static_label_new(time: float32) = var font = loadFont[MAX_GLYPHS]("Overhaul.ttf", lineHeightPixels = 200) + # var font = loadFont[MAX_GLYPHS]("DejaVuSans.ttf", lineHeightPixels = 200) var renderdata = initRenderData() var pipeline = createPipeline[GlyphShader[MAX_GLYPHS]](renderPass = vulkan.swapchain.renderPass) - var glyphs = font.initGlyphs(1000, baseScale = 0.3) + var glyphs = font.initGlyphs(1000, baseScale = 0.1) assignBuffers(renderdata, glyphs) assignBuffers(renderdata, font.descriptorSet) @@ -33,7 +34,12 @@ while ((getMonoTime() - start).inMilliseconds().int / 1000) < time: let t = getMonoTime() glyphs.reset() - glyphs.add("semicongine".toRunes()) + glyphs.add("semi-\ncon-\nginea".toRunes(), vec3(0.0, 0.0)) + glyphs.add("semi-\ncon-\ngine".toRunes(), vec3(0.5, -0.5)) + glyphs.add("semi-\ncon-\ngine".toRunes(), vec3(-0.5, 0.5)) + glyphs.add("semi-\ncon-\ngineb".toRunes(), vec3(0.5, 0.5)) + glyphs.add("semi-\ncon-\ngineb".toRunes(), vec3(0.9, 0.1)) + glyphs.add("semi-\ncon-\ngineb".toRunes(), vec3(0.1, 0.9)) glyphs.updateAllGPUBuffers(flush = true) withNextFrame(framebuffer, commandbuffer):