33std::uint64_t TextBlock2D::sNumOfTextBlocks = 0;
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))
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 & 0xE0) == 0xC0)
162 if (i + 1 >= length)
break;
166 (bytes[i + 1] & 0x3F);
170 else if ((c & 0xF0) == 0xE0)
173 if (i + 2 >= length)
break;
177 ((bytes[i + 1] & 0x3F) << 6) |
178 (bytes[i + 2] & 0x3F);
182 else if ((c & 0xF8) == 0xF0)
185 if (i + 3 >= length)
break;
189 ((bytes[i + 1] & 0x3F) << 12) |
190 ((bytes[i + 2] & 0x3F) << 6) |
191 (bytes[i + 3] & 0x3F);
202 result.push_back(codePoint);
217 const std::uint32_t space{ 0x20 };
218 const std::uint32_t newLine{ 0x0A };
221 if (!autoTextWrappingRequested)
223 for (std::uint32_t glyph : codePoints)
225 if (glyph == newLine)
235 PushQuad(meshName, meshes, glyphQuad);
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);
266 nextWord.push_back(glyphQuad);
268 isLastGlyphProcessed = nextGlyphIndex >= codePoints.size();
273 if (line.empty() || isNewWordFitLine)
280 stringProcessed.append(
" ");
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 const std::string meshName =
mMaterialMesh[quad.fontAtlasMaterialName];
309 PushQuad(meshName, meshes, quad, horizontalShift);
315 nextGlyphIndex = currentIndex;
327 std::vector<std::uint32_t> allUsedGlyphs;
328 std::vector<std::vector<std::uint32_t>> linesOfGlyphs;
332 for (
const auto& textRunString : textRuns)
334 auto textRunCodePoints =
DecodeUtf8(textRunString);
335 linesOfGlyphs.push_back(textRunCodePoints);
336 std::copy(textRunCodePoints.begin(), textRunCodePoints.end(), std::back_inserter(allUsedGlyphs));
344 float maximumLineLengh = 0.0f;
345 std::vector<float> lineLengths;
347 std::vector<std::vector<GlyphQuad>> linesOfGlyphQuads;
354 for (
auto line : linesOfGlyphs)
357 std::vector<GlyphQuad> lineOfGlyphQuads;
358 for (
const auto& glyph : line)
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 const std::string meshName =
mMaterialMesh[glyphQuad.fontAtlasMaterialName];
388 float horizontalShift = 0.0f;
397 PushQuad(meshName, meshes, glyphQuad, horizontalShift);
411 std::vector<std::vector<ShapedGlyph>> linesOfShapedGlyphs;
412 std::vector<ShapedGlyph> preShapedGlyphs;
414 for (
const auto& textRun : textRuns)
417 linesOfShapedGlyphs.push_back(shapedTextRun);
418 std::copy(shapedTextRun.begin(), shapedTextRun.end(), std::back_inserter(preShapedGlyphs));
420 std::vector<GlyphIndex> ensureGlyphs;
421 for (
const auto& shapedGlyph : preShapedGlyphs)
424 gi.
index = shapedGlyph.glyphIndex;
425 ensureGlyphs.push_back(gi);
435 float maximumLineLengh = 0.0f;
436 std::vector<float> lineLengths;
438 for (
auto& line : linesOfShapedGlyphs)
440 float lineLength = 0.0f;
441 for (
const auto& glyph : line)
443 lineLength += glyph.xAdvance;
445 lineLengths.push_back(lineLength);
446 if (lineLength > maximumLineLengh)
448 maximumLineLengh = lineLength;
455 for (
auto& line : linesOfShapedGlyphs)
457 for (
const auto& glyph : line)
460 glyphIndex.
index = glyph.glyphIndex;
465 float horizontalShift = 0.0f;
474 PushQuad(meshName, meshes, glyphQuad, horizontalShift);
475 penX += glyph.xAdvance;
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);
503 const float x0 = penX + glyphMetrics.
bearingX;
504 const float y0 = penY - glyphMetrics.
bearingY;
505 const float y1 = y0 + glyphMetrics.
height;
506 const float x1 = x0 + glyphMetrics.
width;
514 const auto atlasWidth =
static_cast<float>(fontAtlas->GetCpuImageData().GetWidth());
515 const auto atlasHeight =
static_cast<float>(fontAtlas->GetCpuImageData().GetHeight());
536 const glm::vec2 shift(horizontalShift, 0.0f);
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;
573 auto modelCache =
mTextRenderer->GetRenderResourceContext().meshCache;
577 auto meshName = material.second;
578 auto mesh = meshes.at(meshName);
579 modelCache->LoadCustomMesh(meshName,
591 const std::string meshName = materialMesh.second;
592 const std::string materialName = materialMesh.first;
599 std::vector<std::string> result;
600 std::string_view sv(text);
601 size_t start = 0, end = 0;
604 end = sv.find(separator, start);
606 result.emplace_back(sv.substr(start, end - start));
607 if (end == std::string_view::npos)
break;
616 auto scriptsRequiredShaping =
mTextRenderer->GetScriptsRequiredShaping();
617 for (
const auto& script : scriptsRequiredShaping)
620 if (codePoint >= range.first && codePoint <= range.second)
633 if (codePoint <= 0x7F) {
634 out.push_back(
static_cast<char>(codePoint));
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)));
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)));
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)));
657 std::vector<TextBlock2D::ShapedGlyph> result;
660 buf = hb_buffer_create();
661 hb_buffer_add_utf8(buf, text.c_str(), -1, 0, -1);
663 hb_buffer_guess_segment_properties(buf);
666 hb_font_t* font = hb_ft_font_create_referenced(fontFace);
668 hb_shape(font, buf, NULL, 0);
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);
674 for (
unsigned int i = 0; i < glyph_count; i++)
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);
686 hb_font_destroy(font);
687 hb_buffer_destroy(buf);
697 for (
auto codePoint : glyphs)
699 if (codePoint == 0x000A || codePoint == 0x000D || codePoint == 0x0009)
705 auto fontAtlasMaterialName =
mFontResources->GetFontAtlasMaterialName(glyphIndex);
706 const auto search =
mMaterialMesh.find(fontAtlasMaterialName);
709 const std::string meshName = mTextBlockID +
"_" + std::to_string(
mMaterialMesh.size());
715 std::unordered_map<std::string, TextBlock2D::Mesh> meshes;
728 for (
const auto& glyph : glyphs)
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)
739 glyphIndex.
index = glyph.glyphIndex;
740 auto fontAtlasMaterialName =
mFontResources->GetFontAtlasMaterialName(glyphIndex);
741 const auto search =
mMaterialMesh.find(fontAtlasMaterialName);
744 const std::string meshName = mTextBlockID +
"_" + std::to_string(
mMaterialMesh.size());
750 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 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 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.
std::string fontAtlasMaterialName
CPU-side mesh data for glyph quads.
Result of text shaping for a single glyph.