aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-11-05 15:53:28 +0100
committerdec05eba <dec05eba@protonmail.com>2022-11-05 15:53:28 +0100
commit4daa57f6d139f51a62ea4bcffa738bd5035df33a (patch)
treeba767bbbf1c7153a12e3d9e866a079c30d1c69e0 /src
parentf89117b5cf36797b04291942b2f2494895fc58dd (diff)
Support as many emoji as possible, using separate emoji images in text
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp18
-rw-r--r--src/ResourceLoader.cpp1
-rw-r--r--src/Text.cpp462
-rw-r--r--src/plugins/Youtube.cpp17
4 files changed, 371 insertions, 127 deletions
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 <string.h>
#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/graphics/Sprite.hpp>
#include <mglpp/system/Utf8.hpp>
+// TODO: Remove
+#include <sstream>
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<TextElement> &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<TextElement> 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<Tab> &result_tabs) {
if(args.url.empty())
return PluginResult::OK;
- result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeCommentRepliesPage>(program, video_url, args.url), nullptr});
+
+ const BodyItem *body_item = (BodyItem*)args.userdata;
+ result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeCommentRepliesPage>(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);
}