text_block_parser.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  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_block_parser.h"
  8. #include "base/platform/base_platform_info.h"
  9. #include "ui/integration.h"
  10. #include "ui/text/text_extended_data.h"
  11. #include "ui/text/text_isolated_emoji.h"
  12. #include "styles/style_basic.h"
  13. #include <QtCore/QUrl>
  14. #include <private/qfixed_p.h>
  15. namespace Ui::Text {
  16. namespace {
  17. constexpr auto kStringLinkIndexShift = uint16(0x8000);
  18. constexpr auto kMaxDiacAfterSymbol = 2;
  19. [[nodiscard]] TextWithEntities PrepareRichFromRich(
  20. const TextWithEntities &text,
  21. const TextParseOptions &options) {
  22. auto result = text;
  23. const auto &preparsed = text.entities;
  24. const bool parseLinks = (options.flags & TextParseLinks);
  25. const bool parseColorized = (options.flags & TextParseColorized);
  26. if (!preparsed.isEmpty() && (parseLinks || parseColorized)) {
  27. bool parseMentions = (options.flags & TextParseMentions);
  28. bool parseHashtags = (options.flags & TextParseHashtags);
  29. bool parseBotCommands = (options.flags & TextParseBotCommands);
  30. bool parseMarkdown = (options.flags & TextParseMarkdown);
  31. if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMarkdown) {
  32. int32 i = 0, l = preparsed.size();
  33. result.entities.clear();
  34. result.entities.reserve(l);
  35. for (; i < l; ++i) {
  36. auto type = preparsed.at(i).type();
  37. if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
  38. (type == EntityType::Hashtag && !parseHashtags) ||
  39. (type == EntityType::Cashtag && !parseHashtags) ||
  40. (!parseLinks
  41. && (type == EntityType::Url
  42. || type == EntityType::CustomUrl)) ||
  43. (type == EntityType::BotCommand && !parseBotCommands) || // #TODO entities
  44. (!parseMarkdown && (type == EntityType::Bold
  45. || type == EntityType::Semibold
  46. || type == EntityType::Italic
  47. || type == EntityType::Underline
  48. || type == EntityType::StrikeOut
  49. || type == EntityType::Colorized
  50. || type == EntityType::Spoiler
  51. || type == EntityType::Code
  52. || type == EntityType::Pre
  53. || type == EntityType::Blockquote))) {
  54. continue;
  55. }
  56. result.entities.push_back(preparsed.at(i));
  57. }
  58. }
  59. }
  60. return result;
  61. }
  62. // Tilde fix in OpenSans.
  63. [[nodiscard]] bool ComputeCheckTilde(const style::TextStyle &st) {
  64. const auto &font = st.font;
  65. return (font->size() * style::DevicePixelRatio() == 13)
  66. && (font->flags() == 0)
  67. && (font->f.family() == qstr("Open Sans"));
  68. }
  69. [[nodiscard]] bool IsDiacriticAllowedAfter(QChar ch) {
  70. const auto code = ch.unicode();
  71. const auto category = ch.category();
  72. return (code > 32)
  73. && (category != QChar::Other_Control)
  74. && (category != QChar::Other_Format)
  75. && (category != QChar::Other_PrivateUse)
  76. && (category != QChar::Other_NotAssigned);
  77. }
  78. } // namespace
  79. BlockParser::StartedEntity::StartedEntity(TextBlockFlags flags)
  80. : _value(flags.value())
  81. , _type(Type::Flags) {
  82. Expects(_value >= 0 && _value < int(kStringLinkIndexShift));
  83. }
  84. BlockParser::StartedEntity::StartedEntity(uint16 index, Type type)
  85. : _value(index)
  86. , _type(type) {
  87. Expects((_type == Type::Link)
  88. ? (_value >= kStringLinkIndexShift)
  89. : (_value < kStringLinkIndexShift));
  90. }
  91. BlockParser::StartedEntity::Type BlockParser::StartedEntity::type() const {
  92. return _type;
  93. }
  94. std::optional<TextBlockFlags> BlockParser::StartedEntity::flags() const {
  95. if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) {
  96. return TextBlockFlags::from_raw(uint16(_value));
  97. }
  98. return std::nullopt;
  99. }
  100. std::optional<uint16> BlockParser::StartedEntity::linkIndex() const {
  101. if ((_value < int(kStringLinkIndexShift) && (_type == Type::IndexedLink))
  102. || (_value >= int(kStringLinkIndexShift) && (_type == Type::Link))) {
  103. return uint16(_value);
  104. }
  105. return std::nullopt;
  106. }
  107. std::optional<uint16> BlockParser::StartedEntity::colorIndex() const {
  108. if (_type == Type::Colorized) {
  109. return uint16(_value);
  110. }
  111. return std::nullopt;
  112. }
  113. BlockParser::BlockParser(
  114. not_null<String*> string,
  115. const TextWithEntities &textWithEntities,
  116. const TextParseOptions &options,
  117. const MarkedContext &context)
  118. : BlockParser(
  119. string,
  120. PrepareRichFromRich(textWithEntities, options),
  121. options,
  122. context,
  123. ReadyToken()) {
  124. }
  125. BlockParser::BlockParser(
  126. not_null<String*> string,
  127. TextWithEntities &&source,
  128. const TextParseOptions &options,
  129. const MarkedContext &context,
  130. ReadyToken)
  131. : _t(string)
  132. , _tText(string->_text)
  133. , _tBlocks(string->_blocks)
  134. , _source(std::move(source))
  135. , _context(context)
  136. , _start(_source.text.constData())
  137. , _end(_start + _source.text.size())
  138. , _ptr(_start)
  139. , _entitiesEnd(_source.entities.end())
  140. , _waitingEntity(_source.entities.begin())
  141. , _multiline(options.flags & TextParseMultiline)
  142. , _checkTilde(ComputeCheckTilde(*_t->_st)) {
  143. parse(options);
  144. }
  145. void BlockParser::createBlock(int skipBack) {
  146. if (_linkIndex < kStringLinkIndexShift && _linkIndex > _maxLinkIndex) {
  147. _maxLinkIndex = _linkIndex;
  148. }
  149. if (_linkIndex > kStringLinkIndexShift) {
  150. _maxShiftedLinkIndex = std::max(
  151. uint16(_linkIndex - kStringLinkIndexShift),
  152. _maxShiftedLinkIndex);
  153. }
  154. const auto length = int(_tText.size()) + skipBack - _blockStart;
  155. if (length <= 0) {
  156. return;
  157. }
  158. const auto newline = !_emoji
  159. && (length == 1)
  160. && (_tText.at(_blockStart) == QChar::LineFeed);
  161. if (_newlineAwaited) {
  162. _newlineAwaited = false;
  163. if (!newline) {
  164. _t->insertModifications(_blockStart, 1);
  165. _tText.insert(_blockStart, QChar::LineFeed);
  166. createBlock(skipBack - length);
  167. }
  168. }
  169. const auto linkIndex = _monoIndex ? _monoIndex : _linkIndex;
  170. auto custom = _customEmojiData.isEmpty()
  171. ? nullptr
  172. : MakeCustomEmoji(_customEmojiData, _context);
  173. const auto push = [&](auto &&factory, auto &&...args) {
  174. _tBlocks.push_back(factory({
  175. .position = uint16(_blockStart),
  176. .flags = _flags,
  177. .linkIndex = linkIndex,
  178. .colorIndex = _colorIndex,
  179. }, std::forward<decltype(args)>(args)...));
  180. };
  181. if (custom) {
  182. push(&Block::CustomEmoji, std::move(custom));
  183. } else if (_emoji) {
  184. push(&Block::Emoji, _emoji);
  185. } else if (newline) {
  186. push(&Block::Newline, _quoteIndex);
  187. } else {
  188. push(&Block::Text/*, _t->_minResizeWidth*/);
  189. }
  190. // Diacritic can't attach from the next block to this one.
  191. _allowDiacritic = false;
  192. _blockStart += length;
  193. _customEmojiData = QByteArray();
  194. _emoji = nullptr;
  195. }
  196. void BlockParser::createNewlineBlock(bool fromOriginalText) {
  197. if (!fromOriginalText) {
  198. _t->insertModifications(_tText.size(), 1);
  199. }
  200. _tText.push_back(QChar::LineFeed);
  201. _allowDiacritic = false;
  202. createBlock();
  203. }
  204. void BlockParser::ensureAtNewline(QuoteDetails quote) {
  205. createBlock();
  206. const auto lastType = _tBlocks.empty()
  207. ? TextBlockType::Newline
  208. : _tBlocks.back()->type();
  209. if (lastType != TextBlockType::Newline) {
  210. auto saved = base::take(_customEmojiData);
  211. createNewlineBlock(false);
  212. _customEmojiData = base::take(saved);
  213. }
  214. _quoteStartPosition = _tText.size();
  215. auto &quotes = _t->ensureQuotes()->list;
  216. quotes.push_back(std::move(quote));
  217. const auto index = _quoteIndex = int(quotes.size());
  218. if (_tBlocks.empty()) {
  219. _t->_startQuoteIndex = index;
  220. } else {
  221. auto &last = _tBlocks.back();
  222. Assert(last->type() == TextBlockType::Newline);
  223. last.unsafe<NewlineBlock>().setQuoteIndex(index);
  224. }
  225. }
  226. void BlockParser::finishEntities() {
  227. while (!_startedEntities.empty()
  228. && (_ptr >= _startedEntities.begin()->first || _ptr >= _end)) {
  229. auto list = std::move(_startedEntities.begin()->second);
  230. _startedEntities.erase(_startedEntities.begin());
  231. while (!list.empty()) {
  232. if (list.back().type() == StartedEntity::Type::CustomEmoji) {
  233. createBlock();
  234. } else if (const auto flags = list.back().flags()) {
  235. if (_flags & (*flags)) {
  236. createBlock();
  237. _flags &= ~(*flags);
  238. const auto lastType = _tBlocks.empty()
  239. ? TextBlockType::Newline
  240. : _tBlocks.back()->type();
  241. if ((*flags)
  242. & (TextBlockFlag::Pre | TextBlockFlag::Blockquote)) {
  243. if (_quoteIndex) {
  244. auto &quotes = _t->ensureQuotes()->list;
  245. auto &quote = quotes[_quoteIndex - 1];
  246. const auto from = _quoteStartPosition;
  247. const auto till = _tText.size();
  248. if (quote.pre && till > from) {
  249. quote.copy = std::make_shared<PreClickHandler>(
  250. _t,
  251. from,
  252. till - from);
  253. } else if (quote.blockquote && quote.collapsed) {
  254. quote.toggle = std::make_shared<BlockquoteClickHandler>(
  255. _t,
  256. _quoteIndex);
  257. }
  258. }
  259. _quoteIndex = 0;
  260. if (lastType != TextBlockType::Newline) {
  261. _newlineAwaited = true;
  262. } else if (_tBlocks.empty()) {
  263. _t->_startQuoteIndex = 0;
  264. } else {
  265. auto &last = _tBlocks.back();
  266. last.unsafe<NewlineBlock>().setQuoteIndex(0);
  267. }
  268. }
  269. if (IsMono(*flags)) {
  270. _monoIndex = 0;
  271. }
  272. }
  273. } else if (const auto linkIndex = list.back().linkIndex()) {
  274. if (_linkIndex == *linkIndex) {
  275. createBlock();
  276. _linkIndex = 0;
  277. }
  278. } else if (const auto colorIndex = list.back().colorIndex()) {
  279. if (_colorIndex == *colorIndex) {
  280. createBlock();
  281. _colorIndex = 0;
  282. }
  283. }
  284. list.pop_back();
  285. }
  286. }
  287. }
  288. // Returns true if at least one entity was parsed in the current position.
  289. bool BlockParser::checkEntities() {
  290. finishEntities();
  291. skipPassedEntities();
  292. if (_waitingEntity == _entitiesEnd
  293. || _ptr < _start + _waitingEntity->offset()) {
  294. return false;
  295. }
  296. auto flags = TextBlockFlags();
  297. auto link = EntityLinkData();
  298. auto monoIndex = 0;
  299. const auto entityType = _waitingEntity->type();
  300. const auto entityLength = _waitingEntity->length();
  301. const auto entityBegin = _start + _waitingEntity->offset();
  302. const auto entityEnd = entityBegin + entityLength;
  303. const auto pushSimpleUrl = [&](EntityType type) {
  304. link.type = type;
  305. link.data = QString(entityBegin, entityLength);
  306. if (type == EntityType::Url) {
  307. computeLinkText(link.data, &link.text, &link.shown);
  308. } else {
  309. link.text = link.data;
  310. }
  311. };
  312. const auto pushComplexUrl = [&] {
  313. link.type = entityType;
  314. link.data = _waitingEntity->data();
  315. link.text = QString(entityBegin, entityLength);
  316. };
  317. using Type = StartedEntity::Type;
  318. if (entityType == EntityType::CustomEmoji) {
  319. createBlock();
  320. _customEmojiData = _waitingEntity->data();
  321. _startedEntities[entityEnd].emplace_back(0, Type::CustomEmoji);
  322. } else if (entityType == EntityType::Bold) {
  323. flags = TextBlockFlag::Bold;
  324. } else if (entityType == EntityType::Semibold) {
  325. flags = TextBlockFlag::Semibold;
  326. } else if (entityType == EntityType::Italic) {
  327. flags = TextBlockFlag::Italic;
  328. } else if (entityType == EntityType::Underline) {
  329. flags = TextBlockFlag::Underline;
  330. } else if (entityType == EntityType::Spoiler) {
  331. flags = TextBlockFlag::Spoiler;
  332. } else if (entityType == EntityType::StrikeOut) {
  333. flags = TextBlockFlag::StrikeOut;
  334. } else if ((entityType == EntityType::Code) // #TODO entities
  335. || (entityType == EntityType::Pre)) {
  336. if (entityType == EntityType::Code) {
  337. flags = TextBlockFlag::Code;
  338. } else {
  339. flags = TextBlockFlag::Pre;
  340. ensureAtNewline({
  341. .language = _waitingEntity->data(),
  342. .pre = true,
  343. });
  344. }
  345. const auto text = QString(entityBegin, entityLength);
  346. // It is better to trim the text to identify "Sample\n" as inline.
  347. const auto trimmed = text.trimmed();
  348. const auto isSingleLine = !trimmed.isEmpty()
  349. && ranges::none_of(trimmed, IsNewline);
  350. // TODO: remove trimming.
  351. if (isSingleLine && (entityType == EntityType::Code)) {
  352. _monos.push_back({ .text = text, .type = entityType });
  353. monoIndex = _monos.size();
  354. }
  355. } else if (entityType == EntityType::Blockquote) {
  356. flags = TextBlockFlag::Blockquote;
  357. ensureAtNewline({
  358. .blockquote = true,
  359. .collapsed = !_waitingEntity->data().isEmpty(),
  360. });
  361. } else if (entityType == EntityType::Url
  362. || entityType == EntityType::Email
  363. || entityType == EntityType::Phone
  364. || entityType == EntityType::Mention
  365. || entityType == EntityType::Hashtag
  366. || entityType == EntityType::Cashtag
  367. || entityType == EntityType::BotCommand) {
  368. pushSimpleUrl(entityType);
  369. } else if (entityType == EntityType::CustomUrl) {
  370. const auto url = _waitingEntity->data();
  371. const auto text = QString(entityBegin, entityLength);
  372. if (url == text) {
  373. pushSimpleUrl(EntityType::Url);
  374. } else {
  375. pushComplexUrl();
  376. }
  377. } else if (entityType == EntityType::MentionName) {
  378. pushComplexUrl();
  379. } else if (entityType == EntityType::Colorized) {
  380. createBlock();
  381. const auto data = _waitingEntity->data();
  382. _colorIndex = data.isEmpty() ? 1 : (data.front().unicode() + 1);
  383. _startedEntities[entityEnd].emplace_back(
  384. _colorIndex,
  385. Type::Colorized);
  386. }
  387. if (link.type != EntityType::Invalid) {
  388. createBlock();
  389. _links.push_back(link);
  390. const auto tempIndex = _links.size();
  391. const auto useCustom = processCustomIndex(tempIndex);
  392. _linkIndex = tempIndex + (useCustom ? 0 : kStringLinkIndexShift);
  393. _startedEntities[entityEnd].emplace_back(
  394. _linkIndex,
  395. useCustom ? Type::IndexedLink : Type::Link);
  396. } else if (flags) {
  397. if (!(_flags & flags)) {
  398. createBlock();
  399. _flags |= flags;
  400. _startedEntities[entityEnd].emplace_back(flags);
  401. _monoIndex = monoIndex;
  402. }
  403. }
  404. ++_waitingEntity;
  405. skipBadEntities();
  406. return true;
  407. }
  408. bool BlockParser::processCustomIndex(uint16 index) {
  409. auto &url = _links[index - 1].data;
  410. if (url.isEmpty()) {
  411. return false;
  412. }
  413. if (url.startsWith("internal:index")) {
  414. const auto customIndex = uint16(url.back().unicode());
  415. // if (customIndex != index) {
  416. url = QString();
  417. _linksIndexes.push_back(customIndex);
  418. return true;
  419. // }
  420. }
  421. return false;
  422. }
  423. void BlockParser::skipPassedEntities() {
  424. while (_waitingEntity != _entitiesEnd
  425. && _start + _waitingEntity->offset() + _waitingEntity->length() <= _ptr) {
  426. ++_waitingEntity;
  427. }
  428. }
  429. void BlockParser::skipBadEntities() {
  430. if (_links.size() >= 0x7FFF) {
  431. while (_waitingEntity != _entitiesEnd
  432. && (isLinkEntity(*_waitingEntity)
  433. || isInvalidEntity(*_waitingEntity))) {
  434. ++_waitingEntity;
  435. }
  436. } else {
  437. while (_waitingEntity != _entitiesEnd && isInvalidEntity(*_waitingEntity)) {
  438. ++_waitingEntity;
  439. }
  440. }
  441. }
  442. void BlockParser::parseCurrentChar() {
  443. _ch = ((_ptr < _end) ? *_ptr : QChar(0));
  444. _emojiLookback = 0;
  445. const auto inCustomEmoji = !_customEmojiData.isEmpty();
  446. const auto isNewLine = !inCustomEmoji && _multiline && IsNewline(_ch);
  447. const auto replaceWithSpace = IsSpace(_ch) && (_ch != QChar::Nbsp);
  448. const auto isDiacritic = IsDiacritic(_ch);
  449. const auto isTilde = !inCustomEmoji && _checkTilde && (_ch == '~');
  450. const auto skip = [&] {
  451. if (IsBad(_ch) || _ch.isLowSurrogate()) {
  452. return true;
  453. } else if (_ch.unicode() == 0xFE0F && Platform::IsMac()) {
  454. // Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
  455. return true;
  456. } else if (isDiacritic) {
  457. if (!_allowDiacritic
  458. || _emoji
  459. || ++_diacritics > kMaxDiacAfterSymbol) {
  460. return true;
  461. }
  462. } else if (_ch.isHighSurrogate()) {
  463. if (_ptr + 1 >= _end || !(_ptr + 1)->isLowSurrogate()) {
  464. return true;
  465. }
  466. const auto ucs4 = QChar::surrogateToUcs4(_ch, *(_ptr + 1));
  467. if (ucs4 >= 0xE0000) {
  468. // Unicode tags are skipped.
  469. // Only place they work is in some flag emoji,
  470. // but in that case they were already parsed as emoji before.
  471. //
  472. // For unknown reason in some unknown cases strings with such
  473. // symbols lead to crashes on some Linux distributions, see
  474. // https://github.com/telegramdesktop/tdesktop/issues/7005
  475. //
  476. // At least one crashing text was starting that way:
  477. //
  478. // 0xd83d 0xdcda 0xdb40 0xdc69 0xdb40 0xdc64 0xdb40 0xdc6a
  479. // 0xdb40 0xdc77 0xdb40 0xdc7f 0x32 ... simple text here ...
  480. //
  481. // or in codepoints:
  482. //
  483. // 0x1f4da 0xe0069 0xe0064 0xe006a 0xe0077 0xe007f 0x32 ...
  484. return true;
  485. }
  486. }
  487. return false;
  488. }();
  489. if (_ch.isHighSurrogate() && !skip) {
  490. _tText.push_back(_ch);
  491. ++_ptr;
  492. _ch = *_ptr;
  493. _emojiLookback = 1;
  494. }
  495. if (skip) {
  496. if (_ptr < _end) {
  497. _t->insertModifications(_tText.size(), -1);
  498. }
  499. _ch = QChar(0);
  500. _allowDiacritic = false;
  501. } else {
  502. if (isTilde) { // Tilde fix in OpenSans.
  503. if (!(_flags & TextBlockFlag::Tilde)) {
  504. createBlock(-_emojiLookback);
  505. _flags |= TextBlockFlag::Tilde;
  506. }
  507. } else {
  508. if (_flags & TextBlockFlag::Tilde) {
  509. createBlock(-_emojiLookback);
  510. _flags &= ~TextBlockFlag::Tilde;
  511. }
  512. }
  513. if (isNewLine) {
  514. createBlock();
  515. createNewlineBlock(true);
  516. } else if (replaceWithSpace) {
  517. _tText.push_back(QChar::Space);
  518. _allowDiacritic = false;
  519. } else {
  520. if (_emoji) {
  521. createBlock(-_emojiLookback);
  522. }
  523. _tText.push_back(_ch);
  524. _allowDiacritic = IsDiacriticAllowedAfter(_ch);
  525. }
  526. if (!isDiacritic) {
  527. _diacritics = 0;
  528. }
  529. }
  530. }
  531. void BlockParser::parseEmojiFromCurrent() {
  532. if (!_customEmojiData.isEmpty()) {
  533. return;
  534. }
  535. int len = 0;
  536. auto e = Emoji::Find(_ptr - _emojiLookback, _end, &len);
  537. if (!e) return;
  538. for (int l = len - _emojiLookback - 1; l > 0; --l) {
  539. _tText.push_back(*++_ptr);
  540. }
  541. if (e->hasPostfix()) {
  542. Assert(!_tText.isEmpty());
  543. const auto last = _tText[_tText.size() - 1];
  544. if (last.unicode() != Emoji::kPostfix) {
  545. _t->insertModifications(_tText.size(), 1);
  546. _tText.push_back(QChar(Emoji::kPostfix));
  547. ++len;
  548. }
  549. }
  550. createBlock(-len);
  551. _emoji = e;
  552. }
  553. bool BlockParser::isInvalidEntity(const EntityInText &entity) const {
  554. const auto length = entity.length();
  555. return (_start + entity.offset() + length > _end) || (length <= 0);
  556. }
  557. bool BlockParser::isLinkEntity(const EntityInText &entity) const {
  558. const auto type = entity.type();
  559. const auto urls = {
  560. EntityType::Url,
  561. EntityType::CustomUrl,
  562. EntityType::Email,
  563. EntityType::Hashtag,
  564. EntityType::Cashtag,
  565. EntityType::Mention,
  566. EntityType::MentionName,
  567. EntityType::Phone,
  568. EntityType::BotCommand
  569. };
  570. return ranges::find(urls, type) != std::end(urls);
  571. }
  572. void BlockParser::parse(const TextParseOptions &options) {
  573. skipBadEntities();
  574. trimSourceRange();
  575. _tText.resize(0);
  576. if (_t->_extended) {
  577. base::take(_t->_extended->modifications);
  578. }
  579. _tText.reserve(_end - _ptr);
  580. if (_ptr > _start) {
  581. _t->insertModifications(0, -(_ptr - _start));
  582. }
  583. for (; _ptr <= _end; ++_ptr) {
  584. while (checkEntities()) {
  585. }
  586. parseCurrentChar();
  587. parseEmojiFromCurrent();
  588. if (_tText.size() >= 0x8000) {
  589. break; // 32k max
  590. }
  591. }
  592. createBlock();
  593. finalize(options);
  594. }
  595. void BlockParser::trimSourceRange() {
  596. const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
  597. _source.entities,
  598. _end - _start);
  599. while (_ptr != _end && IsTrimmed(*_ptr) && _ptr != _start + firstMonospaceOffset) {
  600. ++_ptr;
  601. }
  602. while (_ptr != _end && IsTrimmed(*(_end - 1))) {
  603. --_end;
  604. }
  605. }
  606. // void BlockParser::checkForElidedSkipBlock() {
  607. // if (!_sumFinished || !_rich) {
  608. // return;
  609. // }
  610. // // We could've skipped the final skip block command.
  611. // for (; _ptr < _end; ++_ptr) {
  612. // if (*_ptr == TextCommand && readSkipBlockCommand()) {
  613. // break;
  614. // }
  615. // }
  616. // }
  617. void BlockParser::finalize(const TextParseOptions &options) {
  618. auto links = (_maxLinkIndex || _maxShiftedLinkIndex)
  619. ? &_t->ensureExtended()->links
  620. : nullptr;
  621. if (links) {
  622. links->resize(_maxLinkIndex + _maxShiftedLinkIndex);
  623. }
  624. auto counterCustomIndex = uint16(0);
  625. auto currentIndex = uint16(0); // Current the latest index of _t->_links.
  626. struct {
  627. uint16 mono = 0;
  628. uint16 lnk = 0;
  629. } lastHandlerIndex;
  630. const auto avoidIntersectionsWithCustom = [&] {
  631. while (ranges::contains(_linksIndexes, currentIndex)) {
  632. currentIndex++;
  633. }
  634. };
  635. auto isolatedEmojiCount = 0;
  636. _t->_hasCustomEmoji = false;
  637. _t->_isIsolatedEmoji = true;
  638. _t->_isOnlyCustomEmoji = true;
  639. _t->_hasNotEmojiAndSpaces = false;
  640. auto spacesCheckFrom = uint16(-1);
  641. const auto length = int(_tText.size());
  642. for (auto &block : _tBlocks) {
  643. if (block->type() == TextBlockType::CustomEmoji) {
  644. _t->_hasCustomEmoji = true;
  645. } else if (block->type() != TextBlockType::Newline
  646. && block->type() != TextBlockType::Skip) {
  647. _t->_isOnlyCustomEmoji = false;
  648. } else if (block->linkIndex()) {
  649. _t->_isOnlyCustomEmoji = _t->_isIsolatedEmoji = false;
  650. }
  651. if (!_t->_hasNotEmojiAndSpaces) {
  652. if (block->type() == TextBlockType::Text) {
  653. if (spacesCheckFrom == uint16(-1)) {
  654. spacesCheckFrom = block->position();
  655. }
  656. } else if (spacesCheckFrom != uint16(-1)) {
  657. const auto checkTill = block->position();
  658. for (auto i = spacesCheckFrom; i != checkTill; ++i) {
  659. Assert(i < length);
  660. if (!_tText[i].isSpace()) {
  661. _t->_hasNotEmojiAndSpaces = true;
  662. break;
  663. }
  664. }
  665. spacesCheckFrom = uint16(-1);
  666. }
  667. }
  668. if (_t->_isIsolatedEmoji) {
  669. if (block->type() == TextBlockType::CustomEmoji
  670. || block->type() == TextBlockType::Emoji) {
  671. if (++isolatedEmojiCount > kIsolatedEmojiLimit) {
  672. _t->_isIsolatedEmoji = false;
  673. }
  674. } else if (block->type() != TextBlockType::Skip) {
  675. _t->_isIsolatedEmoji = false;
  676. }
  677. }
  678. if (block->flags() & TextBlockFlag::Spoiler) {
  679. auto &spoiler = _t->ensureExtended()->spoiler;
  680. if (!spoiler) {
  681. spoiler = std::make_unique<SpoilerData>(_context.repaint);
  682. }
  683. }
  684. const auto shiftedIndex = block->linkIndex();
  685. auto useCustomIndex = false;
  686. if (shiftedIndex <= kStringLinkIndexShift) {
  687. if (IsMono(block->flags()) && shiftedIndex) {
  688. const auto monoIndex = shiftedIndex;
  689. if (lastHandlerIndex.mono == monoIndex) {
  690. block->setLinkIndex(currentIndex);
  691. continue; // Optimization.
  692. } else {
  693. currentIndex++;
  694. }
  695. avoidIntersectionsWithCustom();
  696. block->setLinkIndex(currentIndex);
  697. const auto handler = Integration::Instance().createLinkHandler(
  698. _monos[monoIndex - 1],
  699. _context);
  700. if (!links) {
  701. links = &_t->ensureExtended()->links;
  702. }
  703. links->resize(currentIndex);
  704. if (handler) {
  705. _t->setLink(currentIndex, handler);
  706. }
  707. lastHandlerIndex.mono = monoIndex;
  708. continue;
  709. } else if (shiftedIndex) {
  710. useCustomIndex = true;
  711. } else {
  712. continue;
  713. }
  714. }
  715. const auto usedIndex = [&] {
  716. return useCustomIndex
  717. ? _linksIndexes[counterCustomIndex - 1]
  718. : currentIndex;
  719. };
  720. const auto realIndex = useCustomIndex
  721. ? shiftedIndex
  722. : (shiftedIndex - kStringLinkIndexShift);
  723. if (lastHandlerIndex.lnk == realIndex) {
  724. block->setLinkIndex(usedIndex());
  725. continue; // Optimization.
  726. } else {
  727. (useCustomIndex ? counterCustomIndex : currentIndex)++;
  728. }
  729. if (!useCustomIndex) {
  730. avoidIntersectionsWithCustom();
  731. }
  732. block->setLinkIndex(usedIndex());
  733. if (links) {
  734. links->resize(std::max(usedIndex(), uint16(links->size())));
  735. }
  736. const auto handler = Integration::Instance().createLinkHandler(
  737. _links[realIndex - 1],
  738. _context);
  739. if (handler) {
  740. _t->setLink(usedIndex(), handler);
  741. }
  742. lastHandlerIndex.lnk = realIndex;
  743. }
  744. const auto hasSpoiler = (_t->_extended && _t->_extended->spoiler);
  745. if (!_t->_hasCustomEmoji || hasSpoiler) {
  746. _t->_isOnlyCustomEmoji = false;
  747. }
  748. if (_tBlocks.empty() || hasSpoiler) {
  749. _t->_isIsolatedEmoji = false;
  750. }
  751. if (!_t->_hasNotEmojiAndSpaces && spacesCheckFrom != uint16(-1)) {
  752. Assert(spacesCheckFrom < length);
  753. for (auto i = spacesCheckFrom; i != length; ++i) {
  754. Assert(i < length);
  755. if (!_tText[i].isSpace()) {
  756. _t->_hasNotEmojiAndSpaces = true;
  757. break;
  758. }
  759. }
  760. }
  761. _tText.squeeze();
  762. _tBlocks.shrink_to_fit();
  763. if (const auto extended = _t->_extended.get()) {
  764. extended->links.shrink_to_fit();
  765. extended->modifications.shrink_to_fit();
  766. }
  767. }
  768. void BlockParser::computeLinkText(
  769. const QString &linkData,
  770. QString *outLinkText,
  771. EntityLinkShown *outShown) {
  772. auto url = QUrl(linkData);
  773. auto good = QUrl(url.isValid()
  774. ? url.toEncoded()
  775. : QByteArray());
  776. auto readable = good.isValid()
  777. ? good.toDisplayString()
  778. : linkData;
  779. *outLinkText = _t->_st->font->elided(readable, st::linkCropLimit);
  780. *outShown = (*outLinkText == readable)
  781. ? EntityLinkShown::Full
  782. : EntityLinkShown::Partial;
  783. }
  784. } // namespace Ui::Text