From 83a18bda7fca5f130e071a4348c126e967cf7f95 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Fri, 6 Feb 2026 18:04:49 +0300 Subject: [PATCH 1/2] Render: Bitmap fonts #1258 --- engine/includes/components/textrender.h | 13 +- engine/includes/resources/font.h | 70 +-- engine/src/components/textrender.cpp | 48 +- .../src/editor/converters/fontconverter.cpp | 2 - engine/src/resources/font.cpp | 472 ++++++------------ modules/uikit/includes/components/label.h | 18 +- modules/uikit/src/components/label.cpp | 80 +-- .../bin/engine/materials/DefaultFont.shader | 10 +- 8 files changed, 304 insertions(+), 409 deletions(-) diff --git a/engine/includes/components/textrender.h b/engine/includes/components/textrender.h index fc6ae4f67..bd39edfa3 100644 --- a/engine/includes/components/textrender.h +++ b/engine/includes/components/textrender.h @@ -44,6 +44,9 @@ class ENGINE_EXPORT TextRender : public Renderable { Vector4 color() const; void setColor(const Vector4 &color); + bool translated() const; + void setTranslated(bool enable); + bool wordWrap() const; void setWordWrap(bool wrap); @@ -54,7 +57,7 @@ class ENGINE_EXPORT TextRender : public Renderable { void setAlign(int alignment); bool kerning() const; - void setKerning(const bool kerning); + void setKerning(const bool enable); int layer() const; void setLayer(int layer); @@ -93,16 +96,16 @@ class ENGINE_EXPORT TextRender : public Renderable { int m_priority; - float m_fontWeight; - - bool m_kerning; + int m_flags; - bool m_wrap; + float m_fontWeight; bool m_dirtyMesh; bool m_dirtyMaterial; + bool m_translated; + }; #endif // TEXTRENDER_H diff --git a/engine/includes/resources/font.h b/engine/includes/resources/font.h index 93cea7e60..6f0b416cc 100644 --- a/engine/includes/resources/font.h +++ b/engine/includes/resources/font.h @@ -15,73 +15,75 @@ enum Alignment { class Mesh; class Texture; +class AtlasNode; class ENGINE_EXPORT Font : public Resource { A_OBJECT(Font, Resource, Resources) A_NOPROPERTIES() - A_METHODS( - A_METHOD(int, Font::atlasIndex), - A_METHOD(int, Font::requestKerning), - A_METHOD(void, Font::requestCharacters), - A_METHOD(int, Font::length), - A_METHOD(float, Font::spaceWidth), - A_METHOD(float, Font::lineHeight) - ) + A_NOMETHODS() -public: - Font(); - ~Font(); + struct GlyphData { + Vector3Vector vertices; - Texture *page(int index = 0); + Vector2Vector uvs; - int atlasIndex(int glyph) const; + IndexVector indices; - int requestKerning(int glyph, int previous) const; + ByteArray data; - void requestCharacters(const TString &characters); + AtlasNode *node = nullptr; - int length(const TString &characters) const; + int width = 0; - float spaceWidth() const; + int height = 0; - float lineHeight() const; + bool copied = false; - float textWidth(const TString &text, int size, bool kerning); + }; - void composeMesh(Mesh *mesh, const TString &text, int size, int alignment, bool kerning, bool wrap, const Vector2 &boundaries); + enum Flags { + Kerning = (1<<0), + Wrap = (1<<1), + Sdf = (1<<2) + }; - void loadUserData(const VariantMap &data) override; +public: + Font(); + ~Font(); + + Texture *page(); + + float textWidth(const TString &text, int size, int flags); + + void composeMesh(Mesh *mesh, const TString &text, int size, int alignment, int flags, const Vector2 &boundaries); private: void clear(); - Mesh *shape(int key) const; + void clearAtlas(); - int addElement(Texture *texture); + void requestCharacters(const std::u32string &characters, uint32_t size); - void packSheets(int padding); + int requestKerning(int glyph, int previous) const; - void addPage(Texture *texture); + GlyphData *glyph(int key); + + void packSheets(int padding); -protected: VariantMap saveUserData() const override; + void loadUserData(const VariantMap &data) override; private: - std::unordered_map m_glyphMap; - std::unordered_map m_shapes; - - std::vector m_pages; - std::vector m_sources; + std::unordered_map m_shapes; ByteArray m_data; int32_t *m_face; - int32_t m_scale; + Texture *m_page; - float m_spaceWidth; - float m_lineHeight; + AtlasNode *m_root; bool m_useKerning; diff --git a/engine/src/components/textrender.cpp b/engine/src/components/textrender.cpp index 1910ec48e..4dcd640fd 100644 --- a/engine/src/components/textrender.cpp +++ b/engine/src/components/textrender.cpp @@ -29,10 +29,10 @@ TextRender::TextRender() : m_alignment(Left), m_priority(0), m_fontWeight(0.5f), - m_kerning(true), - m_wrap(false), + m_flags(Font::Kerning | Font::Sdf), m_dirtyMesh(true), - m_dirtyMaterial(true) { + m_dirtyMaterial(true), + m_translated(false) { m_mesh->makeDynamic(); @@ -56,8 +56,7 @@ TextRender::~TextRender() { Mesh *TextRender::meshToDraw(int instance) { A_UNUSED(instance); if(m_dirtyMesh && m_font && !m_text.isEmpty()) { - m_font->composeMesh(m_mesh, m_text, m_size, m_alignment, m_kerning, m_wrap, m_boundaries); - + m_font->composeMesh(m_mesh, m_translated ? Engine::translate(m_text) : m_text, m_size, m_alignment, m_flags, m_boundaries); m_dirtyMesh = false; } @@ -139,17 +138,38 @@ void TextRender::setColor(const Vector4 &color) { m_color = color; m_dirtyMaterial = true; } +/*! + Returns true if text in text render must be translated; othewise returns false. +*/ +bool TextRender::translated() const { + return m_translated; +} +/*! + Sets \a enable or disable translation from dictionary for current text render. +*/ +void TextRender::setTranslated(bool enable) { + if(m_translated != enable) { + m_translated = enable; + m_dirtyMesh = true; + } +} /*! Returns true if word wrap enabled; otherwise returns false. */ bool TextRender::wordWrap() const { - return m_wrap; + return m_flags & Font::Wrap; } /*! Sets the word \a wrap policy. Set true to enable word wrap and false to disable. */ void TextRender::setWordWrap(bool wrap) { - m_wrap = wrap; + if(wordWrap() != wrap) { + if(wrap) { + m_flags |= Font::Wrap; + } else { + m_flags &= ~Font::Wrap; + } + } m_dirtyMesh = true; } /*! @@ -182,15 +202,19 @@ void TextRender::setAlign(int alignment) { Returns true if glyph kerning enabled; otherwise returns false. */ bool TextRender::kerning() const { - return m_kerning; + return m_flags & Font::Kerning; } /*! - Set true to enable glyph \a kerning and false to disable. + Set true to \a enable glyph kerning and false to disable. \note Glyph kerning functionality depends on fonts which you are using. In case of font doesn't support kerning, you will not see the difference. */ -void TextRender::setKerning(const bool kerning) { - if(m_kerning != kerning) { - m_kerning = kerning; +void TextRender::setKerning(const bool enable) { + if(kerning() != enable) { + if(enable) { + m_flags |= Font::Kerning; + } else { + m_flags &= ~Font::Kerning; + } m_dirtyMesh = true; } } diff --git a/engine/src/editor/converters/fontconverter.cpp b/engine/src/editor/converters/fontconverter.cpp index 96c06ade3..13900d955 100644 --- a/engine/src/editor/converters/fontconverter.cpp +++ b/engine/src/editor/converters/fontconverter.cpp @@ -46,8 +46,6 @@ AssetConverter::ReturnCode FontConverter::convertFile(AssetConverterSettings *se map[gData] = src.readAll(); src.close(); - font->loadUserData(map); - return settings->saveBinary(Engine::toVariant(font), settings->absoluteDestination()); } return InternalError; diff --git a/engine/src/resources/font.cpp b/engine/src/resources/font.cpp index 47d743ff5..782976fca 100644 --- a/engine/src/resources/font.cpp +++ b/engine/src/resources/font.cpp @@ -19,80 +19,6 @@ namespace { #define DF_GLYPH_SIZE 64 -struct Point { - short dx, dy; - int f; -}; - -struct Grid { - int32_t w, h; - Point *grid; -}; - -static Point pointInside = { 0, 0, 0 }; -static Point pointEmpty = { 9999, 9999, 9999*9999 }; - -static FT_Library library = nullptr; -//FT_Done_FreeType(library); - -static inline Point get(Grid &g, int32_t x, int32_t y) { - return g.grid[y * (g.w + 2) + x]; -} - -static inline void put(Grid &g, int32_t x, int32_t y, const Point &p) { - g.grid[y * (g.w + 2) + x] = p; -} - -static inline void compare(Grid &g, Point &p, int32_t x, int32_t y, int32_t offsetx, int32_t offsety) { - int add; - Point other = get(g, x + offsetx, y + offsety); - if(offsety == 0) { - add = 2 * other.dx + 1; - } else if(offsetx == 0) { - add = 2 * other.dy + 1; - } else { - add = 2 * (other.dy + other.dx + 1); - } - other.f += add; - if(other.f < p.f) { - p.f = other.f; - if(offsety == 0) { - p.dx = other.dx + 1; - p.dy = other.dy; - } else if(offsetx == 0) { - p.dy = other.dy + 1; - p.dx = other.dx; - } else { - p.dy = other.dy + 1; - p.dx = other.dx + 1; - } - } -} - -static void generateSDF(Grid &g) { - for(int32_t y = 1; y <= g.h; y++) { - for(int32_t x = 1; x <= g.w; x++) { - Point p = get(g, x, y); - compare(g, p, x, y, -1, 0); - compare(g, p, x, y, 0, -1); - compare(g, p, x, y, -1, -1); - compare(g, p, x, y, 1, -1); - put(g, x, y, p); - } - } - - for(int32_t y = g.h; y > 0; y--) { - for(int32_t x = g.w; x > 0; x--) { - Point p = get(g, x, y); - compare(g, p, x, y, 1, 0); - compare(g, p, x, y, 0, 1); - compare(g, p, x, y, -1, 1); - compare(g, p, x, y, 1, 1); - put(g, x, y, p); - } - } -} - /*! \class Font \brief The Font resource provides support for vector fonts. @@ -105,85 +31,71 @@ static void generateSDF(Grid &g) { Font::Font() : m_face(nullptr), - m_scale(DF_GLYPH_SIZE), - m_spaceWidth(0.0f), - m_lineHeight(0.0f), + m_page(nullptr), + m_root(new AtlasNode), m_useKerning(false) { - FT_Init_FreeType( &library ); } Font::~Font() { - Font::clear(); + clear(); } /*! - Returns the index of the \a glyph in the atlas. + \internal */ -int Font::atlasIndex(int glyph) const { - auto it = m_glyphMap.find(glyph); - if(it != m_glyphMap.end()) { - return (*it).second; +void Font::requestCharacters(const std::u32string &characters, uint32_t size) { + FT_Face face = reinterpret_cast(m_face); + if(face == nullptr) { + return; + } + + FT_Error error = FT_Set_Pixel_Sizes(face, 0, size); + if(error != 0) { + return; } - return 0; -} -/*! - Requests \a characters to be added to the font atlas. -*/ -void Font::requestCharacters(const TString &characters) { - std::u32string u32 = characters.toUtf32(); bool isNew = false; - for(auto it : u32) { + for(auto it : characters) { uint32_t ch = it; - if(m_glyphMap.find(ch) == m_glyphMap.end() && m_face) { - FT_Error error = FT_Load_Glyph(reinterpret_cast(m_face), FT_Get_Char_Index(reinterpret_cast(m_face), it), FT_LOAD_RENDER); + if(m_shapes.find(ch) == m_shapes.end()) { + error = FT_Load_Glyph(face, FT_Get_Char_Index(face, it), FT_LOAD_RENDER); if(!error) { - int index = -1; - - FT_GlyphSlot slot = reinterpret_cast(m_face)->glyph; - error = FT_Render_Glyph(slot, FT_RENDER_MODE_SDF); + FT_GlyphSlot slot = face->glyph; + error = FT_Render_Glyph(slot, (size < DF_GLYPH_SIZE) ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_SDF); if(!error) { if(slot->bitmap.width && slot->bitmap.rows) { - Texture::Surface s; - ByteArray buffer; - buffer.resize(slot->bitmap.width * slot->bitmap.rows); + GlyphData data; - memcpy(buffer.data(), slot->bitmap.buffer, buffer.size()); + FT_Glyph ftGlyph; + error = FT_Get_Glyph(slot, &ftGlyph); + if(!error) { + FT_BBox bbox; + FT_Glyph_Get_CBox(ftGlyph, ft_glyph_bbox_pixels, &bbox); - s.push_back(buffer); + data.vertices = {Vector3(bbox.xMin, bbox.yMax, 0.0f) / static_cast(size), + Vector3(bbox.xMax, bbox.yMax, 0.0f) / static_cast(size), + Vector3(bbox.xMax, bbox.yMin, 0.0f) / static_cast(size), + Vector3(bbox.xMin, bbox.yMin, 0.0f) / static_cast(size)}; - Texture *t = Engine::objectCreate("", this); - t->resize(slot->bitmap.width, slot->bitmap.rows); - t->clear(); - t->addSurface(s); + data.indices = {0, 1, 2, 0, 2, 3}; - index = addElement(t); - } - } + isNew = true; + } - FT_Glyph glyph; - error = FT_Get_Glyph(slot, &glyph); - if(!error && index > -1) { - FT_BBox bbox; - FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_pixels, &bbox); - - Mesh *m = shape(index); - if(m) { - m->setVertices({Vector3(bbox.xMin, bbox.yMax, 0.0f) / m_scale, - Vector3(bbox.xMax, bbox.yMax, 0.0f) / m_scale, - Vector3(bbox.xMax, bbox.yMin, 0.0f) / m_scale, - Vector3(bbox.xMin, bbox.yMin, 0.0f) / m_scale}); - } - m_glyphMap[ch] = index; + data.width = slot->bitmap.width; + data.height = slot->bitmap.rows; + data.data.resize(data.width * data.height); + memcpy(data.data.data(), slot->bitmap.buffer, data.data.size()); - isNew = true; + m_shapes[ch] = data; + } } } } } if(isNew) { - packSheets(1); + packSheets(10); notifyCurrentState(); } } @@ -194,63 +106,51 @@ void Font::requestCharacters(const TString &characters) { int Font::requestKerning(int glyph, int previous) const { if(m_useKerning && previous) { FT_Vector delta; - FT_Get_Kerning( reinterpret_cast(m_face), previous, glyph, FT_KERNING_DEFAULT, &delta ); + FT_Get_Kerning( reinterpret_cast(m_face), previous, glyph, FT_KERNING_DEFAULT, &delta ); return delta.x >> 6; } return 0; } -/*! - Returns the number of \a characters in the string. -*/ -int Font::length(const TString &characters) const { - return characters.toUtf32().length(); -} -/*! - Returns visual width of space character for the font in world units. -*/ -float Font::spaceWidth() const { - return m_spaceWidth; -} -/*! - Returns visual height for the font in world units. -*/ -float Font::lineHeight() const { - return m_lineHeight; -} - -float Font::textWidth(const TString &text, int size, bool kerning) { - TString data = Engine::translate(text); - requestCharacters(data); +float Font::textWidth(const TString &text, int size, int flags) { float pos = 0; - uint32_t length = Font::length(data); + std::u32string u32 = text.toUtf32(); + uint32_t length = u32.length(); if(length) { - std::u32string u32 = data.toUtf32(); + int adjustedSize = (flags & Sdf) ? DF_GLYPH_SIZE : size; + requestCharacters(u32, adjustedSize); + + FT_Face face = reinterpret_cast(m_face); uint32_t previous = 0; uint32_t it = 0; + float spaceWidth = 0; + FT_Error error = FT_Load_Glyph( face, FT_Get_Char_Index( face, ' ' ), FT_LOAD_BITMAP_METRICS_ONLY ); + if(!error) { + spaceWidth = face->glyph->advance.x / (DF_GLYPH_SIZE * 64.0f) * size; + } + for(uint32_t i = 0; i < length; i++) { uint32_t ch = u32[i]; switch(ch) { case ' ': { - pos += m_spaceWidth * size; + pos += spaceWidth; } break; case '\t': { - pos += m_spaceWidth * size * 4; + pos += spaceWidth * 4; } break; default: { - if(kerning) { + if(flags & Kerning) { pos += requestKerning(ch, previous); } - uint32_t index = atlasIndex(ch); - Mesh *glyph = shape(index); - if(glyph == nullptr) { + GlyphData *data = glyph(ch); + if(data == nullptr) { continue; } - Vector3Vector &shape = glyph->vertices(); + Vector3Vector &shape = data->vertices; pos += shape[2].x * size; it++; @@ -263,16 +163,29 @@ float Font::textWidth(const TString &text, int size, bool kerning) { return pos; } -void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, bool kerning, bool wrap, const Vector2 &boundaries) { - float spaceWidth = m_spaceWidth * size; - float spaceLine = m_lineHeight * size; +void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, int flags, const Vector2 &boundaries) { + std::u32string u32 = text.toUtf32(); + uint32_t length = u32.length(); + if(length) { + int adjustedSize = (flags & Sdf) ? DF_GLYPH_SIZE : size; + requestCharacters(u32, adjustedSize); - TString data = Engine::translate(text); - requestCharacters(data); + FT_Face face = reinterpret_cast(m_face); - uint32_t length = Font::length(data); - if(length) { - std::u32string u32 = data.toUtf32(); + float spaceWidth = 0; + float spaceLine = 0; + + FT_Error error = FT_Load_Glyph( face, FT_Get_Char_Index( face, ' ' ), FT_LOAD_BITMAP_METRICS_ONLY ); + if(!error) { + spaceWidth = (adjustedSize == DF_GLYPH_SIZE) ? (DF_GLYPH_SIZE * 64.0f * size) : 64.0f; + spaceWidth = face->glyph->advance.x / spaceWidth; + } + + error = FT_Load_Glyph( face, FT_Get_Char_Index( face, '\n' ), FT_LOAD_BITMAP_METRICS_ONLY ); + if(!error) { + spaceLine = (adjustedSize == DF_GLYPH_SIZE) ? (adjustedSize + size) : size; + spaceLine = face->glyph->metrics.height / spaceLine / 2; + } IndexVector &indices = mesh->indices(); Vector3Vector &vertices = mesh->vertices(); @@ -311,21 +224,20 @@ void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, space = 0; } break; default: { - if(kerning) { + if(flags & Kerning) { pos.x += requestKerning(ch, previous); } - uint32_t index = atlasIndex(ch); - Mesh *glyph = shape(index); - if(glyph == nullptr) { + GlyphData *data = glyph(ch); + if(data == nullptr) { continue; } - Vector3Vector &shape = glyph->vertices(); - Vector2Vector &uv = glyph->uv0(); + Vector3Vector &shape = data->vertices; + Vector2Vector &uv = data->uvs; float x = pos.x + shape[2].x * size; - if(wrap && boundaries.x > 0.0f && boundaries.x < x && space > 0 && space < it) { + if((flags & Wrap) && boundaries.x > 0.0f && boundaries.x < x && space > 0 && space < it) { float shift = vertices[space * 4].x; if((shift - spaceWidth) > 0.0f) { for(uint32_t s = space; s < it; s++) { @@ -401,34 +313,22 @@ void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, void Font::loadUserData(const VariantMap &data) { clear(); { + static FT_Library library = nullptr; + if(library == nullptr) { + FT_Init_FreeType( &library ); + } + auto it = data.find(gData); if(it != data.end()) { - - FT_FaceRec_ *face = reinterpret_cast(m_face); + FT_Face face = reinterpret_cast(m_face); m_data = (*it).second.toByteArray(); - FT_Error error = FT_New_Memory_Face(library, reinterpret_cast(&m_data[0]), m_data.size(), 0, &face); + FT_Error error = FT_New_Memory_Face(library, m_data.data(), m_data.size(), 0, &face); if(error) { Log(Log::ERR) << "Can't load font. System returned error:" << error; return; } - - m_face = reinterpret_cast(face); - error = FT_Set_Char_Size( face, m_scale * 64, 0, 0, 0 ); - if(error) { - Log(Log::ERR) << "Can't set default font size. System returned error:" << error; - return; - } m_useKerning = FT_HAS_KERNING( face ); - - error = FT_Load_Glyph( face, FT_Get_Char_Index( face, ' ' ), FT_LOAD_DEFAULT ); - if(!error) { - m_spaceWidth = static_cast(face->glyph->advance.x) / m_scale / 64.0f; - } - - error = FT_Load_Glyph( face, FT_Get_Char_Index( face, '\n' ), FT_LOAD_DEFAULT ); - if(!error) { - m_lineHeight = static_cast(face->glyph->metrics.height) / m_scale / 32.0f; - } + m_face = reinterpret_cast(face); } } } @@ -447,173 +347,131 @@ VariantMap Font::saveUserData() const { Cleans up all font data. */ void Font::clear() { - m_glyphMap.clear(); FT_Done_Face(reinterpret_cast(m_face)); - for(auto it : m_sources) { - it->decRef(); + if(m_page) { + m_page->decRef(); } - m_sources.clear(); + m_page = nullptr; - for(auto it : m_pages) { - it->decRef(); - } - m_pages.clear(); + m_shapes.clear(); } +/*! + \internal +*/ +void Font::clearAtlas() { + if(m_root->left) { + delete m_root->left; + m_root->left = nullptr; + } -Mesh *Font::shape(int key) const { - PROFILE_FUNCTION(); - - auto it = m_shapes.find(key); - if(it != m_shapes.end()) { - return it->second; + if(m_root->right) { + delete m_root->right; + m_root->right = nullptr; } - return nullptr; + + m_root->occupied = false; } /*! - Adds new sub \a texture as element to current glyph sheet. - All elements will be packed to a single glyph sheet texture using Font::packSheets() method. - Returns the id of the new element. - - \sa packSheets() + \internal */ -int Font::addElement(Texture *texture) { +Font::GlyphData *Font::glyph(int key) { PROFILE_FUNCTION(); - m_sources.push_back(texture); - - Mesh *mesh = Engine::objectCreate(); - mesh->makeDynamic(); - mesh->setVertices({Vector3(0.0f, 0.0f, 0.0f), - Vector3(0.0f, texture->height(), 0.0f), - Vector3(texture->width(), texture->height(), 0.0f), - Vector3(texture->width(), 0.0f, 0.0f) }); - - mesh->setIndices({0, 1, 2, 0, 2, 3}); - - int index = (m_sources.size() - 1); - m_shapes[index] = mesh; - - return index; + auto it = m_shapes.find(key); + if(it != m_shapes.end()) { + return &(it->second); + } + return nullptr; } /*! Packs all added elements into a glyph sheets. Parameter \a padding can be used to delimit elements. - - \sa addElement() */ void Font::packSheets(int padding) { PROFILE_FUNCTION(); - uint32_t atlasWidth = 1; - uint32_t atlasHeight = 1; - - std::vector nodes; - nodes.resize(m_sources.size()); + uint32_t atlasWidth = 1024; + uint32_t atlasHeight = 1024; - AtlasNode root; - - if(m_pages.empty()) { - Texture *texture = Engine::objectCreate(); - texture->setFiltering(Texture::Bilinear); - addPage(texture); + if(m_page == nullptr) { + m_page = Engine::objectCreate(); + m_page->incRef(); + m_page->setFiltering(Texture::None); } while(true) { - root.w = atlasWidth; - root.h = atlasHeight; - - uint32_t i; - for(i = 0; i < m_sources.size(); i++) { - Texture *texture = m_sources[i]; - - int32_t width = (texture->width() + padding * 2); - int32_t height = (texture->height() + padding * 2); + m_root->w = atlasWidth; + m_root->h = atlasHeight; + + uint32_t i = 0; + for(auto &it : m_shapes) { + i++; + if(it.second.node) { + continue; + } + int32_t width = (it.second.width + padding * 2); + int32_t height = (it.second.height + padding * 2); - AtlasNode *node = root.insert(width, height); + AtlasNode *node = m_root->insert(width, height); if(node) { node->occupied = true; - - nodes[i] = node; - } else { + it.second.node = node; + } else { // Not enough space. Increasing page size atlasWidth *= 2; atlasHeight *= 2; - if(root.left) { - delete root.left; - root.left = nullptr; - } + clearAtlas(); - if(root.right) { - delete root.right; - root.right = nullptr; + for(auto &it : m_shapes) { + it.second.copied = false; + it.second.node = nullptr; } - root.occupied = false; - break; } } - if(i == m_sources.size()) { + if(i == m_shapes.size()) { break; } } - for(auto it : m_pages) { - it->resize(atlasWidth, atlasHeight); - for(uint32_t i = 0; i < nodes.size(); i++) { - AtlasNode *node = nodes[i]; - - int32_t w = node->w - padding * 2; - int32_t h = node->h - padding * 2; + if(m_page) { + m_page->resize(atlasWidth, atlasHeight); + for(auto &it : m_shapes) { + if(!it.second.copied) { + AtlasNode *node = it.second.node; + uint8_t *src = it.second.data.data(); + uint8_t *dst = m_page->surface(0).front().data(); + for(int32_t y = 0; y < it.second.height; y++) { + uint32_t index = (node->y + y + padding) * atlasWidth + node->x + padding; + memcpy(&dst[index], &src[y * it.second.width], it.second.width); + } - uint8_t *src = m_sources[i]->surface(0).front().data(); - uint8_t *dst = it->surface(0).front().data(); - for(int32_t y = 0; y < h; y++) { - memcpy(&dst[(y + node->y + padding) * atlasWidth + node->x], &src[y * w], w); - } + it.second.copied = true; - Mesh *mesh = shape(i); - if(mesh) { Vector4 uvFrame; - uvFrame.x = node->x / static_cast(atlasWidth); + uvFrame.x = (node->x + padding) / static_cast(atlasWidth); uvFrame.y = (node->y + padding) / static_cast(atlasHeight); - uvFrame.z = uvFrame.x + w / static_cast(atlasWidth); - uvFrame.w = uvFrame.y + h / static_cast(atlasHeight); - - mesh->setUv0({Vector2(uvFrame.x, uvFrame.y), - Vector2(uvFrame.z, uvFrame.y), - Vector2(uvFrame.z, uvFrame.w), - Vector2(uvFrame.x, uvFrame.w)}); + uvFrame.z = uvFrame.x + it.second.width / static_cast(atlasWidth); + uvFrame.w = uvFrame.y + it.second.height / static_cast(atlasHeight); - mesh->recalcBounds(); + it.second.uvs = {Vector2(uvFrame.x, uvFrame.y), + Vector2(uvFrame.z, uvFrame.y), + Vector2(uvFrame.z, uvFrame.w), + Vector2(uvFrame.x, uvFrame.w)}; } } - it->setDirty(); + m_page->setDirty(); } } /*! - Returns glyph sheet texture at \a index. + Returns glyph sheet texture. */ -Texture *Font::page(int index) { +Texture *Font::page() { PROFILE_FUNCTION(); - if(index < m_pages.size()) { - return m_pages[index]; - } - - return nullptr; -} -/*! - Adds a new glyph sheet \a texture. -*/ -void Font::addPage(Texture *texture) { - PROFILE_FUNCTION(); - - if(texture) { - texture->incRef(); - m_pages.push_back(texture); - } + return m_page; } diff --git a/modules/uikit/includes/components/label.h b/modules/uikit/includes/components/label.h index 630edf9dc..98e4b8b8c 100644 --- a/modules/uikit/includes/components/label.h +++ b/modules/uikit/includes/components/label.h @@ -14,6 +14,7 @@ class UIKIT_EXPORT Label : public Widget { A_PROPERTIES( A_PROPERTY(TString, text, Label::text, Label::setText), + A_PROPERTY(bool, translated, Label::translated, Label::setTranslated), A_PROPERTYEX(int, alignment, Label::align, Label::setAlign, "editor=Alignment, css=text-align"), A_PROPERTYEX(Font *, font, Label::font, Label::setFont, "editor=Asset"), A_PROPERTYEX(int, fontSize, Label::fontSize, Label::setFontSize, "css=font-size"), @@ -39,6 +40,9 @@ class UIKIT_EXPORT Label : public Widget { Vector4 color() const; void setColor(const Vector4 &color); + bool translated() const; + void setTranslated(bool enable); + bool wordWrap() const; void setWordWrap(bool wrap); @@ -46,12 +50,10 @@ class UIKIT_EXPORT Label : public Widget { void setAlign(int alignment); bool kerning() const; - void setKerning(const bool kerning); + void setKerning(const bool enable); Vector2 cursorAt(int position); - void setClipOffset(const Vector2 &offset); - private: void draw(CommandBuffer &buffer) override; void applyStyle() override; @@ -75,8 +77,6 @@ class UIKIT_EXPORT Label : public Widget { Vector2 m_meshSize; - Vector2 m_clipOffset; - Font *m_font; MaterialInstance *m_material; @@ -87,14 +87,14 @@ class UIKIT_EXPORT Label : public Widget { int m_alignment; - float m_fontWeight; + int m_flags; - bool m_kerning; - - bool m_wrap; + float m_fontWeight; bool m_dirty; + bool m_translated; + }; #endif // LABEL_H diff --git a/modules/uikit/src/components/label.cpp b/modules/uikit/src/components/label.cpp index e9b8cf441..d47f27e39 100644 --- a/modules/uikit/src/components/label.cpp +++ b/modules/uikit/src/components/label.cpp @@ -20,8 +20,8 @@ namespace { const char *gColor("mainColor"); const char *gTexture("mainTexture"); - const char *gClipRect("clipRect"); const char *gWeight("weight"); + const char *gUseSDF("useSdf"); }; /*! @@ -41,9 +41,9 @@ Label::Label() : m_size(16), m_alignment(Left), m_fontWeight(0.5f), - m_kerning(true), - m_wrap(false), - m_dirty(true) { + m_flags(Font::Wrap), + m_dirty(true), + m_translated(false) { m_mesh->makeDynamic(); @@ -58,6 +58,7 @@ Label::~Label() { if(m_font) { m_font->unsubscribe(this); } + m_font = nullptr; } /*! \internal @@ -68,11 +69,12 @@ void Label::draw(CommandBuffer &buffer) { if(m_dirty && m_font) { m_mesh->setName(actor()->name()); - m_font->composeMesh(m_mesh, m_text, m_size, m_alignment, m_kerning, m_wrap, m_meshSize); - - if(m_material) { - m_material->setTexture(gTexture, m_font->page()); - } + m_font->composeMesh(m_mesh, + m_translated ? Engine::translate(m_text) : m_text, + m_size, m_alignment, m_flags, m_meshSize); + m_material->setTexture(gTexture, m_font->page()); + bool sdf = m_flags & Font::Sdf; + m_material->setBool(gUseSDF, &sdf); m_dirty = false; } @@ -190,18 +192,37 @@ void Label::setColor(const Vector4 &color) { m_material->setVector4(gColor, &m_color); } } +/*! + Returns true if text in label must be translated; othewise returns false. +*/ +bool Label::translated() const { + return m_translated; +} +/*! + Sets \a enable or disable translation from dictionary for current label. +*/ +void Label::setTranslated(bool enable) { + if(m_translated != enable) { + m_translated = enable; + m_dirty = true; + } +} /*! Returns true if word wrap enabled; otherwise returns false. */ bool Label::wordWrap() const { - return m_wrap; + return m_flags & Font::Wrap; } /*! Sets the word \a wrap policy. Set true to enable word wrap and false to disable. */ void Label::setWordWrap(bool wrap) { - if(m_wrap != wrap) { - m_wrap = wrap; + if(wordWrap() != wrap) { + if(wrap) { + m_flags |= Font::Wrap; + } else { + m_flags &= ~Font::Wrap; + } m_dirty = true; } } @@ -224,15 +245,19 @@ void Label::setAlign(int alignment) { Returns true if glyph kerning enabled; otherwise returns false. */ bool Label::kerning() const { - return m_kerning; + return m_flags & Font::Kerning; } /*! - Set true to enable glyph \a kerning and false to disable. + Set true to \a enable glyph kerning and false to disable. \note Glyph kerning functionality depends on fonts which you are using. In case of font doesn't support kerning, you will not see the difference. */ -void Label::setKerning(const bool kerning) { - if(m_kerning != kerning) { - m_kerning = kerning; +void Label::setKerning(const bool enable) { + if(kerning() != enable) { + if(enable) { + m_flags |= Font::Kerning; + } else { + m_flags &= ~Font::Kerning; + } m_dirty = true; } } @@ -241,20 +266,7 @@ void Label::setKerning(const bool kerning) { */ Vector2 Label::cursorAt(int position) { std::u32string u32 = m_text.toUtf32(); - return Vector2(m_font->textWidth(TString::fromUtf32(u32.substr(0, position)), m_size, m_kerning), 0.0f); -} -/*! - \internal -*/ -void Label::setClipOffset(const Vector2 &offset) { - if(m_clipOffset != offset) { - m_clipOffset = offset; - if(m_material) { - Vector4 clipRect(m_clipOffset, m_meshSize.x + m_clipOffset.x, m_meshSize.y + m_clipOffset.y); - - m_material->setVector4(gClipRect, &clipRect); - } - } + return Vector2(m_font->textWidth(TString::fromUtf32(u32.substr(0, position)), m_size, m_flags), 0.0f); } /*! \internal @@ -307,12 +319,6 @@ void Label::boundChanged(const Vector2 &size) { if(m_meshSize != size) { m_meshSize = size; m_dirty = true; - - if(m_material) { - Vector4 clipRect(m_clipOffset, m_meshSize.x + m_clipOffset.x, m_meshSize.y + m_clipOffset.y); - - m_material->setVector4(gClipRect, &clipRect); - } } } /*! diff --git a/worldeditor/bin/engine/materials/DefaultFont.shader b/worldeditor/bin/engine/materials/DefaultFont.shader index 90702f800..7daa322a9 100644 --- a/worldeditor/bin/engine/materials/DefaultFont.shader +++ b/worldeditor/bin/engine/materials/DefaultFont.shader @@ -3,6 +3,7 @@ + Date: Fri, 6 Feb 2026 21:42:11 +0300 Subject: [PATCH 2/2] update --- engine/src/components/textrender.cpp | 1 + engine/src/resources/font.cpp | 12 +++++------- modules/uikit/src/components/label.cpp | 3 +++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/engine/src/components/textrender.cpp b/engine/src/components/textrender.cpp index 4dcd640fd..5747ca692 100644 --- a/engine/src/components/textrender.cpp +++ b/engine/src/components/textrender.cpp @@ -57,6 +57,7 @@ Mesh *TextRender::meshToDraw(int instance) { A_UNUSED(instance); if(m_dirtyMesh && m_font && !m_text.isEmpty()) { m_font->composeMesh(m_mesh, m_translated ? Engine::translate(m_text) : m_text, m_size, m_alignment, m_flags, m_boundaries); + m_mesh->setColors(Vector4Vector(m_mesh->vertices().size(), Vector4(1.0f))); m_dirtyMesh = false; } diff --git a/engine/src/resources/font.cpp b/engine/src/resources/font.cpp index 782976fca..ef8e773bf 100644 --- a/engine/src/resources/font.cpp +++ b/engine/src/resources/font.cpp @@ -57,6 +57,7 @@ void Font::requestCharacters(const std::u32string &characters, uint32_t size) { bool isNew = false; for(auto it : characters) { uint32_t ch = it; + Mathf::hashCombine(ch, size); if(m_shapes.find(ch) == m_shapes.end()) { error = FT_Load_Glyph(face, FT_Get_Char_Index(face, it), FT_LOAD_RENDER); if(!error) { @@ -129,11 +130,13 @@ float Font::textWidth(const TString &text, int size, int flags) { float spaceWidth = 0; FT_Error error = FT_Load_Glyph( face, FT_Get_Char_Index( face, ' ' ), FT_LOAD_BITMAP_METRICS_ONLY ); if(!error) { - spaceWidth = face->glyph->advance.x / (DF_GLYPH_SIZE * 64.0f) * size; + spaceWidth = (adjustedSize == DF_GLYPH_SIZE) ? (DF_GLYPH_SIZE * 64.0f * size) : 64.0f; + spaceWidth = face->glyph->advance.x / spaceWidth; } for(uint32_t i = 0; i < length; i++) { uint32_t ch = u32[i]; + Mathf::hashCombine(ch, size); switch(ch) { case ' ': { pos += spaceWidth; @@ -195,7 +198,6 @@ void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, vertices.resize(length * 4); indices.resize(length * 6); uv0.resize(vertices.size()); - colors.resize(vertices.size()); std::list width; std::list position; @@ -207,6 +209,7 @@ void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, for(uint32_t i = 0; i < length; i++) { uint32_t ch = u32[i]; + Mathf::hashCombine(ch, size); switch(ch) { case ' ': { pos += Vector3(spaceWidth, 0.0f, 0.0f); @@ -262,11 +265,6 @@ void Font::composeMesh(Mesh *mesh, const TString &text, int size, int alignment, uv0[it * 4 + 2] = uv[2]; uv0[it * 4 + 3] = uv[3]; - colors[it * 4 + 0] = Vector4(1.0f); - colors[it * 4 + 1] = Vector4(1.0f); - colors[it * 4 + 2] = Vector4(1.0f); - colors[it * 4 + 3] = Vector4(1.0f); - indices[it * 6 + 0] = it * 4 + 0; indices[it * 6 + 1] = it * 4 + 1; indices[it * 6 + 2] = it * 4 + 2; diff --git a/modules/uikit/src/components/label.cpp b/modules/uikit/src/components/label.cpp index d47f27e39..332dbedf5 100644 --- a/modules/uikit/src/components/label.cpp +++ b/modules/uikit/src/components/label.cpp @@ -72,6 +72,9 @@ void Label::draw(CommandBuffer &buffer) { m_font->composeMesh(m_mesh, m_translated ? Engine::translate(m_text) : m_text, m_size, m_alignment, m_flags, m_meshSize); + + m_mesh->setColors(Vector4Vector(m_mesh->vertices().size(), Vector4(1.0f))); + m_material->setTexture(gTexture, m_font->page()); bool sdf = m_flags & Font::Sdf; m_material->setBool(gUseSDF, &sdf);