changeset 1401:4ecb004ee7f8

did: add horizontal advancment for glyph rendering
author sam <sam@basx.dev>
date Mon, 16 Dec 2024 23:58:35 +0700
parents 20602878744e
children caf441eebc23
files semicongine/text.nim semicongine/text/font.nim tests/test_text.nim
diffstat 3 files changed, 68 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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,
--- 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):