diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 6bad47d..a3becde 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -101,11 +101,6 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) { a->textBox->set_text_style_modifier_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos, currentMods[TextStyleModifier::ModifierType::COLOR]); } - if(t.gui.text_button_wide("print text", "Print HTML Selection")) { - std::cout << "-----HTML-----\n" << a->textBox->get_rich_text_data_between(a->cursor->selectionBeginPos, a->cursor->selectionEndPos).get_html() << std::endl; - } - - t.gui.pop_id(); if(a->textBox->inputChangedTextBox) diff --git a/src/InputManager.cpp b/src/InputManager.cpp index f8ab630..4e176d9 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -5,6 +5,8 @@ #include +#include "Server/CommandList.hpp" + #ifdef __EMSCRIPTEN__ #include @@ -116,6 +118,14 @@ std::string InputManager::get_clipboard_str() { #endif } +std::string InputManager::get_clipboard_data_for_mimetype(const std::string& mimeType) { + size_t datSize = 0; + void* dat = SDL_GetClipboardData(mimeType.c_str(), &datSize); + if(!dat) + return ""; + return std::string(static_cast(dat), datSize); +} + void InputManager::set_clipboard_str(std::string_view s) { #ifdef __EMSCRIPTEN__ emscripten_browser_clipboard::copy(std::string(s)); @@ -124,6 +134,40 @@ void InputManager::set_clipboard_str(std::string_view s) { #endif } +std::string InputManager::get_infpnt_richtext_mimetype() { + return "infpnt/richtext" + std::string(VERSION_STRING); +} + +void InputManager::set_clipboard_plain_and_richtext_pair(const std::pair& plainAndRichtextPair) { + std::unordered_map clipboardData; + clipboardData["text/plain"] = plainAndRichtextPair.first; + clipboardData[get_infpnt_richtext_mimetype()] = plainAndRichtextPair.second; + set_clipboard_data(clipboardData); +} + +void InputManager::set_clipboard_data(const std::unordered_map& newClipboardData) { + SDL_ClearClipboardData(); + clipboardData = newClipboardData; + + std::vector mimeTypes; + for(auto& [k, v] : clipboardData) + mimeTypes.emplace_back(k.c_str()); + + SDL_SetClipboardData( + [](void *userdata, const char *mime_type, size_t *size) { + std::unordered_map& clipboardData = *static_cast*>(userdata); + std::string mimeTypeStr(mime_type); + auto it = clipboardData.find(mimeTypeStr); + if(it == clipboardData.end()) + return static_cast(nullptr); + *size = it->second.length(); + return static_cast(it->second.c_str()); + }, + [](void *userdata) { + }, + static_cast(&clipboardData), mimeTypes.data(), mimeTypes.size()); +} + std::string InputManager::key_assignment_to_str(const Vector2ui32& k) { std::string toRet; if(k.x() & SDL_KMOD_CTRL) @@ -276,19 +320,24 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_COPY); if(text.textBox && key(KEY_TEXT_COPY).repeat) - set_clipboard_str(text.textBox->process_copy(*text.cursor)); + set_clipboard_plain_and_richtext_pair(text.textBox->process_copy(*text.cursor)); break; case SDLK_X: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_CUT); if(text.textBox && key(KEY_TEXT_CUT).repeat) - set_clipboard_str(text.textBox->process_cut(*text.cursor)); + set_clipboard_plain_and_richtext_pair(text.textBox->process_cut(*text.cursor)); break; case SDLK_V: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_PASTE); - if(text.textBox && key(KEY_TEXT_PASTE).repeat) - text.textBox->process_text_input(*text.cursor, get_clipboard_str(), text.modMap); + if(text.textBox && key(KEY_TEXT_PASTE).repeat) { + std::string richTextClipboard = get_clipboard_data_for_mimetype(get_infpnt_richtext_mimetype()); + if(!richTextClipboard.empty()) + text.textBox->process_rich_text_input(*text.cursor, richTextClipboard); + else + text.textBox->process_text_input(*text.cursor, get_clipboard_data_for_mimetype("text/plain"), text.modMap); + } break; case SDLK_A: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 5d34ca5..8b821f8 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -195,7 +195,15 @@ struct InputManager { bool get_clipboard_paste_happened(); std::string get_clipboard_str(); + + std::string get_clipboard_data_for_mimetype(const std::string& mimeType); + void set_clipboard_str(std::string_view s); + void set_clipboard_plain_and_richtext_pair(const std::pair& plainAndRichtextPair); + void set_clipboard_data(const std::unordered_map& newClipboardData); + std::string get_infpnt_richtext_mimetype(); + + std::unordered_map clipboardData; #ifdef __EMSCRIPTEN__ bool clipboardPasteEventHappened = false; diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp index e5d962e..f179030 100644 --- a/src/RichText/TextBox.cpp +++ b/src/RichText/TextBox.cpp @@ -1,5 +1,6 @@ #include "TextBox.hpp" #include "TextStyleModifier.hpp" +#include "cereal/archives/portable_binary.hpp" #include #include #include @@ -15,128 +16,13 @@ namespace RichText { -std::string TextData::get_html() { - auto nextTStyleModIt = tStyleMods.begin(); - - skia::textlayout::TextStyle tStyle; - - std::stringstream toRet; - - for(size_t pIndex = 0; pIndex < paragraphs.size(); pIndex++) { - Paragraph& pData = paragraphs[pIndex]; - size_t tIndex = 0; - - toRet << "

