text_stack_engine.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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_stack_engine.h"
  8. #include "ui/text/text_block.h"
  9. #include "styles/style_basic.h"
  10. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  11. #include <private/qharfbuzz_p.h>
  12. #endif // Qt < 6.0.0
  13. namespace Ui::Text {
  14. namespace {
  15. constexpr auto kMaxItemLength = 4096;
  16. } // namespace
  17. StackEngine::StackEngine(
  18. not_null<const String*> t,
  19. gsl::span<QScriptAnalysis> analysis,
  20. int from,
  21. int till,
  22. int blockIndexHint)
  23. : StackEngine(
  24. t,
  25. from,
  26. ((from > 0 || (till >= 0 && till < t->_text.size()))
  27. ? QString::fromRawData(
  28. t->_text.constData() + from,
  29. ((till < 0) ? int(t->_text.size()) : till) - from)
  30. : t->_text),
  31. analysis,
  32. blockIndexHint) {
  33. }
  34. StackEngine::StackEngine(
  35. not_null<const String*> t,
  36. int offset,
  37. const QString &text,
  38. gsl::span<QScriptAnalysis> analysis,
  39. int blockIndexHint,
  40. int blockIndexLimit)
  41. : _t(t)
  42. , _text(text)
  43. , _analysis(analysis.data())
  44. , _offset(offset)
  45. , _positionEnd(_offset + _text.size())
  46. , _font(_t->_st->font)
  47. , _engine(_text, _font->f)
  48. , _tBlocks(_t->_blocks)
  49. , _bStart(begin(_tBlocks) + blockIndexHint)
  50. , _bEnd((blockIndexLimit >= 0)
  51. ? (begin(_tBlocks) + blockIndexLimit)
  52. : end(_tBlocks))
  53. , _bCached(_bStart) {
  54. Expects(analysis.size() >= _text.size());
  55. _engine.validate();
  56. itemize();
  57. }
  58. std::vector<Block>::const_iterator StackEngine::adjustBlock(
  59. int offset) const {
  60. Expects(offset < _positionEnd);
  61. if (blockPosition(_bCached) > offset) {
  62. _bCached = begin(_tBlocks);
  63. }
  64. Assert(_bCached != end(_tBlocks));
  65. for (auto i = _bCached + 1; blockPosition(i) <= offset; ++i) {
  66. _bCached = i;
  67. }
  68. return _bCached;
  69. }
  70. int StackEngine::blockPosition(std::vector<Block>::const_iterator i) const {
  71. return (i == _bEnd) ? _positionEnd : (*i)->position();
  72. }
  73. int StackEngine::blockEnd(std::vector<Block>::const_iterator i) const {
  74. return (i == _bEnd) ? _positionEnd : blockPosition(i + 1);
  75. }
  76. void StackEngine::itemize() {
  77. const auto layoutData = _engine.layoutData;
  78. if (layoutData->items.size()) {
  79. return;
  80. }
  81. const auto length = layoutData->string.length();
  82. if (!length) {
  83. return;
  84. }
  85. _bStart = adjustBlock(_offset);
  86. const auto chars = _engine.layoutData->string.constData();
  87. {
  88. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  89. QUnicodeTools::ScriptItemArray scriptItems;
  90. QUnicodeTools::initScripts(_engine.layoutData->string, &scriptItems);
  91. for (int i = 0; i < scriptItems.length(); ++i) {
  92. const auto &item = scriptItems.at(i);
  93. int end = i < scriptItems.length() - 1 ? scriptItems.at(i + 1).position : length;
  94. for (int j = item.position; j < end; ++j)
  95. _analysis[j].script = item.script;
  96. }
  97. #else // Qt >= 6.0.0
  98. QVarLengthArray<uchar> scripts(length);
  99. QUnicodeTools::initScripts(reinterpret_cast<const ushort*>(chars), length, scripts.data());
  100. for (int i = 0; i < length; ++i)
  101. _analysis[i].script = scripts.at(i);
  102. #endif // Qt < 6.0.0
  103. }
  104. // Override script and flags for emoji and custom emoji blocks.
  105. const auto end = _offset + length;
  106. for (auto block = _bStart; blockPosition(block) < end; ++block) {
  107. const auto type = (*block)->type();
  108. const auto from = std::max(_offset, int(blockPosition(block)));
  109. const auto till = std::min(int(end), int(blockEnd(block)));
  110. if (till > from) {
  111. if (type == TextBlockType::Emoji
  112. || type == TextBlockType::CustomEmoji
  113. || type == TextBlockType::Skip) {
  114. for (auto i = from - _offset, count = till - _offset; i != count; ++i) {
  115. _analysis[i].script = QChar::Script_Common;
  116. _analysis[i].flags = (chars[i] == QChar::Space)
  117. ? QScriptAnalysis::None
  118. : QScriptAnalysis::Object;
  119. }
  120. } else {
  121. for (auto i = from - _offset, count = till - _offset; i != count; ++i) {
  122. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  123. _analysis[i].script = hbscript_to_script(script_to_hbscript(_analysis[i].script)); // retain the old behavior
  124. #endif // Qt < 6.0.0
  125. if (chars[i] == QChar::LineFeed) {
  126. _analysis[i].flags = QScriptAnalysis::LineOrParagraphSeparator;
  127. } else {
  128. _analysis[i].flags = QScriptAnalysis::None;
  129. }
  130. }
  131. }
  132. }
  133. }
  134. {
  135. auto &m_string = _engine.layoutData->string;
  136. auto m_analysis = _analysis;
  137. auto &m_items = _engine.layoutData->items;
  138. auto start = 0;
  139. auto startBlock = _bStart;
  140. auto currentBlock = startBlock;
  141. auto nextBlock = currentBlock + 1;
  142. for (int i = 1; i != length; ++i) {
  143. while (blockPosition(nextBlock) <= _offset + i) {
  144. currentBlock = nextBlock++;
  145. }
  146. // According to the unicode spec we should be treating characters in the Common script
  147. // (punctuation, spaces, etc) as being the same script as the surrounding text for the
  148. // purpose of splitting up text. This is important because, for example, a fullstop
  149. // (0x2E) can be used to indicate an abbreviation and so must be treated as part of a
  150. // word. Thus it must be passed along with the word in languages that have to calculate
  151. // word breaks. For example the thai word "[lookup-in-git]." has no word breaks
  152. // but the word "[lookup-too]" does.
  153. // Unfortuntely because we split up the strings for both wordwrapping and for setting
  154. // the font and because Japanese and Chinese are also aliases of the script "Common",
  155. // doing this would break too many things. So instead we only pass the full stop
  156. // along, and nothing else.
  157. if (currentBlock != startBlock
  158. || m_analysis[i].flags != m_analysis[start].flags) {
  159. // In emoji blocks we can have one item or two items.
  160. // First item is the emoji itself,
  161. // while the second item are the spaces after the emoji,
  162. // which fall in the same block, but have different flags.
  163. } else if ((*startBlock)->type() != TextBlockType::Text
  164. && m_analysis[i].flags == m_analysis[start].flags) {
  165. // Otherwise, only text blocks may have arbitrary items.
  166. Assert(i - start < kMaxItemLength);
  167. continue;
  168. } else if (m_analysis[i].bidiLevel == m_analysis[start].bidiLevel
  169. && m_analysis[i].flags == m_analysis[start].flags
  170. && (m_analysis[i].script == m_analysis[start].script || m_string[i] == u'.')
  171. //&& m_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject // only emojis are objects here, no tabs
  172. && i - start < kMaxItemLength) {
  173. continue;
  174. }
  175. m_items.append(QScriptItem(start, m_analysis[start]));
  176. start = i;
  177. startBlock = currentBlock;
  178. }
  179. m_items.append(QScriptItem(start, m_analysis[start]));
  180. }
  181. }
  182. void StackEngine::updateFont(not_null<const AbstractBlock*> block) {
  183. const auto flags = block->flags();
  184. const auto newFont = WithFlags(_t->_st->font, flags);
  185. if (_font != newFont) {
  186. _font = (newFont->family() == _t->_st->font->family())
  187. ? WithFlags(_t->_st->font, flags, newFont->flags())
  188. : newFont;
  189. _engine.fnt = _font->f;
  190. _engine.resetFontEngineCache();
  191. }
  192. }
  193. std::vector<Block>::const_iterator StackEngine::shapeGetBlock(int item) {
  194. auto &si = _engine.layoutData->items[item];
  195. const auto blockIt = adjustBlock(_offset + si.position);
  196. const auto block = blockIt->get();
  197. updateFont(block);
  198. _engine.shape(item);
  199. if (si.analysis.flags == QScriptAnalysis::Object) {
  200. si.width = block->objectWidth();
  201. }
  202. return blockIt;
  203. }
  204. int StackEngine::blockIndex(int position) const {
  205. return int(adjustBlock(_offset + position) - begin(_tBlocks));
  206. }
  207. } // namespace Ui::Text