mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
fix(icons): bypass HarfBuzz for icon rendering to support all Unicode codepoints
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user