aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--TODO5
-rw-r--r--fonts/NotoSans-Bold.ttfbin513000 -> 0 bytes
-rw-r--r--fonts/NotoSans-Regular.ttfbin509848 -> 0 bytes
-rw-r--r--fonts/NotoSans.LICENSE43
-rw-r--r--include/Body.hpp12
-rw-r--r--include/QuickMedia.hpp5
-rw-r--r--include/Text.hpp18
-rw-r--r--src/Body.cpp41
-rw-r--r--src/QuickMedia.cpp95
-rw-r--r--src/Text.cpp200
-rw-r--r--src/plugins/Fourchan.cpp2
-rw-r--r--src/plugins/Matrix.cpp2
13 files changed, 250 insertions, 178 deletions
diff --git a/README.md b/README.md
index 7a9f2f4..14e7fae 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ QuickMedia youtube --tor
echo -e "hello\nworld" | QuickMedia dmenu
```
## Installation
-If you are running arch linux then you can install QuickMedia from aur (https://aur.archlinux.org/packages/quickmedia-git/), otherwise you will need to use [sibs](https://git.dec05eba.com/sibs/) to build QuickMedia manually.
+If you are running arch linux then you can install QuickMedia from aur (https://aur.archlinux.org/packages/quickmedia-git/), otherwise you will need to use [sibs](https://git.dec05eba.com/sibs/) to build QuickMedia manually.\
## Controls
Press `arrow up` and `arrow down` to navigate the menu and also to scroll to the previous/next image when viewing manga in scorll mode. Alternatively you can use the mouse scroll to scroll to the previous/next manga in scroll mode.\
Press `Enter` (aka `Return`) to select the item.\
@@ -58,7 +58,8 @@ and store it in `$HOME/.config/quickmedia/credentials/mangadex.json` under the k
See project.conf \[dependencies].
## Runtime
### Required
-`curl` is required for network requests.
+`curl` is required for network requests.\
+`noto-fonts` and `noto-fonts-cjk` is required for alphanumerical and japanese characters.
### Optional
`mpv` is required for playing videos. This is not required if you dont plan on playing videos.\
`youtube-dl` needs to be installed to play videos from youtube.\
diff --git a/TODO b/TODO
index bc861f3..5ddbb0e 100644
--- a/TODO
+++ b/TODO
@@ -19,4 +19,7 @@ Add login page for mangadex instead of having to manually add remember_me token
Allow deleting watch history with delete key (and show confirmation).
Add pagination to nyaa.si results.
Add navigation to nyaa.si submitter torrents.
-Create a large texture and add downloaded images to it. This will save memory usage because sfml has to use power of two textures (and so does opengl internally) for textures, so if you have multiple textures they will use more memory than one large texture with the same texture data. \ No newline at end of file
+Create a large texture and add downloaded images to it. This will save memory usage because sfml has to use power of two textures (and so does opengl internally) for textures, so if you have multiple textures they will use more memory than one large texture with the same texture data.
+Use fallback cjk font for regular sf::Text as well (search, tabs, chapter name when viewing a page, path in file-manager, etc).
+Fix some japanese fonts not rendering (full width alphanumeric?).
+Also add support for full chinese and korean range. \ No newline at end of file
diff --git a/fonts/NotoSans-Bold.ttf b/fonts/NotoSans-Bold.ttf
deleted file mode 100644
index f95a702..0000000
--- a/fonts/NotoSans-Bold.ttf
+++ /dev/null
Binary files differ
diff --git a/fonts/NotoSans-Regular.ttf b/fonts/NotoSans-Regular.ttf
deleted file mode 100644
index 7da1a0f..0000000
--- a/fonts/NotoSans-Regular.ttf
+++ /dev/null
Binary files differ
diff --git a/fonts/NotoSans.LICENSE b/fonts/NotoSans.LICENSE
deleted file mode 100644
index 5f12258..0000000
--- a/fonts/NotoSans.LICENSE
+++ /dev/null
@@ -1,43 +0,0 @@
-Copyright 2012 Google Inc. All Rights Reserved.
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-
-—————————————————————————————-
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-—————————————————————————————-
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
-
-“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
-
-“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
-
-“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
-
-“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
-
-5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/include/Body.hpp b/include/Body.hpp
index e4c21fc..235de72 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -36,17 +36,23 @@ namespace QuickMedia {
dirty_description = true;
}
+ void set_author(std::string str) {
+ author = std::move(str);
+ dirty_author = true;
+ }
+
const std::string& get_title() const { return title; }
const std::string& get_description() const { return description; }
+ const std::string& get_author() const { return author; }
// TODO: Use a list of strings instead, not all plugins need all of these fields
std::string url;
std::string thumbnail_url;
std::string attached_content_url; // TODO: Remove and use |url| instead
- std::string author;
bool visible;
bool dirty;
bool dirty_description;
+ bool dirty_author;
bool thumbnail_is_local;
std::unique_ptr<Text> title_text;
std::unique_ptr<Text> description_text;
@@ -57,13 +63,14 @@ namespace QuickMedia {
private:
std::string title;
std::string description;
+ std::string author;
};
using BodyItems = std::vector<std::unique_ptr<BodyItem>>;
class Body {
public:
- Body(Program *program, sf::Font *font, sf::Font *bold_font);
+ Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font);
// Select previous item, ignoring invisible items. Returns true if the item was changed. This can be used to check if the top was hit when wrap_around is set to false
bool select_previous_item();
@@ -97,6 +104,7 @@ namespace QuickMedia {
sf::Font *font;
sf::Font *bold_font;
+ sf::Font *cjk_font;
sf::Text progress_text;
sf::Text author_text;
sf::Text replies_text;
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 18f3a6b..03bfcb9 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -82,8 +82,9 @@ namespace QuickMedia {
sf::RenderWindow window;
int monitor_hz;
sf::Vector2f window_size;
- sf::Font font;
- sf::Font bold_font;
+ std::unique_ptr<sf::Font> font;
+ std::unique_ptr<sf::Font> bold_font;
+ std::unique_ptr<sf::Font> cjk_font;
Body *body;
Plugin *current_plugin;
sf::Texture plugin_logo;
diff --git a/include/Text.hpp b/include/Text.hpp
index 8b6c0b9..d70f356 100644
--- a/include/Text.hpp
+++ b/include/Text.hpp
@@ -35,21 +35,22 @@ namespace QuickMedia
};
TextElement() {}
- TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type), ownLine(false) {}
+ TextElement(const StringViewUtf32 &_text, Type _type) : text(_text), type(_type), is_japanese(false) {}
StringViewUtf32 text;
sf::Vector2f position;
Type type;
- bool ownLine; // Currently only used for emoji, to make emoji bigger when it's the only thing on a line
+ //bool ownLine; // Currently only used for emoji, to make emoji bigger when it's the only thing on a line
+ bool is_japanese;
};
class Text
{
public:
- Text(const sf::Font *font);
- Text(const sf::String &str, const sf::Font *font, unsigned int characterSize, float maxWidth, bool plainText = true);
+ Text(const sf::Font *font, const sf::Font *cjk_font);
+ Text(sf::String str, const sf::Font *font, const sf::Font *cjk_font, unsigned int characterSize, float maxWidth, bool plainText = true);
- void setString(const sf::String &str);
+ void setString(sf::String str);
const sf::String& getString() const;
void setPosition(float x, float y);
@@ -89,6 +90,7 @@ namespace QuickMedia
END
};
+#if 0
void updateCaret();
bool isCaretAtEnd() const;
int getStartOfLine(int startIndex) const;
@@ -97,11 +99,15 @@ namespace QuickMedia
int getPreviousLineClosestPosition(int startIndex) const;
int getNextLineClosestPosition(int startIndex) const;
+#endif
+
+ void splitTextByFont();
private:
sf::String str;
const sf::Font *font;
+ const sf::Font *cjk_font;
unsigned int characterSize;
- sf::VertexArray vertices;
+ sf::VertexArray vertices[2];
float maxWidth;
sf::Vector2f position;
sf::Color color;
diff --git a/src/Body.cpp b/src/Body.cpp
index 3ebd11e..fd6ad07 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -15,7 +15,14 @@ const sf::Color front_color(32, 36, 42);
const sf::Color back_color(33, 35, 37);
namespace QuickMedia {
- BodyItem::BodyItem(std::string _title): visible(true), dirty(false), dirty_description(false), thumbnail_is_local(false), title_color(sf::Color::White) {
+ BodyItem::BodyItem(std::string _title) :
+ visible(true),
+ dirty(false),
+ dirty_description(false),
+ dirty_author(false),
+ thumbnail_is_local(false),
+ title_color(sf::Color::White)
+ {
set_title(std::move(_title));
}
@@ -29,6 +36,7 @@ namespace QuickMedia {
visible = other.visible;
dirty = other.dirty;
dirty_description = other.dirty_description;
+ dirty_author = other.dirty_author;
thumbnail_is_local = other.thumbnail_is_local;
if(other.title_text)
title_text = std::make_unique<Text>(*other.title_text);
@@ -43,9 +51,10 @@ namespace QuickMedia {
title_color = other.title_color;
}
- Body::Body(Program *program, sf::Font *font, sf::Font *bold_font) :
+ Body::Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font) :
font(font),
bold_font(bold_font),
+ cjk_font(cjk_font),
progress_text("", *font, 14),
author_text("", *bold_font, 16),
replies_text("", *font, 14),
@@ -355,26 +364,34 @@ namespace QuickMedia {
thumbnail_it.second.referenced = false;
}
- // TODO: Change font size. Currently it doesn't work because it glitches out. Why does that happen??
for(auto &body_item : items) {
if(body_item->dirty) {
body_item->dirty = false;
+ // TODO: Find a way to optimize fromUtf8
+ sf::String str = sf::String::fromUtf8(body_item->get_title().data(), body_item->get_title().data() + body_item->get_title().size());
if(body_item->title_text)
- body_item->title_text->setString(body_item->get_title());
+ body_item->title_text->setString(std::move(str));
else
- body_item->title_text = std::make_unique<Text>(body_item->get_title(), font, 16, size.x - 50 - image_padding_x * 2.0f);
+ body_item->title_text = std::make_unique<Text>(std::move(str), font, cjk_font, 16, size.x - 50 - image_padding_x * 2.0f);
body_item->title_text->setFillColor(body_item->title_color);
body_item->title_text->updateGeometry();
}
if(body_item->dirty_description) {
- body_item->dirty_description = true;
+ body_item->dirty_description = false;
+ sf::String str = sf::String::fromUtf8(body_item->get_description().data(), body_item->get_description().data() + body_item->get_description().size());
if(body_item->description_text)
- body_item->description_text->setString(body_item->get_description());
+ body_item->description_text->setString(std::move(str));
else
- body_item->description_text = std::make_unique<Text>(body_item->get_description(), font, 14, size.x - 50 - image_padding_x * 2.0f);
+ body_item->description_text = std::make_unique<Text>(std::move(str), font, cjk_font, 14, size.x - 50 - image_padding_x * 2.0f);
body_item->description_text->updateGeometry();
}
+
+ if(body_item->dirty_author) {
+ body_item->dirty_author = false;
+ sf::String str = sf::String::fromUtf8(body_item->get_author().data(), body_item->get_author().data() + body_item->get_author().size());
+ author_text.setString(std::move(str));
+ }
}
// Find the starting row that can be drawn to make selected row visible as well
@@ -388,7 +405,7 @@ namespace QuickMedia {
if(!item->get_title().empty()) {
item_height += item->title_text->getHeight();
}
- if(!item->author.empty()) {
+ if(!item->get_author().empty()) {
item_height += author_text.getCharacterSize() + 2.0f;
}
if(item->description_text) {
@@ -437,7 +454,7 @@ namespace QuickMedia {
if(!item->get_title().empty()) {
item_height += item->title_text->getHeight();
}
- if(!item->author.empty()) {
+ if(!item->get_author().empty()) {
item_height += author_text.getCharacterSize() + 2.0f;
}
if(item->description_text) {
@@ -496,9 +513,7 @@ namespace QuickMedia {
}
}
- if(!item->author.empty()) {
- // TODO: Remove this call, should not be called every frame
- author_text.setString(item->author);
+ if(!item->get_author().empty()) {
author_text.setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y));
window.draw(author_text);
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index f6b9d61..295749f 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -168,16 +168,47 @@ namespace QuickMedia {
resources_root = "../../../";
}
- if(!font.loadFromFile(resources_root + "fonts/NotoSans-Regular.ttf")) {
- fprintf(stderr, "Failed to load font: NotoSans-Regular.ttf\n");
+ const std::string noto_sans_directories[] = {
+ "/usr/share/fonts/noto", "/usr/share/fonts/truetype/noto",
+ "/usr/share/fonts/noto-cjk", "/usr/share/fonts/truetype/noto-cjk"
+ };
+ for(const std::string &noto_sans_dir : noto_sans_directories) {
+ if(!font) {
+ auto new_font = std::make_unique<sf::Font>();
+ if(new_font->loadFromFile(noto_sans_dir + "/NotoSans-Regular.ttf"))
+ font = std::move(new_font);
+ }
+
+ if(!bold_font) {
+ auto new_font = std::make_unique<sf::Font>();
+ if(new_font->loadFromFile(noto_sans_dir + "/NotoSans-Bold.ttf"))
+ bold_font = std::move(new_font);
+ }
+
+ if(!cjk_font) {
+ auto new_font = std::make_unique<sf::Font>();
+ if(new_font->loadFromFile(noto_sans_dir + "/NotoSansCJK-Regular.ttc"))
+ cjk_font = std::move(new_font);
+ }
+ }
+
+ if(!font) {
+ fprintf(stderr, "Failed to find NotoSans-Regular.ttf in /usr/share/fonts/noto and /usr/share/fonts/truetype/noto\n");
+ abort();
+ }
+
+ if(!bold_font) {
+ fprintf(stderr, "Failed to find NotoSans-Bold.ttf in /usr/share/fonts/noto and /usr/share/fonts/truetype/noto\n");
abort();
}
- if(!bold_font.loadFromFile(resources_root + "fonts/NotoSans-Bold.ttf")) {
- fprintf(stderr, "Failed to load font: NotoSans-Bold.ttf\n");
+
+ if(!cjk_font) {
+ fprintf(stderr, "Failed to find NotoSansCJK-Regular.ttc in /usr/share/fonts/noto and /usr/share/fonts/truetype/noto\n");
abort();
}
- body = new Body(this, &font, &bold_font);
- related_media_body = new Body(this, &font, &bold_font);
+
+ body = new Body(this, font.get(), bold_font.get(), cjk_font.get());
+ related_media_body = new Body(this, font.get(), bold_font.get(), cjk_font.get());
related_media_body->draw_thumbnails = true;
struct sigaction action;
@@ -454,7 +485,7 @@ namespace QuickMedia {
if(search_placeholder.empty())
search_placeholder = "Search...";
- search_bar = std::make_unique<SearchBar>(font, &plugin_logo, search_placeholder);
+ search_bar = std::make_unique<SearchBar>(*font, &plugin_logo, search_placeholder);
search_bar->text_autosearch_delay = current_plugin->get_search_delay();
while(window.isOpen()) {
@@ -926,16 +957,16 @@ namespace QuickMedia {
std::string autocomplete_text;
bool autocomplete_running = false;
- Body history_body(this, &font, &bold_font);
+ Body history_body(this, font.get(), bold_font.get(), cjk_font.get());
std::unique_ptr<Body> recommended_body;
- sf::Text all_tab_text("All", font, tab_text_size);
- sf::Text history_tab_text("History", font, tab_text_size);
- sf::Text recommended_tab_text("Recommended", font, tab_text_size);
- sf::Text login_tab_text("Login", font, tab_text_size);
+ sf::Text all_tab_text("All", *font, tab_text_size);
+ sf::Text history_tab_text("History", *font, tab_text_size);
+ sf::Text recommended_tab_text("Recommended", *font, tab_text_size);
+ sf::Text login_tab_text("Login", *font, tab_text_size);
SearchBar *focused_login_input = nullptr;
if(current_plugin->name == "youtube") {
- recommended_body = std::make_unique<Body>(this, &font, &bold_font);
+ recommended_body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
recommended_body->draw_thumbnails = true;
fill_recommended_items_from_json(load_recommended_json(current_plugin), recommended_body->items);
}
@@ -973,7 +1004,7 @@ namespace QuickMedia {
if(recommended_body)
tabs.push_back(Tab{recommended_body.get(), nullptr, SearchSuggestionTab::RECOMMENDED, &recommended_tab_text});
if(is_fourchan) {
- tabs.push_back(Tab{nullptr, std::make_unique<LoginTab>(font), SearchSuggestionTab::LOGIN, &login_tab_text});
+ tabs.push_back(Tab{nullptr, std::make_unique<LoginTab>(*font), SearchSuggestionTab::LOGIN, &login_tab_text});
focused_login_input = tabs.back().login_tab->username.get();
tabs.back().login_tab->username->caret_visible = true;
@@ -1403,7 +1434,7 @@ namespace QuickMedia {
sf::Vector2f related_media_window_size;
bool related_media_window_visible = false;
- sf::Text related_videos_text("Related videos", bold_font, 20);
+ sf::Text related_videos_text("Related videos", *bold_font, 20);
const float related_videos_text_height = related_videos_text.getCharacterSize();
sf::WindowHandle video_player_window = None;
@@ -1794,18 +1825,18 @@ namespace QuickMedia {
chapters_tab.type = EpisodeListTabType::CHAPTERS;
chapters_tab.body = body;
chapters_tab.creator = nullptr;
- chapters_tab.text = sf::Text("Chapters", font, tab_text_size);
+ chapters_tab.text = sf::Text("Chapters", *font, tab_text_size);
tabs.push_back(std::move(chapters_tab));
const std::vector<Creator>& creators = manga->get_creators();
for(const Creator &creator : creators) {
EpisodeListTab tab;
tab.type = EpisodeListTabType::CREATOR;
- tab.body = new Body(this, &font, &bold_font);
+ tab.body = new Body(this, font.get(), bold_font.get(), cjk_font.get());
tab.body->draw_thumbnails = true;
tab.creator = &creator;
tab.creator_page_download_future = std::async(std::launch::async, download_creator_page, creator.url);
- tab.text = sf::Text(creator.name, font, tab_text_size);
+ tab.text = sf::Text(creator.name, *font, tab_text_size);
tabs.push_back(std::move(tab));
}
@@ -2069,7 +2100,7 @@ namespace QuickMedia {
sf::Texture image_texture;
sf::Sprite image;
- sf::Text error_message("", font, 30);
+ sf::Text error_message("", *font, 30);
error_message.setFillColor(sf::Color::White);
assert(current_plugin->is_manga());
@@ -2132,7 +2163,7 @@ namespace QuickMedia {
bool redraw = true;
sf::Event event;
- sf::Text chapter_text(content_title + " | " + chapter_title + " | Page " + std::to_string(image_index + 1) + "/" + std::to_string(num_images), font, 14);
+ sf::Text chapter_text(content_title + " | " + chapter_title + " | Page " + std::to_string(image_index + 1) + "/" + std::to_string(num_images), *font, 14);
if(image_index == num_images)
chapter_text.setString(content_title + " | " + chapter_title + " | End");
chapter_text.setFillColor(sf::Color::White);
@@ -2296,7 +2327,7 @@ namespace QuickMedia {
json_chapter = Json::Value(Json::objectValue);
}
- ImageViewer image_viewer(image_plugin, images_url, content_title, chapter_title, image_index, content_cache_dir, &font);
+ ImageViewer image_viewer(image_plugin, images_url, content_title, chapter_title, image_index, content_cache_dir, font.get());
json_chapter["current"] = std::min(latest_read, image_viewer.get_num_pages());
json_chapter["total"] = image_viewer.get_num_pages();
@@ -2490,7 +2521,7 @@ namespace QuickMedia {
search_bar->text_autosearch_delay = file_manager->get_search_delay();
Page previous_page = pop_page_stack();
- sf::Text current_dir_text(file_manager->get_current_dir().string(), bold_font, 18);
+ sf::Text current_dir_text(file_manager->get_current_dir().string(), *bold_font, 18);
// TODO: Make asynchronous.
// TODO: Automatically go to the parent if this fails (recursively).
@@ -2664,7 +2695,7 @@ namespace QuickMedia {
std::mutex attachment_load_mutex;
GoogleCaptchaChallengeInfo challenge_info;
- sf::Text challenge_description_text("", font, 24);
+ sf::Text challenge_description_text("", *font, 24);
challenge_description_text.setFillColor(sf::Color::White);
const size_t captcha_num_columns = 3;
const size_t captcha_num_rows = 3;
@@ -3050,11 +3081,11 @@ namespace QuickMedia {
void Program::chat_login_page() {
assert(current_plugin->name == "matrix");
- SearchBar login_input(font, nullptr, "Username");
- SearchBar password_input(font, nullptr, "Password", true);
- SearchBar homeserver_input(font, nullptr, "Homeserver");
+ SearchBar login_input(*font, nullptr, "Username");
+ SearchBar password_input(*font, nullptr, "Password", true);
+ SearchBar homeserver_input(*font, nullptr, "Homeserver");
- sf::Text status_text("", font, 18);
+ sf::Text status_text("", *font, 18);
const int num_inputs = 3;
SearchBar *inputs[num_inputs] = { &login_input, &password_input, &homeserver_input };
@@ -3150,18 +3181,18 @@ namespace QuickMedia {
ChatTab messages_tab;
messages_tab.type = ChatTabType::MESSAGES;
- messages_tab.body = std::make_unique<Body>(this, &font, &bold_font);
+ messages_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
messages_tab.body->draw_thumbnails = true;
//messages_tab.body->line_seperator_color = sf::Color::Transparent;
- messages_tab.text = sf::Text("Messages", font, tab_text_size);
+ messages_tab.text = sf::Text("Messages", *font, tab_text_size);
tabs.push_back(std::move(messages_tab));
ChatTab rooms_tab;
rooms_tab.type = ChatTabType::ROOMS;
- rooms_tab.body = std::make_unique<Body>(this, &font, &bold_font);
+ rooms_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
rooms_tab.body->draw_thumbnails = true;
//rooms_tab.body->line_seperator_color = sf::Color::Transparent;
- rooms_tab.text = sf::Text("Rooms", font, tab_text_size);
+ rooms_tab.text = sf::Text("Rooms", *font, tab_text_size);
tabs.push_back(std::move(rooms_tab));
const int MESSAGES_TAB_INDEX = 0;
@@ -3206,7 +3237,7 @@ namespace QuickMedia {
plugin_logo.setSmooth(true);
}
- SearchBar chat_input(font, &plugin_logo, "Send a message...");
+ SearchBar chat_input(*font, &plugin_logo, "Send a message...");
chat_input.set_background_color(sf::Color::Transparent);
// TODO: Filer for rooms and settings
diff --git a/src/Text.cpp b/src/Text.cpp
index 0517c15..bdd41c3 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -21,31 +21,12 @@ namespace QuickMedia
return -1;
}
- Text::Text(const sf::Font *_font) :
- font(_font),
- characterSize(0),
- vertices(sf::PrimitiveType::Quads),
- maxWidth(0.0f),
- color(sf::Color::White),
- urlColor(URL_COLOR),
- dirty(false),
- dirtyText(false),
- dirtyCaret(false),
- plainText(false),
- editable(false),
- visible(true),
- caretMoveDirection(CaretMoveDirection::NONE),
- lineSpacing(0.0f),
- characterSpacing(0.0f),
- caretIndex(0)
- {
-
- }
+ Text::Text(const sf::Font *_font, const sf::Font *_cjk_font) : Text("", _font, _cjk_font, 0, 0.0f, false) {}
- Text::Text(const sf::String &_str, const sf::Font *_font, unsigned int _characterSize, float _maxWidth, bool _plainText) :
+ Text::Text(sf::String _str, const sf::Font *_font, const sf::Font *_cjk_font, unsigned int _characterSize, float _maxWidth, bool _plainText) :
font(_font),
+ cjk_font(_cjk_font),
characterSize(_characterSize),
- vertices(sf::PrimitiveType::Quads),
maxWidth(_maxWidth),
color(sf::Color::White),
urlColor(URL_COLOR),
@@ -60,19 +41,21 @@ namespace QuickMedia
characterSpacing(0.0f),
caretIndex(0)
{
- setString(_str);
+ vertices[0].setPrimitiveType(sf::PrimitiveType::Quads);
+ vertices[1].setPrimitiveType(sf::PrimitiveType::Quads);
+ setString(std::move(_str));
}
- void Text::setString(const sf::String &str)
+ void Text::setString(sf::String str)
{
if(str != this->str)
{
- this->str = str;
+ this->str = std::move(str);
dirty = true;
dirtyText = true;
- if((int)str.getSize() < caretIndex)
+ if((int)this->str.getSize() < caretIndex)
{
- caretIndex = str.getSize();
+ caretIndex = this->str.getSize();
dirtyCaret = true;
}
}
@@ -177,22 +160,68 @@ namespace QuickMedia
{
return boundingBox.height;
}
+
+ // TODO: Is there a more efficient way to do this? maybe japanese characters have a specific bit-pattern?
+ static bool is_japanese_codepoint(sf::Uint32 codepoint) {
+ return (codepoint >= 0x2E80 && codepoint <= 0x2FD5) // Kanji radicals
+ || (codepoint >= 0x3000 && codepoint <= 0x303F) // Punctuation
+ || (codepoint >= 0x3041 && codepoint <= 0x3096) // Hiragana
+ || (codepoint >= 0x30A0 && codepoint <= 0x30FF) // Katakana
+ || (codepoint >= 0x31F0 && codepoint <= 0x31FF) // Miscellaneous symbols and characters 1
+ || (codepoint >= 0x3220 && codepoint <= 0x3243) // Miscellaneous symbols and characters 2
+ || (codepoint >= 0x3280 && codepoint <= 0x337F) // Miscellaneous symbols and characters 3
+ || (codepoint >= 0x3400 && codepoint <= 0x4DB5) // Kanji 1
+ || (codepoint >= 0x4E00 && codepoint <= 0x9FCB) // Kanji 2
+ || (codepoint >= 0xF900 && codepoint <= 0xFA6A) // Kanji 3
+ || (codepoint >= 0xFF01 && codepoint <= 0xFF5E) // Alphanumeric and punctuation (full width)
+ || (codepoint >= 0xFF5F && codepoint <= 0xFF9F); // Katakana and punctuation (half width)
+ }
+
+ static size_t find_end_of_japanese(const sf::Uint32 *str, size_t size) {
+ for(size_t i = 0; i < size; ++i) {
+ if(!is_japanese_codepoint(str[i]))
+ return i;
+ }
+ return size;
+ }
+
+ static size_t find_end_of_non_japanese(const sf::Uint32 *str, size_t size) {
+ for(size_t i = 0; i < size; ++i) {
+ if(is_japanese_codepoint(str[i]))
+ return i;
+ }
+ return size;
+ }
+
+ void Text::splitTextByFont() {
+ textElements.clear();
+ size_t index = 0;
+ size_t size = str.getSize();
+ while(index < size) {
+ size_t offset;
+ bool is_japanese = is_japanese_codepoint(str[index]);
+ if(is_japanese)
+ offset = find_end_of_japanese(str.getData() + index + 1, size - index - 1);
+ else
+ offset = find_end_of_non_japanese(str.getData() + index + 1, size - index - 1);
+ textElements.push_back({ StringViewUtf32(str.getData() + index, offset + 1), TextElement::Type::TEXT });
+ textElements.back().is_japanese = is_japanese;
+ index += 1 + offset;
+ }
+ }
// Logic loosely based on https://github.com/SFML/SFML/wiki/Source:-CurvedText
- void Text::updateGeometry(bool update_even_if_not_dirty)
- {
- if(dirtyText)
- {
- textElements.clear();
- StringViewUtf32 wholeStr(this->str.getData(), this->str.getSize());
- textElements.push_back({ wholeStr, TextElement::Type::TEXT });
+ void Text::updateGeometry(bool update_even_if_not_dirty) {
+ if(dirtyText) {
dirtyText = false;
+ splitTextByFont();
}
if(!update_even_if_not_dirty && !dirty)
return;
- vertices.clear();
+ vertices[0].clear();
+ vertices[1].clear();
float hspace = font->getGlyph(' ', characterSize, false).advance + characterSpacing;
float vspace = font->getLineSpacing(characterSize);
@@ -205,14 +234,20 @@ namespace QuickMedia
for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex)
{
TextElement &textElement = textElements[textElementIndex];
+ const sf::Font *ff = font;
+ size_t vertices_index = 0;
+ if(textElement.is_japanese) {
+ ff = cjk_font;
+ vertices_index = 1;
+ }
- usize vertexOffset = vertices.getVertexCount();
- vertices.resize(vertices.getVertexCount() + 4 * (textElement.text.size + 1));
+ usize vertexOffset = vertices[vertices_index].getVertexCount();
+ vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * (textElement.text.size + 1));
textElement.position = glyphPos;
for(size_t i = 0; i < textElement.text.size; ++i)
{
sf::Uint32 codePoint = textElement.text[i];
- float kerning = font->getKerning(prevCodePoint, codePoint, characterSize);
+ float kerning = ff->getKerning(prevCodePoint, codePoint, characterSize);
prevCodePoint = codePoint;
glyphPos.x += kerning;
@@ -222,10 +257,10 @@ namespace QuickMedia
{
case ' ':
{
- vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
glyphPos.x += hspace;
if(glyphPos.x > maxWidth * 0.5f)
{
@@ -236,10 +271,10 @@ namespace QuickMedia
}
case '\t':
{
- vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
glyphPos.x += (hspace * TAB_WIDTH);
if(glyphPos.x > maxWidth * 0.5f)
{
@@ -250,17 +285,17 @@ namespace QuickMedia
}
case '\n':
{
- vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
glyphPos.x = 0.0f;
glyphPos.y += floor(vspace + lineSpacing);
continue;
}
}
- const sf::Glyph &glyph = font->getGlyph(codePoint, characterSize, false);
+ const sf::Glyph &glyph = ff->getGlyph(codePoint, characterSize, false);
if(glyphPos.x + glyph.advance > maxWidth)
{
// If there was a space in the text and text width is too long, then we need to word wrap at space index instead,
@@ -271,7 +306,7 @@ namespace QuickMedia
{
for(size_t k = 0; k < 4; ++k)
{
- sf::Vector2f &vertexPos = vertices[vertexOffset + j * 4 + k].position;
+ sf::Vector2f &vertexPos = vertices[vertices_index][vertexOffset + j * 4 + k].position;
vertexPos.x -= lastSpacingAccumulatedOffset;
vertexPos.y += floor(vspace + lineSpacing);
}
@@ -299,18 +334,18 @@ namespace QuickMedia
sf::Color fontColor = (textElement.type == TextElement::Type::TEXT ? color : urlColor);
- vertices[vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft };
- vertices[vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight };
- vertices[vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight };
- vertices[vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft };
+ vertices[vertices_index][vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft };
+ vertices[vertices_index][vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight };
+ vertices[vertices_index][vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight };
+ vertices[vertices_index][vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft };
glyphPos.x += glyph.advance + characterSpacing;
}
- vertices[vertices.getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices.getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices.getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices.getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertices[vertices_index].getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertices[vertices_index].getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertices[vertices_index].getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertices[vertices_index].getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
prevCodePoint = 0;
}
@@ -318,16 +353,19 @@ namespace QuickMedia
boundingBox.height = glyphPos.y + lineSpacing;
boundingBox.height += vspace;
- usize numVertices = vertices.getVertexCount();
- for(usize i = 0; i < numVertices; i += 4)
- {
- const sf::Vertex &bottomRight = vertices[i + 2];
- boundingBox.width = std::max(boundingBox.width, bottomRight.position.x);
+ for(size_t vertices_index = 0; vertices_index < 2; ++vertices_index) {
+ usize numVertices = vertices[vertices_index].getVertexCount();
+ for(usize i = 0; i < numVertices; i += 4)
+ {
+ const sf::Vertex &bottomRight = vertices[vertices_index][i + 2];
+ boundingBox.width = std::max(boundingBox.width, bottomRight.position.x);
+ }
}
dirty = false;
}
+#if 0
void Text::updateCaret()
{
assert(!dirty && !dirtyText);
@@ -339,6 +377,7 @@ namespace QuickMedia
return;
}
+
switch(caretMoveDirection)
{
case CaretMoveDirection::UP:
@@ -380,13 +419,13 @@ namespace QuickMedia
caretPosition = topLeftVertex.position;
}
}
-
+
bool Text::isCaretAtEnd() const
{
assert(!dirty && !dirtyText);
return textElements[0].text.size == 0 || caretIndex == (int)textElements[0].text.size;
}
-
+
// TODO: This can be optimized by using binary search
int Text::getStartOfLine(int startIndex) const
{
@@ -604,21 +643,22 @@ namespace QuickMedia
dirtyCaret = true;
}
}
-
+#endif
bool Text::draw(sf::RenderTarget &target)
{
updateGeometry();
+#if 0
if(dirtyCaret || caretMoveDirection != CaretMoveDirection::NONE)
{
updateCaret();
dirtyCaret = false;
caretMoveDirection = CaretMoveDirection::NONE;
}
+#endif
float vspace = font->getLineSpacing(characterSize);
- sf::RenderStates states;
sf::Vector2f pos = position;
pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left
@@ -633,22 +673,29 @@ namespace QuickMedia
if(!editable && visible && lastSeenTimer.getElapsedTime().asMilliseconds() > 3000)
{
visible = false;
- vertices.resize(0);
+ vertices[0].resize(0);
+ vertices[1].resize(0);
}
return false;
}
- if(!visible)
+ if(!visible) {
+ visible = true;
updateGeometry(true);
+ }
- states.transform.translate(pos);
- states.texture = &font->getTexture(characterSize);
- target.draw(vertices, states);
+ const sf::Font *fonts[] = { font, cjk_font };
+ for(size_t i = 0; i < 2; ++i) {
+ sf::RenderStates states;
+ states.transform.translate(pos);
+ states.texture = &fonts[i]->getTexture(characterSize);
+ target.draw(vertices[i], states);
+ }
lastSeenTimer.restart();
- visible = true;
pos.y -= floor(vspace);
+#if 0
if(!editable) return true;
//float rows = floor(totalHeight / (vspace + lineSpacing));
@@ -659,5 +706,8 @@ namespace QuickMedia
caretRect.setPosition(sf::Vector2f(floor(pos.x + caretPosition.x), floor(pos.y + caretRow * (vspace + lineSpacing))));
target.draw(caretRect);
return true;
+#else
+ return true;
+#endif
}
}
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index 4490f39..a70070e 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -519,7 +519,7 @@ namespace QuickMedia {
html_unescape_sequences(comment_text);
BodyItem *body_item = result_items[body_item_index].get();
body_item->set_title(std::move(comment_text));
- body_item->author = std::move(author_str);
+ body_item->set_author(std::move(author_str));
const Json::Value &ext = post["ext"];
const Json::Value &tim = post["tim"];
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index e806f39..1e8b927 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -190,7 +190,7 @@ namespace QuickMedia {
result_items.back()->append_description(it->body);
} else {
auto body_item = std::make_unique<BodyItem>("");
- body_item->author = user_info.display_name;
+ body_item->set_author(user_info.display_name);
body_item->set_description(it->body);
if(!it->thumbnail_url.empty())
body_item->thumbnail_url = it->thumbnail_url;