text_word_parser.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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_word_parser.h"
  8. #include "ui/text/text_bidi_algorithm.h"
  9. #include "styles/style_basic.h"
  10. // COPIED FROM qtextlayout.cpp AND MODIFIED
  11. namespace Ui::Text {
  12. glyph_t WordParser::LineBreakHelper::currentGlyph() const {
  13. Q_ASSERT(currentPosition > 0);
  14. Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
  15. return glyphs.glyphs[logClusters[currentPosition - 1]];
  16. }
  17. void WordParser::LineBreakHelper::saveCurrentGlyph() {
  18. if (currentPosition > 0
  19. && logClusters[currentPosition - 1] < glyphs.numGlyphs) {
  20. // needed to calculate right bearing later
  21. previousGlyph = currentGlyph();
  22. previousGlyphFontEngine = fontEngine;
  23. } else {
  24. previousGlyph = 0;
  25. previousGlyphFontEngine = nullptr;
  26. }
  27. }
  28. void WordParser::LineBreakHelper::calculateRightBearing(
  29. QFontEngine *engine,
  30. glyph_t glyph) {
  31. qreal rb;
  32. engine->getGlyphBearings(glyph, 0, &rb);
  33. // We only care about negative right bearings, so we limit the range
  34. // of the bearing here so that we can assume it's negative in the rest
  35. // of the code, as well as use QFixed(1) as a sentinel to represent
  36. // the state where we have yet to compute the right bearing.
  37. rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
  38. }
  39. void WordParser::LineBreakHelper::calculateRightBearing() {
  40. if (currentPosition > 0
  41. && logClusters[currentPosition - 1] < glyphs.numGlyphs
  42. && !whiteSpaceOrObject) {
  43. calculateRightBearing(fontEngine.data(), currentGlyph());
  44. } else {
  45. rightBearing = 0;
  46. }
  47. }
  48. void WordParser::LineBreakHelper::calculateRightBearingForPreviousGlyph() {
  49. if (previousGlyph > 0) {
  50. calculateRightBearing(previousGlyphFontEngine.data(), previousGlyph);
  51. } else {
  52. rightBearing = 0;
  53. }
  54. }
  55. // We always calculate the right bearing right before it is needed.
  56. // So we don't need caching / optimizations referred to delayed right bearing calculations.
  57. //static const QFixed RightBearingNotCalculated;
  58. //inline void WordParser::LineBreakHelper::resetRightBearing()
  59. //{
  60. // rightBearing = RightBearingNotCalculated;
  61. //}
  62. // We express the negative right bearing as an absolute number
  63. // so that it can be applied to the width using addition.
  64. QFixed WordParser::LineBreakHelper::negativeRightBearing() const {
  65. //if (rightBearing == RightBearingNotCalculated)
  66. // return QFixed(0);
  67. return qAbs(rightBearing);
  68. }
  69. void WordParser::addNextCluster(
  70. int &pos,
  71. int end,
  72. ScriptLine &line,
  73. int &glyphCount,
  74. const QScriptItem &current,
  75. const unsigned short *logClusters,
  76. const QGlyphLayout &glyphs) {
  77. int glyphPosition = logClusters[pos];
  78. do { // got to the first next cluster
  79. ++pos;
  80. ++line.length;
  81. } while (pos < end && logClusters[pos] == glyphPosition);
  82. do { // calculate the textWidth for the rest of the current cluster.
  83. if (!glyphs.attributes[glyphPosition].dontPrint)
  84. line.textWidth += glyphs.advances[glyphPosition];
  85. ++glyphPosition;
  86. } while (glyphPosition < current.num_glyphs
  87. && !glyphs.attributes[glyphPosition].clusterStart);
  88. Q_ASSERT((pos == end && glyphPosition == current.num_glyphs)
  89. || logClusters[pos] == glyphPosition);
  90. ++glyphCount;
  91. }
  92. WordParser::BidiInitedAnalysis::BidiInitedAnalysis(not_null<String*> text)
  93. : list(text->_text.size()) {
  94. BidiAlgorithm bidi(
  95. text->_text.constData(),
  96. list.data(),
  97. text->_text.size(),
  98. false, // baseDirectionIsRtl
  99. begin(text->_blocks),
  100. end(text->_blocks),
  101. 0); // offsetInBlocks
  102. bidi.process();
  103. }
  104. WordParser::WordParser(not_null<String*> string)
  105. : _t(string)
  106. , _tText(_t->_text)
  107. , _tBlocks(_t->_blocks)
  108. , _tWords(_t->_words)
  109. , _analysis(_t)
  110. , _engine(_t, _analysis.list)
  111. , _e(_engine.wrapped()) {
  112. parse();
  113. }
  114. void WordParser::parse() {
  115. _tWords.clear();
  116. if (_tText.isEmpty()) {
  117. return;
  118. }
  119. _newItem = _e.findItem(0);
  120. _attributes = _e.attributes();
  121. if (!_attributes) {
  122. return;
  123. }
  124. _lbh.logClusters = _e.layoutData->logClustersPtr;
  125. while (_newItem < _e.layoutData->items.size()) {
  126. if (_newItem != _item) {
  127. _attributes = moveToNewItemGetAttributes();
  128. if (!_attributes) {
  129. return;
  130. }
  131. }
  132. const auto &current = _e.layoutData->items[_item];
  133. const auto atSpaceBreak = [&] {
  134. for (auto index = _lbh.currentPosition; index < _itemEnd; ++index) {
  135. if (!_attributes[index].whiteSpace) {
  136. return false;
  137. } else if (isSpaceBreak(_attributes, index)) {
  138. return true;
  139. }
  140. }
  141. return false;
  142. }();
  143. if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
  144. pushAccumulatedWord();
  145. processSingleGlyphItem();
  146. pushNewline(_wordStart, _engine.blockIndex(_wordStart));
  147. wordProcessed(_itemEnd);
  148. } else if (current.analysis.flags == QScriptAnalysis::Object) {
  149. pushAccumulatedWord();
  150. processSingleGlyphItem(current.width);
  151. _lbh.calculateRightBearing();
  152. pushFinishedWord(
  153. _wordStart,
  154. _lbh.tmpData.textWidth,
  155. -_lbh.negativeRightBearing());
  156. wordProcessed(_itemEnd);
  157. } else if (atSpaceBreak) {
  158. pushAccumulatedWord();
  159. accumulateWhitespaces();
  160. ensureWordForRightPadding();
  161. _tWords.back().add_rpadding(_lbh.spaceData.textWidth);
  162. wordProcessed(_lbh.currentPosition, true);
  163. } else {
  164. _lbh.whiteSpaceOrObject = false;
  165. do {
  166. addNextCluster(
  167. _lbh.currentPosition,
  168. _itemEnd,
  169. _lbh.tmpData,
  170. _lbh.glyphCount,
  171. current,
  172. _lbh.logClusters,
  173. _lbh.glyphs);
  174. if (_lbh.currentPosition >= _e.layoutData->string.length()
  175. || isSpaceBreak(_attributes, _lbh.currentPosition)
  176. || isLineBreak(_attributes, _lbh.currentPosition)) {
  177. maybeStartUnfinishedWord();
  178. _lbh.calculateRightBearing();
  179. pushFinishedWord(
  180. _wordStart,
  181. _lbh.tmpData.textWidth,
  182. -_lbh.negativeRightBearing());
  183. wordProcessed(_lbh.currentPosition);
  184. break;
  185. } else if (_attributes[_lbh.currentPosition].graphemeBoundary) {
  186. maybeStartUnfinishedWord();
  187. if (_addingEachGrapheme) {
  188. _lbh.calculateRightBearing();
  189. pushUnfinishedWord(
  190. _wordStart,
  191. _lbh.tmpData.textWidth,
  192. -_lbh.negativeRightBearing());
  193. wordContinued(_lbh.currentPosition);
  194. } else {
  195. _lastGraphemeBoundaryPosition = _lbh.currentPosition;
  196. _lastGraphemeBoundaryLine = _lbh.tmpData;
  197. _lbh.saveCurrentGlyph();
  198. }
  199. }
  200. } while (_lbh.currentPosition < _itemEnd);
  201. }
  202. if (_lbh.currentPosition == _itemEnd)
  203. _newItem = _item + 1;
  204. }
  205. if (!_tWords.empty()) {
  206. _tWords.shrink_to_fit();
  207. }
  208. }
  209. const QCharAttributes *WordParser::moveToNewItemGetAttributes() {
  210. _item = _newItem;
  211. auto &si = _e.layoutData->items[_item];
  212. auto result = _e.attributes();
  213. if (!si.num_glyphs) {
  214. _engine.shapeGetBlock(_item);
  215. result = _e.attributes();
  216. if (!result) {
  217. return nullptr;
  218. }
  219. _lbh.logClusters = _e.layoutData->logClustersPtr;
  220. }
  221. _lbh.currentPosition = si.position;
  222. _itemEnd = si.position + _e.length(_item);
  223. _lbh.glyphs = _e.shapedGlyphs(&si);
  224. const auto fontEngine = _e.fontEngine(si);
  225. if (_lbh.fontEngine != fontEngine) {
  226. _lbh.fontEngine = fontEngine;
  227. }
  228. return result;
  229. }
  230. void WordParser::pushAccumulatedWord() {
  231. if (_wordStart < _lbh.currentPosition) {
  232. _lbh.calculateRightBearing();
  233. pushFinishedWord(
  234. _wordStart,
  235. _lbh.tmpData.textWidth,
  236. -_lbh.negativeRightBearing());
  237. wordProcessed(_lbh.currentPosition);
  238. }
  239. }
  240. void WordParser::processSingleGlyphItem(QFixed added) {
  241. _lbh.whiteSpaceOrObject = true;
  242. ++_lbh.tmpData.length;
  243. _lbh.tmpData.textWidth += added;
  244. _newItem = _item + 1;
  245. ++_lbh.glyphCount;
  246. }
  247. void WordParser::wordProcessed(int nextWordStart, bool spaces) {
  248. wordContinued(nextWordStart, spaces);
  249. _addingEachGrapheme = false;
  250. _lastGraphemeBoundaryPosition = -1;
  251. _lastGraphemeBoundaryLine = ScriptLine();
  252. }
  253. void WordParser::wordContinued(int nextPartStart, bool spaces) {
  254. if (spaces) {
  255. _lbh.spaceData.textWidth = 0;
  256. _lbh.spaceData.length = 0;
  257. } else {
  258. _lbh.tmpData.textWidth = 0;
  259. _lbh.tmpData.length = 0;
  260. }
  261. _wordStart = nextPartStart;
  262. }
  263. void WordParser::accumulateWhitespaces() {
  264. const auto &current = _e.layoutData->items[_item];
  265. _lbh.whiteSpaceOrObject = true;
  266. while (_lbh.currentPosition < _itemEnd
  267. && _attributes[_lbh.currentPosition].whiteSpace)
  268. addNextCluster(
  269. _lbh.currentPosition,
  270. _itemEnd,
  271. _lbh.spaceData,
  272. _lbh.glyphCount,
  273. current,
  274. _lbh.logClusters,
  275. _lbh.glyphs);
  276. }
  277. void WordParser::ensureWordForRightPadding() {
  278. if (_tWords.empty()) {
  279. _lbh.calculateRightBearing();
  280. pushFinishedWord(
  281. _wordStart,
  282. _lbh.tmpData.textWidth,
  283. -_lbh.negativeRightBearing());
  284. }
  285. }
  286. void WordParser::maybeStartUnfinishedWord() {
  287. if (!_addingEachGrapheme && _lbh.tmpData.textWidth > _t->_minResizeWidth) {
  288. if (_lastGraphemeBoundaryPosition >= 0) {
  289. _lbh.calculateRightBearingForPreviousGlyph();
  290. pushUnfinishedWord(
  291. _wordStart,
  292. _lastGraphemeBoundaryLine.textWidth,
  293. -_lbh.negativeRightBearing());
  294. _lbh.tmpData.textWidth -= _lastGraphemeBoundaryLine.textWidth;
  295. _lbh.tmpData.length -= _lastGraphemeBoundaryLine.length;
  296. _wordStart = _lastGraphemeBoundaryPosition;
  297. }
  298. _addingEachGrapheme = true;
  299. }
  300. }
  301. void WordParser::pushFinishedWord(
  302. uint16 position,
  303. QFixed width,
  304. QFixed rbearing) {
  305. const auto unfinished = false;
  306. _tWords.push_back(Word(position, unfinished, width, rbearing));
  307. }
  308. void WordParser::pushUnfinishedWord(
  309. uint16 position,
  310. QFixed width,
  311. QFixed rbearing) {
  312. const auto unfinished = true;
  313. _tWords.push_back(Word(position, unfinished, width, rbearing));
  314. }
  315. void WordParser::pushNewline(uint16 position, int newlineBlockIndex) {
  316. _tWords.push_back(Word(position, newlineBlockIndex));
  317. }
  318. bool WordParser::isLineBreak(
  319. const QCharAttributes *attributes,
  320. int index) const {
  321. // Don't break by '/' or '.' in the middle of the word.
  322. // In case of a line break or white space it'll allow break anyway.
  323. return attributes[index].lineBreak
  324. && (index <= 0
  325. || (_tText[index - 1] != '/' && _tText[index - 1] != '.'));
  326. }
  327. bool WordParser::isSpaceBreak(
  328. const QCharAttributes *attributes,
  329. int index) const {
  330. // Don't break on &nbsp;
  331. return attributes[index].whiteSpace && (_tText[index] != QChar::Nbsp);
  332. }
  333. } // namespace Ui::Text