Rendering Engine 0.2.9
Modular Graphics Rendering Engine | v0.2.9
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 bIsTextShapeEnabled(properties.textShapeEnabled),
46 mFontName(properties.fontName),
47 mFontSize(properties.fontSize),
48 mLineSpacingScale(properties.lineSpacingScale),
49 mTextAlign(properties.textAlign),
50 mMaxLineLength(properties.maxLineLength),
51 mOutlineThicknessPx(properties.outlineThicknessPx > 2 ? 2 : properties.outlineThicknessPx),
52 mColor(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)),
53 mDimensions(glm::vec2(0.0f, 0.0f))
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
66{
68}
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 <= 0x7F)
154 {
155 // 1-byte sequence (ASCII)
156 codePoint = c;
157 i += 1;
158 }
159 else if ((c & 0xE0) == 0xC0)
160 {
161 // 2-byte sequence
162 if (i + 1 >= length) break;
163
164 codePoint =
165 ((c & 0x1F) << 6) |
166 (bytes[i + 1] & 0x3F);
167
168 i += 2;
169 }
170 else if ((c & 0xF0) == 0xE0)
171 {
172 // 3-byte sequence
173 if (i + 2 >= length) break;
174
175 codePoint =
176 ((c & 0x0F) << 12) |
177 ((bytes[i + 1] & 0x3F) << 6) |
178 (bytes[i + 2] & 0x3F);
179
180 i += 3;
181 }
182 else if ((c & 0xF8) == 0xF0)
183 {
184 // 4-byte sequence
185 if (i + 3 >= length) break;
186
187 codePoint =
188 ((c & 0x07) << 18) |
189 ((bytes[i + 1] & 0x3F) << 12) |
190 ((bytes[i + 2] & 0x3F) << 6) |
191 (bytes[i + 3] & 0x3F);
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 // Pen position (baseline)
214 float penX = 0.0f;
215 float penY = 0.0f;
216
217 const std::uint32_t space{ 0x20 };
218 const std::uint32_t newLine{ 0x0A };
219
220 const bool autoTextWrappingRequested = mMaxLineLength > 0.0f;
221 if (!autoTextWrappingRequested)
222 {
223 for (std::uint32_t glyph : codePoints)
224 {
225 if (glyph == newLine)
226 {
227 penX = 0.0f;
228 penY += fontMetrics.lineHeight;
229 continue;
230 }
231
232 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(glyph);
233 GlyphQuad glyphQuad = MakeGlyphQuad(glyphIndex, penX, penY);
234 const std::string meshName = mMaterialMesh[glyphQuad.fontAtlasMaterialName];
235 PushQuad(meshName, meshes, glyphQuad);
236 penX += 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 += 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 += 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 const std::string meshName = mMaterialMesh[quad.fontAtlasMaterialName];
309 PushQuad(meshName, meshes, quad, horizontalShift);
310 }
311 line.clear();
312
313 penX = 0.0f;
314 penY += fontMetrics.lineHeight;
315 nextGlyphIndex = currentIndex;
316 }
317 }
318 }
319
320 UploadMeshes(meshes);
321
322 Initialize();
323}
324
326{
327 std::vector<std::uint32_t> allUsedGlyphs;
328 std::vector<std::vector<std::uint32_t>> linesOfGlyphs;
329
330 auto textRuns = SplitString(mText, "\n");
331
332 for (const auto& textRunString : textRuns)
333 {
334 auto textRunCodePoints = DecodeUtf8(textRunString);
335 linesOfGlyphs.push_back(textRunCodePoints);
336 std::copy(textRunCodePoints.begin(), textRunCodePoints.end(), std::back_inserter(allUsedGlyphs));
337 }
338 // It is better to ensure all required glyphs at once, as those if not ready yet,
339 // will be added to a single font atlas
340 mFontResources->EnsureGlyphs(allUsedGlyphs);
341 auto meshes = PrepareMeshSlots(allUsedGlyphs);
342
343
344 float maximumLineLengh = 0.0f;
345 std::vector<float> lineLengths;
346
347 std::vector<std::vector<GlyphQuad>> linesOfGlyphQuads;
348
349 const FontMetrics& fontMetrics = mFontResources->GetFontMetrics();
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 += 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 += (fontMetrics.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 const std::string meshName = mMaterialMesh[glyphQuad.fontAtlasMaterialName];
388 float horizontalShift = 0.0f;
390 {
391 horizontalShift = (mMaxLineLength - lineLengths[curLine]) / 2.0f;
392 }
394 {
395 horizontalShift = (mMaxLineLength - lineLengths[curLine]);
396 }
397 PushQuad(meshName, meshes, glyphQuad, horizontalShift);
398 }
399 ++curLine;
400 }
401
402 UploadMeshes(meshes);
403
404 Initialize();
405}
406
408{
409 auto textRuns = SplitString(mText, "\n");
410
411 std::vector<std::vector<ShapedGlyph>> linesOfShapedGlyphs;
412 std::vector<ShapedGlyph> preShapedGlyphs;
413
414 for (const auto& textRun : textRuns)
415 {
416 auto shapedTextRun = ShapeText(textRun);
417 linesOfShapedGlyphs.push_back(shapedTextRun);
418 std::copy(shapedTextRun.begin(), shapedTextRun.end(), std::back_inserter(preShapedGlyphs));
419 }
420 std::vector<GlyphIndex> ensureGlyphs;
421 for (const auto& shapedGlyph : preShapedGlyphs)
422 {
423 GlyphIndex gi;
424 gi.index = shapedGlyph.glyphIndex;
425 ensureGlyphs.push_back(gi);
426 }
427 mFontResources->EnsureGlyphs(ensureGlyphs);
428
429 auto meshes = PrepareMeshSlots(preShapedGlyphs);
430
431 // Pen position (baseline)
432 float penX = 0.0f;
433 float penY = 0.0f;
434
435 float maximumLineLengh = 0.0f;
436 std::vector<float> lineLengths;
437
438 for (auto& line : linesOfShapedGlyphs)
439 {
440 float lineLength = 0.0f;
441 for (const auto& glyph : line)
442 {
443 lineLength += glyph.xAdvance;
444 }
445 lineLengths.push_back(lineLength);
446 if (lineLength > maximumLineLengh)
447 {
448 maximumLineLengh = lineLength;
449 }
450 }
451 mMaxLineLength = (mMaxLineLength > maximumLineLengh ? mMaxLineLength : maximumLineLengh);
452
453 const FontMetrics& fontMetrics = mFontResources->GetFontMetrics();
454 int curLine = 0;
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 const std::string meshName = mMaterialMesh[glyphQuad.fontAtlasMaterialName];
464
465 float horizontalShift = 0.0f;
467 {
468 horizontalShift = (mMaxLineLength - lineLengths[curLine]) / 2.0f;
469 }
471 {
472 horizontalShift = (mMaxLineLength - lineLengths[curLine]);
473 }
474 PushQuad(meshName, meshes, glyphQuad, horizontalShift);
475 penX += glyph.xAdvance;
476 }
477
478 penX = 0.0f;
479 penY += (fontMetrics.lineHeight * mLineSpacingScale);
480 ++curLine;
481 }
482
483 mDimensions = glm::vec2(mMaxLineLength, linesOfShapedGlyphs.size() * fontMetrics.lineHeight * mLineSpacingScale);
484
485 UploadMeshes(meshes);
486
487 Initialize();
488}
489
491{
492 GlyphQuad result;
493
494 const GlyphMetrics& glyphMetrics = mFontResources->GetGlyphMetrics(glyphIndex);
495 auto fontAtlasMaterialName = mFontResources->GetFontAtlasMaterialName(glyphIndex);
496 const auto fontAtlasTextureName = mFontResources->GetFontAtlasTextureName(glyphIndex);
497 auto textureCache = mTextRenderer->GetRenderResourceContext().textureCache;
498 const auto& fontAtlas = textureCache->GetTextureResources(fontAtlasTextureName);
499
500 result.fontAtlasMaterialName = fontAtlasMaterialName;
501
502 // Positions
503 const float x0 = penX + glyphMetrics.bearingX;
504 const float y0 = penY - glyphMetrics.bearingY; // y0 - top
505 const float y1 = y0 + glyphMetrics.height; // y1 - bottom
506 const float x1 = x0 + glyphMetrics.width;
507
508 result.x0 = x0 - mOutlineThicknessPx * 2;
509 result.y0 = y0 - mOutlineThicknessPx * 2;
510 result.x1 = x1 + mOutlineThicknessPx * 2;
511 result.y1 = y1 + mOutlineThicknessPx * 2;
512
513 // UVs
514 const auto atlasWidth = static_cast<float>(fontAtlas->GetCpuImageData().GetWidth());
515 const auto atlasHeight = static_cast<float>(fontAtlas->GetCpuImageData().GetHeight());
516
517 const float u0 = static_cast<float>(glyphMetrics.atlasX - mOutlineThicknessPx * 2) / atlasWidth;
518 const float v0 = static_cast<float>(glyphMetrics.atlasY - mOutlineThicknessPx * 2) / atlasHeight;
519 const float u1 = static_cast<float>(glyphMetrics.atlasX + glyphMetrics.width + mOutlineThicknessPx * 2) / atlasWidth;
520 const float v1 = static_cast<float>(glyphMetrics.atlasY + glyphMetrics.height + mOutlineThicknessPx * 2) / atlasHeight;
521
522 result.u0 = u0;
523 result.v0 = v0;
524 result.u1 = u1;
525 result.v1 = v1;
526
527 result.advanceX = glyphMetrics.advanceX;
528
529 return result;
530}
531
532void TextBlock2D::PushQuad(std::string meshName, std::unordered_map<std::string, TextBlock2D::Mesh>& meshes, GlyphQuad glyphQuad, float horizontalShift)
533{
534 const uint32_t vertexBase = meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).positions2D.size();
535
536 const glm::vec2 shift(horizontalShift, 0.0f);
537
538 const glm::vec2 vert_0 = glm::vec2(glyphQuad.x0, glyphQuad.y0) + shift;
539 const glm::vec2 vert_1 = glm::vec2(glyphQuad.x1, glyphQuad.y0) + shift;
540 const glm::vec2 vert_2 = glm::vec2(glyphQuad.x1, glyphQuad.y1) + shift;
541 const glm::vec2 vert_3 = glm::vec2(glyphQuad.x0, glyphQuad.y1) + shift;
542
543 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).positions2D.push_back(vert_0);
544 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).positions2D.push_back(vert_1);
545 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).positions2D.push_back(vert_2);
546 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).positions2D.push_back(vert_3);
547
548 // UVs
549 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).texCoords.push_back(glm::vec2(glyphQuad.u0, glyphQuad.v0));
550 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).texCoords.push_back(glm::vec2(glyphQuad.u1, glyphQuad.v0));
551 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).texCoords.push_back(glm::vec2(glyphQuad.u1, glyphQuad.v1));
552 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).texCoords.push_back(glm::vec2(glyphQuad.u0, glyphQuad.v1));
553
554 // Colors
555 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
556 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
557 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
558 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).colors.push_back(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
559
560 // Indices
561 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).indices.push_back(vertexBase + 0);
562 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).indices.push_back(vertexBase + 1);
563 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).indices.push_back(vertexBase + 2);
564
565 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).indices.push_back(vertexBase + 2);
566 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).indices.push_back(vertexBase + 3);
567 meshes.at(mMaterialMesh[glyphQuad.fontAtlasMaterialName]).indices.push_back(vertexBase + 0);
568}
569
570void TextBlock2D::UploadMeshes(const std::unordered_map<std::string, TextBlock2D::Mesh>& meshes)
571{
572 // Once meshes are ready, request its upload from ModelCache
573 auto modelCache = mTextRenderer->GetRenderResourceContext().meshCache;
574
575 for (auto& material : mMaterialMesh)
576 {
577 auto meshName = material.second;
578 auto mesh = meshes.at(meshName);
579 modelCache->LoadCustomMesh(meshName,
580 mesh.positions2D,
581 mesh.texCoords,
582 mesh.colors,
583 mesh.indices);
584 }
585
586 // Set mesh name and material name for THIS drawable, so the first font atlas for now only.
587
588 Shutdown();
589 for (const auto& materialMesh : mMaterialMesh)
590 {
591 const std::string meshName = materialMesh.second;
592 const std::string materialName = materialMesh.first;
593 AddRenderBatch(meshName, materialName);
594 }
595}
596
597std::vector<std::string> TextBlock2D::SplitString(const std::string& text, std::string separator)
598{
599 std::vector<std::string> result;
600 std::string_view sv(text);
601 size_t start = 0, end = 0;
602
603 while (true) {
604 end = sv.find(separator, start);
605 // Extract token (including empty ones)
606 result.emplace_back(sv.substr(start, end - start));
607 if (end == std::string_view::npos) break;
608 start = end + 1; // Move past the delimiter
609 }
610
611 return result;
612}
613
614bool TextBlock2D::IsTextShapingRequired(std::uint32_t codePoint) const
615{
616 auto scriptsRequiredShaping = mTextRenderer->GetScriptsRequiredShaping();
617 for (const auto& script : scriptsRequiredShaping)
618 {
619 auto range = mTextRenderer->GetScriptRange(script);
620 if (codePoint >= range.first && codePoint <= range.second)
621 {
622 return true;
623 }
624 }
625
626 return false;
627}
628
629std::string TextBlock2D::CodepointToUtf8(std::uint32_t codePoint)
630{
631 std::string out;
632
633 if (codePoint <= 0x7F) {
634 out.push_back(static_cast<char>(codePoint));
635 }
636 else if (codePoint <= 0x7FF) {
637 out.push_back(static_cast<char>(0xC0 | ((codePoint >> 6) & 0x1F)));
638 out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
639 }
640 else if (codePoint <= 0xFFFF) {
641 out.push_back(static_cast<char>(0xE0 | ((codePoint >> 12) & 0x0F)));
642 out.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));
643 out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
644 }
645 else if (codePoint <= 0x10FFFF) {
646 out.push_back(static_cast<char>(0xF0 | ((codePoint >> 18) & 0x07)));
647 out.push_back(static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F)));
648 out.push_back(static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F)));
649 out.push_back(static_cast<char>(0x80 | (codePoint & 0x3F)));
650 }
651
652 return out;
653}
654
655std::vector<TextBlock2D::ShapedGlyph> TextBlock2D::ShapeText(const std::string& text)
656{
657 std::vector<TextBlock2D::ShapedGlyph> result;
658
659 hb_buffer_t* buf;
660 buf = hb_buffer_create();
661 hb_buffer_add_utf8(buf, text.c_str(), -1, 0, -1);
662
663 hb_buffer_guess_segment_properties(buf);
664
665 auto* fontFace = mFontResources->GetFontFace();
666 hb_font_t* font = hb_ft_font_create_referenced(fontFace);
667
668 hb_shape(font, buf, NULL, 0);
669
670 unsigned int glyph_count;
671 hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
672 hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
673
674 for (unsigned int i = 0; i < glyph_count; i++)
675 {
676 ShapedGlyph shapedGlyph;
677 shapedGlyph.glyphIndex = glyph_info[i].codepoint;
678 shapedGlyph.xOffset = glyph_pos[i].x_offset / 64.0f;
679 shapedGlyph.yOffset = glyph_pos[i].y_offset / 64.0f;
680 shapedGlyph.xAdvance = glyph_pos[i].x_advance / 64.0f;
681 shapedGlyph.yAdvance = glyph_pos[i].y_advance / 64.0f;
682 shapedGlyph.cluster = glyph_info[i].cluster;
683 result.push_back(shapedGlyph);
684 }
685
686 hb_font_destroy(font);
687 hb_buffer_destroy(buf);
688
689 return result;
690}
691
692template<>
693std::unordered_map<std::string, TextBlock2D::Mesh> TextBlock2D::PrepareMeshSlots(const std::vector<std::uint32_t>& glyphs)
694{
695 mMaterialMesh.clear();
696 // Prepare map of used material names with corresponding mesh names.
697 for (auto codePoint : glyphs)
698 {
699 if (codePoint == 0x000A || codePoint == 0x000D || codePoint == 0x0009)
700 {
701 continue;
702 }
703
704 GlyphIndex glyphIndex = mFontResources->GetIndexFromCodePoint(codePoint);
705 auto fontAtlasMaterialName = mFontResources->GetFontAtlasMaterialName(glyphIndex);
706 const auto search = mMaterialMesh.find(fontAtlasMaterialName);
707 if (search == mMaterialMesh.end())
708 {
709 const std::string meshName = mTextBlockID + "_" + std::to_string(mMaterialMesh.size());
710 mMaterialMesh[fontAtlasMaterialName] = meshName;
711 }
712 }
713
714 // Prepare map of used mesh names with mesh structures
715 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
716 for (const auto& mesh : mMaterialMesh)
717 {
718 meshes[mesh.second] = TextBlock2D::Mesh();
719 }
720 return meshes;
721}
722
723template<>
724std::unordered_map<std::string, TextBlock2D::Mesh> TextBlock2D::PrepareMeshSlots(const std::vector<ShapedGlyph>& glyphs)
725{
726 mMaterialMesh.clear();
727 // Prepare map of used material names with corresponding mesh names.
728 for (const auto& glyph : glyphs)
729 {
730 const std::uint32_t linefeed = 0x000A;
731 const std::uint32_t carriageReturn = 0x000D;
732 const std::uint32_t tab = 0x0009;
733 if (glyph.glyphIndex == linefeed || glyph.glyphIndex == carriageReturn || glyph.glyphIndex == tab)
734 {
735 continue;
736 }
737
738 GlyphIndex glyphIndex;
739 glyphIndex.index = glyph.glyphIndex;
740 auto fontAtlasMaterialName = mFontResources->GetFontAtlasMaterialName(glyphIndex);
741 const auto search = mMaterialMesh.find(fontAtlasMaterialName);
742 if (search == mMaterialMesh.end())
743 {
744 const std::string meshName = mTextBlockID + "_" + std::to_string(mMaterialMesh.size());
745 mMaterialMesh[fontAtlasMaterialName] = meshName;
746 }
747 }
748
749 // Prepare map of used mesh names with mesh structures
750 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
751 for (const auto& mesh : mMaterialMesh)
752 {
753 meshes[mesh.second] = TextBlock2D::Mesh();
754 }
755
756 return meshes;
757}
758
759} // 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:87
2D drawable component for rendering objects in 2D space.
Definition: drawable_2d.hpp:27
SceneComponent2D & GetTransform()
Access to the underlying SceneComponent2D (transform).
Definition: drawable_2d.cpp:55
void Update(float deltaTime) override
Updates model matrix (and any other logic).
Definition: drawable_2d.cpp:17
void Initialize() override
Initializes render resources.
Definition: drawable_2d.cpp:12
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 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 PushQuad(std::string meshName, std::unordered_map< std::string, TextBlock2D::Mesh > &meshes, GlyphQuad glyphQuad, float horizontalShift=0.0f)
Appends a glyph quad to a mesh.
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.