From e19a29c7e51860144f02d7e7b08ac5e430e1f78f Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 7 Nov 2022 22:21:52 +0100 Subject: Support images in text, add custom emoji to matrix --- src/Text.cpp | 138 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 25 deletions(-) (limited to 'src/Text.cpp') diff --git a/src/Text.cpp b/src/Text.cpp index 3ecf24c..c43944b 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -4,10 +4,12 @@ #include "../include/Theme.hpp" #include "../include/AsyncImageLoader.hpp" #include "../include/StringUtils.hpp" +#include "../include/Scale.hpp" #include "../generated/Emoji.hpp" #include #include #include +#include #include #include #include @@ -45,6 +47,8 @@ namespace QuickMedia static const uint8_t FORMATTED_TEXT_START = '\x02'; static const uint8_t FORMATTED_TEXT_END = '\x03'; + static const mgl::vec2i MAX_IMAGE_SIZE(300, 300); + enum class FormattedTextType : uint8_t { TEXT, IMAGE @@ -92,6 +96,7 @@ namespace QuickMedia setString(std::move(_str)); } + // TODO: Validate |str|. Turn |str| into a valid utf-8 string void Text::setString(std::string str) { //if(str != this->str) @@ -119,6 +124,7 @@ namespace QuickMedia dirtyText = true; } + // TODO: Alt text. Helpful when copying the text. Or do we want to copy the url instead? // static std::string Text::formatted_image(const std::string &url, bool local, mgl::vec2i size) { const uint32_t str_size = url.size(); @@ -455,6 +461,7 @@ namespace QuickMedia return size; image_url.assign(str + offset, text_size); + image_size = clamp_to_size(image_size, MAX_IMAGE_SIZE); return std::min(offset + text_size + 1, size); // + 1 for FORMATTED_TEXT_END } @@ -518,7 +525,8 @@ namespace QuickMedia text_elements.push_back(std::move(text_element)); } else { text_element.text_num_bytes = 1; - text_elements.push_back(std::move(text_element)); + if(!text_element.url.empty()) + text_elements.push_back(std::move(text_element)); } } } else if(match_emoji_sequence((const unsigned char*)str.data() + index, size - index, emoji_sequence, emoji_sequence_length, emoji_byte_length)) { @@ -585,10 +593,18 @@ namespace QuickMedia return vertices[vertex_ref.vertices_index][vertex_ref.index + 5].position.x; } + float Text::get_text_quad_top_side(const VertexRef &vertex_ref) const { + return vertices[vertex_ref.vertices_index][vertex_ref.index + 1].position.y; + } + float Text::get_text_quad_bottom_side(const VertexRef &vertex_ref) const { return vertices[vertex_ref.vertices_index][vertex_ref.index + 4].position.y; } + float Text::get_text_quad_height(const VertexRef &vertex_ref) const { + return get_text_quad_bottom_side(vertex_ref) - get_text_quad_top_side(vertex_ref); + } + float Text::get_caret_offset_by_caret_index(int index) const { const int num_vertices = vertices_linear.size(); if(num_vertices == 0) @@ -636,6 +652,69 @@ namespace QuickMedia static mgl::vec2f vec2f_floor(mgl::vec2f value) { return mgl::vec2f((int)value.x, (int)value.y); } + + void Text::move_vertex_lines_by_largest_items(int vertices_linear_end) { + if(vertices_linear.empty() || vertices_linear_end == 0) + return; + + 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); + const float vertex_height = get_text_quad_height(vertices_linear[0]); + float vertex_max_height = std::max(vertex_height, vspace); + float vertex_second_max_height = vspace; + int current_line = vertices_linear[0].line; + int current_line_vertices_linear_start = 0; + float move_y = 0.0f; + + for(int i = 0; i < vertices_linear_end; ++i) { + VertexRef &vertex_ref = vertices_linear[i]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + for(int v = 0; v < 6; ++v) { + vertex[v].position.y += move_y; + } + + if(vertices_linear[i].line != current_line) { + const float vertices_move_down_offset = vertex_max_height - vertex_second_max_height; + if(vertex_max_height > vspace/* && vertex_max_height - vertex_min_height > 2.0f*/) { + for(int j = current_line_vertices_linear_start; j <= i; ++j) { + VertexRef &vertex_ref = vertices_linear[j]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + for(int v = 0; v < 6; ++v) { + vertex[v].position.y += vertices_move_down_offset; + } + } + move_y += vertices_move_down_offset; + } + + vertex_max_height = vspace; + current_line = vertices_linear[i].line; + current_line_vertices_linear_start = i; + } + + const float vertex_height = std::max(get_text_quad_height(vertex_ref), vspace); + if(vertex_height > vertex_max_height) { + vertex_second_max_height = vertex_max_height; + vertex_max_height = vertex_height; + } + } + + const float vertices_move_down_offset = vertex_max_height - vertex_second_max_height; + if(vertex_max_height > vspace/* && vertex_max_height - vertex_min_height > 2.0f*/) { + // TODO: current_line_vertices_linear_start vs vertices_linear_end + for(int j = current_line_vertices_linear_start; j < vertices_linear_end; ++j) { + VertexRef &vertex_ref = vertices_linear[j]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + for(int v = 0; v < 6; ++v) { + vertex[v].position.y += vertices_move_down_offset; + } + } + } + } void Text::updateGeometry(bool update_even_if_not_dirty) { if(dirtyText) { @@ -668,8 +747,8 @@ namespace QuickMedia latin_font = FontLoader::get_font(FontLoader::FontType::LATIN, characterSize); const float latin_font_width = latin_font->get_glyph(' ').advance; - const float hspace_latin = latin_font_width + characterSpacing; const float vspace = font_get_real_height(latin_font); + const float hspace_latin = latin_font_width + characterSpacing; const float emoji_spacing = 2.0f; int hspace_monospace = 0; @@ -686,7 +765,7 @@ namespace QuickMedia 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) + for(int textElementIndex = 0; textElementIndex < (int)textElements.size(); ++textElementIndex) { TextElement &textElement = textElements[textElementIndex]; @@ -714,11 +793,11 @@ namespace QuickMedia 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); + const float font_height_offset = vspace; + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - textElement.size.y); + mgl::vec2f vertexTopRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset - textElement.size.y); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset); + mgl::vec2f vertexBottomRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset); vertexTopLeft = vec2f_floor(vertexTopLeft); vertexTopRight = vec2f_floor(vertexTopRight); @@ -1018,6 +1097,7 @@ namespace QuickMedia } } vertices_linear_done:; + move_vertex_lines_by_largest_items(vertices_linear_index); // TODO: Optimize for(TextElement &textElement : textElements) { @@ -1052,19 +1132,19 @@ namespace QuickMedia boundingBox.size.y = 0.0f; for(VertexRef &vertex_ref : vertices_linear) { boundingBox.size.x = std::max(boundingBox.size.x, get_text_quad_right_side(vertex_ref)); - //boundingBox.size.y = std::max(boundingBox.size.y, get_text_quad_bottom_side(vertex_ref)); + boundingBox.size.y = std::max(boundingBox.size.y, get_text_quad_bottom_side(vertex_ref)); } - boundingBox.size.y = num_lines * line_height; + //boundingBox.size.y = num_lines * line_height; //boundingBox.size.y = text_offset_y; // TODO: - //if(vertices_linear.empty()) - // boundingBox.size.y = line_height; + if(vertices_linear.empty()) + boundingBox.size.y = line_height; - //if(editable) - // boundingBox.size.y = num_lines * line_height; + if(editable) + 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) { @@ -1458,16 +1538,21 @@ namespace QuickMedia target.draw(vertex_buffers[FONT_INDEX_EMOJI]); }*/ + // TODO: Use rounded rectangle for fallback image + // TODO: Use a new vector with only the image data instead of this. // TODO: Sprite mgl::Sprite sprite; - mgl::Rectangle fallback_emoji(mgl::vec2f(vspace, vspace)); - fallback_emoji.set_color(get_theme().shade_color); + mgl::Rectangle fallback_image(mgl::vec2f(vspace, vspace)); + fallback_image.set_color(get_theme().image_loading_background_color); for(const TextElement &textElement : textElements) { if(textElement.text_type == TextElement::TextType::EMOJI) { - if(textElement.pos.to_vec2f().y + vspace > boundingBox.size.y) + // TODO: + if(textElement.pos.to_vec2f().y + vspace > boundingBox.size.y + 10.0f) { + fprintf(stderr, "bounding box y: %f\n", boundingBox.size.y); continue; + } auto emoji_data = AsyncImageLoader::get_instance().get_thumbnail(textElement.url, textElement.local, { (int)vspace, (int)vspace }); if(emoji_data->loading_state == LoadingState::FINISHED_LOADING) { @@ -1480,18 +1565,17 @@ namespace QuickMedia if(emoji_data->loading_state == LoadingState::APPLIED_TO_TEXTURE && emoji_data->texture.get_size().x > 0) { sprite.set_texture(&emoji_data->texture); sprite.set_position(pos + textElement.pos.to_vec2f()); - sprite.set_size(textElement.size.to_vec2f()); + //sprite.set_size(textElement.size.to_vec2f()); target.draw(sprite); } else { - fallback_emoji.set_position(pos + textElement.pos.to_vec2f()); - target.draw(fallback_emoji); + fallback_image.set_position(pos + textElement.pos.to_vec2f()); + target.draw(fallback_image); } } } // 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); @@ -1502,18 +1586,22 @@ namespace QuickMedia thumbnail_data->loading_state = LoadingState::APPLIED_TO_TEXTURE; } - if(thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) { - if(textElement.pos.to_vec2f().y + thumbnail_data->texture->get_size().y > boundingBox.size.y) + if(thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE && thumbnail_data->texture.get_size().x > 0) { + // TODO: + if(textElement.pos.to_vec2f().y + thumbnail_data->texture.get_size().y > boundingBox.size.y + 10.0f) continue; sprite.set_texture(&thumbnail_data->texture); sprite.set_position(pos + textElement.pos.to_vec2f()); - sprite.set_size(textElement.size.to_vec2f()); + //sprite.set_size(textElement.size.to_vec2f()); target.draw(sprite); + } else { + fallback_image.set_size(textElement.size.to_vec2f()); + fallback_image.set_position(pos + textElement.pos.to_vec2f()); + target.draw(fallback_image); } } } - #endif if(!editable) return true; pos.y -= floor(vspace*1.25f); -- cgit v1.2.3