fix(icons): bypass HarfBuzz for icon rendering to support all Unicode codepoints

This commit is contained in:
Lemmy
2026-04-03 22:49:16 -04:00
parent 9a027d64e5
commit d388028e00
7 changed files with 74 additions and 30 deletions
+5 -4
View File
@@ -168,8 +168,8 @@ TextMetrics GlRenderer::measureText(std::string_view text, float fontSize) {
return TextMetrics{.width = m.width, .top = m.top, .bottom = m.bottom};
}
TextMetrics GlRenderer::measureIcon(std::string_view text, float fontSize) {
auto m = m_iconTextRenderer.measure(text, fontSize);
TextMetrics GlRenderer::measureGlyph(char32_t codepoint, float fontSize) {
auto m = m_iconTextRenderer.measureGlyph(codepoint, fontSize);
return TextMetrics{.width = m.width, .top = m.top, .bottom = m.bottom};
}
@@ -221,10 +221,11 @@ void GlRenderer::renderNode(const Node* node, float parentX, float parentY, floa
}
case NodeType::Icon: {
const auto* icon = static_cast<const IconNode*>(node);
if (!icon->text().empty()) {
if (icon->codepoint() != 0) {
auto color = icon->color();
color.a *= effectiveOpacity;
m_iconTextRenderer.draw(sw, sh, absX, absY, icon->text(), icon->fontSize(), color);
m_iconTextRenderer.drawGlyph(sw, sh, absX, absY,
icon->codepoint(), icon->fontSize(), color);
}
break;
}
+1 -1
View File
@@ -27,7 +27,7 @@ public:
void render() override;
void setScene(Node* root) override;
[[nodiscard]] TextMetrics measureText(std::string_view text, float fontSize) override;
[[nodiscard]] TextMetrics measureIcon(std::string_view text, float fontSize) override;
[[nodiscard]] TextMetrics measureGlyph(char32_t codepoint, float fontSize) override;
[[nodiscard]] TextureManager& textureManager() override;
private:
+1 -1
View File
@@ -27,6 +27,6 @@ public:
virtual void render() = 0;
virtual void setScene(Node* root) = 0;
[[nodiscard]] virtual TextMetrics measureText(std::string_view /*text*/, float /*fontSize*/) { return {}; }
[[nodiscard]] virtual TextMetrics measureIcon(std::string_view /*text*/, float /*fontSize*/) { return {}; }
[[nodiscard]] virtual TextMetrics measureGlyph(char32_t /*codepoint*/, float /*fontSize*/) { return {}; }
[[nodiscard]] virtual TextureManager& textureManager() = 0;
};
+4 -23
View File
@@ -3,39 +3,20 @@
#include "render/core/Color.hpp"
#include "render/scene/Node.hpp"
#include <string>
class IconNode : public Node {
public:
IconNode()
: Node(NodeType::Icon) {}
[[nodiscard]] const std::string& text() const noexcept { return m_text; }
[[nodiscard]] char32_t codepoint() const noexcept { return m_codepoint; }
[[nodiscard]] float fontSize() const noexcept { return m_fontSize; }
[[nodiscard]] const Color& color() const noexcept { return m_color; }
void setCodepoint(char32_t codepoint) {
// Encode as UTF-8
std::string encoded;
if (codepoint <= 0x7F) {
encoded += static_cast<char>(codepoint);
} else if (codepoint <= 0x7FF) {
encoded += static_cast<char>(0xC0 | (codepoint >> 6));
encoded += static_cast<char>(0x80 | (codepoint & 0x3F));
} else if (codepoint <= 0xFFFF) {
encoded += static_cast<char>(0xE0 | (codepoint >> 12));
encoded += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
encoded += static_cast<char>(0x80 | (codepoint & 0x3F));
} else if (codepoint <= 0x10FFFF) {
encoded += static_cast<char>(0xF0 | (codepoint >> 18));
encoded += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F));
encoded += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
encoded += static_cast<char>(0x80 | (codepoint & 0x3F));
}
if (m_text == encoded) {
if (m_codepoint == codepoint) {
return;
}
m_text = std::move(encoded);
m_codepoint = codepoint;
markDirty();
}
@@ -53,7 +34,7 @@ public:
}
private:
std::string m_text;
char32_t m_codepoint = 0;
float m_fontSize = 16.0f;
Color m_color;
};
+54
View File
@@ -225,6 +225,60 @@ void MsdfTextRenderer::draw(float surfaceWidth,
}
}
MsdfTextRenderer::TextMetrics MsdfTextRenderer::measureGlyph(char32_t codepoint, float fontSize) {
if (m_fontSlots.empty()) {
return {};
}
const auto glyphIndex = FT_Get_Char_Index(m_fontSlots[0].face, codepoint);
if (glyphIndex == 0) {
return {};
}
Glyph& glyph = loadGlyph(0, glyphIndex);
const float scale = fontSize / kAtlasEmSize;
FT_Set_Pixel_Sizes(m_fontSlots[0].face, 0, static_cast<FT_UInt>(kAtlasEmSize));
FT_Load_Glyph(m_fontSlots[0].face, glyphIndex, FT_LOAD_NO_HINTING);
const float advance = static_cast<float>(m_fontSlots[0].face->glyph->advance.x) / 64.0f * scale;
const float top = -glyph.bearingY * scale;
const float bottom = top + glyph.atlasHeight * scale;
const float width = std::max(glyph.bearingX * scale + glyph.atlasWidth * scale, advance);
return TextMetrics{.width = width, .top = top, .bottom = bottom};
}
void MsdfTextRenderer::drawGlyph(float surfaceWidth, float surfaceHeight,
float x, float baselineY,
char32_t codepoint, float fontSize, const Color& color) {
if (m_fontSlots.empty()) {
return;
}
const auto glyphIndex = FT_Get_Char_Index(m_fontSlots[0].face, codepoint);
if (glyphIndex == 0) {
return;
}
Glyph& glyph = loadGlyph(0, glyphIndex);
const float scale = fontSize / kAtlasEmSize;
const float pxRange = std::max(static_cast<float>(kDistanceRange) * scale, 1.0f);
if (glyph.atlasWidth > 0.0f && glyph.atlasHeight > 0.0f) {
const float glyphX = x + glyph.bearingX * scale;
const float glyphY = std::round(baselineY) - glyph.bearingY * scale;
const float glyphW = glyph.atlasWidth * scale;
const float glyphH = glyph.atlasHeight * scale;
GLuint atlasTexture = m_atlasPages[glyph.atlasPage];
m_program.draw(atlasTexture, surfaceWidth, surfaceHeight,
glyphX, glyphY, glyphW, glyphH,
glyph.u0, glyph.v0, glyph.u1, glyph.v1,
pxRange, color);
}
}
void MsdfTextRenderer::cleanup() {
for (auto tex : m_atlasPages) {
if (tex != 0) {
+8
View File
@@ -47,6 +47,14 @@ public:
std::string_view text,
float fontSize,
const Color& color);
// Direct codepoint rendering — bypasses HarfBuzz shaping.
// Use for icon fonts where codepoints may collide with Unicode control ranges.
[[nodiscard]] TextMetrics measureGlyph(char32_t codepoint, float fontSize);
void drawGlyph(float surfaceWidth, float surfaceHeight,
float x, float baselineY,
char32_t codepoint, float fontSize, const Color& color);
void cleanup();
private:
+1 -1
View File
@@ -35,7 +35,7 @@ void Icon::setColor(const Color& color) {
}
void Icon::measure(Renderer& renderer) {
auto metrics = renderer.measureIcon(m_iconNode->text(), m_iconNode->fontSize());
auto metrics = renderer.measureGlyph(m_iconNode->codepoint(), m_iconNode->fontSize());
Node::setSize(metrics.width, metrics.bottom - metrics.top);
m_iconNode->setPosition(0.0f, -metrics.top);
}