text_utilities.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. #include "ui/text/text_utilities.h"
  8. #include "base/algorithm.h"
  9. #include "base/qt/qt_string_view.h"
  10. #include "ui/text/custom_emoji_instance.h"
  11. #include "ui/text/text_custom_emoji.h"
  12. #include "styles/style_basic.h"
  13. #include <QtCore/QRegularExpression>
  14. namespace Ui {
  15. namespace Text {
  16. namespace {
  17. struct IconEmojiData {
  18. base::flat_map<not_null<const style::IconEmoji*>, int> indices;
  19. std::vector<not_null<const style::IconEmoji*>> list;
  20. };
  21. [[nodiscard]] TextWithEntities WithSingleEntity(
  22. const QString &text,
  23. EntityType type,
  24. const QString &data = QString()) {
  25. auto result = TextWithEntities{ text };
  26. result.entities.push_back({ type, 0, int(text.size()), data });
  27. return result;
  28. }
  29. [[nodiscard]] IconEmojiData &IconEmojiInfo() {
  30. static IconEmojiData result;
  31. return result;
  32. }
  33. [[nodiscard]] QString IconEmojiPrefix() {
  34. return u"icon-emoji-"_q;
  35. }
  36. class IconEmojiObject final : public CustomEmoji {
  37. public:
  38. explicit IconEmojiObject(not_null<const style::IconEmoji*> emoji);
  39. int width() override;
  40. QString entityData() override;
  41. void paint(QPainter &p, const Context &context) override;
  42. void unload() override;
  43. bool ready() override;
  44. bool readyInDefaultState() override;
  45. private:
  46. const not_null<const style::IconEmoji*> _emoji;
  47. Ui::CustomEmoji::IconEmojiFrameCache _cache;
  48. };
  49. IconEmojiObject::IconEmojiObject(not_null<const style::IconEmoji*> emoji)
  50. : _emoji(emoji) {
  51. }
  52. int IconEmojiObject::width() {
  53. return _emoji->padding.left()
  54. + _emoji->icon.width()
  55. + _emoji->padding.right();
  56. }
  57. QString IconEmojiObject::entityData() {
  58. return IconEmojiPrefix()
  59. + QString::number(IconEmojiInfo().indices[_emoji]);
  60. }
  61. void IconEmojiObject::paint(QPainter &p, const Context &context) {
  62. Ui::CustomEmoji::PaintIconEmoji(p, context, _emoji, _cache);
  63. }
  64. void IconEmojiObject::unload() {
  65. }
  66. bool IconEmojiObject::ready() {
  67. return true;
  68. }
  69. bool IconEmojiObject::readyInDefaultState() {
  70. return true;
  71. }
  72. } // namespace
  73. TextWithEntities Bold(const QString &text) {
  74. return WithSingleEntity(text, EntityType::Bold);
  75. }
  76. TextWithEntities Semibold(const QString &text) {
  77. return WithSingleEntity(text, EntityType::Semibold);
  78. }
  79. TextWithEntities Italic(const QString &text) {
  80. return WithSingleEntity(text, EntityType::Italic);
  81. }
  82. TextWithEntities Link(const QString &text, const QString &url) {
  83. return WithSingleEntity(text, EntityType::CustomUrl, url);
  84. }
  85. TextWithEntities Link(const QString &text, int index) {
  86. return Link(text, u"internal:index"_q + QChar(index));
  87. }
  88. TextWithEntities Link(TextWithEntities text, const QString &url) {
  89. return Wrapped(std::move(text), EntityType::CustomUrl, url);
  90. }
  91. TextWithEntities Link(TextWithEntities text, int index) {
  92. return Link(std::move(text), u"internal:index"_q + QChar(index));
  93. }
  94. TextWithEntities Colorized(const QString &text, int index) {
  95. const auto data = index ? QString(QChar(index)) : QString();
  96. return WithSingleEntity(text, EntityType::Colorized, data);
  97. }
  98. TextWithEntities Colorized(TextWithEntities text, int index) {
  99. const auto data = index ? QString(QChar(index)) : QString();
  100. return Wrapped(std::move(text), EntityType::Colorized, data);
  101. }
  102. TextWithEntities Wrapped(
  103. TextWithEntities text,
  104. EntityType type,
  105. const QString &data) {
  106. text.entities.insert(
  107. text.entities.begin(),
  108. { type, 0, int(text.text.size()), data });
  109. return text;
  110. }
  111. TextWithEntities RichLangValue(const QString &text) {
  112. static const auto kStart = QRegularExpression("(\\*\\*|__)");
  113. auto result = TextWithEntities();
  114. auto offset = 0;
  115. while (offset < text.size()) {
  116. const auto m = kStart.match(text, offset);
  117. if (!m.hasMatch()) {
  118. result.text.append(base::StringViewMid(text, offset));
  119. break;
  120. }
  121. const auto position = m.capturedStart();
  122. const auto from = m.capturedEnd();
  123. const auto tag = m.capturedView();
  124. const auto till = text.indexOf(tag, from + 1);
  125. if (till <= from) {
  126. offset = from;
  127. continue;
  128. }
  129. if (position > offset) {
  130. result.text.append(base::StringViewMid(text, offset, position - offset));
  131. }
  132. const auto type = (tag == qstr("__"))
  133. ? EntityType::Italic
  134. : EntityType::Bold;
  135. result.entities.push_back({ type, int(result.text.size()), int(till - from) });
  136. result.text.append(base::StringViewMid(text, from, till - from));
  137. offset = till + tag.size();
  138. }
  139. return result;
  140. }
  141. TextWithEntities SingleCustomEmoji(QString data, QString text) {
  142. return {
  143. text.isEmpty() ? u"@"_q : text,
  144. { EntityInText(EntityType::CustomEmoji, 0, 1, data) },
  145. };
  146. }
  147. TextWithEntities IconEmoji(
  148. not_null<const style::IconEmoji*> emoji,
  149. QString text) {
  150. const auto index = [&] {
  151. auto &info = IconEmojiInfo();
  152. const auto count = int(info.list.size());
  153. auto i = info.indices.emplace(emoji, count).first;
  154. if (i->second == count) {
  155. info.list.push_back(emoji);
  156. }
  157. return i->second;
  158. }();
  159. return SingleCustomEmoji(
  160. IconEmojiPrefix() + QString::number(index),
  161. text);
  162. }
  163. std::unique_ptr<CustomEmoji> TryMakeSimpleEmoji(QStringView data) {
  164. const auto prefix = IconEmojiPrefix();
  165. if (!data.startsWith(prefix)) {
  166. return nullptr;
  167. }
  168. auto &info = IconEmojiInfo();
  169. const auto index = data.mid(prefix.size()).toInt();
  170. return (index >= 0 && index < info.list.size())
  171. ? std::make_unique<IconEmojiObject>(info.list[index])
  172. : nullptr;
  173. }
  174. TextWithEntities Mid(const TextWithEntities &text, int position, int n) {
  175. if (n == -1) {
  176. n = int(text.text.size()) - position;
  177. }
  178. const auto midEnd = (position + n);
  179. auto entities = ranges::views::all(
  180. text.entities
  181. ) | ranges::views::filter([&](const EntityInText &entity) {
  182. // Intersects of ranges.
  183. const auto l1 = entity.offset();
  184. const auto r1 = entity.offset() + entity.length() - 1;
  185. const auto l2 = position;
  186. const auto r2 = midEnd - 1;
  187. return !(l1 > r2 || l2 > r1);
  188. }) | ranges::views::transform([&](const EntityInText &entity) {
  189. if ((entity.offset() == position) && (entity.length() == n)) {
  190. return entity;
  191. }
  192. const auto start = std::max(entity.offset(), position);
  193. const auto end = std::min(entity.offset() + entity.length(), midEnd);
  194. return EntityInText(
  195. entity.type(),
  196. start - position,
  197. end - start,
  198. entity.data());
  199. }) | ranges::to<EntitiesInText>();
  200. return {
  201. .text = text.text.mid(position, n),
  202. .entities = std::move(entities),
  203. };
  204. }
  205. TextWithEntities Filtered(
  206. const TextWithEntities &text,
  207. const std::vector<EntityType> &types) {
  208. auto result = ranges::views::all(
  209. text.entities
  210. ) | ranges::views::filter([&](const EntityInText &entity) {
  211. return ranges::contains(types, entity.type());
  212. }) | ranges::to<EntitiesInText>();
  213. return { .text = text.text, .entities = std::move(result) };
  214. }
  215. QString FixAmpersandInAction(QString text) {
  216. return text.replace('&', u"&&"_q);
  217. }
  218. TextWithEntities WrapEmailPattern(const QString &pattern) {
  219. constexpr auto kHidden = '*';
  220. const auto from = int(pattern.indexOf(kHidden));
  221. const auto to = int(pattern.lastIndexOf(kHidden));
  222. if (from != -1 && to != -1 && from <= to) {
  223. const auto length = to - from + 1;
  224. auto result = TextWithEntities{ pattern };
  225. result.entities.push_back({ EntityType::Spoiler, from, length });
  226. return result;
  227. }
  228. return { pattern };
  229. }
  230. } // namespace Text
  231. } // namespace Ui