1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #pragma once
8 
9 #include "ui/text/text_entity.h"
10 #include "ui/text/text_block.h"
11 #include "ui/painter.h"
12 #include "ui/click_handler.h"
13 #include "base/flags.h"
14 
15 #include <private/qfixed_p.h>
16 #include <any>
17 
18 static const QChar TextCommand(0x0010);
19 enum TextCommands {
20 	TextCommandBold        = 0x01,
21 	TextCommandNoBold      = 0x02,
22 	TextCommandItalic      = 0x03,
23 	TextCommandNoItalic    = 0x04,
24 	TextCommandUnderline   = 0x05,
25 	TextCommandNoUnderline = 0x06,
26 	TextCommandStrikeOut   = 0x07,
27 	TextCommandNoStrikeOut = 0x08,
28 	TextCommandSemibold    = 0x09,
29 	TextCommandNoSemibold  = 0x0A,
30 	TextCommandLinkIndex   = 0x0B, // 0 - NoLink
31 	TextCommandLinkText    = 0x0C,
32 	TextCommandSkipBlock   = 0x0D,
33 
34 	TextCommandLangTag     = 0x20,
35 };
36 
37 struct TextParseOptions {
38 	int32 flags;
39 	int32 maxw;
40 	int32 maxh;
41 	Qt::LayoutDirection dir;
42 };
43 extern const TextParseOptions _defaultOptions, _textPlainOptions;
44 
45 enum class TextSelectType {
46 	Letters    = 0x01,
47 	Words      = 0x02,
48 	Paragraphs = 0x03,
49 };
50 
51 struct TextSelection {
52 	constexpr TextSelection() = default;
TextSelectionTextSelection53 	constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) {
54 	}
emptyTextSelection55 	constexpr bool empty() const {
56 		return from == to;
57 	}
58 	uint16 from = 0;
59 	uint16 to = 0;
60 };
61 
62 inline bool operator==(TextSelection a, TextSelection b) {
63 	return a.from == b.from && a.to == b.to;
64 }
65 
66 inline bool operator!=(TextSelection a, TextSelection b) {
67 	return !(a == b);
68 }
69 
70 static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
71 
72 namespace Ui {
73 namespace Text {
74 
75 struct IsolatedEmoji;
76 
77 struct StateRequest {
78 	enum class Flag {
79 		BreakEverywhere = (1 << 0),
80 		LookupSymbol = (1 << 1),
81 		LookupLink = (1 << 2),
82 		LookupCustomTooltip = (1 << 3),
83 	};
84 	using Flags = base::flags<Flag>;
is_flag_typeStateRequest85 	friend inline constexpr auto is_flag_type(Flag) { return true; };
86 
StateRequestStateRequest87 	StateRequest() {
88 	}
89 
90 	style::align align = style::al_left;
91 	Flags flags = Flag::LookupLink;
92 };
93 
94 struct StateResult {
95 	ClickHandlerPtr link;
96 	bool uponSymbol = false;
97 	bool afterSymbol = false;
98 	uint16 symbol = 0;
99 };
100 
101 struct StateRequestElided : public StateRequest {
StateRequestElidedStateRequestElided102 	StateRequestElided() {
103 	}
StateRequestElidedStateRequestElided104 	StateRequestElided(const StateRequest &other) : StateRequest(other) {
105 	}
106 	int lines = 1;
107 	int removeFromEnd = 0;
108 };
109 
110 class String {
111 public:
112 	String(int32 minResizeWidth = QFIXED_MAX);
113 	String(
114 		const style::TextStyle &st,
115 		const QString &text,
116 		const TextParseOptions &options = _defaultOptions,
117 		int32 minResizeWidth = QFIXED_MAX,
118 		bool richText = false);
119 	String(const String &other) = default;
120 	String(String &&other) = default;
121 	String &operator=(const String &other) = default;
122 	String &operator=(String &&other) = default;
123 	~String() = default;
124 
125 	int countWidth(int width, bool breakEverywhere = false) const;
126 	int countHeight(int width, bool breakEverywhere = false) const;
127 	void countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere = false) const;
128 	void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions);
129 	void setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options = _defaultOptions);
130 	void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions, const std::any &context = {});
131 
132 	void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
133 	bool hasLinks() const;
134 
135 	bool hasSkipBlock() const;
136 	bool updateSkipBlock(int width, int height);
137 	bool removeSkipBlock();
138 
maxWidth()139 	int maxWidth() const {
140 		return _maxWidth.ceil().toInt();
141 	}
minHeight()142 	int minHeight() const {
143 		return _minHeight;
144 	}
145 	int countMaxMonospaceWidth() const;
146 
147 	void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const;
148 	void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
149 	void drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const;
150 	void drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
151 	void drawRight(Painter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const;
152 	void drawRightElided(Painter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
153 
154 	StateResult getState(QPoint point, int width, StateRequest request = StateRequest()) const;
155 	StateResult getStateLeft(QPoint point, int width, int outerw, StateRequest request = StateRequest()) const;
156 	StateResult getStateElided(QPoint point, int width, StateRequestElided request = StateRequestElided()) const;
157 	StateResult getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request = StateRequestElided()) const;
158 
159 	[[nodiscard]] TextSelection adjustSelection(TextSelection selection, TextSelectType selectType) const;
isFullSelection(TextSelection selection)160 	bool isFullSelection(TextSelection selection) const {
161 		return (selection.from == 0) && (selection.to >= _text.size());
162 	}
163 
164 	bool isEmpty() const;
isNull()165 	bool isNull() const {
166 		return !_st;
167 	}
length()168 	int length() const {
169 		return _text.size();
170 	}
171 
172 	QString toString(TextSelection selection = AllTextSelection) const;
173 	TextWithEntities toTextWithEntities(
174 		TextSelection selection = AllTextSelection) const;
175 	TextForMimeData toTextForMimeData(
176 		TextSelection selection = AllTextSelection) const;
177 	IsolatedEmoji toIsolatedEmoji() const;
178 
style()179 	const style::TextStyle *style() const {
180 		return _st;
181 	}
182 
183 	void clear();
184 
185 private:
186 	using TextBlocks = QVector<Block>;
187 	using TextLinks = QVector<ClickHandlerPtr>;
188 
189 	uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
190 	uint16 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
191 
192 	// Template method for originalText(), originalTextWithEntities().
193 	template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
194 	void enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const;
195 
196 	// Template method for countWidth(), countHeight(), countLineWidths().
197 	// callback(lineWidth, lineHeight) will be called for all lines with:
198 	// QFixed lineWidth, int lineHeight
199 	template <typename Callback>
200 	void enumerateLines(int w, bool breakEverywhere, Callback callback) const;
201 
202 	void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
203 
204 	// clear() deletes all blocks and calls this method
205 	// it is also called from move constructor / assignment operator
206 	void clearFields();
207 
208 	TextForMimeData toText(
209 		TextSelection selection,
210 		bool composeExpanded,
211 		bool composeEntities) const;
212 
213 	QFixed _minResizeWidth;
214 	QFixed _maxWidth = 0;
215 	int32 _minHeight = 0;
216 
217 	QString _text;
218 	const style::TextStyle *_st = nullptr;
219 
220 	TextBlocks _blocks;
221 	TextLinks _links;
222 
223 	Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
224 
225 	friend class Parser;
226 	friend class Renderer;
227 
228 };
229 
230 [[nodiscard]] bool IsWordSeparator(QChar ch);
231 [[nodiscard]] bool IsAlmostLinkEnd(QChar ch);
232 [[nodiscard]] bool IsLinkEnd(QChar ch);
233 [[nodiscard]] bool IsNewline(QChar ch);
234 [[nodiscard]] bool IsSpace(QChar ch, bool rich = false);
235 [[nodiscard]] bool IsDiac(QChar ch);
236 [[nodiscard]] bool IsReplacedBySpace(QChar ch);
237 [[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false);
238 
239 } // namespace Text
240 } // namespace Ui
241 
snapSelection(int from,int to)242 inline TextSelection snapSelection(int from, int to) {
243 	return { static_cast<uint16>(std::clamp(from, 0, 0xFFFF)), static_cast<uint16>(std::clamp(to, 0, 0xFFFF)) };
244 }
shiftSelection(TextSelection selection,uint16 byLength)245 inline TextSelection shiftSelection(TextSelection selection, uint16 byLength) {
246 	return snapSelection(int(selection.from) + byLength, int(selection.to) + byLength);
247 }
unshiftSelection(TextSelection selection,uint16 byLength)248 inline TextSelection unshiftSelection(TextSelection selection, uint16 byLength) {
249 	return snapSelection(int(selection.from) - int(byLength), int(selection.to) - int(byLength));
250 }
shiftSelection(TextSelection selection,const Ui::Text::String & byText)251 inline TextSelection shiftSelection(TextSelection selection, const Ui::Text::String &byText) {
252 	return shiftSelection(selection, byText.length());
253 }
unshiftSelection(TextSelection selection,const Ui::Text::String & byText)254 inline TextSelection unshiftSelection(TextSelection selection, const Ui::Text::String &byText) {
255 	return unshiftSelection(selection, byText.length());
256 }
257 
258 // textcmd
259 QString textcmdSkipBlock(ushort w, ushort h);
260 QString textcmdStartLink(ushort lnkIndex);
261 QString textcmdStartLink(const QString &url);
262 QString textcmdStopLink();
263 QString textcmdLink(ushort lnkIndex, const QString &text);
264 QString textcmdLink(const QString &url, const QString &text);
265 QString textcmdStartSemibold();
266 QString textcmdStopSemibold();
267 const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
268