From 9cdac48374cb76c19a1fa421de665440dff8bf37 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Tue, 11 Nov 2025 11:46:02 +0300 Subject: [PATCH] Can now extract formatted HTML from rich textbox --- src/DrawingProgram/TextBoxEditTool.cpp | 2 - src/RichText/TextBox.cpp | 128 +++++++++++++++++++++++++ src/RichText/TextBox.hpp | 31 +++--- 3 files changed, 147 insertions(+), 14 deletions(-) diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 8230d1b..a3becde 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -101,8 +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]); } - - t.gui.pop_id(); if(a->textBox->inputChangedTextBox) diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp index 20fe3d8..ef38ea3 100644 --- a/src/RichText/TextBox.cpp +++ b/src/RichText/TextBox.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,96 @@ 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 << "

"; + } + + return toRet.str(); +} + +std::string TextData::get_css_from_textstyle(const skia::textlayout::TextStyle& tStyle) { + std::stringstream toRet; + toRet << "font: " << 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(" << static_cast(paint.getColor4f().fR * 255.0f) << " " << static_cast(paint.getColor4f().fG * 255.0f) << " " << static_cast(paint.getColor4f().fB * 255.0f) << " / " << paint.getColor4f().fA << ");" + << "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(); +} + void TextData::save(cereal::PortableBinaryOutputArchive& a) const { a(paragraphs); a(static_cast(tStyleMods.size())); @@ -280,6 +371,43 @@ TextData TextBox::get_rich_text_data() { return toRet; } +TextData TextBox::get_rich_text_data_between(TextPosition p1, TextPosition p2) { + auto [start, end] = get_start_end_text_pos(p1, p2); + + TextData toRet; + + if(start == end) { + toRet.paragraphs.emplace_back(); + return toRet; + } + + for(size_t pIndex = start.fParagraphIndex; pIndex <= end.fParagraphIndex; pIndex++) { + size_t textIndexStart = pIndex == start.fParagraphIndex ? start.fTextByteIndex : 0; + size_t textIndexEnd = pIndex == end.fParagraphIndex ? end.fTextByteIndex : paragraphs[pIndex].text.size(); + toRet.paragraphs.emplace_back(); + toRet.paragraphs.back().text = paragraphs[pIndex].text.substr(textIndexStart, textIndexEnd - textIndexStart); + toRet.paragraphs.back().pStyleData = paragraphs[pIndex].pStyleData; + } + + toRet.tStyleMods.emplace_back(TextPosition{0, 0}, get_mods_used_at_pos(p1)); + + for(auto& [modPos, modifiers] : tStyleMods) { + if(modPos <= p1) + continue; + else if(modPos > p2) + break; + TextPosition insertPos; + insertPos.fParagraphIndex = modPos.fParagraphIndex - p1.fParagraphIndex; + if(modPos.fParagraphIndex == p1.fParagraphIndex) + insertPos.fTextByteIndex = modPos.fTextByteIndex - p1.fTextByteIndex; + else + insertPos.fTextByteIndex = modPos.fTextByteIndex; + toRet.tStyleMods.emplace_back(insertPos, modifiers); + } + + return toRet; +} + void TextBox::clear_text() { paragraphs.clear(); tStyleMods.clear(); diff --git a/src/RichText/TextBox.hpp b/src/RichText/TextBox.hpp index 70cd1fb..91dfe55 100644 --- a/src/RichText/TextBox.hpp +++ b/src/RichText/TextBox.hpp @@ -33,18 +33,22 @@ struct PositionedTextStyleMod { typedef std::vector TextStyleModContainer; -struct TextData { - struct Paragraph { - std::string text; - ParagraphStyleData pStyleData; - template void serialize(Archive& a) { - a(text, pStyleData); - } - }; - void save(cereal::PortableBinaryOutputArchive& a) const; - void load(cereal::PortableBinaryInputArchive& a); - TextStyleModContainer tStyleMods; - std::vector paragraphs; +class TextData { + public: + struct Paragraph { + std::string text; + ParagraphStyleData pStyleData; + template void serialize(Archive& a) { + a(text, pStyleData); + } + }; + std::string get_html(); + void save(cereal::PortableBinaryOutputArchive& a) const; + void load(cereal::PortableBinaryInputArchive& a); + TextStyleModContainer tStyleMods; + std::vector paragraphs; + private: + std::string get_css_from_textstyle(const skia::textlayout::TextStyle& tStyle); }; class TextBox { @@ -116,6 +120,9 @@ class TextBox { TextData get_rich_text_data(); void set_rich_text_data(const TextData& richText); + + TextData get_rich_text_data_between(TextPosition p1, TextPosition p2); + void clear_text(); void set_string(const std::string& str);