Mercurial > games > semicongine
changeset 1403:02d302c868d5
fix: font-rendering alignment, still something off with positioning
author | sam <sam@basx.dev> |
---|---|
date | Wed, 18 Dec 2024 23:39:54 +0700 |
parents | caf441eebc23 |
children | 80cfa19d1e2c |
files | semicongine/text.nim semicongine/text/font.nim tests/test_text.nim |
diffstat | 3 files changed, 101 insertions(+), 70 deletions(-) [+] |
line wrap: on
line diff
--- a/semicongine/text.nim Wed Dec 18 00:11:40 2024 +0700 +++ b/semicongine/text.nim Wed Dec 18 23:39:54 2024 +0700 @@ -20,6 +20,11 @@ SPACE = Rune(' ') type + TextAlignment* = enum + Left + Center + Right + GlyphQuad[N: static int] = object pos: array[N, Vec4f] # vertex offsets to glyph center: [left, bottom, right, top] uv: array[N, Vec4f] # [left, bottom, right, top] @@ -34,6 +39,7 @@ lineAdvance*: float32 lineHeight*: float32 # like lineAdvance - lineGap ascent*: float32 # from baseline to highest glyph + descent*: float32 # from baseline to highest glyph descriptorSet*: DescriptorSetData[GlyphDescriptorSet[N]] descriptorGlyphIndex: Table[Rune, uint16] fallbackCharacter: Rune @@ -42,7 +48,7 @@ Glyphs*[N: static int] = object cursor: int - font: Font[N] + font*: Font[N] baseScale*: float32 position*: GPUArray[Vec3f, VertexBufferMapped] color*: GPUArray[Vec4f, VertexBufferMapped] @@ -110,29 +116,83 @@ result.color.data.setLen(count) result.glyphIndex.data.setLen(count) +iterator splitLines(text: seq[Rune]): seq[Rune] = + var current = newSeq[Rune]() + for c in text: + if c == Rune('\n'): + yield current + current = newSeq[Rune]() + else: + current.add c + yield current + +proc width(font: Font, text: seq[Rune], scale: float32): float32 = + for i in 0 ..< text.len: + if not (i == text.len - 1 and text[i].isWhiteSpace): + if text[i] in font.advance: + result += font.advance[text[i]] * scale + else: + result += font.advance[font.fallbackCharacter] * scale + if i < text.len - 1: + result += font.kerning.getOrDefault((text[i], text[i + 1]), 0) * scale + +proc textDimension*(font: Font, text: seq[Rune], scale: float32): Vec2f = + let nLines = text.countIt(it == Rune('\n')).float32 + let h = nLines * font.lineAdvance * scale + font.lineHeight * scale + let w = max(splitLines(text).toSeq.mapIt(width(font, it, scale))) + + return vec2(w, h * 0.5) + proc add*( glyphs: var Glyphs, text: seq[Rune], position: Vec3f, - scale = 1'f32, - color = vec4(1, 1, 1, 1), + alignment: TextAlignment = Left, + anchor: Vec2f = vec2(0, 1), + scale: float32 = 1'f32, + color: Vec4f = vec4(1, 1, 1, 1), ) = ## Add text for rendering. ## `position` is the display position, where as `(0, 0) is top-left and (1, 1) is bottom right. ## The z-compontent goes from 0 (near plane) to 1 (far plane) and is usually just used for ordering layers ## this should be called again after aspect ratio of window changes + ## Anchor is the anchor to use inside the text + assert text.len <= glyphs.position.len, &"Set {text.len} but Glyphs-object only supports {glyphs.position.len}" - var origin = vec3( - position.x * getAspectRatio() * 2'f32 - 1'f32, - -(position.y * 2'f32 - 1'f32), - position.z, - ) - let s = scale * glyphs.baseScale - var cursorPos = origin + + let + s = scale * glyphs.baseScale + d = textDimension(glyphs.font, text, s) + baselineStart = vec2(0, (glyphs.font.ascent + glyphs.font.descent) * s) + pos = position.xy - anchor * d + baselineStart + lineWidths = splitLines(text).toSeq.mapIt(width(glyphs.font, it, s)) + + var + origin = vec3( + pos.x * getAspectRatio() * 2'f32 - 1'f32, -(pos.y * 2'f32 - 1'f32), position.z + ) + cursorPos = origin + lineI = 0 + + case alignment + of Left: + cursorPos.x = origin.x + of Center: + cursorPos.x = origin.x - ((lineWidths[lineI] - d.x) / 2) + of Right: + cursorPos.x = origin.x - (lineWidths[lineI] - d.x) + for i in 0 ..< text.len: if text[i] == Rune('\n'): - cursorPos.x = origin.x + inc lineI + case alignment + of Left: + cursorPos.x = origin.x + of Center: + cursorPos.x = origin.x - ((lineWidths[lineI] - d.x) / 2) + of Right: + cursorPos.x = origin.x - (lineWidths[lineI] - d.x) cursorPos.y = cursorPos.y - glyphs.font.lineAdvance * s else: if not text[i].isWhitespace(): @@ -156,60 +216,16 @@ cursorPos.x = cursorPos.x + glyphs.font.kerning.getOrDefault((text[i], text[i + 1]), 0) * s -proc textDimension(glyphs: var Glyphs, text: seq[Rune], scale: float32): Vec2f = - let s = scale * glyphs.baseScale - let nLines = text.countIt(it == Rune('\n')).float32 - let height = nLines * glyphs.font.lineAdvance * s + glyphs.font.lineHeight * s - - var width = 0'f32 - var lineI = 0 - var currentWidth = 0'f32 - for i in 0 ..< text.len: - if text[i] == NEWLINE: - width = max(currentWidth, width) - currentWidth = 0'f32 - inc lineI - else: - if not (i == text.len - 1 and text[i].isWhiteSpace): - if text[i] in glyphs.font.advance: - currentWidth += glyphs.font.advance[text[i]] * s - else: - currentWidth += glyphs.font.advance[glyphs.font.fallbackCharacter] * s - if i < text.len - 1: - currentWidth += glyphs.font.kerning.getOrDefault((text[i], text[i + 1]), 0) * s - return vec2(width, height) - -proc add*( - glyphs: var Glyphs, - text: seq[Rune], - position: Vec3f, - anchor: Vec2f, - scale = 1'f32, - color = vec4(1, 1, 1, 1), -) = - let s = scale * glyphs.baseScale - let baselineStart = vec2(0, glyphs.font.ascent * s) - let pos = position.xy + anchor * textDimension(glyphs, text, scale) + baselineStart - add(glyphs, text, pos.toVec3(position.z), scale, color) - proc add*( glyphs: var Glyphs, text: string, position: Vec3f, - scale = 1'f32, - color = vec4(1, 1, 1, 1), + alignment: TextAlignment = Left, + anchor: Vec2f = vec2(0, 1), + scale: float32 = 1'f32, + color: Vec4f = vec4(1, 1, 1, 1), ) = - add(glyphs, text.toRunes, position, scale, color) - -proc add*( - glyphs: var Glyphs, - text: string, - position: Vec3f, - anchor: Vec2f, - scale = 1'f32, - color = vec4(1, 1, 1, 1), -) = - add(glyphs, text.toRunes, position, anchor, scale, color) + add(glyphs, text.toRunes, position, alignment, anchor, scale, color) proc reset*(glyphs: var Glyphs) = glyphs.cursor = 0
--- a/semicongine/text/font.nim Wed Dec 18 00:11:40 2024 +0700 +++ b/semicongine/text/font.nim Wed Dec 18 23:39:54 2024 +0700 @@ -153,6 +153,7 @@ result.lineAdvance = float32(ascent - descent + lineGap) * glyph2QuadScale result.lineHeight = float32(ascent - descent) * glyph2QuadScale result.ascent = float32(ascent) * glyph2QuadScale + result.descent = float32(descent) * glyph2QuadScale proc loadFont*[N: static int]( path: string,
--- a/tests/test_text.nim Wed Dec 18 00:11:40 2024 +0700 +++ b/tests/test_text.nim Wed Dec 18 23:39:54 2024 +0700 @@ -18,8 +18,8 @@ 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 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) @@ -34,13 +34,27 @@ while ((getMonoTime() - start).inMilliseconds().int / 1000) < time: let t = getMonoTime() glyphs.reset() - glyphs.add("semi-\ncon-\nginea", vec3(0.0, 0.0), vec2(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", vec3(0.5, 0.5), vec2(0.5, 0.5)) - glyphs.add("semi-\ncon-\ngineb", vec3(0.9, 0.9), vec2(0, 0)) - # glyphs.add("semi-\ncon-\ngineb".toRunes(), vec3(0.1, 0.9)) + glyphs.add("+", vec3(0.0, 0.0), anchor = vec2(0.5, 0.5), color = vec4(0, 0, 0, 1)) + glyphs.add( + "Hello world\nHow are you today?\nWell, I am fine".toRunes(), + vec3(0.5, 0.5), + alignment = Right, + anchor = vec2(1, 0.5), + color = vec4(0, 0, 0, 1), + ) + glyphs.add( + "Hello world\nHow are you today?\nWell, I am fine".toRunes(), + vec3(0.5, 0.5), + alignment = Left, + anchor = vec2(0, 0.5), + color = vec4(0, 0, 0, 1), + ) + glyphs.add("semi-\ncon-\ngine".toRunes(), vec3(0.5, -0.5), color = vec4(0, 0, 0, 1)) + glyphs.add("semi-\ncon-\ngine".toRunes(), vec3(-0.5, 0.5), color = vec4(0, 0, 0, 1)) + # glyphs.add("11111111111111111", vec3(0.5, 0.5), vec2(1, 0)) + # glyphs.add("22222222222222222", vec3(0.5, 0.5)) + # glyphs.add("33333333333333333", vec3(0.5, 0.5), vec2(0, 1)) + glyphs.add("semi-\ncon-\ngineb".toRunes(), vec3(0.1, 0.9), color = vec4(0, 0, 0, 1)) glyphs.updateAllGPUBuffers(flush = true) withNextFrame(framebuffer, commandbuffer): @@ -51,7 +65,7 @@ commandbuffer, vulkan.swapchain.width, vulkan.swapchain.height, - vec4(0, 0, 0, 0), + vec4(1, 1, 1, 1), ): withPipeline(commandbuffer, pipeline): renderGlyphs(commandbuffer, pipeline, glyphs)