"; - - for(;;) { - if(nextTStyleModIt == tStyleMods.end() || nextTStyleModIt->pos.fParagraphIndex != pIndex) { - if(pData.text.length() != tIndex) { - toRet << "" - << pData.text.substr(tIndex, pData.text.length() - tIndex) - << ""; - } - break; - } - else { - auto& nextTStyleMod = *nextTStyleModIt; - - // Print with previous style - if(nextTStyleMod.pos.fTextByteIndex != tIndex) { - toRet << "" - << pData.text.substr(tIndex, nextTStyleMod.pos.fTextByteIndex - tIndex) - << ""; - } - - tIndex = nextTStyleMod.pos.fTextByteIndex; - - // Modify tStyle with new data - for(auto& [modType, modifier] : nextTStyleMod.mods) - modifier->modify_text_style(tStyle); - - ++nextTStyleModIt; - } - } - - toRet << "

"; +std::string TextData::get_serialized() { + std::stringstream s; + { + cereal::PortableBinaryOutputArchive a(s); + a(*this); } - - return toRet.str(); -} - -std::string TextData::get_css_from_paragraph_style(const ParagraphStyleData& pStyle) { - std::stringstream toRet; - toRet << "text-align:"; - switch(pStyle.textAlignment) { - case skia::textlayout::TextAlign::kLeft: - toRet << "left"; - break; - case skia::textlayout::TextAlign::kRight: - toRet << "right"; - break; - case skia::textlayout::TextAlign::kCenter: - toRet << "center"; - break; - case skia::textlayout::TextAlign::kJustify: - toRet << "justify"; - break; - default: - toRet << "left"; - break; - } - toRet << ";" - << "direction:"; - switch(pStyle.textDirection) { - case skia::textlayout::TextDirection::kLtr: - toRet << "ltr"; - break; - case skia::textlayout::TextDirection::kRtl: - toRet << "rtl"; - break; - } - toRet << ";"; - return toRet.str(); -} - -std::string TextData::get_css_from_text_style(const skia::textlayout::TextStyle& tStyle) { - std::stringstream toRet; - toRet << "font: " << static_cast(tStyle.getFontSize()) << "px "; - auto& fontFamilies = tStyle.getFontFamilies(); - for(size_t i = 0; i < fontFamilies.size(); i++) { - auto& family = fontFamilies[i]; - toRet << std::string(family.c_str(), family.size()); - if(i != fontFamilies.size() - 1) - toRet << ","; - } - SkPaint paint = tStyle.getForeground(); - toRet << ";" - << "color: rgb(" << std::fixed << std::setprecision(7) << paint.getColor4f().fR * 255.0f << " " << paint.getColor4f().fG * 255.0f << " " << paint.getColor4f().fB * 255.0f << " / " << paint.getColor4f().fA << ");" << std::defaultfloat - << "font-weight:" << tStyle.getFontStyle().weight() << "; " - << "font-style:"; - switch(tStyle.getFontStyle().slant()) { - case SkFontStyle::Slant::kUpright_Slant: - toRet << "normal"; - break; - case SkFontStyle::Slant::kItalic_Slant: - toRet << "italic"; - break; - case SkFontStyle::Slant::kOblique_Slant: - toRet << "oblique"; - break; - } - toRet << ";" - << "text-decoration-line:"; - if(tStyle.getDecorationType() == skia::textlayout::TextDecoration::kNoDecoration) - toRet << " none"; - else { - if(tStyle.getDecorationType() & skia::textlayout::TextDecoration::kOverline) - toRet << " overline"; - if(tStyle.getDecorationType() & skia::textlayout::TextDecoration::kUnderline) - toRet << " underline"; - if(tStyle.getDecorationType() & skia::textlayout::TextDecoration::kLineThrough) - toRet << " line-through"; - } - toRet << ";"; - return toRet.str(); + return s.str(); } void TextData::save(cereal::PortableBinaryOutputArchive& a) const { @@ -265,12 +151,12 @@ void TextBox::process_mouse_left_button(Cursor& cur, const Vector2f& pos, bool c } } -std::string TextBox::process_copy(Cursor& cur) { - return get_text_between(cur.selectionBeginPos, cur.selectionEndPos); +std::pair TextBox::process_copy(Cursor& cur) { + return {get_text_between(cur.selectionBeginPos, cur.selectionEndPos), get_rich_text_data_between(cur.selectionBeginPos, cur.selectionEndPos).get_serialized()}; } -std::string TextBox::process_cut(Cursor& cur) { - std::string toRet = process_copy(cur); +std::pair TextBox::process_cut(Cursor& cur) { + auto toRet = process_copy(cur); if(cur.selectionBeginPos != cur.selectionEndPos) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.previousX = std::nullopt; @@ -289,6 +175,22 @@ void TextBox::process_text_input(Cursor& cur, const std::string& in, const std:: } } +void TextBox::process_rich_text_input(Cursor& cur, const std::string& serializedRichText) { + if(!serializedRichText.empty()) { + if(cur.selectionBeginPos != cur.selectionEndPos) + cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); + RichText::TextData textData; + std::stringstream ss(serializedRichText); + { + cereal::PortableBinaryInputArchive a(ss); + a(textData); + } + cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert_rich_text(cur.pos, textData); + cur.previousX = std::nullopt; + inputChangedTextBox = true; + } +} + size_t TextBox::count_grapheme(const std::string& text) { auto u = SkUnicodes::ICU::Make(); auto breakIterator = u->makeBreakIterator(SkUnicode::BreakType::kGraphemes); @@ -442,6 +344,44 @@ TextData TextBox::get_rich_text_data_between(TextPosition p1, TextPosition p2) { return toRet; } +TextPosition TextBox::insert_rich_text(TextPosition p, const TextData& richText) { + p = move(Movement::NOWHERE, p); + + if(richText.paragraphs.empty()) + return p; + + std::string strToInsert; + for(auto& p : richText.paragraphs) { + strToInsert += p.text; + strToInsert += '\n'; + } + strToInsert.pop_back(); + TextPosition toRet = insert(p, strToInsert); + + TextPosition lastPos; + lastPos.fParagraphIndex = p.fParagraphIndex + richText.paragraphs.size() - 1; + if(richText.paragraphs.size() == 1) + lastPos.fTextByteIndex = p.fTextByteIndex + richText.paragraphs[0].text.size(); + else + lastPos.fTextByteIndex = richText.paragraphs.back().text.size(); + + for(auto& [modPos, modMap] : richText.tStyleMods) { + TextPosition shiftedModPos; + shiftedModPos.fParagraphIndex = modPos.fParagraphIndex + p.fParagraphIndex; + if(shiftedModPos.fParagraphIndex == p.fParagraphIndex) + shiftedModPos.fTextByteIndex = p.fTextByteIndex + modPos.fTextByteIndex; + else + shiftedModPos.fTextByteIndex = modPos.fTextByteIndex; + for(auto& [modType, modifier] : modMap) + set_text_style_modifier_between(shiftedModPos, lastPos, modifier); + } + + inputChangedTextBox = true; + needsRebuild = true; + + return toRet; +} + void TextBox::clear_text() { paragraphs.clear(); tStyleMods.clear(); diff --git a/src/RichText/TextBox.hpp b/src/RichText/TextBox.hpp index 64064e8..16806e7 100644 --- a/src/RichText/TextBox.hpp +++ b/src/RichText/TextBox.hpp @@ -16,8 +16,14 @@ struct TextPosition { // Set to uint32_t values because theyll be saved and sent over networks. Size has to be consistent size_t fParagraphIndex = std::numeric_limits::max(); size_t fTextByteIndex = std::numeric_limits::max(); - template void serialize(Archive& a) { - a((uint32_t)fParagraphIndex, (uint32_t)fTextByteIndex); + template void save(Archive& a) const { + a(static_cast(fParagraphIndex), static_cast(fTextByteIndex)); + } + template void load(Archive& a) { + uint32_t p, t; + a(p, t); + fParagraphIndex = p; + fTextByteIndex = t; } bool operator==(const TextPosition& o) const = default; bool operator<(const TextPosition& o) const; @@ -42,14 +48,11 @@ class TextData { a(text, pStyleData); } }; - std::string get_html(); + std::string get_serialized(); void save(cereal::PortableBinaryOutputArchive& a) const; void load(cereal::PortableBinaryInputArchive& a); TextStyleModContainer tStyleMods; std::vector paragraphs; - private: - std::string get_css_from_text_style(const skia::textlayout::TextStyle& tStyle); - std::string get_css_from_paragraph_style(const ParagraphStyleData& pStyle); }; class TextBox { @@ -123,18 +126,20 @@ class TextBox { void set_rich_text_data(const TextData& richText); TextData get_rich_text_data_between(TextPosition p1, TextPosition p2); + TextPosition insert_rich_text(TextPosition p, const TextData& richText); void clear_text(); void set_string(const std::string& str); void process_mouse_left_button(Cursor& cur, const Vector2f& pos, bool clicked, bool held, bool shift); void process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift, const std::optional& inputModMap = std::nullopt); - std::string process_copy(Cursor& cur); - std::string process_cut(Cursor& cur); + std::pair process_copy(Cursor& cur); + std::pair process_cut(Cursor& cur); std::string get_text_between(TextPosition p1, TextPosition p2); std::string get_string(); void set_tab_space_width(unsigned newTabWidth); void process_text_input(Cursor& cur, const std::string& in, const std::optional& inputModMap = std::nullopt); + void process_rich_text_input(Cursor& cur, const std::string& serializedRichText); private: std::pair get_start_end_text_pos(TextPosition p1, TextPosition p2);