aboutsummaryrefslogtreecommitdiff
path: root/src/Text.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-11-07 22:21:52 +0100
committerdec05eba <dec05eba@protonmail.com>2022-11-11 00:53:46 +0100
commite19a29c7e51860144f02d7e7b08ac5e430e1f78f (patch)
treef57adcf98b77a271b4df74a20e2389b73f495df7 /src/Text.cpp
parent5d2a7d977f9b0a1604e106f4e2b0c2c9b89c3235 (diff)
Support images in text, add custom emoji to matrix
Diffstat (limited to 'src/Text.cpp')
-rw-r--r--src/Text.cpp138
1 files changed, 113 insertions, 25 deletions
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 <string.h>
#include <stack>
#include <unistd.h>
+#include <assert.h>
#include <mglpp/graphics/Rectangle.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/window/Window.hpp>
@@ -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);