~aleteoryx/muditaos

07b243a08489c5da81666959d506fe64ff23a501 — Adam 5 years ago b7eb436
Egd-3408 Rich text  (#712)

* EGD-3408 gui::Text Rich text parser added with pugixml tree & tree walker

* EGD-3408 Fitted code in
M .gitmodules => .gitmodules +4 -1
@@ 46,4 46,7 @@
	branch = rt1051
[submodule "module-bluetooth/lib/btstack"]
	path = module-bluetooth/lib/btstack
	url = git@github.com:mudita/btstack.git
	url = ../btstack.git
[submodule "module-utils/pugixml"]
	path = module-utils/pugixml
	url = ../pugixml.git

M board/rt1051/ldscripts/memory.ld => board/rt1051/ldscripts/memory.ld +2 -2
@@ 14,6 14,6 @@ MEMORY
	SRAM_OC (rwx) : ORIGIN = 0x20200000, LENGTH = 0x10000 /* 64K bytes (alias RAM) */
	/*SRAM_ITC (rwx) : ORIGIN = 0x0, LENGTH = 0x0*/ /* 0K bytes (alias RAM2) */
	SRAM_DTC (rwx) : ORIGIN = 0x20000000, LENGTH = 0x70000 /* 448K bytes (alias RAM3) */
	BOARD_SDRAM_TEXT (rx) : ORIGIN = 0x80000000, LENGTH = 0x0600000 /* 6M bytes for application code */
	BOARD_SDRAM_HEAP (rwx) : ORIGIN = 0x80600000, LENGTH = 0x0A00000 /* 10M bytes for heap (alias RAM4) */
	BOARD_SDRAM_TEXT (rx) : ORIGIN = 0x80000000, LENGTH = 0x0620000 /* 5.something M bytes for application code */
	BOARD_SDRAM_HEAP (rwx) : ORIGIN = 0x80620000, LENGTH = 0x9e0000 /* 9.somethin M bytes for heap (alias RAM4) */
}

M changelog.md => changelog.md +2 -0
@@ 4,6 4,8 @@

### Added

* `[GUI]` Added rich text parsing for full text styling needs

### Other



M image/assets/lang/lang_en.json => image/assets/lang/lang_en.json +1 -1
@@ 189,7 189,7 @@
    "app_messages_new_message": "New Message",
    "app_messages_no_messages": "There are no messages yet.\nPress Left arrow to add new.",
    "app_messages_thread_delete_confirmation": "Do you really want to delete\nthis conversation?",
    "app_messages_message_delete_confirmation": "Do you really want to delete\nthis message?",
    "app_messages_message_delete_confirmation": "<text align='center'><p>Do you really want to <text weight='bold'>delete</text></p>this message?</text>",
    "app_messages_thread_no_result" : "There are no results",
    "app_messages_message": "Message",
    "app_messages_templates": "Templates",

M module-apps/application-settings/windows/UITestWindow.cpp => module-apps/application-settings/windows/UITestWindow.cpp +9 -5
@@ 3,6 3,7 @@
#include "Label.hpp"
#include "Margins.hpp"
#include "i18/i18.hpp"
#include "log/log.hpp"
#include "messages/AppMessage.hpp"
#include "service-appmgr/ApplicationManager.hpp"
#include <GridLayout.hpp>


