Rendering Engine 0.2.17
Modular Graphics Rendering Engine | v0.2.17
Loading...
Searching...
No Matches
text_block_2d.cpp
Go to the documentation of this file.
1#include "text_block_2d.hpp"
2#include "camera_2d.hpp"
3#include "text_renderer.hpp"
4#include "font_resources.hpp"
5#include "texture_cache.hpp"
6#include "image_data_gpu.hpp"
7#include "image_data.hpp"
8#include "model_cache.hpp"
9#include "scene.hpp"
10
12
13#include "i_renderer.hpp"
15#include "model_cache.hpp"
16#include "material_cache.hpp"
17#include "material.hpp"
18
19#include <hb.h>
20#include <hb-ft.h>
21#include <ft2build.h>
22#include FT_FREETYPE_H
23#include FT_GLYPH_H
24#include FT_TYPES_H
25#include FT_OUTLINE_H
26#include FT_RENDER_H
27#include <cassert>
28
29namespace rendering_engine
30{
31std::string TextBlock2D::sDefaultFontName = "RobotoMono-Regular";
32
33std::uint64_t TextBlock2D::sNumOfTextBlocks = 0;
34
35template<>
36std::unordered_map<std::string, TextBlock2D::Mesh> TextBlock2D::PrepareMeshSlots(const std::vector<std::uint32_t>& glyphs);
37
38template<>
39std::unordered_map<std::string, TextBlock2D::Mesh> TextBlock2D::PrepareMeshSlots(const std::vector<ShapedGlyph>& glyphs);
40
41TextBlock2D::TextBlock2D(Scene& scene, std::shared_ptr<TextRenderer> textRenderer, Properties properties)
42 :
43 Drawable2D(textRenderer->GetRenderResourceContext(), scene),
44 mTextRenderer(textRenderer),
45 mColor(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)),
46 mFontName(properties.fontName),
47 mFontSize(properties.fontSize),
48 mLineSpacingScale(properties.lineSpacingScale),
49 mMaxLineLength(properties.maxLineLength),
50 mTextAlign(properties.textAlign),
51 mDimensions(glm::vec2(0.0f, 0.0f)),
52 bIsTextShapeEnabled(properties.textShapeEnabled),
53 mOutlineThicknessPx(properties.outlineThicknessPx > 2.0f ? 2.0f : properties.outlineThicknessPx)
54{
55 mFontResources = mTextRenderer->GetFontResources(mFontName, mFontSize);
56 if (!mFontResources)
57 {
58 throw std::runtime_error("FontResources is not initialized for this TextBlock2D.");
59 }
60
61 ++sNumOfTextBlocks;
62 mTextBlockID = "TextBlock_" + std::to_string(sNumOfTextBlocks);
63}
64
69
70void TextBlock2D::Update(float deltaTime)
71{
72 Drawable2D::Update(deltaTime);
73}
74
75void TextBlock2D::Draw(const Camera2D& camera)
76{
77 Transformations2D transformations;
78 transformations.model = GetTransform().GetWorldMatrix();
79 transformations.view = camera.GetWorldView();
80 transformations.proj = camera.GetProjectionMatrix();
81
82 for (auto& renderBatch : mRenderBatches)
83 {
84 renderBatch.renderResources->SubmitResources(transformations, renderBatch.materialParameters);
85 }
86}
87
88void TextBlock2D::SetText(std::string text)
89{
90 if (mText == text)
91 {
92 //Text has not changes, no new ensure/mesh-construction needed.
93 return;
94 }
95 mText = text;
96
97
99 {
101 }
102 else
103 {
105 }
107}
108
109void TextBlock2D::SetTextColor(glm::vec4 color)
110{
111 for (auto& renderBatch : mRenderBatches)
112 {
113 renderBatch.materialParameters.SetMaterialVec4("FontColor", color);
114 }
115}
116
117void TextBlock2D::SetOutlineColor(glm::vec4 color)
118{
119 for (auto& renderBatch : mRenderBatches)
120 {
121 renderBatch.materialParameters.SetMaterialVec4("OutlineColor", color);
122 }
123}
124
126{
127 return mDimensions;
128}
129
130void TextBlock2D::SetOutlineThickness(float thicknessPx)
131{
132 for (auto& renderBatch : mRenderBatches)
133 {
134 renderBatch.materialParameters.SetMaterialFloat("OutlineThicknessPx", thicknessPx);
135 }
136}
137
138std::vector<std::uint32_t> TextBlock2D::DecodeUtf8(const std::string& text)
139{
140 std::vector<std::uint32_t> result;
141 result.reserve(text.size()); // worst case: ASCII
142
143 const unsigned char* bytes =
144 reinterpret_cast<const unsigned char*>(text.data());
145 const size_t length = text.size();
146
147 size_t i = 0;
148 while (i < length)
149 {
150 std::uint32_t codePoint = 0;
151 unsigned char c = bytes[i];
152
153 if (c <= 0x7FU)
154 {
155 // 1-byte sequence (ASCII)
156 codePoint = c;
157 i += 1;
158 }
159 else if ((c & 0xE0U) == 0xC0U)
160 {
161 // 2-byte sequence
162 if (i + 1 >= length) break;
163
164 codePoint =
165 ((c & 0x1FU) << 6) |
166 (bytes[i + 1] & 0x3FU);
167
168 i += 2;
169 }
170 else if ((c & 0xF0U) == 0xE0U)
171 {
172 // 3-byte sequence
173 if (i + 2 >= length) break;
174
175 codePoint =
176 ((c & 0x0FU) << 12) |
177 ((bytes[i + 1] & 0x3FU) << 6) |
178 (bytes[i + 2] & 0x3FU);
179
180 i += 3;
181 }
182 else if ((c & 0xF8U) == 0xF0U)
183 {
184 // 4-byte sequence
185 if (i + 3 >= length) break;
186
187 codePoint =
188 ((c & 0x07U) << 18) |
189 ((bytes[i + 1] & 0x3FU) << 12) |
190 ((bytes[i + 2] & 0x3FU) << 6) |
191 (bytes[i + 3] & 0x3FU);
192
193 i += 4;
194 }
195 else
196 {
197 // Invalid UTF-8 start byte
198 ++i;
199 continue;
200 }
201
202 result.push_back(codePoint);
203 }
204
205 return result;
206}
207
208void TextBlock2D::ConstructMeshAutoLinebreak(const std::vector<std::uint32_t>& codePoints)
209{
210 auto meshes = PrepareMeshSlots(codePoints);
211
212 const FontMetrics& fontMetrics = mFontResources->GetFontMetrics();
213 const float lineHeight = static_cast<float>(fontMetrics.lineHeight);
214 // Pen position (baseline)
215 float penX = 0.0f;
216 float penY = 0.0f;
217
218 const std::uint32_t space{ 0x20 };
219 const std::uint32_t newLine{ 0x0A };
220
221 const bool autoTextWrappingRequested = mMaxLineLength > 0.0f;
222 if (!autoTextWrappingRequested)
223 {
224 for (std::uint32_t glyph : codePoints)
225 {
226 if (glyph == newLine)
227 {
228 penX = 0.0f;
229 penY += lineHeight;
230 continue;
231 }
232
233 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(glyph);
234 GlyphQuad glyphQuad = MakeGlyphQuad(glyphIndex, penX, penY);
235 PushQuad(meshes, glyphQuad);
236 penX += static_cast<float>(glyphQuad.advanceX);
237 }
238 }
239 else
240 {
241 bool isStringComplete = false;
242
243 // This variable describe the index we stay until new word is added.
244 std::uint32_t currentIndex = 0;
245 bool isLastGlyphProcessed = false;
246 float lineLength = 0.0f;
247 std::uint32_t nextGlyphIndex = 0;
248
249 std::vector<GlyphQuad> line;
250 std::vector<GlyphQuad> nextWord;
251
252 while (!isStringComplete)
253 {
254 std::string stringProcessed;
255
256 while (nextGlyphIndex < codePoints.size())
257 {
258 if (codePoints[nextGlyphIndex] == space)
259 break;
260
261 const std::string curGlyph = CodepointToUtf8(codePoints[nextGlyphIndex]);
262 stringProcessed.append(curGlyph);
263 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(codePoints[nextGlyphIndex]);
264 GlyphQuad glyphQuad = MakeGlyphQuad(glyphIndex, penX, penY);
265 penX += static_cast<float>(glyphQuad.advanceX);
266 nextWord.push_back(glyphQuad);
267 ++nextGlyphIndex;
268 isLastGlyphProcessed = nextGlyphIndex >= codePoints.size();
269 }
270
271 const bool isNewWordFitLine = penX <= mMaxLineLength;
272
273 if (line.empty() || isNewWordFitLine)
274 {
275 if (!line.empty())
276 {
277 //Insert SPACE
278 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(space);
279 GlyphQuad glyphQuad = MakeGlyphQuad(glyphIndex, penX, penY);
280 stringProcessed.append(" ");
281 penX += static_cast<float>(glyphQuad.advanceX);
282 nextWord.push_back(glyphQuad);
283 ++nextGlyphIndex;
284 }
285 // Add new word to the line
286 std::copy(nextWord.begin(), nextWord.end(), std::back_inserter(line));
287 nextWord.clear();
288 lineLength = penX;
289 currentIndex = nextGlyphIndex;
290 isStringComplete = isLastGlyphProcessed;
291 }
292
293 if (!isNewWordFitLine || isLastGlyphProcessed)
294 {
295 nextWord.clear();
296 // Finalize current line and switch to next. Push quads for all line, setting horizontal alignment
297 for (auto& quad : line)
298 {
299 float horizontalShift = 0.0f;
301 {
302 horizontalShift = (mMaxLineLength - lineLength) / 2.0f;
303 }
305 {
306 horizontalShift = (mMaxLineLength - lineLength);
307 }
308 PushQuad(meshes, quad, horizontalShift);
309 }
310 line.clear();
311
312 penX = 0.0f;
313 penY += lineHeight;
314 nextGlyphIndex = currentIndex;
315 }
316 }
317 }
318
319 UploadMeshes(meshes);
320
321 Initialize();
322}
323
325{
326 std::vector<std::uint32_t> allUsedGlyphs;
327 std::vector<std::vector<std::uint32_t>> linesOfGlyphs;
328
329 auto textRuns = SplitString(mText, "\n");
330
331 for (const auto& textRunString : textRuns)
332 {
333 auto textRunCodePoints = DecodeUtf8(textRunString);
334 linesOfGlyphs.push_back(textRunCodePoints);
335 std::copy(textRunCodePoints.begin(), textRunCodePoints.end(), std::back_inserter(allUsedGlyphs));
336 }
337 // It is better to ensure all required glyphs at once, as those if not ready yet,
338 // will be added to a single font atlas
339 mFontResources->EnsureGlyphs(allUsedGlyphs);
340 auto meshes = PrepareMeshSlots(allUsedGlyphs);
341
342
343 float maximumLineLengh = 0.0f;
344 std::vector<float> lineLengths;
345
346 std::vector<std::vector<GlyphQuad>> linesOfGlyphQuads;
347
348 const FontMetrics& fontMetrics = mFontResources->GetFontMetrics();
349 const float lineHeight = static_cast<float>(fontMetrics.lineHeight);
350
351 float penX = 0.0f;
352 float penY = 0.0f;
353
354 for (auto line : linesOfGlyphs)
355 {
356 penX = 0.0f;
357 std::vector<GlyphQuad> lineOfGlyphQuads;
358 for (const auto& glyph : line)
359 {
360 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(glyph);
361 GlyphQuad glyphQuad = MakeGlyphQuad(glyphIndex, penX, penY);
362
363 penX += static_cast<float>(glyphQuad.advanceX);
364 lineOfGlyphQuads.push_back(glyphQuad);
365 }
366
367 lineLengths.push_back(penX);
368 linesOfGlyphQuads.push_back(lineOfGlyphQuads);
369 const float lineLength = penX;
370
371 penY += (lineHeight * mLineSpacingScale);
372
373 if (lineLength > maximumLineLengh)
374 {
375 maximumLineLengh = lineLength;
376 }
377 }
378
379 mMaxLineLength = (mMaxLineLength > maximumLineLengh ? mMaxLineLength : maximumLineLengh);
380 mDimensions = glm::vec2(mMaxLineLength, penY);
381
382 size_t curLine = 0;
383 for (const auto& line : linesOfGlyphQuads)
384 {
385 for (const auto& glyphQuad : line)
386 {
387 float horizontalShift = 0.0f;
389 {
390 horizontalShift = (mMaxLineLength - lineLengths[curLine]) / 2.0f;
391 }
393 {
394 horizontalShift = (mMaxLineLength - lineLengths[curLine]);
395 }
396 PushQuad(meshes, glyphQuad, horizontalShift);
397 }
398 ++curLine;
399 }
400
401 UploadMeshes(meshes);
402
403 Initialize();
404}
405
407{
408 auto textRuns = SplitString(mText, "\n");
409
410 std::vector<std::vector<ShapedGlyph>> linesOfShapedGlyphs;
411 std::vector<ShapedGlyph> preShapedGlyphs;
412
413 for (const auto& textRun : textRuns)
414 {
415 auto shapedTextRun = ShapeText(textRun);
416 linesOfShapedGlyphs.push_back(shapedTextRun);
417 std::copy(shapedTextRun.begin(), shapedTextRun.end(), std::back_inserter(preShapedGlyphs));
418 }
419 std::vector<GlyphIndex> ensureGlyphs;
420 for (const auto& shapedGlyph : preShapedGlyphs)
421 {
422 GlyphIndex gi;
423 gi.index = shapedGlyph.glyphIndex;
424 ensureGlyphs.push_back(gi);
425 }
426 mFontResources->EnsureGlyphs(ensureGlyphs);
427
428 auto meshes = PrepareMeshSlots(preShapedGlyphs);
429
430 // Pen position (baseline)
431 float penX = 0.0f;
432 float penY = 0.0f;
433
434 float maximumLineLengh = 0.0f;
435 std::vector<float> lineLengths;
436
437 for (auto& line : linesOfShapedGlyphs)
438 {
439 float lineLength = 0.0f;
440 for (const auto& glyph : line)
441 {
442 lineLength += glyph.xAdvance;
443 }
444 lineLengths.push_back(lineLength);
445 if (lineLength > maximumLineLengh)
446 {
447 maximumLineLengh = lineLength;
448 }
449 }
450 mMaxLineLength = (mMaxLineLength > maximumLineLengh ? mMaxLineLength : maximumLineLengh);
451
452 const FontMetrics& fontMetrics = mFontResources->GetFontMetrics();
453 const float lineHeight = static_cast<float>(fontMetrics.lineHeight);
454 std::size_t curLine = 0U;
455 for (auto& line : linesOfShapedGlyphs)
456 {
457 for (const auto& glyph : line)
458 {
459 GlyphIndex glyphIndex;
460 glyphIndex.index = glyph.glyphIndex;
461
462 GlyphQuad glyphQuad = MakeGlyphQuad(glyphIndex, penX + glyph.xOffset, penY + glyph.yOffset);
463
464 float horizontalShift = 0.0f;
466 {
467 horizontalShift = (mMaxLineLength - lineLengths[curLine]) / 2.0f;
468 }
470 {
471 horizontalShift = (mMaxLineLength - lineLengths[curLine]);
472 }
473 PushQuad(meshes, glyphQuad, horizontalShift);
474 penX += glyph.xAdvance;
475 }
476
477 penX = 0.0f;
478 penY += (lineHeight * mLineSpacingScale);
479 ++curLine;
480 }
481
482 const float lineCount =
483 static_cast<float>(linesOfShapedGlyphs.size());
484
485 mDimensions = glm::vec2(mMaxLineLength, lineCount * lineHeight * mLineSpacingScale);
486
487 UploadMeshes(meshes);
488
489 Initialize();
490}
491
493{
494 GlyphQuad result;
495
496 const GlyphMetrics& glyphMetrics = mFontResources->GetGlyphMetrics(glyphIndex);
497 auto fontAtlasMaterialName = mFontResources->GetFontAtlasMaterialName(glyphIndex);
498 const auto fontAtlasTextureName = mFontResources->GetFontAtlasTextureName(glyphIndex);
499 auto textureCache = mTextRenderer->GetRenderResourceContext().textureCache;
500 const auto& fontAtlas = textureCache->GetTextureResources(fontAtlasTextureName);
501
502 result.fontAtlasMaterialName = fontAtlasMaterialName;
503
504 const float bearingX = static_cast<float>(glyphMetrics.bearingX);
505 const float bearingY = static_cast<float>(glyphMetrics.bearingY);
506 const float glyphWidth = static_cast<float>(glyphMetrics.width);
507 const float glyphHeight = static_cast<float>(glyphMetrics.height);
508
509 // Positions
510 const float x0 = penX + bearingX;
511 const float y0 = penY - bearingY; // y0 - top
512 const float y1 = y0 + glyphHeight; // y1 - bottom
513 const float x1 = x0 + glyphWidth;
514
515 result.x0 = x0 - mOutlineThicknessPx * 2;
516 result.y0 = y0 - mOutlineThicknessPx * 2;
517 result.x1 = x1 + mOutlineThicknessPx * 2;
518 result.y1 = y1 + mOutlineThicknessPx * 2;
519
520 // UVs
521 const float atlasWidth =
522 static_cast<float>(fontAtlas->GetCpuImageData().GetWidth());
523 const float atlasHeight =
524 static_cast<float>(fontAtlas->GetCpuImageData().GetHeight());
525
526 const float atlasX = static_cast<float>(glyphMetrics.atlasX);
527 const float atlasY = static_cast<float>(glyphMetrics.atlasY);
528
529 result.u0 = (atlasX - mOutlineThicknessPx * 2) / atlasWidth;
530 result.v0 = (atlasY - mOutlineThicknessPx * 2) / atlasHeight;
531 result.u1 = (atlasX + glyphWidth + mOutlineThicknessPx * 2) / atlasWidth;
532 result.v1 = (atlasY + glyphHeight + mOutlineThicknessPx * 2) / atlasHeight;
533
534 result.advanceX = glyphMetrics.advanceX;
535
536 return result;
537}
538
539void TextBlock2D::PushQuad(std::unordered_map<std::string, TextBlock2D::Mesh>& meshes, GlyphQuad glyphQuad, float horizontalShift)
540{
541 auto& mesh = meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]);
542
543 if (mesh.positions2D.size() >
544 static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max()) - 3U)
545 {
546 throw std::overflow_error("TextBlock2D mesh exceeds 32-bit index range.");
547 }
548
549 const std::uint32_t vertexBase = static_cast<std::uint32_t>(mesh.positions2D.size());
550
551 const glm::vec2 shift(horizontalShift, 0.0f);
552
553 const glm::vec2 vert_0 = glm::vec2(glyphQuad.x0, glyphQuad.y0) + shift;
554 const glm::vec2 vert_1 = glm::vec2(glyphQuad.x1, glyphQuad.y0) + shift;
555 const glm::vec2 vert_2 = glm::vec2(glyphQuad.x1, glyphQuad.y1) + shift;
556 const glm::vec2 vert_3 = glm::vec2(glyphQuad.x0, glyphQuad.y1) + shift;
557
558 mesh.positions2D.push_back(vert_0);
559 mesh.positions2D.push_back(vert_1);
560 mesh.positions2D.push_back(vert_2);
561 mesh.positions2D.push_back(vert_3);
562
563 // UVs
564 mesh.texCoords.push_back(glm::vec2(glyphQuad.u0, glyphQuad.v0));
565 mesh.texCoords.push_back(glm::vec2(glyphQuad.u1, glyphQuad.v0));
566 mesh.texCoords.push_back(glm::vec2(glyphQuad.u1, glyphQuad.v1));
567 mesh.texCoords.push_back(glm::vec2(glyphQuad.u0, glyphQuad.v1));
568
569 // Colors
570 mesh.colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
571 mesh.colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
572 mesh.colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
573 mesh.colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
574
575 // Indices
576 mesh.indices.push_back(vertexBase + 0U);
577 mesh.indices.push_back(vertexBase + 1U);
578 mesh.indices.push_back(vertexBase + 2U);
579
580 mesh.indices.push_back(vertexBase + 2U);
581 mesh.indices.push_back(vertexBase + 3U);
582 mesh.indices.push_back(vertexBase + 0U);
583}
584
585void TextBlock2D::UploadMeshes(const std::unordered_map<std::string, TextBlock2D::Mesh>& meshes)
586{
587 // Once meshes are ready, request its upload from ModelCache
588 auto modelCache = mTextRenderer->GetRenderResourceContext().meshCache;
589
590 for (auto& material : mMaterialMesh)
591 {
592 auto meshName = material.second;
593 auto mesh = meshes.at(meshName);
594 modelCache->LoadCustomMesh(meshName,
595 mesh.positions2D,
596 mesh.texCoords,
597 mesh.colors,
598 mesh.indices);
599 }
600
601 // Set mesh name and material name for THIS drawable, so the first font atlas for now only.
602
603 Shutdown();
604 for (const auto& materialMesh : mMaterialMesh)
605 {
606 const std::string meshName = materialMesh.second;
607 const std::string materialName = materialMesh.first;
608 AddRenderBatch(meshName, materialName);
609 }
610}
611
612std::vector<std::string> TextBlock2D::SplitString(const std::string& text, std::string separator)
613{
614 std::vector<std::string> result;
615 std::string_view sv(text);
616 size_t start = 0, end = 0;
617
618 while (true) {
619 end = sv.find(separator, start);
620 // Extract token (including empty ones)
621 result.emplace_back(sv.substr(start, end - start));
622 if (end == std::string_view::npos) break;
623 start = end + 1; // Move past the delimiter
624 }
625
626 return result;
627}
628
629bool TextBlock2D::IsTextShapingRequired(std::uint32_t codePoint) const
630{
631 auto scriptsRequiredShaping = mTextRenderer->GetScriptsRequiredShaping();
632 for (const auto& script : scriptsRequiredShaping)
633 {
634 auto range = mTextRenderer->GetScriptRange(script);
635 if (codePoint >= range.first && codePoint <= range.second)
636 {
637 return true;
638 }
639 }
640
641 return false;
642}
643
644std::string TextBlock2D::CodepointToUtf8(std::uint32_t codePoint)
645{
646 std::string out;
647
648 if (codePoint <= 0x7F) {
649 out.push_back(static_cast<char>(codePoint));
650 }
651 else if (codePoint <= 0x7FF) {
652 out.push_back(static_cast<char>(0xC0 | ((codePoint >> 6) & 0x1F)));
653 out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
654 }
655 else if (codePoint <= 0xFFFF) {
656 out.push_back(static_cast<char>(0xE0 | ((codePoint >> 12) & 0x0F)));
657 out.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));
658 out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
659 }
660 else if (codePoint <= 0x10FFFF) {
661 out.push_back(static_cast<char>(0xF0 | ((codePoint >> 18) & 0x07)));
662 out.push_back(static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F)));
663 out.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));
664 out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
665 }
666
667 return out;
668}
669
670std::vector<TextBlock2D::ShapedGlyph> TextBlock2D::ShapeText(const std::string& text)
671{
672 std::vector<TextBlock2D::ShapedGlyph> result;
673
674 hb_buffer_t* buf;
675 buf = hb_buffer_create();
676 hb_buffer_add_utf8(buf, text.c_str(), -1, 0, -1);
677
678 hb_buffer_guess_segment_properties(buf);
679
680 auto* fontFace = mFontResources->GetFontFace();
681 hb_font_t* font = hb_ft_font_create_referenced(fontFace);
682
683 hb_shape(font, buf, NULL, 0);
684
685 unsigned int glyph_count;
686 hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
687 hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
688
689 for (unsigned int i = 0; i < glyph_count; i++)
690 {
691 ShapedGlyph shapedGlyph;
692 shapedGlyph.glyphIndex = glyph_info[i].codepoint;
693 shapedGlyph.xOffset = static_cast<float>(glyph_pos[i].x_offset) / 64.0f;
694 shapedGlyph.yOffset = static_cast<float>(glyph_pos[i].y_offset) / 64.0f;
695 shapedGlyph.xAdvance =static_cast<float>(glyph_pos[i].x_advance) / 64.0f;
696 shapedGlyph.yAdvance = static_cast<float>(glyph_pos[i].y_advance) / 64.0f;
697 shapedGlyph.cluster = glyph_info[i].cluster;
698 result.push_back(shapedGlyph);
699 }
700
701 hb_font_destroy(font);
702 hb_buffer_destroy(buf);
703
704 return result;
705}
706
707template<>
708std::unordered_map<std::string, TextBlock2D::Mesh> TextBlock2D::PrepareMeshSlots(const std::vector<std::uint32_t>& glyphs)
709{
710 mMaterialMesh.clear();
711 // Prepare map of used material names with corresponding mesh names.
712 for (auto codePoint : glyphs)
713 {
714 if (codePoint == 0x000A || codePoint == 0x000D || codePoint == 0x0009)
715 {
716 continue;
717 }
718
719 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(codePoint);
720 auto fontAtlasMaterialName = mFontResources->GetFontAtlasMaterialName(glyphIndex);
721 const auto search = mMaterialMesh.find(fontAtlasMaterialName);
722 if (search == mMaterialMesh.end())
723 {
724 const std::string meshName = mTextBlockID + "_" + std::to_string(mMaterialMesh.size());
725 mMaterialMesh[fontAtlasMaterialName] = meshName;
726 }
727 }
728
729 // Prepare map of used mesh names with mesh structures
730 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
731 for (const auto& mesh : mMaterialMesh)
732 {
733 meshes[mesh.second] = TextBlock2D::Mesh();
734 }
735 return meshes;
736}
737
738template<>
739std::unordered_map<std::string, TextBlock2D::Mesh> TextBlock2D::PrepareMeshSlots(const std::vector<ShapedGlyph>& glyphs)
740{
741 mMaterialMesh.clear();
742 // Prepare map of used material names with corresponding mesh names.
743 for (const auto& glyph : glyphs)
744 {
745 const std::uint32_t linefeed = 0x000A;
746 const std::uint32_t carriageReturn = 0x000D;
747 const std::uint32_t tab = 0x0009;
748 if (glyph.glyphIndex == linefeed || glyph.glyphIndex == carriageReturn || glyph.glyphIndex == tab)
749 {
750 continue;
751 }
752
753 GlyphIndex glyphIndex;
754 glyphIndex.index = glyph.glyphIndex;
755 auto fontAtlasMaterialName = mFontResources->GetFontAtlasMaterialName(glyphIndex);
756 const auto search = mMaterialMesh.find(fontAtlasMaterialName);
757 if (search == mMaterialMesh.end())
758 {
759 const std::string meshName = mTextBlockID + "_" + std::to_string(mMaterialMesh.size());
760 mMaterialMesh[fontAtlasMaterialName] = meshName;
761 }
762 }
763
764 // Prepare map of used mesh names with mesh structures
765 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
766 for (const auto& mesh : mMaterialMesh)
767 {
768 meshes[mesh.second] = TextBlock2D::Mesh();
769 }
770
771 return meshes;
772}
773
774} // namespace rendering_engine
Represents a 2D camera with position, rotation, and zoom control.
Definition camera_2d.hpp:36
glm::mat4 GetProjectionMatrix() const
Returns the current orthographic projection matrix.
Definition camera_2d.cpp:30
const glm::mat4 & GetWorldView() const
Gets the world-view (model) matrix for the camera.
Definition camera_2d.cpp:88
2D drawable component for rendering objects in 2D space.
SceneComponent2D & GetTransform()
Access to the underlying SceneComponent2D (transform).
void Update(float deltaTime) override
Updates model matrix (and any other logic).
void Initialize() override
Initializes render resources.
std::vector< RenderBatch > mRenderBatches
virtual void Shutdown()
Releases all render resources owned by this drawable.
void AddRenderBatch(std::string meshName, std::string materialName)
const glm::mat4 & GetWorldMatrix()
Returns the world transformation matrix.
Base class representing a renderable scene.
Definition scene.hpp:44
TextBlock2D(Scene &scene, std::shared_ptr< TextRenderer > textRenderer, Properties properties=Properties())
Constructs a 2D text block.
std::vector< std::string > SplitString(const std::string &text, std::string separator)
Splits a string by a separator.
std::vector< ShapedGlyph > ShapeText(const std::string &text)
Shapes text and returns shaped glyphs.
static std::string sDefaultFontName
void SetOutlineThickness(float thicknessPx)
Sets outline thickness for all render batches.
virtual void SetText(std::string text)
Sets the displayed text.
std::unordered_map< std::string, std::string > mMaterialMesh
void ShapeTextAndConstructMesh()
Shapes text and constructs glyph geometry.
void ConstructMeshAutoLinebreak(const std::vector< std::uint32_t > &codePoints)
std::vector< std::uint32_t > DecodeUtf8(const std::string &text)
Decodes a UTF-8 string into Unicode code points.
void SetTextColor(glm::vec4 color)
Sets the text color.
void PushQuad(std::unordered_map< std::string, TextBlock2D::Mesh > &meshes, GlyphQuad glyphQuad, float horizontalShift=0.0f)
Appends a glyph quad to a mesh.
void UploadMeshes(const std::unordered_map< std::string, TextBlock2D::Mesh > &meshes)
Uploads prepared meshes to GPU resources.
void Initialize() override
Initializes render resource pointers (material, mesh, etc.). Must be called after setting material an...
std::shared_ptr< FontResources > mFontResources
glm::vec2 GetDimensions() const
Returns text block dimensions.
const std::shared_ptr< TextRenderer > mTextRenderer
void ConstructMesh()
Constructs glyph geometry without shaping.
bool IsTextShapingRequired(std::uint32_t codePoint) const
Checks whether shaping is required for a code point.
std::string CodepointToUtf8(std::uint32_t codePoint)
Converts a Unicode code point to UTF-8.
GlyphQuad MakeGlyphQuad(GlyphIndex glyphIndext, float penX, float penY)
Creates a glyph quad at the current pen position.
void Update(float deltaTime) override
Updates logic (animation, movement, etc.) for this drawable.
void Draw(const Camera2D &camera) override
Submits this quad to the renderer for drawing.
void SetOutlineColor(glm::vec4 color)
Sets the outline color.
std::unordered_map< std::string, TextBlock2D::Mesh > PrepareMeshSlots(const std::vector< T > &glyphs)
Prepares mesh slots based on glyph usage.
Global metrics describing font vertical layout.
Opaque glyph identifier within a font.
Metrics describing a single glyph and its atlas placement.
Renderable quad representing a single glyph.
CPU-side mesh data for glyph quads.
Result of text shaping for a single glyph.
Contains transformation matrices for 2D rendering.
glm::mat4 view
View (camera) transformation matrix.
glm::mat4 proj
Projection transformation matrix.
glm::mat4 model
Model transformation matrix.