Mercurial > games > semicongine
comparison semicongine/text.nim @ 1408:17d960ff6a24
did: implement decent text rendering (I hope, we'll see)
| author | sam <sam@basx.dev> |
|---|---|
| date | Sun, 22 Dec 2024 22:32:12 +0700 |
| parents | 56f927b89716 |
| children | 5a56f8ac328b |
comparison
equal
deleted
inserted
replaced
| 1407:56f927b89716 | 1408:17d960ff6a24 |
|---|---|
| 45 descriptorSet*: DescriptorSetData[GlyphDescriptorSet[MaxGlyphs]] | 45 descriptorSet*: DescriptorSetData[GlyphDescriptorSet[MaxGlyphs]] |
| 46 descriptorGlyphIndex: Table[Rune, uint16] | 46 descriptorGlyphIndex: Table[Rune, uint16] |
| 47 fallbackCharacter: Rune | 47 fallbackCharacter: Rune |
| 48 | 48 |
| 49 Font*[MaxGlyphs: static int] = ref FontObj[MaxGlyphs] | 49 Font*[MaxGlyphs: static int] = ref FontObj[MaxGlyphs] |
| 50 | |
| 51 TextHandle* = distinct int | |
| 50 Text = object | 52 Text = object |
| 51 bufferOffset: int | 53 bufferOffset: int |
| 52 text: seq[Rune] | 54 text: seq[Rune] |
| 53 position: Vec3f = vec3() | 55 position: Vec3f = vec3() |
| 54 alignment: TextAlignment = Left | 56 alignment: TextAlignment = Left |
| 55 anchor: Vec2f = vec2() | 57 anchor: Vec2f = vec2() |
| 56 scale: float32 = 0 | 58 scale: float32 = 0 |
| 57 color: Vec4f = vec4(1, 1, 1, 1) | 59 color: Vec4f = vec4(1, 1, 1, 1) |
| 60 capacity: int | |
| 58 | 61 |
| 59 TextBuffer*[MaxGlyphs: static int] = object | 62 TextBuffer*[MaxGlyphs: static int] = object |
| 60 cursor: int | 63 cursor: int |
| 61 font*: Font[MaxGlyphs] | 64 font*: Font[MaxGlyphs] |
| 62 baseScale*: float32 | 65 baseScale*: float32 |
| 154 let h = (nLines * font.lineAdvance * scale + font.lineHeight * scale) | 157 let h = (nLines * font.lineAdvance * scale + font.lineHeight * scale) |
| 155 let w = max(splitLines(text).toSeq.mapIt(width(font, it, scale))) | 158 let w = max(splitLines(text).toSeq.mapIt(width(font, it, scale))) |
| 156 | 159 |
| 157 return vec2(w, h) | 160 return vec2(w, h) |
| 158 | 161 |
| 162 proc updateGlyphData*(textbuffer: var TextBuffer, textHandle: TextHandle) = | |
| 163 let | |
| 164 i = int(textHandle) | |
| 165 text = textbuffer.texts[i].text | |
| 166 position = textbuffer.texts[i].position | |
| 167 alignment = textbuffer.texts[i].alignment | |
| 168 anchor = textbuffer.texts[i].anchor | |
| 169 scale = textbuffer.texts[i].scale | |
| 170 color = textbuffer.texts[i].color | |
| 171 offset = textbuffer.texts[i].bufferOffset | |
| 172 capacity = textbuffer.texts[i].capacity | |
| 173 | |
| 174 globalScale = scale * textbuffer.baseScale | |
| 175 box = textDimension(textbuffer.font, text, globalScale) | |
| 176 xH = textbuffer.font.xHeight * globalScale | |
| 177 aratio = getAspectRatio() | |
| 178 origin = vec3( | |
| 179 position.x - (anchor.x * 0.5 + 0.5) * box.x / aratio, | |
| 180 position.y + (anchor.y * -0.5 + 0.5) * box.y - xH * 0.5 - | |
| 181 textbuffer.font.lineHeight * globalScale * 0.5, | |
| 182 position.z, | |
| 183 ) | |
| 184 lineWidths = splitLines(text).toSeq.mapIt(width(textbuffer.font, it, globalScale)) | |
| 185 maxWidth = box.x | |
| 186 | |
| 187 var | |
| 188 cursorPos = origin | |
| 189 lineI = 0 | |
| 190 | |
| 191 case alignment | |
| 192 of Left: | |
| 193 cursorPos.x = origin.x | |
| 194 of Center: | |
| 195 cursorPos.x = origin.x + ((maxWidth - lineWidths[lineI]) / aratio * 0.5) | |
| 196 of Right: | |
| 197 cursorPos.x = origin.x + (maxWidth - lineWidths[lineI]) / aratio | |
| 198 | |
| 199 for i in 0 ..< capacity: | |
| 200 if i < text.len: | |
| 201 if text[i] == Rune('\n'): | |
| 202 inc lineI | |
| 203 case alignment | |
| 204 of Left: | |
| 205 cursorPos.x = origin.x | |
| 206 of Center: | |
| 207 cursorPos.x = origin.x + ((maxWidth - lineWidths[lineI]) / aratio * 0.5) | |
| 208 of Right: | |
| 209 cursorPos.x = origin.x + (maxWidth - lineWidths[lineI]) / aratio | |
| 210 cursorPos.y = cursorPos.y - textbuffer.font.lineAdvance * globalScale | |
| 211 else: | |
| 212 if not text[i].isWhitespace(): | |
| 213 textbuffer.position[offset + i] = cursorPos | |
| 214 textbuffer.scale[offset + i] = globalScale | |
| 215 textbuffer.color[offset + i] = color | |
| 216 if text[i] in textbuffer.font.descriptorGlyphIndex: | |
| 217 textbuffer.glyphIndex[offset + i] = | |
| 218 textbuffer.font.descriptorGlyphIndex[text[i]] | |
| 219 else: | |
| 220 textbuffer.glyphIndex[offset + i] = | |
| 221 textbuffer.font.descriptorGlyphIndex[textbuffer.font.fallbackCharacter] | |
| 222 | |
| 223 if text[i] in textbuffer.font.advance: | |
| 224 cursorPos.x = | |
| 225 cursorPos.x + textbuffer.font.advance[text[i]] * globalScale / aratio | |
| 226 else: | |
| 227 cursorPos.x = | |
| 228 cursorPos.x + | |
| 229 textbuffer.font.advance[textbuffer.font.fallbackCharacter] * globalScale / | |
| 230 aratio | |
| 231 | |
| 232 if i < text.len - 1: | |
| 233 cursorPos.x = | |
| 234 cursorPos.x + | |
| 235 textbuffer.font.kerning.getOrDefault((text[i], text[i + 1]), 0) * globalScale / | |
| 236 aratio | |
| 237 else: | |
| 238 textbuffer.position[offset + i] = vec3() | |
| 239 textbuffer.scale[offset + i] = 0 | |
| 240 textbuffer.color[offset + i] = vec4() | |
| 241 textbuffer.glyphIndex[offset + i] = 0 | |
| 242 | |
| 243 proc updateGlyphData*(textbuffer: var TextBuffer) = | |
| 244 for i in 0 ..< textbuffer.texts.len: | |
| 245 textbuffer.updateGlyphData(TextHandle(i)) | |
| 246 | |
| 247 proc refresh*(textbuffer: var TextBuffer) = | |
| 248 textbuffer.updateGlyphData() | |
| 249 textbuffer.updateAllGPUBuffers(flush = true) | |
| 250 | |
| 159 proc add*( | 251 proc add*( |
| 160 textbuffer: var TextBuffer, | 252 textbuffer: var TextBuffer, |
| 161 text: seq[Rune], | 253 text: seq[Rune], |
| 162 position: Vec3f, | 254 position: Vec3f, |
| 163 alignment: TextAlignment = Left, | 255 alignment: TextAlignment = Left, |
| 164 anchor: Vec2f = vec2(0, 0), | 256 anchor: Vec2f = vec2(0, 0), |
| 165 scale: float32 = 1'f32, | 257 scale: float32 = 1'f32, |
| 166 color: Vec4f = vec4(1, 1, 1, 1), | 258 color: Vec4f = vec4(1, 1, 1, 1), |
| 167 ) = | 259 capacity: int = 0, |
| 260 ): TextHandle = | |
| 168 ## This should be called again after aspect ratio of window changes | 261 ## This should be called again after aspect ratio of window changes |
| 169 | 262 |
| 170 assert text.len <= textbuffer.position.len, | 263 let cap = if capacity == 0: text.len else: capacity |
| 171 &"Set {text.len} but TextBuffer-object only supports {textbuffer.position.len}" | 264 assert textbuffer.cursor + cap <= textbuffer.position.len, |
| 265 &"Text is too big for TextBuffer ({textbuffer.position.len - textbuffer.cursor} left, but need {cap})" | |
| 266 | |
| 267 result = TextHandle(textbuffer.texts.len) | |
| 172 | 268 |
| 173 textbuffer.texts.add Text( | 269 textbuffer.texts.add Text( |
| 174 bufferOffset: textbuffer.cursor, | 270 bufferOffset: textbuffer.cursor, |
| 175 text: text, | 271 text: text, |
| 176 position: position, | 272 position: position, |
| 177 alignment: alignment, | 273 alignment: alignment, |
| 178 anchor: anchor, | 274 anchor: anchor, |
| 179 scale: scale, | 275 scale: scale, |
| 180 color: color, | 276 color: color, |
| 277 capacity: cap, | |
| 181 ) | 278 ) |
| 182 | 279 textbuffer.cursor += cap |
| 183 let | 280 textbuffer.updateGlyphData(result) |
| 184 globalScale = scale * textbuffer.baseScale | |
| 185 box = textDimension(textbuffer.font, text, globalScale) | |
| 186 xH = textbuffer.font.xHeight * globalScale | |
| 187 origin = vec3( | |
| 188 position.x - (anchor.x * 0.5 + 0.5) * box.x / getAspectRatio(), | |
| 189 position.y + (anchor.y * -0.5 + 0.5) * box.y - xH * 0.5 - | |
| 190 textbuffer.font.lineHeight * globalScale * 0.5, | |
| 191 position.z, | |
| 192 ) | |
| 193 lineWidths = splitLines(text).toSeq.mapIt(width(textbuffer.font, it, globalScale)) | |
| 194 maxWidth = box.x | |
| 195 aratio = getAspectRatio() | |
| 196 # echo text, anchor | |
| 197 | |
| 198 var | |
| 199 cursorPos = origin | |
| 200 lineI = 0 | |
| 201 | |
| 202 case alignment | |
| 203 of Left: | |
| 204 cursorPos.x = origin.x | |
| 205 of Center: | |
| 206 cursorPos.x = origin.x + ((maxWidth - lineWidths[lineI]) / aratio * 0.5) | |
| 207 of Right: | |
| 208 cursorPos.x = origin.x + (maxWidth - lineWidths[lineI]) / aratio | |
| 209 | |
| 210 for i in 0 ..< text.len: | |
| 211 if text[i] == Rune('\n'): | |
| 212 inc lineI | |
| 213 case alignment | |
| 214 of Left: | |
| 215 cursorPos.x = origin.x | |
| 216 of Center: | |
| 217 cursorPos.x = origin.x + ((maxWidth - lineWidths[lineI]) / aratio * 0.5) | |
| 218 of Right: | |
| 219 cursorPos.x = origin.x + (maxWidth - lineWidths[lineI]) / aratio | |
| 220 cursorPos.y = cursorPos.y - textbuffer.font.lineAdvance * globalScale | |
| 221 else: | |
| 222 if not text[i].isWhitespace(): | |
| 223 textbuffer.position[textbuffer.cursor] = cursorPos | |
| 224 textbuffer.scale[textbuffer.cursor] = globalScale | |
| 225 textbuffer.color[textbuffer.cursor] = color | |
| 226 if text[i] in textbuffer.font.descriptorGlyphIndex: | |
| 227 textbuffer.glyphIndex[textbuffer.cursor] = | |
| 228 textbuffer.font.descriptorGlyphIndex[text[i]] | |
| 229 else: | |
| 230 textbuffer.glyphIndex[textbuffer.cursor] = | |
| 231 textbuffer.font.descriptorGlyphIndex[textbuffer.font.fallbackCharacter] | |
| 232 inc textbuffer.cursor | |
| 233 | |
| 234 if text[i] in textbuffer.font.advance: | |
| 235 cursorPos.x = | |
| 236 cursorPos.x + textbuffer.font.advance[text[i]] * globalScale / aratio | |
| 237 else: | |
| 238 cursorPos.x = | |
| 239 cursorPos.x + | |
| 240 textbuffer.font.advance[textbuffer.font.fallbackCharacter] * globalScale / | |
| 241 aratio | |
| 242 | |
| 243 if i < text.len - 1: | |
| 244 cursorPos.x = | |
| 245 cursorPos.x + | |
| 246 textbuffer.font.kerning.getOrDefault((text[i], text[i + 1]), 0) * globalScale / | |
| 247 aratio | |
| 248 | 281 |
| 249 proc add*( | 282 proc add*( |
| 250 textbuffer: var TextBuffer, | 283 textbuffer: var TextBuffer, |
| 251 text: string, | 284 text: string, |
| 252 position: Vec3f, | 285 position: Vec3f, |
| 253 alignment: TextAlignment = Left, | 286 alignment: TextAlignment = Left, |
| 254 anchor: Vec2f = vec2(0, 0), | 287 anchor: Vec2f = vec2(0, 0), |
| 255 scale: float32 = 1'f32, | 288 scale: float32 = 1'f32, |
| 256 color: Vec4f = vec4(1, 1, 1, 1), | 289 color: Vec4f = vec4(1, 1, 1, 1), |
| 290 capacity: int = 0, | |
| 291 ): TextHandle = | |
| 292 add(textbuffer, text.toRunes, position, alignment, anchor, scale, color, capacity) | |
| 293 | |
| 294 proc text*(textbuffer: var TextBuffer, textHandle: TextHandle, text: seq[Rune]) = | |
| 295 if text.len <= textbuffer.texts[int(textHandle)].capacity: | |
| 296 textbuffer.texts[int(textHandle)].text = text | |
| 297 else: | |
| 298 textbuffer.texts[int(textHandle)].text = | |
| 299 text[0 ..< textbuffer.texts[int(textHandle)].capacity] | |
| 300 | |
| 301 proc text*(textbuffer: var TextBuffer, textHandle: TextHandle, text: string) = | |
| 302 text(textbuffer, textHandle, text.toRunes) | |
| 303 | |
| 304 proc position*(textbuffer: var TextBuffer, textHandle: TextHandle, position: Vec3f) = | |
| 305 textbuffer.texts[int(textHandle)].position = position | |
| 306 | |
| 307 proc alignment*( | |
| 308 textbuffer: var TextBuffer, textHandle: TextHandle, alignment: TextAlignment | |
| 257 ) = | 309 ) = |
| 258 add(textbuffer, text.toRunes, position, alignment, anchor, scale, color) | 310 textbuffer.texts[int(textHandle)].alignment = alignment |
| 311 | |
| 312 proc anchor*(textbuffer: var TextBuffer, textHandle: TextHandle, anchor: Vec2f) = | |
| 313 textbuffer.texts[int(textHandle)].anchor = anchor | |
| 314 | |
| 315 proc scale*(textbuffer: var TextBuffer, textHandle: TextHandle, scale: float32) = | |
| 316 textbuffer.texts[int(textHandle)].scale = scale | |
| 317 | |
| 318 proc color*(textbuffer: var TextBuffer, textHandle: TextHandle, color: Vec4f) = | |
| 319 textbuffer.texts[int(textHandle)].color = color | |
| 259 | 320 |
| 260 proc reset*(textbuffer: var TextBuffer) = | 321 proc reset*(textbuffer: var TextBuffer) = |
| 261 textbuffer.cursor = 0 | 322 textbuffer.cursor = 0 |
| 262 for i in 0 ..< textbuffer.texts.len: | 323 for i in 0 ..< textbuffer.texts.len: |
| 263 textbuffer.texts[i] = default(Text) | 324 textbuffer.texts[i] = default(Text) |
