Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
231 lines
7.3 KiB
C++
231 lines
7.3 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/text/text_stack_engine.h"
|
|
|
|
#include "ui/text/text_block.h"
|
|
#include "styles/style_basic.h"
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
#include <private/qharfbuzz_p.h>
|
|
#endif // Qt < 6.0.0
|
|
|
|
namespace Ui::Text {
|
|
namespace {
|
|
|
|
constexpr auto kMaxItemLength = 4096;
|
|
|
|
} // namespace
|
|
|
|
StackEngine::StackEngine(
|
|
not_null<const String*> t,
|
|
gsl::span<QScriptAnalysis> analysis,
|
|
int from,
|
|
int till,
|
|
int blockIndexHint)
|
|
: StackEngine(
|
|
t,
|
|
from,
|
|
((from > 0 || (till >= 0 && till < t->_text.size()))
|
|
? QString::fromRawData(
|
|
t->_text.constData() + from,
|
|
((till < 0) ? int(t->_text.size()) : till) - from)
|
|
: t->_text),
|
|
analysis,
|
|
blockIndexHint) {
|
|
}
|
|
|
|
StackEngine::StackEngine(
|
|
not_null<const String*> t,
|
|
int offset,
|
|
const QString &text,
|
|
gsl::span<QScriptAnalysis> analysis,
|
|
int blockIndexHint,
|
|
int blockIndexLimit)
|
|
: _t(t)
|
|
, _text(text)
|
|
, _analysis(analysis.data())
|
|
, _offset(offset)
|
|
, _positionEnd(_offset + _text.size())
|
|
, _font(_t->_st->font)
|
|
, _engine(_text, _font->f)
|
|
, _tBlocks(_t->_blocks)
|
|
, _bStart(begin(_tBlocks) + blockIndexHint)
|
|
, _bEnd((blockIndexLimit >= 0)
|
|
? (begin(_tBlocks) + blockIndexLimit)
|
|
: end(_tBlocks))
|
|
, _bCached(_bStart) {
|
|
Expects(analysis.size() >= _text.size());
|
|
|
|
_engine.validate();
|
|
itemize();
|
|
}
|
|
|
|
std::vector<Block>::const_iterator StackEngine::adjustBlock(
|
|
int offset) const {
|
|
Expects(offset < _positionEnd);
|
|
|
|
if (blockPosition(_bCached) > offset) {
|
|
_bCached = begin(_tBlocks);
|
|
}
|
|
Assert(_bCached != end(_tBlocks));
|
|
for (auto i = _bCached + 1; blockPosition(i) <= offset; ++i) {
|
|
_bCached = i;
|
|
}
|
|
return _bCached;
|
|
}
|
|
|
|
int StackEngine::blockPosition(std::vector<Block>::const_iterator i) const {
|
|
return (i == _bEnd) ? _positionEnd : (*i)->position();
|
|
}
|
|
|
|
int StackEngine::blockEnd(std::vector<Block>::const_iterator i) const {
|
|
return (i == _bEnd) ? _positionEnd : blockPosition(i + 1);
|
|
}
|
|
|
|
void StackEngine::itemize() {
|
|
const auto layoutData = _engine.layoutData;
|
|
if (layoutData->items.size()) {
|
|
return;
|
|
}
|
|
|
|
const auto length = layoutData->string.length();
|
|
if (!length) {
|
|
return;
|
|
}
|
|
|
|
_bStart = adjustBlock(_offset);
|
|
const auto chars = _engine.layoutData->string.constData();
|
|
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QUnicodeTools::ScriptItemArray scriptItems;
|
|
QUnicodeTools::initScripts(_engine.layoutData->string, &scriptItems);
|
|
for (int i = 0; i < scriptItems.length(); ++i) {
|
|
const auto &item = scriptItems.at(i);
|
|
int end = i < scriptItems.length() - 1 ? scriptItems.at(i + 1).position : length;
|
|
for (int j = item.position; j < end; ++j)
|
|
_analysis[j].script = item.script;
|
|
}
|
|
#else // Qt >= 6.0.0
|
|
QVarLengthArray<uchar> scripts(length);
|
|
QUnicodeTools::initScripts(reinterpret_cast<const ushort*>(chars), length, scripts.data());
|
|
for (int i = 0; i < length; ++i)
|
|
_analysis[i].script = scripts.at(i);
|
|
#endif // Qt < 6.0.0
|
|
}
|
|
|
|
// Override script and flags for emoji and custom emoji blocks.
|
|
const auto end = _offset + length;
|
|
for (auto block = _bStart; blockPosition(block) < end; ++block) {
|
|
const auto type = (*block)->type();
|
|
const auto from = std::max(_offset, int(blockPosition(block)));
|
|
const auto till = std::min(int(end), int(blockEnd(block)));
|
|
if (till > from) {
|
|
if (type == TextBlockType::Emoji
|
|
|| type == TextBlockType::CustomEmoji
|
|
|| type == TextBlockType::Skip) {
|
|
for (auto i = from - _offset, count = till - _offset; i != count; ++i) {
|
|
_analysis[i].script = QChar::Script_Common;
|
|
_analysis[i].flags = (chars[i] == QChar::Space)
|
|
? QScriptAnalysis::None
|
|
: QScriptAnalysis::Object;
|
|
}
|
|
} else {
|
|
for (auto i = from - _offset, count = till - _offset; i != count; ++i) {
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
_analysis[i].script = hbscript_to_script(script_to_hbscript(_analysis[i].script)); // retain the old behavior
|
|
#endif // Qt < 6.0.0
|
|
if (chars[i] == QChar::LineFeed) {
|
|
_analysis[i].flags = QScriptAnalysis::LineOrParagraphSeparator;
|
|
} else {
|
|
_analysis[i].flags = QScriptAnalysis::None;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
auto &m_string = _engine.layoutData->string;
|
|
auto m_analysis = _analysis;
|
|
auto &m_items = _engine.layoutData->items;
|
|
|
|
auto start = 0;
|
|
auto startBlock = _bStart;
|
|
auto currentBlock = startBlock;
|
|
auto nextBlock = currentBlock + 1;
|
|
for (int i = 1; i != length; ++i) {
|
|
while (blockPosition(nextBlock) <= _offset + i) {
|
|
currentBlock = nextBlock++;
|
|
}
|
|
// According to the unicode spec we should be treating characters in the Common script
|
|
// (punctuation, spaces, etc) as being the same script as the surrounding text for the
|
|
// purpose of splitting up text. This is important because, for example, a fullstop
|
|
// (0x2E) can be used to indicate an abbreviation and so must be treated as part of a
|
|
// word. Thus it must be passed along with the word in languages that have to calculate
|
|
// word breaks. For example the thai word "[lookup-in-git]." has no word breaks
|
|
// but the word "[lookup-too]" does.
|
|
// Unfortuntely because we split up the strings for both wordwrapping and for setting
|
|
// the font and because Japanese and Chinese are also aliases of the script "Common",
|
|
// doing this would break too many things. So instead we only pass the full stop
|
|
// along, and nothing else.
|
|
if (currentBlock != startBlock
|
|
|| m_analysis[i].flags != m_analysis[start].flags) {
|
|
// In emoji blocks we can have one item or two items.
|
|
// First item is the emoji itself,
|
|
// while the second item are the spaces after the emoji,
|
|
// which fall in the same block, but have different flags.
|
|
} else if ((*startBlock)->type() != TextBlockType::Text
|
|
&& m_analysis[i].flags == m_analysis[start].flags) {
|
|
// Otherwise, only text blocks may have arbitrary items.
|
|
Assert(i - start < kMaxItemLength);
|
|
continue;
|
|
} else if (m_analysis[i].bidiLevel == m_analysis[start].bidiLevel
|
|
&& m_analysis[i].flags == m_analysis[start].flags
|
|
&& (m_analysis[i].script == m_analysis[start].script || m_string[i] == u'.')
|
|
//&& m_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject // only emojis are objects here, no tabs
|
|
&& i - start < kMaxItemLength) {
|
|
continue;
|
|
}
|
|
m_items.append(QScriptItem(start, m_analysis[start]));
|
|
start = i;
|
|
startBlock = currentBlock;
|
|
}
|
|
m_items.append(QScriptItem(start, m_analysis[start]));
|
|
}
|
|
}
|
|
|
|
void StackEngine::updateFont(not_null<const AbstractBlock*> block) {
|
|
const auto flags = block->flags();
|
|
const auto newFont = WithFlags(_t->_st->font, flags);
|
|
if (_font != newFont) {
|
|
_font = (newFont->family() == _t->_st->font->family())
|
|
? WithFlags(_t->_st->font, flags, newFont->flags())
|
|
: newFont;
|
|
_engine.fnt = _font->f;
|
|
_engine.resetFontEngineCache();
|
|
}
|
|
}
|
|
|
|
std::vector<Block>::const_iterator StackEngine::shapeGetBlock(int item) {
|
|
auto &si = _engine.layoutData->items[item];
|
|
const auto blockIt = adjustBlock(_offset + si.position);
|
|
const auto block = blockIt->get();
|
|
updateFont(block);
|
|
_engine.shape(item);
|
|
if (si.analysis.flags == QScriptAnalysis::Object) {
|
|
si.width = block->objectWidth();
|
|
}
|
|
return blockIt;
|
|
}
|
|
|
|
int StackEngine::blockIndex(int position) const {
|
|
return int(adjustBlock(_offset + position) - begin(_tBlocks));
|
|
}
|
|
|
|
} // namespace Ui::Text
|