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)