text_renderer.cpp 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628
  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_renderer.h"
  8. #include "ui/text/text_bidi_algorithm.h"
  9. #include "ui/text/text_block.h"
  10. #include "ui/text/text_extended_data.h"
  11. #include "ui/text/text_stack_engine.h"
  12. #include "ui/text/text_word.h"
  13. #include "styles/style_basic.h"
  14. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  15. #include <private/qharfbuzz_p.h>
  16. #endif // Qt < 6.0.0
  17. namespace Ui::Text {
  18. namespace {
  19. constexpr auto kMaxItemLength = 4096;
  20. void InitTextItemWithScriptItem(QTextItemInt &ti, const QScriptItem &si) {
  21. // explicitly initialize flags so that initFontAttributes can be called
  22. // multiple times on the same TextItem
  23. ti.flags = { };
  24. if (si.analysis.bidiLevel % 2)
  25. ti.flags |= QTextItem::RightToLeft;
  26. ti.ascent = si.ascent;
  27. ti.descent = si.descent;
  28. if (ti.charFormat.hasProperty(QTextFormat::TextUnderlineStyle)) {
  29. ti.underlineStyle = ti.charFormat.underlineStyle();
  30. } else if (ti.charFormat.boolProperty(QTextFormat::FontUnderline)
  31. || ti.f->underline()) {
  32. ti.underlineStyle = QTextCharFormat::SingleUnderline;
  33. }
  34. // compat
  35. if (ti.underlineStyle == QTextCharFormat::SingleUnderline)
  36. ti.flags |= QTextItem::Underline;
  37. if (ti.f->overline() || ti.charFormat.fontOverline())
  38. ti.flags |= QTextItem::Overline;
  39. if (ti.f->strikeOut() || ti.charFormat.fontStrikeOut())
  40. ti.flags |= QTextItem::StrikeOut;
  41. }
  42. void AppendRange(
  43. QVarLengthArray<FixedRange> &ranges,
  44. FixedRange range) {
  45. for (auto i = ranges.begin(); i != ranges.end(); ++i) {
  46. if (range.till < i->from) {
  47. ranges.insert(i, range);
  48. return;
  49. } else if (!Distinct(range, *i)) {
  50. *i = United(*i, range);
  51. for (auto j = i + 1; j != ranges.end(); ++j) {
  52. if (j->from > i->till) {
  53. ranges.erase(i + 1, j);
  54. return;
  55. } else {
  56. *i = United(*i, *j);
  57. }
  58. }
  59. ranges.erase(i + 1, ranges.end());
  60. return;
  61. }
  62. }
  63. ranges.push_back(range);
  64. }
  65. } // namespace
  66. FixedRange Intersected(FixedRange a, FixedRange b) {
  67. return {
  68. .from = std::max(a.from, b.from),
  69. .till = std::min(a.till, b.till),
  70. };
  71. }
  72. bool Intersects(FixedRange a, FixedRange b) {
  73. return (a.till > b.from) && (b.till > a.from);
  74. }
  75. FixedRange United(FixedRange a, FixedRange b) {
  76. return {
  77. .from = std::min(a.from, b.from),
  78. .till = std::max(a.till, b.till),
  79. };
  80. }
  81. bool Distinct(FixedRange a, FixedRange b) {
  82. return (a.till < b.from) || (b.till < a.from);
  83. }
  84. Renderer::Renderer(const Ui::Text::String &t)
  85. : _t(&t)
  86. , _spoiler(_t->_extended ? _t->_extended->spoiler.get() : nullptr) {
  87. }
  88. Renderer::~Renderer() {
  89. restoreAfterElided();
  90. if (_p) {
  91. _p->setPen(_originalPen);
  92. }
  93. }
  94. void Renderer::draw(QPainter &p, const PaintContext &context) {
  95. if (_t->isEmpty()) {
  96. return;
  97. }
  98. _p = &p;
  99. _p->setFont(_t->_st->font);
  100. _palette = context.palette ? context.palette : &st::defaultTextPalette;
  101. _colors = context.colors;
  102. _originalPen = _p->pen();
  103. _originalPenSelected = (_palette->selectFg->c.alphaF() == 0)
  104. ? _originalPen
  105. : _palette->selectFg->p;
  106. _x = _startLeft = context.position.x();
  107. _y = _startTop = context.position.y();
  108. _yFrom = context.clip.isNull() ? 0 : context.clip.y();
  109. _yTo = context.clip.isNull()
  110. ? -1
  111. : (context.clip.y() + context.clip.height());
  112. _geometry = context.geometry.layout
  113. ? context.geometry
  114. : SimpleGeometry(
  115. ((context.useFullWidth || !(context.align & Qt::AlignLeft))
  116. ? context.availableWidth
  117. : std::min(context.availableWidth, _t->maxWidth())),
  118. (context.elisionLines
  119. ? context.elisionLines
  120. : (context.elisionHeight / _t->_st->font->height)),
  121. context.elisionRemoveFromEnd,
  122. context.elisionBreakEverywhere);
  123. _breakEverywhere = _geometry.breakEverywhere;
  124. _spoilerCache = context.spoiler;
  125. _selection = context.selection;
  126. _highlight = context.highlight;
  127. _fullWidthSelection = context.fullWidthSelection;
  128. _align = context.align;
  129. _cachedNow = context.now;
  130. _pausedEmoji = context.paused || context.pausedEmoji;
  131. _pausedSpoiler = context.paused || context.pausedSpoiler;
  132. _spoilerOpacity = _spoiler
  133. ? (1. - _spoiler->revealAnimation.value(
  134. _spoiler->revealed ? 1. : 0.))
  135. : 0.;
  136. _quotePreCache = context.pre;
  137. _quoteBlockquoteCache = context.blockquote;
  138. _elisionMiddle = context.elisionMiddle && (context.elisionLines == 1);
  139. enumerate();
  140. }
  141. void Renderer::enumerate() {
  142. Expects(!_geometry.outElided);
  143. _lineHeight = _t->lineHeight();
  144. _blocksSize = _t->_blocks.size();
  145. _str = _t->_text.unicode();
  146. if (_p) {
  147. const auto clip = _p->hasClipping() ? _p->clipBoundingRect() : QRect();
  148. if (clip.width() > 0 || clip.height() > 0) {
  149. if (_yFrom < clip.y()) {
  150. _yFrom = clip.y();
  151. }
  152. if (_yTo < 0 || _yTo > clip.y() + clip.height()) {
  153. _yTo = clip.y() + clip.height();
  154. }
  155. }
  156. }
  157. if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
  158. initNextParagraph(
  159. _t->_blocks.cbegin(),
  160. _t->_startQuoteIndex,
  161. UnpackParagraphDirection(
  162. _t->_startParagraphLTR,
  163. _t->_startParagraphRTL));
  164. }
  165. _lineHeight = _t->lineHeight();
  166. _fontHeight = _t->_st->font->height;
  167. auto last_rBearing = QFixed(0);
  168. _last_rPadding = QFixed(0);
  169. const auto guard = gsl::finally([&] {
  170. if (_p) {
  171. paintSpoilerRects();
  172. }
  173. if (_highlight) {
  174. composeHighlightPath();
  175. }
  176. });
  177. auto blockIndex = 0;
  178. auto longWordLine = true;
  179. auto lastWordStart = begin(_t->_words);
  180. auto lastWordStart_wLeft = _wLeft;
  181. auto e = end(_t->_words);
  182. for (auto w = begin(_t->_words); w != e; ++w) {
  183. if (w->newline()) {
  184. blockIndex = w->newlineBlockIndex();
  185. const auto qindex = _t->quoteIndex(_t->_blocks[blockIndex].get());
  186. const auto changed = (_quoteIndex != qindex);
  187. const auto hidden = !_quoteLinesLeft;
  188. if (_quoteLinesLeft) {
  189. --_quoteLinesLeft;
  190. }
  191. if (!hidden) {
  192. fillParagraphBg(changed ? _quotePadding.bottom() : 0);
  193. if (!drawLine(w->position(), begin(_t->_blocks) + blockIndex) && !_quoteExpandLinkLookup) {
  194. return;
  195. }
  196. _y += _lineHeight;
  197. }
  198. last_rBearing = 0;
  199. _last_rPadding = w->f_rpadding();
  200. initNextParagraph(
  201. begin(_t->_blocks) + blockIndex + 1,
  202. qindex,
  203. static_cast<const NewlineBlock*>(_t->_blocks[blockIndex].get())->paragraphDirection());
  204. _lineStartPadding = _last_rPadding;
  205. longWordLine = true;
  206. lastWordStart = w + 1;
  207. lastWordStart_wLeft = _wLeft;
  208. continue;
  209. } else if (!_quoteLinesLeft) {
  210. continue;
  211. }
  212. const auto wordEndsHere = !w->unfinished();
  213. auto w__f_width = w->f_width();
  214. const auto w__f_rbearing = w->f_rbearing();
  215. const auto newWidthLeft = _wLeft
  216. - last_rBearing
  217. - (_last_rPadding + w__f_width - w__f_rbearing);
  218. if (newWidthLeft >= 0
  219. || (w->position() == _lineStart && !_elidedLine)) {
  220. last_rBearing = w__f_rbearing;
  221. _last_rPadding = w->f_rpadding();
  222. _wLeft = newWidthLeft;
  223. if (wordEndsHere) {
  224. longWordLine = false;
  225. }
  226. if (wordEndsHere || longWordLine) {
  227. lastWordStart = w + 1;
  228. lastWordStart_wLeft = _wLeft;
  229. }
  230. continue;
  231. }
  232. if (_elidedLine) {
  233. } else if (w != lastWordStart && !_breakEverywhere) {
  234. // word did not fit completely, so we roll back the state to the beginning of this long word
  235. w = lastWordStart;
  236. _wLeft = lastWordStart_wLeft;
  237. w__f_width = w->f_width();
  238. }
  239. const auto lineEnd = !_elidedLine
  240. ? w->position()
  241. : (w + 1 != end(_t->_words))
  242. ? (w + 1)->position()
  243. : int(_t->_text.size());
  244. if (_quoteLinesLeft) {
  245. --_quoteLinesLeft;
  246. }
  247. fillParagraphBg(0);
  248. while (_t->blockPosition(begin(_t->_blocks) + blockIndex + 1) < lineEnd) {
  249. ++blockIndex;
  250. }
  251. if (!drawLine(lineEnd, begin(_t->_blocks) + blockIndex) && !_quoteExpandLinkLookup) {
  252. return;
  253. }
  254. _y += _lineHeight;
  255. _lineStart = w->position();
  256. _lineStartBlock = blockIndex;
  257. initNextLine();
  258. last_rBearing = w->f_rbearing();
  259. _last_rPadding = w->f_rpadding();
  260. _wLeft -= w__f_width - last_rBearing;
  261. longWordLine = !wordEndsHere;
  262. lastWordStart = w + 1;
  263. lastWordStart_wLeft = _wLeft;
  264. }
  265. if (_lineStart < _t->_text.size()) {
  266. if (_quoteLinesLeft) {
  267. --_quoteLinesLeft;
  268. fillParagraphBg(_quotePadding.bottom());
  269. if (!drawLine(_t->_text.size(), end(_t->_blocks))) {
  270. return;
  271. }
  272. }
  273. }
  274. if (!_p && _lookupSymbol) {
  275. _lookupResult.symbol = _t->_text.size();
  276. _lookupResult.afterSymbol = false;
  277. }
  278. }
  279. void Renderer::fillParagraphBg(int paddingBottom) {
  280. if (_quote) {
  281. const auto cutoff = _quote->collapsed
  282. && ((!paddingBottom && !_quoteLinesLeft) // !expanded
  283. || (paddingBottom // expanded
  284. && _quoteLinesLeft + kQuoteCollapsedLines < -1));
  285. if (cutoff) {
  286. paddingBottom = _quotePadding.bottom();
  287. }
  288. const auto &st = _t->quoteStyle(_quote);
  289. const auto skip = st.verticalSkip;
  290. const auto isTop = (_y != _quoteLineTop);
  291. const auto isBottom = (paddingBottom != 0);
  292. const auto left = _startLeft + _quoteShift;
  293. const auto start = _quoteTop + skip;
  294. const auto top = _quoteLineTop + (isTop ? skip : 0);
  295. const auto fill = _y + _lineHeight + paddingBottom - top
  296. - (isBottom ? skip : 0);
  297. const auto rect = QRect(left, top, _startLineWidth, fill);
  298. const auto cache = (!_p || !_quote)
  299. ? nullptr
  300. : _quote->pre
  301. ? _quotePreCache
  302. : _quote->blockquote
  303. ? _quoteBlockquoteCache
  304. : nullptr;
  305. if (cache) {
  306. auto &valid = _quote->pre
  307. ? _quotePreValid
  308. : _quoteBlockquoteValid;
  309. if (!valid) {
  310. valid = true;
  311. ValidateQuotePaintCache(*cache, st);
  312. }
  313. FillQuotePaint(*_p, rect, *cache, st, {
  314. .skippedTop = uint32(top - start),
  315. .skipBottom = !isBottom,
  316. .expandIcon = cutoff && !_quote->expanded,
  317. .collapseIcon = cutoff && _quote->expanded,
  318. });
  319. }
  320. if (cutoff && _quoteExpandLinkLookup
  321. && _lookupY >= start
  322. && _lookupY < _quoteLineTop + _lineHeight + paddingBottom - skip
  323. && _lookupX >= left
  324. && _lookupX < left + _startLineWidth) {
  325. _quoteExpandLinkLookup = false;
  326. _quoteExpandLink = _quote->toggle;
  327. }
  328. if (isTop && st.header > 0) {
  329. if (_p) {
  330. const auto font = _t->_st->font->monospace();
  331. const auto topleft = rect.topLeft();
  332. const auto position = topleft + st.headerPosition;
  333. const auto lbaseline = position + QPoint(0, font->ascent);
  334. _p->setFont(font);
  335. _p->setPen(_palette->monoFg->p);
  336. _p->drawText(lbaseline, _t->quoteHeaderText(_quote));
  337. } else if (_lookupX >= left
  338. && _lookupX < left + _startLineWidth
  339. && _lookupY >= top
  340. && _lookupY < top + st.header) {
  341. if (_lookupLink) {
  342. _lookupResult.link = _quote->copy;
  343. }
  344. if (_lookupSymbol) {
  345. _lookupResult.symbol = _lineStart;
  346. _lookupResult.afterSymbol = false;
  347. }
  348. }
  349. }
  350. }
  351. _quoteLineTop = _y + _lineHeight + paddingBottom;
  352. }
  353. StateResult Renderer::getState(
  354. QPoint point,
  355. GeometryDescriptor geometry,
  356. StateRequest request) {
  357. if (_t->isEmpty() || point.y() < 0) {
  358. return {};
  359. }
  360. _lookupRequest = request;
  361. _lookupX = point.x();
  362. _lookupY = point.y();
  363. _lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol);
  364. _lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink);
  365. if (!_lookupSymbol && _lookupX < 0) {
  366. return {};
  367. }
  368. _geometry = std::move(geometry);
  369. _breakEverywhere = _geometry.breakEverywhere;
  370. _yFrom = _lookupY;
  371. _yTo = _lookupY + 1;
  372. _align = _lookupRequest.align;
  373. enumerate();
  374. if (_quoteExpandLink && !_lookupResult.link) {
  375. _lookupResult.link = _quoteExpandLink;
  376. }
  377. return _lookupResult;
  378. }
  379. crl::time Renderer::now() const {
  380. if (!_cachedNow) {
  381. _cachedNow = crl::now();
  382. }
  383. return _cachedNow;
  384. }
  385. void Renderer::initNextParagraph(
  386. Blocks::const_iterator i,
  387. int16 paragraphIndex,
  388. Qt::LayoutDirection direction) {
  389. _paragraphDirection = (direction == Qt::LayoutDirectionAuto)
  390. ? style::LayoutDirection()
  391. : direction;
  392. _paragraphStartBlock = i;
  393. if (_quoteIndex != paragraphIndex) {
  394. _y += _quotePadding.bottom();
  395. _quoteIndex = paragraphIndex;
  396. _quote = _t->quoteByIndex(paragraphIndex);
  397. _quotePadding = _t->quotePadding(_quote);
  398. _quoteLinesLeft = _t->quoteLinesLimit(_quote);
  399. _quoteTop = _quoteLineTop = _y;
  400. _y += _quotePadding.top();
  401. _quotePadding.setTop(0);
  402. _quoteDirection = _paragraphDirection;
  403. _quoteExpandLinkLookup = _lookupLink
  404. && _quote
  405. && _quote->collapsed;
  406. }
  407. const auto e = _t->_blocks.cend();
  408. if (i == e) {
  409. _lineStart = _paragraphStart = _t->_text.size();
  410. _lineStartBlock = _t->_blocks.size();
  411. _paragraphLength = 0;
  412. } else {
  413. _lineStart = _paragraphStart = (*i)->position();
  414. _lineStartBlock = i - _t->_blocks.cbegin();
  415. for (; i != e; ++i) {
  416. if ((*i)->type() == TextBlockType::Newline) {
  417. break;
  418. }
  419. }
  420. _paragraphLength = ((i == e)
  421. ? _t->_text.size()
  422. : (*i)->position())
  423. - _paragraphStart;
  424. }
  425. _paragraphAnalysis.resize(0);
  426. initNextLine();
  427. }
  428. void Renderer::initNextLine() {
  429. const auto line = _geometry.layout(_lineIndex++);
  430. _x = _startLeft + line.left + _quotePadding.left();
  431. _startLineWidth = line.width;
  432. _quoteShift = 0;
  433. if (_quote && _quote->maxWidth < _startLineWidth) {
  434. const auto delta = _startLineWidth - _quote->maxWidth;
  435. _startLineWidth = _quote->maxWidth;
  436. if (_align & Qt::AlignHCenter) {
  437. _quoteShift = delta / 2;
  438. } else if (((_align & Qt::AlignLeft)
  439. && (_quoteDirection == Qt::RightToLeft))
  440. || ((_align & Qt::AlignRight)
  441. && (_quoteDirection == Qt::LeftToRight))) {
  442. _quoteShift = delta;
  443. }
  444. _x += _quoteShift;
  445. }
  446. _lineWidth = _startLineWidth
  447. - _quotePadding.left()
  448. - _quotePadding.right();
  449. _lineStartPadding = 0;
  450. _wLeft = _lineWidth;
  451. _elidedLine = line.elided;
  452. }
  453. void Renderer::initParagraphBidi() {
  454. if (!_paragraphLength || !_paragraphAnalysis.isEmpty()) {
  455. return;
  456. }
  457. _paragraphAnalysis.resize(_paragraphLength);
  458. BidiAlgorithm bidi(
  459. _str + _paragraphStart,
  460. _paragraphAnalysis.data(),
  461. _paragraphLength,
  462. (_paragraphDirection == Qt::RightToLeft),
  463. _paragraphStartBlock,
  464. _t->_blocks.cend(),
  465. _paragraphStart);
  466. bidi.process();
  467. }
  468. bool Renderer::drawLine(uint16 lineEnd, Blocks::const_iterator blocksEnd) {
  469. _yDelta = (_lineHeight - _fontHeight) / 2;
  470. if (_yTo >= 0 && (_y + _yDelta >= _yTo || _y >= _yTo)) {
  471. return false;
  472. }
  473. if (_y + _yDelta + _fontHeight <= _yFrom) {
  474. if (_lookupSymbol) {
  475. _lookupResult.symbol = (lineEnd > _lineStart) ? (lineEnd - 1) : _lineStart;
  476. _lookupResult.afterSymbol = (lineEnd > _lineStart) ? true : false;
  477. }
  478. return !_elidedLine;
  479. }
  480. // Trimming pending spaces, because they sometimes don't fit on the line.
  481. // They also are not counted in the line width, they're in the right padding.
  482. // Line width is a sum of block / word widths and paddings between them, without trailing one.
  483. auto trimmedLineEnd = lineEnd;
  484. for (; trimmedLineEnd > _lineStart; --trimmedLineEnd) {
  485. auto ch = _t->_text[trimmedLineEnd - 1];
  486. if (ch != QChar::Space && ch != QChar::LineFeed) {
  487. break;
  488. }
  489. }
  490. auto endBlock = (blocksEnd == end(_t->_blocks)) ? nullptr : blocksEnd->get();
  491. if (_elidedLine) {
  492. // If we decided to draw the last line elided only because of the skip block
  493. // that did not fit on this line, we just draw the line till the very end.
  494. // Skip block is ignored in the elided lines, instead "removeFromEnd" is used.
  495. if (endBlock && endBlock->type() == TextBlockType::Skip) {
  496. endBlock = nullptr;
  497. }
  498. if (!endBlock) {
  499. _elidedLine = false;
  500. }
  501. }
  502. if (!_elidedLine && _elisionMiddle) {
  503. _elisionMiddle = false;
  504. }
  505. if (_elisionMiddle) {
  506. trimmedLineEnd = _t->blockEnd(end(_t->_blocks));
  507. }
  508. const auto startBlock = _t->_blocks[_lineStartBlock].get();
  509. const auto extendLeft = (startBlock->position() < _lineStart)
  510. ? qMin(_lineStart - startBlock->position(), 2)
  511. : 0;
  512. _localFrom = _lineStart - extendLeft;
  513. const auto extendedLineEnd = (endBlock && endBlock->position() < trimmedLineEnd && !_elidedLine)
  514. ? qMin(uint16(trimmedLineEnd + 2), _t->blockEnd(blocksEnd))
  515. : trimmedLineEnd;
  516. auto lineText = QString::fromRawData(
  517. _t->_text.constData() + _localFrom,
  518. extendedLineEnd - _localFrom);
  519. auto lineStart = extendLeft;
  520. auto lineLength = trimmedLineEnd - _lineStart;
  521. if (_elidedLine) {
  522. if (_elisionMiddle) {
  523. _paragraphLength = lineLength;
  524. initParagraphBidi();
  525. } else {
  526. initParagraphBidi();
  527. prepareElidedLine(lineText, lineStart, lineLength, endBlock);
  528. }
  529. }
  530. auto x = _x;
  531. if (_elisionMiddle) {
  532. } else if (_align & Qt::AlignHCenter) {
  533. x += (_wLeft / 2).toInt();
  534. } else if (((_align & Qt::AlignLeft)
  535. && (_paragraphDirection == Qt::RightToLeft))
  536. || ((_align & Qt::AlignRight)
  537. && (_paragraphDirection == Qt::LeftToRight))) {
  538. x += _wLeft;
  539. }
  540. if (!_p) {
  541. if (_lookupX < x) {
  542. if (_lookupSymbol) {
  543. if (_paragraphDirection == Qt::RightToLeft) {
  544. _lookupResult.symbol = (lineEnd > _lineStart) ? (lineEnd - 1) : _lineStart;
  545. _lookupResult.afterSymbol = (lineEnd > _lineStart) ? true : false;
  546. //_lookupResult.uponSymbol = ((_lookupX >= _x) && (lineEnd < _t->_text.size()) && (!endBlock || endBlock->type() != TextBlockType::Skip)) ? true : false;
  547. } else {
  548. _lookupResult.symbol = _lineStart;
  549. _lookupResult.afterSymbol = false;
  550. //_lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineStart > 0)) ? true : false;
  551. }
  552. }
  553. if (_lookupLink) {
  554. _lookupResult.link = nullptr;
  555. }
  556. _lookupResult.uponSymbol = false;
  557. return false;
  558. } else if (_lookupX >= x + (_lineWidth - _wLeft)) {
  559. if (_paragraphDirection == Qt::RightToLeft) {
  560. _lookupResult.symbol = _lineStart;
  561. _lookupResult.afterSymbol = false;
  562. //_lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineStart > 0)) ? true : false;
  563. } else {
  564. _lookupResult.symbol = (lineEnd > _lineStart) ? (lineEnd - 1) : _lineStart;
  565. _lookupResult.afterSymbol = (lineEnd > _lineStart) ? true : false;
  566. //_lookupResult.uponSymbol = ((_lookupX < _x + _w) && (lineEnd < _t->_text.size()) && (!endBlock || endBlock->type() != TextBlockType::Skip)) ? true : false;
  567. }
  568. if (_lookupLink) {
  569. _lookupResult.link = nullptr;
  570. }
  571. _lookupResult.uponSymbol = false;
  572. return false;
  573. }
  574. }
  575. if (_fullWidthSelection) {
  576. const auto selectFromStart = (_selection.to > _lineStart)
  577. && (_lineStart > 0)
  578. && (_selection.from <= _lineStart);
  579. const auto selectTillEnd = (_selection.to > trimmedLineEnd)
  580. && (trimmedLineEnd < _t->_text.size())
  581. && (_selection.from <= trimmedLineEnd)
  582. && (!endBlock || endBlock->type() != TextBlockType::Skip);
  583. if ((selectFromStart && _paragraphDirection == Qt::LeftToRight)
  584. || (selectTillEnd && _paragraphDirection == Qt::RightToLeft)) {
  585. if (x > _x) {
  586. fillSelectRange({ _x, x });
  587. }
  588. }
  589. if ((selectTillEnd && _paragraphDirection == Qt::LeftToRight)
  590. || (selectFromStart && _paragraphDirection == Qt::RightToLeft)) {
  591. if (x < _x + _wLeft) {
  592. fillSelectRange({ x + _lineWidth - _wLeft, _x + _lineWidth });
  593. }
  594. }
  595. }
  596. if (trimmedLineEnd == _lineStart && !_elidedLine) {
  597. return true;
  598. }
  599. if (!_elidedLine) {
  600. initParagraphBidi(); // if was not inited
  601. }
  602. _f = _t->_st->font;
  603. auto leftLineLengthLeft = _elisionMiddle
  604. ? (_lineWidth.toReal() - _f->elidew) / 2.
  605. : -1;
  606. auto rightLineLengthLeft = leftLineLengthLeft;
  607. auto engine = StackEngine(
  608. _t,
  609. _localFrom,
  610. lineText,
  611. gsl::span(_paragraphAnalysis).subspan(_localFrom - _paragraphStart),
  612. _lineStartBlock,
  613. _blocksSize);
  614. auto &e = engine.wrapped();
  615. int firstItem = e.findItem(lineStart), lastItem = e.findItem(lineStart + lineLength - 1);
  616. int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0;
  617. if (!nItems) {
  618. return !_elidedLine;
  619. }
  620. int skipIndex = -1;
  621. QVarLengthArray<int> visualOrder(nItems);
  622. QVarLengthArray<uchar> levels(nItems);
  623. QVarLengthArray<std::vector<Block>::const_iterator> blocks(nItems);
  624. for (int i = 0; i < nItems; ++i) {
  625. const auto blockIt = blocks[i] = engine.shapeGetBlock(firstItem + i);
  626. auto &si = e.layoutData->items[firstItem + i];
  627. if ((*blockIt)->type() == TextBlockType::Skip) {
  628. levels[i] = si.analysis.bidiLevel = 0;
  629. skipIndex = i;
  630. } else {
  631. levels[i] = si.analysis.bidiLevel;
  632. }
  633. }
  634. QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
  635. if (style::RightToLeft() && skipIndex == nItems - 1) {
  636. for (auto i = nItems; i > 1;) {
  637. --i;
  638. visualOrder[i] = visualOrder[i - 1];
  639. }
  640. visualOrder[0] = skipIndex;
  641. }
  642. auto textY = _y + _yDelta + _t->_st->font->ascent;
  643. auto emojiY = (_t->_st->font->height - st::emojiSize) / 2;
  644. auto lastLeftToMiddleX = x;
  645. _f = style::font();
  646. for (int i = 0; i < nItems; ++i) {
  647. const auto paintRightToMiddleElision = (leftLineLengthLeft == 0);
  648. const auto item = firstItem + visualOrder[paintRightToMiddleElision
  649. ? (nItems - 1 - i)
  650. : i];
  651. const auto blockIt = blocks[item - firstItem];
  652. const auto block = blockIt->get();
  653. const auto isLastItem = (item == lastItem);
  654. const auto &si = e.layoutData->items.at(item);
  655. const auto rtl = (si.analysis.bidiLevel % 2);
  656. applyBlockProperties(e, block);
  657. if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
  658. const auto _type = block->type();
  659. if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest
  660. if (_elisionMiddle) {
  661. return false;
  662. }
  663. if (_lookupLink) {
  664. if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
  665. if (const auto link = lookupLink(block)) {
  666. _lookupResult.link = link;
  667. }
  668. }
  669. }
  670. if (_type != TextBlockType::Skip) {
  671. _lookupResult.uponSymbol = true;
  672. }
  673. if (_lookupSymbol) {
  674. if (_type == TextBlockType::Skip) {
  675. if (_paragraphDirection == Qt::RightToLeft) {
  676. _lookupResult.symbol = _lineStart;
  677. _lookupResult.afterSymbol = false;
  678. } else {
  679. _lookupResult.symbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart;
  680. _lookupResult.afterSymbol = (trimmedLineEnd > _lineStart) ? true : false;
  681. }
  682. return false;
  683. }
  684. // Emoji with spaces after symbol lookup
  685. auto chFrom = _str + _t->blockPosition(blockIt);
  686. auto chTo = _str + _t->blockEnd(blockIt);
  687. while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) {
  688. --chTo;
  689. }
  690. if (_lookupX < x + (block->objectWidth() / 2)) {
  691. _lookupResult.symbol = ((rtl && chTo > chFrom) ? (chTo - 1) : chFrom) - _str;
  692. _lookupResult.afterSymbol = (rtl && chTo > chFrom) ? true : false;
  693. } else {
  694. _lookupResult.symbol = ((rtl || chTo <= chFrom) ? chFrom : (chTo - 1)) - _str;
  695. _lookupResult.afterSymbol = (rtl || chTo <= chFrom) ? false : true;
  696. }
  697. }
  698. return false;
  699. } else if (_p
  700. && (_type == TextBlockType::Emoji
  701. || _type == TextBlockType::CustomEmoji)) {
  702. if (_elisionMiddle && !paintRightToMiddleElision) {
  703. if (leftLineLengthLeft - si.width.toReal() < 0) {
  704. leftLineLengthLeft = 0;
  705. i = -1;
  706. lastLeftToMiddleX = (x + si.width);
  707. _p->setPen(*_currentPen);
  708. rightLineLengthLeft = std::ceil((x).toReal()) - _x.toReal() - _f->elidew;
  709. } else {
  710. leftLineLengthLeft -= si.width.toReal();
  711. leftLineLengthLeft = std::max(0.01, leftLineLengthLeft);
  712. }
  713. } else if (_elisionMiddle && paintRightToMiddleElision && rightLineLengthLeft) {
  714. if (i == 0) {
  715. x = _x + _lineWidth;
  716. }
  717. if (rightLineLengthLeft - si.width.toReal() < 0) {
  718. rightLineLengthLeft = 0;
  719. i = nItems;
  720. {
  721. _p->setPen(*_currentPen);
  722. const auto bigWidth = x - lastLeftToMiddleX;
  723. const auto smallWidth = _f->elidew;
  724. const auto left = lastLeftToMiddleX;
  725. _p->drawText(
  726. (left + (bigWidth - smallWidth) / 2).toReal(),
  727. textY,
  728. kQEllipsis);
  729. }
  730. continue;
  731. } else {
  732. rightLineLengthLeft -= si.width.toReal();
  733. rightLineLengthLeft = std::max(0.01, rightLineLengthLeft);
  734. }
  735. x -= si.width;
  736. }
  737. const auto fillSelect = _background.selectActiveBlock
  738. ? FixedRange{ x, x + si.width }
  739. : findSelectEmojiRange(
  740. si,
  741. blockIt,
  742. x,
  743. _selection);
  744. fillSelectRange(fillSelect);
  745. if (_highlight) {
  746. pushHighlightRange(findSelectEmojiRange(
  747. si,
  748. blockIt,
  749. x,
  750. _highlight->range));
  751. }
  752. const auto hasSpoiler = _background.spoiler
  753. && (_spoilerOpacity > 0.);
  754. const auto fillSpoiler = hasSpoiler
  755. ? FixedRange{ x, x + si.width }
  756. : FixedRange();
  757. const auto opacity = _p->opacity();
  758. if (!hasSpoiler || _spoilerOpacity < 1.) {
  759. if (hasSpoiler) {
  760. _p->setOpacity(opacity * (1. - _spoilerOpacity));
  761. }
  762. const auto ex = (x + st::emojiPadding).toInt();
  763. const auto ey = _y + _yDelta + emojiY;
  764. if (_type == TextBlockType::Emoji) {
  765. Emoji::Draw(
  766. *_p,
  767. static_cast<const EmojiBlock*>(block)->emoji(),
  768. Emoji::GetSizeNormal(),
  769. ex,
  770. ey);
  771. } else {
  772. const auto custom = static_cast<const CustomEmojiBlock*>(block)->custom();
  773. const auto selected = (fillSelect.from <= x)
  774. && (fillSelect.till > x);
  775. const auto color = (selected
  776. ? _currentPenSelected
  777. : _currentPen)->color();
  778. if (!_customEmojiContext) {
  779. _customEmojiContext = CustomEmoji::Context{
  780. .textColor = color,
  781. .now = now(),
  782. .paused = _pausedEmoji,
  783. };
  784. _customEmojiSkip = (st::emojiSize
  785. - AdjustCustomEmojiSize(st::emojiSize)) / 2;
  786. } else {
  787. _customEmojiContext->textColor = color;
  788. }
  789. _customEmojiContext->position = {
  790. ex + _customEmojiSkip,
  791. ey + _customEmojiSkip,
  792. };
  793. custom->paint(*_p, *_customEmojiContext);
  794. }
  795. if (hasSpoiler) {
  796. _p->setOpacity(opacity);
  797. }
  798. }
  799. if (hasSpoiler) {
  800. // Elided item should be a text item
  801. // with '...' at the end, so this should not be it.
  802. const auto isElidedItem = false;
  803. pushSpoilerRange(
  804. fillSpoiler,
  805. fillSelect,
  806. isElidedItem,
  807. rtl);
  808. }
  809. //} else if (_p && currentBlock->type() == TextBlockSkip) { // debug
  810. // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
  811. }
  812. x += si.width;
  813. if (paintRightToMiddleElision) {
  814. x -= si.width;
  815. }
  816. continue;
  817. }
  818. unsigned short *logClusters = e.logClusters(&si);
  819. QGlyphLayout glyphs = e.shapedGlyphs(&si);
  820. int itemStart = qMax(lineStart, si.position), itemEnd;
  821. int itemLength = e.length(item);
  822. int glyphsStart = logClusters[itemStart - si.position], glyphsEnd;
  823. if (lineStart + lineLength < si.position + itemLength) {
  824. itemEnd = lineStart + lineLength;
  825. glyphsEnd = logClusters[itemEnd - si.position];
  826. } else {
  827. itemEnd = si.position + itemLength;
  828. glyphsEnd = si.num_glyphs;
  829. }
  830. QFixed itemWidth = 0;
  831. for (int g = glyphsStart; g < glyphsEnd; ++g)
  832. itemWidth += glyphs.effectiveAdvance(g);
  833. if (_elisionMiddle && !paintRightToMiddleElision) {
  834. itemWidth = 0;
  835. for (int g = glyphsStart; g < glyphsEnd; ++g) {
  836. const auto adv = glyphs.effectiveAdvance(g);
  837. if (leftLineLengthLeft - adv.toReal() < 0) {
  838. leftLineLengthLeft = 0;
  839. if (lineText.at(g).isSpace()) {
  840. rightLineLengthLeft += _f->spacew;
  841. }
  842. glyphsEnd = g;
  843. i = -1;
  844. lastLeftToMiddleX = (x + itemWidth);
  845. break;
  846. } else {
  847. leftLineLengthLeft -= adv.toReal();
  848. leftLineLengthLeft = std::max(0.01, leftLineLengthLeft);
  849. itemWidth += adv;
  850. }
  851. }
  852. }
  853. if (_elisionMiddle && paintRightToMiddleElision && rightLineLengthLeft) {
  854. itemWidth = 0;
  855. if (i == 0) {
  856. x = _x + _lineWidth;
  857. }
  858. for (int g = glyphsEnd - 1; g >= glyphsStart; --g) {
  859. const auto adv = glyphs.effectiveAdvance(g);
  860. if (rightLineLengthLeft - adv.toReal() < 0) {
  861. rightLineLengthLeft = 0;
  862. glyphsStart = std::min(g + 1, glyphsEnd - 1);
  863. i = nItems;
  864. if (lineText.at(glyphsStart).isSpace()) {
  865. x -= _f->spacew;
  866. }
  867. {
  868. _p->setPen(*_currentPen);
  869. const auto bigWidth = x - itemWidth - lastLeftToMiddleX;
  870. const auto smallWidth = _f->elidew;
  871. const auto left = lastLeftToMiddleX;
  872. _p->drawText(
  873. (left + (bigWidth - smallWidth) / 2).toReal(),
  874. textY,
  875. kQEllipsis);
  876. }
  877. break;
  878. } else {
  879. rightLineLengthLeft -= adv.toReal();
  880. rightLineLengthLeft = std::max(0.01, rightLineLengthLeft);
  881. itemWidth += adv;
  882. }
  883. }
  884. x -= itemWidth;
  885. }
  886. if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest
  887. if (_elisionMiddle) {
  888. return false;
  889. }
  890. if (_lookupLink) {
  891. if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
  892. if (const auto link = lookupLink(block)) {
  893. _lookupResult.link = link;
  894. }
  895. }
  896. }
  897. _lookupResult.uponSymbol = true;
  898. if (_lookupSymbol) {
  899. QFixed tmpx = rtl ? (x + itemWidth) : x;
  900. for (int ch = 0, g, itemL = itemEnd - itemStart; ch < itemL;) {
  901. g = logClusters[itemStart - si.position + ch];
  902. QFixed gwidth = glyphs.effectiveAdvance(g);
  903. // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes
  904. int ch2 = ch + 1;
  905. while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) {
  906. ++ch2;
  907. }
  908. for (int charsCount = (ch2 - ch); ch < ch2; ++ch) {
  909. QFixed shift1 = QFixed(2 * (charsCount - (ch2 - ch)) + 2) * gwidth / QFixed(2 * charsCount),
  910. shift2 = QFixed(2 * (charsCount - (ch2 - ch)) + 1) * gwidth / QFixed(2 * charsCount);
  911. if ((rtl && _lookupX >= tmpx - shift1) ||
  912. (!rtl && _lookupX < tmpx + shift1)) {
  913. _lookupResult.symbol = _localFrom + itemStart + ch;
  914. if ((rtl && _lookupX >= tmpx - shift2) ||
  915. (!rtl && _lookupX < tmpx + shift2)) {
  916. _lookupResult.afterSymbol = false;
  917. } else {
  918. _lookupResult.afterSymbol = true;
  919. }
  920. return false;
  921. }
  922. }
  923. if (rtl) {
  924. tmpx -= gwidth;
  925. } else {
  926. tmpx += gwidth;
  927. }
  928. }
  929. if (itemEnd > itemStart) {
  930. _lookupResult.symbol = _localFrom + itemEnd - 1;
  931. _lookupResult.afterSymbol = true;
  932. } else {
  933. _lookupResult.symbol = _localFrom + itemStart;
  934. _lookupResult.afterSymbol = false;
  935. }
  936. }
  937. return false;
  938. } else if (_p) {
  939. QTextItemInt gf;
  940. gf.glyphs = glyphs.mid(glyphsStart, glyphsEnd - glyphsStart);
  941. gf.f = &e.fnt;
  942. gf.chars = e.layoutData->string.unicode() + itemStart;
  943. gf.num_chars = itemEnd - itemStart;
  944. gf.fontEngine = e.fontEngine(si);
  945. gf.logClusters = logClusters + itemStart - si.position;
  946. gf.width = itemWidth;
  947. gf.justified = false;
  948. InitTextItemWithScriptItem(gf, si);
  949. const auto itemRange = FixedRange{ x, x + itemWidth };
  950. auto selectedRect = QRect();
  951. auto fillSelect = itemRange;
  952. if (!_background.selectActiveBlock) {
  953. fillSelect = findSelectTextRange(
  954. si,
  955. itemStart,
  956. itemEnd,
  957. x,
  958. itemWidth,
  959. gf,
  960. _selection);
  961. const auto from = fillSelect.from.toInt();
  962. selectedRect = QRect(
  963. from,
  964. _y + _yDelta,
  965. fillSelect.till.toInt() - from,
  966. _fontHeight);
  967. }
  968. const auto hasSelected = !fillSelect.empty();
  969. const auto hasNotSelected = (fillSelect.from != itemRange.from)
  970. || (fillSelect.till != itemRange.till);
  971. fillSelectRange(fillSelect);
  972. if (_highlight) {
  973. pushHighlightRange(findSelectTextRange(
  974. si,
  975. itemStart,
  976. itemEnd,
  977. x,
  978. itemWidth,
  979. gf,
  980. _highlight->range));
  981. }
  982. const auto hasSpoiler = _background.spoiler
  983. && (_spoilerOpacity > 0.);
  984. const auto opacity = _p->opacity();
  985. const auto isElidedBlock = _indexOfElidedBlock
  986. == int(blockIt - begin(_t->_blocks));
  987. const auto isElidedItem = isElidedBlock && isLastItem;
  988. const auto complexClipping = hasSpoiler
  989. && isElidedItem
  990. && (_spoilerOpacity == 1.);
  991. if (!hasSpoiler || (_spoilerOpacity < 1.) || isElidedItem) {
  992. const auto complexClippingEnabled = complexClipping
  993. && _p->hasClipping();
  994. const auto complexClippingRegion = complexClipping
  995. ? _p->clipRegion()
  996. : QRegion();
  997. if (complexClipping) {
  998. const auto elided = isElidedBlock ? _f->elidew : 0;
  999. _p->setClipRect(
  1000. QRect(
  1001. (rtl
  1002. ? x.toInt()
  1003. : (x + itemWidth).toInt() - elided),
  1004. _y - _lineHeight,
  1005. elided,
  1006. _y + 2 * _lineHeight),
  1007. Qt::IntersectClip);
  1008. } else if (hasSpoiler && !isElidedItem) {
  1009. _p->setOpacity(opacity * (1. - _spoilerOpacity));
  1010. }
  1011. if (Q_UNLIKELY(hasSelected)) {
  1012. if (Q_UNLIKELY(hasNotSelected)) {
  1013. // There is a bug in retina QPainter clipping stack.
  1014. // You can see glitches in rendering in such text:
  1015. // aA
  1016. // Aa
  1017. // Where selection is both 'A'-s.
  1018. // I can't debug it right now, this is a workaround.
  1019. #ifdef Q_OS_MAC
  1020. _p->save();
  1021. #endif // Q_OS_MAC
  1022. const auto clippingEnabled = _p->hasClipping();
  1023. const auto clippingRegion = _p->clipRegion();
  1024. _p->setClipRect(selectedRect, Qt::IntersectClip);
  1025. _p->setPen(*_currentPenSelected);
  1026. _p->drawTextItem(QPointF(x.toReal(), textY), gf);
  1027. const auto externalClipping = clippingEnabled
  1028. ? clippingRegion
  1029. : QRegion(QRect(
  1030. (_x - _lineWidth).toInt(),
  1031. _y - _lineHeight,
  1032. (_x + 2 * _lineWidth).toInt(),
  1033. _y + 2 * _lineHeight));
  1034. _p->setClipRegion(externalClipping - selectedRect);
  1035. _p->setPen(*_currentPen);
  1036. _p->drawTextItem(QPointF(x.toReal(), textY), gf);
  1037. #ifdef Q_OS_MAC
  1038. _p->restore();
  1039. #else // Q_OS_MAC
  1040. if (clippingEnabled) {
  1041. _p->setClipRegion(clippingRegion);
  1042. } else {
  1043. _p->setClipping(false);
  1044. }
  1045. #endif // Q_OS_MAC
  1046. } else {
  1047. _p->setPen(*_currentPenSelected);
  1048. _p->drawTextItem(QPointF(x.toReal(), textY), gf);
  1049. }
  1050. } else {
  1051. _p->setPen(*_currentPen);
  1052. _p->drawTextItem(QPointF(x.toReal(), textY), gf);
  1053. }
  1054. if (complexClipping) {
  1055. if (complexClippingEnabled) {
  1056. _p->setClipRegion(complexClippingRegion);
  1057. } else {
  1058. _p->setClipping(false);
  1059. }
  1060. } else if (hasSpoiler && !isElidedItem) {
  1061. _p->setOpacity(opacity);
  1062. }
  1063. }
  1064. if (hasSpoiler) {
  1065. pushSpoilerRange(
  1066. itemRange,
  1067. fillSelect,
  1068. isElidedItem,
  1069. rtl);
  1070. }
  1071. }
  1072. x += itemWidth;
  1073. if (paintRightToMiddleElision) {
  1074. x -= itemWidth;
  1075. }
  1076. }
  1077. fillRectsFromRanges();
  1078. return !_elidedLine;
  1079. }
  1080. FixedRange Renderer::findSelectEmojiRange(
  1081. const QScriptItem &si,
  1082. std::vector<Block>::const_iterator blockIt,
  1083. QFixed x,
  1084. TextSelection selection) const {
  1085. if (_localFrom + si.position >= selection.to) {
  1086. return {};
  1087. }
  1088. auto chFrom = _str + _t->blockPosition(blockIt);
  1089. auto chTo = _str + _t->blockEnd(blockIt);
  1090. while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) {
  1091. --chTo;
  1092. }
  1093. if (_localFrom + si.position >= selection.from) {
  1094. return { x, x + si.width };
  1095. }
  1096. return {};
  1097. }
  1098. FixedRange Renderer::findSelectTextRange(
  1099. const QScriptItem &si,
  1100. int itemStart,
  1101. int itemEnd,
  1102. QFixed x,
  1103. QFixed itemWidth,
  1104. const QTextItemInt &gf,
  1105. TextSelection selection) const {
  1106. if (_localFrom + itemStart >= selection.to
  1107. || _localFrom + itemEnd <= selection.from) {
  1108. return {};
  1109. }
  1110. auto selX = x;
  1111. auto selWidth = itemWidth;
  1112. const auto rtl = (si.analysis.bidiLevel % 2);
  1113. if (_localFrom + itemStart < selection.from
  1114. || _localFrom + itemEnd > selection.to) {
  1115. selWidth = 0;
  1116. const auto itemL = itemEnd - itemStart;
  1117. const auto selStart = std::max(
  1118. selection.from - (_localFrom + itemStart),
  1119. 0);
  1120. const auto selEnd = std::min(
  1121. selection.to - (_localFrom + itemStart),
  1122. itemL);
  1123. const auto lczero = gf.logClusters[0];
  1124. for (int ch = 0, g; ch < selEnd;) {
  1125. g = gf.logClusters[ch];
  1126. const auto gwidth = gf.glyphs.effectiveAdvance(g - lczero);
  1127. // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes
  1128. int ch2 = ch + 1;
  1129. while ((ch2 < itemL) && (g == gf.logClusters[ch2])) {
  1130. ++ch2;
  1131. }
  1132. if (ch2 <= selStart) {
  1133. selX += gwidth;
  1134. } else if (ch >= selStart && ch2 <= selEnd) {
  1135. selWidth += gwidth;
  1136. } else {
  1137. int sStart = ch, sEnd = ch2;
  1138. if (ch < selStart) {
  1139. sStart = selStart;
  1140. selX += QFixed(sStart - ch) * gwidth / QFixed(ch2 - ch);
  1141. }
  1142. if (ch2 >= selEnd) {
  1143. sEnd = selEnd;
  1144. selWidth += QFixed(sEnd - sStart) * gwidth / QFixed(ch2 - ch);
  1145. break;
  1146. }
  1147. selWidth += QFixed(sEnd - sStart) * gwidth / QFixed(ch2 - ch);
  1148. }
  1149. ch = ch2;
  1150. }
  1151. }
  1152. if (rtl) selX = x + itemWidth - (selX - x) - selWidth;
  1153. return { selX, selX + selWidth };
  1154. }
  1155. void Renderer::fillSelectRange(FixedRange range) {
  1156. if (range.empty()) {
  1157. return;
  1158. }
  1159. const auto left = range.from.toInt();
  1160. const auto width = range.till.toInt() - left;
  1161. _p->fillRect(left, _y + _yDelta, width, _fontHeight, _palette->selectBg);
  1162. }
  1163. void Renderer::pushHighlightRange(FixedRange range) {
  1164. if (range.empty()) {
  1165. return;
  1166. }
  1167. AppendRange(_highlightRanges, range);
  1168. }
  1169. void Renderer::pushSpoilerRange(
  1170. FixedRange range,
  1171. FixedRange selected,
  1172. bool isElidedItem,
  1173. bool rtl) {
  1174. if (!_background.spoiler || !_spoiler) {
  1175. return;
  1176. } else if (isElidedItem) {
  1177. const auto elided = _f->elidew;
  1178. if (rtl) {
  1179. range.from += elided;
  1180. } else {
  1181. range.till -= elided;
  1182. }
  1183. }
  1184. if (range.empty()) {
  1185. return;
  1186. } else if (selected.empty() || !Intersects(range, selected)) {
  1187. AppendRange(_spoilerRanges, range);
  1188. } else {
  1189. AppendRange(_spoilerRanges, { range.from, selected.from });
  1190. AppendRange(_spoilerSelectedRanges, Intersected(range, selected));
  1191. AppendRange(_spoilerRanges, { selected.till, range.till });
  1192. }
  1193. }
  1194. void Renderer::fillRectsFromRanges() {
  1195. fillRectsFromRanges(_spoilerRects, _spoilerRanges);
  1196. fillRectsFromRanges(_spoilerSelectedRects, _spoilerSelectedRanges);
  1197. fillRectsFromRanges(_highlightRects, _highlightRanges);
  1198. }
  1199. void Renderer::fillRectsFromRanges(
  1200. QVarLengthArray<QRect, kSpoilersRectsSize> &rects,
  1201. QVarLengthArray<FixedRange> &ranges) {
  1202. if (ranges.empty()) {
  1203. return;
  1204. }
  1205. auto lastTill = ranges.front().from.toInt() - 1;
  1206. const auto y = _y + _yDelta;
  1207. for (const auto &range : ranges) {
  1208. auto from = range.from.toInt();
  1209. auto till = range.till.toInt();
  1210. if (from <= lastTill) {
  1211. auto &last = rects.back();
  1212. from = std::min(from, last.x());
  1213. till = std::max(till, last.x() + last.width());
  1214. last = { from, y, till - from, _fontHeight };
  1215. } else {
  1216. rects.push_back({ from, y, till - from, _fontHeight });
  1217. }
  1218. lastTill = till;
  1219. }
  1220. ranges.clear();
  1221. }
  1222. void Renderer::paintSpoilerRects() {
  1223. Expects(_p != nullptr);
  1224. if (!_spoiler) {
  1225. return;
  1226. }
  1227. const auto opacity = _p->opacity();
  1228. if (_spoilerOpacity < 1.) {
  1229. _p->setOpacity(opacity * _spoilerOpacity);
  1230. }
  1231. const auto index = _spoiler->animation.index(now(), _pausedSpoiler);
  1232. paintSpoilerRects(
  1233. _spoilerRects,
  1234. _palette->spoilerFg,
  1235. index);
  1236. paintSpoilerRects(
  1237. _spoilerSelectedRects,
  1238. _palette->selectSpoilerFg,
  1239. index);
  1240. if (_spoilerOpacity < 1.) {
  1241. _p->setOpacity(opacity);
  1242. }
  1243. }
  1244. void Renderer::paintSpoilerRects(
  1245. const QVarLengthArray<QRect, kSpoilersRectsSize> &rects,
  1246. const style::color &color,
  1247. int index) {
  1248. if (rects.empty()) {
  1249. return;
  1250. }
  1251. const auto frame = _spoilerCache->lookup(color->c)->frame(index);
  1252. if (_spoilerCache) {
  1253. for (const auto &rect : rects) {
  1254. Ui::FillSpoilerRect(*_p, rect, frame, -rect.topLeft());
  1255. }
  1256. } else {
  1257. // Show forgotten spoiler context part.
  1258. for (const auto &rect : rects) {
  1259. _p->fillRect(rect, Qt::red);
  1260. }
  1261. }
  1262. }
  1263. void Renderer::composeHighlightPath() {
  1264. Expects(_highlight != nullptr);
  1265. Expects(_highlight->outPath != nullptr);
  1266. if (_highlight->interpolateProgress >= 1.) {
  1267. _highlight->outPath->addRect(_highlight->interpolateTo);
  1268. } else if (_highlight->interpolateProgress <= 0.) {
  1269. for (const auto &rect : _highlightRects) {
  1270. _highlight->outPath->addRect(rect);
  1271. }
  1272. } else {
  1273. const auto to = _highlight->interpolateTo;
  1274. const auto progress = _highlight->interpolateProgress;
  1275. const auto lerp = [=](int from, int to) {
  1276. return from + (to - from) * progress;
  1277. };
  1278. for (const auto &rect : _highlightRects) {
  1279. _highlight->outPath->addRect(
  1280. lerp(rect.x(), to.x()),
  1281. lerp(rect.y(), to.y()),
  1282. lerp(rect.width(), to.width()),
  1283. lerp(rect.height(), to.height()));
  1284. }
  1285. }
  1286. }
  1287. const AbstractBlock *Renderer::markBlockForElisionGetEnd(int blockIndex) {
  1288. if (_elideSavedBlock) {
  1289. restoreAfterElided();
  1290. }
  1291. if (_t->_blocks[blockIndex]->type() != TextBlockType::Text) {
  1292. _elideSavedIndex = blockIndex;
  1293. auto mutableText = const_cast<String*>(_t);
  1294. _elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
  1295. mutableText->_blocks[blockIndex] = Block::Text({
  1296. .position = (*_elideSavedBlock)->position(),
  1297. .flags = (*_elideSavedBlock)->flags(),
  1298. .linkIndex = (*_elideSavedBlock)->linkIndex(),
  1299. .colorIndex = (*_elideSavedBlock)->colorIndex(),
  1300. });
  1301. }
  1302. _indexOfElidedBlock = blockIndex;
  1303. _blocksSize = blockIndex + 1;
  1304. return (blockIndex + 1 < _t->_blocks.size())
  1305. ? _t->_blocks[blockIndex + 1].get()
  1306. : nullptr;
  1307. }
  1308. void Renderer::setElideBidi(int elideStart) {
  1309. const auto elideLength = kQEllipsis.size();
  1310. const auto newParLength = elideStart + elideLength - _paragraphStart;
  1311. if (newParLength > _paragraphAnalysis.size()) {
  1312. _paragraphAnalysis.resize(newParLength);
  1313. }
  1314. const auto bidiLevel = (newParLength > elideLength)
  1315. ? _paragraphAnalysis[newParLength - elideLength - 1].bidiLevel
  1316. : (_paragraphDirection == Qt::RightToLeft)
  1317. ? 1
  1318. : 0;
  1319. for (auto i = elideLength; i > 0; --i) {
  1320. _paragraphAnalysis[newParLength - i].bidiLevel = bidiLevel;
  1321. }
  1322. }
  1323. void Renderer::prepareElidedLine(
  1324. QString &lineText,
  1325. int lineStart,
  1326. int &lineLength,
  1327. const AbstractBlock *&endBlock,
  1328. int recursed) {
  1329. _f = _t->_st->font;
  1330. auto engine = StackEngine(
  1331. _t,
  1332. _localFrom,
  1333. lineText,
  1334. gsl::span(_paragraphAnalysis).subspan(_localFrom - _paragraphStart),
  1335. _lineStartBlock,
  1336. _blocksSize);
  1337. auto &e = engine.wrapped();
  1338. _wLeft = _lineWidth
  1339. - _lineStartPadding
  1340. - _quotePadding.left()
  1341. - _quotePadding.right();
  1342. const auto firstItem = e.findItem(lineStart);
  1343. const auto lastItem = e.findItem(lineStart + lineLength - 1);
  1344. const auto nItems = (firstItem >= 0 && lastItem >= firstItem)
  1345. ? (lastItem - firstItem + 1)
  1346. : 0;
  1347. auto elisionWidth = _t->_st->font->elidew;
  1348. for (auto i = 0; i < nItems; ++i) {
  1349. const auto blockIt = engine.shapeGetBlock(firstItem + i);
  1350. const auto block = blockIt->get();
  1351. const auto blockIndex = int(blockIt - begin(_t->_blocks));
  1352. const auto nextBlock = (blockIndex + 1 < _blocksSize)
  1353. ? _t->_blocks[blockIndex + 1].get()
  1354. : nullptr;
  1355. const auto font = WithFlags(_t->_st->font, block->flags());
  1356. elisionWidth = font->elidew;
  1357. auto &si = e.layoutData->items[firstItem + i];
  1358. const auto _type = block->type();
  1359. if (_type == TextBlockType::Emoji
  1360. || _type == TextBlockType::CustomEmoji
  1361. || _type == TextBlockType::Skip
  1362. || _type == TextBlockType::Newline) {
  1363. if (_wLeft < elisionWidth + si.width) {
  1364. _wLeft -= elisionWidth;
  1365. prepareElisionAt(lineText, lineLength, block->position());
  1366. endBlock = markBlockForElisionGetEnd(blockIndex);
  1367. return;
  1368. }
  1369. _wLeft -= si.width;
  1370. } else if (_type == TextBlockType::Text) {
  1371. unsigned short *logClusters = e.logClusters(&si);
  1372. QGlyphLayout glyphs = e.shapedGlyphs(&si);
  1373. int itemStart = qMax(lineStart, si.position), itemEnd;
  1374. int itemLength = e.length(firstItem + i);
  1375. int glyphsStart = logClusters[itemStart - si.position], glyphsEnd;
  1376. if (lineStart + lineLength < si.position + itemLength) {
  1377. itemEnd = lineStart + lineLength;
  1378. glyphsEnd = logClusters[itemEnd - si.position];
  1379. } else {
  1380. itemEnd = si.position + itemLength;
  1381. glyphsEnd = si.num_glyphs;
  1382. }
  1383. for (auto g = glyphsStart; g < glyphsEnd; ++g) {
  1384. auto adv = glyphs.effectiveAdvance(g);
  1385. if (_wLeft < elisionWidth + adv) {
  1386. _wLeft -= elisionWidth;
  1387. auto pos = itemStart;
  1388. while (pos < itemEnd && logClusters[pos - si.position] < g) {
  1389. ++pos;
  1390. }
  1391. if (lineText.size() <= pos || recursed > 3) {
  1392. prepareElisionAt(lineText, lineLength, _localFrom + pos);
  1393. endBlock = markBlockForElisionGetEnd(blockIndex);
  1394. return;
  1395. }
  1396. lineText = lineText.mid(0, pos);
  1397. lineLength = _localFrom + pos - _lineStart;
  1398. _blocksSize = blockIndex + 1;
  1399. endBlock = nextBlock;
  1400. prepareElidedLine(lineText, lineStart, lineLength, endBlock, recursed + 1);
  1401. return;
  1402. } else {
  1403. _wLeft -= adv;
  1404. }
  1405. }
  1406. }
  1407. }
  1408. _wLeft -= elisionWidth;
  1409. const auto elideStart = _localFrom + lineText.size();
  1410. auto blockIndex = engine.blockIndex(lineText.size() - 1);
  1411. for (; blockIndex + 1 < _blocksSize && _t->_blocks[blockIndex]->position() < elideStart; ++blockIndex) {
  1412. }
  1413. prepareElisionAt(lineText, lineLength, elideStart);
  1414. if (recursed) {
  1415. _indexOfElidedBlock = blockIndex;
  1416. } else {
  1417. endBlock = markBlockForElisionGetEnd(blockIndex);
  1418. }
  1419. }
  1420. void Renderer::prepareElisionAt(
  1421. QString &lineText,
  1422. int &lineLength,
  1423. uint16 position) {
  1424. lineText = lineText.mid(0, position - _localFrom) + kQEllipsis;
  1425. lineLength = position + kQEllipsis.size() - _lineStart;
  1426. _selection.to = qMin(_selection.to, position);
  1427. setElideBidi(position);
  1428. }
  1429. void Renderer::restoreAfterElided() {
  1430. if (_elideSavedBlock) {
  1431. const_cast<String*>(_t)->_blocks[_elideSavedIndex] = std::move(*_elideSavedBlock);
  1432. }
  1433. }
  1434. void Renderer::applyBlockProperties(
  1435. QTextEngine &e,
  1436. not_null<const AbstractBlock*> block) {
  1437. const auto flags = block->flags();
  1438. const auto usedFont = [&] {
  1439. if (const auto index = block->linkIndex()) {
  1440. const auto underline = _t->_st->linkUnderline;
  1441. const auto underlined = (underline == st::kLinkUnderlineNever)
  1442. ? false
  1443. : (underline == st::kLinkUnderlineActive)
  1444. ? ((_palette && _palette->linkAlwaysActive)
  1445. || ClickHandler::showAsActive(_t->_extended
  1446. ? _t->_extended->links[index - 1]
  1447. : nullptr))
  1448. : true;
  1449. return underlined ? _t->_st->font->underline() : _t->_st->font;
  1450. }
  1451. return _t->_st->font;
  1452. }();
  1453. const auto newFont = WithFlags(usedFont, flags);
  1454. if (_f != newFont) {
  1455. _f = newFont;
  1456. const auto use = (_f->family() == _t->_st->font->family())
  1457. ? WithFlags(_t->_st->font, flags, _f->flags())
  1458. : _f;
  1459. e.fnt = use->f;
  1460. e.resetFontEngineCache();
  1461. }
  1462. if (_p) {
  1463. const auto flags = block->flags();
  1464. const auto isMono = IsMono(flags);
  1465. _background = {};
  1466. if ((flags & TextBlockFlag::Spoiler) && _spoiler) {
  1467. _background.spoiler = true;
  1468. }
  1469. if (isMono
  1470. && block->linkIndex()
  1471. && (!_background.spoiler || _spoiler->revealed)) {
  1472. const auto pressed = ClickHandler::showAsPressed(_t->_extended
  1473. ? _t->_extended->links[block->linkIndex() - 1]
  1474. : nullptr);
  1475. _background.selectActiveBlock = pressed;
  1476. }
  1477. if (const auto color = block->colorIndex()) {
  1478. if (color == 1) {
  1479. if (_quote && _quote->blockquote && _quoteBlockquoteCache) {
  1480. _quoteLinkPenOverride = QPen(_quoteBlockquoteCache->outlines[0]);
  1481. _currentPen = &_quoteLinkPenOverride;
  1482. _currentPenSelected = &_quoteLinkPenOverride;
  1483. } else {
  1484. _currentPen = &_palette->linkFg->p;
  1485. _currentPenSelected = &_palette->selectLinkFg->p;
  1486. }
  1487. } else if (color - 1 <= _colors.size()) {
  1488. _currentPen = _colors[color - 2].pen;
  1489. _currentPenSelected = _colors[color - 2].penSelected;
  1490. } else {
  1491. _currentPen = &_originalPen;
  1492. _currentPenSelected = &_originalPenSelected;
  1493. }
  1494. } else if (isMono) {
  1495. _currentPen = &_palette->monoFg->p;
  1496. _currentPenSelected = &_palette->selectMonoFg->p;
  1497. } else if (block->linkIndex()) {
  1498. if (_quote && _quote->blockquote && _quoteBlockquoteCache) {
  1499. _quoteLinkPenOverride = QPen(_quoteBlockquoteCache->outlines[0]);
  1500. _currentPen = &_quoteLinkPenOverride;
  1501. _currentPenSelected = &_quoteLinkPenOverride;
  1502. } else {
  1503. _currentPen = &_palette->linkFg->p;
  1504. _currentPenSelected = &_palette->selectLinkFg->p;
  1505. }
  1506. } else {
  1507. _currentPen = &_originalPen;
  1508. _currentPenSelected = &_originalPenSelected;
  1509. }
  1510. }
  1511. }
  1512. ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
  1513. const auto spoilerLink = (_spoiler
  1514. && !_spoiler->revealed
  1515. && (block->flags() & TextBlockFlag::Spoiler))
  1516. ? _spoiler->link
  1517. : ClickHandlerPtr();
  1518. return (spoilerLink || !block->linkIndex() || !_t->_extended)
  1519. ? spoilerLink
  1520. : _t->_extended->links[block->linkIndex() - 1];
  1521. }
  1522. } // namespace Ui::Text