From 4daa57f6d139f51a62ea4bcffa738bd5035df33a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 5 Nov 2022 15:53:28 +0100 Subject: Support as many emoji as possible, using separate emoji images in text --- src/QuickMedia.cpp | 18 +- src/ResourceLoader.cpp | 1 - src/Text.cpp | 462 +++++++++++++++++++++++++++++++++++------------- src/plugins/Youtube.cpp | 17 +- 4 files changed, 371 insertions(+), 127 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index e31b846..bbff6c6 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -387,7 +387,7 @@ namespace QuickMedia { bool no_dialog = false; for(int i = 1; i < argc; ++i) { - if(!plugin_name) { + if(!plugin_name && argv[i][0] != '-') { std::string youtube_url_converted = invidious_url_to_youtube_url(argv[i]); std::string youtube_channel_id; std::string youtube_video_id_dummy; @@ -407,6 +407,12 @@ namespace QuickMedia { break; } } + + if(!plugin_name) { + fprintf(stderr, "\"%s\" is not a valid plugin/youtube url\n", argv[i]); + usage(); + return -1; + } } if(strcmp(argv[i], "--no-video") == 0) { @@ -6986,10 +6992,12 @@ namespace QuickMedia { if(clipboard_success && !clipboard_text.empty() && get_file_type(clipboard_text) == FileType::REGULAR) { const time_t now = time(nullptr); - const struct tm *t = localtime(&now); - char filename[256]; - strftime(filename, sizeof(filename)-1, "Clipboard_%Y-%m-%d_%H-%M-%S", t); - strcat(filename, file_ext.c_str()); + struct tm t; + localtime_r(&now, &t); + char filename[256] = {0}; + const int num_bytes_written = strftime(filename, sizeof(filename)-1, "Clipboard_%Y-%m-%d_%H-%M-%S", &t); + if((int)sizeof(filename) - (num_bytes_written + file_ext.size()) >= 1) + strcat(filename, file_ext.c_str()); upload_file(clipboard_text, filename); } diff --git a/src/ResourceLoader.cpp b/src/ResourceLoader.cpp index e5d16dd..e086ee7 100644 --- a/src/ResourceLoader.cpp +++ b/src/ResourceLoader.cpp @@ -49,7 +49,6 @@ namespace QuickMedia { return true; } - fprintf(stderr, "Warning: failed to find font: %s\n", font_name.c_str()); return false; } } diff --git a/src/Text.cpp b/src/Text.cpp index d29c9b5..be31434 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -2,13 +2,19 @@ #include "../include/ResourceLoader.hpp" #include "../include/Config.hpp" #include "../include/Theme.hpp" +#include "../include/AsyncImageLoader.hpp" +#include "../include/StringUtils.hpp" #include "../generated/Emoji.hpp" +#include #include #include #include #include #include +#include #include +// TODO: Remove +#include namespace QuickMedia { @@ -27,7 +33,16 @@ namespace QuickMedia static const size_t FONT_INDEX_CJK = 1; static const size_t FONT_INDEX_SYMBOLS = 2; static const size_t FONT_INDEX_EMOJI = 3; - static const size_t FONT_ARRAY_SIZE = 4; + static const size_t FONT_INDEX_IMAGE = 4; + static const size_t FONT_ARRAY_SIZE = 5; + + static const uint8_t FORMATTED_TEXT_START = '\x02'; + static const uint8_t FORMATTED_TEXT_END = '\x03'; + + enum class FormattedTextType : uint8_t { + TEXT, + IMAGE + }; Text::Text() : bold_font(false), @@ -111,6 +126,38 @@ namespace QuickMedia dirtyText = true; } + void Text::append_image(const std::string &url, bool local, mgl::vec2i size) { + str += Text::formatted_image(url, local, size); + } + + // static + std::string Text::formatted_image(const std::string &url, bool local, mgl::vec2i size) { + std::string result; + result += FORMATTED_TEXT_START; + result += (uint8_t)FormattedTextType::IMAGE; + result.append((const char*)&size.x, sizeof(size.x)); + result.append((const char*)&size.y, sizeof(size.y)); + result.append((const char*)&local, sizeof(local)); + result.append(url); + result += FORMATTED_TEXT_END; + return result; + } + + // static + std::string Text::formatted_text(const std::string &text, mgl::Color color, bool bold) { + std::string result; + result += FORMATTED_TEXT_START; + result += (uint8_t)FormattedTextType::TEXT; + result += color.r; + result += color.g; + result += color.b; + result += color.a; + result += (uint8_t)bold; + result.append(text); + result += FORMATTED_TEXT_END; + return result; + } + void Text::insert_text_at_caret_position(const std::string &str) { this->str.insert(caretIndex, str); dirty = true; @@ -299,7 +346,14 @@ namespace QuickMedia return size; } - static size_t find_end_of_emoji(const char *str, size_t size) { + // TODO: Optimize, dont use ostringstream + static std::string codepoint_to_hex_str(uint32_t codepoint) { + std::ostringstream ss; + ss << std::hex << codepoint; + return ss.str(); + } + + 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; @@ -309,7 +363,7 @@ namespace QuickMedia clen = 1; } - if(!codepoint_is_emoji(codepoint)) + if(!is_symbol_codepoint(codepoint)) return i; i += clen; @@ -317,7 +371,11 @@ namespace QuickMedia return size; } - static size_t find_end_of_symbol(const char *str, size_t size) { + static size_t find_end_latin(const char *str, size_t size) { + uint32_t emoji_sequence[32]; + size_t emoji_sequence_length = 0; + size_t emoji_byte_length = 0; + for(size_t i = 0; i < size;) { const unsigned char *cp = (const unsigned char*)&str[i]; uint32_t codepoint; @@ -327,36 +385,80 @@ namespace QuickMedia clen = 1; } - if(!is_symbol_codepoint(codepoint)) + if(is_cjk_codepoint(codepoint) || is_symbol_codepoint(codepoint) || codepoint == FORMATTED_TEXT_START || match_emoji_sequence(cp, size - i, emoji_sequence, emoji_sequence_length, emoji_byte_length)) return i; i += clen; } + return size; } - 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; +// TODO: +#if 0 + static size_t parse_formatted_image(const char *str, size_t size, std::string &image_url, bool &image_local, mgl::vec2i &image_size) { + image_url.clear(); + image_local = true; + image_size = { 0, 0 }; + + for(size_t i = 0; i < size; ++i) { + const char *cp = &str[i]; + if(*cp == FORMATTED_TEXT_END) { + const size_t image_len = i; + if(image_len < sizeof(image_size.x) + sizeof(image_size.y) + sizeof(image_local)) + return size; + + memcpy(&image_size.x, str, sizeof(image_size.x)); + memcpy(&image_size.y, str + sizeof(image_size.x), sizeof(image_size.y)); + memcpy(&image_local, str + sizeof(image_size.x) + sizeof(image_size.y), sizeof(image_local)); + const size_t image_url_index = sizeof(image_size.x) + sizeof(image_size.y) + sizeof(image_local); + image_url.assign(str + image_url_index, image_len - image_url_index); + return i + 1; } + } + return size; + } - if(is_cjk_codepoint(codepoint) || codepoint_is_emoji(codepoint) || is_symbol_codepoint(codepoint)) - return i; + static size_t parse_formatted_text(const char *str, size_t size, TextElement &text_element) { + if(size == 0) + return 0; - i += clen; + FormattedTextType formatted_text_type = (FormattedTextType)*(uint8_t*)&str[0]; + switch(formatted_text_type) { + case FormattedTextType::TEXT: { + text_element.type = TextElement::Type::TEXT; + // TODO: + //return parse_formatted_text(str + 1, size - 1, text_element) + } + case FormattedTextType::IMAGE: { + text_element.type = TextElement::Type::IMAGE; + return parse_formatted_image(str + 1, size - 1, text_element.url, text_element.local, text_element.size); + } + default: + break; } - return size; + return 0; } +#endif + void Text::split_text_by_type(std::vector &text_elements, const std::string &str) { + text_elements.clear(); - void Text::splitTextByFont() { - textElements.clear(); + mgl::Font *latin_font; + if(bold_font) + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN_BOLD, characterSize); + else + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN, characterSize); + + const float vspace = font_get_real_height(latin_font); size_t index = 0; size_t size = str.size(); + + size_t offset; + uint32_t emoji_sequence[32]; + size_t emoji_sequence_length = 0; + size_t emoji_byte_length = 0; + std::string emoji_codepoint_combined; + while(index < size) { const unsigned char *cp = (const unsigned char*)&str[index]; uint32_t codepoint; @@ -366,23 +468,77 @@ namespace QuickMedia clen = 1; } - size_t offset; - TextElement::TextType text_type = TextElement::TextType::LATIN; + TextElement text_element; + if(is_symbol_codepoint(codepoint)) { - text_type = TextElement::TextType::SYMBOL; offset = find_end_of_symbol(str.data() + index, size - index); + + text_element.create_text(std::string_view(str.data() + index, offset)); + text_element.text_type = TextElement::TextType::SYMBOL; + text_element.text_num_bytes = text_element.text.size(); + text_elements.push_back(std::move(text_element)); } else if(is_cjk_codepoint(codepoint)) { - text_type = TextElement::TextType::CJK; 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.data() + index, size - index); + + text_element.create_text(std::string_view(str.data() + index, offset)); + text_element.text_type = TextElement::TextType::CJK; + text_element.text_num_bytes = text_element.text.size(); + text_elements.push_back(std::move(text_element)); + } else if(codepoint == FORMATTED_TEXT_START) { + // TODO: + offset = 1; + #if 0 + text_element.type = TextElement::Type::IMAGE; + + index += 1; + offset = parse_formatted_text(str.data() + index, size - index, text_element); + text_element.text_num_bytes = ... // TODO + text_elements.push_back(std::move(text_element)); + #endif + } else if(match_emoji_sequence((const unsigned char*)str.data() + index, size - index, emoji_sequence, emoji_sequence_length, emoji_byte_length)) { + offset = emoji_byte_length; + + emoji_codepoint_combined.clear(); + for(size_t i = 0; i < emoji_sequence_length; ++i) { + if(!emoji_codepoint_combined.empty()) + emoji_codepoint_combined += '-'; + emoji_codepoint_combined += codepoint_to_hex_str(emoji_sequence[i]); + } + + // Twemoji issue + string_replace_all(emoji_codepoint_combined, "-fe0f-20e3", "-20e3"); + if(emoji_codepoint_combined == "1f441-fe0f-200d-1f5e8-fe0f") + emoji_codepoint_combined = "1f441-200d-1f5e8"; + + std::string image_url; + bool image_local = false; + if(emoji_codepoint_combined != "1f3f3-fe0f-200d-26a7-fe0f") { + image_url = "/usr/share/quickmedia/emoji/" + emoji_codepoint_combined + ".png"; + image_local = true; + } else { + image_url = "/home/dec05eba/Pictures/troonjak.png"; + image_local = true; + } + + text_element.create_text("E"); + text_element.text_type = TextElement::TextType::EMOJI; + text_element.text = "E"; + text_element.url = std::move(image_url); + text_element.local = image_local; + text_element.size = { (int)vspace, (int)vspace }; + text_element.text_num_bytes = emoji_byte_length; + text_elements.push_back(std::move(text_element)); } else { offset = find_end_latin(str.data() + index, size - index); + + text_element.create_text(std::string_view(str.data() + index, offset)); + text_element.text_type = TextElement::TextType::LATIN; + text_element.text_num_bytes = text_element.text.size(); + text_elements.push_back(std::move(text_element)); } - textElements.push_back({ std::string_view(str.data() + index, offset), TextElement::Type::TEXT }); - textElements.back().text_type = text_type; + // We never want an infinite loop even if there is a bug in the above code + offset = std::max(offset, (size_t)1); index += offset; } } @@ -451,7 +607,7 @@ namespace QuickMedia if(dirtyText) { assert(dirty); dirtyText = false; - splitTextByFont(); + split_text_by_type(textElements, str); // TODO: Optimize if(highlight_urls) { url_ranges = extract_urls(str); @@ -480,7 +636,7 @@ namespace QuickMedia 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 float emoji_spacing = 2.0f; const mgl::Color url_color = get_theme().url_text_color; @@ -492,10 +648,48 @@ namespace QuickMedia for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex) { TextElement &textElement = textElements[textElementIndex]; + mgl::Font *ff = latin_font; int vertices_index = FONT_INDEX_LATIN; - prevCodePoint = 0; - if(textElement.text_type == TextElement::TextType::CJK) { + if(textElement.type == TextElement::Type::IMAGE) { + vertices_index = FONT_INDEX_IMAGE; + mgl::Color image_color(255, 255, 255, color.a); + int vertexStart = vertices[vertices_index].size(); + + if(prevCodePoint != 0) + glyphPos.x += emoji_spacing; + + const float font_height_offset = 0.0f;//floor(vspace * 0.6f); + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - textElement.size.y * 0.5f); + mgl::vec2f vertexTopRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset - textElement.size.y * 0.5f); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset + textElement.size.y * 0.5f); + mgl::vec2f vertexBottomRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset + textElement.size.y * 0.5f); + + vertexTopLeft = vec2f_floor(vertexTopLeft); + vertexTopRight = vec2f_floor(vertexTopRight); + vertexBottomLeft = vec2f_floor(vertexBottomLeft); + vertexBottomRight = vec2f_floor(vertexBottomRight); + + mgl::vec2f textureTopLeft(0.0f, 0.0f); + mgl::vec2f textureTopRight(0.0f + textElement.size.x, 0.0f); + mgl::vec2f textureBottomLeft(0.0f, 0.0f + textElement.size.y); + mgl::vec2f textureBottomRight(0.0f + textElement.size.x, 0.0f + textElement.size.y); + + vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, image_color); + vertices[vertices_index].emplace_back(vertexTopLeft, textureTopLeft, image_color); + vertices[vertices_index].emplace_back(vertexBottomLeft, textureBottomLeft, image_color); + vertices[vertices_index].emplace_back(vertexBottomLeft, textureBottomLeft, image_color); + vertices[vertices_index].emplace_back(vertexBottomRight, textureBottomRight, image_color); + vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, image_color); + + // TODO: Size.x update + glyphPos.x += floor(textElement.size.x) + characterSpacing + emoji_spacing; + textElement.vertex_ref_index = vertices_linear.size(); + vertices_linear.push_back({vertices_index, vertexStart, 0, textElement.text_num_bytes, 'E'}); + + prevCodePoint = 0; + continue; + } else if(textElement.text_type == TextElement::TextType::CJK) { ff = FontLoader::get_font(FontLoader::FontType::CJK, characterSize); vertices_index = FONT_INDEX_CJK; } else if(textElement.text_type == TextElement::TextType::SYMBOL) { @@ -504,47 +698,41 @@ namespace QuickMedia } else if(textElement.text_type == TextElement::TextType::EMOJI) { vertices_index = FONT_INDEX_EMOJI; mgl::Color emoji_color(255, 255, 255, color.a); - for(size_t i = 0; i < textElement.text.size();) - { - 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 = floor(vspace * 0.6f); - mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - floor(emoji_rec.height * emoji_scale) * 0.5f); - mgl::vec2f vertexTopRight(glyphPos.x + floor(emoji_rec.width * emoji_scale), glyphPos.y + font_height_offset - 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 + floor(emoji_rec.width * emoji_scale), glyphPos.y + font_height_offset + floor(emoji_rec.height * emoji_scale) * 0.5f); - - vertexTopLeft = vec2f_floor(vertexTopLeft); - vertexTopRight = vec2f_floor(vertexTopRight); - vertexBottomLeft = vec2f_floor(vertexBottomLeft); - vertexBottomRight = vec2f_floor(vertexBottomRight); - - 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].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 += floor(emoji_rec.width * emoji_scale) + characterSpacing; - vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); - i += clen; - } + int vertexStart = vertices[vertices_index].size(); + const mgl::vec2f emoji_size = { vspace, vspace }; + + if(prevCodePoint != 0) + glyphPos.x += emoji_spacing; + + const float font_height_offset = floor(vspace * 0.5f); + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - emoji_size.y * 0.5f); + mgl::vec2f vertexTopRight(glyphPos.x + emoji_size.x, glyphPos.y + font_height_offset - emoji_size.y * 0.5f); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset + emoji_size.y * 0.5f); + mgl::vec2f vertexBottomRight(glyphPos.x + emoji_size.x, glyphPos.y + font_height_offset + emoji_size.y * 0.5f); + + vertexTopLeft = vec2f_floor(vertexTopLeft); + vertexTopRight = vec2f_floor(vertexTopRight); + vertexBottomLeft = vec2f_floor(vertexBottomLeft); + vertexBottomRight = vec2f_floor(vertexBottomRight); + + mgl::vec2f textureTopLeft(0.0f, 0.0f); + mgl::vec2f textureTopRight(0.0f + emoji_size.x, 0.0f); + mgl::vec2f textureBottomLeft(0.0f, 0.0f + emoji_size.y); + mgl::vec2f textureBottomRight(0.0f + emoji_size.x, 0.0f + emoji_size.y); + + 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 += emoji_size.x + characterSpacing + emoji_spacing; + textElement.vertex_ref_index = vertices_linear.size(); + vertices_linear.push_back({vertices_index, vertexStart, 0, textElement.text_num_bytes, 'E'}); + + prevCodePoint = 0; continue; } @@ -600,7 +788,7 @@ namespace QuickMedia 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}); + vertices_linear.push_back({vertices_index, vertexStart, 0, (int)clen, codepoint}); break; } case '\t': @@ -624,7 +812,7 @@ namespace QuickMedia 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}); + vertices_linear.push_back({vertices_index, vertexStart, 0, (int)clen, codepoint}); break; } case '\n': @@ -648,7 +836,7 @@ namespace QuickMedia glyphPos.x = 0.0f; glyphPos.y += floor(vspace + lineSpacing); - vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + vertices_linear.push_back({vertices_index, vertexStart, 0, (int)clen, codepoint}); break; } default: { @@ -677,7 +865,7 @@ namespace QuickMedia vertices[vertices_index].emplace_back(vertexTopRight, textureTopRight, text_color); glyphPos.x += glyph.advance + characterSpacing; - vertices_linear.push_back({vertices_index, vertexStart, 0, codepoint}); + vertices_linear.push_back({vertices_index, vertexStart, 0, (int)clen, codepoint}); break; } } @@ -745,6 +933,15 @@ namespace QuickMedia } } + // TODO: Optimize + for(TextElement &textElement : textElements) { + if(textElement.text_type == TextElement::TextType::EMOJI || textElement.type == TextElement::Type::IMAGE) { + const VertexRef &vertex_ref = vertices_linear[textElement.vertex_ref_index]; + const mgl::vec2f top_left_vertex_pos = vertices[vertex_ref.vertices_index][vertex_ref.index + 1].position; + textElement.pos = { (int)top_left_vertex_pos.x, (int)top_left_vertex_pos.y }; + } + } + boundingBox.size.x = 0.0f; for(VertexRef &vertex_ref : vertices_linear) { boundingBox.size.x = std::max(boundingBox.size.x, get_text_quad_right_side(vertex_ref)); @@ -946,40 +1143,15 @@ namespace QuickMedia // 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; - } + size_t str_index = 0; + for(size_t i = 0; i < vertices_linear.size(); ++i) { + if(i == caret_index) + break; - i += clen; - ++codepoint_index; + auto &vertex = vertices_linear[i]; + str_index += vertex.text_num_bytes; } - return codepoint_index; + return str_index; } // TODO: Optimize text editing by only processing the changed parts in updateGeometry. @@ -1012,8 +1184,8 @@ namespace QuickMedia else if(event.key.code == mgl::Keyboard::Backspace && caretIndex > 0) { 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); + if(str_index > 0 && str_index <= str.size()) { + const size_t codepoint_start = str_index - vertices_linear[caretIndex - 1].text_num_bytes; str.erase(codepoint_start, str_index - codepoint_start); --caretIndex; dirty = true; @@ -1024,12 +1196,12 @@ namespace QuickMedia else if(event.key.code == mgl::Keyboard::Delete && !caretAtEnd) { 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; + const size_t codepoint_end = str_index + vertices_linear[caretIndex].text_num_bytes; + if(str_index < str.size() && codepoint_end <= str.size()) { + str.erase(str_index, codepoint_end - str_index); + dirty = true; + dirtyText = true; + } } else if(event.key.code == mgl::Keyboard::D && event.key.control) { @@ -1079,7 +1251,7 @@ namespace QuickMedia { stringToAdd = window.get_clipboard_string(); } - else if(event.text.codepoint >= 32 || (event.text.codepoint == '\t' && !single_line_edit)) + else if(event.text.codepoint >= 32 || (event.text.codepoint == '\t' && !single_line_edit)) stringToAdd.assign(event.text.str, event.text.size); else return; @@ -1091,7 +1263,14 @@ namespace QuickMedia str.insert(str_index, stringToAdd); } - caretIndex += utf8_get_length(stringToAdd); + std::vector new_text_elements; + split_text_by_type(new_text_elements, stringToAdd); + for(auto &text_element : new_text_elements) { + if(text_element.type == TextElement::Type::IMAGE || text_element.text_type == TextElement::TextType::EMOJI) + caretIndex += 1; + else + caretIndex += text_element.text_num_bytes; + } dirty = true; dirtyText = true; dirtyCaret = true; @@ -1135,11 +1314,56 @@ namespace QuickMedia target.draw(vertex_buffers[i]); } - if(vertex_buffers[FONT_INDEX_EMOJI].size() > 0) { + /*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]); + }*/ + + // TODO: Use a new vector with only the image data instead of this. + // TODO: Sprite + mgl::Sprite sprite; + for(const TextElement &textElement : textElements) { + if(textElement.text_type == TextElement::TextType::EMOJI) { + auto emoji_data = AsyncImageLoader::get_instance().get_thumbnail(textElement.url, textElement.local, { (int)vspace, (int)vspace }); + if(emoji_data->loading_state == LoadingState::FINISHED_LOADING) { + if(!emoji_data->texture.load_from_image(*emoji_data->image)) + fprintf(stderr, "Warning: failed to load text emoji: %s\n", textElement.url.c_str()); + emoji_data->image.reset(); + emoji_data->loading_state = LoadingState::APPLIED_TO_TEXTURE; + } + + if(emoji_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) { + sprite.set_texture(&emoji_data->texture); + sprite.set_position(pos + textElement.pos.to_vec2f()); + sprite.set_size(textElement.size.to_vec2f()); + target.draw(sprite); + } + } + } + + // TODO: Use a new vector with only the image data instead of this. + // TODO: Sprite + #if 0 + for(const TextElement &textElement : textElements) { + if(textElement.type == TextElement::Type::IMAGE) { + auto thumbnail_data = AsyncImageLoader::get_instance().get_thumbnail(textElement.url, textElement.local, textElement.size); + if(thumbnail_data->loading_state == LoadingState::FINISHED_LOADING && thumbnail_data->image->get_size().x > 0 && thumbnail_data->image->get_size().y > 0) { + if(!thumbnail_data->texture.load_from_image(*thumbnail_data->image)) + fprintf(stderr, "Warning: failed to load text image: %s\n", textElement.url.c_str()); + thumbnail_data->image.reset(); + thumbnail_data->loading_state = LoadingState::APPLIED_TO_TEXTURE; + } + + if(thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) { + sprite.set_texture(&thumbnail_data->texture); + sprite.set_position(pos + textElement.pos.to_vec2f()); + sprite.set_size(textElement.size.to_vec2f()); + target.draw(sprite); + } + } } + #endif if(!editable) return true; pos.y -= floor(vspace*1.25f); diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 23e95c6..d3d498e 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -1139,7 +1139,9 @@ namespace QuickMedia { PluginResult YoutubeCommentsPage::submit(const SubmitArgs &args, std::vector &result_tabs) { if(args.url.empty()) return PluginResult::OK; - result_tabs.push_back(Tab{create_body(), std::make_unique(program, video_url, args.url), nullptr}); + + const BodyItem *body_item = (BodyItem*)args.userdata; + result_tabs.push_back(Tab{create_body(), std::make_unique(program, video_url, args.url, body_item), nullptr}); return PluginResult::OK; } @@ -1266,6 +1268,7 @@ namespace QuickMedia { } body_item->set_description(std::move(description)); + body_item->userdata = body_item.get(); return body_item; } @@ -1458,7 +1461,7 @@ namespace QuickMedia { PluginResult YoutubeCommentRepliesPage::get_page(const std::string&, int page, BodyItems &result_items) { while(current_page < page) { - PluginResult plugin_result = lazy_fetch(result_items); + PluginResult plugin_result = lazy_fetch(result_items, false); if(plugin_result != PluginResult::OK) return plugin_result; ++current_page; } @@ -1470,6 +1473,16 @@ namespace QuickMedia { } PluginResult YoutubeCommentRepliesPage::lazy_fetch(BodyItems &result_items) { + return lazy_fetch(result_items, true); + } + + PluginResult YoutubeCommentRepliesPage::lazy_fetch(BodyItems &result_items, bool first_fetch) { + if(first_fetch) { + auto body_item = BodyItem::create(""); + *body_item = *replied_to_body_item; + body_item->set_author("(OP) " + body_item->get_author()); + result_items.push_back(std::move(body_item)); + } return fetch_comments(this, video_url, continuation_token, result_items); } -- cgit v1.2.3