From 44e66882f6e517b06522cb1e510ed9dea7574273 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 21 Nov 2020 18:20:00 +0100 Subject: Render emoji in text, do not show notification count for cache sync, lazy load 4chan board --- src/Body.cpp | 2 +- src/Entry.cpp | 2 +- src/FontLoader.cpp | 52 --------- src/ImageViewer.cpp | 2 +- src/QuickMedia.cpp | 4 +- src/ResourceLoader.cpp | 79 ++++++++++++++ src/SearchBar.cpp | 2 +- src/Text.cpp | 71 +++++++++++-- src/plugins/Fourchan.cpp | 266 +++++++++++++++++++++++------------------------ src/plugins/Matrix.cpp | 6 +- 10 files changed, 284 insertions(+), 202 deletions(-) delete mode 100644 src/FontLoader.cpp create mode 100644 src/ResourceLoader.cpp (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index b456841..5c2a66c 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -1,7 +1,7 @@ #include "../include/Body.hpp" #include "../include/QuickMedia.hpp" #include "../include/Scale.hpp" -#include "../include/FontLoader.hpp" +#include "../include/ResourceLoader.hpp" #include "../plugins/Plugin.hpp" #include #include diff --git a/src/Entry.cpp b/src/Entry.cpp index cc05aa0..c18006f 100644 --- a/src/Entry.cpp +++ b/src/Entry.cpp @@ -1,5 +1,5 @@ #include "../include/Entry.hpp" -#include "../include/FontLoader.hpp" +#include "../include/ResourceLoader.hpp" #include #include #include diff --git a/src/FontLoader.cpp b/src/FontLoader.cpp deleted file mode 100644 index ca33377..0000000 --- a/src/FontLoader.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "../include/FontLoader.hpp" -#include -#include -#include - -static std::array, 3> font_cache; - -namespace QuickMedia::FontLoader { - sf::Font* get_font(FontType font_type) { - sf::Font *font = font_cache[(size_t)font_type].get(); - if(!font) { - auto new_font = std::make_unique(); - std::vector noto_directories; - std::string font_file_name; - switch(font_type) { - case FontType::LATIN: { - noto_directories.push_back("/usr/share/fonts/noto"); - noto_directories.push_back("/usr/share/fonts/truetype/noto"); - font_file_name = "NotoSans-Regular.ttf"; - break; - } - case FontType::LATIN_BOLD: { - noto_directories.push_back("/usr/share/fonts/noto"); - noto_directories.push_back("/usr/share/fonts/truetype/noto"); - font_file_name = "NotoSans-Bold.ttf"; - break; - } - case FontType::CJK: { - noto_directories.push_back("/usr/share/fonts/noto-cjk"); - noto_directories.push_back("/usr/share/fonts/truetype/noto-cjk"); - font_file_name = "NotoSansCJK-Regular.ttc"; - break; - } - case FontType::EMOJI: { - noto_directories.push_back("/usr/share/fonts/noto"); - noto_directories.push_back("/usr/share/fonts/truetype/noto"); - font_file_name = "NotoColorEmoji.ttf"; - break; - } - } - - for(const std::string ¬o_dir : noto_directories) { - if(new_font->loadFromFile(noto_dir + "/" + font_file_name)) - break; - } - - font_cache[(size_t)font_type] = std::move(new_font); - font = font_cache[(size_t)font_type].get(); - } - return font; - } -} \ No newline at end of file diff --git a/src/ImageViewer.cpp b/src/ImageViewer.cpp index be03781..8704c15 100644 --- a/src/ImageViewer.cpp +++ b/src/ImageViewer.cpp @@ -2,7 +2,7 @@ #include "../include/Notification.hpp" #include "../include/Storage.hpp" #include "../include/SfmlFixes.hpp" -#include "../include/FontLoader.hpp" +#include "../include/ResourceLoader.hpp" #include "../plugins/Manga.hpp" #include #include diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c9ab3e1..f494d35 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -20,7 +20,7 @@ #include "../include/Entry.hpp" #include "../include/NetUtils.hpp" #include "../include/SfmlFixes.hpp" -#include "../include/FontLoader.hpp" +#include "../include/ResourceLoader.hpp" #include "../include/AsyncTask.hpp" #include "../external/hash-library/sha256.h" @@ -340,6 +340,8 @@ namespace QuickMedia { resources_root = "../../../"; } + set_resource_loader_root_path(resources_root.c_str()); + if(!circle_mask_shader.loadFromFile(resources_root + "shaders/circle_mask.glsl", sf::Shader::Type::Fragment)) { fprintf(stderr, "Failed to load %s/shaders/circle_mask.glsl", resources_root.c_str()); abort(); diff --git a/src/ResourceLoader.cpp b/src/ResourceLoader.cpp new file mode 100644 index 0000000..9a6060d --- /dev/null +++ b/src/ResourceLoader.cpp @@ -0,0 +1,79 @@ +#include "../include/ResourceLoader.hpp" +#include +#include +#include +#include +#include + +static std::string resource_root; +static std::array, 3> font_cache; +static std::unordered_map> texture_cache; + +namespace QuickMedia { + void set_resource_loader_root_path(const char *new_resource_root) { + resource_root = new_resource_root; + } +} + +namespace QuickMedia::FontLoader { + sf::Font* get_font(FontType font_type) { + sf::Font *font = font_cache[(size_t)font_type].get(); + if(!font) { + auto new_font = std::make_unique(); + std::vector noto_directories; + std::string font_file_name; + switch(font_type) { + case FontType::LATIN: { + noto_directories.push_back("/usr/share/fonts/noto"); + noto_directories.push_back("/usr/share/fonts/truetype/noto"); + font_file_name = "NotoSans-Regular.ttf"; + break; + } + case FontType::LATIN_BOLD: { + noto_directories.push_back("/usr/share/fonts/noto"); + noto_directories.push_back("/usr/share/fonts/truetype/noto"); + font_file_name = "NotoSans-Bold.ttf"; + break; + } + case FontType::CJK: { + noto_directories.push_back("/usr/share/fonts/noto-cjk"); + noto_directories.push_back("/usr/share/fonts/truetype/noto-cjk"); + font_file_name = "NotoSansCJK-Regular.ttc"; + break; + } + case FontType::EMOJI: { + noto_directories.push_back("/usr/share/fonts/noto"); + noto_directories.push_back("/usr/share/fonts/truetype/noto"); + font_file_name = "NotoColorEmoji.ttf"; + break; + } + } + + for(const std::string ¬o_dir : noto_directories) { + if(new_font->loadFromFile(noto_dir + "/" + font_file_name)) + break; + } + + font_cache[(size_t)font_type] = std::move(new_font); + font = font_cache[(size_t)font_type].get(); + } + return font; + } +} + +namespace QuickMedia::TextureLoader { + sf::Texture* get_texture(const char *filepath) { + assert(!resource_root.empty()); + std::string str = filepath; + auto it = texture_cache.find(str); + if(it != texture_cache.end()) + return it->second.get(); + + auto new_texture = std::make_unique(); + sf::Texture *result = new_texture.get(); + if(!new_texture->loadFromFile(resource_root + str)) + fprintf(stderr, "Failed to load image: %s%s\n", resource_root.c_str(), filepath); + texture_cache[str] = std::move(new_texture); + return result; + } +} \ No newline at end of file diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index dea9951..9b02903 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -1,6 +1,6 @@ #include "../include/SearchBar.hpp" #include "../include/Scale.hpp" -#include "../include/FontLoader.hpp" +#include "../include/ResourceLoader.hpp" #include #include #include diff --git a/src/Text.cpp b/src/Text.cpp index b463f92..539f211 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -1,5 +1,6 @@ #include "../include/Text.hpp" -#include "../include/FontLoader.hpp" +#include "../include/ResourceLoader.hpp" +#include "../generated/Emoji.hpp" #include #include #include @@ -46,6 +47,7 @@ namespace QuickMedia { vertices[0].setPrimitiveType(sf::PrimitiveType::Quads); vertices[1].setPrimitiveType(sf::PrimitiveType::Quads); + vertices[2].setPrimitiveType(sf::PrimitiveType::Quads); setString(std::move(_str)); } @@ -226,9 +228,17 @@ namespace QuickMedia return size; } - static size_t find_end_of_non_cjk(const sf::Uint32 *str, size_t size) { + static size_t find_end_of_emoji(const sf::Uint32 *str, size_t size) { for(size_t i = 0; i < size; ++i) { - if(is_cjk_codepoint(str[i])) + if(!codepoint_is_emoji(str[i])) + return i; + } + return size; + } + + static size_t find_end_of_non_cjk_and_non_emoji(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])) return i; } return size; @@ -240,13 +250,18 @@ namespace QuickMedia size_t size = str.getSize(); while(index < size) { size_t offset; - bool is_cjk = is_cjk_codepoint(str[index]); - if(is_cjk) + TextElement::TextType text_type = TextElement::TextType::LATIN; + if(is_cjk_codepoint(str[index])) { + text_type = TextElement::TextType::CJK; offset = find_end_of_cjk(str.getData() + index + 1, size - index - 1); - else - offset = find_end_of_non_cjk(str.getData() + index + 1, size - index - 1); + } else if(codepoint_is_emoji(str[index])) { + text_type = TextElement::TextType::EMOJI; + offset = find_end_of_emoji(str.getData() + index + 1, size - index - 1); + } else { + offset = find_end_of_non_cjk_and_non_emoji(str.getData() + index + 1, size - index - 1); + } textElements.push_back({ StringViewUtf32(str.getData() + index, offset + 1), TextElement::Type::TEXT }); - textElements.back().text_type = is_cjk ? TextElement::TextType::CJK : TextElement::TextType::LATIN; + textElements.back().text_type = text_type; index += 1 + offset; } } @@ -317,6 +332,7 @@ namespace QuickMedia vertices_linear.clear(); vertices[0].clear(); vertices[1].clear(); + vertices[2].clear(); boundingBox = sf::FloatRect(); sf::Font *latin_font; @@ -325,7 +341,8 @@ namespace QuickMedia else latin_font = FontLoader::get_font(FontLoader::FontType::LATIN); - float hspace = latin_font->getGlyph(' ', characterSize, false).advance + characterSpacing; + float latin_font_height = latin_font->getGlyph(' ', characterSize, false).advance; + float hspace = latin_font_height + characterSpacing; float vspace = latin_font->getLineSpacing(characterSize); // TODO: What about japanese font??? sf::Vector2f glyphPos; @@ -338,6 +355,35 @@ namespace QuickMedia if(textElement.text_type == TextElement::TextType::CJK) { ff = FontLoader::get_font(FontLoader::FontType::CJK); vertices_index = 1; + } else if(textElement.text_type == TextElement::TextType::EMOJI) { + vertices_index = 2; + textElement.position = glyphPos; + for(size_t i = 0; i < textElement.text.size; ++i) + { + sf::Uint32 codePoint = textElement.text[i]; + int vertexStart = vertices[vertices_index].getVertexCount(); + EmojiRectangle emoji_rec = emoji_get_extents(codePoint); + + const float font_height_offset = -latin_font_height * 1.0f; + sf::Vector2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - emoji_rec.height * 0.5f); + sf::Vector2f vertexTopRight(glyphPos.x + emoji_rec.width, glyphPos.y + font_height_offset - emoji_rec.height * 0.5f); + sf::Vector2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset + emoji_rec.height * 0.5f); + sf::Vector2f vertexBottomRight(glyphPos.x + emoji_rec.width, glyphPos.y + font_height_offset + emoji_rec.height * 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); + + vertices[vertices_index].append({ vertexTopLeft, sf::Color::White, textureTopLeft }); + vertices[vertices_index].append({ vertexTopRight, sf::Color::White, textureTopRight }); + vertices[vertices_index].append({ vertexBottomRight, sf::Color::White, textureBottomRight }); + vertices[vertices_index].append({ vertexBottomLeft, sf::Color::White, textureBottomLeft }); + + glyphPos.x += emoji_rec.width + characterSpacing; + vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); + } + continue; } //vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * textElement.text.size); // TODO: Precalculate @@ -818,6 +864,13 @@ namespace QuickMedia states.texture = &font->getTexture(characterSize); target.draw(vertices[i], states); } + + if(vertices[2].getVertexCount() > 0) { + sf::RenderStates states; + states.transform.translate(pos); + states.texture = TextureLoader::get_texture("images/emoji.png"); + target.draw(vertices[2], states); + } if(!editable) return true; pos.y -= floor(vspace * 2.0f); diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index d042a2a..2c62ba1 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -135,139 +135,7 @@ namespace QuickMedia { } PluginResult FourchanBoardsPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { - Json::Value json_root; - DownloadResult result = download_json(json_root, fourchan_url + url + "/catalog.json", {}, true); - if(result != DownloadResult::OK) return download_result_to_plugin_result(result); - - if(!json_root.isArray()) - return PluginResult::ERR; - - BodyItems result_items; - - for(const Json::Value &page_data : json_root) { - if(!page_data.isObject()) - continue; - - const Json::Value &threads = page_data["threads"]; - if(!threads.isArray()) - continue; - - for(const Json::Value &thread : threads) { - if(!thread.isObject()) - continue; - - const Json::Value &sub = thread["sub"]; - const char *sub_begin = ""; - const char *sub_end = sub_begin; - sub.getString(&sub_begin, &sub_end); - - const Json::Value &com = thread["com"]; - const char *comment_begin = ""; - const char *comment_end = comment_begin; - com.getString(&comment_begin, &comment_end); - - const Json::Value &thread_num = thread["no"]; - if(!thread_num.isNumeric()) - continue; - - std::string title_text; - extract_comment_pieces(sub_begin, sub_end - sub_begin, - [&title_text](const CommentPiece &cp) { - switch(cp.type) { - case CommentPiece::Type::TEXT: - title_text.append(cp.text.data, cp.text.size); - break; - case CommentPiece::Type::QUOTE: - title_text += '>'; - title_text.append(cp.text.data, cp.text.size); - //comment_text += '\n'; - break; - case CommentPiece::Type::QUOTELINK: { - title_text.append(cp.text.data, cp.text.size); - break; - } - case CommentPiece::Type::LINE_CONTINUE: { - if(!title_text.empty() && title_text.back() == '\n') { - title_text.pop_back(); - } - break; - } - } - } - ); - if(!title_text.empty() && title_text.back() == '\n') - title_text.back() = ' '; - html_unescape_sequences(title_text); - - std::string comment_text; - extract_comment_pieces(comment_begin, comment_end - comment_begin, - [&comment_text](const CommentPiece &cp) { - switch(cp.type) { - case CommentPiece::Type::TEXT: - comment_text.append(cp.text.data, cp.text.size); - break; - case CommentPiece::Type::QUOTE: - comment_text += '>'; - comment_text.append(cp.text.data, cp.text.size); - //comment_text += '\n'; - break; - case CommentPiece::Type::QUOTELINK: { - comment_text.append(cp.text.data, cp.text.size); - break; - } - case CommentPiece::Type::LINE_CONTINUE: { - if(!comment_text.empty() && comment_text.back() == '\n') { - comment_text.pop_back(); - } - break; - } - } - } - ); - html_unescape_sequences(comment_text); - // TODO: Do the same when wrapping is implemented - // TODO: Remove this - int num_lines = 0; - for(size_t i = 0; i < comment_text.size(); ++i) { - if(comment_text[i] == '\n') { - ++num_lines; - if(num_lines == 6) { - comment_text = comment_text.substr(0, i) + " (...)"; - break; - } - } - } - auto body_item = BodyItem::create(std::move(comment_text)); - body_item->set_author(std::move(title_text)); - body_item->url = std::to_string(thread_num.asInt64()); - - const Json::Value &ext = thread["ext"]; - const Json::Value &tim = thread["tim"]; - if(tim.isNumeric() && ext.isString()) { - std::string ext_str = ext.asString(); - if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") { - } else { - fprintf(stderr, "TODO: Support file extension: %s\n", ext_str.c_str()); - } - // "s" means small, that's the url 4chan uses for thumbnails. - // thumbnails always has .jpg extension even if they are gifs or webm. - body_item->thumbnail_url = fourchan_image_url + url + "/" + std::to_string(tim.asInt64()) + "s.jpg"; - - sf::Vector2i thumbnail_size(64, 64); - const Json::Value &tn_w = thread["tn_w"]; - const Json::Value &tn_h = thread["tn_h"]; - if(tn_w.isNumeric() && tn_h.isNumeric()) - thumbnail_size = sf::Vector2i(tn_w.asInt() * 0.5, tn_h.asInt() * 0.5); - body_item->thumbnail_size = std::move(thumbnail_size); - } - - result_items.push_back(std::move(body_item)); - } - } - - auto body = create_body(); - body->items = std::move(result_items); - result_tabs.push_back(Tab{std::move(body), std::make_unique(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + result_tabs.push_back(Tab{create_body(), std::make_unique(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); return PluginResult::OK; } @@ -462,6 +330,138 @@ namespace QuickMedia { return PluginResult::OK; } + PluginResult FourchanThreadListPage::lazy_fetch(BodyItems &result_items) { + Json::Value json_root; + DownloadResult result = download_json(json_root, fourchan_url + board_id + "/catalog.json?s=Index", {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + + if(!json_root.isArray()) + return PluginResult::ERR; + + for(const Json::Value &page_data : json_root) { + if(!page_data.isObject()) + continue; + + const Json::Value &threads = page_data["threads"]; + if(!threads.isArray()) + continue; + + for(const Json::Value &thread : threads) { + if(!thread.isObject()) + continue; + + const Json::Value &sub = thread["sub"]; + const char *sub_begin = ""; + const char *sub_end = sub_begin; + sub.getString(&sub_begin, &sub_end); + + const Json::Value &com = thread["com"]; + const char *comment_begin = ""; + const char *comment_end = comment_begin; + com.getString(&comment_begin, &comment_end); + + const Json::Value &thread_num = thread["no"]; + if(!thread_num.isNumeric()) + continue; + + std::string title_text; + extract_comment_pieces(sub_begin, sub_end - sub_begin, + [&title_text](const CommentPiece &cp) { + switch(cp.type) { + case CommentPiece::Type::TEXT: + title_text.append(cp.text.data, cp.text.size); + break; + case CommentPiece::Type::QUOTE: + title_text += '>'; + title_text.append(cp.text.data, cp.text.size); + //comment_text += '\n'; + break; + case CommentPiece::Type::QUOTELINK: { + title_text.append(cp.text.data, cp.text.size); + break; + } + case CommentPiece::Type::LINE_CONTINUE: { + if(!title_text.empty() && title_text.back() == '\n') { + title_text.pop_back(); + } + break; + } + } + } + ); + if(!title_text.empty() && title_text.back() == '\n') + title_text.back() = ' '; + html_unescape_sequences(title_text); + + std::string comment_text; + extract_comment_pieces(comment_begin, comment_end - comment_begin, + [&comment_text](const CommentPiece &cp) { + switch(cp.type) { + case CommentPiece::Type::TEXT: + comment_text.append(cp.text.data, cp.text.size); + break; + case CommentPiece::Type::QUOTE: + comment_text += '>'; + comment_text.append(cp.text.data, cp.text.size); + //comment_text += '\n'; + break; + case CommentPiece::Type::QUOTELINK: { + comment_text.append(cp.text.data, cp.text.size); + break; + } + case CommentPiece::Type::LINE_CONTINUE: { + if(!comment_text.empty() && comment_text.back() == '\n') { + comment_text.pop_back(); + } + break; + } + } + } + ); + html_unescape_sequences(comment_text); + // TODO: Do the same when wrapping is implemented + // TODO: Remove this + int num_lines = 0; + for(size_t i = 0; i < comment_text.size(); ++i) { + if(comment_text[i] == '\n') { + ++num_lines; + if(num_lines == 6) { + comment_text = comment_text.substr(0, i) + " (...)"; + break; + } + } + } + auto body_item = BodyItem::create(std::move(comment_text)); + body_item->set_author(std::move(title_text)); + body_item->url = std::to_string(thread_num.asInt64()); + + const Json::Value &ext = thread["ext"]; + const Json::Value &tim = thread["tim"]; + if(tim.isNumeric() && ext.isString()) { + std::string ext_str = ext.asString(); + if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") { + } else { + fprintf(stderr, "TODO: Support file extension: %s\n", ext_str.c_str()); + } + // "s" means small, that's the url 4chan uses for thumbnails. + // thumbnails always has .jpg extension even if they are gifs or webm. + body_item->thumbnail_url = fourchan_image_url + board_id + "/" + std::to_string(tim.asInt64()) + "s.jpg"; + + sf::Vector2i thumbnail_size(64, 64); + const Json::Value &tn_w = thread["tn_w"]; + const Json::Value &tn_h = thread["tn_h"]; + if(tn_w.isNumeric() && tn_h.isNumeric()) + thumbnail_size = sf::Vector2i(tn_w.asInt() * 0.5, tn_h.asInt() * 0.5); + body_item->thumbnail_size = std::move(thumbnail_size); + } + + result_items.push_back(std::move(body_item)); + } + } + + return PluginResult::OK; + } + PluginResult FourchanThreadPage::login(const std::string &token, const std::string &pin, std::string &response_msg) { response_msg.clear(); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 1d0e1df..6dabad9 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -455,7 +455,7 @@ namespace QuickMedia { }); } - void MatrixQuickMedia::update_room_description(RoomData *room, Messages &new_messages, bool is_initial_sync) { + void MatrixQuickMedia::update_room_description(RoomData *room, Messages &new_messages, bool is_initial_sync, bool sync_is_cache) { time_t read_marker_message_timestamp = 0; std::shared_ptr me = matrix->get_me(room); if(me) { @@ -485,7 +485,7 @@ namespace QuickMedia { if(!room_body_item) return; - if(last_unread_message) { + if(last_unread_message && !sync_is_cache) { std::string room_desc = "Unread: " + matrix->message_get_author_displayname(last_unread_message) + ": " + extract_first_line_elipses(last_unread_message->body, 150); int unread_notification_count = room->unread_notification_count; if(unread_notification_count > 0) @@ -523,7 +523,7 @@ namespace QuickMedia { } } - update_room_description(room, messages, is_initial_sync); + update_room_description(room, messages, is_initial_sync, it.second.sync_is_cache); } pending_room_messages.clear(); } -- cgit v1.2.3