// This file is part of Desktop App Toolkit, // a set of libraries for developing nice desktop applications. // // For license and copyright information please follow this link: // https://github.com/desktop-app/legal/blob/master/LEGAL // #pragma once #include "ui/style/style_core.h" #include "ui/emoji_config.h" #include namespace Ui { namespace Text { enum TextBlockType { TextBlockTNewline = 0x01, TextBlockTText = 0x02, TextBlockTEmoji = 0x03, TextBlockTSkip = 0x04, }; enum TextBlockFlags { TextBlockFBold = 0x01, TextBlockFItalic = 0x02, TextBlockFUnderline = 0x04, TextBlockFStrikeOut = 0x08, TextBlockFTilde = 0x10, // tilde fix in OpenSans TextBlockFSemibold = 0x20, TextBlockFCode = 0x40, TextBlockFPre = 0x80, }; class AbstractBlock { public: uint16 from() const { return _from; } int width() const { return _width.toInt(); } int rpadding() const { return _rpadding.toInt(); } QFixed f_width() const { return _width; } QFixed f_rpadding() const { return _rpadding; } // Should be virtual, but optimized throught type() call. QFixed f_rbearing() const; uint16 lnkIndex() const { return (_flags >> 12) & 0xFFFF; } void setLnkIndex(uint16 lnkIndex) { _flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); } TextBlockType type() const { return TextBlockType((_flags >> 8) & 0x0F); } int32 flags() const { return (_flags & 0xFF); } protected: AbstractBlock( const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : _from(from) , _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { } uint16 _from = 0; uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags QFixed _width = 0; // Right padding: spaces after the last content of the block (like a word). // This holds spaces after the end of the block, for example a text ending // with a space before a link has started. If text block has a leading spaces // (for example a text block after a link block) it is prepended with an empty // word that holds those spaces as a right padding. QFixed _rpadding = 0; }; class NewlineBlock final : public AbstractBlock { public: NewlineBlock( const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex) { _flags |= ((TextBlockTNewline & 0x0F) << 8); } Qt::LayoutDirection nextDirection() const { return _nextDir; } private: Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto; friend class String; friend class Parser; friend class Renderer; }; class TextWord final { public: TextWord() = default; TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) : _from(from) , _rbearing((rbearing.value() > 0x7FFF) ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) , _width(width) , _rpadding(rpadding) { } uint16 from() const { return _from; } QFixed f_rbearing() const { return QFixed::fromFixed(_rbearing); } QFixed f_width() const { return _width; } QFixed f_rpadding() const { return _rpadding; } void add_rpadding(QFixed padding) { _rpadding += padding; } private: uint16 _from = 0; int16 _rbearing = 0; QFixed _width, _rpadding; }; class TextBlock final : public AbstractBlock { public: TextBlock( const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex); private: QFixed real_f_rbearing() const { return _words.isEmpty() ? 0 : _words.back().f_rbearing(); } QVector _words; friend class String; friend class Parser; friend class Renderer; friend class BlockParser; friend class AbstractBlock; }; class EmojiBlock final : public AbstractBlock { public: EmojiBlock( const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji); private: EmojiPtr _emoji = nullptr; friend class String; friend class Parser; friend class Renderer; }; class SkipBlock final : public AbstractBlock { public: SkipBlock( const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : AbstractBlock(font, str, from, 1, 0, lnkIndex) , _height(h) { _flags |= ((TextBlockTSkip & 0x0F) << 8); _width = w; } int height() const { return _height; } private: int _height = 0; friend class String; friend class Parser; friend class Renderer; }; class Block final { public: Block() { Unexpected("Should not be called."); } Block(const Block &other) { switch (other->type()) { case TextBlockTNewline: emplace(other.unsafe()); break; case TextBlockTText: emplace(other.unsafe()); break; case TextBlockTEmoji: emplace(other.unsafe()); break; case TextBlockTSkip: emplace(other.unsafe()); break; default: Unexpected("Bad text block type in Block(const Block&)."); } } Block(Block &&other) { switch (other->type()) { case TextBlockTNewline: emplace(std::move(other.unsafe())); break; case TextBlockTText: emplace(std::move(other.unsafe())); break; case TextBlockTEmoji: emplace(std::move(other.unsafe())); break; case TextBlockTSkip: emplace(std::move(other.unsafe())); break; default: Unexpected("Bad text block type in Block(Block&&)."); } } Block &operator=(const Block &other) { if (&other == this) { return *this; } destroy(); switch (other->type()) { case TextBlockTNewline: emplace(other.unsafe()); break; case TextBlockTText: emplace(other.unsafe()); break; case TextBlockTEmoji: emplace(other.unsafe()); break; case TextBlockTSkip: emplace(other.unsafe()); break; default: Unexpected("Bad text block type in operator=(const Block&)."); } return *this; } Block &operator=(Block &&other) { if (&other == this) { return *this; } destroy(); switch (other->type()) { case TextBlockTNewline: emplace(std::move(other.unsafe())); break; case TextBlockTText: emplace(std::move(other.unsafe())); break; case TextBlockTEmoji: emplace(std::move(other.unsafe())); break; case TextBlockTSkip: emplace(std::move(other.unsafe())); break; default: Unexpected("Bad text block type in operator=(Block&&)."); } return *this; } ~Block() { destroy(); } [[nodiscard]] static Block Newline( const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) { return New(font, str, from, length, flags, lnkIndex); } [[nodiscard]] static Block Text( const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) { return New( font, str, minResizeWidth, from, length, flags, lnkIndex); } [[nodiscard]] static Block Emoji( const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji) { return New( font, str, from, length, flags, lnkIndex, emoji); } [[nodiscard]] static Block Skip( const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) { return New(font, str, from, w, h, lnkIndex); } template [[nodiscard]] FinalBlock &unsafe() { return *reinterpret_cast(&_data); } template [[nodiscard]] const FinalBlock &unsafe() const { return *reinterpret_cast(&_data); } [[nodiscard]] AbstractBlock *get() { return &unsafe(); } [[nodiscard]] const AbstractBlock *get() const { return &unsafe(); } [[nodiscard]] AbstractBlock *operator->() { return get(); } [[nodiscard]] const AbstractBlock *operator->() const { return get(); } [[nodiscard]] AbstractBlock &operator*() { return *get(); } [[nodiscard]] const AbstractBlock &operator*() const { return *get(); } private: struct Tag { }; explicit Block(const Tag &) { } template [[nodiscard]] static Block New(Args &&...args) { auto result = Block(Tag{}); result.emplace(std::forward(args)...); return result; } template void emplace(Args &&...args) { new (&_data) FinalType(std::forward(args)...); } void destroy() { switch (get()->type()) { case TextBlockTNewline: unsafe().~NewlineBlock(); break; case TextBlockTText: unsafe().~TextBlock(); break; case TextBlockTEmoji: unsafe().~EmojiBlock(); break; case TextBlockTSkip: unsafe().~SkipBlock(); break; default: Unexpected("Bad text block type in Block(Block&&)."); } } static_assert(sizeof(NewlineBlock) <= sizeof(TextBlock)); static_assert(alignof(NewlineBlock) <= alignof(void*)); static_assert(sizeof(EmojiBlock) <= sizeof(TextBlock)); static_assert(alignof(EmojiBlock) <= alignof(void*)); static_assert(sizeof(SkipBlock) <= sizeof(TextBlock)); static_assert(alignof(SkipBlock) <= alignof(void*)); std::aligned_storage_t _data; }; } // namespace Text } // namespace Ui