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) |