diff options
Diffstat (limited to 'src/Text.cpp')
-rw-r--r-- | src/Text.cpp | 590 |
1 files changed, 350 insertions, 240 deletions
diff --git a/src/Text.cpp b/src/Text.cpp index 14aebb1..69d6664 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -3,13 +3,13 @@ #include "../include/Config.hpp" #include "../include/Theme.hpp" #include "../generated/Emoji.hpp" -#include <SFML/Graphics/RectangleShape.hpp> -#include <SFML/Window/Clipboard.hpp> -#include <SFML/Window/Event.hpp> -#include <SFML/Graphics/RenderTarget.hpp> -#include <SFML/Graphics/Font.hpp> +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/window/Event.hpp> +#include <mglpp/window/Window.hpp> +#include <mglpp/graphics/Font.hpp> +#include <mglpp/graphics/Texture.hpp> +#include <mglpp/system/Utf8.hpp> #include <cmath> -#include <functional> namespace QuickMedia { @@ -21,19 +21,8 @@ namespace QuickMedia static const size_t FONT_INDEX_SYMBOLS = 2; static const size_t FONT_INDEX_EMOJI = 3; static const size_t FONT_ARRAY_SIZE = 4; - - size_t StringViewUtf32::find(const StringViewUtf32 &other, size_t offset) const { - if(offset >= size) - return -1; - - auto it = std::search(data + offset, data + size - offset, std::boyer_moore_searcher(other.data, other.data + other.size)); - if(it != data + size) - return it - data; - - return -1; - } - Text::Text(sf::String _str, bool bold_font, unsigned int characterSize, float maxWidth, bool highlight_urls) : + Text::Text(std::string _str, bool bold_font, unsigned int characterSize, float maxWidth, bool highlight_urls) : bold_font(bold_font), characterSize(characterSize), maxWidth(maxWidth), @@ -50,52 +39,54 @@ namespace QuickMedia caretIndex(0), caret_offset_x(0.0f) { - for(size_t i = 0; i < FONT_ARRAY_SIZE; ++i) { - vertices[i].setPrimitiveType(sf::PrimitiveType::Triangles); - vertex_buffers[i] = sf::VertexBuffer(sf::PrimitiveType::Triangles, sf::VertexBuffer::Static); - } setString(std::move(_str)); } - void Text::setString(const sf::String &str) + void Text::setString(std::string str) { //if(str != this->str) //{ - size_t prev_str_size = this->str.getSize(); - this->str = str; + size_t prev_str_size = this->str.size(); + this->str = std::move(str); dirty = true; dirtyText = true; - if((int)this->str.getSize() < caretIndex || prev_str_size == 0) + if((int)this->str.size() < caretIndex || prev_str_size == 0) { - caretIndex = this->str.getSize(); + caretIndex = this->str.size(); dirtyCaret = true; } // } } - const sf::String& Text::getString() const + const std::string& Text::getString() const { return str; } - void Text::appendText(const sf::String &str) { + void Text::appendText(const std::string &str) { this->str += str; dirty = true; dirtyText = true; } + + void Text::insert_text_at_caret_position(const std::string &str) { + this->str.insert(caretIndex, str); + dirty = true; + dirtyText = true; + } - void Text::setPosition(float x, float y) + void Text::set_position(float x, float y) { position.x = x; position.y = y; } - void Text::setPosition(const sf::Vector2f &position) + void Text::set_position(const mgl::vec2f &position) { this->position = position; } - sf::Vector2f Text::getPosition() const + mgl::vec2f Text::get_position() const { return position; } @@ -106,7 +97,7 @@ namespace QuickMedia if(std::abs(maxWidth - this->maxWidth) > 1.0f) { this->maxWidth = maxWidth; - if(num_lines > 1 || maxWidth < boundingBox.width) { + if(num_lines > 1 || maxWidth < boundingBox.size.x) { dirty = true; dirtyCaret = true; } @@ -127,13 +118,13 @@ namespace QuickMedia } } - unsigned int Text::getCharacterSize() const + unsigned int Text::get_character_size() const { return characterSize; } - void Text::replace(size_t start_index, size_t length, const sf::String &insert_str) { - int string_diff = (int)insert_str.getSize() - (int)length; + void Text::replace(size_t start_index, size_t length, const std::string &insert_str) { + int string_diff = (int)insert_str.size() - (int)length; str.replace(start_index, length, insert_str); dirty = true; dirtyText = true; @@ -147,7 +138,7 @@ namespace QuickMedia return caretIndex; } - void Text::setFillColor(sf::Color color) + void Text::set_color(mgl::Color color) { if(color != this->color) { @@ -197,16 +188,16 @@ namespace QuickMedia float Text::getWidth() const { - return boundingBox.width; + return boundingBox.size.x; } float Text::getHeight() const { - return boundingBox.height; + return boundingBox.size.y; } // TODO: Is there a more efficient way to do this? maybe japanese characters have a specific bit-pattern? - static bool is_japanese_codepoint(sf::Uint32 codepoint) { + static bool is_japanese_codepoint(uint32_t codepoint) { return (codepoint >= 0x2E80 && codepoint <= 0x2FD5) // Kanji radicals || (codepoint >= 0x3000 && codepoint <= 0x303F) // Punctuation || (codepoint >= 0x3041 && codepoint <= 0x3096) // Hiragana @@ -221,12 +212,12 @@ namespace QuickMedia || (codepoint >= 0xFF5F && codepoint <= 0xFF9F); // Katakana and punctuation (half width) } - static bool is_korean_codepoint(sf::Uint32 codepoint) { + static bool is_korean_codepoint(uint32_t codepoint) { return codepoint >= 0xAC00 && codepoint <= 0xD7A3; } // TODO: Is there a more efficient way to do this? maybe chinese characters have a specific bit-pattern? - static bool is_chinese_codepoint(sf::Uint32 codepoint) { + static bool is_chinese_codepoint(uint32_t codepoint) { return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) // CJK Unified Ideographs || (codepoint >= 0x3400 && codepoint <= 0x4DBF) // CJK Unified Ideographs Extension A || (codepoint >= 0x20000 && codepoint <= 0x2A6DF) // CJK Unified Ideographs Extension B @@ -238,44 +229,84 @@ namespace QuickMedia } // TODO: Merge chinese, japanese and korean codepoints into one function since they share ranges - static bool is_cjk_codepoint(sf::Uint32 codepoint) { + static bool is_cjk_codepoint(uint32_t codepoint) { return is_chinese_codepoint(codepoint) || is_japanese_codepoint(codepoint) || is_korean_codepoint(codepoint); } - static bool is_symbol_codepoint(sf::Uint32 codepoint) { + static bool is_symbol_codepoint(uint32_t codepoint) { // TODO: Add the remaining NotoSansSymbols2-Regular.ttf codepoints as well. // See codepoint ranges with: fc-query --format='%{charset}\n' /usr/share/fonts/noto/NotoSansSymbols2-Regular.ttf. return codepoint >= 0x2800 && codepoint <= 0x28FF; // Braille } - static size_t find_end_of_cjk(const sf::Uint32 *str, size_t size) { - for(size_t i = 0; i < size; ++i) { - if(!is_cjk_codepoint(str[i])) + static size_t find_end_of_cjk(const char *str, size_t size) { + for(size_t i = 0; i < size;) { + const unsigned char *cp = (const unsigned char*)&str[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, size - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + if(!is_cjk_codepoint(codepoint)) return i; + + i += clen; } return size; } - static size_t find_end_of_emoji(const sf::Uint32 *str, size_t size) { - for(size_t i = 0; i < size; ++i) { - if(!codepoint_is_emoji(str[i])) + static size_t find_end_of_emoji(const char *str, size_t size) { + for(size_t i = 0; i < size;) { + const unsigned char *cp = (const unsigned char*)&str[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, size - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + if(!codepoint_is_emoji(codepoint)) return i; + + i += clen; } return size; } - static size_t find_end_of_symbol(const sf::Uint32 *str, size_t size) { - for(size_t i = 0; i < size; ++i) { - if(!is_symbol_codepoint(str[i])) + static size_t find_end_of_symbol(const char *str, size_t size) { + for(size_t i = 0; i < size;) { + const unsigned char *cp = (const unsigned char*)&str[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, size - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + if(!is_symbol_codepoint(codepoint)) return i; + + i += clen; } return size; } - static size_t find_end_latin(const sf::Uint32 *str, size_t size) { - for(size_t i = 0; i < size; ++i) { - if(is_cjk_codepoint(str[i]) || codepoint_is_emoji(str[i]) || is_symbol_codepoint(str[i])) + static size_t find_end_latin(const char *str, size_t size) { + for(size_t i = 0; i < size;) { + const unsigned char *cp = (const unsigned char*)&str[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, size - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + if(is_cjk_codepoint(codepoint) || codepoint_is_emoji(codepoint) || is_symbol_codepoint(codepoint)) return i; + + i += clen; } return size; } @@ -283,30 +314,39 @@ namespace QuickMedia void Text::splitTextByFont() { textElements.clear(); size_t index = 0; - size_t size = str.getSize(); + size_t size = str.size(); while(index < size) { + const unsigned char *cp = (const unsigned char*)&str[index]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, size - index, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + size_t offset; TextElement::TextType text_type = TextElement::TextType::LATIN; - if(is_symbol_codepoint(str[index])) { + if(is_symbol_codepoint(codepoint)) { text_type = TextElement::TextType::SYMBOL; - offset = find_end_of_symbol(str.getData() + index + 1, size - index - 1); - } else if(is_cjk_codepoint(str[index])) { + offset = find_end_of_symbol(str.data() + index, size - index); + } else if(is_cjk_codepoint(codepoint)) { text_type = TextElement::TextType::CJK; - offset = find_end_of_cjk(str.getData() + index + 1, size - index - 1); - } else if(codepoint_is_emoji(str[index])) { + offset = find_end_of_cjk(str.data() + index, size - index); + } else if(codepoint_is_emoji(codepoint)) { text_type = TextElement::TextType::EMOJI; - offset = find_end_of_emoji(str.getData() + index + 1, size - index - 1); + offset = find_end_of_emoji(str.data() + index, size - index); } else { - offset = find_end_latin(str.getData() + index + 1, size - index - 1); + offset = find_end_latin(str.data() + index, size - index); } - textElements.push_back({ StringViewUtf32(str.getData() + index, offset + 1), TextElement::Type::TEXT }); + + textElements.push_back({ std::string_view(str.data() + index, offset), TextElement::Type::TEXT }); textElements.back().text_type = text_type; - index += 1 + offset; + index += offset; } } - float Text::font_get_real_height(sf::Font *font) { - return font->getGlyph('|', characterSize, false).bounds.height + std::floor(4.0f * ((float)characterSize / (float)14.0f)); + float Text::font_get_real_height(mgl::Font *font) { + return font->get_glyph('|').size.y + std::floor(4.0f * ((float)characterSize / (float)14.0f)); } float Text::get_text_quad_left_side(const VertexRef &vertex_ref) const { @@ -350,7 +390,7 @@ namespace QuickMedia } } - sf::Uint32 Text::get_vertex_codepoint(int index) const { + uint32_t Text::get_vertex_codepoint(int index) const { const int num_vertices = vertices_linear.size(); if(num_vertices == 0) { return 0; @@ -368,10 +408,7 @@ namespace QuickMedia splitTextByFont(); // TODO: Optimize if(highlight_urls) { - auto u8 = str.toUtf8(); - std::string *u8_str = (std::string*)&u8; - url_ranges = extract_urls(*u8_str); - convert_utf8_to_utf32_ranges(*u8_str, url_ranges); + url_ranges = extract_urls(str); } else { url_ranges.clear(); } @@ -384,81 +421,88 @@ namespace QuickMedia vertices_linear.clear(); for(size_t i = 0; i < FONT_ARRAY_SIZE; ++i) { vertices[i].clear(); - vertices[i].resize(0); + vertices[i].shrink_to_fit(); } - boundingBox = sf::FloatRect(); + boundingBox = mgl::FloatRect(mgl::vec2f(0.0f, 0.0f), mgl::vec2f(0.0f, 0.0f)); - sf::Font *latin_font; + mgl::Font *latin_font; if(bold_font) - latin_font = FontLoader::get_font(FontLoader::FontType::LATIN_BOLD); + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN_BOLD, characterSize); else - latin_font = FontLoader::get_font(FontLoader::FontType::LATIN); + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN, characterSize); - const float latin_font_width = latin_font->getGlyph(' ', characterSize, false).advance; + const float latin_font_width = latin_font->get_glyph(' ').advance; const float hspace = latin_font_width + characterSpacing; const float vspace = font_get_real_height(latin_font); const float emoji_scale = vspace / 20.0f; - const sf::Color url_color = get_theme().url_text_color; + const mgl::Color url_color = get_theme().url_text_color; size_t url_range_index = 0; - sf::Vector2f glyphPos; - sf::Uint32 prevCodePoint = 0; + mgl::vec2f glyphPos; + uint32_t prevCodePoint = 0; // TODO: Only do this if dirtyText (then the Text object shouldn't be reset in Body. There should be a cleanup function in text instead) for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex) { TextElement &textElement = textElements[textElementIndex]; - const sf::Font *ff = latin_font; + mgl::Font *ff = latin_font; int vertices_index = FONT_INDEX_LATIN; prevCodePoint = 0; if(textElement.text_type == TextElement::TextType::CJK) { - ff = FontLoader::get_font(FontLoader::FontType::CJK); + ff = FontLoader::get_font(FontLoader::FontType::CJK, characterSize); vertices_index = FONT_INDEX_CJK; } else if(textElement.text_type == TextElement::TextType::SYMBOL) { - ff = FontLoader::get_font(FontLoader::FontType::SYMBOLS); + ff = FontLoader::get_font(FontLoader::FontType::SYMBOLS, characterSize); vertices_index = FONT_INDEX_SYMBOLS; } else if(textElement.text_type == TextElement::TextType::EMOJI) { vertices_index = FONT_INDEX_EMOJI; - textElement.position = glyphPos; - sf::Color emoji_color(255, 255, 255, color.a); - for(size_t i = 0; i < textElement.text.size; ++i) + mgl::Color emoji_color(255, 255, 255, color.a); + for(size_t i = 0; i < textElement.text.size();) { - sf::Uint32 codePoint = textElement.text[i]; - int vertexStart = vertices[vertices_index].getVertexCount(); - EmojiRectangle emoji_rec = emoji_get_extents(codePoint); - + const unsigned char *cp = (const unsigned char*)&textElement.text[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, textElement.text.size() - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + int vertexStart = vertices[vertices_index].size(); + EmojiRectangle emoji_rec = emoji_get_extents(codepoint); + const float font_height_offset = std::floor(-vspace * 0.2f); - sf::Vector2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - std::floor(emoji_rec.height * emoji_scale) * 0.5f); - sf::Vector2f vertexTopRight(glyphPos.x + std::floor(emoji_rec.width * emoji_scale), glyphPos.y + font_height_offset - std::floor(emoji_rec.height * emoji_scale) * 0.5f); - sf::Vector2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset + emoji_rec.height * emoji_scale * 0.5f); - sf::Vector2f vertexBottomRight(glyphPos.x + std::floor(emoji_rec.width * emoji_scale), glyphPos.y + font_height_offset + std::floor(emoji_rec.height * emoji_scale) * 0.5f); + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - std::floor(emoji_rec.height * emoji_scale) * 0.5f); + mgl::vec2f vertexTopRight(glyphPos.x + std::floor(emoji_rec.width * emoji_scale), glyphPos.y + font_height_offset - std::floor(emoji_rec.height * emoji_scale) * 0.5f); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset + emoji_rec.height * emoji_scale * 0.5f); + mgl::vec2f vertexBottomRight(glyphPos.x + std::floor(emoji_rec.width * emoji_scale), glyphPos.y + font_height_offset + std::floor(emoji_rec.height * emoji_scale) * 0.5f); - sf::Vector2f textureTopLeft(emoji_rec.x, emoji_rec.y); - sf::Vector2f textureTopRight(emoji_rec.x + emoji_rec.width, emoji_rec.y); - sf::Vector2f textureBottomLeft(emoji_rec.x, emoji_rec.y + emoji_rec.height); - sf::Vector2f textureBottomRight(emoji_rec.x + emoji_rec.width, emoji_rec.y + emoji_rec.height); + mgl::vec2f textureTopLeft(emoji_rec.x, emoji_rec.y); + mgl::vec2f textureTopRight(emoji_rec.x + emoji_rec.width, emoji_rec.y); + mgl::vec2f textureBottomLeft(emoji_rec.x, emoji_rec.y + emoji_rec.height); + mgl::vec2f textureBottomRight(emoji_rec.x + emoji_rec.width, emoji_rec.y + emoji_rec.height); - vertices[vertices_index].append({ vertexTopRight, emoji_color, textureTopRight }); - vertices[vertices_index].append({ vertexTopLeft, emoji_color, textureTopLeft }); - vertices[vertices_index].append({ vertexBottomLeft, emoji_color, textureBottomLeft }); - vertices[vertices_index].append({ vertexBottomLeft, emoji_color, textureBottomLeft }); - vertices[vertices_index].append({ vertexBottomRight, emoji_color, textureBottomRight }); - vertices[vertices_index].append({ vertexTopRight, emoji_color, textureTopRight }); + vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, emoji_color); + vertices[vertices_index].emplace_back(vertexTopLeft, textureTopLeft, emoji_color); + vertices[vertices_index].emplace_back(vertexBottomLeft, textureBottomLeft, emoji_color); + vertices[vertices_index].emplace_back(vertexBottomLeft, textureBottomLeft, emoji_color); + vertices[vertices_index].emplace_back(vertexBottomRight, textureBottomRight, emoji_color); + vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, emoji_color); glyphPos.x += std::floor(emoji_rec.width * emoji_scale) + characterSpacing; - vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); + vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + + i += clen; } continue; } - //vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * textElement.text.size); // TODO: Precalculate - textElement.position = glyphPos; - for(size_t i = 0; i < textElement.text.size; ++i) + //vertices[vertices_index].resize(vertices[vertices_index].size() + 4 * textElement.text.size); // TODO: Precalculate + for(size_t i = 0; i < textElement.text.size();) { - sf::Color text_color = color; + mgl::Color text_color = color; if(url_range_index < url_ranges.size()) { - size_t string_offset = (textElement.text.data + i) - str.getData(); + size_t string_offset = (textElement.text.data() + i) - str.data(); if(string_offset >= url_ranges[url_range_index].start && string_offset < url_ranges[url_range_index].start + url_ranges[url_range_index].length) { text_color = url_color; text_color.a = color.a; @@ -467,96 +511,107 @@ namespace QuickMedia } } - sf::Uint32 codePoint = textElement.text[i]; + const unsigned char *cp = (const unsigned char*)&textElement.text[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, textElement.text.size() - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } // TODO: Make this work when combining multiple different fonts (for example latin and japanese). // For japanese we could use a hack, because all japanese characters are monospace (exception being half-width characters). - float kerning = ff->getKerning(prevCodePoint, codePoint, characterSize); - prevCodePoint = codePoint; + // TODO: + float kerning = ff->get_kerning(prevCodePoint, codepoint); + //float kerning = 0.0f; + prevCodePoint = codepoint; glyphPos.x += kerning; - int vertexStart = vertices[vertices_index].getVertexCount(); + int vertexStart = vertices[vertices_index].size(); - switch(codePoint) + i += clen; + switch(codepoint) { case ' ': { - sf::Vector2f vertexTopLeft(glyphPos.x, glyphPos.y - vspace); - sf::Vector2f vertexTopRight(glyphPos.x + hspace, glyphPos.y - vspace); - sf::Vector2f vertexBottomLeft(glyphPos.x, glyphPos.y); - sf::Vector2f vertexBottomRight(glyphPos.x + hspace, glyphPos.y); - - vertices[vertices_index].append({ vertexTopRight, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexTopLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomRight, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexTopRight, sf::Color::Transparent, sf::Vector2f() }); + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y - vspace); + mgl::vec2f vertexTopRight(glyphPos.x + hspace, glyphPos.y - vspace); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y); + mgl::vec2f vertexBottomRight(glyphPos.x + hspace, glyphPos.y); + + vertices[vertices_index].emplace_back(vertexTopRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexTopLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexTopRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); glyphPos.x += hspace; - vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); - continue; + vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + break; } case '\t': { const float char_width = hspace * TAB_WIDTH; - sf::Vector2f vertexTopLeft(glyphPos.x, glyphPos.y - vspace); - sf::Vector2f vertexTopRight(glyphPos.x + char_width, glyphPos.y - vspace); - sf::Vector2f vertexBottomLeft(glyphPos.x, glyphPos.y); - sf::Vector2f vertexBottomRight(glyphPos.x + char_width, glyphPos.y); - - vertices[vertices_index].append({ vertexTopRight, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexTopLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomRight, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexTopRight, sf::Color::Transparent, sf::Vector2f() }); + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y - vspace); + mgl::vec2f vertexTopRight(glyphPos.x + char_width, glyphPos.y - vspace); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y); + mgl::vec2f vertexBottomRight(glyphPos.x + char_width, glyphPos.y); + + vertices[vertices_index].emplace_back(vertexTopRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexTopLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexTopRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); glyphPos.x += char_width; - vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); - continue; + vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + break; } case '\n': { - sf::Vector2f vertexTopLeft(glyphPos.x, glyphPos.y - vspace); - sf::Vector2f vertexTopRight(glyphPos.x, glyphPos.y - vspace); - sf::Vector2f vertexBottomLeft(glyphPos.x, glyphPos.y); - sf::Vector2f vertexBottomRight(glyphPos.x, glyphPos.y); - - vertices[vertices_index].append({ vertexTopRight, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexTopLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomLeft, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexBottomRight, sf::Color::Transparent, sf::Vector2f() }); - vertices[vertices_index].append({ vertexTopRight, sf::Color::Transparent, sf::Vector2f() }); + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y - vspace); + mgl::vec2f vertexTopRight(glyphPos.x, glyphPos.y - vspace); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y); + mgl::vec2f vertexBottomRight(glyphPos.x, glyphPos.y); + + vertices[vertices_index].emplace_back(vertexTopRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexTopLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomLeft, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexBottomRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); + vertices[vertices_index].emplace_back(vertexTopRight, mgl::vec2f(), mgl::Color(0, 0, 0, 0)); glyphPos.x = 0.0f; glyphPos.y += floor(vspace + lineSpacing); - vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); - continue; + vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + break; } - } + default: { + mgl::FontGlyph glyph = ff->get_glyph(codepoint); - const sf::Glyph &glyph = ff->getGlyph(codePoint, characterSize, false); - - sf::Vector2f vertexTopLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top); - sf::Vector2f vertexTopRight(glyphPos.x + glyph.bounds.left + glyph.bounds.width, glyphPos.y + glyph.bounds.top); - sf::Vector2f vertexBottomLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top + glyph.bounds.height); - sf::Vector2f vertexBottomRight(glyphPos.x + glyph.bounds.left + glyph.bounds.width, glyphPos.y + glyph.bounds.top + glyph.bounds.height); - - sf::Vector2f textureTopLeft(glyph.textureRect.left, glyph.textureRect.top); - sf::Vector2f textureTopRight(glyph.textureRect.left + glyph.textureRect.width, glyph.textureRect.top); - sf::Vector2f textureBottomLeft(glyph.textureRect.left, glyph.textureRect.top + glyph.textureRect.height); - sf::Vector2f textureBottomRight(glyph.textureRect.left + glyph.textureRect.width, glyph.textureRect.top + glyph.textureRect.height); - - vertices[vertices_index].append({ vertexTopRight, text_color, textureTopRight }); - vertices[vertices_index].append({ vertexTopLeft, text_color, textureTopLeft }); - vertices[vertices_index].append({ vertexBottomLeft, text_color, textureBottomLeft }); - vertices[vertices_index].append({ vertexBottomLeft, text_color, textureBottomLeft }); - vertices[vertices_index].append({ vertexBottomRight, text_color, textureBottomRight }); - vertices[vertices_index].append({ vertexTopRight, text_color, textureTopRight }); - - glyphPos.x += glyph.advance + characterSpacing; - vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); + mgl::vec2f vertexTopLeft(glyphPos.x + glyph.position.x, glyphPos.y + glyph.position.y); + mgl::vec2f vertexTopRight(glyphPos.x + glyph.position.x + glyph.size.x, glyphPos.y + glyph.position.y); + mgl::vec2f vertexBottomLeft(glyphPos.x + glyph.position.x, glyphPos.y + glyph.position.y + glyph.size.y); + mgl::vec2f vertexBottomRight(glyphPos.x + glyph.position.x + glyph.size.x, glyphPos.y + glyph.position.y + glyph.size.y); + + mgl::vec2f textureTopLeft(glyph.texture_position.x, glyph.texture_position.y); + mgl::vec2f textureTopRight(glyph.texture_position.x + glyph.texture_size.x, glyph.texture_position.y); + mgl::vec2f textureBottomLeft(glyph.texture_position.x, glyph.texture_position.y + glyph.texture_size.y); + mgl::vec2f textureBottomRight(glyph.texture_position.x + glyph.texture_size.x, glyph.texture_position.y + glyph.texture_size.y); + + vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, text_color); + vertices[vertices_index].emplace_back(vertexTopLeft, textureTopLeft, text_color); + vertices[vertices_index].emplace_back(vertexBottomLeft, textureBottomLeft, text_color); + vertices[vertices_index].emplace_back(vertexBottomLeft, textureBottomLeft, text_color); + vertices[vertices_index].emplace_back(vertexBottomRight, textureBottomRight, text_color); + vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, text_color); + + glyphPos.x += glyph.advance + characterSpacing; + vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + break; + } + } } } @@ -569,7 +624,7 @@ namespace QuickMedia for(int i = 0; i < (int)vertices_linear.size(); ++i) { VertexRef &vertex_ref = vertices_linear[i]; - sf::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; for(int v = 0; v < 6; ++v) { vertex[v].position.x -= text_wrap_offset; vertex[v].position.y += text_offset_y; @@ -598,7 +653,7 @@ namespace QuickMedia float vertex_left_side = get_text_quad_left_side(vertices_linear[last_space_index + 1]); for(int j = last_space_index + 1; j <= i; ++j) { VertexRef &vertex_ref_wrap = vertices_linear[j]; - sf::Vertex *vertex = &vertices[vertex_ref_wrap.vertices_index][vertex_ref_wrap.index]; + mgl::Vertex *vertex = &vertices[vertex_ref_wrap.vertices_index][vertex_ref_wrap.index]; for(int v = 0; v < 6; ++v) { vertex[v].position.x -= vertex_left_side; vertex[v].position.y += line_height; @@ -621,24 +676,23 @@ namespace QuickMedia } } - boundingBox.width = 0.0f; + boundingBox.size.x = 0.0f; for(VertexRef &vertex_ref : vertices_linear) { - boundingBox.width = std::max(boundingBox.width, get_text_quad_right_side(vertex_ref)); + boundingBox.size.x = std::max(boundingBox.size.x, get_text_quad_right_side(vertex_ref)); } - boundingBox.height = num_lines * line_height; + boundingBox.size.y = num_lines * line_height; // TODO: Clear |vertices| somehow even with editable text for(size_t i = 0; i < FONT_ARRAY_SIZE; ++i) { - vertex_buffers[i].create(vertices[i].getVertexCount()); - if(vertices[i].getVertexCount() > 0) - vertex_buffers[i].update(&vertices[i][0], vertices[i].getVertexCount(), 0); + // TODO: Use VertexBuffer::Dynamic for editable text? + vertex_buffers[i].update(vertices[i].data(), vertices[i].size(), mgl::PrimitiveType::Triangles, mgl::VertexBuffer::Static); } //url_ranges.clear(); if(!editable) { for(size_t i = 0; i < FONT_ARRAY_SIZE; ++i) { vertices[i].clear(); - vertices[i].resize(0); + vertices[i].shrink_to_fit(); } vertices_linear.clear(); vertices_linear.shrink_to_fit(); @@ -649,17 +703,17 @@ namespace QuickMedia { assert(!dirty && !dirtyText); - sf::Font *latin_font; + mgl::Font *latin_font; if(bold_font) - latin_font = FontLoader::get_font(FontLoader::FontType::LATIN_BOLD); + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN_BOLD, characterSize); else - latin_font = FontLoader::get_font(FontLoader::FontType::LATIN); + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN, characterSize); const float vspace = font_get_real_height(latin_font); if(vertices_linear.empty()) { caretIndex = 0; - caretPosition = sf::Vector2f(0.0f, floor(vspace)); + caretPosition = mgl::vec2f(0.0f, floor(vspace)); caret_offset_x = 0.0f; return; } @@ -749,7 +803,7 @@ namespace QuickMedia return num_vertices; } - static bool is_special_character(sf::Uint32 codepoint) { + static bool is_special_character(uint32_t codepoint) { return (codepoint <= 47) || (codepoint >= 58 && codepoint <= 64) || (codepoint >= 91 && codepoint <= 96) || (codepoint >= 123 && codepoint <= 127); } @@ -817,18 +871,56 @@ namespace QuickMedia return startIndex; } + // TODO: Optimize + size_t Text::get_string_index_from_caret_index(size_t caret_index) const { + size_t codepoint_index = 0; + for(size_t i = 0; i < str.size();) { + if(codepoint_index == caret_index) + return i; + + unsigned char *cp = (unsigned char*)&str[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, str.size() - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + i += clen; + ++codepoint_index; + } + return str.size(); + } + + static size_t utf8_get_length(const std::string &str) { + size_t codepoint_index = 0; + for(size_t i = 0; i < str.size();) { + unsigned char *cp = (unsigned char*)&str[i]; + uint32_t codepoint; + size_t clen; + if(!mgl::utf8_decode(cp, str.size() - i, &codepoint, &clen)) { + codepoint = *cp; + clen = 1; + } + + i += clen; + ++codepoint_index; + } + return codepoint_index; + } + // TODO: Optimize text editing by only processing the changed parts in updateGeometry. // TODO: Split text into lines and add to vertices list so the lines that are cut off are not visible. This is good when using the text and as text input // where there are a max number of rows shown at a time. - void Text::processEvent(const sf::Event &event) + void Text::processEvent(mgl::Window &window, const mgl::Event &event) { if(!editable) return; bool caretAtEnd = caretIndex == (int)vertices_linear.size(); - if(event.type == sf::Event::KeyPressed) + if(event.type == mgl::Event::KeyPressed) { - if(event.key.code == sf::Keyboard::Left && caretIndex > 0) + if(event.key.code == mgl::Keyboard::Left && caretIndex > 0) { if(event.key.control) caretMoveDirection = CaretMoveDirection::LEFT_WORD; @@ -836,7 +928,7 @@ namespace QuickMedia caretMoveDirection = CaretMoveDirection::LEFT; dirtyCaret = true; } - else if(event.key.code == sf::Keyboard::Right && !caretAtEnd) + else if(event.key.code == mgl::Keyboard::Right && !caretAtEnd) { if(event.key.control) caretMoveDirection = CaretMoveDirection::RIGHT_WORD; @@ -844,48 +936,60 @@ namespace QuickMedia caretMoveDirection = CaretMoveDirection::RIGHT; dirtyCaret = true; } - else if(event.key.code == sf::Keyboard::BackSpace && caretIndex > 0) + else if(event.key.code == mgl::Keyboard::Backspace && caretIndex > 0) { - str.erase(caretIndex - 1, 1); - --caretIndex; - dirty = true; - dirtyText = true; - dirtyCaret = true; + const size_t str_index = get_string_index_from_caret_index(caretIndex); + if(str_index > 0) { + const size_t codepoint_start = mgl::utf8_get_start_of_codepoint((const unsigned char*)str.c_str(), str.size(), str_index - 1); + str.erase(codepoint_start, str_index - codepoint_start); + --caretIndex; + dirty = true; + dirtyText = true; + dirtyCaret = true; + } } - else if(event.key.code == sf::Keyboard::Delete && !caretAtEnd) + else if(event.key.code == mgl::Keyboard::Delete && !caretAtEnd) { - str.erase(caretIndex, 1); + const size_t str_index = get_string_index_from_caret_index(caretIndex); + uint32_t decoded_codepoint = 0; + size_t decoded_length = 0; + mgl::utf8_decode((const unsigned char*)str.c_str() + str_index, str.size() - str_index, &decoded_codepoint, &decoded_length); + str.erase(str_index, decoded_length); dirty = true; dirtyText = true; } - else if(event.key.code == sf::Keyboard::D && event.key.control) + else if(event.key.code == mgl::Keyboard::D && event.key.control) { setString(""); } - else if(event.key.code == sf::Keyboard::Up) + else if(event.key.code == mgl::Keyboard::Up) { caretMoveDirection = CaretMoveDirection::UP; } - else if(event.key.code == sf::Keyboard::Down) + else if(event.key.code == mgl::Keyboard::Down) { caretMoveDirection = CaretMoveDirection::DOWN; } - else if(event.key.code == sf::Keyboard::Home) + else if(event.key.code == mgl::Keyboard::Home) { caretMoveDirection = CaretMoveDirection::HOME; } - else if(event.key.code == sf::Keyboard::End) + else if(event.key.code == mgl::Keyboard::End) { caretMoveDirection = CaretMoveDirection::END; } - else if(event.key.code == sf::Keyboard::Enter) + else if(event.key.code == mgl::Keyboard::Enter) { if(event.key.shift && !single_line_edit) { - if(caretAtEnd) + if(caretAtEnd) { str += '\n'; - else - str.insert(caretIndex, '\n'); + } else { + const size_t str_index = get_string_index_from_caret_index(caretIndex); + if(str_index > 0) { + str.insert(str_index, 1, '\n'); + } + } ++caretIndex; dirty = true; @@ -894,34 +998,38 @@ namespace QuickMedia } } } - else if(event.type == sf::Event::TextEntered) + else if(event.type == mgl::Event::TextEntered) { - if(event.text.unicode == 8 || event.text.unicode == 127) // backspace, del + if(event.text.codepoint == 8 || event.text.codepoint == 127) // backspace, del return; - sf::String stringToAdd; - if(event.text.unicode == 22) // ctrl+v + std::string stringToAdd; + if(event.text.codepoint == 22) // ctrl+v { - stringToAdd = sf::Clipboard::getString(); + stringToAdd = window.get_clipboard(); } - else if(event.text.unicode >= 32 || (event.text.unicode == '\t' && !single_line_edit)) - stringToAdd = event.text.unicode; + else if(event.text.codepoint >= 32 || (event.text.codepoint == '\t' && !single_line_edit)) + stringToAdd.assign(event.text.str, event.text.size); else return; - if(caretAtEnd) + if(caretAtEnd) { str += stringToAdd; - else - str.insert(caretIndex, stringToAdd); + } else { + const size_t str_index = get_string_index_from_caret_index(caretIndex); + if(str_index > 0 && str_index != std::string::npos) { + str.insert(str_index, stringToAdd); + } + } - caretIndex += stringToAdd.getSize(); + caretIndex += utf8_get_length(stringToAdd); dirty = true; dirtyText = true; dirtyCaret = true; } } - bool Text::draw(sf::RenderTarget &target) + bool Text::draw(mgl::Window &target) { updateGeometry(); @@ -931,35 +1039,34 @@ namespace QuickMedia caretMoveDirection = CaretMoveDirection::NONE; } - sf::Vector2f pos = position; + mgl::vec2f pos = position; FontLoader::FontType latin_font_type; if(bold_font) latin_font_type = FontLoader::FontType::LATIN_BOLD; else latin_font_type = FontLoader::FontType::LATIN; - sf::Font *latin_font = FontLoader::get_font(latin_font_type); + mgl::Font *latin_font = FontLoader::get_font(latin_font_type, characterSize); const float vspace = font_get_real_height(latin_font); pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left const FontLoader::FontType font_types[] = { latin_font_type, FontLoader::FontType::CJK, FontLoader::FontType::SYMBOLS }; for(size_t i = 0; i < FONT_INDEX_EMOJI; ++i) { - if(vertex_buffers[i].getVertexCount() == 0) + if(vertex_buffers[i].size() == 0) continue; - sf::Font *font = FontLoader::get_font(font_types[i]); - sf::RenderStates states; - states.transform.translate(pos); - states.texture = &font->getTexture(characterSize); - target.draw(vertex_buffers[i], states); + mgl::Font *font = FontLoader::get_font(font_types[i], characterSize); + mgl::Texture font_texture = font->get_texture(); + vertex_buffers[i].set_texture(&font_texture); + vertex_buffers[i].set_position(pos); + target.draw(vertex_buffers[i]); } - if(vertex_buffers[FONT_INDEX_EMOJI].getVertexCount() > 0) { - sf::RenderStates states; - states.transform.translate(pos); - states.texture = TextureLoader::get_texture("images/emoji.png"); - target.draw(vertex_buffers[FONT_INDEX_EMOJI], states); + if(vertex_buffers[FONT_INDEX_EMOJI].size() > 0) { + vertex_buffers[FONT_INDEX_EMOJI].set_texture(TextureLoader::get_texture("images/emoji.png", true)); + vertex_buffers[FONT_INDEX_EMOJI].set_position(pos); + target.draw(vertex_buffers[FONT_INDEX_EMOJI]); } if(!editable) return true; @@ -967,8 +1074,11 @@ namespace QuickMedia const float caret_margin = std::floor(2.0f * get_config().scale); - sf::RectangleShape caretRect(sf::Vector2f(std::floor(2.0f * get_config().scale), floor(vspace - caret_margin * 2.0f))); - caretRect.setPosition(floor(pos.x + caretPosition.x), floor(pos.y + caretPosition.y + caret_margin + std::floor(4.0f * get_config().scale))); + mgl::Rectangle caretRect(mgl::vec2f(0.0f, 0.0f), mgl::vec2f(std::floor(2.0f * get_config().scale), floor(vspace - caret_margin * 2.0f))); + caretRect.set_position(mgl::vec2f( + floor(pos.x + caretPosition.x), + floor(pos.y + caretPosition.y + caret_margin + std::floor(4.0f * get_config().scale)) + )); target.draw(caretRect); return true; } |