33std::uint64_t TextBlock2D::sNumOfTextBlocks = 0;
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)
58 throw std::runtime_error(
"FontResources is not initialized for this TextBlock2D.");
62 mTextBlockID =
"TextBlock_" + std::to_string(sNumOfTextBlocks);
84 renderBatch.renderResources->SubmitResources(transformations, renderBatch.materialParameters);
113 renderBatch.materialParameters.SetMaterialVec4(
"FontColor", color);
121 renderBatch.materialParameters.SetMaterialVec4(
"OutlineColor", color);
134 renderBatch.materialParameters.SetMaterialFloat(
"OutlineThicknessPx", thicknessPx);
140 std::vector<std::uint32_t> result;
141 result.reserve(text.size());
143 const unsigned char* bytes =
144 reinterpret_cast<const unsigned char*
>(text.data());
145 const size_t length = text.size();
150 std::uint32_t codePoint = 0;
151 unsigned char c = bytes[i];
159 else if ((c & 0xE0U) == 0xC0U)
162 if (i + 1 >= length)
break;
166 (bytes[i + 1] & 0x3FU);
170 else if ((c & 0xF0U) == 0xE0U)
173 if (i + 2 >= length)
break;
176 ((c & 0x0FU) << 12) |
177 ((bytes[i + 1] & 0x3FU) << 6) |
178 (bytes[i + 2] & 0x3FU);
182 else if ((c & 0xF8U) == 0xF0U)
185 if (i + 3 >= length)
break;
188 ((c & 0x07U) << 18) |
189 ((bytes[i + 1] & 0x3FU) << 12) |
190 ((bytes[i + 2] & 0x3FU) << 6) |
191 (bytes[i + 3] & 0x3FU);
202 result.push_back(codePoint);
213 const float lineHeight =
static_cast<float>(fontMetrics.
lineHeight);
218 const std::uint32_t space{ 0x20 };
219 const std::uint32_t newLine{ 0x0A };
222 if (!autoTextWrappingRequested)
224 for (std::uint32_t glyph : codePoints)
226 if (glyph == newLine)
236 penX +=
static_cast<float>(glyphQuad.
advanceX);
241 bool isStringComplete =
false;
244 std::uint32_t currentIndex = 0;
245 bool isLastGlyphProcessed =
false;
246 float lineLength = 0.0f;
247 std::uint32_t nextGlyphIndex = 0;
249 std::vector<GlyphQuad> line;
250 std::vector<GlyphQuad> nextWord;
252 while (!isStringComplete)
254 std::string stringProcessed;
256 while (nextGlyphIndex < codePoints.size())
258 if (codePoints[nextGlyphIndex] == space)
261 const std::string curGlyph =
CodepointToUtf8(codePoints[nextGlyphIndex]);
262 stringProcessed.append(curGlyph);
265 penX +=
static_cast<float>(glyphQuad.
advanceX);
266 nextWord.push_back(glyphQuad);
268 isLastGlyphProcessed = nextGlyphIndex >= codePoints.size();
273 if (line.empty() || isNewWordFitLine)
280 stringProcessed.append(
" ");
281 penX +=
static_cast<float>(glyphQuad.
advanceX);
282 nextWord.push_back(glyphQuad);
286 std::copy(nextWord.begin(), nextWord.end(), std::back_inserter(line));
289 currentIndex = nextGlyphIndex;
290 isStringComplete = isLastGlyphProcessed;
293 if (!isNewWordFitLine || isLastGlyphProcessed)
297 for (
auto& quad : line)
299 float horizontalShift = 0.0f;
308 PushQuad(meshes, quad, horizontalShift);
314 nextGlyphIndex = currentIndex;
326 std::vector<std::uint32_t> allUsedGlyphs;
327 std::vector<std::vector<std::uint32_t>> linesOfGlyphs;
331 for (
const auto& textRunString : textRuns)
333 auto textRunCodePoints =
DecodeUtf8(textRunString);
334 linesOfGlyphs.push_back(textRunCodePoints);
335 std::copy(textRunCodePoints.begin(), textRunCodePoints.end(), std::back_inserter(allUsedGlyphs));
343 float maximumLineLengh = 0.0f;
344 std::vector<float> lineLengths;
346 std::vector<std::vector<GlyphQuad>> linesOfGlyphQuads;
349 const float lineHeight =
static_cast<float>(fontMetrics.
lineHeight);
354 for (
auto line : linesOfGlyphs)
357 std::vector<GlyphQuad> lineOfGlyphQuads;
358 for (
const auto& glyph : line)
363 penX +=
static_cast<float>(glyphQuad.
advanceX);
364 lineOfGlyphQuads.push_back(glyphQuad);
367 lineLengths.push_back(penX);
368 linesOfGlyphQuads.push_back(lineOfGlyphQuads);
369 const float lineLength = penX;
373 if (lineLength > maximumLineLengh)
375 maximumLineLengh = lineLength;
383 for (
const auto& line : linesOfGlyphQuads)
385 for (
const auto& glyphQuad : line)
387 float horizontalShift = 0.0f;
396 PushQuad(meshes, glyphQuad, horizontalShift);
410 std::vector<std::vector<ShapedGlyph>> linesOfShapedGlyphs;
411 std::vector<ShapedGlyph> preShapedGlyphs;
413 for (
const auto& textRun : textRuns)
416 linesOfShapedGlyphs.push_back(shapedTextRun);
417 std::copy(shapedTextRun.begin(), shapedTextRun.end(), std::back_inserter(preShapedGlyphs));
419 std::vector<GlyphIndex> ensureGlyphs;
420 for (
const auto& shapedGlyph : preShapedGlyphs)
423 gi.
index = shapedGlyph.glyphIndex;
424 ensureGlyphs.push_back(gi);
434 float maximumLineLengh = 0.0f;
435 std::vector<float> lineLengths;
437 for (
auto& line : linesOfShapedGlyphs)
439 float lineLength = 0.0f;
440 for (
const auto& glyph : line)
442 lineLength += glyph.xAdvance;
444 lineLengths.push_back(lineLength);
445 if (lineLength > maximumLineLengh)
447 maximumLineLengh = lineLength;
453 const float lineHeight =
static_cast<float>(fontMetrics.
lineHeight);
454 std::size_t curLine = 0U;
455 for (
auto& line : linesOfShapedGlyphs)
457 for (
const auto& glyph : line)
460 glyphIndex.
index = glyph.glyphIndex;
464 float horizontalShift = 0.0f;
473 PushQuad(meshes, glyphQuad, horizontalShift);
474 penX += glyph.xAdvance;
482 const float lineCount =
483 static_cast<float>(linesOfShapedGlyphs.size());
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);
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);
510 const float x0 = penX + bearingX;
511 const float y0 = penY - bearingY;
512 const float y1 = y0 + glyphHeight;
513 const float x1 = x0 + glyphWidth;
521 const float atlasWidth =
522 static_cast<float>(fontAtlas->GetCpuImageData().GetWidth());
523 const float atlasHeight =
524 static_cast<float>(fontAtlas->GetCpuImageData().GetHeight());
526 const float atlasX =
static_cast<float>(glyphMetrics.
atlasX);
527 const float atlasY =
static_cast<float>(glyphMetrics.
atlasY);
543 if (mesh.positions2D.size() >
544 static_cast<std::size_t
>(std::numeric_limits<std::uint32_t>::max()) - 3U)
546 throw std::overflow_error(
"TextBlock2D mesh exceeds 32-bit index range.");
549 const std::uint32_t vertexBase =
static_cast<std::uint32_t
>(mesh.positions2D.size());
551 const glm::vec2 shift(horizontalShift, 0.0f);
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;
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);
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));
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));
576 mesh.indices.push_back(vertexBase + 0U);
577 mesh.indices.push_back(vertexBase + 1U);
578 mesh.indices.push_back(vertexBase + 2U);
580 mesh.indices.push_back(vertexBase + 2U);
581 mesh.indices.push_back(vertexBase + 3U);
582 mesh.indices.push_back(vertexBase + 0U);
588 auto modelCache =
mTextRenderer->GetRenderResourceContext().meshCache;
592 auto meshName = material.second;
593 auto mesh = meshes.at(meshName);
594 modelCache->LoadCustomMesh(meshName,
606 const std::string meshName = materialMesh.second;
607 const std::string materialName = materialMesh.first;
614 std::vector<std::string> result;
615 std::string_view sv(text);
616 size_t start = 0, end = 0;
619 end = sv.find(separator, start);
621 result.emplace_back(sv.substr(start, end - start));
622 if (end == std::string_view::npos)
break;
631 auto scriptsRequiredShaping =
mTextRenderer->GetScriptsRequiredShaping();
632 for (
const auto& script : scriptsRequiredShaping)
635 if (codePoint >= range.first && codePoint <= range.second)
648 if (codePoint <= 0x7F) {
649 out.push_back(
static_cast<char>(codePoint));
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)));
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)));
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)));
672 std::vector<TextBlock2D::ShapedGlyph> result;
675 buf = hb_buffer_create();
676 hb_buffer_add_utf8(buf, text.c_str(), -1, 0, -1);
678 hb_buffer_guess_segment_properties(buf);
681 hb_font_t* font = hb_ft_font_create_referenced(fontFace);
683 hb_shape(font, buf, NULL, 0);
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);
689 for (
unsigned int i = 0; i < glyph_count; i++)
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);
701 hb_font_destroy(font);
702 hb_buffer_destroy(buf);
712 for (
auto codePoint : glyphs)
714 if (codePoint == 0x000A || codePoint == 0x000D || codePoint == 0x0009)
720 auto fontAtlasMaterialName =
mFontResources->GetFontAtlasMaterialName(glyphIndex);
721 const auto search =
mMaterialMesh.find(fontAtlasMaterialName);
724 const std::string meshName = mTextBlockID +
"_" + std::to_string(
mMaterialMesh.size());
730 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
743 for (
const auto& glyph : glyphs)
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)
754 glyphIndex.
index = glyph.glyphIndex;
755 auto fontAtlasMaterialName =
mFontResources->GetFontAtlasMaterialName(glyphIndex);
756 const auto search =
mMaterialMesh.find(fontAtlasMaterialName);
759 const std::string meshName = mTextBlockID +
"_" + std::to_string(
mMaterialMesh.size());
765 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
Represents a 2D camera with position, rotation, and zoom control.
glm::mat4 GetProjectionMatrix() const
Returns the current orthographic projection matrix.
const glm::mat4 & GetWorldView() const
Gets the world-view (model) matrix for the camera.
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.
TextBlock2D(Scene &scene, std::shared_ptr< TextRenderer > textRenderer, Properties properties=Properties())
Constructs a 2D text block.
const unsigned int mFontSize
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)
const std::string mFontName
std::vector< std::uint32_t > DecodeUtf8(const std::string &text)
Decodes a UTF-8 string into Unicode code points.
const bool bIsTextShapeEnabled
void SetTextColor(glm::vec4 color)
Sets the text color.
const TextAlign mTextAlign
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...
const float mOutlineThicknessPx
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.
std::string fontAtlasMaterialName
CPU-side mesh data for glyph quads.
Result of text shaping for a single glyph.