text.cpp 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970
  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.h"
  8. #include "ui/effects/spoiler_mess.h"
  9. #include "ui/text/text_block_parser.h"
  10. #include "ui/text/text_extended_data.h"
  11. #include "ui/text/text_isolated_emoji.h"
  12. #include "ui/text/text_renderer.h"
  13. #include "ui/text/text_word_parser.h"
  14. #include "ui/basic_click_handlers.h"
  15. #include "ui/integration.h"
  16. #include "ui/painter.h"
  17. #include "base/platform/base_platform_info.h"
  18. #include "styles/style_basic.h"
  19. #include <QtGui/QGuiApplication>
  20. namespace Ui {
  21. const QString kQEllipsis = u"..."_q;
  22. } // namespace Ui
  23. namespace Ui::Text {
  24. namespace {
  25. constexpr auto kDefaultSpoilerCacheCapacity = 24;
  26. [[nodiscard]] Qt::LayoutDirection StringDirection(
  27. const QString &str,
  28. int from,
  29. int to) {
  30. auto p = reinterpret_cast<const ushort*>(str.unicode()) + from;
  31. const auto end = p + (to - from);
  32. while (p < end) {
  33. uint ucs4 = *p;
  34. if (QChar::isHighSurrogate(ucs4) && p < end - 1) {
  35. ushort low = p[1];
  36. if (QChar::isLowSurrogate(low)) {
  37. ucs4 = QChar::surrogateToUcs4(ucs4, low);
  38. ++p;
  39. }
  40. }
  41. switch (QChar::direction(ucs4)) {
  42. case QChar::DirL:
  43. return Qt::LeftToRight;
  44. case QChar::DirR:
  45. case QChar::DirAL:
  46. return Qt::RightToLeft;
  47. default:
  48. break;
  49. }
  50. ++p;
  51. }
  52. return Qt::LayoutDirectionAuto;
  53. }
  54. bool IsParagraphSeparator(QChar ch) {
  55. switch (ch.unicode()) {
  56. case QChar::LineFeed:
  57. return true;
  58. default:
  59. break;
  60. }
  61. return false;
  62. }
  63. } // namespace
  64. } // namespace Ui::Text
  65. const TextParseOptions kDefaultTextOptions = {
  66. TextParseLinks | TextParseMultiline, // flags
  67. 0, // maxw
  68. 0, // maxh
  69. Qt::LayoutDirectionAuto, // dir
  70. };
  71. const TextParseOptions kMarkupTextOptions = {
  72. TextParseLinks | TextParseMultiline | TextParseMarkdown, // flags
  73. 0, // maxw
  74. 0, // maxh
  75. Qt::LayoutDirectionAuto, // dir
  76. };
  77. const TextParseOptions kPlainTextOptions = {
  78. TextParseMultiline, // flags
  79. 0, // maxw
  80. 0, // maxh
  81. Qt::LayoutDirectionAuto, // dir
  82. };
  83. namespace Ui::Text {
  84. struct SpoilerMessCache::Entry {
  85. SpoilerMessCached mess;
  86. QColor color;
  87. };
  88. SpoilerMessCache::SpoilerMessCache(int capacity) : _capacity(capacity) {
  89. Expects(capacity > 0);
  90. _cache.reserve(capacity);
  91. }
  92. SpoilerMessCache::~SpoilerMessCache() = default;
  93. not_null<SpoilerMessCached*> SpoilerMessCache::lookup(QColor color) {
  94. for (auto &entry : _cache) {
  95. if (entry.color == color) {
  96. return &entry.mess;
  97. }
  98. }
  99. Assert(_cache.size() < _capacity);
  100. _cache.push_back({
  101. .mess = Ui::SpoilerMessCached(DefaultTextSpoilerMask(), color),
  102. .color = color,
  103. });
  104. return &_cache.back().mess;
  105. }
  106. void SpoilerMessCache::reset() {
  107. _cache.clear();
  108. }
  109. not_null<SpoilerMessCache*> DefaultSpoilerCache() {
  110. struct Data {
  111. Data() : cache(kDefaultSpoilerCacheCapacity) {
  112. style::PaletteChanged() | rpl::start_with_next([=] {
  113. cache.reset();
  114. }, lifetime);
  115. }
  116. SpoilerMessCache cache;
  117. rpl::lifetime lifetime;
  118. };
  119. static auto data = Data();
  120. return &data.cache;
  121. }
  122. GeometryDescriptor SimpleGeometry(
  123. int availableWidth,
  124. int elisionLines,
  125. int elisionRemoveFromEnd,
  126. bool elisionBreakEverywhere) {
  127. constexpr auto wrap = [](
  128. Fn<LineGeometry(int line)> layout,
  129. bool breakEverywhere = false) {
  130. return GeometryDescriptor{ std::move(layout), breakEverywhere };
  131. };
  132. // Try to minimize captured values (to minimize Fn allocations).
  133. if (!elisionLines) {
  134. return wrap([=](int line) {
  135. return LineGeometry{ .width = availableWidth };
  136. });
  137. } else if (!elisionRemoveFromEnd) {
  138. return wrap([=](int line) {
  139. return LineGeometry{
  140. .width = availableWidth,
  141. .elided = (line + 1 >= elisionLines),
  142. };
  143. }, elisionBreakEverywhere);
  144. } else {
  145. return wrap([=](int line) {
  146. const auto elided = (line + 1 >= elisionLines);
  147. const auto removeFromEnd = (elided ? elisionRemoveFromEnd : 0);
  148. return LineGeometry{
  149. .width = availableWidth - removeFromEnd,
  150. .elided = elided,
  151. };
  152. }, elisionBreakEverywhere);
  153. }
  154. };
  155. void ValidateQuotePaintCache(
  156. QuotePaintCache &cache,
  157. const style::QuoteStyle &st) {
  158. const auto icon = st.icon.empty() ? nullptr : &st.icon;
  159. const auto expand = st.expand.empty() ? nullptr : &st.expand;
  160. const auto collapse = st.collapse.empty() ? nullptr : &st.collapse;
  161. if (!cache.corners.isNull()
  162. && cache.bgCached == cache.bg
  163. && cache.outlines == cache.outlines
  164. && (!st.header || cache.headerCached == cache.header)
  165. && ((!icon && !expand && !collapse)
  166. || cache.iconCached == cache.icon)) {
  167. return;
  168. }
  169. cache.bgCached = cache.bg;
  170. cache.outlinesCached = cache.outlines;
  171. if (st.header) {
  172. cache.headerCached = cache.header;
  173. }
  174. if (icon || expand || collapse) {
  175. cache.iconCached = cache.icon;
  176. }
  177. const auto radius = st.radius;
  178. const auto header = st.header;
  179. const auto outline = st.outline;
  180. const auto wiconsize = icon
  181. ? (icon->width() + st.iconPosition.x())
  182. : 0;
  183. const auto hiconsize = icon
  184. ? (icon->height() + st.iconPosition.y())
  185. : 0;
  186. const auto wcorner = std::max({ radius, outline, wiconsize });
  187. const auto hcorner = std::max({ header, radius, hiconsize });
  188. const auto middle = st::lineWidth;
  189. const auto wside = 2 * wcorner + middle;
  190. const auto hside = 2 * hcorner + middle;
  191. const auto full = QSize(wside, hside);
  192. const auto ratio = style::DevicePixelRatio();
  193. if (!cache.outlines[1].alpha()) {
  194. cache.outline = QImage();
  195. } else if (const auto outline = st.outline) {
  196. const auto third = (cache.outlines[2].alpha() != 0);
  197. const auto size = QSize(outline, outline * (third ? 6 : 4));
  198. cache.outline = QImage(
  199. size * ratio,
  200. QImage::Format_ARGB32_Premultiplied);
  201. cache.outline.fill(cache.outlines[0]);
  202. cache.outline.setDevicePixelRatio(ratio);
  203. auto p = QPainter(&cache.outline);
  204. p.setCompositionMode(QPainter::CompositionMode_Source);
  205. auto hq = PainterHighQualityEnabler(p);
  206. auto path = QPainterPath();
  207. path.moveTo(outline, outline);
  208. path.lineTo(outline, outline * (third ? 4 : 3));
  209. path.lineTo(0, outline * (third ? 5 : 4));
  210. path.lineTo(0, outline * 2);
  211. path.lineTo(outline, outline);
  212. p.fillPath(path, cache.outlines[third ? 2 : 1]);
  213. if (third) {
  214. auto path = QPainterPath();
  215. path.moveTo(outline, outline * 3);
  216. path.lineTo(outline, outline * 5);
  217. path.lineTo(0, outline * 6);
  218. path.lineTo(0, outline * 4);
  219. path.lineTo(outline, outline * 3);
  220. p.fillPath(path, cache.outlines[1]);
  221. }
  222. }
  223. auto image = QImage(full * ratio, QImage::Format_ARGB32_Premultiplied);
  224. image.fill(Qt::transparent);
  225. image.setDevicePixelRatio(ratio);
  226. auto p = QPainter(&image);
  227. auto hq = PainterHighQualityEnabler(p);
  228. p.setPen(Qt::NoPen);
  229. if (header) {
  230. p.setBrush(cache.header);
  231. p.setClipRect(outline, 0, wside - outline, header);
  232. p.drawRoundedRect(0, 0, wside, hcorner + radius, radius, radius);
  233. }
  234. if (outline) {
  235. const auto rect = QRect(0, 0, outline + radius * 2, hside);
  236. if (!cache.outline.isNull()) {
  237. const auto shift = QPoint(0, st.outlineShift);
  238. p.translate(shift);
  239. p.setBrush(cache.outline);
  240. p.setClipRect(QRect(-shift, QSize(outline, hside)));
  241. p.drawRoundedRect(rect.translated(-shift), radius, radius);
  242. p.translate(-shift);
  243. } else {
  244. p.setBrush(cache.outlines[0]);
  245. p.setClipRect(0, 0, outline, hside);
  246. p.drawRoundedRect(rect, radius, radius);
  247. }
  248. }
  249. p.setBrush(cache.bg);
  250. p.setClipRect(outline, header, wside - outline, hside - header);
  251. p.drawRoundedRect(0, 0, wside, hside, radius, radius);
  252. if (icon) {
  253. p.setClipping(false);
  254. const auto left = wside - icon->width() - st.iconPosition.x();
  255. const auto top = st.iconPosition.y();
  256. icon->paint(p, left, top, wside, cache.icon);
  257. }
  258. p.end();
  259. cache.corners = std::move(image);
  260. cache.expand = expand ? expand->instance(cache.icon) : QImage();
  261. cache.collapse = collapse ? collapse->instance(cache.icon) : QImage();
  262. }
  263. void FillQuotePaint(
  264. QPainter &p,
  265. QRect rect,
  266. const QuotePaintCache &cache,
  267. const style::QuoteStyle &st,
  268. SkipBlockPaintParts parts) {
  269. const auto &image = cache.corners;
  270. const auto ratio = int(image.devicePixelRatio());
  271. const auto iwidth = image.width() / ratio;
  272. const auto iheight = image.height() / ratio;
  273. const auto imiddle = st::lineWidth;
  274. const auto whalf = (iwidth - imiddle) / 2;
  275. const auto hhalf = (iheight - imiddle) / 2;
  276. const auto x = rect.left();
  277. const auto width = rect.width();
  278. auto y = rect.top();
  279. auto height = rect.height();
  280. const auto till = y + height;
  281. if (!parts.skippedTop) {
  282. const auto top = std::min(height, hhalf);
  283. p.drawImage(
  284. QRect(x, y, whalf, top),
  285. image,
  286. QRect(0, 0, whalf * ratio, top * ratio));
  287. p.drawImage(
  288. QRect(x + width - whalf, y, whalf, top),
  289. image,
  290. QRect((iwidth - whalf) * ratio, 0, whalf * ratio, top * ratio));
  291. if (const auto middle = width - 2 * whalf) {
  292. const auto header = st.header;
  293. const auto fillHeader = std::min(header, top);
  294. if (fillHeader) {
  295. p.fillRect(x + whalf, y, middle, fillHeader, cache.header);
  296. }
  297. if (const auto fillBody = top - fillHeader) {
  298. p.fillRect(
  299. QRect(x + whalf, y + fillHeader, middle, fillBody),
  300. cache.bg);
  301. }
  302. }
  303. height -= top;
  304. if (!height) {
  305. return;
  306. }
  307. y += top;
  308. rect.setTop(y);
  309. }
  310. const auto outline = st.outline;
  311. if (!parts.skipBottom) {
  312. const auto bottom = std::min(height, hhalf);
  313. const auto skip = !cache.outline.isNull() ? outline : 0;
  314. p.drawImage(
  315. QRect(x + skip, y + height - bottom, whalf - skip, bottom),
  316. image,
  317. QRect(
  318. skip * ratio,
  319. (iheight - bottom) * ratio,
  320. (whalf - skip) * ratio,
  321. bottom * ratio));
  322. p.drawImage(
  323. QRect(
  324. x + width - whalf,
  325. y + height - bottom,
  326. whalf,
  327. bottom),
  328. image,
  329. QRect(
  330. (iwidth - whalf) * ratio,
  331. (iheight - bottom) * ratio,
  332. whalf * ratio,
  333. bottom * ratio));
  334. if (const auto middle = width - 2 * whalf) {
  335. p.fillRect(
  336. QRect(x + whalf, y + height - bottom, middle, bottom),
  337. cache.bg);
  338. }
  339. if (skip) {
  340. if (cache.bottomCorner.size() != QSize(skip, whalf)) {
  341. cache.bottomCorner = QImage(
  342. QSize(skip, hhalf) * ratio,
  343. QImage::Format_ARGB32_Premultiplied);
  344. cache.bottomCorner.setDevicePixelRatio(ratio);
  345. cache.bottomCorner.fill(Qt::transparent);
  346. cache.bottomRounding = QImage(
  347. QSize(skip, hhalf) * ratio,
  348. QImage::Format_ARGB32_Premultiplied);
  349. cache.bottomRounding.setDevicePixelRatio(ratio);
  350. cache.bottomRounding.fill(Qt::transparent);
  351. const auto radius = st.radius;
  352. auto q = QPainter(&cache.bottomRounding);
  353. auto hq = PainterHighQualityEnabler(q);
  354. q.setPen(Qt::NoPen);
  355. q.setBrush(Qt::white);
  356. q.drawRoundedRect(
  357. 0,
  358. -2 * radius,
  359. skip + 2 * radius,
  360. hhalf + 2 * radius,
  361. radius,
  362. radius);
  363. }
  364. auto q = QPainter(&cache.bottomCorner);
  365. const auto skipped = (height - bottom)
  366. + (parts.skippedTop ? int(parts.skippedTop) : hhalf)
  367. - st.outlineShift;
  368. q.translate(0, -skipped);
  369. q.fillRect(0, skipped, skip, bottom, cache.outline);
  370. q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  371. q.drawImage(0, skipped + bottom - hhalf, cache.bottomRounding);
  372. q.end();
  373. p.drawImage(
  374. QRect(x, y + height - bottom, skip, bottom),
  375. cache.bottomCorner,
  376. QRect(0, 0, skip * ratio, bottom * ratio));
  377. }
  378. height -= bottom;
  379. rect.setHeight(height);
  380. }
  381. if (outline && height > 0) {
  382. if (!cache.outline.isNull()) {
  383. const auto skipped = st.outlineShift
  384. - (parts.skippedTop ? int(parts.skippedTop) : hhalf);
  385. const auto top = y + skipped;
  386. p.translate(x, top);
  387. p.fillRect(0, -skipped, outline, height, cache.outline);
  388. p.translate(-x, -top);
  389. } else {
  390. p.fillRect(x, y, outline, height, cache.outlines[0]);
  391. }
  392. }
  393. p.fillRect(x + outline, y, width - outline, height, cache.bg);
  394. const auto icon = parts.expandIcon
  395. ? &cache.expand
  396. : parts.collapseIcon
  397. ? &cache.collapse
  398. : nullptr;
  399. if (icon && !icon->isNull()) {
  400. const auto position = parts.expandIcon
  401. ? st.expandPosition
  402. : st.collapsePosition;
  403. const auto size = icon->size() / icon->devicePixelRatio();
  404. p.drawImage(
  405. QRect(
  406. x + width - size.width() - position.x(),
  407. till - size.height() - position.y(),
  408. size.width(),
  409. size.height()),
  410. *icon);
  411. }
  412. }
  413. String::ExtendedWrap::ExtendedWrap() noexcept = default;
  414. String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept
  415. : unique_ptr(std::move(other)) {
  416. adjustFrom(&other);
  417. }
  418. String::ExtendedWrap &String::ExtendedWrap::operator=(
  419. ExtendedWrap &&other) noexcept {
  420. *static_cast<unique_ptr*>(this) = std::move(other);
  421. adjustFrom(&other);
  422. return *this;
  423. }
  424. String::ExtendedWrap::ExtendedWrap(
  425. std::unique_ptr<ExtendedData> &&other) noexcept
  426. : unique_ptr(std::move(other)) {
  427. Assert(!get() || !get()->spoiler);
  428. }
  429. String::ExtendedWrap &String::ExtendedWrap::operator=(
  430. std::unique_ptr<ExtendedData> &&other) noexcept {
  431. *static_cast<unique_ptr*>(this) = std::move(other);
  432. Assert(!get() || !get()->spoiler);
  433. return *this;
  434. }
  435. String::ExtendedWrap::~ExtendedWrap() = default;
  436. void String::ExtendedWrap::adjustFrom(const ExtendedWrap *other) {
  437. const auto data = get();
  438. if (!data) {
  439. return;
  440. }
  441. const auto raw = [](auto pointer) {
  442. return reinterpret_cast<quintptr>(pointer);
  443. };
  444. const auto adjust = [&](auto &link) {
  445. const auto otherText = raw(link->text().get());
  446. link->setText(
  447. reinterpret_cast<String*>(otherText + raw(this) - raw(other)));
  448. };
  449. if (const auto spoiler = data->spoiler.get()) {
  450. if (spoiler->link) {
  451. adjust(spoiler->link);
  452. }
  453. }
  454. if (const auto quotes = data->quotes.get()) {
  455. for (auto &quote : quotes->list) {
  456. if (quote.copy) {
  457. adjust(quote.copy);
  458. }
  459. if (quote.toggle) {
  460. adjust(quote.toggle);
  461. }
  462. }
  463. }
  464. }
  465. String::String(int32 minResizeWidth)
  466. : _minResizeWidth(minResizeWidth) {
  467. }
  468. String::String(
  469. const style::TextStyle &st,
  470. const QString &text,
  471. const TextParseOptions &options,
  472. int32 minResizeWidth)
  473. : _minResizeWidth(minResizeWidth) {
  474. setText(st, text, options);
  475. }
  476. String::String(
  477. const style::TextStyle &st,
  478. const TextWithEntities &textWithEntities,
  479. const TextParseOptions &options,
  480. int32 minResizeWidth,
  481. const MarkedContext &context)
  482. : _minResizeWidth(minResizeWidth) {
  483. setMarkedText(st, textWithEntities, options, context);
  484. }
  485. String::String(String &&other) = default;
  486. String &String::operator=(String &&other) = default;
  487. String::~String() = default;
  488. void String::setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options) {
  489. setMarkedText(st, { text }, options);
  490. }
  491. void String::recountNaturalSize(
  492. bool initial,
  493. Qt::LayoutDirection optionsDirection) {
  494. auto lastNewlineBlock = begin(_blocks);
  495. auto lastNewlineStart = 0;
  496. const auto computeParagraphDirection = [&](int paragraphEnd) {
  497. const auto direction = (optionsDirection != Qt::LayoutDirectionAuto)
  498. ? optionsDirection
  499. : StringDirection(_text, lastNewlineStart, paragraphEnd);
  500. if (paragraphEnd) {
  501. while (blockPosition(lastNewlineBlock) < lastNewlineStart) {
  502. ++lastNewlineBlock;
  503. }
  504. Assert(lastNewlineBlock != end(_blocks));
  505. const auto block = lastNewlineBlock->get();
  506. if (block->type() == TextBlockType::Newline) {
  507. Assert(block->position() == lastNewlineStart);
  508. static_cast<NewlineBlock*>(block)->setParagraphDirection(
  509. direction);
  510. } else {
  511. Assert(!lastNewlineStart);
  512. _startParagraphLTR = (direction == Qt::LeftToRight);
  513. _startParagraphRTL = (direction == Qt::RightToLeft);
  514. }
  515. }
  516. };
  517. auto qindex = quoteIndex(nullptr);
  518. auto quote = quoteByIndex(qindex);
  519. auto qpadding = quotePadding(quote);
  520. auto qminwidth = quoteMinWidth(quote);
  521. auto qlinesleft = quoteLinesLimit(quote);
  522. auto qmaxwidth = QFixed(qminwidth);
  523. auto qoldheight = 0;
  524. _maxWidth = 0;
  525. _minHeight = qpadding.top();
  526. const auto lineHeight = this->lineHeight();
  527. auto maxWidth = QFixed();
  528. auto width = QFixed(qminwidth);
  529. auto last_rBearing = QFixed();
  530. auto last_rPadding = QFixed();
  531. for (const auto &word : _words) {
  532. if (word.newline()) {
  533. const auto block = word.newlineBlockIndex();
  534. const auto index = quoteIndex(_blocks[block].get());
  535. const auto changed = (qindex != index);
  536. const auto hidden = !qlinesleft;
  537. accumulate_max(maxWidth, width);
  538. accumulate_max(qmaxwidth, width);
  539. if (changed) {
  540. _minHeight += qpadding.bottom();
  541. if (quote) {
  542. quote->maxWidth = qmaxwidth.ceil().toInt();
  543. quote->minHeight = _minHeight - qoldheight;
  544. }
  545. qoldheight = _minHeight;
  546. qindex = index;
  547. quote = quoteByIndex(qindex);
  548. qpadding = quotePadding(quote);
  549. qminwidth = quoteMinWidth(quote);
  550. qlinesleft = quoteLinesLimit(quote);
  551. qmaxwidth = qminwidth;
  552. _minHeight += qpadding.top();
  553. qpadding.setTop(0);
  554. } else if (qlinesleft > 0) {
  555. --qlinesleft;
  556. }
  557. if (initial) {
  558. computeParagraphDirection(word.position());
  559. }
  560. lastNewlineStart = word.position();
  561. if (!hidden) {
  562. _minHeight += lineHeight;
  563. }
  564. last_rBearing = 0;// b->f_rbearing(); (0 for newline)
  565. last_rPadding = word.f_rpadding();
  566. width = qminwidth;
  567. // + (b->f_width() - last_rBearing); (0 for newline)
  568. continue;
  569. }
  570. auto w__f_rbearing = word.f_rbearing(); // cache
  571. // We need to accumulate max width after each block, because
  572. // some blocks have width less than -1 * previous right bearing.
  573. // In that cases the _width gets _smaller_ after moving to the next block.
  574. //
  575. // But when we layout block and we're sure that _maxWidth is enough
  576. // for all the blocks to fit on their line we check each block, even the
  577. // intermediate one with a large negative right bearing.
  578. accumulate_max(maxWidth, width);
  579. accumulate_max(qmaxwidth, width);
  580. width += last_rBearing + (last_rPadding + word.f_width() - w__f_rbearing);
  581. last_rBearing = w__f_rbearing;
  582. last_rPadding = word.f_rpadding();
  583. }
  584. if (initial) {
  585. computeParagraphDirection(_text.size());
  586. }
  587. if (width > 0) {
  588. const auto useSkipHeight = (_blocks.back()->type() == TextBlockType::Skip)
  589. && (_words.back().f_width() == width);
  590. _minHeight += qpadding.top() + qpadding.bottom();
  591. if (qlinesleft != 0) {
  592. _minHeight += useSkipHeight
  593. ? _blocks.back().unsafe<SkipBlock>().height()
  594. : lineHeight;
  595. }
  596. accumulate_max(maxWidth, width);
  597. accumulate_max(qmaxwidth, width);
  598. }
  599. _maxWidth = maxWidth.ceil().toInt();
  600. if (quote) {
  601. quote->maxWidth = qmaxwidth.ceil().toInt();
  602. quote->minHeight = _minHeight - qoldheight;
  603. _endsWithQuoteOrOtherDirection = true;
  604. } else {
  605. const auto lastIsNewline = (lastNewlineBlock != end(_blocks))
  606. && (lastNewlineBlock->get()->type() == TextBlockType::Newline);
  607. const auto lastNewline = lastIsNewline
  608. ? static_cast<NewlineBlock*>(lastNewlineBlock->get())
  609. : nullptr;
  610. const auto lastLineDirection = lastNewline
  611. ? lastNewline->paragraphDirection()
  612. : _startParagraphRTL
  613. ? Qt::RightToLeft
  614. : Qt::LeftToRight;
  615. _endsWithQuoteOrOtherDirection
  616. = ((lastLineDirection != style::LayoutDirection())
  617. && (lastLineDirection != Qt::LayoutDirectionAuto));
  618. }
  619. }
  620. int String::countMaxMonospaceWidth() const {
  621. auto result = 0;
  622. if (const auto quotes = _extended ? _extended->quotes.get() : nullptr) {
  623. for (const auto &quote : quotes->list) {
  624. if (quote.pre) {
  625. accumulate_max(result, quote.maxWidth);
  626. }
  627. }
  628. }
  629. return result;
  630. }
  631. void String::setMarkedText(
  632. const style::TextStyle &st,
  633. const TextWithEntities &textWithEntities,
  634. const TextParseOptions &options,
  635. const MarkedContext &context) {
  636. _st = &st;
  637. clear();
  638. {
  639. // utf codes of the text display for emoji extraction
  640. // auto text = textWithEntities.text;
  641. // auto newText = QString();
  642. // newText.reserve(8 * text.size());
  643. // newText.append("\t{ ");
  644. // for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
  645. // if (*ch == TextCommand) {
  646. // break;
  647. // } else if (IsNewline(*ch)) {
  648. // newText.append("},").append(*ch).append("\t{ ");
  649. // } else {
  650. // if (ch->isHighSurrogate() || ch->isLowSurrogate()) {
  651. // if (ch->isHighSurrogate() && (ch + 1 != e) && ((ch + 1)->isLowSurrogate())) {
  652. // newText.append("0x").append(QString::number((uint32(ch->unicode()) << 16) | uint32((ch + 1)->unicode()), 16).toUpper()).append("U, ");
  653. // ++ch;
  654. // } else {
  655. // newText.append("BADx").append(QString::number(ch->unicode(), 16).toUpper()).append("U, ");
  656. // }
  657. // } else {
  658. // newText.append("0x").append(QString::number(ch->unicode(), 16).toUpper()).append("U, ");
  659. // }
  660. // }
  661. // }
  662. // newText.append("},\n\n").append(text);
  663. // BlockParser block(this, { newText, EntitiesInText() }, options, context);
  664. BlockParser block(this, textWithEntities, options, context);
  665. WordParser word(this);
  666. }
  667. recountNaturalSize(true, options.dir);
  668. }
  669. void String::setLink(uint16 index, const ClickHandlerPtr &link) {
  670. const auto extended = _extended.get();
  671. if (extended && index > 0 && index <= extended->links.size()) {
  672. extended->links[index - 1] = link;
  673. }
  674. }
  675. void String::setSpoilerRevealed(bool revealed, anim::type animated) {
  676. const auto data = _extended ? _extended->spoiler.get() : nullptr;
  677. if (!data) {
  678. return;
  679. } else if (data->revealed == revealed) {
  680. if (animated == anim::type::instant
  681. && data->revealAnimation.animating()) {
  682. data->revealAnimation.stop();
  683. data->animation.repaintCallback()();
  684. }
  685. return;
  686. }
  687. data->revealed = revealed;
  688. if (animated == anim::type::instant) {
  689. data->revealAnimation.stop();
  690. data->animation.repaintCallback()();
  691. } else {
  692. data->revealAnimation.start(
  693. data->animation.repaintCallback(),
  694. revealed ? 0. : 1.,
  695. revealed ? 1. : 0.,
  696. st::fadeWrapDuration);
  697. }
  698. }
  699. void String::setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter) {
  700. Expects(_extended && _extended->spoiler);
  701. _extended->spoiler->link = std::make_shared<SpoilerClickHandler>(
  702. this,
  703. std::move(filter));
  704. }
  705. void String::setBlockquoteExpandCallback(
  706. Fn<void(int index, bool expanded)> callback) {
  707. Expects(_extended && _extended->quotes);
  708. _extended->quotes->expandCallback = std::move(callback);
  709. }
  710. bool String::hasLinks() const {
  711. return _extended && !_extended->links.empty();
  712. }
  713. bool String::hasSpoilers() const {
  714. return _extended && (_extended->spoiler != nullptr);
  715. }
  716. bool String::hasCollapsedBlockquots() const {
  717. return _extended
  718. && _extended->quotes
  719. && ranges::any_of(_extended->quotes->list, &QuoteDetails::collapsed);
  720. }
  721. bool String::blockquoteCollapsed(int index) const {
  722. Expects(_extended && _extended->quotes);
  723. Expects(index > 0 && index <= _extended->quotes->list.size());
  724. return _extended->quotes->list[index - 1].collapsed;
  725. }
  726. bool String::blockquoteExpanded(int index) const {
  727. Expects(_extended && _extended->quotes);
  728. Expects(index > 0 && index <= _extended->quotes->list.size());
  729. return _extended->quotes->list[index - 1].expanded;
  730. }
  731. void String::setBlockquoteExpanded(int index, bool expanded) {
  732. Expects(_extended && _extended->quotes);
  733. Expects(index > 0 && index <= _extended->quotes->list.size());
  734. auto &quote = _extended->quotes->list[index - 1];
  735. if (quote.expanded == expanded) {
  736. return;
  737. }
  738. quote.expanded = expanded;
  739. recountNaturalSize(false);
  740. if (const auto onstack = _extended->quotes->expandCallback) {
  741. onstack(index, expanded);
  742. }
  743. }
  744. bool String::hasSkipBlock() const {
  745. return !_blocks.empty()
  746. && (_blocks.back()->type() == TextBlockType::Skip);
  747. }
  748. bool String::updateSkipBlock(int width, int height) {
  749. if (!width || !height) {
  750. return removeSkipBlock();
  751. }
  752. if (!_blocks.empty() && _blocks.back()->type() == TextBlockType::Skip) {
  753. const auto &block = _blocks.back().unsafe<SkipBlock>();
  754. if (block.width() == width && block.height() == height) {
  755. return false;
  756. }
  757. const auto size = block.position();
  758. _text.resize(size);
  759. _blocks.pop_back();
  760. _words.pop_back();
  761. removeModificationsAfter(size);
  762. } else if (_endsWithQuoteOrOtherDirection) {
  763. insertModifications(_text.size(), 1);
  764. _words.push_back(Word(
  765. uint16(_text.size()),
  766. int(_blocks.size())));
  767. _blocks.push_back(Block::Newline({
  768. .position = _words.back().position(),
  769. }, 0));
  770. _text.push_back(QChar::LineFeed);
  771. _skipBlockAddedNewline = true;
  772. }
  773. insertModifications(_text.size(), 1);
  774. const auto unfinished = false;
  775. const auto rbearing = 0;
  776. _words.push_back(Word(
  777. uint16(_text.size()),
  778. unfinished,
  779. width,
  780. rbearing));
  781. _blocks.push_back(Block::Skip({
  782. .position = _words.back().position(),
  783. }, width, height));
  784. _text.push_back('_');
  785. recountNaturalSize(false);
  786. return true;
  787. }
  788. bool String::removeSkipBlock() {
  789. if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) {
  790. return false;
  791. } else if (_skipBlockAddedNewline) {
  792. const auto size = _blocks.back()->position() - 1;
  793. _text.resize(size);
  794. _blocks.pop_back();
  795. _blocks.pop_back();
  796. _words.pop_back();
  797. _words.pop_back();
  798. _skipBlockAddedNewline = false;
  799. removeModificationsAfter(size);
  800. } else {
  801. const auto size = _blocks.back()->position();
  802. _text.resize(size);
  803. _blocks.pop_back();
  804. _words.pop_back();
  805. removeModificationsAfter(size);
  806. }
  807. recountNaturalSize(false);
  808. return true;
  809. }
  810. void String::insertModifications(int position, int delta) {
  811. auto &modifications = ensureExtended()->modifications;
  812. auto i = end(modifications);
  813. while (i != begin(modifications) && (i - 1)->position >= position) {
  814. --i;
  815. if (i->position < position) {
  816. break;
  817. } else if (delta > 0) {
  818. ++i->position;
  819. } else if (i->position == position) {
  820. break;
  821. }
  822. }
  823. if (i != end(modifications) && i->position == position) {
  824. ++i->skipped;
  825. } else {
  826. modifications.insert(i, {
  827. .position = position,
  828. .skipped = uint16(delta < 0 ? (-delta) : 0),
  829. .added = (delta > 0),
  830. });
  831. }
  832. }
  833. void String::removeModificationsAfter(int size) {
  834. if (!_extended) {
  835. return;
  836. }
  837. auto &modifications = _extended->modifications;
  838. for (auto i = end(modifications); i != begin(modifications);) {
  839. --i;
  840. if (i->position > size) {
  841. i = modifications.erase(i);
  842. } else if (i->position == size) {
  843. i->added = false;
  844. if (!i->skipped) {
  845. i = modifications.erase(i);
  846. }
  847. } else {
  848. break;
  849. }
  850. }
  851. }
  852. String::DimensionsResult String::countDimensions(
  853. GeometryDescriptor geometry) const {
  854. return countDimensions(std::move(geometry), {});
  855. }
  856. String::DimensionsResult String::countDimensions(
  857. GeometryDescriptor geometry,
  858. DimensionsRequest request) const {
  859. auto result = DimensionsResult();
  860. if (request.lineWidths && request.reserve) {
  861. result.lineWidths.reserve(request.reserve);
  862. }
  863. enumerateLines(geometry, [&](QFixed lineWidth, int lineBottom) {
  864. const auto width = lineWidth.ceil().toInt();
  865. if (request.lineWidths) {
  866. result.lineWidths.push_back(width);
  867. }
  868. result.width = std::max(result.width, width);
  869. result.height = lineBottom;
  870. });
  871. return result;
  872. }
  873. int String::countWidth(int width, bool breakEverywhere) const {
  874. if (QFixed(width) >= _maxWidth) {
  875. return _maxWidth;
  876. }
  877. QFixed maxLineWidth = 0;
  878. enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int) {
  879. if (lineWidth > maxLineWidth) {
  880. maxLineWidth = lineWidth;
  881. }
  882. });
  883. return maxLineWidth.ceil().toInt();
  884. }
  885. int String::countHeight(int width, bool breakEverywhere) const {
  886. if (QFixed(width) >= _maxWidth) {
  887. return _minHeight;
  888. }
  889. int result = 0;
  890. enumerateLines(width, breakEverywhere, [&](auto, int lineBottom) {
  891. result = lineBottom;
  892. });
  893. return result;
  894. }
  895. std::vector<int> String::countLineWidths(int width) const {
  896. return countLineWidths(width, {});
  897. }
  898. std::vector<int> String::countLineWidths(
  899. int width,
  900. LineWidthsOptions options) const {
  901. auto result = std::vector<int>();
  902. if (options.reserve) {
  903. result.reserve(options.reserve);
  904. }
  905. enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int) {
  906. result.push_back(lineWidth.ceil().toInt());
  907. });
  908. return result;
  909. }
  910. template <typename Callback>
  911. void String::enumerateLines(
  912. int w,
  913. bool breakEverywhere,
  914. Callback &&callback) const {
  915. if (isEmpty()) {
  916. return;
  917. }
  918. const auto width = std::max(w, _minResizeWidth);
  919. auto g = SimpleGeometry(width, 0, 0, false);
  920. g.breakEverywhere = breakEverywhere;
  921. enumerateLines(g, std::forward<Callback>(callback));
  922. }
  923. template <typename Callback>
  924. void String::enumerateLines(
  925. GeometryDescriptor geometry,
  926. Callback &&callback) const {
  927. if (isEmpty()) {
  928. return;
  929. }
  930. const auto withElided = [&](bool elided) {
  931. if (geometry.outElided) {
  932. *geometry.outElided = elided;
  933. }
  934. };
  935. auto qindex = 0;
  936. auto quote = (QuoteDetails*)nullptr;
  937. auto qlinesleft = -1;
  938. auto qpadding = QMargins();
  939. auto top = 0;
  940. auto lineLeft = 0;
  941. auto lineWidth = 0;
  942. auto lineElided = false;
  943. auto widthLeft = QFixed(0);
  944. auto lineIndex = 0;
  945. const auto initNextLine = [&] {
  946. const auto line = geometry.layout(lineIndex++);
  947. lineLeft = line.left;
  948. lineWidth = line.width;
  949. lineElided = line.elided;
  950. if (quote && quote->maxWidth < lineWidth) {
  951. lineWidth = quote->maxWidth;
  952. }
  953. widthLeft = lineWidth - qpadding.left() - qpadding.right();
  954. };
  955. const auto initNextParagraph = [&](int16 paragraphIndex) {
  956. if (qindex != paragraphIndex) {
  957. //top += qpadding.bottom(); // This was done before callback().
  958. qindex = paragraphIndex;
  959. quote = quoteByIndex(qindex);
  960. qpadding = quotePadding(quote);
  961. qlinesleft = quoteLinesLimit(quote);
  962. top += qpadding.top();
  963. qpadding.setTop(0);
  964. }
  965. initNextLine();
  966. };
  967. if ((*_blocks.cbegin())->type() != TextBlockType::Newline) {
  968. initNextParagraph(_startQuoteIndex);
  969. }
  970. const auto lineHeight = this->lineHeight();
  971. auto last_rBearing = QFixed();
  972. auto last_rPadding = QFixed();
  973. auto longWordLine = true;
  974. auto lastWordStart = begin(_words);
  975. auto lastWordStart_wLeft = widthLeft;
  976. for (auto w = lastWordStart, e = end(_words); w != e; ++w) {
  977. if (w->newline()) {
  978. const auto block = w->newlineBlockIndex();
  979. const auto index = quoteIndex(_blocks[block].get());
  980. const auto hidden = !qlinesleft;
  981. const auto changed = (qindex != index);
  982. if (changed) {
  983. top += qpadding.bottom();
  984. }
  985. if (qlinesleft > 0) {
  986. --qlinesleft;
  987. }
  988. if (!hidden) {
  989. callback(lineLeft + lineWidth - widthLeft, top += lineHeight);
  990. }
  991. if (lineElided) {
  992. return withElided(true);
  993. }
  994. last_rBearing = 0;// b->f_rbearing(); (0 for newline)
  995. last_rPadding = w->f_rpadding();
  996. initNextParagraph(index);
  997. longWordLine = true;
  998. lastWordStart = w;
  999. lastWordStart_wLeft = widthLeft;
  1000. continue;
  1001. } else if (!qlinesleft) {
  1002. continue;
  1003. }
  1004. const auto wordEndsHere = !w->unfinished();
  1005. auto w__f_width = w->f_width();
  1006. const auto w__f_rbearing = w->f_rbearing();
  1007. const auto newWidthLeft = widthLeft
  1008. - last_rBearing
  1009. - (last_rPadding + w__f_width - w__f_rbearing);
  1010. if (newWidthLeft >= 0) {
  1011. last_rBearing = w__f_rbearing;
  1012. last_rPadding = w->f_rpadding();
  1013. widthLeft = newWidthLeft;
  1014. if (wordEndsHere) {
  1015. longWordLine = false;
  1016. }
  1017. if (wordEndsHere || longWordLine) {
  1018. lastWordStart_wLeft = widthLeft;
  1019. lastWordStart = w + 1;
  1020. }
  1021. continue;
  1022. }
  1023. if (lineElided) {
  1024. } else if (w != lastWordStart && !geometry.breakEverywhere) {
  1025. w = lastWordStart;
  1026. widthLeft = lastWordStart_wLeft;
  1027. w__f_width = w->f_width();
  1028. }
  1029. if (qlinesleft > 0) {
  1030. --qlinesleft;
  1031. }
  1032. callback(lineLeft + lineWidth - widthLeft, top += lineHeight);
  1033. if (lineElided) {
  1034. return withElided(true);
  1035. }
  1036. initNextLine();
  1037. last_rBearing = w->f_rbearing();
  1038. last_rPadding = w->f_rpadding();
  1039. widthLeft -= w__f_width - last_rBearing;
  1040. longWordLine = !wordEndsHere;
  1041. lastWordStart = w + 1;
  1042. lastWordStart_wLeft = widthLeft;
  1043. }
  1044. if (widthLeft < lineWidth) {
  1045. const auto useSkipHeight = (_blocks.back()->type() == TextBlockType::Skip)
  1046. && (widthLeft + _words.back().f_width() == lineWidth);
  1047. const auto useLineHeight = useSkipHeight
  1048. ? _blocks.back().unsafe<SkipBlock>().height()
  1049. : lineHeight;
  1050. callback(
  1051. lineLeft + lineWidth - widthLeft,
  1052. top + useLineHeight + qpadding.bottom());
  1053. }
  1054. return withElided(false);
  1055. }
  1056. void String::draw(QPainter &p, const PaintContext &context) const {
  1057. Renderer(*this).draw(p, context);
  1058. }
  1059. StateResult String::getState(
  1060. QPoint point,
  1061. GeometryDescriptor geometry,
  1062. StateRequest request) const {
  1063. return Renderer(*this).getState(point, std::move(geometry), request);
  1064. }
  1065. void String::draw(Painter &p, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection, bool fullWidthSelection) const {
  1066. // p.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
  1067. Renderer(*this).draw(p, {
  1068. .position = { left, top },
  1069. .availableWidth = w,
  1070. .align = align,
  1071. .clip = (yTo >= 0
  1072. ? QRect(left, top + yFrom, w, yTo - yFrom)
  1073. : QRect()),
  1074. .palette = &p.textPalette(),
  1075. .paused = p.inactive(),
  1076. .fullWidthSelection = fullWidthSelection,
  1077. .selection = selection,
  1078. });
  1079. }
  1080. void String::drawElided(Painter &p, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
  1081. // p.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
  1082. Renderer(*this).draw(p, {
  1083. .position = { left, top },
  1084. .availableWidth = w,
  1085. .align = align,
  1086. .clip = (yTo >= 0
  1087. ? QRect(left, top + yFrom, w, yTo - yFrom)
  1088. : QRect()),
  1089. .palette = &p.textPalette(),
  1090. .paused = p.inactive(),
  1091. .selection = selection,
  1092. .elisionLines = lines,
  1093. .elisionRemoveFromEnd = removeFromEnd,
  1094. });
  1095. }
  1096. void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
  1097. Renderer(*this).draw(p, {
  1098. .position = { left, top },
  1099. //.outerWidth = outerw,
  1100. .availableWidth = width,
  1101. .align = align,
  1102. .clip = (yTo >= 0
  1103. ? QRect(left, top + yFrom, width, yTo - yFrom)
  1104. : QRect()),
  1105. .palette = &p.textPalette(),
  1106. .paused = p.inactive(),
  1107. .selection = selection,
  1108. });
  1109. }
  1110. void String::drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
  1111. drawElided(p, style::RightToLeft() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
  1112. }
  1113. void String::drawRight(Painter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
  1114. drawLeft(p, (outerw - right - width), top, width, outerw, align, yFrom, yTo, selection);
  1115. }
  1116. void String::drawRightElided(Painter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
  1117. drawLeftElided(p, (outerw - right - width), top, width, outerw, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
  1118. }
  1119. StateResult String::getState(QPoint point, int width, StateRequest request) const {
  1120. if (isEmpty()) {
  1121. return {};
  1122. }
  1123. return Renderer(*this).getState(
  1124. point,
  1125. SimpleGeometry(width, 0, 0, false),
  1126. request);
  1127. }
  1128. StateResult String::getStateLeft(QPoint point, int width, int outerw, StateRequest request) const {
  1129. return getState(style::rtlpoint(point, outerw), width, request);
  1130. }
  1131. StateResult String::getStateElided(QPoint point, int width, StateRequestElided request) const {
  1132. if (isEmpty()) {
  1133. return {};
  1134. }
  1135. return Renderer(*this).getState(point, SimpleGeometry(
  1136. width,
  1137. request.lines,
  1138. request.removeFromEnd,
  1139. request.flags & StateRequest::Flag::BreakEverywhere
  1140. ), static_cast<StateRequest>(request));
  1141. }
  1142. StateResult String::getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request) const {
  1143. return getStateElided(style::rtlpoint(point, outerw), width, request);
  1144. }
  1145. TextSelection String::adjustSelection(TextSelection selection, TextSelectType selectType) const {
  1146. uint16 from = selection.from, to = selection.to;
  1147. if (from < _text.size() && from <= to) {
  1148. if (to > _text.size()) to = _text.size();
  1149. if (selectType == TextSelectType::Paragraphs) {
  1150. // Full selection of monospace entity.
  1151. for (const auto &b : _blocks) {
  1152. if (b->position() < from) {
  1153. continue;
  1154. }
  1155. if (!IsMono(b->flags())) {
  1156. break;
  1157. }
  1158. const auto &entities = toTextWithEntities().entities;
  1159. const auto eIt = ranges::find_if(entities, [&](
  1160. const EntityInText &e) {
  1161. return (e.type() == EntityType::Pre
  1162. || e.type() == EntityType::Code)
  1163. && (from >= e.offset())
  1164. && ((e.offset() + e.length()) >= to);
  1165. });
  1166. if (eIt != entities.end()) {
  1167. from = eIt->offset();
  1168. to = eIt->offset() + eIt->length();
  1169. while (to > 0 && IsSpace(_text.at(to - 1))) {
  1170. --to;
  1171. }
  1172. if (to >= from) {
  1173. return { from, to };
  1174. }
  1175. }
  1176. break;
  1177. }
  1178. if (!IsParagraphSeparator(_text.at(from))) {
  1179. while (from > 0 && !IsParagraphSeparator(_text.at(from - 1))) {
  1180. --from;
  1181. }
  1182. }
  1183. if (to < _text.size()) {
  1184. if (IsParagraphSeparator(_text.at(to))) {
  1185. ++to;
  1186. } else {
  1187. while (to < _text.size() && !IsParagraphSeparator(_text.at(to))) {
  1188. ++to;
  1189. }
  1190. }
  1191. }
  1192. } else if (selectType == TextSelectType::Words) {
  1193. if (!IsWordSeparator(_text.at(from))) {
  1194. while (from > 0 && !IsWordSeparator(_text.at(from - 1))) {
  1195. --from;
  1196. }
  1197. }
  1198. if (to < _text.size()) {
  1199. if (IsWordSeparator(_text.at(to))) {
  1200. ++to;
  1201. } else {
  1202. while (to < _text.size() && !IsWordSeparator(_text.at(to))) {
  1203. ++to;
  1204. }
  1205. }
  1206. }
  1207. }
  1208. }
  1209. return { from, to };
  1210. }
  1211. bool String::isEmpty() const {
  1212. return _blocks.empty() || _blocks[0]->type() == TextBlockType::Skip;
  1213. }
  1214. not_null<ExtendedData*> String::ensureExtended() {
  1215. if (!_extended) {
  1216. _extended = std::make_unique<ExtendedData>();
  1217. }
  1218. return _extended.get();
  1219. }
  1220. not_null<QuotesData*> String::ensureQuotes() {
  1221. const auto extended = ensureExtended();
  1222. if (!extended->quotes) {
  1223. extended->quotes = std::make_unique<QuotesData>();
  1224. }
  1225. return extended->quotes.get();
  1226. }
  1227. uint16 String::blockPosition(
  1228. std::vector<Block>::const_iterator i,
  1229. int fullLengthOverride) const {
  1230. return (i != end(_blocks))
  1231. ? CountPosition(i)
  1232. : (fullLengthOverride >= 0)
  1233. ? uint16(fullLengthOverride)
  1234. : uint16(_text.size());
  1235. }
  1236. uint16 String::blockEnd(
  1237. std::vector<Block>::const_iterator i,
  1238. int fullLengthOverride) const {
  1239. return (i != end(_blocks) && i + 1 != end(_blocks))
  1240. ? CountPosition(i + 1)
  1241. : (fullLengthOverride >= 0)
  1242. ? uint16(fullLengthOverride)
  1243. : uint16(_text.size());
  1244. }
  1245. uint16 String::blockLength(
  1246. std::vector<Block>::const_iterator i,
  1247. int fullLengthOverride) const {
  1248. return (i == end(_blocks))
  1249. ? 0
  1250. : (i + 1 != end(_blocks))
  1251. ? (CountPosition(i + 1) - CountPosition(i))
  1252. : (fullLengthOverride >= 0)
  1253. ? (fullLengthOverride - CountPosition(i))
  1254. : (int(_text.size()) - CountPosition(i));
  1255. }
  1256. QuoteDetails *String::quoteByIndex(int index) const {
  1257. Expects(!index
  1258. || (_extended
  1259. && _extended->quotes
  1260. && index <= _extended->quotes->list.size()));
  1261. return index ? &_extended->quotes->list[index - 1] : nullptr;
  1262. }
  1263. int String::quoteIndex(const AbstractBlock *block) const {
  1264. Expects(!block || block->type() == TextBlockType::Newline);
  1265. return block
  1266. ? static_cast<const NewlineBlock*>(block)->quoteIndex()
  1267. : _startQuoteIndex;
  1268. }
  1269. const style::QuoteStyle &String::quoteStyle(
  1270. not_null<QuoteDetails*> quote) const {
  1271. return quote->pre ? _st->pre : _st->blockquote;
  1272. }
  1273. QMargins String::quotePadding(QuoteDetails *quote) const {
  1274. if (!quote) {
  1275. return {};
  1276. }
  1277. const auto &st = quoteStyle(quote);
  1278. const auto skip = st.verticalSkip;
  1279. const auto top = st.header;
  1280. return st.padding + QMargins(0, top + skip, 0, skip);
  1281. }
  1282. int String::quoteMinWidth(QuoteDetails *quote) const {
  1283. if (!quote) {
  1284. return 0;
  1285. }
  1286. const auto qpadding = quotePadding(quote);
  1287. const auto &qheader = quoteHeaderText(quote);
  1288. const auto &qst = quoteStyle(quote);
  1289. const auto radius = qst.radius;
  1290. const auto header = qst.header;
  1291. const auto outline = qst.outline;
  1292. const auto iconsize = (!qst.icon.empty())
  1293. ? std::max(
  1294. qst.icon.width() + qst.iconPosition.x(),
  1295. qst.icon.height() + qst.iconPosition.y())
  1296. : 0;
  1297. const auto corner = std::max({ header, radius, outline, iconsize });
  1298. const auto top = qpadding.left()
  1299. + (qheader.isEmpty()
  1300. ? 0
  1301. : (_st->font->monospace()->width(qheader)
  1302. + _st->pre.headerPosition.x()))
  1303. + std::max(
  1304. qpadding.right(),
  1305. (!qst.icon.empty()
  1306. ? (qst.iconPosition.x() + qst.icon.width())
  1307. : 0));
  1308. return std::max(top, 2 * corner);
  1309. }
  1310. const QString &String::quoteHeaderText(QuoteDetails *quote) const {
  1311. static const auto kEmptyHeader = QString();
  1312. static const auto kDefaultHeader
  1313. = Integration::Instance().phraseQuoteHeaderCopy();
  1314. return (!quote || !quote->pre)
  1315. ? kEmptyHeader
  1316. : quote->language.isEmpty()
  1317. ? kDefaultHeader
  1318. : quote->language;
  1319. }
  1320. int String::quoteLinesLimit(QuoteDetails *quote) const {
  1321. return (quote && quote->collapsed && !quote->expanded)
  1322. ? kQuoteCollapsedLines
  1323. : -1;
  1324. }
  1325. template <
  1326. typename AppendPartCallback,
  1327. typename ClickHandlerStartCallback,
  1328. typename ClickHandlerFinishCallback,
  1329. typename FlagsChangeCallback>
  1330. void String::enumerateText(
  1331. TextSelection selection,
  1332. AppendPartCallback appendPartCallback,
  1333. ClickHandlerStartCallback clickHandlerStartCallback,
  1334. ClickHandlerFinishCallback clickHandlerFinishCallback,
  1335. FlagsChangeCallback flagsChangeCallback) const {
  1336. if (isEmpty() || selection.empty()) {
  1337. return;
  1338. }
  1339. int linkIndex = 0;
  1340. uint16 linkPosition = 0;
  1341. int quoteIndex = _startQuoteIndex;
  1342. TextBlockFlags flags = {};
  1343. for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
  1344. const auto blockPosition = (i == e)
  1345. ? uint16(_text.size())
  1346. : (*i)->position();
  1347. const auto blockFlags = (i == e) ? TextBlockFlags() : (*i)->flags();
  1348. const auto blockQuoteIndex = (i == e)
  1349. ? 0
  1350. : ((*i)->type() != TextBlockType::Newline)
  1351. ? quoteIndex
  1352. : static_cast<const NewlineBlock*>(i->get())->quoteIndex();
  1353. const auto blockLinkIndex = [&] {
  1354. if (IsMono(blockFlags) || (i == e)) {
  1355. return 0;
  1356. }
  1357. const auto result = (*i)->linkIndex();
  1358. return (result && _extended && _extended->links[result - 1])
  1359. ? result
  1360. : 0;
  1361. }();
  1362. if (blockLinkIndex != linkIndex) {
  1363. if (linkIndex) {
  1364. auto rangeFrom = qMax(selection.from, linkPosition);
  1365. auto rangeTo = qMin(selection.to, blockPosition);
  1366. if (rangeTo > rangeFrom) { // handle click handler
  1367. const auto r = base::StringViewMid(
  1368. _text,
  1369. rangeFrom,
  1370. rangeTo - rangeFrom);
  1371. // Ignore links that are partially copied.
  1372. const auto handler = (linkPosition != rangeFrom
  1373. || blockPosition != rangeTo
  1374. || !_extended)
  1375. ? nullptr
  1376. : _extended->links[linkIndex - 1];
  1377. const auto type = handler
  1378. ? handler->getTextEntity().type
  1379. : EntityType::Invalid;
  1380. clickHandlerFinishCallback(r, handler, type);
  1381. }
  1382. }
  1383. linkIndex = blockLinkIndex;
  1384. if (linkIndex) {
  1385. linkPosition = blockPosition;
  1386. const auto handler = _extended
  1387. ? _extended->links[linkIndex - 1]
  1388. : nullptr;
  1389. clickHandlerStartCallback(handler
  1390. ? handler->getTextEntity().type
  1391. : EntityType::Invalid);
  1392. }
  1393. }
  1394. const auto checkBlockFlags = (blockPosition >= selection.from)
  1395. && (blockPosition <= selection.to);
  1396. if (checkBlockFlags
  1397. && (blockFlags != flags
  1398. || ((flags & TextBlockFlag::Pre)
  1399. && blockQuoteIndex != quoteIndex))) {
  1400. flagsChangeCallback(
  1401. flags,
  1402. quoteIndex,
  1403. blockFlags,
  1404. blockQuoteIndex);
  1405. flags = blockFlags;
  1406. }
  1407. quoteIndex = blockQuoteIndex;
  1408. if (i == e
  1409. || (linkIndex ? linkPosition : blockPosition) >= selection.to) {
  1410. break;
  1411. }
  1412. const auto blockType = (*i)->type();
  1413. if (blockType == TextBlockType::Skip) {
  1414. continue;
  1415. }
  1416. auto rangeFrom = qMax(selection.from, blockPosition);
  1417. auto rangeTo = qMin(
  1418. selection.to,
  1419. uint16(blockPosition + blockLength(i)));
  1420. if (rangeTo > rangeFrom) {
  1421. const auto customEmojiData = (blockType == TextBlockType::CustomEmoji)
  1422. ? static_cast<const CustomEmojiBlock*>(i->get())->custom()->entityData()
  1423. : QString();
  1424. appendPartCallback(
  1425. base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom),
  1426. customEmojiData);
  1427. }
  1428. }
  1429. }
  1430. bool String::hasPersistentAnimation() const {
  1431. return _hasCustomEmoji || hasSpoilers();
  1432. }
  1433. void String::unloadPersistentAnimation() {
  1434. if (_hasCustomEmoji) {
  1435. for (const auto &block : _blocks) {
  1436. const auto raw = block.get();
  1437. if (raw->type() == TextBlockType::CustomEmoji) {
  1438. static_cast<const CustomEmojiBlock*>(raw)->custom()->unload();
  1439. }
  1440. }
  1441. }
  1442. }
  1443. bool String::isOnlyCustomEmoji() const {
  1444. return _isOnlyCustomEmoji;
  1445. }
  1446. OnlyCustomEmoji String::toOnlyCustomEmoji() const {
  1447. if (!_isOnlyCustomEmoji) {
  1448. return {};
  1449. }
  1450. auto result = OnlyCustomEmoji();
  1451. result.lines.emplace_back();
  1452. for (const auto &block : _blocks) {
  1453. const auto raw = block.get();
  1454. if (raw->type() == TextBlockType::CustomEmoji) {
  1455. const auto custom = static_cast<const CustomEmojiBlock*>(raw);
  1456. result.lines.back().push_back({
  1457. .entityData = custom->custom()->entityData(),
  1458. });
  1459. } else if (raw->type() == TextBlockType::Newline) {
  1460. result.lines.emplace_back();
  1461. }
  1462. }
  1463. return result;
  1464. }
  1465. bool String::hasNotEmojiAndSpaces() const {
  1466. return _hasNotEmojiAndSpaces;
  1467. }
  1468. const std::vector<Modification> &String::modifications() const {
  1469. static const auto kEmpty = std::vector<Modification>();
  1470. return _extended ? _extended->modifications : kEmpty;
  1471. }
  1472. QString String::toString(TextSelection selection) const {
  1473. return toText(selection, false, false).rich.text;
  1474. }
  1475. TextWithEntities String::toTextWithEntities(TextSelection selection) const {
  1476. return toText(selection, false, true).rich;
  1477. }
  1478. TextForMimeData String::toTextForMimeData(TextSelection selection) const {
  1479. return toText(selection, true, true);
  1480. }
  1481. TextForMimeData String::toText(
  1482. TextSelection selection,
  1483. bool composeExpanded,
  1484. bool composeEntities) const {
  1485. struct MarkdownTagTracker {
  1486. TextBlockFlags flag = TextBlockFlags();
  1487. EntityType type = EntityType();
  1488. int start = 0;
  1489. };
  1490. auto result = TextForMimeData();
  1491. result.rich.text.reserve(_text.size());
  1492. if (composeExpanded) {
  1493. result.expanded.reserve(_text.size());
  1494. }
  1495. const auto insertEntity = [&](EntityInText &&entity) {
  1496. auto i = result.rich.entities.end();
  1497. while (i != result.rich.entities.begin()) {
  1498. auto j = i;
  1499. if ((--j)->offset() <= entity.offset()) {
  1500. break;
  1501. }
  1502. i = j;
  1503. }
  1504. result.rich.entities.insert(i, std::move(entity));
  1505. };
  1506. using Flag = TextBlockFlag;
  1507. using Flags = TextBlockFlags;
  1508. auto linkStart = 0;
  1509. auto markdownTrackers = composeEntities
  1510. ? std::vector<MarkdownTagTracker>{
  1511. { Flag::Italic, EntityType::Italic },
  1512. { Flag::Bold, EntityType::Bold },
  1513. { Flag::Semibold, EntityType::Semibold },
  1514. { Flag::Underline, EntityType::Underline },
  1515. { Flag::Spoiler, EntityType::Spoiler },
  1516. { Flag::StrikeOut, EntityType::StrikeOut },
  1517. { Flag::Code, EntityType::Code },
  1518. { Flag::Pre, EntityType::Pre },
  1519. { Flag::Blockquote, EntityType::Blockquote },
  1520. } : std::vector<MarkdownTagTracker>();
  1521. const auto flagsChangeCallback = [&](
  1522. Flags oldFlags,
  1523. int oldQuoteIndex,
  1524. Flags newFlags,
  1525. int newQuoteIndex) {
  1526. if (!composeEntities) {
  1527. return;
  1528. }
  1529. for (auto &tracker : markdownTrackers) {
  1530. const auto flag = tracker.flag;
  1531. const auto quoteWithCollapseChanged = (flag == Flag::Blockquote)
  1532. && (oldFlags & flag)
  1533. && (newFlags & flag)
  1534. && (oldQuoteIndex != newQuoteIndex);
  1535. const auto quoteWithLanguageChanged = (flag == Flag::Pre)
  1536. && (oldFlags & flag)
  1537. && (newFlags & flag)
  1538. && (oldQuoteIndex != newQuoteIndex);
  1539. const auto quote = !oldQuoteIndex
  1540. ? nullptr
  1541. : &_extended->quotes->list[oldQuoteIndex - 1];
  1542. const auto data = !quote
  1543. ? QString()
  1544. : quote->pre
  1545. ? quote->language
  1546. : quote->blockquote
  1547. ? (quote->collapsed ? u"1"_q : QString())
  1548. : QString();
  1549. if (((oldFlags & flag) && !(newFlags & flag))
  1550. || quoteWithLanguageChanged
  1551. || quoteWithCollapseChanged) {
  1552. insertEntity({
  1553. tracker.type,
  1554. tracker.start,
  1555. int(result.rich.text.size()) - tracker.start,
  1556. data,
  1557. });
  1558. }
  1559. if (((newFlags & flag) && !(oldFlags & flag))
  1560. || quoteWithLanguageChanged
  1561. || quoteWithCollapseChanged) {
  1562. tracker.start = result.rich.text.size();
  1563. }
  1564. }
  1565. };
  1566. const auto clickHandlerStartCallback = [&](EntityType type) {
  1567. linkStart = result.rich.text.size();
  1568. };
  1569. const auto clickHandlerFinishCallback = [&](
  1570. QStringView inText,
  1571. const ClickHandlerPtr &handler,
  1572. EntityType type) {
  1573. if (!handler || (!composeExpanded && !composeEntities)) {
  1574. return;
  1575. }
  1576. // This logic is duplicated in TextForMimeData::WithExpandedLinks.
  1577. const auto entity = handler->getTextEntity();
  1578. const auto plainUrl = (entity.type == EntityType::Url)
  1579. || (entity.type == EntityType::Email)
  1580. || (entity.type == EntityType::Phone);
  1581. const auto full = plainUrl
  1582. ? QStringView(entity.data).mid(0, entity.data.size())
  1583. : inText;
  1584. const auto customTextLink = (entity.type == EntityType::CustomUrl);
  1585. const auto internalLink = customTextLink
  1586. && entity.data.startsWith(qstr("internal:"));
  1587. if (composeExpanded) {
  1588. const auto sameAsTextLink = customTextLink
  1589. && (entity.data
  1590. == UrlClickHandler::EncodeForOpening(full.toString()));
  1591. if (customTextLink && !internalLink && !sameAsTextLink) {
  1592. const auto &url = entity.data;
  1593. result.expanded.append(qstr(" (")).append(url).append(')');
  1594. }
  1595. }
  1596. if (composeEntities && !internalLink) {
  1597. insertEntity({
  1598. entity.type,
  1599. linkStart,
  1600. int(result.rich.text.size() - linkStart),
  1601. plainUrl ? QString() : entity.data });
  1602. }
  1603. };
  1604. const auto appendPartCallback = [&](
  1605. QStringView part,
  1606. const QString &customEmojiData) {
  1607. result.rich.text += part;
  1608. if (composeExpanded) {
  1609. result.expanded += part;
  1610. }
  1611. if (composeEntities && !customEmojiData.isEmpty()) {
  1612. insertEntity({
  1613. EntityType::CustomEmoji,
  1614. int(result.rich.text.size() - part.size()),
  1615. int(part.size()),
  1616. customEmojiData,
  1617. });
  1618. }
  1619. };
  1620. enumerateText(
  1621. selection,
  1622. appendPartCallback,
  1623. clickHandlerStartCallback,
  1624. clickHandlerFinishCallback,
  1625. flagsChangeCallback);
  1626. if (composeEntities) {
  1627. const auto proj = [](const EntityInText &entity) {
  1628. const auto type = entity.type();
  1629. const auto isUrl = (type == EntityType::Url)
  1630. || (type == EntityType::CustomUrl)
  1631. || (type == EntityType::BotCommand)
  1632. || (type == EntityType::Mention)
  1633. || (type == EntityType::MentionName)
  1634. || (type == EntityType::Hashtag)
  1635. || (type == EntityType::Cashtag);
  1636. return std::pair{ entity.offset(), isUrl ? 0 : 1 };
  1637. };
  1638. const auto pred = [&](const EntityInText &a, const EntityInText &b) {
  1639. return proj(a) < proj(b);
  1640. };
  1641. std::sort(
  1642. result.rich.entities.begin(),
  1643. result.rich.entities.end(),
  1644. pred);
  1645. }
  1646. return result;
  1647. }
  1648. bool String::isIsolatedEmoji() const {
  1649. return _isIsolatedEmoji;
  1650. }
  1651. IsolatedEmoji String::toIsolatedEmoji() const {
  1652. if (!_isIsolatedEmoji) {
  1653. return {};
  1654. }
  1655. auto result = IsolatedEmoji();
  1656. const auto skip = (_blocks.empty()
  1657. || _blocks.back()->type() != TextBlockType::Skip) ? 0 : 1;
  1658. if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) {
  1659. return {};
  1660. }
  1661. auto index = 0;
  1662. for (const auto &block : _blocks) {
  1663. const auto type = block->type();
  1664. if (block->linkIndex()) {
  1665. return {};
  1666. } else if (type == TextBlockType::Emoji) {
  1667. result.items[index++] = block.unsafe<EmojiBlock>().emoji();
  1668. } else if (type == TextBlockType::CustomEmoji) {
  1669. result.items[index++]
  1670. = block.unsafe<CustomEmojiBlock>().custom()->entityData();
  1671. } else if (type != TextBlockType::Skip) {
  1672. return {};
  1673. }
  1674. }
  1675. return result;
  1676. }
  1677. int String::lineHeight() const {
  1678. return _st->lineHeight ? _st->lineHeight : _st->font->height;
  1679. }
  1680. void String::clear() {
  1681. _text.clear();
  1682. _blocks.clear();
  1683. _extended = nullptr;
  1684. _maxWidth = _minHeight = 0;
  1685. _startQuoteIndex = 0;
  1686. _startParagraphLTR = false;
  1687. _startParagraphRTL = false;
  1688. }
  1689. bool IsBad(QChar ch) {
  1690. return (ch.unicode() == 0)
  1691. || (ch.unicode() >= 8232 && ch.unicode() < 8237)
  1692. || (ch.unicode() >= 65024 && ch.unicode() < 65040 && ch.unicode() != 65039)
  1693. || (ch.unicode() >= 127 && ch.unicode() < 160 && ch.unicode() != 156)
  1694. // qt harfbuzz crash see https://github.com/telegramdesktop/tdesktop/issues/4551
  1695. || (Platform::IsMac() && ch.unicode() == 6158);
  1696. }
  1697. bool IsWordSeparator(QChar ch) {
  1698. switch (ch.unicode()) {
  1699. case QChar::Space:
  1700. case QChar::LineFeed:
  1701. case '.':
  1702. case ',':
  1703. case '?':
  1704. case '!':
  1705. case '@':
  1706. case '#':
  1707. case '$':
  1708. case ':':
  1709. case ';':
  1710. case '-':
  1711. case '<':
  1712. case '>':
  1713. case '[':
  1714. case ']':
  1715. case '(':
  1716. case ')':
  1717. case '{':
  1718. case '}':
  1719. case '=':
  1720. case '/':
  1721. case '+':
  1722. case '%':
  1723. case '&':
  1724. case '^':
  1725. case '*':
  1726. case '\'':
  1727. case '"':
  1728. case '`':
  1729. case '~':
  1730. case '|':
  1731. return true;
  1732. default:
  1733. break;
  1734. }
  1735. return false;
  1736. }
  1737. bool IsAlmostLinkEnd(QChar ch) {
  1738. switch (ch.unicode()) {
  1739. case '?':
  1740. case ',':
  1741. case '.':
  1742. case '"':
  1743. case ':':
  1744. case '!':
  1745. case '\'':
  1746. return true;
  1747. default:
  1748. break;
  1749. }
  1750. return false;
  1751. }
  1752. bool IsLinkEnd(QChar ch) {
  1753. return IsBad(ch)
  1754. || IsSpace(ch)
  1755. || IsNewline(ch)
  1756. || ch.isLowSurrogate()
  1757. || ch.isHighSurrogate();
  1758. }
  1759. bool IsNewline(QChar ch) {
  1760. return (ch == QChar::LineFeed)
  1761. || (ch.unicode() == 156);
  1762. }
  1763. bool IsSpace(QChar ch) {
  1764. return ch.isSpace()
  1765. || (ch.unicode() < 32)
  1766. || (ch == QChar::ParagraphSeparator)
  1767. || (ch == QChar::LineSeparator)
  1768. || (ch == QChar::ObjectReplacementCharacter)
  1769. || (ch == QChar::CarriageReturn)
  1770. || (ch == QChar::Tabulation);
  1771. }
  1772. bool IsDiacritic(QChar ch) { // diacritic and variation selectors
  1773. return (ch.category() == QChar::Mark_NonSpacing)
  1774. || (ch.unicode() == 1652)
  1775. || (ch.unicode() >= 64606 && ch.unicode() <= 64611);
  1776. }
  1777. bool IsReplacedBySpace(QChar ch) {
  1778. // Those symbols are replaced by space on the Telegram server,
  1779. // so we replace them as well, for sent / received consistency.
  1780. //
  1781. // \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
  1782. // QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
  1783. // \xcc[\xb3\xbf\x8a] // 819, 831, 778
  1784. // QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a");
  1785. // [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09
  1786. return (/*code >= 0x00 && */ch.unicode() <= 0x02)
  1787. || (ch.unicode() >= 0x07 && ch.unicode() <= 0x09)
  1788. || (ch.unicode() >= 0x0b && ch.unicode() <= 0x1f)
  1789. || (ch.unicode() == 819)
  1790. || (ch.unicode() == 831)
  1791. || (ch.unicode() == 778)
  1792. || (ch.unicode() >= 8232 && ch.unicode() <= 8237);
  1793. }
  1794. bool IsTrimmed(QChar ch) {
  1795. return IsSpace(ch)
  1796. || IsBad(ch)
  1797. || (ch == QChar(8203)); // zero width space
  1798. }
  1799. } // namespace Ui::Text