From 7211e99bc87b3ed69c690fca0a3cd64fc49b8fd1 Mon Sep 17 00:00:00 2001 From: boolean-false Date: Tue, 29 Sep 2026 20:31:14 +0700 Subject: [PATCH] fix text width and caret positioning in text boxes --- src/graphics/core/Font.cpp | 46 ++++++++++++++++++---------- src/graphics/core/Font.hpp | 10 ++++-- src/graphics/ui/elements/TextBox.cpp | 38 ++++++++++++++++------- 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index e94881adc..fde3e0c17 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -29,6 +29,10 @@ Font::Font( if (this->fontFile.has_value()) { if (auto fontPtr = this->fontFile->lock()) { monospace = fontPtr->isMonospace(); + if (monospace && this->glyphs.size() > '0' && + this->glyphs['0'].xAdvance > 0) { + glyphInterval = this->glyphs['0'].xAdvance; + } } } } @@ -62,21 +66,13 @@ bool Font::isPrintableChar(uint codepoint) const { int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const { auto font = this->font.has_value() ? this->font->lock() : nullptr; - if (font == nullptr || font->isMonospace()) { - return std::min(text.length() - offset, length) * _glyphInterval; + if (font != nullptr) { + return font->calcWidth(text, offset, length); } - int totalWidth = 0; - for (int i = offset; i < offset + length && i < text.length(); i++) { - auto codepoint = text[i]; - if (!font->isPrintableChar(codepoint)) { - totalWidth += _glyphInterval; - } else if (auto glyph = font->getGlyph(codepoint)) { - totalWidth += glyph->xAdvance; - } else { - totalWidth += _glyphInterval; - } + if (offset >= text.length()) { + return 0; } - return totalWidth; + return std::min(text.length() - offset, length) * _glyphInterval; } int Font::calcWidth(std::wstring_view text, size_t length) const { @@ -84,7 +80,24 @@ int Font::calcWidth(std::wstring_view text, size_t length) const { } int Font::calcWidth(std::wstring_view text, size_t offset, size_t length) const { - return std::min(text.length()-offset, length) * glyphInterval; + if (offset >= text.length()) { + return 0; + } + size_t end = offset + std::min(text.length() - offset, length); + int width = 0; + for (size_t i = offset; i < end; i++) { + width += getAdvance(text[i]); + } + return width; +} + +int Font::getAdvance(uint codepoint) const { + if (isPrintableChar(codepoint)) { + if (auto glyph = getGlyph(codepoint)) { + return glyph->xAdvance; + } + } + return glyphInterval; } static inline void draw_glyph( @@ -194,11 +207,10 @@ static inline void draw_text( } int yOffset = 0; int xOffset = 0; - float advance = 1.0f; + float advance = font.getAdvance(c) / cellAdvance; if (auto glyph = font.getGlyph(c)) { yOffset = glyph->yOffset; xOffset = glyph->xOffset; - advance = glyph->xAdvance / cellAdvance; } uint charpage = c >> 8; if (charpage == page){ @@ -325,7 +337,7 @@ std::unique_ptr Font::createBitmapFont( return std::make_unique(std::move(textures), std::move(glyphs), res, 4); } -const Glyph* Font::getGlyph(int codepoint) { +const Glyph* Font::getGlyph(int codepoint) const { if (codepoint < 0) { return nullptr; } diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index 19e77ef83..b20b5e530 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -57,6 +57,10 @@ public: /// @return pixel width of the substring int calcWidth(std::wstring_view text, size_t offset, size_t length) const; + /// @brief Get the horizontal advance of a character in pixels + /// @param codepoint character unicode codepoint + int getAdvance(uint codepoint) const; + /// @brief Check if character is visible (non-whitespace) /// @param codepoint character unicode codepoint bool isPrintableChar(uint codepoint) const; @@ -87,7 +91,7 @@ public: return {std::nullopt, lineHeight, yoffset, glyphInterval}; } - const Glyph* getGlyph(int codepoint); + const Glyph* getGlyph(int codepoint) const; static std::unique_ptr createBitmapFont( std::vector> pages @@ -97,7 +101,7 @@ private: int yoffset; int glyphInterval; bool monospace = true; - std::vector> pages; - std::vector glyphs; + mutable std::vector> pages; + mutable std::vector glyphs; std::optional> fontFile; }; diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index d60b09142..2f6a20301 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -255,9 +255,9 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { // drawing caret here if (isFocused() && editable && static_cast((time - caretLastMove) * 2) % 2 == 0) { uint line = label->getLineByTextIndex(caret); - auto linestart = label->getTextLineOffset(line); - uint lcaret = caret - label->getTextLineOffset(line); - int width = rawTextCache.metrics.calcWidth(input.substr(linestart), 0, lcaret); + size_t lineStart = label->getTextLineOffset(line); + size_t lineCaret = caret - lineStart; + int width = rawTextCache.metrics.calcWidth(input, lineStart, lineCaret); batch->rect( lcoord.x + width, @@ -275,11 +275,13 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { uint endLine = label->getLineByTextIndex(selectionEnd); batch->setColor(glm::vec4(0.8f, 0.9f, 1.0f, 0.25f)); + size_t startLineOffset = label->getTextLineOffset(startLine); + size_t endLineOffset = label->getTextLineOffset(endLine); int start = rawTextCache.metrics.calcWidth( - labelText, 0, selectionStart - label->getTextLineOffset(startLine) + labelText, startLineOffset, selectionStart - startLineOffset ); int end = rawTextCache.metrics.calcWidth( - labelText, 0, selectionEnd - label->getTextLineOffset(endLine) + labelText, endLineOffset, selectionEnd - endLineOffset ); int lineY = label->getLineYOffset(startLine); @@ -352,6 +354,11 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { } void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) { + if (lineNumbersLabel->getFontName() != label->getFontName()) { + lineNumbersLabel->setFontName(label->getFontName()); + } + lineNumbersLabel->setLineInterval(label->getLineInterval()); + auto font = assets.getShared(label->getFontName()); if (font != nullptr) { rawTextCache.prepare(font, font->getMetrics(), label->getSize().x); @@ -681,13 +688,19 @@ int TextBox::calcIndexAt(int x, int y) const { uint line = label->getLineByYOffset(y - lcoord.y); line = std::min(line, label->getLinesNumber() - 1); size_t lineLength = getLineLength(line); + size_t lineStart = label->getTextLineOffset(line); uint offset = 0; - while (lcoord.x + rawTextCache.metrics.calcWidth(labelText, 0, offset) < x && - offset < lineLength - 1) { + while (offset < lineLength - 1) { + int width = rawTextCache.metrics.calcWidth( + labelText, lineStart, offset + ); + if (lcoord.x + width >= x) { + break; + } offset++; } return std::min( - offset + label->getTextLineOffset(line), labelText.length() + offset + lineStart, labelText.length() ); } @@ -1224,9 +1237,12 @@ void TextBox::setCaret(size_t position) { offset -= getSize().y; scrolled(-glm::ceil(offset / static_cast(scrollStep) + 0.5f)); } - int lcaret = caret - rawTextCache.getTextLineOffset(line); - int realoffset = rawTextCache.metrics.calcWidth(labelText, 0, lcaret) - - static_cast(textOffset); + size_t lineStart = rawTextCache.getTextLineOffset(line); + size_t lineCaret = caret - lineStart; + int caretWidth = rawTextCache.metrics.calcWidth( + labelText, lineStart, lineCaret + ); + int realoffset = caretWidth - static_cast(textOffset); if (realoffset - width > 0) { setTextOffset(textOffset + realoffset - width);