From d71d2121b1ffc10d2c5565d911bfa31a505d343a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Jun 2020 19:04:52 +0400 Subject: [PATCH] Move some methods out of the global scope. --- ui/text/text.cpp | 174 +++++++++++-- ui/text/text.h | 135 ++--------- ui/text/text_block.cpp | 469 +++++++++++++++++++----------------- ui/text/text_entity.cpp | 51 ++-- ui/widgets/input_fields.cpp | 6 +- 5 files changed, 450 insertions(+), 385 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index d743bf8..ca49fdc 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -21,10 +21,14 @@ namespace Text { namespace { constexpr auto kStringLinkIndexShift = uint16(0x8000); +constexpr auto kMaxDiacAfterSymbol = 2; -Qt::LayoutDirection StringDirection(const QString &str, int32 from, int32 to) { - const ushort *p = reinterpret_cast(str.unicode()) + from; - const ushort *end = p + (to - from); +Qt::LayoutDirection StringDirection( + const QString &str, + int from, + int to) { + auto p = reinterpret_cast(str.unicode()) + from; + const auto end = p + (to - from); while (p < end) { uint ucs4 = *p; if (QChar::isHighSurrogate(ucs4) && p < end - 1) { @@ -98,7 +102,9 @@ TextWithEntities PrepareRichFromRich( return result; } -QFixed ComputeStopAfter(const TextParseOptions &options, const style::TextStyle &st) { +QFixed ComputeStopAfter( + const TextParseOptions &options, + const style::TextStyle &st) { return (options.maxw > 0 && options.maxh > 0) ? ((options.maxh / st.font->height) + 1) * options.maxw : QFIXED_MAX; @@ -112,11 +118,17 @@ bool ComputeCheckTilde(const style::TextStyle &st) { && (font->f.family() == qstr("DAOpenSansRegular")); } -} // namespace -} // namespace Text -} // namespace Ui +bool IsParagraphSeparator(QChar ch) { + switch (ch.unicode()) { + case QChar::LineFeed: + return true; + default: + break; + } + return false; +} -bool chIsBad(QChar ch) { +bool IsBad(QChar ch) { return (ch == 0) || (ch >= 8232 && ch < 8237) || (ch >= 65024 && ch < 65040 && ch != 65039) @@ -130,9 +142,13 @@ bool chIsBad(QChar ch) { && !Platform::IsMac10_12OrGreater() && ch >= 0x0B00 && ch <= 0x0B7F - && chIsDiac(ch)); + && IsDiac(ch)); } +} // namespace +} // namespace Text +} // namespace Ui + QString textcmdSkipBlock(ushort w, ushort h) { static QString cmd(5, TextCommand); cmd[1] = QChar(TextCommandSkipBlock); @@ -745,18 +761,18 @@ bool Parser::readCommand() { void Parser::parseCurrentChar() { _ch = ((_ptr < _end) ? *_ptr : 0); _emojiLookback = 0; - const auto isNewLine = _multiline && chIsNewline(_ch); - const auto isSpace = chIsSpace(_ch); - const auto isDiac = chIsDiac(_ch); + const auto isNewLine = _multiline && IsNewline(_ch); + const auto isSpace = IsSpace(_ch); + const auto isDiac = IsDiac(_ch); const auto isTilde = _checkTilde && (_ch == '~'); const auto skip = [&] { - if (chIsBad(_ch) || _ch.isLowSurrogate()) { + if (IsBad(_ch) || _ch.isLowSurrogate()) { return true; } else if (_ch == 0xFE0F && Platform::IsMac()) { // Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :( return true; } else if (isDiac) { - if (_lastSkipped || _emoji || ++_diacs > chMaxDiacAfterSymbol()) { + if (_lastSkipped || _emoji || ++_diacs > kMaxDiacAfterSymbol) { return true; } } else if (_ch.isHighSurrogate()) { @@ -891,10 +907,10 @@ void Parser::trimSourceRange() { _source.entities, _end - _start); - while (_ptr != _end && chIsTrimmed(*_ptr, _rich) && _ptr != _start + firstMonospaceOffset) { + while (_ptr != _end && IsTrimmed(*_ptr, _rich) && _ptr != _start + firstMonospaceOffset) { ++_ptr; } - while (_ptr != _end && chIsTrimmed(*(_end - 1), _rich)) { + while (_ptr != _end && IsTrimmed(*(_end - 1), _rich)) { --_end; } } @@ -2838,7 +2854,7 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t // for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) { // if (*ch == TextCommand) { // break; -// } else if (chIsNewline(*ch)) { +// } else if (IsNewline(*ch)) { // newText.append("},").append(*ch).append("\t{ "); // } else { // if (ch->isHighSurrogate() || ch->isLowSurrogate()) { @@ -3103,31 +3119,31 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se if (from < _text.size() && from <= to) { if (to > _text.size()) to = _text.size(); if (selectType == TextSelectType::Paragraphs) { - if (!chIsParagraphSeparator(_text.at(from))) { - while (from > 0 && !chIsParagraphSeparator(_text.at(from - 1))) { + if (!IsParagraphSeparator(_text.at(from))) { + while (from > 0 && !IsParagraphSeparator(_text.at(from - 1))) { --from; } } if (to < _text.size()) { - if (chIsParagraphSeparator(_text.at(to))) { + if (IsParagraphSeparator(_text.at(to))) { ++to; } else { - while (to < _text.size() && !chIsParagraphSeparator(_text.at(to))) { + while (to < _text.size() && !IsParagraphSeparator(_text.at(to))) { ++to; } } } } else if (selectType == TextSelectType::Words) { - if (!chIsWordSeparator(_text.at(from))) { - while (from > 0 && !chIsWordSeparator(_text.at(from - 1))) { + if (!IsWordSeparator(_text.at(from))) { + while (from > 0 && !IsWordSeparator(_text.at(from - 1))) { --from; } } if (to < _text.size()) { - if (chIsWordSeparator(_text.at(to))) { + if (IsWordSeparator(_text.at(to))) { ++to; } else { - while (to < _text.size() && !chIsWordSeparator(_text.at(to))) { + while (to < _text.size() && !IsWordSeparator(_text.at(to))) { ++to; } } @@ -3355,5 +3371,113 @@ void String::clearFields() { _startDir = Qt::LayoutDirectionAuto; } +bool IsWordSeparator(QChar ch) { + switch (ch.unicode()) { + case QChar::Space: + case QChar::LineFeed: + case '.': + case ',': + case '?': + case '!': + case '@': + case '#': + case '$': + case ':': + case ';': + case '-': + case '<': + case '>': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '=': + case '/': + case '+': + case '%': + case '&': + case '^': + case '*': + case '\'': + case '"': + case '`': + case '~': + case '|': + return true; + default: + break; + } + return false; +} + +bool IsAlmostLinkEnd(QChar ch) { + switch (ch.unicode()) { + case '?': + case ',': + case '.': + case '"': + case ':': + case '!': + case '\'': + return true; + default: + break; + } + return false; +} + +bool IsLinkEnd(QChar ch) { + return (ch == TextCommand) + || IsBad(ch) + || IsSpace(ch) + || IsNewline(ch) + || ch.isLowSurrogate() + || ch.isHighSurrogate(); +} + +bool IsNewline(QChar ch) { + return (ch == QChar::LineFeed) + || (ch == 156); +} + +bool IsSpace(QChar ch, bool rich) { + return ch.isSpace() + || (ch < 32 && !(rich && ch == TextCommand)) + || (ch == QChar::ParagraphSeparator) + || (ch == QChar::LineSeparator) + || (ch == QChar::ObjectReplacementCharacter) + || (ch == QChar::CarriageReturn) + || (ch == QChar::Tabulation) + || (ch == QChar(8203)/*Zero width space.*/); +} + +bool IsDiac(QChar ch) { // diac and variation selectors + return (ch.category() == QChar::Mark_NonSpacing) + || (ch == 1652) + || (ch >= 64606 && ch <= 64611); +} + +bool IsReplacedBySpace(QChar ch) { + // \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237 + // QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad"); + // \xcc[\xb3\xbf\x8a] // 819, 831, 778 + // QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a"); + // [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09 + return (/*code >= 0x00 && */ch <= 0x02) + || (ch >= 0x07 && ch <= 0x09) + || (ch >= 0x0b && ch <= 0x1f) + || (ch == 819) + || (ch == 831) + || (ch == 778) + || (ch >= 8232 && ch <= 8237); +} + +bool IsTrimmed(QChar ch, bool rich) { + return (!rich || ch != TextCommand) + && (IsSpace(ch) || IsBad(ch)); +} + } // namespace Text } // namespace Ui diff --git a/ui/text/text.h b/ui/text/text.h index 1768d34..9019990 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -48,19 +48,20 @@ enum class TextSelectType { }; struct TextSelection { - constexpr TextSelection() : from(0), to(0) { - } + constexpr TextSelection() = default; constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) { } constexpr bool empty() const { return from == to; } - uint16 from; - uint16 to; + uint16 from = 0; + uint16 to = 0; }; + inline bool operator==(TextSelection a, TextSelection b) { return a.from == b.from && a.to == b.to; } + inline bool operator!=(TextSelection a, TextSelection b) { return !(a == b); } @@ -225,6 +226,15 @@ private: }; +[[nodiscard]] bool IsWordSeparator(QChar ch); +[[nodiscard]] bool IsAlmostLinkEnd(QChar ch); +[[nodiscard]] bool IsLinkEnd(QChar ch); +[[nodiscard]] bool IsNewline(QChar ch); +[[nodiscard]] bool IsSpace(QChar ch, bool rich = false); +[[nodiscard]] bool IsDiac(QChar ch); +[[nodiscard]] bool IsReplacedBySpace(QChar ch); +[[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false); + } // namespace Text } // namespace Ui @@ -254,120 +264,3 @@ QString textcmdLink(const QString &url, const QString &text); QString textcmdStartSemibold(); QString textcmdStopSemibold(); const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); - -inline bool chIsSpace(QChar ch, bool rich = false) { - return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation) || (ch == QChar(8203)/*Zero width space.*/); -} -inline bool chIsDiac(QChar ch) { // diac and variation selectors - return (ch.category() == QChar::Mark_NonSpacing) || (ch == 1652) || (ch >= 64606 && ch <= 64611); -} - -bool chIsBad(QChar ch); - -inline bool chIsTrimmed(QChar ch, bool rich = false) { - return (!rich || ch != TextCommand) && (chIsSpace(ch) || chIsBad(ch)); -} -inline bool chReplacedBySpace(QChar ch) { - // \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237 - // QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad"); - // \xcc[\xb3\xbf\x8a] // 819, 831, 778 - // QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a"); - // [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09 - return (/*code >= 0x00 && */ch <= 0x02) || (ch >= 0x07 && ch <= 0x09) || (ch >= 0x0b && ch <= 0x1f) || - (ch == 819) || (ch == 831) || (ch == 778) || (ch >= 8232 && ch <= 8237); -} -inline int32 chMaxDiacAfterSymbol() { - return 2; -} -inline bool chIsNewline(QChar ch) { - return (ch == QChar::LineFeed || ch == 156); -} -inline bool chIsLinkEnd(QChar ch) { - return ch == TextCommand || chIsBad(ch) || chIsSpace(ch) || chIsNewline(ch) || ch.isLowSurrogate() || ch.isHighSurrogate(); -} -inline bool chIsAlmostLinkEnd(QChar ch) { - switch (ch.unicode()) { - case '?': - case ',': - case '.': - case '"': - case ':': - case '!': - case '\'': - return true; - default: - break; - } - return false; -} -inline bool chIsWordSeparator(QChar ch) { - switch (ch.unicode()) { - case QChar::Space: - case QChar::LineFeed: - case '.': - case ',': - case '?': - case '!': - case '@': - case '#': - case '$': - case ':': - case ';': - case '-': - case '<': - case '>': - case '[': - case ']': - case '(': - case ')': - case '{': - case '}': - case '=': - case '/': - case '+': - case '%': - case '&': - case '^': - case '*': - case '\'': - case '"': - case '`': - case '~': - case '|': - return true; - default: - break; - } - return false; -} -inline bool chIsSentenceEnd(QChar ch) { - switch (ch.unicode()) { - case '.': - case '?': - case '!': - return true; - default: - break; - } - return false; -} -inline bool chIsSentencePartEnd(QChar ch) { - switch (ch.unicode()) { - case ',': - case ':': - case ';': - return true; - default: - break; - } - return false; -} -inline bool chIsParagraphSeparator(QChar ch) { - switch (ch.unicode()) { - case QChar::LineFeed: - return true; - default: - break; - } - return false; -} diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index ace85d1..d62a114 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -17,89 +17,34 @@ namespace Text { namespace { struct ScriptLine { - ScriptLine() : length(0), textWidth(0) { - } - - int32 length; + int length = 0; QFixed textWidth; }; // All members finished with "_" are internal. -struct LineBreakHelper -{ - LineBreakHelper() - : glyphCount(0), maxGlyphs(INT_MAX), currentPosition(0), fontEngine(0), logClusters(0) - { - } - - +struct LineBreakHelper { ScriptLine tmpData; ScriptLine spaceData; QGlyphLayout glyphs; - int glyphCount; - int maxGlyphs; - int currentPosition; + int glyphCount = 0; + int maxGlyphs = INT_MAX; + int currentPosition = 0; glyph_t previousGlyph_ = 0; QFontEngine *previousFontEngine_ = nullptr; QFixed rightBearing; - QFontEngine *fontEngine; - const unsigned short *logClusters; + QFontEngine *fontEngine = nullptr; + const unsigned short *logClusters = nullptr; - inline glyph_t currentGlyph() const - { - Q_ASSERT(currentPosition > 0); - Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs); - - return glyphs.glyphs[logClusters[currentPosition - 1]]; - } - - inline void saveCurrentGlyph() - { - if (currentPosition > 0 && - logClusters[currentPosition - 1] < glyphs.numGlyphs) { - previousGlyph_ = currentGlyph(); // needed to calculate right bearing later - previousFontEngine_ = fontEngine; - } else { - previousGlyph_ = 0; - previousFontEngine_ = nullptr; - } - } - - inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph) - { - qreal rb; - engine->getGlyphBearings(glyph, 0, &rb); - - // We only care about negative right bearings, so we limit the range - // of the bearing here so that we can assume it's negative in the rest - // of the code, as well ase use QFixed(1) as a sentinel to represent - // the state where we have yet to compute the right bearing. - rightBearing = qMin(QFixed::fromReal(rb), QFixed(0)); - } - - inline void calculateRightBearing() - { - if (currentPosition > 0 && - logClusters[currentPosition - 1] < glyphs.numGlyphs) { - calculateRightBearing(fontEngine, currentGlyph()); - } else { - rightBearing = 0; - } - } - - inline void calculateRightBearingForPreviousGlyph() - { - if (previousGlyph_ > 0) { - calculateRightBearing(previousFontEngine_, previousGlyph_); - } else { - rightBearing = 0; - } - } + glyph_t currentGlyph() const; + void saveCurrentGlyph(); + void calculateRightBearing(QFontEngine *engine, glyph_t glyph); + void calculateRightBearing(); + void calculateRightBearingForPreviousGlyph(); // We always calculate the right bearing right before it is needed. // So we don't need caching / optimizations referred to delayed right bearing calculations. @@ -113,26 +58,92 @@ struct LineBreakHelper // We express the negative right bearing as an absolute number // so that it can be applied to the width using addition. - inline QFixed negativeRightBearing() const - { - //if (rightBearing == RightBearingNotCalculated) - // return QFixed(0); - - return qAbs(rightBearing); - } + QFixed negativeRightBearing() const; }; //const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1); +glyph_t LineBreakHelper::currentGlyph() const { + Q_ASSERT(currentPosition > 0); + Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs); + + return glyphs.glyphs[logClusters[currentPosition - 1]]; +} + +void LineBreakHelper::saveCurrentGlyph() { + if (currentPosition > 0 + && logClusters[currentPosition - 1] < glyphs.numGlyphs) { + // needed to calculate right bearing later + previousGlyph_ = currentGlyph(); + previousFontEngine_ = fontEngine; + } else { + previousGlyph_ = 0; + previousFontEngine_ = nullptr; + } +} + +void LineBreakHelper::calculateRightBearing( + QFontEngine *engine, + glyph_t glyph) { + qreal rb; + engine->getGlyphBearings(glyph, 0, &rb); + + // We only care about negative right bearings, so we limit the range + // of the bearing here so that we can assume it's negative in the rest + // of the code, as well ase use QFixed(1) as a sentinel to represent + // the state where we have yet to compute the right bearing. + rightBearing = qMin(QFixed::fromReal(rb), QFixed(0)); +} + +void LineBreakHelper::calculateRightBearing() { + if (currentPosition > 0 + && logClusters[currentPosition - 1] < glyphs.numGlyphs) { + calculateRightBearing(fontEngine, currentGlyph()); + } else { + rightBearing = 0; + } +} + +void LineBreakHelper::calculateRightBearingForPreviousGlyph() { + if (previousGlyph_ > 0) { + calculateRightBearing(previousFontEngine_, previousGlyph_); + } else { + rightBearing = 0; + } +} + +// We always calculate the right bearing right before it is needed. +// So we don't need caching / optimizations referred to delayed right bearing calculations. + +//static const QFixed RightBearingNotCalculated; + +//inline void resetRightBearing() +//{ +// rightBearing = RightBearingNotCalculated; +//} + +// We express the negative right bearing as an absolute number +// so that it can be applied to the width using addition. +QFixed LineBreakHelper::negativeRightBearing() const { + //if (rightBearing == RightBearingNotCalculated) + // return QFixed(0); + + return qAbs(rightBearing); +} + QString DebugCurrentParsingString, DebugCurrentParsingPart; int DebugCurrentParsingFrom = 0; int DebugCurrentParsingLength = 0; -static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyphCount, - const QScriptItem ¤t, const unsigned short *logClusters, - const QGlyphLayout &glyphs) -{ +void addNextCluster( + int &pos, + int end, + ScriptLine &line, + int &glyphCount, + const QScriptItem ¤t, + const unsigned short *logClusters, + const QGlyphLayout &glyphs) { int glyphPosition = logClusters[pos]; do { // got to the first next cluster ++pos; @@ -142,7 +153,8 @@ static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyp if (!glyphs.attributes[glyphPosition].dontPrint) line.textWidth += glyphs.advances[glyphPosition]; ++glyphPosition; - } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart); + } while (glyphPosition < current.num_glyphs + && !glyphs.attributes[glyphPosition].clusterStart); if (!((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition)) { auto str = QStringList(); @@ -153,7 +165,8 @@ static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyp base::Integration::Instance().logAssertionViolation(QString("pos: %1, end: %2, glyphPosition: %3, glyphCount: %4, lineLength: %5, num_glyphs: %6, logClusters[0..pos]: %7").arg(pos).arg(end).arg(glyphPosition).arg(glyphCount).arg(line.length).arg(current.num_glyphs).arg(str.join(","))); Unexpected("Values in addNextCluster()"); } - Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition); + Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) + || logClusters[pos] == glyphPosition); ++glyphCount; } @@ -162,146 +175,16 @@ static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyp class BlockParser { public: - - BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str) - : block(b), eng(e), str(str) { - parseWords(minResizeWidth, blockFrom); - } - - void parseWords(QFixed minResizeWidth, int32 blockFrom) { - LineBreakHelper lbh; - - // Helper for debugging crashes in text processing. - // - // auto debugChars = QString(); - // debugChars.reserve(str.size() * 7); - // for (const auto ch : str) { - // debugChars.append( - // "0x").append( - // QString::number(ch.unicode(), 16).toUpper()).append( - // ' '); - // } - // LOG(("Text: %1, chars: %2").arg(str).arg(debugChars)); - - int item = -1; - int newItem = eng->findItem(0); - - style::align alignment = eng->option.alignment(); - - const QCharAttributes *attributes = eng->attributes(); - if (!attributes) - return; - int end = 0; - lbh.logClusters = eng->layoutData->logClustersPtr; - - block->_words.clear(); - - int wordStart = lbh.currentPosition; - - bool addingEachGrapheme = false; - int lastGraphemeBoundaryPosition = -1; - ScriptLine lastGraphemeBoundaryLine; - - while (newItem < eng->layoutData->items.size()) { - if (newItem != item) { - item = newItem; - const QScriptItem ¤t = eng->layoutData->items[item]; - if (!current.num_glyphs) { - eng->shape(item); - attributes = eng->attributes(); - if (!attributes) - return; - lbh.logClusters = eng->layoutData->logClustersPtr; - } - lbh.currentPosition = current.position; - end = current.position + eng->length(item); - lbh.glyphs = eng->shapedGlyphs(¤t); - QFontEngine *fontEngine = eng->fontEngine(current); - if (lbh.fontEngine != fontEngine) { - lbh.fontEngine = fontEngine; - } - } - const QScriptItem ¤t = eng->layoutData->items[item]; - - if (attributes[lbh.currentPosition].whiteSpace) { - while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace) - addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount, - current, lbh.logClusters, lbh.glyphs); - - if (block->_words.isEmpty()) { - block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); - } - block->_words.back().add_rpadding(lbh.spaceData.textWidth); - block->_width += lbh.spaceData.textWidth; - lbh.spaceData.length = 0; - lbh.spaceData.textWidth = 0; - - wordStart = lbh.currentPosition; - - addingEachGrapheme = false; - lastGraphemeBoundaryPosition = -1; - lastGraphemeBoundaryLine = ScriptLine(); - } else { - do { - addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, - current, lbh.logClusters, lbh.glyphs); - - if (lbh.currentPosition >= eng->layoutData->string.length() - || attributes[lbh.currentPosition].whiteSpace - || isLineBreak(attributes, lbh.currentPosition)) { - lbh.calculateRightBearing(); - block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); - block->_width += lbh.tmpData.textWidth; - lbh.tmpData.textWidth = 0; - lbh.tmpData.length = 0; - wordStart = lbh.currentPosition; - break; - } else if (attributes[lbh.currentPosition].graphemeBoundary) { - if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) { - if (lastGraphemeBoundaryPosition >= 0) { - lbh.calculateRightBearingForPreviousGlyph(); - block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing())); - block->_width += lastGraphemeBoundaryLine.textWidth; - lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth; - lbh.tmpData.length -= lastGraphemeBoundaryLine.length; - wordStart = lastGraphemeBoundaryPosition; - } - addingEachGrapheme = true; - } - if (addingEachGrapheme) { - lbh.calculateRightBearing(); - block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing())); - block->_width += lbh.tmpData.textWidth; - lbh.tmpData.textWidth = 0; - lbh.tmpData.length = 0; - wordStart = lbh.currentPosition; - } else { - lastGraphemeBoundaryPosition = lbh.currentPosition; - lastGraphemeBoundaryLine = lbh.tmpData; - lbh.saveCurrentGlyph(); - } - } - } while (lbh.currentPosition < end); - } - if (lbh.currentPosition == end) - newItem = item + 1; - } - if (!block->_words.isEmpty()) { - block->_rpadding = block->_words.back().f_rpadding(); - block->_width -= block->_rpadding; - block->_words.squeeze(); - } - } - - bool isLineBreak(const QCharAttributes *attributes, int32 index) { - bool lineBreak = attributes[index].lineBreak; - if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') { - return false; // don't break after / in links - } - return lineBreak; - } + BlockParser( + QTextEngine *e, + TextBlock *b, + QFixed minResizeWidth, + int blockFrom, + const QString &str); private: + void parseWords(QFixed minResizeWidth, int blockFrom); + bool isLineBreak(const QCharAttributes *attributes, int index); TextBlock *block; QTextEngine *eng; @@ -309,6 +192,156 @@ private: }; +BlockParser::BlockParser( + QTextEngine *e, + TextBlock *b, + QFixed minResizeWidth, + int blockFrom, + const QString &str) +: block(b) +, eng(e) +, str(str) { + parseWords(minResizeWidth, blockFrom); +} + +void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) { + LineBreakHelper lbh; + + // Helper for debugging crashes in text processing. + // + // auto debugChars = QString(); + // debugChars.reserve(str.size() * 7); + // for (const auto ch : str) { + // debugChars.append( + // "0x").append( + // QString::number(ch.unicode(), 16).toUpper()).append( + // ' '); + // } + // LOG(("Text: %1, chars: %2").arg(str).arg(debugChars)); + + int item = -1; + int newItem = eng->findItem(0); + + style::align alignment = eng->option.alignment(); + + const QCharAttributes *attributes = eng->attributes(); + if (!attributes) + return; + int end = 0; + lbh.logClusters = eng->layoutData->logClustersPtr; + + block->_words.clear(); + + int wordStart = lbh.currentPosition; + + bool addingEachGrapheme = false; + int lastGraphemeBoundaryPosition = -1; + ScriptLine lastGraphemeBoundaryLine; + + while (newItem < eng->layoutData->items.size()) { + if (newItem != item) { + item = newItem; + const QScriptItem ¤t = eng->layoutData->items[item]; + if (!current.num_glyphs) { + eng->shape(item); + attributes = eng->attributes(); + if (!attributes) + return; + lbh.logClusters = eng->layoutData->logClustersPtr; + } + lbh.currentPosition = current.position; + end = current.position + eng->length(item); + lbh.glyphs = eng->shapedGlyphs(¤t); + QFontEngine *fontEngine = eng->fontEngine(current); + if (lbh.fontEngine != fontEngine) { + lbh.fontEngine = fontEngine; + } + } + const QScriptItem ¤t = eng->layoutData->items[item]; + + if (attributes[lbh.currentPosition].whiteSpace) { + while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace) + addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount, + current, lbh.logClusters, lbh.glyphs); + + if (block->_words.isEmpty()) { + block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); + } + block->_words.back().add_rpadding(lbh.spaceData.textWidth); + block->_width += lbh.spaceData.textWidth; + lbh.spaceData.length = 0; + lbh.spaceData.textWidth = 0; + + wordStart = lbh.currentPosition; + + addingEachGrapheme = false; + lastGraphemeBoundaryPosition = -1; + lastGraphemeBoundaryLine = ScriptLine(); + } else { + do { + addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, + current, lbh.logClusters, lbh.glyphs); + + if (lbh.currentPosition >= eng->layoutData->string.length() + || attributes[lbh.currentPosition].whiteSpace + || isLineBreak(attributes, lbh.currentPosition)) { + lbh.calculateRightBearing(); + block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); + block->_width += lbh.tmpData.textWidth; + lbh.tmpData.textWidth = 0; + lbh.tmpData.length = 0; + wordStart = lbh.currentPosition; + break; + } else if (attributes[lbh.currentPosition].graphemeBoundary) { + if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) { + if (lastGraphemeBoundaryPosition >= 0) { + lbh.calculateRightBearingForPreviousGlyph(); + block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing())); + block->_width += lastGraphemeBoundaryLine.textWidth; + lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth; + lbh.tmpData.length -= lastGraphemeBoundaryLine.length; + wordStart = lastGraphemeBoundaryPosition; + } + addingEachGrapheme = true; + } + if (addingEachGrapheme) { + lbh.calculateRightBearing(); + block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing())); + block->_width += lbh.tmpData.textWidth; + lbh.tmpData.textWidth = 0; + lbh.tmpData.length = 0; + wordStart = lbh.currentPosition; + } else { + lastGraphemeBoundaryPosition = lbh.currentPosition; + lastGraphemeBoundaryLine = lbh.tmpData; + lbh.saveCurrentGlyph(); + } + } + } while (lbh.currentPosition < end); + } + if (lbh.currentPosition == end) + newItem = item + 1; + } + if (!block->_words.isEmpty()) { + block->_rpadding = block->_words.back().f_rpadding(); + block->_width -= block->_rpadding; + block->_words.squeeze(); + } +} + +bool BlockParser::isLineBreak( + const QCharAttributes *attributes, + int index) { + bool lineBreak = attributes[index].lineBreak; + if (lineBreak + && block->lnkIndex() > 0 + && index > 0 + && str.at(index - 1) == '/') { + return false; // don't break after / in links + } + return lineBreak; +} + QFixed AbstractBlock::f_rbearing() const { return (type() == TextBlockTText) ? static_cast(this)->real_f_rbearing() diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 995c72d..087fa77 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -21,6 +21,8 @@ namespace TextUtilities { namespace { +using namespace Ui::Text; + QString ExpressionMailNameAtEnd() { // Matches email first part (before '@') at the end of the string. // First we find a domain without protocol (like "gmail.com"), then @@ -1189,6 +1191,18 @@ std::unique_ptr MimeDataFromText( return result; } +bool IsSentencePartEnd(QChar ch) { + return (ch == ',') + || (ch == ':') + || (ch == ';'); +} + +bool IsSentenceEnd(QChar ch) { + return (ch == '.') + || (ch == '?') + || (ch == '!'); +} + } // namespace const QRegularExpression &RegExpMailNameAtEnd() { @@ -1303,10 +1317,10 @@ QString SingleLine(const QString &text) { auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); // Trim. - while (s < e && chIsTrimmed(*s)) { + while (s < e && IsTrimmed(*s)) { ++s; } - while (s < e && chIsTrimmed(*(e - 1))) { + while (s < e && IsTrimmed(*(e - 1))) { --e; } if (e - s != text.size()) { @@ -1314,7 +1328,7 @@ QString SingleLine(const QString &text) { } for (auto ch = s; ch != e; ++ch) { - if (chIsNewline(*ch) || *ch == TextCommand) { + if (IsNewline(*ch) || *ch == TextCommand) { result[int(ch - s)] = QChar::Space; } } @@ -1336,7 +1350,7 @@ QString RemoveAccents(const QString &text) { if (copying) result[i] = *ch; continue; } - if (chIsDiac(*ch)) { + if (IsDiac(*ch)) { copying = true; --i; continue; @@ -1434,14 +1448,14 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) { if (inEntity && !canBreakEntity) { markGoodAsLevel(0); } else { - if (chIsNewline(*ch)) { + if (IsNewline(*ch)) { if (inEntity) { - if (ch + 1 < end && chIsNewline(*(ch + 1))) { + if (ch + 1 < end && IsNewline(*(ch + 1))) { markGoodAsLevel(12); } else { markGoodAsLevel(11); } - } else if (ch + 1 < end && chIsNewline(*(ch + 1))) { + } else if (ch + 1 < end && IsNewline(*(ch + 1))) { markGoodAsLevel(15); } else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityType::Pre) { markGoodAsLevel(14); @@ -1450,15 +1464,15 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) { } else { markGoodAsLevel(13); } - } else if (chIsSpace(*ch)) { - if (chIsSentenceEnd(*(ch - 1))) { + } else if (IsSpace(*ch)) { + if (IsSentenceEnd(*(ch - 1))) { markGoodAsLevel(9 + noEntityLevel); - } else if (chIsSentencePartEnd(*(ch - 1))) { + } else if (IsSentencePartEnd(*(ch - 1))) { markGoodAsLevel(7 + noEntityLevel); } else { markGoodAsLevel(5 + noEntityLevel); } - } else if (chIsWordSeparator(*(ch - 1))) { + } else if (IsWordSeparator(*(ch - 1))) { markGoodAsLevel(3 + noEntityLevel); } else { markGoodAsLevel(1 + noEntityLevel); @@ -1761,13 +1775,14 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) { const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd; for (; p < end; ++p) { QChar ch(*p); - if (chIsLinkEnd(ch)) break; // link finished - if (chIsAlmostLinkEnd(ch)) { + if (IsLinkEnd(ch)) { + break; // link finished + } else if (IsAlmostLinkEnd(ch)) { const QChar *endTest = p + 1; - while (endTest < end && chIsAlmostLinkEnd(*endTest)) { + while (endTest < end && IsAlmostLinkEnd(*endTest)) { ++endTest; } - if (endTest >= end || chIsLinkEnd(*endTest)) { + if (endTest >= end || IsLinkEnd(*endTest)) { break; // link finished at p } p = endTest; @@ -1879,7 +1894,7 @@ void ApplyServerCleaning(TextWithEntities &result) { if (ch->unicode() == '\r') { MovePartAndGoForward(result, to, from, (ch - start) - from); ++from; - } else if (chReplacedBySpace(*ch)) { + } else if (IsReplacedBySpace(*ch)) { *ch = ' '; } } @@ -1893,7 +1908,7 @@ void Trim(TextWithEntities &result) { // right trim for (auto s = result.text.data(), e = s + result.text.size(), ch = e; ch != s;) { --ch; - if (!chIsTrimmed(*ch)) { + if (!IsTrimmed(*ch)) { if (ch + 1 < e) { auto l = ch + 1 - s; for (auto &entity : result.entities) { @@ -1916,7 +1931,7 @@ void Trim(TextWithEntities &result) { // left trim for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) { - if (!chIsTrimmed(*ch) || (ch - s) == firstMonospaceOffset) { + if (!IsTrimmed(*ch) || (ch - s) == firstMonospaceOffset) { if (ch > s) { auto l = ch - s; for (auto &entity : result.entities) { diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 69460f6..23abd56 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -104,12 +104,12 @@ QString GetFullSimpleTextTag(const TextWithTags &textWithTags) { auto from = 0; auto till = int(text.size()); for (; from != till; ++from) { - if (!IsNewline(text[from]) && !chIsSpace(text[from])) { + if (!IsNewline(text[from]) && !Text::IsSpace(text[from])) { break; } } while (till != from) { - if (!IsNewline(text[till - 1]) && !chIsSpace(text[till - 1])) { + if (!IsNewline(text[till - 1]) && !Text::IsSpace(text[till - 1])) { break; } --till; @@ -3100,7 +3100,7 @@ bool InputField::commitMarkdownReplacement( const auto ch = outer.at(check); if (IsNewline(ch)) { return check + 1; - } else if (!chIsSpace(ch)) { + } else if (!Text::IsSpace(ch)) { break; } }