@@ 24,13 25,16 @@ namespace gui
        bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::select));
        bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));
        setTitle("UI TEST");
        text = new gui::Text(this,
                             style::window::default_left_margin,
                             title->offset_h(),
                             style::window_width - 2 * style::window::default_left_margin,
                             300);
        text = new gui::Text(
            this, style::window::default_left_margin, title->offset_h(), style::window::default_body_width, 300);
        text->setEditMode(EditMode::EDIT);
        text->setFont(style::window::font::medium);
        LOG_DEBUG(
            "----------------------------------------------------------------------------------------------------");
        text->addRichText("<p><text font='gt_pressura' color='12' size='30'>This</text><br>Text<text size='20' "
                          "weight='bold'> is rich </text><text color='3'>example</text></br></p>");
        LOG_DEBUG(
            "----------------------------------------------------------------------------------------------------");
        text->addText(
            TextBlock("!#$%&'()*+,-.0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"
                      "ĄąĆćĘꣳŃńÓóŚśŹźŻżÀàÂâÇçÉéÈèÊêËëÎîÏïÔôÙûÛùÜüŸÿÄäÖößÁáÍíÚúÑñ¿¡",

M module-apps/windows/Dialog.cpp => module-apps/windows/Dialog.cpp +2 -2
@@ 55,7 55,7 @@ Dialog::Dialog(app::Application *app, const std::string &name, const Dialog::Met
    icon = new Image(this, style::image::x, style::image::y, meta.icon);

    text = new Text(this, style::text::x, style::text::y, style::text::w, style::text::h);
    text->setText(meta.text);
    text->setRichText(meta.text);
    text->setTextType(TextType::MULTI_LINE);
    text->setEditMode(EditMode::BROWSE);
    text->setEdges(RectangleEdgeFlags::GUI_RECT_EDGE_NO_EDGES);


@@ 67,7 67,7 @@ void Dialog::update(const Meta &meta)
{
    this->meta = meta;
    setTitle(meta.title);
    text->setText(meta.text);
    text->setRichText(meta.text);
    icon->set(meta.icon);
    // meta.action not used
}

M module-bsp/board/rt1051/common/startup_mimxrt1052.cpp => module-bsp/board/rt1051/common/startup_mimxrt1052.cpp +0 -2
@@ 1049,8 1049,6 @@ extern "C"
            LOG_FATAL(" - DTCM: Asynchronous fault on DTCM interface.");
        if (abfsr.ahbp)
            LOG_FATAL(" - AHBP: Asynchronous fault on AHBP interface");
        if (abfsr.axim)
            LOG_FATAL(" - AXIM: Asynchronous fault on AXIM interface, AXIMTYPE: 0x%0" PRIX32, abfsr.aximtype);
        if (abfsr.eppb)
            LOG_FATAL(" - EPPB: Asynchronous fault on EPPB interface");
#endif // (DEBUG_DETAILED_HARD_FAULT_INFO == 1)

M module-gui/gui/core/Font.cpp => module-gui/gui/core/Font.cpp +41 -0
@@ 1,9 1,15 @@
#include "Font.hpp"
#include "FontManager.hpp" // for FontManager
#include "RawFont.hpp"
#include "log/log.hpp"
#include <algorithm>
#include <sstream>

namespace gui
{

    auto toWeight(const std::string &val) -> Font::Weight;

    Font::Font(std::string name, unsigned int size, Weight weight)
    {
        setFont(name, size, weight);


@@ 12,6 18,27 @@ namespace gui
    Font::Font(unsigned int size, Weight weight) : Font(font_default_type, size, weight)
    {}

    Font::Font(RawFont *rawfont)
    {
        if (rawfont == nullptr) {
            font = FontManager::getInstance().getFont(""); // get default
        }

        auto name  = rawfont->getName();
        auto pos   = name.npos;
        auto parse = [&]() {
            pos      = name.rfind('_');
            auto val = name.substr(pos + 1, name.length());
            name.erase(pos);
            return val;
        };

        unsigned int size = std::stoi(parse());
        Weight weight     = toWeight(parse());

        setFont(name, size, weight);
    }

    void Font::setFont(std::string new_name, unsigned int new_size, Weight new_weight)
    {
        bool update = false;


@@ 49,4 76,18 @@ namespace gui
    {
        return font;
    }

    auto toWeight(const std::string &val) -> Font::Weight
    {
        if (val == c_str(Font::Weight::Regular)) {
            return Font::Weight::Regular;
        }
        else if (val == c_str(Font::Weight::Bold)) {
            return Font::Weight::Bold;
        }
        else if (val == c_str(Font::Weight::Light)) {
            return Font::Weight::Light;
        }
        return Font::Weight::Regular;
    }
}; // namespace gui

M module-gui/gui/core/Font.hpp => module-gui/gui/core/Font.hpp +13 -0
@@ 29,12 29,25 @@ namespace gui
      public:
        Font(std::string name, unsigned int size, Weight weight = Weight::Regular);
        Font(unsigned int size, Weight weight = Weight::Regular);
        Font(RawFont *font);
        void setFont(std::string name, unsigned int size, Weight weight = Weight::Regular);
        /// just for gt_pressura - we have it as default
        void setFont(unsigned int size, Weight weight = Weight::Regular);
        void setSize(unsigned int size);
        void setWeight(Weight weight);
        auto raw() -> RawFont *;
        [[nodiscard]] auto getSize() const
        {
            return size;
        }
        [[nodiscard]] auto getWeight() const
        {
            return weight;
        }
        [[nodiscard]] auto getName() const
        {
            return name;
        }
    };
}; // namespace gui


M module-gui/gui/widgets/CMakeLists.txt => module-gui/gui/widgets/CMakeLists.txt +2 -0
@@ 34,6 34,8 @@ target_sources( ${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/Style.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/InputMode.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/GridLayout.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/RichTextParser.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/TextFormat.cpp"
    PUBLIC
        "${CMAKE_CURRENT_LIST_DIR}/Alignment.hpp"
        "${CMAKE_CURRENT_LIST_DIR}/BottomBar.hpp"

A module-gui/gui/widgets/RichTextParser.cpp => module-gui/gui/widgets/RichTextParser.cpp +381 -0
@@ 0,0 1,381 @@
#include "RichTextParser.hpp"
#include "Color.hpp"
#include "Font.hpp"
#include "TextBlock.hpp"
#include "TextDocument.hpp"
#include <sstream>
#include <string>
#include <log/log.hpp>
#include "TextFormat.hpp"

#include <module-utils/pugixml/src/pugixml.hpp>
#include <utility>

#ifndef DEBUG_RTP
const std::string node_types[] = {"null", "document", "element", "pcdata ", "cdata", "comment", "pi", "declaration"};
#define log_parser(...) LOG_DEBUG(__VA_ARGS__)
#endif

namespace text
{
    class Attribute
    {
        std::string name;

      protected:
        explicit Attribute(std::string name) : name(std::move(name)){};

      public:
        Attribute() = delete;
        /// return true on success, othervise set fallback value and return false
        /// @note could run: preVisitHook -> visit -> postVisit hook
        virtual auto visit(gui::TextFormat &fmt, std::string value) -> bool = 0;
        [[nodiscard]] auto getName() const -> const std::string &
        {
            return name;
        }

        [[nodiscard]] auto is(const std::string &name) const
        {
            return getName() == name;
        }
    };

    class AttributeAlign : public Attribute
    {
        const std::string center = gui::text::center;
        const std::string left   = gui::text::left;
        const std::string right  = gui::text::right;

      public:
        AttributeAlign() : Attribute(gui::text::align)
        {}

        auto visit(gui::TextFormat &fmt, std::string value) -> bool final
        {
            log_parser("append: Attr %s", getName().c_str());
            using namespace gui;
            auto alignment = Alignment(Alignment::Horizontal::Left, Alignment::Vertical::Bottom);
            if (value == center) {
                alignment.horizontal = Alignment::Horizontal::Center;
                fmt.setAlignment(alignment);
                return true;
            }
            else if (value == right) {
                alignment.horizontal = Alignment::Horizontal::Right;
                fmt.setAlignment(alignment);
                return true;
            }
            else if (value == left) {
                alignment.horizontal = Alignment::Horizontal::Left;
                fmt.setAlignment(alignment);
                return true;
            }

            fmt.setAlignment(alignment);
            return false;
        }
    };

    class AttributeColor : public Attribute
    {

        const int max = 15;

      public:
        AttributeColor() : Attribute(gui::text::color)
        {}

        auto visit(gui::TextFormat &fmt, std::string value) -> bool final
        {
            log_parser("append: Attr %s", getName().c_str());
            using namespace gui;
            if (value.empty()) {
                fmt.setColor(ColorFullBlack);
                return false;
            }
            try {
                int val = std::stoi(value);
                if ((val == 0 && value.c_str()[0] != '0') || val > max) {
                    fmt.setColor(ColorFullBlack);
                    return false;
                }
                fmt.setColor(Color{static_cast<uint8_t>(val), 0});
                return true;
            }
            catch (const std::exception &exception) {
                LOG_ERROR("%s", exception.what());
                return false;
            }
            return false;
        }
    };

    class AttributeFont : public Attribute
    {
      public:
        AttributeFont() : Attribute(gui::text::font)
        {}

        auto visit(gui::TextFormat &fmt, std::string value) -> bool final
        {
            log_parser("append: Attr %s", getName().c_str());
            using namespace gui;
            // here it's tricky -> we need to get font copy from fmt -> change it -> set it
            auto font = gui::Font(fmt.getFont());
            font.setFont(value, font.getSize());
            fmt.setFont(font.raw());
            return true;
        }
    };

    class AttributeSize : public Attribute
    {
      public:
        AttributeSize() : Attribute(gui::text::size)
        {}

        auto visit(gui::TextFormat &fmt, std::string value) -> bool final
        {
            log_parser("append: Attr %s", getName().c_str());
            if (value.empty()) {
                return false;
            }
            try {
                int val = std::stoi(value);

                if (val == 0 && value.front() != '0') {
                    return false;
                }

                auto font = gui::Font(fmt.getFont());
                font.setSize(val);
                fmt.setFont(font.raw());
                return true;
            }
            catch (const std::exception &exception) {
                LOG_ERROR("%s", exception.what());
                return false;
            }
        }
    };

    class AttributeWeight : public Attribute
    {
        const std::string regular = "regular";
        const std::string bold    = "bold";
        const std::string light   = "light";

      public:
        AttributeWeight() : Attribute("weight")
        {}

        auto visit(gui::TextFormat &fmt, std::string value) -> bool final
        {
            log_parser("append: Attr %s", getName().c_str());
            using namespace gui;
            auto font = gui::Font(fmt.getFont());
            if (value == regular) {
                font.setWeight(Font::Weight::Regular);
            }
            else if (value == light) {
                font.setWeight(Font::Weight::Light);
            }
            else if (value == bold) {
                font.setWeight(Font::Weight::Bold);
            }
            else {
                font.setWeight(Font::Weight::Regular);
                fmt.setFont(font.raw());
                return false;
            }

            fmt.setFont(font.raw());
            return true;
        }
    };

    class NodeDecor
    {
        std::list<std::unique_ptr<Attribute>> attrs;
        NodeDecor()
        {
            attrs.emplace_back(std::make_unique<AttributeAlign>());
            attrs.emplace_back(std::make_unique<AttributeColor>());
            attrs.emplace_back(std::make_unique<AttributeFont>());
            attrs.emplace_back(std::make_unique<AttributeSize>());
            attrs.emplace_back(std::make_unique<AttributeWeight>());
        }

      public:
        // for each met style -> put it on stack to be used
        // too deep -> can be optimized
        auto stack_visit(gui::TextFormat &format, const std::string &name, const std::string &value) -> bool
        {
            for (auto &attr : attrs) {
                if (attr->is(name)) {
                    if (!attr->visit(format, value)) {
                        LOG_ERROR("Attribute %s parsing error, default set", name.c_str());
                    }
                    return true;
                }
            }
            LOG_ERROR("Attr: %s not found", name.c_str());
            return false;
        }

        static auto get() -> NodeDecor &
        {
            static NodeDecor *ptr = nullptr;
            if (ptr == nullptr) {
                ptr = new NodeDecor();
            }
            return *ptr;
        }
    };
}; // namespace text

struct walker : pugi::xml_tree_walker
{
  protected:
    std::list<gui::TextBlock> blocks;
    std::list<gui::TextFormat> style_stack;
    bool add_newline = false;

  public:
    walker(gui::TextFormat entry_style)
    {
        style_stack.push_back(entry_style);
    }

    enum class Action
    {
        Enter, /// enter/visit node
        Exit   /// exit/leave  node
    };

    auto log_node(pugi::xml_node &node, Action dir)
    {
        log_parser(
            "%s: %s format: %s",
            dir == Action::Enter ? "enter" : "leave",
            [&]() {
                std::stringstream ss;
                ss << node_types[node.type()] << ": name='" << node.name() << "', value='" << node.value() << "'";
                return ss.str();
            }()
                .c_str(),
            style_stack.back().str().c_str());
    }

    auto is_newline_node(pugi::xml_node &node) const
    {
        return std::string(node.name()) == gui::text::node_br || std::string(node.name()) == gui::text::node_p;
    }

    auto push_text_node(pugi::xml_node &node)
    {
        auto local_style = style_stack.back();
        for (auto &attribute : node.attributes()) {
            log_parser("attribute name: %s value: %s", attribute.name(), attribute.value());
            auto &decor = text::NodeDecor::get();
            decor.stack_visit(local_style, attribute.name(), attribute.value());
        }
        style_stack.push_back(local_style);
        log_parser("Attr loaded: %s", style_stack.back().str().c_str());
    }

    auto push_newline_node(pugi::xml_node &)
    {
        if (blocks.size() != 0u) {
            blocks.back().setEnd(gui::TextBlock::End::Newline);
        }
    }

    auto push_data_node(pugi::xml_node &node)
    {
        blocks.emplace_back(node.value(), std::make_unique<gui::TextFormat>(style_stack.back()));
    }

    auto for_each(pugi::xml_node &node) -> bool final
    {
        log_node(node, Action::Enter);
        if (node.type() == pugi::xml_node_type::node_element) {
            if (std::string(node.name()) == gui::text::node_text) {
                push_text_node(node);
                return true;
            }
            if (is_newline_node(node)) {
                push_newline_node(node);
                return true;
            }
        }

        std::string to_show = node.value();
        if (node.type() == pugi::xml_node_type::node_pcdata && !to_show.empty()) {
            push_data_node(node);
        }
        return true;
    }

    auto pop_text_node(pugi::xml_node &node)
    {
        style_stack.pop_back();
    }

    auto pop_newline_node(pugi::xml_node &node)
    {
        if (blocks.size() != 0u) {
            blocks.back().setEnd(gui::TextBlock::End::Newline);
        }
    }

    auto on_leave(pugi::xml_node &node) -> bool final
    {
        log_node(node, Action::Exit);

        if (node.type() == pugi::xml_node_type::node_element) {
            if (std::string(node.name()) == gui::text::node_text) {
                pop_text_node(node);
                return true;
            }
            if (is_newline_node(node)) {
                pop_newline_node(node);
                return true;
            }
        }
        return true;
    }

    auto end(pugi::xml_node &node) -> bool final
    {
        return true;
    }

    auto souvenirs() -> std::list<gui::TextBlock> &
    {
        return blocks;
    }
};

namespace gui::text
{

    auto RichTextParser::parse(const UTF8 &text, TextFormat *base_style) -> std::unique_ptr<TextDocument>
    {
        LOG_DEBUG("parsing: %s", text.c_str());
        if (text.empty() || base_style == nullptr) {
            LOG_ERROR("no: %s", text.empty() ? "text" : "base style");
            return std::unique_ptr<TextDocument>();
        }

        pugi::xml_document doc;
        walker walker(*base_style);

        doc.load_string(text.c_str());
        doc.traverse(walker);

        return std::make_unique<TextDocument>(walker.souvenirs());
    }

}; // namespace gui::text

A module-gui/gui/widgets/RichTextParser.hpp => module-gui/gui/widgets/RichTextParser.hpp +48 -0
@@ 0,0 1,48 @@
#pragma once

#include <memory>
#include <utf8/UTF8.hpp>

namespace gui
{
    class TextDocument;
    class TextFormat;
} // namespace gui

namespace gui::text
{
    inline const auto align     = "align";
    inline const auto center    = "center";
    inline const auto right     = "right";
    inline const auto left      = "left";
    inline const auto color     = "color";
    inline const auto font      = "font";
    inline const auto size      = "size";
    inline const auto weight    = "weight";
    inline const auto regular   = "regular";
    inline const auto bold      = "bold";
    inline const auto light     = "light";
    inline const auto node_text = "text";
    inline const auto node_br   = "br";
    inline const auto node_p    = "p";

    /// Rich text parser utility
    /// supported specifiers
    /// * gui::text::node_text `<text> </text>` attributes:
    ///        * gui::text::align - alignment in Horizontal axis ( gui::text::center, gui::text::left, gui::text::right
    ///        )
    ///        * gui::text::color - Text color from 0 to 15 ( eink black color depth )
    ///        * gui::text::font  - Font to use ( now we have gt_pressura and some dejavu glyphs)
    ///        * gui::text::size  - size of font to use
    ///        * gui::text::weight - weight of font to use, one of: gui::text::bold, gui::text::regular,
    ///        gui::text::light
    /// please mind that selected font must exist on system, othervise closest relative will be selected
    /// * gui::text::node_p `<p> </p>` and gui::text::node_br `<br> </br>` now working identical - marking start and end
    /// with newline
    /// @return empty document on error
    class RichTextParser
    {
      public:
        auto parse(const UTF8 &text, TextFormat *base_style) -> std::unique_ptr<TextDocument>;
    };
} // namespace gui::text

M module-gui/gui/widgets/Text.cpp => module-gui/gui/widgets/Text.cpp +30 -9
@@ 19,6 19,7 @@
#include <cassert>
#include <FontManager.hpp>
#include <RawFont.hpp>
#include <RichTextParser.hpp>

#if DEBUG_GUI_TEXT == 1
#define debug_text(...) LOG_DEBUG(__VA_ARGS__)


@@ 46,13 47,13 @@ namespace gui
               const UTF8 &text,
               ExpandMode expandMode,
               TextType textType)
        : Rect(parent, x, y, w, h), lines(this), expandMode{expandMode}, textType{textType}
        : Rect(parent, x, y, w, h), lines(this), expandMode{expandMode}, textType{textType},
          format(FontManager::getInstance().getFont(style::window::font::small))
    {
        alignment = style::text::defaultTextAlignment;

        setPenWidth(style::window::default_border_no_focus_w);
        setPenFocusWidth(style::window::default_border_focus_w);
        font = FontManager::getInstance().getFont(style::window::font::small);
        buildDocument(text);

        setBorderColor(gui::ColorFullBlack);


@@ 90,7 91,8 @@ namespace gui
    void Text::setText(const UTF8 &text)
    {
        debug_text("setText: %s", text.c_str());
        setText(std::make_unique<TextDocument>(textToTextBlocks(text, font, TextBlock::End::None)));
        /// TODO here should be format passed
        setText(std::make_unique<TextDocument>(textToTextBlocks(text, format.getFont(), TextBlock::End::None)));
    }

    void Text::setText(std::unique_ptr<TextDocument> &&document)


@@ 113,6 115,25 @@ namespace gui
        drawLines();
    }

    void Text::setRichText(const UTF8 &text)
    {
        setText("");
        addRichText(text);
    }

    void Text::addRichText(const UTF8 &text)
    {
        auto tmp_document = text::RichTextParser().parse(text, &format);
        if (tmp_document->isEmpty()) {
            LOG_ERROR("Nothing to parse/parser error in rich text: %s", text.c_str());
            addText(text); // fallback
        }
        for (auto block : tmp_document->getBlockCursor(0)) {
            *cursor << block;
        }
        drawLines();
    }

    void Text::clear()
    {
        buildDocument("");


@@ 142,13 163,13 @@ namespace gui
    void Text::setFont(const UTF8 &fontName)
    {
        RawFont *newFont = FontManager::getInstance().getFont(fontName);
        font             = newFont;
        format.setFont(newFont);
        buildCursor();
    }

    void Text::setFont(RawFont *fontName)
    void Text::setFont(RawFont *font)
    {
        font = fontName;
        format.setFont(font);
        buildCursor();
    }



@@ 367,8 388,8 @@ namespace gui
            uint16_t w_used = lines.maxWidth();
            if (lines.size() == 0) {
                debug_text("No lines to show, try to at least fit in cursor");
                if (font != nullptr) {
                    h_used += font->info.line_height;
                if (format.getFont() != nullptr) {
                    h_used += format.getFont()->info.line_height;
                    w_used += TextCursor::default_width;
                    debug_text("empty line height: %d", h_used);
                }


@@ 413,7 434,7 @@ namespace gui

    void Text::buildDocument(const UTF8 &text)
    {
        buildDocument(std::make_unique<TextDocument>(textToTextBlocks(text, font, TextBlock::End::None)));
        buildDocument(std::make_unique<TextDocument>(textToTextBlocks(text, format.getFont(), TextBlock::End::None)));
    }

    void Text::buildDocument(std::unique_ptr<TextDocument> &&document_moved)

M module-gui/gui/widgets/Text.hpp => module-gui/gui/widgets/Text.hpp +12 -2
@@ 138,9 138,8 @@ namespace gui
      protected:
        TextType textType = TextType::MULTI_LINE;
        /// points to default text font to use
        RawFont *font = nullptr;
        Color textColor;
        bool underline = false;
        TextFormat format;

        bool moveCursor(const NavigationDirection &direction, std::unique_ptr<TextDocument> &document);
        bool handleNavigation(const InputEvent &inputEvent);


@@ 173,6 172,13 @@ namespace gui

        void addText(const UTF8 &text);
        void addText(TextBlock text);
        /// @defgroup richtext can be virtualized by parametrized RichTextParser virtual api ( as second param )
        /// @{
        /// set rich text with default RichTextParser - please see RichTextParser documentation on how to use format
        void setRichText(const UTF8 &text);
        /// add rich text with default RichTextParser - please see RichTextParser documentation on how to use format
        void addRichText(const UTF8 &text);
        /// @}
        virtual void clear();
        bool isEmpty();
        virtual UTF8 getText();


@@ 196,6 202,10 @@ namespace gui
        void setRadius(int value) override;
        void setAlignment(const Alignment &value) override;
        void setPadding(const Padding &value) override;
        [[nodiscard]] auto getTextFormat() const noexcept -> const TextFormat &
        {
            return format;
        }

      private:
        gui::KeyInputMappedTranslation translator;

M module-gui/gui/widgets/TextBlock.cpp => module-gui/gui/widgets/TextBlock.cpp +1 -1
@@ 11,7 11,7 @@ namespace gui
    TextBlock::TextBlock(const UTF8 text, std::unique_ptr<TextFormat> format) : format(std::move(format)), text{text}
    {}

    TextBlock::TextBlock(const UTF8 text, RawFont *font, TextBlock::End eol)
    TextBlock::TextBlock(const UTF8 text, const RawFont *font, TextBlock::End eol)
        : TextBlock(text, std::make_unique<TextFormat>(font))
    {
        if (getEnd() != End::Newline && eol == End::Newline) {

M module-gui/gui/widgets/TextBlock.hpp => module-gui/gui/widgets/TextBlock.hpp +1 -1
@@ 29,7 29,7 @@ namespace gui
        End end = End::Newline;

      public:
        TextBlock(const UTF8 text, RawFont *font, End eol = End::None);
        TextBlock(const UTF8 text, const RawFont *font, End eol = End::None);
        TextBlock(const UTF8 text, std::unique_ptr<TextFormat> format);
        TextBlock(const TextBlock &);


M module-gui/gui/widgets/TextBlockCursor.cpp => module-gui/gui/widgets/TextBlockCursor.cpp +10 -0
@@ 235,4 235,14 @@ namespace gui
    {
        return *currentBlock();
    }

    auto BlockCursor::begin() -> std::list<TextBlock>::iterator
    {
        return document == nullptr ? document->blocks.end() : document->blocks.begin();
    }

    auto BlockCursor::end() -> std::list<TextBlock>::iterator
    {
        return document->blocks.end();
    }
} // namespace gui

M module-gui/gui/widgets/TextBlockCursor.hpp => module-gui/gui/widgets/TextBlockCursor.hpp +7 -0
@@ 3,6 3,7 @@
#include "TextConstants.hpp"
#include <cstdio>
#include <stdint.h>
#include <list>

namespace gui
{


@@ 77,5 78,11 @@ namespace gui
        // return if handled ( this is not i.e. at begin/end)
        bool removeChar();
        const TextBlock &operator*();

        /// iterable
        /// {
        auto begin() -> std::list<TextBlock>::iterator;
        auto end() -> std::list<TextBlock>::iterator;
        /// }
    };
} // namespace gui

M module-gui/gui/widgets/TextCursor.cpp => module-gui/gui/widgets/TextCursor.cpp +6 -3
@@ 17,7 17,9 @@ namespace gui

    TextCursor::TextCursor(gui::Text *parent, gui::TextDocument *document)
        : Rect(parent, 0, 0, default_width, 1),
          BlockCursor(document, text::npos, text::npos, parent != nullptr ? parent->font : nullptr), text(parent)
          BlockCursor(
              document, text::npos, text::npos, parent != nullptr ? parent->getTextFormat().getFont() : nullptr),
          text(parent)
    {
        setFilled(true);
        setVisible(false);


@@ 123,8 125,9 @@ namespace gui
            setArea({x, y, w, h});
            return;
        }
        if (document->isEmpty() && text->font != nullptr) {
            h += text->font->info.line_height;
        auto default_font = text->format.getFont();
        if (document->isEmpty() && default_font != nullptr) {
            h += default_font->info.line_height;
            x = getAxisAlignmentValue(Axis::X, w);
            y = getAxisAlignmentValue(Axis::Y, h);
        }

M module-gui/gui/widgets/TextDocument.cpp => module-gui/gui/widgets/TextDocument.cpp +2 -0
@@ 70,6 70,8 @@ namespace gui
            loop_position += el.length();
            ++block_no;
        }
        // TODO ok... here we might want to return BlockCursor(this) <- but returning text::npos / empty/none block if
        // we wanted to return anything
        return BlockCursor();
    }


M module-gui/gui/widgets/TextFixedSize.cpp => module-gui/gui/widgets/TextFixedSize.cpp +1 -1
@@ 47,7 47,7 @@ namespace gui
        auto cursor          = 0;

        unsigned int currentLine = 0;
        unsigned int lineHeight  = font->info.line_height + underlinePadding;
        unsigned int lineHeight  = format.getFont()->info.line_height + underlinePadding;

        auto line_x_position = padding.left;
        do {

A module-gui/gui/widgets/TextFormat.cpp => module-gui/gui/widgets/TextFormat.cpp +13 -0
@@ 0,0 1,13 @@
#include "TextFormat.hpp"
#include "RawFont.hpp"

namespace gui
{
    auto TextFormat::str() const -> std::string
    {
        std::string ret = font->getName();
        ret += " color: ";
        ret += std::to_string(color.intensity);
        return ret;
    }
} // namespace gui

M module-gui/gui/widgets/TextFormat.hpp => module-gui/gui/widgets/TextFormat.hpp +12 -2
@@ 1,7 1,9 @@
#pragma once

#include "Alignment.hpp"
#include <Color.hpp>
#include <functional>
#include <string>

namespace gui
{


@@ 10,8 12,9 @@ namespace gui
    class TextFormat
    {
      private:
        RawFont *font = nullptr;
        mutable RawFont *font = nullptr;
        Color color = ColorFullBlack;
        Alignment alignment   = Alignment(Alignment::Horizontal::Left);

        static constexpr auto setter = [](auto &local, auto &next) {
            if (local != next) {


@@ 20,7 23,7 @@ namespace gui
        };

      public:
        TextFormat(RawFont *font, Color color = {}) : font(font), color(color){};
        TextFormat(const RawFont *font, Color color = {}) : font(const_cast<RawFont *>(font)), color(color){};
        TextFormat(const TextFormat &) = default;

        [[nodiscard]] auto getFont() const


@@ 44,5 47,12 @@ namespace gui
        {
            setter(this->color, color);
        }

        void setAlignment(Alignment alignment)
        {
            setter(this->alignment, alignment);
        }

        auto str() const -> std::string;
    };
}; // namespace gui

M module-gui/gui/widgets/TextParse.cpp => module-gui/gui/widgets/TextParse.cpp +1 -1
@@ 3,7 3,7 @@

namespace gui
{
    auto textToTextBlocks(const UTF8 &text, RawFont *font, TextBlock::End end) -> std::list<TextBlock>
    auto textToTextBlocks(const UTF8 &text, const RawFont *font, TextBlock::End end) -> std::list<TextBlock>
    {
        std::list<TextBlock> blocks;
        std::stringstream ss(text.c_str());

M module-gui/gui/widgets/TextParse.hpp => module-gui/gui/widgets/TextParse.hpp +1 -1
@@ 6,7 6,7 @@
namespace gui
{
    /// @note with nullptr font - nullptr will be assigned as font
    auto textToTextBlocks(const UTF8 &text, RawFont *font, TextBlock::End end = TextBlock::End::Newline)
    auto textToTextBlocks(const UTF8 &text, const RawFont *font, TextBlock::End end = TextBlock::End::Newline)
        -> std::list<TextBlock>;
    auto textToTextBlocks(const UTF8 &text, TextFormat format) -> std::list<TextBlock>;
} // namespace gui

M module-gui/test/test-catch-text/test-gui-Text.cpp => module-gui/test/test-catch-text/test-gui-Text.cpp +0 -18
@@ 97,24 97,6 @@ TEST_CASE("Text drawLines")
        text.drawLines();
        REQUIRE(text.linesSize() == lines_count);
    }

    // Test deactivated
    //    SECTION("get 2 lines out of N")
    //    {
    //        unsigned int lines_count         = 5;
    //        const unsigned int lines_to_show = 2;
    //        REQUIRE(lines_to_show < lines_count);
    //        auto testline    = mockup::multiLineString(lines_count);
    //        auto font        = fontmanager.getFont(0);
    //        auto line_height = font->info.line_height;
    //        auto text        = TestText();
    //        text.setMaximumSize(6, line_height * lines_to_show);
    //        text.setText(std::make_unique<TextDocument>(
    //            textToTextBlocks(testline, fontmanager.getFont(0), TextBlock::End::Newline)));
    //
    //        text.drawLines();
    //        REQUIRE(text.linesSize() == lines_to_show);
    //    }
}

TEST_CASE("Text buildDrawList")

M module-utils/CMakeLists.txt => module-utils/CMakeLists.txt +3 -1
@@ 44,7 44,9 @@ include(third-party/re2.cmake)
include(third-party/protobuf-lite.cmake)
include(third-party/libphonenumber.cmake)

target_link_libraries(${PROJECT_NAME} PUBLIC module-os module-vfs)
add_subdirectory(pugixml)

target_link_libraries(${PROJECT_NAME} PUBLIC module-os module-vfs pugixml)

# Board specific compilation definitions,options,include directories and features
target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_CONFIG_DEFINITIONS})

A module-utils/pugixml => module-utils/pugixml +1 -0
@@ 0,0 1,1 @@
Subproject commit 3594fd01041663c4a2f3f149e47d71835863fb02

M module-utils/utf8/UTF8.hpp => module-utils/utf8/UTF8.hpp +2 -0
@@ 98,10 98,12 @@ class UTF8
    {
        return strLength;
    }

    bool empty() const noexcept
    {
        return strLength == 0U;
    }

    uint32_t used() const
    {
        return sizeUsed;

M module-vfs/board/cross/freeRTOS_FAT/portable/ff_eMMC_user_disk.cpp => module-vfs/board/cross/freeRTOS_FAT/portable/ff_eMMC_user_disk.cpp +0 -1
@@ 240,7 240,6 @@ BaseType_t xReturn = pdPASS;

		/* It is better not to use the 64-bit format such as %Lu because it
		might not be implemented. */
        LOG_PRINTF("Partition Nr   %lu\r\n", pxDisk->xStatus.bPartitionNumber);
        LOG_PRINTF("Type           %8u (%s)\r\n", pxIOManager->xPartition.ucType, pcTypeName);
        LOG_PRINTF( "VolLabel       '%8s' \r\n", pxIOManager->xPartition.pcVolumeLabel );
		LOG_PRINTF( "TotalSectors   %8lu\r\n", pxIOManager->xPartition.ulTotalSectors );