aboutsummaryrefslogtreecommitdiff
path: root/src/Text.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-11-06 13:54:02 +0100
committerdec05eba <dec05eba@protonmail.com>2022-11-07 14:24:32 +0100
commit8025d1075db0779bde635148f6e38303eb29d6c8 (patch)
tree64dce4cc00fa55edba0ab7d2522e13473e6ef3c4 /src/Text.cpp
parentf8b3a9d055bfc0e4bb9e9a570ccc8853ec38a225 (diff)
Formatted text with color in matrix, monospace for codeblocks
Diffstat (limited to 'src/Text.cpp')
-rw-r--r--src/Text.cpp309
1 files changed, 169 insertions, 140 deletions
diff --git a/src/Text.cpp b/src/Text.cpp
index 7692eef..e6fe90c 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -6,6 +6,7 @@
#include "../include/StringUtils.hpp"
#include "../generated/Emoji.hpp"
#include <string.h>
+#include <stack>
#include <mglpp/graphics/Rectangle.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/window/Window.hpp>
@@ -16,6 +17,8 @@
// TODO: Remove
#include <sstream>
+// TODO: text editing should take into consideration FORMATTED_TEXT_START/FORMATTED_TEXT_END.
+
namespace QuickMedia
{
static float floor(float v) {
@@ -30,11 +33,11 @@ namespace QuickMedia
static const float WORD_WRAP_MIN_SIZE = 80.0f;
static const size_t FONT_INDEX_LATIN = 0;
- 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_INDEX_IMAGE = 4;
- static const size_t FONT_ARRAY_SIZE = 5;
+ static const size_t FONT_INDEX_MONOSPACE = 1;
+ static const size_t FONT_INDEX_CJK = 2;
+ static const size_t FONT_INDEX_SYMBOLS = 3;
+ static const size_t FONT_INDEX_EMOJI = 4;
+ static const size_t FONT_INDEX_IMAGE = 5;
static const uint8_t FORMATTED_TEXT_START = '\x02';
static const uint8_t FORMATTED_TEXT_END = '\x03';
@@ -49,6 +52,7 @@ namespace QuickMedia
characterSize(12),
maxWidth(0.0f),
color(get_theme().text_color),
+ force_color(false),
dirty(true),
dirtyText(true),
dirtyCaret(true),
@@ -69,6 +73,7 @@ namespace QuickMedia
characterSize(characterSize),
maxWidth(maxWidth),
color(get_theme().text_color),
+ force_color(false),
dirty(true),
dirtyText(true),
dirtyCaret(true),
@@ -83,21 +88,6 @@ namespace QuickMedia
{
setString(std::move(_str));
}
-
- Text::Text(const Text &other) : Text(other.str, other.bold_font, other.characterSize, other.maxWidth, other.highlight_urls) {
-
- }
-
- Text& Text::operator=(const Text &other) {
- str = other.str;
- bold_font = other.bold_font;
- characterSize = other.characterSize;
- maxWidth = other.maxWidth;
- highlight_urls = other.highlight_urls;
- caretIndex = other.caretIndex;
- position = other.position;
- return *this;
- }
void Text::setString(std::string str)
{
@@ -126,25 +116,24 @@ 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) {
+ const uint32_t str_size = url.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((const char*)&str_size, sizeof(str_size));
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 Text::formatted_text(const std::string &text, mgl::Color color, uint8_t text_flags) {
+ const uint32_t str_size = text.size();
std::string result;
result += FORMATTED_TEXT_START;
result += (uint8_t)FormattedTextType::TEXT;
@@ -152,7 +141,8 @@ namespace QuickMedia
result += color.g;
result += color.b;
result += color.a;
- result += (uint8_t)bold;
+ result += text_flags;
+ result.append((const char*)&str_size, sizeof(str_size));
result.append(text);
result += FORMATTED_TEXT_END;
return result;
@@ -163,6 +153,18 @@ namespace QuickMedia
dirty = true;
dirtyText = true;
}
+
+ // static
+ std::string Text::to_printable_string(const std::string &str) {
+ std::string result;
+ std::vector<TextElement> tmp_text_elements;
+ Text::split_text_by_type(tmp_text_elements, str, 0.0f);
+ for(auto &text_element : tmp_text_elements) {
+ if(text_element.type == TextElement::Type::TEXT)
+ result.append(text_element.text);
+ }
+ return result;
+ }
void Text::set_position(float x, float y)
{
@@ -227,13 +229,17 @@ namespace QuickMedia
return caretIndex;
}
- void Text::set_color(mgl::Color color)
+ void Text::set_color(mgl::Color color, bool force_color)
{
- if(color != this->color)
- {
+ if(color != this->color) {
this->color = color;
dirty = true;
}
+
+ if(force_color != this->force_color) {
+ this->force_color = force_color;
+ dirty = true;
+ }
}
void Text::setLineSpacing(float lineSpacing)
@@ -328,24 +334,6 @@ namespace QuickMedia
return codepoint >= 0x2800 && codepoint <= 0x28FF; // Braille
}
- static size_t find_end_of_cjk(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;
- }
-
- if(!is_cjk_codepoint(codepoint))
- return i;
-
- i += clen;
- }
- return size;
- }
-
// TODO: Optimize, dont use ostringstream
static std::string codepoint_to_hex_str(uint32_t codepoint) {
std::ostringstream ss;
@@ -353,7 +341,11 @@ namespace QuickMedia
return ss.str();
}
- static size_t find_end_of_symbol(const char *str, size_t size) {
+ static size_t find_end_text(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;
@@ -363,60 +355,64 @@ namespace QuickMedia
clen = 1;
}
- if(!is_symbol_codepoint(codepoint))
+ if(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) {
- uint32_t emoji_sequence[32];
- size_t emoji_sequence_length = 0;
- size_t emoji_byte_length = 0;
+ static size_t parse_formatted_string(const char *str, size_t size, std::string_view &text, mgl::Color &color, uint8_t &flags) {
+ flags = FORMATTED_TEXT_FLAG_NONE;
- 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;
- }
+ if(size < 5 + sizeof(uint32_t))
+ return size;
- 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;
+ color.r = str[0];
+ color.g = str[1];
+ color.b = str[2];
+ color.a = str[3];
+ flags |= (uint8_t)str[4];
- i += clen;
- }
+ uint32_t text_size;
+ memcpy(&text_size, str + 5, sizeof(text_size));
- return size;
+ if(size < 5 + sizeof(uint32_t) + text_size)
+ return size;
+
+ text = std::string_view(str + 5 + sizeof(uint32_t), text_size);
+ return std::min(5 + sizeof(uint32_t) + text_size + 1, size); // + 1 for FORMATTED_TEXT_END
}
-// 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(size < sizeof(image_size.x) + sizeof(image_size.y) + sizeof(image_local) + sizeof(uint32_t))
+ return size;
+
+ size_t offset = 0;
+ memcpy(&image_size.x, str, sizeof(image_size.x));
+ offset += sizeof(image_size.x);
+
+ memcpy(&image_size.y, str + offset, sizeof(image_size.y));
+ offset += sizeof(image_size.y);
+
+ memcpy(&image_local, str + offset, sizeof(image_local));
+ offset += sizeof(image_local);
+
+ uint32_t text_size;
+ memcpy(&text_size, str + offset, sizeof(text_size));
+ offset += sizeof(text_size);
+
+ if(size < offset + text_size)
+ return size;
+
+ image_url.assign(str + offset, text_size);
+ return std::min(offset + text_size + 1, size); // + 1 for FORMATTED_TEXT_END
}
static size_t parse_formatted_text(const char *str, size_t size, TextElement &text_element) {
@@ -427,8 +423,7 @@ namespace QuickMedia
switch(formatted_text_type) {
case FormattedTextType::TEXT: {
text_element.type = TextElement::Type::TEXT;
- // TODO:
- //return parse_formatted_text(str + 1, size - 1, text_element)
+ return parse_formatted_string(str + 1, size - 1, text_element.text, text_element.color, text_element.text_flags);
}
case FormattedTextType::IMAGE: {
text_element.type = TextElement::Type::IMAGE;
@@ -439,17 +434,9 @@ namespace QuickMedia
}
return 0;
}
-#endif
- void Text::split_text_by_type(std::vector<TextElement> &text_elements, const std::string &str) {
- text_elements.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);
+ // static
+ void Text::split_text_by_type(std::vector<TextElement> &text_elements, std::string_view str, float vspace) {
size_t index = 0;
size_t size = str.size();
@@ -470,31 +457,27 @@ namespace QuickMedia
TextElement text_element;
- if(is_symbol_codepoint(codepoint)) {
- 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)) {
- offset = find_end_of_cjk(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;
-
+ if(codepoint == FORMATTED_TEXT_START) {
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
+ offset = parse_formatted_text(str.data() + index, size - index, text_element) + 1; // TODO:
+ if(offset > 0) {
+ text_element.text_type = TextElement::TextType::TEXT;
+ if(text_element.type == TextElement::Type::TEXT) {
+ const std::string_view inside_text = text_element.text;
+ text_element.text = std::string_view("", 0);
+ text_element.text_num_bytes = 0;
+ text_element.type = TextElement::Type::FORMAT_START;
+ text_elements.push_back(text_element);
+
+ split_text_by_type(text_elements, inside_text, vspace);
+
+ text_element.type = TextElement::Type::FORMAT_END;
+ text_elements.push_back(std::move(text_element));
+ } else {
+ text_element.text_num_bytes = 1;
+ 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)) {
offset = emoji_byte_length;
@@ -510,19 +493,18 @@ namespace QuickMedia
if(emoji_codepoint_combined == "1f441-fe0f-200d-1f5e8-fe0f")
emoji_codepoint_combined = "1f441-200d-1f5e8";
- text_element.create_text("E");
+ text_element.create_text(std::string_view(str.data() + index, offset));
text_element.text_type = TextElement::TextType::EMOJI;
- text_element.text = "E";
text_element.url = "/usr/share/quickmedia/emoji/" + emoji_codepoint_combined + ".png";
text_element.local = true;
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);
+ offset = find_end_text(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_type = TextElement::TextType::TEXT;
text_element.text_num_bytes = text_element.text.size();
text_elements.push_back(std::move(text_element));
}
@@ -533,6 +515,16 @@ namespace QuickMedia
}
}
+ void Text::split_text_by_type() {
+ 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);
+ textElements.clear();
+ split_text_by_type(textElements, str, font_get_real_height(latin_font));
+ }
+
float Text::font_get_real_height(mgl::Font *font) {
return font->get_glyph('|').size.y + floor(4.0f * ((float)characterSize / (float)14.0f));
}
@@ -614,7 +606,7 @@ namespace QuickMedia
if(dirtyText) {
assert(dirty);
dirtyText = false;
- split_text_by_type(textElements, str);
+ split_text_by_type();
// TODO: Optimize
if(highlight_urls) {
url_ranges = extract_urls(str);
@@ -646,8 +638,14 @@ namespace QuickMedia
const float emoji_spacing = 2.0f;
const mgl::Color url_color = get_theme().url_text_color;
-
size_t url_range_index = 0;
+
+ struct TextFormat {
+ mgl::Color color;
+ uint8_t text_flags = FORMATTED_TEXT_FLAG_NONE;
+ };
+
+ std::stack<TextFormat> text_format_stack;
mgl::vec2f glyphPos;
uint32_t prevCodePoint = 0;
@@ -656,11 +654,25 @@ namespace QuickMedia
{
TextElement &textElement = textElements[textElementIndex];
+ mgl::Color text_element_color = color;
+ bool monospace = false;
+ if(!text_format_stack.empty()) {
+ if((text_format_stack.top().text_flags & FORMATTED_TEXT_FLAG_COLOR) && !force_color)
+ text_element_color = text_format_stack.top().color;
+ if(text_format_stack.top().text_flags & FORMATTED_TEXT_FLAG_MONOSPACE)
+ monospace = true;
+ }
+
mgl::Font *ff = latin_font;
int vertices_index = FONT_INDEX_LATIN;
- if(textElement.type == TextElement::Type::IMAGE) {
+ if(textElement.type == TextElement::Type::FORMAT_START) {
+ text_format_stack.push({ textElement.color, textElement.text_flags });
+ } else if(textElement.type == TextElement::Type::FORMAT_END) {
+ if(!text_format_stack.empty())
+ text_format_stack.pop();
+ } else if(textElement.type == TextElement::Type::IMAGE) {
vertices_index = FONT_INDEX_IMAGE;
- mgl::Color image_color(255, 255, 255, color.a);
+ mgl::Color image_color(255, 255, 255, text_element_color.a);
int vertexStart = vertices[vertices_index].size();
if(prevCodePoint != 0)
@@ -696,15 +708,9 @@ namespace QuickMedia
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) {
- ff = FontLoader::get_font(FontLoader::FontType::SYMBOLS, characterSize);
- vertices_index = FONT_INDEX_SYMBOLS;
} else if(textElement.text_type == TextElement::TextType::EMOJI) {
vertices_index = FONT_INDEX_EMOJI;
- mgl::Color emoji_color(255, 255, 255, color.a);
+ mgl::Color emoji_color(255, 255, 255, text_element_color.a);
int vertexStart = vertices[vertices_index].size();
const mgl::vec2f emoji_size = { vspace, vspace };
@@ -746,12 +752,12 @@ namespace QuickMedia
//vertices[vertices_index].resize(vertices[vertices_index].size() + 4 * textElement.text.size); // TODO: Precalculate
for(size_t i = 0; i < textElement.text.size();)
{
- mgl::Color text_color = color;
+ mgl::Color text_color = text_element_color;
if(url_range_index < url_ranges.size()) {
size_t string_offset = (textElement.text.data() + i) - str.data();
if(string_offset >= url_ranges[url_range_index].start && string_offset < url_ranges[url_range_index].start + url_ranges[url_range_index].length) {
text_color = url_color;
- text_color.a = color.a;
+ text_color.a = text_element_color.a;
if(string_offset + 1 == url_ranges[url_range_index].start + url_ranges[url_range_index].length)
++url_range_index;
}
@@ -764,6 +770,22 @@ namespace QuickMedia
codepoint = *cp;
clen = 1;
}
+
+ // TODO: CJK monospace
+ if(is_symbol_codepoint(codepoint)) {
+ ff = FontLoader::get_font(FontLoader::FontType::SYMBOLS, characterSize);
+ vertices_index = FONT_INDEX_SYMBOLS;
+ } else if(is_cjk_codepoint(codepoint)) {
+ ff = FontLoader::get_font(FontLoader::FontType::CJK, characterSize);
+ vertices_index = FONT_INDEX_CJK;
+ } else if(monospace) {
+ ff = FontLoader::get_font(FontLoader::FontType::LATIN_MONOSPACE, characterSize);
+ vertices_index = FONT_INDEX_MONOSPACE;
+ } else {
+ ff = latin_font;
+ vertices_index = FONT_INDEX_LATIN;
+ }
+
// TODO: Make this work when combining multiple different fonts (for example latin and japanese).
// For japanese we could use a hack, because all japanese characters are monospace (exception being half-width characters).
float kerning = ff->get_kerning(prevCodePoint, codepoint);
@@ -1277,7 +1299,7 @@ namespace QuickMedia
}
std::vector<TextElement> new_text_elements;
- split_text_by_type(new_text_elements, stringToAdd);
+ Text::split_text_by_type(new_text_elements, stringToAdd, 0.0f);
for(auto &text_element : new_text_elements) {
if(text_element.type == TextElement::Type::IMAGE || text_element.text_type == TextElement::TextType::EMOJI) {
caretIndex += 1;
@@ -1322,7 +1344,8 @@ namespace QuickMedia
const float vspace = font_get_real_height(latin_font);
pos.y += floor(vspace*0.25f); // Origin is at bottom left, we want it to be at top left
- const FontLoader::FontType font_types[] = { latin_font_type, FontLoader::FontType::CJK, FontLoader::FontType::SYMBOLS };
+ assert(FONT_ARRAY_SIZE == 6);
+ const FontLoader::FontType font_types[] = { latin_font_type, FontLoader::FontType::LATIN_MONOSPACE, FontLoader::FontType::CJK, FontLoader::FontType::SYMBOLS };
for(size_t i = 0; i < FONT_INDEX_EMOJI; ++i) {
if(vertex_buffers[i].size() == 0)
continue;
@@ -1346,6 +1369,9 @@ namespace QuickMedia
// 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);
+
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 });
@@ -1356,11 +1382,14 @@ namespace QuickMedia
emoji_data->loading_state = LoadingState::APPLIED_TO_TEXTURE;
}
- if(emoji_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) {
+ 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());
target.draw(sprite);
+ } else {
+ fallback_emoji.set_position(pos + textElement.pos.to_vec2f());
+ target.draw(fallback_emoji);
}
}
}