Add per-line geometry / colors customization for Text.
This commit is contained in:
parent
93458e4cb3
commit
02440524ea
19 changed files with 1087 additions and 875 deletions
|
|
@ -16,7 +16,7 @@ TextPalette {
|
||||||
selectMonoFg: color;
|
selectMonoFg: color;
|
||||||
selectSpoilerFg: color;
|
selectSpoilerFg: color;
|
||||||
selectOverlay: color;
|
selectOverlay: color;
|
||||||
linkAlwaysActive: int;
|
linkAlwaysActive: bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle {
|
TextStyle {
|
||||||
|
|
|
||||||
301
ui/text/text.cpp
301
ui/text/text.cpp
|
|
@ -15,6 +15,12 @@
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "styles/style_basic.h"
|
#include "styles/style_basic.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
const QString kQEllipsis = u"..."_q;
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -125,6 +131,52 @@ not_null<SpoilerMessCache*> DefaultSpoilerCache() {
|
||||||
return &data.cache;
|
return &data.cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GeometryDescriptor SimpleGeometry(
|
||||||
|
int availableWidth,
|
||||||
|
int fontHeight,
|
||||||
|
int elisionHeight,
|
||||||
|
int elisionRemoveFromEnd,
|
||||||
|
bool elisionOneLine,
|
||||||
|
bool elisionBreakEverywhere) {
|
||||||
|
constexpr auto wrap = [](
|
||||||
|
Fn<LineGeometry(LineGeometry, uint16)> layout,
|
||||||
|
bool breakEverywhere = false) {
|
||||||
|
return GeometryDescriptor{ std::move(layout), breakEverywhere };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to minimize captured values (to minimize Fn allocations).
|
||||||
|
if (!elisionOneLine && !elisionHeight) {
|
||||||
|
return wrap([=](LineGeometry line, uint16 positino) {
|
||||||
|
line.width = availableWidth;
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
} else if (elisionOneLine) {
|
||||||
|
return wrap([=](LineGeometry line, uint16 position) {
|
||||||
|
line.elided = true;
|
||||||
|
line.width = availableWidth - elisionRemoveFromEnd;
|
||||||
|
return line;
|
||||||
|
}, elisionBreakEverywhere);
|
||||||
|
} else if (!elisionRemoveFromEnd) {
|
||||||
|
return wrap([=](LineGeometry line, uint16 position) {
|
||||||
|
if (line.top + fontHeight * 2 > elisionHeight) {
|
||||||
|
line.elided = true;
|
||||||
|
}
|
||||||
|
line.width = availableWidth;
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return wrap([=](LineGeometry line, uint16 position) {
|
||||||
|
if (line.top + fontHeight * 2 > elisionHeight) {
|
||||||
|
line.elided = true;
|
||||||
|
line.width = availableWidth - elisionRemoveFromEnd;
|
||||||
|
} else {
|
||||||
|
line.width = availableWidth;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default;
|
String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default;
|
||||||
|
|
||||||
String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept
|
String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept
|
||||||
|
|
@ -188,7 +240,9 @@ void String::setText(const style::TextStyle &st, const QString &text, const Text
|
||||||
recountNaturalSize(true, options.dir);
|
recountNaturalSize(true, options.dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
void String::recountNaturalSize(
|
||||||
|
bool initial,
|
||||||
|
Qt::LayoutDirection optionsDirection) {
|
||||||
NewlineBlock *lastNewline = 0;
|
NewlineBlock *lastNewline = 0;
|
||||||
|
|
||||||
_maxWidth = _minHeight = 0;
|
_maxWidth = _minHeight = 0;
|
||||||
|
|
@ -199,20 +253,23 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
auto b = block.get();
|
auto b = block.get();
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
auto blockHeight = CountBlockHeight(b, _st);
|
auto blockHeight = CountBlockHeight(b, _st);
|
||||||
if (_btype == TextBlockTNewline) {
|
if (_btype == TextBlockType::Newline) {
|
||||||
if (!lineHeight) lineHeight = blockHeight;
|
if (!lineHeight) lineHeight = blockHeight;
|
||||||
if (initial) {
|
if (initial) {
|
||||||
Qt::LayoutDirection dir = optionsDir;
|
Qt::LayoutDirection direction = optionsDirection;
|
||||||
if (dir == Qt::LayoutDirectionAuto) {
|
if (direction == Qt::LayoutDirectionAuto) {
|
||||||
dir = StringDirection(_text, lastNewlineStart, b->from());
|
direction = StringDirection(
|
||||||
|
_text,
|
||||||
|
lastNewlineStart,
|
||||||
|
b->position());
|
||||||
}
|
}
|
||||||
if (lastNewline) {
|
if (lastNewline) {
|
||||||
lastNewline->_nextDir = dir;
|
lastNewline->_nextDirection = direction;
|
||||||
} else {
|
} else {
|
||||||
_startDir = dir;
|
_startDirection = direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastNewlineStart = b->from();
|
lastNewlineStart = b->position();
|
||||||
lastNewline = &block.unsafe<NewlineBlock>();
|
lastNewline = &block.unsafe<NewlineBlock>();
|
||||||
|
|
||||||
_minHeight += lineHeight;
|
_minHeight += lineHeight;
|
||||||
|
|
@ -244,14 +301,14 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (initial) {
|
if (initial) {
|
||||||
Qt::LayoutDirection dir = optionsDir;
|
Qt::LayoutDirection direction = optionsDirection;
|
||||||
if (dir == Qt::LayoutDirectionAuto) {
|
if (direction == Qt::LayoutDirectionAuto) {
|
||||||
dir = StringDirection(_text, lastNewlineStart, _text.size());
|
direction = StringDirection(_text, lastNewlineStart, _text.size());
|
||||||
}
|
}
|
||||||
if (lastNewline) {
|
if (lastNewline) {
|
||||||
lastNewline->_nextDir = dir;
|
lastNewline->_nextDirection = direction;
|
||||||
} else {
|
} else {
|
||||||
_startDir = dir;
|
_startDirection = direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_width > 0) {
|
if (_width > 0) {
|
||||||
|
|
@ -269,7 +326,7 @@ int String::countMaxMonospaceWidth() const {
|
||||||
for (auto &block : _blocks) {
|
for (auto &block : _blocks) {
|
||||||
auto b = block.get();
|
auto b = block.get();
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
if (_btype == TextBlockTNewline) {
|
if (_btype == TextBlockType::Newline) {
|
||||||
last_rBearing = b->f_rbearing();
|
last_rBearing = b->f_rbearing();
|
||||||
last_rPadding = b->f_rpadding();
|
last_rPadding = b->f_rpadding();
|
||||||
|
|
||||||
|
|
@ -283,8 +340,8 @@ int String::countMaxMonospaceWidth() const {
|
||||||
_width = (b->f_width() - last_rBearing);
|
_width = (b->f_width() - last_rBearing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!(b->flags() & (TextBlockFPre | TextBlockFCode))
|
if (!(b->flags() & (TextBlockFlag::Pre | TextBlockFlag::Code))
|
||||||
&& (b->type() != TextBlockTSkip)) {
|
&& (b->type() != TextBlockType::Skip)) {
|
||||||
fullMonospace = false;
|
fullMonospace = false;
|
||||||
}
|
}
|
||||||
auto b__f_rbearing = b->f_rbearing(); // cache
|
auto b__f_rbearing = b->f_rbearing(); // cache
|
||||||
|
|
@ -347,9 +404,10 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t
|
||||||
recountNaturalSize(true, options.dir);
|
recountNaturalSize(true, options.dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
void String::setLink(uint16 index, const ClickHandlerPtr &link) {
|
||||||
if (!lnkIndex || lnkIndex > _links.size()) return;
|
if (index > 0 && index <= _links.size()) {
|
||||||
_links[lnkIndex - 1] = lnk;
|
_links[index - 1] = link;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
||||||
|
|
@ -394,16 +452,17 @@ bool String::hasSpoilers() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::hasSkipBlock() const {
|
bool String::hasSkipBlock() const {
|
||||||
return _blocks.empty() ? false : _blocks.back()->type() == TextBlockTSkip;
|
return !_blocks.empty()
|
||||||
|
&& (_blocks.back()->type() == TextBlockType::Skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::updateSkipBlock(int width, int height) {
|
bool String::updateSkipBlock(int width, int height) {
|
||||||
if (!_blocks.empty() && _blocks.back()->type() == TextBlockTSkip) {
|
if (!_blocks.empty() && _blocks.back()->type() == TextBlockType::Skip) {
|
||||||
const auto block = static_cast<SkipBlock*>(_blocks.back().get());
|
const auto block = static_cast<SkipBlock*>(_blocks.back().get());
|
||||||
if (block->width() == width && block->height() == height) {
|
if (block->f_width().toInt() == width && block->height() == height) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_text.resize(block->from());
|
_text.resize(block->position());
|
||||||
_blocks.pop_back();
|
_blocks.pop_back();
|
||||||
}
|
}
|
||||||
_text.push_back('_');
|
_text.push_back('_');
|
||||||
|
|
@ -420,10 +479,10 @@ bool String::updateSkipBlock(int width, int height) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::removeSkipBlock() {
|
bool String::removeSkipBlock() {
|
||||||
if (_blocks.empty() || _blocks.back()->type() != TextBlockTSkip) {
|
if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_text.resize(_blocks.back()->from());
|
_text.resize(_blocks.back()->position());
|
||||||
_blocks.pop_back();
|
_blocks.pop_back();
|
||||||
recountNaturalSize(false);
|
recountNaturalSize(false);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -454,10 +513,21 @@ int String::countHeight(int width, bool breakEverywhere) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere) const {
|
std::vector<int> String::countLineWidths(int width) const {
|
||||||
enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int lineHeight) {
|
return countLineWidths(width, {});
|
||||||
lineWidths->push_back(lineWidth.ceil().toInt());
|
}
|
||||||
|
|
||||||
|
std::vector<int> String::countLineWidths(
|
||||||
|
int width,
|
||||||
|
LineWidthsOptions options) const {
|
||||||
|
auto result = std::vector<int>();
|
||||||
|
if (options.reserve) {
|
||||||
|
result.reserve(options.reserve);
|
||||||
|
}
|
||||||
|
enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int lineHeight) {
|
||||||
|
result.push_back(lineWidth.ceil().toInt());
|
||||||
});
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Callback>
|
template <typename Callback>
|
||||||
|
|
@ -475,7 +545,7 @@ void String::enumerateLines(
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
int blockHeight = CountBlockHeight(b.get(), _st);
|
int blockHeight = CountBlockHeight(b.get(), _st);
|
||||||
|
|
||||||
if (_btype == TextBlockTNewline) {
|
if (_btype == TextBlockType::Newline) {
|
||||||
if (!lineHeight) lineHeight = blockHeight;
|
if (!lineHeight) lineHeight = blockHeight;
|
||||||
callback(width - widthLeft, lineHeight);
|
callback(width - widthLeft, lineHeight);
|
||||||
|
|
||||||
|
|
@ -500,7 +570,7 @@ void String::enumerateLines(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_btype == TextBlockTText) {
|
if (_btype == TextBlockType::Text) {
|
||||||
const auto t = &b.unsafe<TextBlock>();
|
const auto t = &b.unsafe<TextBlock>();
|
||||||
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
||||||
last_rPadding += b->f_rpadding();
|
last_rPadding += b->f_rpadding();
|
||||||
|
|
@ -577,6 +647,13 @@ void String::draw(QPainter &p, const PaintContext &context) const {
|
||||||
Renderer(*this).draw(p, context);
|
Renderer(*this).draw(p, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StateResult String::getState(
|
||||||
|
QPoint point,
|
||||||
|
GeometryDescriptor geometry,
|
||||||
|
StateRequest request) const {
|
||||||
|
return Renderer(*this).getState(point, std::move(geometry), request);
|
||||||
|
}
|
||||||
|
|
||||||
void String::draw(Painter &p, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection, bool fullWidthSelection) const {
|
void String::draw(Painter &p, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection, bool fullWidthSelection) const {
|
||||||
// p.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
|
// p.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
|
||||||
Renderer(*this).draw(p, {
|
Renderer(*this).draw(p, {
|
||||||
|
|
@ -605,14 +682,16 @@ void String::drawElided(Painter &p, int32 left, int32 top, int32 w, int32 lines,
|
||||||
.palette = &p.textPalette(),
|
.palette = &p.textPalette(),
|
||||||
.paused = p.inactive(),
|
.paused = p.inactive(),
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.elisionLines = lines,
|
.elisionHeight = (lines > 1) ? (lines * _st->font->height) : 0,
|
||||||
.elisionRemoveFromEnd = removeFromEnd,
|
.elisionRemoveFromEnd = removeFromEnd,
|
||||||
|
.elisionOneLine = (lines == 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
|
void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
|
||||||
Renderer(*this).draw(p, {
|
Renderer(*this).draw(p, {
|
||||||
.position = { left, top },
|
.position = { left, top },
|
||||||
|
//.outerWidth = outerw,
|
||||||
.availableWidth = width,
|
.availableWidth = width,
|
||||||
.align = align,
|
.align = align,
|
||||||
.clip = (yTo >= 0
|
.clip = (yTo >= 0
|
||||||
|
|
@ -622,7 +701,6 @@ void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 oute
|
||||||
.paused = p.inactive(),
|
.paused = p.inactive(),
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
});
|
});
|
||||||
draw(p, style::RightToLeft() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
|
void String::drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
|
||||||
|
|
@ -638,7 +716,10 @@ void String::drawRightElided(Painter &p, int32 right, int32 top, int32 width, in
|
||||||
}
|
}
|
||||||
|
|
||||||
StateResult String::getState(QPoint point, int width, StateRequest request) const {
|
StateResult String::getState(QPoint point, int width, StateRequest request) const {
|
||||||
return Renderer(*this).getState(point, width, request);
|
return Renderer(*this).getState(
|
||||||
|
point,
|
||||||
|
SimpleGeometry(width, _st->font->height, 0, 0, false, false),
|
||||||
|
request);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateResult String::getStateLeft(QPoint point, int width, int outerw, StateRequest request) const {
|
StateResult String::getStateLeft(QPoint point, int width, int outerw, StateRequest request) const {
|
||||||
|
|
@ -646,7 +727,14 @@ StateResult String::getStateLeft(QPoint point, int width, int outerw, StateReque
|
||||||
}
|
}
|
||||||
|
|
||||||
StateResult String::getStateElided(QPoint point, int width, StateRequestElided request) const {
|
StateResult String::getStateElided(QPoint point, int width, StateRequestElided request) const {
|
||||||
return Renderer(*this).getStateElided(point, width, request);
|
return Renderer(*this).getState(point, SimpleGeometry(
|
||||||
|
width,
|
||||||
|
_st->font->height,
|
||||||
|
(request.lines > 1) ? (request.lines * _st->font->height) : 0,
|
||||||
|
request.removeFromEnd,
|
||||||
|
(request.lines == 1),
|
||||||
|
request.flags & StateRequest::Flag::BreakEverywhere
|
||||||
|
), static_cast<StateRequest>(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
StateResult String::getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request) const {
|
StateResult String::getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request) const {
|
||||||
|
|
@ -661,7 +749,7 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se
|
||||||
|
|
||||||
// Full selection of monospace entity.
|
// Full selection of monospace entity.
|
||||||
for (const auto &b : _blocks) {
|
for (const auto &b : _blocks) {
|
||||||
if (b->from() < from) {
|
if (b->position() < from) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!IsMono(b->flags())) {
|
if (!IsMono(b->flags())) {
|
||||||
|
|
@ -723,15 +811,15 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::isEmpty() const {
|
bool String::isEmpty() const {
|
||||||
return _blocks.empty() || _blocks[0]->type() == TextBlockTSkip;
|
return _blocks.empty() || _blocks[0]->type() == TextBlockType::Skip;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
|
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
|
||||||
return (i + 1 == e) ? _text.size() : (*(i + 1))->from();
|
return (i + 1 == e) ? _text.size() : (*(i + 1))->position();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, const String::TextBlocks::const_iterator &e) const {
|
uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, const String::TextBlocks::const_iterator &e) const {
|
||||||
return countBlockEnd(i, e) - (*i)->from();
|
return countBlockEnd(i, e) - (*i)->position();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
|
|
@ -749,92 +837,67 @@ void String::enumerateText(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lnkIndex = 0;
|
int linkIndex = 0;
|
||||||
uint16 lnkFrom = 0;
|
uint16 linkPosition = 0;
|
||||||
|
|
||||||
int spoilerIndex = 0;
|
|
||||||
uint16 spoilerFrom = 0;
|
|
||||||
|
|
||||||
int32 flags = 0;
|
int32 flags = 0;
|
||||||
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
|
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
|
||||||
int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
|
const auto blockPosition = (i == e) ? uint16(_text.size()) : (*i)->position();
|
||||||
int blockSpoilerIndex = (i == e) ? 0 : (*i)->spoilerIndex();
|
const auto blockFlags = (i == e) ? TextBlockFlags() : (*i)->flags();
|
||||||
uint16 blockFrom = (i == e) ? _text.size() : (*i)->from();
|
const auto blockLinkIndex = [&] {
|
||||||
int32 blockFlags = (i == e) ? 0 : (*i)->flags();
|
if (IsMono(blockFlags) || (i == e)) {
|
||||||
|
return 0;
|
||||||
if (IsMono(blockFlags)) {
|
}
|
||||||
blockLnkIndex = 0;
|
const auto result = (*i)->linkIndex();
|
||||||
}
|
return (result && _links.at(result - 1)) ? result : 0;
|
||||||
if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links
|
}();
|
||||||
blockLnkIndex = 0;
|
if (blockLinkIndex != linkIndex) {
|
||||||
}
|
if (linkIndex) {
|
||||||
if (blockLnkIndex != lnkIndex) {
|
auto rangeFrom = qMax(selection.from, linkPosition);
|
||||||
if (lnkIndex) {
|
auto rangeTo = qMin(selection.to, blockPosition);
|
||||||
auto rangeFrom = qMax(selection.from, lnkFrom);
|
|
||||||
auto rangeTo = qMin(selection.to, blockFrom);
|
|
||||||
if (rangeTo > rangeFrom) { // handle click handler
|
if (rangeTo > rangeFrom) { // handle click handler
|
||||||
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
||||||
// Ignore links that are partially copied.
|
// Ignore links that are partially copied.
|
||||||
const auto handler = (lnkFrom != rangeFrom || blockFrom != rangeTo)
|
const auto handler = (linkPosition != rangeFrom || blockPosition != rangeTo)
|
||||||
? nullptr
|
? nullptr
|
||||||
: _links.at(lnkIndex - 1);
|
: _links.at(linkIndex - 1);
|
||||||
const auto type = handler
|
const auto type = handler
|
||||||
? handler->getTextEntity().type
|
? handler->getTextEntity().type
|
||||||
: EntityType::Invalid;
|
: EntityType::Invalid;
|
||||||
clickHandlerFinishCallback(r, handler, type);
|
clickHandlerFinishCallback(r, handler, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lnkIndex = blockLnkIndex;
|
linkIndex = blockLinkIndex;
|
||||||
if (lnkIndex) {
|
if (linkIndex) {
|
||||||
lnkFrom = blockFrom;
|
linkPosition = blockPosition;
|
||||||
const auto handler = _links.at(lnkIndex - 1);
|
const auto handler = _links.at(linkIndex - 1);
|
||||||
clickHandlerStartCallback(handler
|
clickHandlerStartCallback(handler
|
||||||
? handler->getTextEntity().type
|
? handler->getTextEntity().type
|
||||||
: EntityType::Invalid);
|
: EntityType::Invalid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blockSpoilerIndex != spoilerIndex) {
|
|
||||||
if (spoilerIndex) {
|
|
||||||
auto rangeFrom = qMax(selection.from, spoilerFrom);
|
|
||||||
auto rangeTo = qMin(selection.to, blockFrom);
|
|
||||||
if (rangeTo > rangeFrom) { // handle click handler
|
|
||||||
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
|
||||||
// Ignore links that are partially copied.
|
|
||||||
const auto handler = (spoilerFrom != rangeFrom
|
|
||||||
|| blockFrom != rangeTo
|
|
||||||
|| !_spoiler.data)
|
|
||||||
? nullptr
|
|
||||||
: _spoiler.data->link;
|
|
||||||
const auto type = EntityType::Spoiler;
|
|
||||||
clickHandlerFinishCallback(r, handler, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spoilerIndex = blockSpoilerIndex;
|
|
||||||
if (spoilerIndex) {
|
|
||||||
spoilerFrom = blockFrom;
|
|
||||||
clickHandlerStartCallback(EntityType::Spoiler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto checkBlockFlags = (blockFrom >= selection.from)
|
const auto checkBlockFlags = (blockPosition >= selection.from)
|
||||||
&& (blockFrom <= selection.to);
|
&& (blockPosition <= selection.to);
|
||||||
if (checkBlockFlags && blockFlags != flags) {
|
if (checkBlockFlags && blockFlags != flags) {
|
||||||
flagsChangeCallback(flags, blockFlags);
|
flagsChangeCallback(flags, blockFlags);
|
||||||
flags = blockFlags;
|
flags = blockFlags;
|
||||||
}
|
}
|
||||||
if (i == e || (lnkIndex ? lnkFrom : blockFrom) >= selection.to) {
|
if (i == e || (linkIndex ? linkPosition : blockPosition) >= selection.to) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto blockType = (*i)->type();
|
const auto blockType = (*i)->type();
|
||||||
if (blockType == TextBlockTSkip) continue;
|
if (blockType == TextBlockType::Skip) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto rangeFrom = qMax(selection.from, blockFrom);
|
auto rangeFrom = qMax(selection.from, blockPosition);
|
||||||
auto rangeTo = qMin(
|
auto rangeTo = qMin(
|
||||||
selection.to,
|
selection.to,
|
||||||
uint16(blockFrom + countBlockLength(i, e)));
|
uint16(blockPosition + countBlockLength(i, e)));
|
||||||
if (rangeTo > rangeFrom) {
|
if (rangeTo > rangeFrom) {
|
||||||
const auto customEmojiData = (blockType == TextBlockTCustomEmoji)
|
const auto customEmojiData = (blockType == TextBlockType::CustomEmoji)
|
||||||
? static_cast<const CustomEmojiBlock*>(i->get())->_custom->entityData()
|
? static_cast<const CustomEmojiBlock*>(i->get())->_custom->entityData()
|
||||||
: QString();
|
: QString();
|
||||||
appendPartCallback(
|
appendPartCallback(
|
||||||
|
|
@ -852,7 +915,7 @@ void String::unloadPersistentAnimation() {
|
||||||
if (_hasCustomEmoji) {
|
if (_hasCustomEmoji) {
|
||||||
for (const auto &block : _blocks) {
|
for (const auto &block : _blocks) {
|
||||||
const auto raw = block.get();
|
const auto raw = block.get();
|
||||||
if (raw->type() == TextBlockTCustomEmoji) {
|
if (raw->type() == TextBlockType::CustomEmoji) {
|
||||||
static_cast<const CustomEmojiBlock*>(raw)->_custom->unload();
|
static_cast<const CustomEmojiBlock*>(raw)->_custom->unload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -871,12 +934,12 @@ OnlyCustomEmoji String::toOnlyCustomEmoji() const {
|
||||||
result.lines.emplace_back();
|
result.lines.emplace_back();
|
||||||
for (const auto &block : _blocks) {
|
for (const auto &block : _blocks) {
|
||||||
const auto raw = block.get();
|
const auto raw = block.get();
|
||||||
if (raw->type() == TextBlockTCustomEmoji) {
|
if (raw->type() == TextBlockType::CustomEmoji) {
|
||||||
const auto custom = static_cast<const CustomEmojiBlock*>(raw);
|
const auto custom = static_cast<const CustomEmojiBlock*>(raw);
|
||||||
result.lines.back().push_back({
|
result.lines.back().push_back({
|
||||||
.entityData = custom->_custom->entityData(),
|
.entityData = custom->_custom->entityData(),
|
||||||
});
|
});
|
||||||
} else if (raw->type() == TextBlockTNewline) {
|
} else if (raw->type() == TextBlockType::Newline) {
|
||||||
result.lines.emplace_back();
|
result.lines.emplace_back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -925,17 +988,16 @@ TextForMimeData String::toText(
|
||||||
result.rich.entities.insert(i, std::move(entity));
|
result.rich.entities.insert(i, std::move(entity));
|
||||||
};
|
};
|
||||||
auto linkStart = 0;
|
auto linkStart = 0;
|
||||||
auto spoilerStart = 0;
|
|
||||||
auto markdownTrackers = composeEntities
|
auto markdownTrackers = composeEntities
|
||||||
? std::vector<MarkdownTagTracker>{
|
? std::vector<MarkdownTagTracker>{
|
||||||
{ TextBlockFItalic, EntityType::Italic },
|
{ TextBlockFlag::Italic, EntityType::Italic },
|
||||||
{ TextBlockFBold, EntityType::Bold },
|
{ TextBlockFlag::Bold, EntityType::Bold },
|
||||||
{ TextBlockFSemibold, EntityType::Semibold },
|
{ TextBlockFlag::Semibold, EntityType::Semibold },
|
||||||
{ TextBlockFUnderline, EntityType::Underline },
|
{ TextBlockFlag::Underline, EntityType::Underline },
|
||||||
{ TextBlockFPlainLink, EntityType::PlainLink },
|
{ TextBlockFlag::Spoiler, EntityType::Spoiler },
|
||||||
{ TextBlockFStrikeOut, EntityType::StrikeOut },
|
{ TextBlockFlag::StrikeOut, EntityType::StrikeOut },
|
||||||
{ TextBlockFCode, EntityType::Code }, // #TODO entities
|
{ TextBlockFlag::Code, EntityType::Code }, // #TODO entities
|
||||||
{ TextBlockFPre, EntityType::Pre },
|
{ TextBlockFlag::Pre, EntityType::Pre },
|
||||||
} : std::vector<MarkdownTagTracker>();
|
} : std::vector<MarkdownTagTracker>();
|
||||||
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
|
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
|
||||||
if (!composeEntities) {
|
if (!composeEntities) {
|
||||||
|
|
@ -954,25 +1016,12 @@ TextForMimeData String::toText(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const auto clickHandlerStartCallback = [&](EntityType type) {
|
const auto clickHandlerStartCallback = [&](EntityType type) {
|
||||||
if (type == EntityType::Spoiler) {
|
linkStart = result.rich.text.size();
|
||||||
spoilerStart = result.rich.text.size();
|
|
||||||
} else {
|
|
||||||
linkStart = result.rich.text.size();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const auto clickHandlerFinishCallback = [&](
|
const auto clickHandlerFinishCallback = [&](
|
||||||
QStringView inText,
|
QStringView inText,
|
||||||
const ClickHandlerPtr &handler,
|
const ClickHandlerPtr &handler,
|
||||||
EntityType type) {
|
EntityType type) {
|
||||||
if (type == EntityType::Spoiler) {
|
|
||||||
insertEntity({
|
|
||||||
type,
|
|
||||||
spoilerStart,
|
|
||||||
int(result.rich.text.size() - spoilerStart),
|
|
||||||
QString(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!handler || (!composeExpanded && !composeEntities)) {
|
if (!handler || (!composeExpanded && !composeEntities)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1061,21 +1110,21 @@ IsolatedEmoji String::toIsolatedEmoji() const {
|
||||||
}
|
}
|
||||||
auto result = IsolatedEmoji();
|
auto result = IsolatedEmoji();
|
||||||
const auto skip = (_blocks.empty()
|
const auto skip = (_blocks.empty()
|
||||||
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1;
|
|| _blocks.back()->type() != TextBlockType::Skip) ? 0 : 1;
|
||||||
if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) {
|
if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto index = 0;
|
auto index = 0;
|
||||||
for (const auto &block : _blocks) {
|
for (const auto &block : _blocks) {
|
||||||
const auto type = block->type();
|
const auto type = block->type();
|
||||||
if (block->lnkIndex()) {
|
if (block->linkIndex()) {
|
||||||
return {};
|
return {};
|
||||||
} else if (type == TextBlockTEmoji) {
|
} else if (type == TextBlockType::Emoji) {
|
||||||
result.items[index++] = block.unsafe<EmojiBlock>()._emoji;
|
result.items[index++] = block.unsafe<EmojiBlock>()._emoji;
|
||||||
} else if (type == TextBlockTCustomEmoji) {
|
} else if (type == TextBlockType::CustomEmoji) {
|
||||||
result.items[index++]
|
result.items[index++]
|
||||||
= block.unsafe<CustomEmojiBlock>()._custom->entityData();
|
= block.unsafe<CustomEmojiBlock>()._custom->entityData();
|
||||||
} else if (type != TextBlockTSkip) {
|
} else if (type != TextBlockType::Skip) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1092,7 +1141,7 @@ void String::clearFields() {
|
||||||
_links.clear();
|
_links.clear();
|
||||||
_spoiler.data = nullptr;
|
_spoiler.data = nullptr;
|
||||||
_maxWidth = _minHeight = 0;
|
_maxWidth = _minHeight = 0;
|
||||||
_startDir = Qt::LayoutDirectionAuto;
|
_startDirection = Qt::LayoutDirectionAuto;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsBad(QChar ch) {
|
bool IsBad(QChar ch) {
|
||||||
|
|
@ -1186,7 +1235,7 @@ bool IsSpace(QChar ch) {
|
||||||
|| (ch == QChar(8203)/*Zero width space.*/);
|
|| (ch == QChar(8203)/*Zero width space.*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsDiac(QChar ch) { // diac and variation selectors
|
bool IsDiacritic(QChar ch) { // diacritic and variation selectors
|
||||||
return (ch.category() == QChar::Mark_NonSpacing)
|
return (ch.category() == QChar::Mark_NonSpacing)
|
||||||
|| (ch == 1652)
|
|| (ch == 1652)
|
||||||
|| (ch >= 64606 && ch <= 64611);
|
|| (ch >= 64606 && ch <= 64611);
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,9 @@ struct TextPalette;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
static const auto kQEllipsis = QStringLiteral("...");
|
extern const QString kQEllipsis;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
static const QChar TextCommand(0x0010);
|
|
||||||
|
|
||||||
struct TextParseOptions {
|
struct TextParseOptions {
|
||||||
int32 flags;
|
int32 flags;
|
||||||
int32 maxw;
|
int32 maxw;
|
||||||
|
|
@ -125,16 +123,42 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SpecialColor {
|
||||||
|
const QPen *pen = nullptr;
|
||||||
|
const QPen *penSelected = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LineGeometry {
|
||||||
|
int left = 0;
|
||||||
|
int top = 0;
|
||||||
|
int width = 0;
|
||||||
|
bool elided = false;
|
||||||
|
};
|
||||||
|
struct GeometryDescriptor {
|
||||||
|
Fn<LineGeometry(LineGeometry line, uint16 position)> layout;
|
||||||
|
bool breakEverywhere = false;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] not_null<SpoilerMessCache*> DefaultSpoilerCache();
|
[[nodiscard]] not_null<SpoilerMessCache*> DefaultSpoilerCache();
|
||||||
|
|
||||||
|
[[nodiscard]] GeometryDescriptor SimpleGeometry(
|
||||||
|
int availableWidth,
|
||||||
|
int fontHeight,
|
||||||
|
int elisionHeight,
|
||||||
|
int elisionRemoveFromEnd,
|
||||||
|
bool elisionOneLine,
|
||||||
|
bool elisionBreakEverywhere);
|
||||||
|
|
||||||
struct PaintContext {
|
struct PaintContext {
|
||||||
QPoint position;
|
QPoint position;
|
||||||
int outerWidth = 0; // For automatic RTL Ui inversion.
|
int outerWidth = 0; // For automatic RTL Ui inversion.
|
||||||
int availableWidth = 0;
|
int availableWidth = 0;
|
||||||
|
GeometryDescriptor geometry; // By default is SimpleGeometry.
|
||||||
style::align align = style::al_left;
|
style::align align = style::al_left;
|
||||||
QRect clip;
|
QRect clip;
|
||||||
|
|
||||||
const style::TextPalette *palette = nullptr;
|
const style::TextPalette *palette = nullptr;
|
||||||
|
std::span<SpecialColor> colors;
|
||||||
SpoilerMessCache *spoiler = nullptr;
|
SpoilerMessCache *spoiler = nullptr;
|
||||||
crl::time now = 0;
|
crl::time now = 0;
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
|
|
@ -144,8 +168,9 @@ struct PaintContext {
|
||||||
TextSelection selection;
|
TextSelection selection;
|
||||||
bool fullWidthSelection = true;
|
bool fullWidthSelection = true;
|
||||||
|
|
||||||
int elisionLines = 0;
|
int elisionHeight = 0;
|
||||||
int elisionRemoveFromEnd = 0;
|
int elisionRemoveFromEnd = 0;
|
||||||
|
bool elisionOneLine = false;
|
||||||
bool elisionBreakEverywhere = false;
|
bool elisionBreakEverywhere = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -167,14 +192,42 @@ public:
|
||||||
String &operator=(String &&other);
|
String &operator=(String &&other);
|
||||||
~String();
|
~String();
|
||||||
|
|
||||||
[[nodiscard]] int countWidth(int width, bool breakEverywhere = false) const;
|
[[nodiscard]] int countWidth(
|
||||||
[[nodiscard]] int countHeight(int width, bool breakEverywhere = false) const;
|
int width,
|
||||||
void countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere = false) const;
|
bool breakEverywhere = false) const;
|
||||||
|
[[nodiscard]] int countHeight(
|
||||||
|
int width,
|
||||||
|
bool breakEverywhere = false) const;
|
||||||
|
|
||||||
|
struct LineWidthsOptions {
|
||||||
|
bool breakEverywhere = false;
|
||||||
|
int reserve = 0;
|
||||||
|
};
|
||||||
|
[[nodiscard]] std::vector<int> countLineWidths(int width) const;
|
||||||
|
[[nodiscard]] std::vector<int> countLineWidths(
|
||||||
|
int width,
|
||||||
|
LineWidthsOptions options) const;
|
||||||
|
|
||||||
|
struct DimensionsResult {
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
std::vector<int> lineWidths;
|
||||||
|
};
|
||||||
|
struct DimensionsRequest {
|
||||||
|
bool lineWidths = false;
|
||||||
|
int reserve = 0;
|
||||||
|
};
|
||||||
|
[[nodiscard]] DimensionsResult countDimensions(
|
||||||
|
GeometryDescriptor geometry) const;
|
||||||
|
[[nodiscard]] DimensionsResult countDimensions(
|
||||||
|
GeometryDescriptor geometry,
|
||||||
|
DimensionsRequest request) const;
|
||||||
|
|
||||||
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = kDefaultTextOptions);
|
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = kDefaultTextOptions);
|
||||||
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {});
|
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {});
|
||||||
|
|
||||||
[[nodiscard]] bool hasLinks() const;
|
[[nodiscard]] bool hasLinks() const;
|
||||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
void setLink(uint16 index, const ClickHandlerPtr &lnk);
|
||||||
|
|
||||||
[[nodiscard]] bool hasSpoilers() const;
|
[[nodiscard]] bool hasSpoilers() const;
|
||||||
void setSpoilerRevealed(bool revealed, anim::type animated);
|
void setSpoilerRevealed(bool revealed, anim::type animated);
|
||||||
|
|
@ -193,6 +246,10 @@ public:
|
||||||
[[nodiscard]] int countMaxMonospaceWidth() const;
|
[[nodiscard]] int countMaxMonospaceWidth() const;
|
||||||
|
|
||||||
void draw(QPainter &p, const PaintContext &context) const;
|
void draw(QPainter &p, const PaintContext &context) const;
|
||||||
|
[[nodiscard]] StateResult getState(
|
||||||
|
QPoint point,
|
||||||
|
GeometryDescriptor geometry,
|
||||||
|
StateRequest request = StateRequest()) const;
|
||||||
|
|
||||||
void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const;
|
void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const;
|
||||||
void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
|
void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
|
||||||
|
|
@ -298,7 +355,7 @@ private:
|
||||||
TextBlocks _blocks;
|
TextBlocks _blocks;
|
||||||
TextLinks _links;
|
TextLinks _links;
|
||||||
|
|
||||||
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
|
Qt::LayoutDirection _startDirection = Qt::LayoutDirectionAuto;
|
||||||
|
|
||||||
SpoilerDataWrap _spoiler;
|
SpoilerDataWrap _spoiler;
|
||||||
|
|
||||||
|
|
@ -313,7 +370,7 @@ private:
|
||||||
[[nodiscard]] bool IsLinkEnd(QChar ch);
|
[[nodiscard]] bool IsLinkEnd(QChar ch);
|
||||||
[[nodiscard]] bool IsNewline(QChar ch);
|
[[nodiscard]] bool IsNewline(QChar ch);
|
||||||
[[nodiscard]] bool IsSpace(QChar ch);
|
[[nodiscard]] bool IsSpace(QChar ch);
|
||||||
[[nodiscard]] bool IsDiac(QChar ch);
|
[[nodiscard]] bool IsDiacritic(QChar ch);
|
||||||
[[nodiscard]] bool IsReplacedBySpace(QChar ch);
|
[[nodiscard]] bool IsReplacedBySpace(QChar ch);
|
||||||
[[nodiscard]] bool IsTrimmed(QChar ch);
|
[[nodiscard]] bool IsTrimmed(QChar ch);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ struct LineBreakHelper {
|
||||||
void calculateRightBearingForPreviousGlyph();
|
void calculateRightBearingForPreviousGlyph();
|
||||||
|
|
||||||
// We always calculate the right bearing right before it is needed.
|
// We always calculate the right bearing right before it is needed.
|
||||||
// So we don't need caching / optimizations referred to delayed right bearing calculations.
|
// So we don't need caching / optimizations referred to
|
||||||
|
// delayed right bearing calculations.
|
||||||
|
|
||||||
//static const QFixed RightBearingNotCalculated;
|
//static const QFixed RightBearingNotCalculated;
|
||||||
|
|
||||||
|
|
@ -156,15 +157,6 @@ void addNextCluster(
|
||||||
} while (glyphPosition < current.num_glyphs
|
} while (glyphPosition < current.num_glyphs
|
||||||
&& !glyphs.attributes[glyphPosition].clusterStart);
|
&& !glyphs.attributes[glyphPosition].clusterStart);
|
||||||
|
|
||||||
if (!((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition)) {
|
|
||||||
auto str = QStringList();
|
|
||||||
for (auto i = 0; i < pos; ++i) {
|
|
||||||
str.append(QString::number(logClusters[i]));
|
|
||||||
}
|
|
||||||
LOG(("text: %1 (from: %2, length: %3) part: %4").arg(DebugCurrentParsingString).arg(DebugCurrentParsingFrom).arg(DebugCurrentParsingLength).arg(DebugCurrentParsingPart));
|
|
||||||
LOG(("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)
|
Q_ASSERT((pos == end && glyphPosition == current.num_glyphs)
|
||||||
|| logClusters[pos] == glyphPosition);
|
|| logClusters[pos] == glyphPosition);
|
||||||
|
|
||||||
|
|
@ -176,32 +168,36 @@ void addNextCluster(
|
||||||
class BlockParser {
|
class BlockParser {
|
||||||
public:
|
public:
|
||||||
BlockParser(
|
BlockParser(
|
||||||
QTextEngine *e,
|
TextBlock &block,
|
||||||
TextBlock *b,
|
QTextEngine &engine,
|
||||||
QFixed minResizeWidth,
|
QFixed minResizeWidth,
|
||||||
int blockFrom,
|
int blockPosition,
|
||||||
const QString &str);
|
const QString &text);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void parseWords(QFixed minResizeWidth, int blockFrom);
|
void parseWords(QFixed minResizeWidth, int blockFrom);
|
||||||
bool isLineBreak(const QCharAttributes *attributes, int index);
|
[[nodiscard]] bool isLineBreak(
|
||||||
bool isSpaceBreak(const QCharAttributes *attributes, int index);
|
const QCharAttributes *attributes,
|
||||||
|
int index) const;
|
||||||
|
[[nodiscard]] bool isSpaceBreak(
|
||||||
|
const QCharAttributes *attributes,
|
||||||
|
int index) const;
|
||||||
|
|
||||||
TextBlock *block;
|
TextBlock █
|
||||||
QTextEngine *eng;
|
QTextEngine &engine;
|
||||||
const QString &str;
|
const QString &text;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockParser::BlockParser(
|
BlockParser::BlockParser(
|
||||||
QTextEngine *e,
|
TextBlock &block,
|
||||||
TextBlock *b,
|
QTextEngine &engine,
|
||||||
QFixed minResizeWidth,
|
QFixed minResizeWidth,
|
||||||
int blockFrom,
|
int blockFrom,
|
||||||
const QString &str)
|
const QString &text)
|
||||||
: block(b)
|
: block(block)
|
||||||
, eng(e)
|
, engine(engine)
|
||||||
, str(str) {
|
, text(text) {
|
||||||
parseWords(minResizeWidth, blockFrom);
|
parseWords(minResizeWidth, blockFrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,15 +217,15 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
// LOG(("Text: %1, chars: %2").arg(str).arg(debugChars));
|
// LOG(("Text: %1, chars: %2").arg(str).arg(debugChars));
|
||||||
|
|
||||||
int item = -1;
|
int item = -1;
|
||||||
int newItem = eng->findItem(0);
|
int newItem = engine.findItem(0);
|
||||||
|
|
||||||
const QCharAttributes *attributes = eng->attributes();
|
const QCharAttributes *attributes = engine.attributes();
|
||||||
if (!attributes)
|
if (!attributes)
|
||||||
return;
|
return;
|
||||||
int end = 0;
|
int end = 0;
|
||||||
lbh.logClusters = eng->layoutData->logClustersPtr;
|
lbh.logClusters = engine.layoutData->logClustersPtr;
|
||||||
|
|
||||||
block->_words.clear();
|
block._words.clear();
|
||||||
|
|
||||||
int wordStart = lbh.currentPosition;
|
int wordStart = lbh.currentPosition;
|
||||||
|
|
||||||
|
|
@ -237,26 +233,26 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
int lastGraphemeBoundaryPosition = -1;
|
int lastGraphemeBoundaryPosition = -1;
|
||||||
ScriptLine lastGraphemeBoundaryLine;
|
ScriptLine lastGraphemeBoundaryLine;
|
||||||
|
|
||||||
while (newItem < eng->layoutData->items.size()) {
|
while (newItem < engine.layoutData->items.size()) {
|
||||||
if (newItem != item) {
|
if (newItem != item) {
|
||||||
item = newItem;
|
item = newItem;
|
||||||
const QScriptItem ¤t = eng->layoutData->items[item];
|
const QScriptItem ¤t = engine.layoutData->items[item];
|
||||||
if (!current.num_glyphs) {
|
if (!current.num_glyphs) {
|
||||||
eng->shape(item);
|
engine.shape(item);
|
||||||
attributes = eng->attributes();
|
attributes = engine.attributes();
|
||||||
if (!attributes)
|
if (!attributes)
|
||||||
return;
|
return;
|
||||||
lbh.logClusters = eng->layoutData->logClustersPtr;
|
lbh.logClusters = engine.layoutData->logClustersPtr;
|
||||||
}
|
}
|
||||||
lbh.currentPosition = current.position;
|
lbh.currentPosition = current.position;
|
||||||
end = current.position + eng->length(item);
|
end = current.position + engine.length(item);
|
||||||
lbh.glyphs = eng->shapedGlyphs(¤t);
|
lbh.glyphs = engine.shapedGlyphs(¤t);
|
||||||
QFontEngine *fontEngine = eng->fontEngine(current);
|
QFontEngine *fontEngine = engine.fontEngine(current);
|
||||||
if (lbh.fontEngine != fontEngine) {
|
if (lbh.fontEngine != fontEngine) {
|
||||||
lbh.fontEngine = fontEngine;
|
lbh.fontEngine = fontEngine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const QScriptItem ¤t = eng->layoutData->items[item];
|
const QScriptItem ¤t = engine.layoutData->items[item];
|
||||||
|
|
||||||
const auto atSpaceBreak = [&] {
|
const auto atSpaceBreak = [&] {
|
||||||
for (auto index = lbh.currentPosition; index < end; ++index) {
|
for (auto index = lbh.currentPosition; index < end; ++index) {
|
||||||
|
|
@ -269,15 +265,25 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
return false;
|
return false;
|
||||||
}();
|
}();
|
||||||
if (atSpaceBreak) {
|
if (atSpaceBreak) {
|
||||||
while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
|
while (lbh.currentPosition < end
|
||||||
addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
|
&& attributes[lbh.currentPosition].whiteSpace)
|
||||||
current, lbh.logClusters, lbh.glyphs);
|
addNextCluster(
|
||||||
|
lbh.currentPosition,
|
||||||
|
end,
|
||||||
|
lbh.spaceData,
|
||||||
|
lbh.glyphCount,
|
||||||
|
current,
|
||||||
|
lbh.logClusters,
|
||||||
|
lbh.glyphs);
|
||||||
|
|
||||||
if (block->_words.isEmpty()) {
|
if (block._words.isEmpty()) {
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
block._words.push_back(TextWord(
|
||||||
|
wordStart + blockFrom,
|
||||||
|
lbh.tmpData.textWidth,
|
||||||
|
-lbh.negativeRightBearing()));
|
||||||
}
|
}
|
||||||
block->_words.back().add_rpadding(lbh.spaceData.textWidth);
|
block._words.back().add_rpadding(lbh.spaceData.textWidth);
|
||||||
block->_width += lbh.spaceData.textWidth;
|
block._width += lbh.spaceData.textWidth;
|
||||||
lbh.spaceData.length = 0;
|
lbh.spaceData.length = 0;
|
||||||
lbh.spaceData.textWidth = 0;
|
lbh.spaceData.textWidth = 0;
|
||||||
|
|
||||||
|
|
@ -288,15 +294,24 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
lastGraphemeBoundaryLine = ScriptLine();
|
lastGraphemeBoundaryLine = ScriptLine();
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
|
addNextCluster(
|
||||||
current, lbh.logClusters, lbh.glyphs);
|
lbh.currentPosition,
|
||||||
|
end,
|
||||||
|
lbh.tmpData,
|
||||||
|
lbh.glyphCount,
|
||||||
|
current,
|
||||||
|
lbh.logClusters,
|
||||||
|
lbh.glyphs);
|
||||||
|
|
||||||
if (lbh.currentPosition >= eng->layoutData->string.length()
|
if (lbh.currentPosition >= engine.layoutData->string.length()
|
||||||
|| isSpaceBreak(attributes, lbh.currentPosition)
|
|| isSpaceBreak(attributes, lbh.currentPosition)
|
||||||
|| isLineBreak(attributes, lbh.currentPosition)) {
|
|| isLineBreak(attributes, lbh.currentPosition)) {
|
||||||
lbh.calculateRightBearing();
|
lbh.calculateRightBearing();
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
block._words.push_back(TextWord(
|
||||||
block->_width += lbh.tmpData.textWidth;
|
wordStart + blockFrom,
|
||||||
|
lbh.tmpData.textWidth,
|
||||||
|
-lbh.negativeRightBearing()));
|
||||||
|
block._width += lbh.tmpData.textWidth;
|
||||||
lbh.tmpData.textWidth = 0;
|
lbh.tmpData.textWidth = 0;
|
||||||
lbh.tmpData.length = 0;
|
lbh.tmpData.length = 0;
|
||||||
wordStart = lbh.currentPosition;
|
wordStart = lbh.currentPosition;
|
||||||
|
|
@ -305,8 +320,11 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) {
|
if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) {
|
||||||
if (lastGraphemeBoundaryPosition >= 0) {
|
if (lastGraphemeBoundaryPosition >= 0) {
|
||||||
lbh.calculateRightBearingForPreviousGlyph();
|
lbh.calculateRightBearingForPreviousGlyph();
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing()));
|
block._words.push_back(TextWord(
|
||||||
block->_width += lastGraphemeBoundaryLine.textWidth;
|
wordStart + blockFrom,
|
||||||
|
-lastGraphemeBoundaryLine.textWidth,
|
||||||
|
-lbh.negativeRightBearing()));
|
||||||
|
block._width += lastGraphemeBoundaryLine.textWidth;
|
||||||
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
|
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
|
||||||
lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
|
lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
|
||||||
wordStart = lastGraphemeBoundaryPosition;
|
wordStart = lastGraphemeBoundaryPosition;
|
||||||
|
|
@ -315,8 +333,11 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
}
|
}
|
||||||
if (addingEachGrapheme) {
|
if (addingEachGrapheme) {
|
||||||
lbh.calculateRightBearing();
|
lbh.calculateRightBearing();
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
block._words.push_back(TextWord(
|
||||||
block->_width += lbh.tmpData.textWidth;
|
wordStart + blockFrom,
|
||||||
|
-lbh.tmpData.textWidth,
|
||||||
|
-lbh.negativeRightBearing()));
|
||||||
|
block._width += lbh.tmpData.textWidth;
|
||||||
lbh.tmpData.textWidth = 0;
|
lbh.tmpData.textWidth = 0;
|
||||||
lbh.tmpData.length = 0;
|
lbh.tmpData.length = 0;
|
||||||
wordStart = lbh.currentPosition;
|
wordStart = lbh.currentPosition;
|
||||||
|
|
@ -331,54 +352,87 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
if (lbh.currentPosition == end)
|
if (lbh.currentPosition == end)
|
||||||
newItem = item + 1;
|
newItem = item + 1;
|
||||||
}
|
}
|
||||||
if (!block->_words.isEmpty()) {
|
if (!block._words.isEmpty()) {
|
||||||
block->_rpadding = block->_words.back().f_rpadding();
|
block._rpadding = block._words.back().f_rpadding();
|
||||||
block->_width -= block->_rpadding;
|
block._width -= block._rpadding;
|
||||||
block->_words.squeeze();
|
block._words.squeeze();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BlockParser::isLineBreak(
|
bool BlockParser::isLineBreak(
|
||||||
const QCharAttributes *attributes,
|
const QCharAttributes *attributes,
|
||||||
int index) {
|
int index) const {
|
||||||
// Don't break after / in links.
|
// Don't break after / in links.
|
||||||
return attributes[index].lineBreak
|
return attributes[index].lineBreak
|
||||||
&& (block->lnkIndex() <= 0
|
&& (block.linkIndex() <= 0 || index <= 0 || text[index - 1] != '/');
|
||||||
|| index <= 0
|
|
||||||
|| str[index - 1] != '/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BlockParser::isSpaceBreak(
|
bool BlockParser::isSpaceBreak(
|
||||||
const QCharAttributes *attributes,
|
const QCharAttributes *attributes,
|
||||||
int index) {
|
int index) const {
|
||||||
// Don't break on
|
// Don't break on
|
||||||
return attributes[index].whiteSpace
|
return attributes[index].whiteSpace && (text[index] != QChar::Nbsp);
|
||||||
&& (str[index] != QChar::Nbsp);
|
}
|
||||||
|
|
||||||
|
style::font WithFlags(
|
||||||
|
const style::font &font,
|
||||||
|
TextBlockFlags flags,
|
||||||
|
uint32 fontFlags) {
|
||||||
|
using namespace style::internal;
|
||||||
|
|
||||||
|
if (!flags && !fontFlags) {
|
||||||
|
return font;
|
||||||
|
} else if (IsMono(flags) || (fontFlags & FontMonospace)) {
|
||||||
|
return font->monospace();
|
||||||
|
}
|
||||||
|
auto result = font;
|
||||||
|
if ((flags & TextBlockFlag::Bold) || (fontFlags & FontBold)) {
|
||||||
|
result = result->bold();
|
||||||
|
} else if ((flags & TextBlockFlag::Semibold)
|
||||||
|
|| (fontFlags & FontSemibold)) {
|
||||||
|
result = result->semibold();
|
||||||
|
}
|
||||||
|
if ((flags & TextBlockFlag::Italic) || (fontFlags & FontItalic)) {
|
||||||
|
result = result->italic();
|
||||||
|
}
|
||||||
|
if ((flags & TextBlockFlag::Underline) || (fontFlags & FontUnderline)) {
|
||||||
|
result = result->underline();
|
||||||
|
}
|
||||||
|
if ((flags & TextBlockFlag::StrikeOut) || (fontFlags & FontStrikeOut)) {
|
||||||
|
result = result->strikeout();
|
||||||
|
}
|
||||||
|
if (flags & TextBlockFlag::Tilde) { // Tilde fix in OpenSans.
|
||||||
|
result = result->semibold();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractBlock::AbstractBlock(
|
AbstractBlock::AbstractBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
|
TextBlockType type,
|
||||||
uint16 flags,
|
uint16 flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex)
|
uint16 colorIndex)
|
||||||
: _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14))
|
: _position(position)
|
||||||
, _from(from)
|
, _type(static_cast<uint16>(type))
|
||||||
, _spoilerIndex(spoilerIndex) {
|
, _flags(flags)
|
||||||
|
, _linkIndex(linkIndex)
|
||||||
|
, _colorIndex(colorIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 AbstractBlock::from() const {
|
uint16 AbstractBlock::position() const {
|
||||||
return _from;
|
return _position;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AbstractBlock::width() const {
|
TextBlockType AbstractBlock::type() const {
|
||||||
return _width.toInt();
|
return static_cast<TextBlockType>(_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AbstractBlock::rpadding() const {
|
TextBlockFlags AbstractBlock::flags() const {
|
||||||
return _rpadding.toInt();
|
return TextBlockFlags::from_raw(_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed AbstractBlock::f_width() const {
|
QFixed AbstractBlock::f_width() const {
|
||||||
|
|
@ -389,77 +443,54 @@ QFixed AbstractBlock::f_rpadding() const {
|
||||||
return _rpadding;
|
return _rpadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 AbstractBlock::lnkIndex() const {
|
uint16 AbstractBlock::linkIndex() const {
|
||||||
return (_flags >> 14) & 0xFFFF;
|
return _linkIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractBlock::setLnkIndex(uint16 lnkIndex) {
|
uint16 AbstractBlock::colorIndex() const {
|
||||||
_flags = (_flags & ~(0xFFFF << 14)) | (lnkIndex << 14);
|
return _colorIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 AbstractBlock::spoilerIndex() const {
|
void AbstractBlock::setLinkIndex(uint16 index) {
|
||||||
return _spoilerIndex;
|
_linkIndex = index;
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) {
|
|
||||||
_spoilerIndex = spoilerIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextBlockType AbstractBlock::type() const {
|
|
||||||
return TextBlockType((_flags >> 10) & 0x0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 AbstractBlock::flags() const {
|
|
||||||
return (_flags & 0b1111111111);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed AbstractBlock::f_rbearing() const {
|
QFixed AbstractBlock::f_rbearing() const {
|
||||||
return (type() == TextBlockTText)
|
return (type() == TextBlockType::Text)
|
||||||
? static_cast<const TextBlock*>(this)->real_f_rbearing()
|
? static_cast<const TextBlock*>(this)->real_f_rbearing()
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBlock::TextBlock(
|
TextBlock::TextBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
QFixed minResizeWidth,
|
uint16 position,
|
||||||
uint16 from,
|
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex)
|
uint16 colorIndex,
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
QFixed minResizeWidth)
|
||||||
_flags |= ((TextBlockTText & 0x0F) << 10);
|
: AbstractBlock(
|
||||||
if (length) {
|
font,
|
||||||
style::font blockFont = font;
|
text,
|
||||||
if (!flags && lnkIndex) {
|
position,
|
||||||
// should use TextStyle lnkFlags somehow... not supported
|
length,
|
||||||
}
|
TextBlockType::Text,
|
||||||
|
flags,
|
||||||
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
linkIndex,
|
||||||
blockFont = blockFont->monospace();
|
colorIndex) {
|
||||||
} else {
|
if (!length) {
|
||||||
if (flags & TextBlockFBold) {
|
return;
|
||||||
blockFont = blockFont->bold();
|
|
||||||
} else if (flags & TextBlockFSemibold) {
|
|
||||||
blockFont = blockFont->semibold();
|
|
||||||
}
|
|
||||||
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
|
|
||||||
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
|
|
||||||
if (flags & TextBlockFStrikeOut) blockFont = blockFont->strikeout();
|
|
||||||
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
|
||||||
blockFont = blockFont->semibold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugCurrentParsingString = str;
|
|
||||||
DebugCurrentParsingFrom = _from;
|
|
||||||
DebugCurrentParsingLength = length;
|
|
||||||
const auto part = DebugCurrentParsingPart = str.mid(_from, length);
|
|
||||||
|
|
||||||
QStackTextEngine engine(part, blockFont->f);
|
|
||||||
BlockParser parser(&engine, this, minResizeWidth, _from, part);
|
|
||||||
}
|
}
|
||||||
|
const auto blockFont = WithFlags(font, flags);
|
||||||
|
if (!flags && linkIndex) {
|
||||||
|
// should use TextStyle lnkFlags somehow... not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto part = text.mid(_position, length);
|
||||||
|
|
||||||
|
QStackTextEngine engine(part, blockFont->f);
|
||||||
|
BlockParser parser(*this, engine, minResizeWidth, _position, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed TextBlock::real_f_rbearing() const {
|
QFixed TextBlock::real_f_rbearing() const {
|
||||||
|
|
@ -468,20 +499,27 @@ QFixed TextBlock::real_f_rbearing() const {
|
||||||
|
|
||||||
EmojiBlock::EmojiBlock(
|
EmojiBlock::EmojiBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
EmojiPtr emoji)
|
EmojiPtr emoji)
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
|
: AbstractBlock(
|
||||||
|
font,
|
||||||
|
text,
|
||||||
|
position,
|
||||||
|
length,
|
||||||
|
TextBlockType::Emoji,
|
||||||
|
flags,
|
||||||
|
linkIndex,
|
||||||
|
colorIndex)
|
||||||
, _emoji(emoji) {
|
, _emoji(emoji) {
|
||||||
_flags |= ((TextBlockTEmoji & 0x0F) << 10);
|
|
||||||
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
||||||
_rpadding = 0;
|
_rpadding = 0;
|
||||||
for (auto i = length; i != 0;) {
|
for (auto i = length; i != 0;) {
|
||||||
auto ch = str[_from + (--i)];
|
auto ch = text[_position + (--i)];
|
||||||
if (ch.unicode() == QChar::Space) {
|
if (ch.unicode() == QChar::Space) {
|
||||||
_rpadding += font->spacew;
|
_rpadding += font->spacew;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -492,20 +530,27 @@ EmojiBlock::EmojiBlock(
|
||||||
|
|
||||||
CustomEmojiBlock::CustomEmojiBlock(
|
CustomEmojiBlock::CustomEmojiBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
std::unique_ptr<CustomEmoji> custom)
|
std::unique_ptr<CustomEmoji> custom)
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
|
: AbstractBlock(
|
||||||
|
font,
|
||||||
|
text,
|
||||||
|
position,
|
||||||
|
length,
|
||||||
|
TextBlockType::CustomEmoji,
|
||||||
|
flags,
|
||||||
|
linkIndex,
|
||||||
|
colorIndex)
|
||||||
, _custom(std::move(custom)) {
|
, _custom(std::move(custom)) {
|
||||||
_flags |= ((TextBlockTCustomEmoji & 0x0F) << 10);
|
|
||||||
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
||||||
_rpadding = 0;
|
_rpadding = 0;
|
||||||
for (auto i = length; i != 0;) {
|
for (auto i = length; i != 0;) {
|
||||||
auto ch = str[_from + (--i)];
|
auto ch = text[_position + (--i)];
|
||||||
if (ch.unicode() == QChar::Space) {
|
if (ch.unicode() == QChar::Space) {
|
||||||
_rpadding += font->spacew;
|
_rpadding += font->spacew;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -516,32 +561,46 @@ CustomEmojiBlock::CustomEmojiBlock(
|
||||||
|
|
||||||
NewlineBlock::NewlineBlock(
|
NewlineBlock::NewlineBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex)
|
uint16 colorIndex)
|
||||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
: AbstractBlock(
|
||||||
_flags |= ((TextBlockTNewline & 0x0F) << 10);
|
font,
|
||||||
|
text,
|
||||||
|
position,
|
||||||
|
length,
|
||||||
|
TextBlockType::Newline,
|
||||||
|
flags,
|
||||||
|
linkIndex,
|
||||||
|
colorIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::LayoutDirection NewlineBlock::nextDirection() const {
|
Qt::LayoutDirection NewlineBlock::nextDirection() const {
|
||||||
return _nextDir;
|
return _nextDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkipBlock::SkipBlock(
|
SkipBlock::SkipBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
int32 w,
|
int32 width,
|
||||||
int32 h,
|
int32 height,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex)
|
uint16 colorIndex)
|
||||||
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex)
|
: AbstractBlock(
|
||||||
, _height(h) {
|
font,
|
||||||
_flags |= ((TextBlockTSkip & 0x0F) << 10);
|
text,
|
||||||
_width = w;
|
position,
|
||||||
|
1,
|
||||||
|
TextBlockType::Skip,
|
||||||
|
0,
|
||||||
|
linkIndex,
|
||||||
|
colorIndex)
|
||||||
|
, _height(height) {
|
||||||
|
_width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SkipBlock::height() const {
|
int SkipBlock::height() const {
|
||||||
|
|
@ -550,11 +609,11 @@ int SkipBlock::height() const {
|
||||||
|
|
||||||
|
|
||||||
TextWord::TextWord(
|
TextWord::TextWord(
|
||||||
uint16 from,
|
uint16 position,
|
||||||
QFixed width,
|
QFixed width,
|
||||||
QFixed rbearing,
|
QFixed rbearing,
|
||||||
QFixed rpadding)
|
QFixed rpadding)
|
||||||
: _from(from)
|
: _position(position)
|
||||||
, _rbearing((rbearing.value() > 0x7FFF)
|
, _rbearing((rbearing.value() > 0x7FFF)
|
||||||
? 0x7FFF
|
? 0x7FFF
|
||||||
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
|
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
|
||||||
|
|
@ -562,8 +621,8 @@ TextWord::TextWord(
|
||||||
, _rpadding(rpadding) {
|
, _rpadding(rpadding) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 TextWord::from() const {
|
uint16 TextWord::position() const {
|
||||||
return _from;
|
return _position;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed TextWord::f_rbearing() const {
|
QFixed TextWord::f_rbearing() const {
|
||||||
|
|
@ -588,19 +647,20 @@ Block::Block() {
|
||||||
|
|
||||||
Block::Block(Block &&other) {
|
Block::Block(Block &&other) {
|
||||||
switch (other->type()) {
|
switch (other->type()) {
|
||||||
case TextBlockTNewline:
|
case TextBlockType::Newline:
|
||||||
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTText:
|
case TextBlockType::Text:
|
||||||
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTEmoji:
|
case TextBlockType::Emoji:
|
||||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTCustomEmoji:
|
case TextBlockType::CustomEmoji:
|
||||||
emplace<CustomEmojiBlock>(std::move(other.unsafe<CustomEmojiBlock>()));
|
emplace<CustomEmojiBlock>(
|
||||||
|
std::move(other.unsafe<CustomEmojiBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTSkip:
|
case TextBlockType::Skip:
|
||||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -614,19 +674,20 @@ Block &Block::operator=(Block &&other) {
|
||||||
}
|
}
|
||||||
destroy();
|
destroy();
|
||||||
switch (other->type()) {
|
switch (other->type()) {
|
||||||
case TextBlockTNewline:
|
case TextBlockType::Newline:
|
||||||
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTText:
|
case TextBlockType::Text:
|
||||||
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTEmoji:
|
case TextBlockType::Emoji:
|
||||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTCustomEmoji:
|
case TextBlockType::CustomEmoji:
|
||||||
emplace<CustomEmojiBlock>(std::move(other.unsafe<CustomEmojiBlock>()));
|
emplace<CustomEmojiBlock>(
|
||||||
|
std::move(other.unsafe<CustomEmojiBlock>()));
|
||||||
break;
|
break;
|
||||||
case TextBlockTSkip:
|
case TextBlockType::Skip:
|
||||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -641,91 +702,98 @@ Block::~Block() {
|
||||||
|
|
||||||
Block Block::Newline(
|
Block Block::Newline(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex) {
|
uint16 colorIndex) {
|
||||||
return New<NewlineBlock>(
|
return New<NewlineBlock>(
|
||||||
font,
|
font,
|
||||||
str,
|
text,
|
||||||
from,
|
position,
|
||||||
length,
|
length,
|
||||||
flags,
|
flags,
|
||||||
lnkIndex,
|
linkIndex,
|
||||||
spoilerIndex);
|
colorIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Block Block::Text(
|
Block Block::Text(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
QFixed minResizeWidth,
|
uint16 position,
|
||||||
uint16 from,
|
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex) {
|
uint16 colorIndex,
|
||||||
|
QFixed minResizeWidth) {
|
||||||
return New<TextBlock>(
|
return New<TextBlock>(
|
||||||
font,
|
font,
|
||||||
str,
|
text,
|
||||||
minResizeWidth,
|
position,
|
||||||
from,
|
|
||||||
length,
|
length,
|
||||||
flags,
|
flags,
|
||||||
lnkIndex,
|
linkIndex,
|
||||||
spoilerIndex);
|
colorIndex,
|
||||||
|
minResizeWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
Block Block::Emoji(
|
Block Block::Emoji(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
EmojiPtr emoji) {
|
EmojiPtr emoji) {
|
||||||
return New<EmojiBlock>(
|
return New<EmojiBlock>(
|
||||||
font,
|
font,
|
||||||
str,
|
text,
|
||||||
from,
|
position,
|
||||||
length,
|
length,
|
||||||
flags,
|
flags,
|
||||||
lnkIndex,
|
linkIndex,
|
||||||
spoilerIndex,
|
colorIndex,
|
||||||
emoji);
|
emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
Block Block::CustomEmoji(
|
Block Block::CustomEmoji(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
std::unique_ptr<Text::CustomEmoji> custom) {
|
std::unique_ptr<Text::CustomEmoji> custom) {
|
||||||
return New<CustomEmojiBlock>(
|
return New<CustomEmojiBlock>(
|
||||||
font,
|
font,
|
||||||
str,
|
text,
|
||||||
from,
|
position,
|
||||||
length,
|
length,
|
||||||
flags,
|
flags,
|
||||||
lnkIndex,
|
linkIndex,
|
||||||
spoilerIndex,
|
colorIndex,
|
||||||
std::move(custom));
|
std::move(custom));
|
||||||
}
|
}
|
||||||
|
|
||||||
Block Block::Skip(
|
Block Block::Skip(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
int32 w,
|
int32 width,
|
||||||
int32 h,
|
int32 height,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex) {
|
uint16 colorIndex) {
|
||||||
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex);
|
return New<SkipBlock>(
|
||||||
|
font,
|
||||||
|
text,
|
||||||
|
position,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
linkIndex,
|
||||||
|
colorIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractBlock *Block::get() {
|
AbstractBlock *Block::get() {
|
||||||
|
|
@ -754,19 +822,19 @@ const AbstractBlock &Block::operator*() const {
|
||||||
|
|
||||||
void Block::destroy() {
|
void Block::destroy() {
|
||||||
switch (get()->type()) {
|
switch (get()->type()) {
|
||||||
case TextBlockTNewline:
|
case TextBlockType::Newline:
|
||||||
unsafe<NewlineBlock>().~NewlineBlock();
|
unsafe<NewlineBlock>().~NewlineBlock();
|
||||||
break;
|
break;
|
||||||
case TextBlockTText:
|
case TextBlockType::Text:
|
||||||
unsafe<TextBlock>().~TextBlock();
|
unsafe<TextBlock>().~TextBlock();
|
||||||
break;
|
break;
|
||||||
case TextBlockTEmoji:
|
case TextBlockType::Emoji:
|
||||||
unsafe<EmojiBlock>().~EmojiBlock();
|
unsafe<EmojiBlock>().~EmojiBlock();
|
||||||
break;
|
break;
|
||||||
case TextBlockTCustomEmoji:
|
case TextBlockType::CustomEmoji:
|
||||||
unsafe<CustomEmojiBlock>().~CustomEmojiBlock();
|
unsafe<CustomEmojiBlock>().~CustomEmojiBlock();
|
||||||
break;
|
break;
|
||||||
case TextBlockTSkip:
|
case TextBlockType::Skip:
|
||||||
unsafe<SkipBlock>().~SkipBlock();
|
unsafe<SkipBlock>().~SkipBlock();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -777,7 +845,7 @@ void Block::destroy() {
|
||||||
int CountBlockHeight(
|
int CountBlockHeight(
|
||||||
const AbstractBlock *block,
|
const AbstractBlock *block,
|
||||||
const style::TextStyle *st) {
|
const style::TextStyle *st) {
|
||||||
return (block->type() == TextBlockTSkip)
|
return (block->type() == TextBlockType::Skip)
|
||||||
? static_cast<const SkipBlock*>(block)->height()
|
? static_cast<const SkipBlock*>(block)->height()
|
||||||
: (st->lineHeight > st->font->height)
|
: (st->lineHeight > st->font->height)
|
||||||
? st->lineHeight
|
? st->lineHeight
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/flags.h"
|
||||||
#include "ui/text/text_custom_emoji.h"
|
#include "ui/text/text_custom_emoji.h"
|
||||||
#include "ui/style/style_core.h"
|
#include "ui/style/style_core.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
|
|
@ -20,59 +21,64 @@ struct TextStyle;
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
enum TextBlockType {
|
enum class TextBlockType : uint16 {
|
||||||
TextBlockTNewline = 0x01,
|
Newline = 0x01,
|
||||||
TextBlockTText = 0x02,
|
Text = 0x02,
|
||||||
TextBlockTEmoji = 0x03,
|
Emoji = 0x03,
|
||||||
TextBlockTCustomEmoji = 0x04,
|
CustomEmoji = 0x04,
|
||||||
TextBlockTSkip = 0x05,
|
Skip = 0x05,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TextBlockFlags {
|
enum class TextBlockFlag : uint16 {
|
||||||
TextBlockFBold = 0x01,
|
Bold = 0x001,
|
||||||
TextBlockFItalic = 0x02,
|
Italic = 0x002,
|
||||||
TextBlockFUnderline = 0x04,
|
Underline = 0x004,
|
||||||
TextBlockFStrikeOut = 0x08,
|
StrikeOut = 0x008,
|
||||||
TextBlockFTilde = 0x10, // tilde fix in OpenSans
|
Tilde = 0x010, // Tilde fix in OpenSans.
|
||||||
TextBlockFSemibold = 0x20,
|
Semibold = 0x020,
|
||||||
TextBlockFCode = 0x40,
|
Code = 0x040,
|
||||||
TextBlockFPre = 0x80,
|
Pre = 0x080,
|
||||||
TextBlockFPlainLink = 0x100,
|
Spoiler = 0x100,
|
||||||
};
|
};
|
||||||
|
inline constexpr bool is_flag_type(TextBlockFlag) { return true; }
|
||||||
|
using TextBlockFlags = base::flags<TextBlockFlag>;
|
||||||
|
|
||||||
|
[[nodiscard]] style::font WithFlags(
|
||||||
|
const style::font &font,
|
||||||
|
TextBlockFlags flags,
|
||||||
|
uint32 fontFlags = 0);
|
||||||
|
|
||||||
class AbstractBlock {
|
class AbstractBlock {
|
||||||
public:
|
public:
|
||||||
uint16 from() const;
|
[[nodiscard]] uint16 position() const;
|
||||||
int width() const;
|
[[nodiscard]] TextBlockType type() const;
|
||||||
int rpadding() const;
|
[[nodiscard]] TextBlockFlags flags() const;
|
||||||
QFixed f_width() const;
|
[[nodiscard]] uint16 colorIndex() const;
|
||||||
QFixed f_rpadding() const;
|
[[nodiscard]] uint16 linkIndex() const;
|
||||||
|
void setLinkIndex(uint16 index);
|
||||||
|
|
||||||
|
[[nodiscard]] QFixed f_width() const;
|
||||||
|
[[nodiscard]] QFixed f_rpadding() const;
|
||||||
|
|
||||||
// Should be virtual, but optimized throught type() call.
|
// Should be virtual, but optimized throught type() call.
|
||||||
QFixed f_rbearing() const;
|
[[nodiscard]] QFixed f_rbearing() const;
|
||||||
|
|
||||||
uint16 lnkIndex() const;
|
|
||||||
void setLnkIndex(uint16 lnkIndex);
|
|
||||||
|
|
||||||
uint16 spoilerIndex() const;
|
|
||||||
void setSpoilerIndex(uint16 spoilerIndex);
|
|
||||||
|
|
||||||
TextBlockType type() const;
|
|
||||||
int32 flags() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AbstractBlock(
|
AbstractBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
|
TextBlockType type,
|
||||||
uint16 flags,
|
uint16 flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags
|
uint16 _position = 0;
|
||||||
uint16 _from = 0;
|
uint16 _type : 4 = 0;
|
||||||
uint16 _spoilerIndex = 0;
|
uint16 _flags : 12 = 0;
|
||||||
|
uint16 _linkIndex = 0;
|
||||||
|
uint16 _colorIndex = 0;
|
||||||
|
|
||||||
QFixed _width = 0;
|
QFixed _width = 0;
|
||||||
|
|
||||||
|
|
@ -90,16 +96,16 @@ public:
|
||||||
NewlineBlock(
|
NewlineBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &str,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
Qt::LayoutDirection nextDirection() const;
|
[[nodiscard]] Qt::LayoutDirection nextDirection() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto;
|
Qt::LayoutDirection _nextDirection = Qt::LayoutDirectionAuto;
|
||||||
|
|
||||||
friend class String;
|
friend class String;
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
|
|
@ -110,17 +116,24 @@ private:
|
||||||
class TextWord final {
|
class TextWord final {
|
||||||
public:
|
public:
|
||||||
TextWord() = default;
|
TextWord() = default;
|
||||||
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0);
|
TextWord(
|
||||||
uint16 from() const;
|
uint16 position,
|
||||||
QFixed f_rbearing() const;
|
QFixed width,
|
||||||
QFixed f_width() const;
|
QFixed rbearing,
|
||||||
QFixed f_rpadding() const;
|
QFixed rpadding = 0);
|
||||||
|
|
||||||
|
[[nodiscard]] uint16 position() const;
|
||||||
|
[[nodiscard]] QFixed f_rbearing() const;
|
||||||
|
[[nodiscard]] QFixed f_width() const;
|
||||||
|
[[nodiscard]] QFixed f_rpadding() const;
|
||||||
|
|
||||||
void add_rpadding(QFixed padding);
|
void add_rpadding(QFixed padding);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16 _from = 0;
|
uint16 _position = 0;
|
||||||
int16 _rbearing = 0;
|
int16 _rbearing = 0;
|
||||||
QFixed _width, _rpadding;
|
QFixed _width;
|
||||||
|
QFixed _rpadding;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -128,16 +141,16 @@ class TextBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
TextBlock(
|
TextBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
QFixed minResizeWidth,
|
uint16 position,
|
||||||
uint16 from,
|
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 colorIndex,
|
||||||
|
QFixed minResizeWidth);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QFixed real_f_rbearing() const;
|
[[nodiscard]] QFixed real_f_rbearing() const;
|
||||||
|
|
||||||
QVector<TextWord> _words;
|
QVector<TextWord> _words;
|
||||||
|
|
||||||
|
|
@ -153,12 +166,12 @@ class EmojiBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
EmojiBlock(
|
EmojiBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
EmojiPtr emoji);
|
EmojiPtr emoji);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -174,12 +187,12 @@ class CustomEmojiBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
CustomEmojiBlock(
|
CustomEmojiBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
std::unique_ptr<CustomEmoji> custom);
|
std::unique_ptr<CustomEmoji> custom);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -195,14 +208,14 @@ class SkipBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
SkipBlock(
|
SkipBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
int32 w,
|
int32 width,
|
||||||
int32 h,
|
int32 height,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
int height() const;
|
[[nodiscard]] int height() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _height = 0;
|
int _height = 0;
|
||||||
|
|
@ -221,52 +234,52 @@ public:
|
||||||
~Block();
|
~Block();
|
||||||
|
|
||||||
[[nodiscard]] static Block Newline(
|
[[nodiscard]] static Block Newline(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
[[nodiscard]] static Block Text(
|
[[nodiscard]] static Block Text(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
QFixed minResizeWidth,
|
uint16 position,
|
||||||
uint16 from,
|
uint16 length,
|
||||||
uint16 length,
|
TextBlockFlags flags,
|
||||||
uint16 flags,
|
uint16 linkIndex,
|
||||||
uint16 lnkIndex,
|
uint16 colorIndex,
|
||||||
uint16 spoilerIndex);
|
QFixed minResizeWidth);
|
||||||
|
|
||||||
[[nodiscard]] static Block Emoji(
|
[[nodiscard]] static Block Emoji(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
EmojiPtr emoji);
|
EmojiPtr emoji);
|
||||||
|
|
||||||
[[nodiscard]] static Block CustomEmoji(
|
[[nodiscard]] static Block CustomEmoji(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
uint16 length,
|
uint16 length,
|
||||||
uint16 flags,
|
TextBlockFlags flags,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex,
|
uint16 colorIndex,
|
||||||
std::unique_ptr<CustomEmoji> custom);
|
std::unique_ptr<CustomEmoji> custom);
|
||||||
|
|
||||||
[[nodiscard]] static Block Skip(
|
[[nodiscard]] static Block Skip(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &str,
|
const QString &text,
|
||||||
uint16 from,
|
uint16 position,
|
||||||
int32 w,
|
int32 width,
|
||||||
int32 h,
|
int32 height,
|
||||||
uint16 lnkIndex,
|
uint16 linkIndex,
|
||||||
uint16 spoilerIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
template <typename FinalBlock>
|
template <typename FinalBlock>
|
||||||
[[nodiscard]] FinalBlock &unsafe() {
|
[[nodiscard]] FinalBlock &unsafe() {
|
||||||
|
|
@ -325,8 +338,8 @@ private:
|
||||||
const AbstractBlock *block,
|
const AbstractBlock *block,
|
||||||
const style::TextStyle *st);
|
const style::TextStyle *st);
|
||||||
|
|
||||||
[[nodiscard]] inline bool IsMono(int32 flags) {
|
[[nodiscard]] inline bool IsMono(TextBlockFlags flags) {
|
||||||
return (flags & TextBlockFPre) || (flags & TextBlockFCode);
|
return (flags & TextBlockFlag::Pre) || (flags & TextBlockFlag::Code);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ui::Text
|
} // namespace Ui::Text
|
||||||
|
|
|
||||||
|
|
@ -1358,7 +1358,7 @@ QString RemoveAccents(const QString &text) {
|
||||||
if (copying) result[i] = *ch;
|
if (copying) result[i] = *ch;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (IsDiac(*ch)) {
|
if (IsDiacritic(*ch)) {
|
||||||
copying = true;
|
copying = true;
|
||||||
--i;
|
--i;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2047,9 +2047,9 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||||
|
|
||||||
auto offset = 0;
|
auto offset = 0;
|
||||||
auto state = State();
|
auto state = State();
|
||||||
auto notClosedEntities = QVector<int>(); // Stack of indices.
|
auto notClosedEntities = std::vector<int>(); // Stack of indices.
|
||||||
const auto closeOne = [&] {
|
const auto closeOne = [&] {
|
||||||
Expects(!notClosedEntities.isEmpty());
|
Expects(!notClosedEntities.empty());
|
||||||
|
|
||||||
auto &entity = result[notClosedEntities.back()];
|
auto &entity = result[notClosedEntities.back()];
|
||||||
entity = {
|
entity = {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ enum class EntityType : uchar {
|
||||||
CustomEmoji,
|
CustomEmoji,
|
||||||
BotCommand,
|
BotCommand,
|
||||||
MediaTimestamp,
|
MediaTimestamp,
|
||||||
PlainLink, // Senders in chat list, attachements in chat list, etc.
|
Colorized, // Senders in chat list, attachments in chat list, etc.
|
||||||
|
|
||||||
Bold,
|
Bold,
|
||||||
Semibold,
|
Semibold,
|
||||||
|
|
@ -61,16 +61,16 @@ public:
|
||||||
int length,
|
int length,
|
||||||
const QString &data = QString());
|
const QString &data = QString());
|
||||||
|
|
||||||
EntityType type() const {
|
[[nodiscard]] EntityType type() const {
|
||||||
return _type;
|
return _type;
|
||||||
}
|
}
|
||||||
int offset() const {
|
[[nodiscard]] int offset() const {
|
||||||
return _offset;
|
return _offset;
|
||||||
}
|
}
|
||||||
int length() const {
|
[[nodiscard]] int length() const {
|
||||||
return _length;
|
return _length;
|
||||||
}
|
}
|
||||||
QString data() const {
|
[[nodiscard]] QString data() const {
|
||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int FirstMonospaceOffset(
|
[[nodiscard]] static int FirstMonospaceOffset(
|
||||||
const EntitiesInText &entities,
|
const EntitiesInText &entities,
|
||||||
int textLength);
|
int textLength);
|
||||||
|
|
||||||
|
|
@ -253,7 +253,7 @@ enum {
|
||||||
TextParseHashtags = 0x008,
|
TextParseHashtags = 0x008,
|
||||||
TextParseBotCommands = 0x010,
|
TextParseBotCommands = 0x010,
|
||||||
TextParseMarkdown = 0x020,
|
TextParseMarkdown = 0x020,
|
||||||
TextParsePlainLinks = 0x040,
|
TextParseColorized = 0x040,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextWithTags {
|
struct TextWithTags {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
auto result = text;
|
auto result = text;
|
||||||
const auto &preparsed = text.entities;
|
const auto &preparsed = text.entities;
|
||||||
const bool parseLinks = (options.flags & TextParseLinks);
|
const bool parseLinks = (options.flags & TextParseLinks);
|
||||||
const bool parsePlainLinks = (options.flags & TextParsePlainLinks);
|
const bool parseColorized = (options.flags & TextParseColorized);
|
||||||
if (!preparsed.isEmpty() && (parseLinks || parsePlainLinks)) {
|
if (!preparsed.isEmpty() && (parseLinks || parseColorized)) {
|
||||||
bool parseMentions = (options.flags & TextParseMentions);
|
bool parseMentions = (options.flags & TextParseMentions);
|
||||||
bool parseHashtags = (options.flags & TextParseHashtags);
|
bool parseHashtags = (options.flags & TextParseHashtags);
|
||||||
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
||||||
|
|
@ -41,9 +41,6 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
|
if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
|
||||||
(type == EntityType::Hashtag && !parseHashtags) ||
|
(type == EntityType::Hashtag && !parseHashtags) ||
|
||||||
(type == EntityType::Cashtag && !parseHashtags) ||
|
(type == EntityType::Cashtag && !parseHashtags) ||
|
||||||
(type == EntityType::PlainLink
|
|
||||||
&& !parsePlainLinks
|
|
||||||
&& !parseMarkdown) ||
|
|
||||||
(!parseLinks
|
(!parseLinks
|
||||||
&& (type == EntityType::Url
|
&& (type == EntityType::Url
|
||||||
|| type == EntityType::CustomUrl)) ||
|
|| type == EntityType::CustomUrl)) ||
|
||||||
|
|
@ -53,6 +50,8 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
|| type == EntityType::Italic
|
|| type == EntityType::Italic
|
||||||
|| type == EntityType::Underline
|
|| type == EntityType::Underline
|
||||||
|| type == EntityType::StrikeOut
|
|| type == EntityType::StrikeOut
|
||||||
|
|| type == EntityType::Colorized
|
||||||
|
|| type == EntityType::Spoiler
|
||||||
|| type == EntityType::Code
|
|| type == EntityType::Code
|
||||||
|| type == EntityType::Pre))) {
|
|| type == EntityType::Pre))) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -72,7 +71,7 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
: QFIXED_MAX;
|
: QFIXED_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open Sans tilde fix.
|
// Tilde fix in OpenSans.
|
||||||
[[nodiscard]] bool ComputeCheckTilde(const style::TextStyle &st) {
|
[[nodiscard]] bool ComputeCheckTilde(const style::TextStyle &st) {
|
||||||
const auto &font = st.font;
|
const auto &font = st.font;
|
||||||
return (font->size() * style::DevicePixelRatio() == 13)
|
return (font->size() * style::DevicePixelRatio() == 13)
|
||||||
|
|
@ -83,7 +82,7 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Parser::StartedEntity::StartedEntity(TextBlockFlags flags)
|
Parser::StartedEntity::StartedEntity(TextBlockFlags flags)
|
||||||
: _value(flags)
|
: _value(flags.value())
|
||||||
, _type(Type::Flags) {
|
, _type(Type::Flags) {
|
||||||
Expects(_value >= 0 && _value < int(kStringLinkIndexShift));
|
Expects(_value >= 0 && _value < int(kStringLinkIndexShift));
|
||||||
}
|
}
|
||||||
|
|
@ -102,12 +101,12 @@ Parser::StartedEntity::Type Parser::StartedEntity::type() const {
|
||||||
|
|
||||||
std::optional<TextBlockFlags> Parser::StartedEntity::flags() const {
|
std::optional<TextBlockFlags> Parser::StartedEntity::flags() const {
|
||||||
if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) {
|
if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) {
|
||||||
return TextBlockFlags(_value);
|
return TextBlockFlags::from_raw(uint16(_value));
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<uint16> Parser::StartedEntity::lnkIndex() const {
|
std::optional<uint16> Parser::StartedEntity::linkIndex() const {
|
||||||
if ((_value < int(kStringLinkIndexShift) && (_type == Type::IndexedLink))
|
if ((_value < int(kStringLinkIndexShift) && (_type == Type::IndexedLink))
|
||||||
|| (_value >= int(kStringLinkIndexShift) && (_type == Type::Link))) {
|
|| (_value >= int(kStringLinkIndexShift) && (_type == Type::Link))) {
|
||||||
return uint16(_value);
|
return uint16(_value);
|
||||||
|
|
@ -115,8 +114,8 @@ std::optional<uint16> Parser::StartedEntity::lnkIndex() const {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<uint16> Parser::StartedEntity::spoilerIndex() const {
|
std::optional<uint16> Parser::StartedEntity::colorIndex() const {
|
||||||
if (_value < int(kStringLinkIndexShift) && (_type == Type::Spoiler)) {
|
if (_type == Type::Colorized) {
|
||||||
return uint16(_value);
|
return uint16(_value);
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
@ -163,47 +162,61 @@ void Parser::blockCreated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::createBlock(int32 skipBack) {
|
void Parser::createBlock(int32 skipBack) {
|
||||||
if (_lnkIndex < kStringLinkIndexShift && _lnkIndex > _maxLnkIndex) {
|
if (_linkIndex < kStringLinkIndexShift && _linkIndex > _maxLinkIndex) {
|
||||||
_maxLnkIndex = _lnkIndex;
|
_maxLinkIndex = _linkIndex;
|
||||||
}
|
}
|
||||||
if (_lnkIndex > kStringLinkIndexShift) {
|
if (_linkIndex > kStringLinkIndexShift) {
|
||||||
_maxShiftedLnkIndex = std::max(
|
_maxShiftedLinkIndex = std::max(
|
||||||
uint16(_lnkIndex - kStringLinkIndexShift),
|
uint16(_linkIndex - kStringLinkIndexShift),
|
||||||
_maxShiftedLnkIndex);
|
_maxShiftedLinkIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 len = int32(_t->_text.size()) + skipBack - _blockStart;
|
const auto length = int32(_t->_text.size()) + skipBack - _blockStart;
|
||||||
if (len > 0) {
|
if (length <= 0) {
|
||||||
bool newline = !_emoji && (len == 1 && _t->_text.at(_blockStart) == QChar::LineFeed);
|
return;
|
||||||
if (_newlineAwaited) {
|
|
||||||
_newlineAwaited = false;
|
|
||||||
if (!newline) {
|
|
||||||
_t->_text.insert(_blockStart, QChar::LineFeed);
|
|
||||||
createBlock(skipBack - len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto lnkIndex = _monoIndex ? _monoIndex : _lnkIndex;
|
|
||||||
auto custom = _customEmojiData.isEmpty()
|
|
||||||
? nullptr
|
|
||||||
: Integration::Instance().createCustomEmoji(
|
|
||||||
_customEmojiData,
|
|
||||||
_context);
|
|
||||||
if (custom) {
|
|
||||||
_t->_blocks.push_back(Block::CustomEmoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, std::move(custom)));
|
|
||||||
} else if (_emoji) {
|
|
||||||
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, _emoji));
|
|
||||||
} else if (newline) {
|
|
||||||
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex));
|
|
||||||
} else {
|
|
||||||
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, lnkIndex, _spoilerIndex));
|
|
||||||
}
|
|
||||||
// Diacritic can't attach from the next block to this one.
|
|
||||||
_allowDiacritic = false;
|
|
||||||
_blockStart += len;
|
|
||||||
_customEmojiData = QByteArray();
|
|
||||||
_emoji = nullptr;
|
|
||||||
blockCreated();
|
|
||||||
}
|
}
|
||||||
|
const auto newline = !_emoji
|
||||||
|
&& (length == 1)
|
||||||
|
&& (_t->_text.at(_blockStart) == QChar::LineFeed);
|
||||||
|
if (_newlineAwaited) {
|
||||||
|
_newlineAwaited = false;
|
||||||
|
if (!newline) {
|
||||||
|
_t->_text.insert(_blockStart, QChar::LineFeed);
|
||||||
|
createBlock(skipBack - length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto linkIndex = _monoIndex ? _monoIndex : _linkIndex;
|
||||||
|
auto custom = _customEmojiData.isEmpty()
|
||||||
|
? nullptr
|
||||||
|
: Integration::Instance().createCustomEmoji(
|
||||||
|
_customEmojiData,
|
||||||
|
_context);
|
||||||
|
const auto push = [&](auto &&factory, auto &&...args) {
|
||||||
|
_t->_blocks.push_back(factory(
|
||||||
|
_t->_st->font,
|
||||||
|
_t->_text,
|
||||||
|
_blockStart,
|
||||||
|
length,
|
||||||
|
_flags,
|
||||||
|
linkIndex,
|
||||||
|
_colorIndex,
|
||||||
|
std::forward<decltype(args)>(args)...));
|
||||||
|
};
|
||||||
|
if (custom) {
|
||||||
|
push(&Block::CustomEmoji, std::move(custom));
|
||||||
|
} else if (_emoji) {
|
||||||
|
push(&Block::Emoji, _emoji);
|
||||||
|
} else if (newline) {
|
||||||
|
push(&Block::Newline);
|
||||||
|
} else {
|
||||||
|
push(&Block::Text, _t->_minResizeWidth);
|
||||||
|
}
|
||||||
|
// Diacritic can't attach from the next block to this one.
|
||||||
|
_allowDiacritic = false;
|
||||||
|
_blockStart += length;
|
||||||
|
_customEmojiData = QByteArray();
|
||||||
|
_emoji = nullptr;
|
||||||
|
blockCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::createNewlineBlock() {
|
void Parser::createNewlineBlock() {
|
||||||
|
|
@ -226,24 +239,24 @@ void Parser::finishEntities() {
|
||||||
if (_flags & (*flags)) {
|
if (_flags & (*flags)) {
|
||||||
createBlock();
|
createBlock();
|
||||||
_flags &= ~(*flags);
|
_flags &= ~(*flags);
|
||||||
if (((*flags) & TextBlockFPre)
|
if (((*flags) & TextBlockFlag::Pre)
|
||||||
&& !_t->_blocks.empty()
|
&& !_t->_blocks.empty()
|
||||||
&& _t->_blocks.back()->type() != TextBlockTNewline) {
|
&& _t->_blocks.back()->type() != TextBlockType::Newline) {
|
||||||
_newlineAwaited = true;
|
_newlineAwaited = true;
|
||||||
}
|
}
|
||||||
if (IsMono(*flags)) {
|
if (IsMono(*flags)) {
|
||||||
_monoIndex = 0;
|
_monoIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (const auto lnkIndex = list.back().lnkIndex()) {
|
} else if (const auto linkIndex = list.back().linkIndex()) {
|
||||||
if (_lnkIndex == *lnkIndex) {
|
if (_linkIndex == *linkIndex) {
|
||||||
createBlock();
|
createBlock();
|
||||||
_lnkIndex = 0;
|
_linkIndex = 0;
|
||||||
}
|
}
|
||||||
} else if (const auto spoilerIndex = list.back().spoilerIndex()) {
|
} else if (const auto colorIndex = list.back().colorIndex()) {
|
||||||
if (_spoilerIndex == *spoilerIndex && (_spoilerIndex != 0)) {
|
if (_colorIndex == *colorIndex) {
|
||||||
createBlock();
|
createBlock();
|
||||||
_spoilerIndex = 0;
|
_colorIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list.pop_back();
|
list.pop_back();
|
||||||
|
|
@ -289,26 +302,26 @@ bool Parser::checkEntities() {
|
||||||
_customEmojiData = _waitingEntity->data();
|
_customEmojiData = _waitingEntity->data();
|
||||||
_startedEntities[entityEnd].emplace_back(0, Type::CustomEmoji);
|
_startedEntities[entityEnd].emplace_back(0, Type::CustomEmoji);
|
||||||
} else if (entityType == EntityType::Bold) {
|
} else if (entityType == EntityType::Bold) {
|
||||||
flags = TextBlockFBold;
|
flags = TextBlockFlag::Bold;
|
||||||
} else if (entityType == EntityType::Semibold) {
|
} else if (entityType == EntityType::Semibold) {
|
||||||
flags = TextBlockFSemibold;
|
flags = TextBlockFlag::Semibold;
|
||||||
} else if (entityType == EntityType::Italic) {
|
} else if (entityType == EntityType::Italic) {
|
||||||
flags = TextBlockFItalic;
|
flags = TextBlockFlag::Italic;
|
||||||
} else if (entityType == EntityType::Underline) {
|
} else if (entityType == EntityType::Underline) {
|
||||||
flags = TextBlockFUnderline;
|
flags = TextBlockFlag::Underline;
|
||||||
} else if (entityType == EntityType::PlainLink) {
|
} else if (entityType == EntityType::Spoiler) {
|
||||||
flags = TextBlockFPlainLink;
|
flags = TextBlockFlag::Spoiler;
|
||||||
} else if (entityType == EntityType::StrikeOut) {
|
} else if (entityType == EntityType::StrikeOut) {
|
||||||
flags = TextBlockFStrikeOut;
|
flags = TextBlockFlag::StrikeOut;
|
||||||
} else if ((entityType == EntityType::Code) // #TODO entities
|
} else if ((entityType == EntityType::Code) // #TODO entities
|
||||||
|| (entityType == EntityType::Pre)) {
|
|| (entityType == EntityType::Pre)) {
|
||||||
if (entityType == EntityType::Code) {
|
if (entityType == EntityType::Code) {
|
||||||
flags = TextBlockFCode;
|
flags = TextBlockFlag::Code;
|
||||||
} else {
|
} else {
|
||||||
flags = TextBlockFPre;
|
flags = TextBlockFlag::Pre;
|
||||||
createBlock();
|
createBlock();
|
||||||
if (!_t->_blocks.empty()
|
if (!_t->_blocks.empty()
|
||||||
&& _t->_blocks.back()->type() != TextBlockTNewline
|
&& _t->_blocks.back()->type() != TextBlockType::Newline
|
||||||
&& _customEmojiData.isEmpty()) {
|
&& _customEmojiData.isEmpty()) {
|
||||||
createNewlineBlock();
|
createNewlineBlock();
|
||||||
}
|
}
|
||||||
|
|
@ -342,6 +355,14 @@ bool Parser::checkEntities() {
|
||||||
}
|
}
|
||||||
} else if (entityType == EntityType::MentionName) {
|
} else if (entityType == EntityType::MentionName) {
|
||||||
pushComplexUrl();
|
pushComplexUrl();
|
||||||
|
} else if (entityType == EntityType::Colorized) {
|
||||||
|
createBlock();
|
||||||
|
|
||||||
|
const auto data = _waitingEntity->data();
|
||||||
|
_colorIndex = data.isEmpty() ? 1 : (data.front().unicode() + 1);
|
||||||
|
_startedEntities[entityEnd].emplace_back(
|
||||||
|
_colorIndex,
|
||||||
|
Type::Colorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link.type != EntityType::Invalid) {
|
if (link.type != EntityType::Invalid) {
|
||||||
|
|
@ -350,9 +371,9 @@ bool Parser::checkEntities() {
|
||||||
_links.push_back(link);
|
_links.push_back(link);
|
||||||
const auto tempIndex = _links.size();
|
const auto tempIndex = _links.size();
|
||||||
const auto useCustom = processCustomIndex(tempIndex);
|
const auto useCustom = processCustomIndex(tempIndex);
|
||||||
_lnkIndex = tempIndex + (useCustom ? 0 : kStringLinkIndexShift);
|
_linkIndex = tempIndex + (useCustom ? 0 : kStringLinkIndexShift);
|
||||||
_startedEntities[entityEnd].emplace_back(
|
_startedEntities[entityEnd].emplace_back(
|
||||||
_lnkIndex,
|
_linkIndex,
|
||||||
useCustom ? Type::IndexedLink : Type::Link);
|
useCustom ? Type::IndexedLink : Type::Link);
|
||||||
} else if (flags) {
|
} else if (flags) {
|
||||||
if (!(_flags & flags)) {
|
if (!(_flags & flags)) {
|
||||||
|
|
@ -361,18 +382,6 @@ bool Parser::checkEntities() {
|
||||||
_startedEntities[entityEnd].emplace_back(flags);
|
_startedEntities[entityEnd].emplace_back(flags);
|
||||||
_monoIndex = monoIndex;
|
_monoIndex = monoIndex;
|
||||||
}
|
}
|
||||||
} else if (entityType == EntityType::Spoiler) {
|
|
||||||
createBlock();
|
|
||||||
|
|
||||||
_spoilers.push_back(EntityLinkData{
|
|
||||||
.data = QString::number(_spoilers.size() + 1),
|
|
||||||
.type = entityType,
|
|
||||||
});
|
|
||||||
_spoilerIndex = _spoilers.size();
|
|
||||||
|
|
||||||
_startedEntities[entityEnd].emplace_back(
|
|
||||||
_spoilerIndex,
|
|
||||||
Type::Spoiler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
++_waitingEntity;
|
++_waitingEntity;
|
||||||
|
|
@ -423,7 +432,7 @@ void Parser::parseCurrentChar() {
|
||||||
const auto inCustomEmoji = !_customEmojiData.isEmpty();
|
const auto inCustomEmoji = !_customEmojiData.isEmpty();
|
||||||
const auto isNewLine = !inCustomEmoji && _multiline && IsNewline(_ch);
|
const auto isNewLine = !inCustomEmoji && _multiline && IsNewline(_ch);
|
||||||
const auto replaceWithSpace = IsSpace(_ch) && (_ch != QChar::Nbsp);
|
const auto replaceWithSpace = IsSpace(_ch) && (_ch != QChar::Nbsp);
|
||||||
const auto isDiac = IsDiac(_ch);
|
const auto isDiacritic = IsDiacritic(_ch);
|
||||||
const auto isTilde = !inCustomEmoji && _checkTilde && (_ch == '~');
|
const auto isTilde = !inCustomEmoji && _checkTilde && (_ch == '~');
|
||||||
const auto skip = [&] {
|
const auto skip = [&] {
|
||||||
if (IsBad(_ch) || _ch.isLowSurrogate()) {
|
if (IsBad(_ch) || _ch.isLowSurrogate()) {
|
||||||
|
|
@ -431,8 +440,10 @@ void Parser::parseCurrentChar() {
|
||||||
} else if (_ch == 0xFE0F && Platform::IsMac()) {
|
} else if (_ch == 0xFE0F && Platform::IsMac()) {
|
||||||
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
|
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
|
||||||
return true;
|
return true;
|
||||||
} else if (isDiac) {
|
} else if (isDiacritic) {
|
||||||
if (!_allowDiacritic || _emoji || ++_diacs > kMaxDiacAfterSymbol) {
|
if (!_allowDiacritic
|
||||||
|
|| _emoji
|
||||||
|
|| ++_diacritics > kMaxDiacAfterSymbol) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (_ch.isHighSurrogate()) {
|
} else if (_ch.isHighSurrogate()) {
|
||||||
|
|
@ -474,15 +485,15 @@ void Parser::parseCurrentChar() {
|
||||||
_ch = 0;
|
_ch = 0;
|
||||||
_allowDiacritic = false;
|
_allowDiacritic = false;
|
||||||
} else {
|
} else {
|
||||||
if (isTilde) { // tilde fix in OpenSans
|
if (isTilde) { // Tilde fix in OpenSans.
|
||||||
if (!(_flags & TextBlockFTilde)) {
|
if (!(_flags & TextBlockFlag::Tilde)) {
|
||||||
createBlock(-_emojiLookback);
|
createBlock(-_emojiLookback);
|
||||||
_flags |= TextBlockFTilde;
|
_flags |= TextBlockFlag::Tilde;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_flags & TextBlockFTilde) {
|
if (_flags & TextBlockFlag::Tilde) {
|
||||||
createBlock(-_emojiLookback);
|
createBlock(-_emojiLookback);
|
||||||
_flags &= ~TextBlockFTilde;
|
_flags &= ~TextBlockFlag::Tilde;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isNewLine) {
|
if (isNewLine) {
|
||||||
|
|
@ -497,8 +508,8 @@ void Parser::parseCurrentChar() {
|
||||||
_t->_text.push_back(_ch);
|
_t->_text.push_back(_ch);
|
||||||
_allowDiacritic = true;
|
_allowDiacritic = true;
|
||||||
}
|
}
|
||||||
if (!isDiac) {
|
if (!isDiacritic) {
|
||||||
_diacs = 0;
|
_diacritics = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -594,7 +605,7 @@ void Parser::trimSourceRange() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
void Parser::finalize(const TextParseOptions &options) {
|
void Parser::finalize(const TextParseOptions &options) {
|
||||||
_t->_links.resize(_maxLnkIndex + _maxShiftedLnkIndex);
|
_t->_links.resize(_maxLinkIndex + _maxShiftedLinkIndex);
|
||||||
auto counterCustomIndex = uint16(0);
|
auto counterCustomIndex = uint16(0);
|
||||||
auto currentIndex = uint16(0); // Current the latest index of _t->_links.
|
auto currentIndex = uint16(0); // Current the latest index of _t->_links.
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -614,21 +625,21 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
auto spacesCheckFrom = uint16(-1);
|
auto spacesCheckFrom = uint16(-1);
|
||||||
const auto length = int(_t->_text.size());
|
const auto length = int(_t->_text.size());
|
||||||
for (auto &block : _t->_blocks) {
|
for (auto &block : _t->_blocks) {
|
||||||
if (block->type() == TextBlockTCustomEmoji) {
|
if (block->type() == TextBlockType::CustomEmoji) {
|
||||||
_t->_hasCustomEmoji = true;
|
_t->_hasCustomEmoji = true;
|
||||||
} else if (block->type() != TextBlockTNewline
|
} else if (block->type() != TextBlockType::Newline
|
||||||
&& block->type() != TextBlockTSkip) {
|
&& block->type() != TextBlockType::Skip) {
|
||||||
_t->_isOnlyCustomEmoji = false;
|
_t->_isOnlyCustomEmoji = false;
|
||||||
} else if (block->lnkIndex()) {
|
} else if (block->linkIndex()) {
|
||||||
_t->_isOnlyCustomEmoji = _t->_isIsolatedEmoji = false;
|
_t->_isOnlyCustomEmoji = _t->_isIsolatedEmoji = false;
|
||||||
}
|
}
|
||||||
if (!_t->_hasNotEmojiAndSpaces) {
|
if (!_t->_hasNotEmojiAndSpaces) {
|
||||||
if (block->type() == TextBlockTText) {
|
if (block->type() == TextBlockType::Text) {
|
||||||
if (spacesCheckFrom == uint16(-1)) {
|
if (spacesCheckFrom == uint16(-1)) {
|
||||||
spacesCheckFrom = block->from();
|
spacesCheckFrom = block->position();
|
||||||
}
|
}
|
||||||
} else if (spacesCheckFrom != uint16(-1)) {
|
} else if (spacesCheckFrom != uint16(-1)) {
|
||||||
const auto checkTill = block->from();
|
const auto checkTill = block->position();
|
||||||
for (auto i = spacesCheckFrom; i != checkTill; ++i) {
|
for (auto i = spacesCheckFrom; i != checkTill; ++i) {
|
||||||
Assert(i < length);
|
Assert(i < length);
|
||||||
if (!_t->_text[i].isSpace()) {
|
if (!_t->_text[i].isSpace()) {
|
||||||
|
|
@ -640,35 +651,35 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_t->_isIsolatedEmoji) {
|
if (_t->_isIsolatedEmoji) {
|
||||||
if (block->type() == TextBlockTCustomEmoji
|
if (block->type() == TextBlockType::CustomEmoji
|
||||||
|| block->type() == TextBlockTEmoji) {
|
|| block->type() == TextBlockType::Emoji) {
|
||||||
if (++isolatedEmojiCount > kIsolatedEmojiLimit) {
|
if (++isolatedEmojiCount > kIsolatedEmojiLimit) {
|
||||||
_t->_isIsolatedEmoji = false;
|
_t->_isIsolatedEmoji = false;
|
||||||
}
|
}
|
||||||
} else if (block->type() != TextBlockTSkip) {
|
} else if (block->type() != TextBlockType::Skip) {
|
||||||
_t->_isIsolatedEmoji = false;
|
_t->_isIsolatedEmoji = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (block->spoilerIndex()) {
|
if (block->flags() & TextBlockFlag::Spoiler) {
|
||||||
if (!_t->_spoiler.data) {
|
if (!_t->_spoiler.data) {
|
||||||
_t->_spoiler.data = std::make_unique<SpoilerData>(
|
_t->_spoiler.data = std::make_unique<SpoilerData>(
|
||||||
Integration::Instance().createSpoilerRepaint(_context));
|
Integration::Instance().createSpoilerRepaint(_context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto shiftedIndex = block->lnkIndex();
|
const auto shiftedIndex = block->linkIndex();
|
||||||
auto useCustomIndex = false;
|
auto useCustomIndex = false;
|
||||||
if (shiftedIndex <= kStringLinkIndexShift) {
|
if (shiftedIndex <= kStringLinkIndexShift) {
|
||||||
if (IsMono(block->flags()) && shiftedIndex) {
|
if (IsMono(block->flags()) && shiftedIndex) {
|
||||||
const auto monoIndex = shiftedIndex;
|
const auto monoIndex = shiftedIndex;
|
||||||
|
|
||||||
if (lastHandlerIndex.mono == monoIndex) {
|
if (lastHandlerIndex.mono == monoIndex) {
|
||||||
block->setLnkIndex(currentIndex);
|
block->setLinkIndex(currentIndex);
|
||||||
continue; // Optimization.
|
continue; // Optimization.
|
||||||
} else {
|
} else {
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
avoidIntersectionsWithCustom();
|
avoidIntersectionsWithCustom();
|
||||||
block->setLnkIndex(currentIndex);
|
block->setLinkIndex(currentIndex);
|
||||||
const auto handler = Integration::Instance().createLinkHandler(
|
const auto handler = Integration::Instance().createLinkHandler(
|
||||||
_monos[monoIndex - 1],
|
_monos[monoIndex - 1],
|
||||||
_context);
|
_context);
|
||||||
|
|
@ -693,7 +704,7 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
? shiftedIndex
|
? shiftedIndex
|
||||||
: (shiftedIndex - kStringLinkIndexShift);
|
: (shiftedIndex - kStringLinkIndexShift);
|
||||||
if (lastHandlerIndex.lnk == realIndex) {
|
if (lastHandlerIndex.lnk == realIndex) {
|
||||||
block->setLnkIndex(usedIndex());
|
block->setLinkIndex(usedIndex());
|
||||||
continue; // Optimization.
|
continue; // Optimization.
|
||||||
} else {
|
} else {
|
||||||
(useCustomIndex ? counterCustomIndex : currentIndex)++;
|
(useCustomIndex ? counterCustomIndex : currentIndex)++;
|
||||||
|
|
@ -701,7 +712,7 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
if (!useCustomIndex) {
|
if (!useCustomIndex) {
|
||||||
avoidIntersectionsWithCustom();
|
avoidIntersectionsWithCustom();
|
||||||
}
|
}
|
||||||
block->setLnkIndex(usedIndex());
|
block->setLinkIndex(usedIndex());
|
||||||
|
|
||||||
_t->_links.resize(std::max(usedIndex(), uint16(_t->_links.size())));
|
_t->_links.resize(std::max(usedIndex(), uint16(_t->_links.size())));
|
||||||
const auto handler = Integration::Instance().createLinkHandler(
|
const auto handler = Integration::Instance().createLinkHandler(
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ private:
|
||||||
Flags,
|
Flags,
|
||||||
Link,
|
Link,
|
||||||
IndexedLink,
|
IndexedLink,
|
||||||
Spoiler,
|
|
||||||
CustomEmoji,
|
CustomEmoji,
|
||||||
|
Colorized,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit StartedEntity(TextBlockFlags flags);
|
explicit StartedEntity(TextBlockFlags flags);
|
||||||
|
|
@ -37,8 +37,8 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] Type type() const;
|
[[nodiscard]] Type type() const;
|
||||||
[[nodiscard]] std::optional<TextBlockFlags> flags() const;
|
[[nodiscard]] std::optional<TextBlockFlags> flags() const;
|
||||||
[[nodiscard]] std::optional<uint16> lnkIndex() const;
|
[[nodiscard]] std::optional<uint16> linkIndex() const;
|
||||||
[[nodiscard]] std::optional<uint16> spoilerIndex() const;
|
[[nodiscard]] std::optional<uint16> colorIndex() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const int _value = 0;
|
const int _value = 0;
|
||||||
|
|
@ -96,23 +96,22 @@ private:
|
||||||
std::vector<uint16> _linksIndexes;
|
std::vector<uint16> _linksIndexes;
|
||||||
|
|
||||||
std::vector<EntityLinkData> _links;
|
std::vector<EntityLinkData> _links;
|
||||||
std::vector<EntityLinkData> _spoilers;
|
|
||||||
std::vector<EntityLinkData> _monos;
|
std::vector<EntityLinkData> _monos;
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
const QChar*,
|
const QChar*,
|
||||||
std::vector<StartedEntity>> _startedEntities;
|
std::vector<StartedEntity>> _startedEntities;
|
||||||
|
|
||||||
uint16 _maxLnkIndex = 0;
|
uint16 _maxLinkIndex = 0;
|
||||||
uint16 _maxShiftedLnkIndex = 0;
|
uint16 _maxShiftedLinkIndex = 0;
|
||||||
|
|
||||||
// current state
|
// current state
|
||||||
int32 _flags = 0;
|
TextBlockFlags _flags;
|
||||||
uint16 _lnkIndex = 0;
|
uint16 _linkIndex = 0;
|
||||||
uint16 _spoilerIndex = 0;
|
uint16 _colorIndex = 0;
|
||||||
uint16 _monoIndex = 0;
|
uint16 _monoIndex = 0;
|
||||||
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
||||||
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
||||||
int32 _diacs = 0; // diac chars skipped without good char
|
int32 _diacritics = 0; // diacritic chars skipped without good char
|
||||||
QFixed _sumWidth;
|
QFixed _sumWidth;
|
||||||
bool _sumFinished = false;
|
bool _sumFinished = false;
|
||||||
bool _newlineAwaited = false;
|
bool _newlineAwaited = false;
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
||||||
_p = &p;
|
_p = &p;
|
||||||
_p->setFont(_t->_st->font);
|
_p->setFont(_t->_st->font);
|
||||||
_palette = context.palette ? context.palette : &st::defaultTextPalette;
|
_palette = context.palette ? context.palette : &st::defaultTextPalette;
|
||||||
|
_colors = context.colors;
|
||||||
_originalPen = _p->pen();
|
_originalPen = _p->pen();
|
||||||
_originalPenSelected = (_palette->selectFg->c.alphaF() == 0)
|
_originalPenSelected = (_palette->selectFg->c.alphaF() == 0)
|
||||||
? _originalPen
|
? _originalPen
|
||||||
|
|
@ -184,18 +185,19 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
||||||
_yTo = context.clip.isNull()
|
_yTo = context.clip.isNull()
|
||||||
? -1
|
? -1
|
||||||
: (context.clip.y() + context.clip.height());
|
: (context.clip.y() + context.clip.height());
|
||||||
if (const auto lines = context.elisionLines) {
|
_geometry = context.geometry.layout
|
||||||
if (_yTo < 0 || (_y + (lines - 1) * _t->_st->font->height) < _yTo) {
|
? context.geometry
|
||||||
_yTo = _y + (lines * _t->_st->font->height);
|
: SimpleGeometry(
|
||||||
_elideLast = true;
|
context.availableWidth,
|
||||||
_elideRemoveFromEnd = context.elisionRemoveFromEnd;
|
_t->_st->font->height,
|
||||||
}
|
context.elisionHeight,
|
||||||
_breakEverywhere = context.elisionBreakEverywhere;
|
context.elisionRemoveFromEnd,
|
||||||
}
|
context.elisionOneLine,
|
||||||
|
context.elisionBreakEverywhere);
|
||||||
|
_breakEverywhere = _geometry.breakEverywhere;
|
||||||
_spoilerCache = context.spoiler;
|
_spoilerCache = context.spoiler;
|
||||||
_selection = context.selection;
|
_selection = context.selection;
|
||||||
_fullWidthSelection = context.fullWidthSelection;
|
_fullWidthSelection = context.fullWidthSelection;
|
||||||
_w = context.availableWidth;
|
|
||||||
_align = context.align;
|
_align = context.align;
|
||||||
_cachedNow = context.now;
|
_cachedNow = context.now;
|
||||||
_pausedEmoji = context.paused || context.pausedEmoji;
|
_pausedEmoji = context.paused || context.pausedEmoji;
|
||||||
|
|
@ -209,34 +211,26 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
||||||
|
|
||||||
void Renderer::enumerate() {
|
void Renderer::enumerate() {
|
||||||
_blocksSize = _t->_blocks.size();
|
_blocksSize = _t->_blocks.size();
|
||||||
_wLeft = _w;
|
|
||||||
if (_elideLast) {
|
|
||||||
_yToElide = _yTo;
|
|
||||||
if (_elideRemoveFromEnd > 0 && !_t->_blocks.empty()) {
|
|
||||||
int firstBlockHeight = CountBlockHeight(_t->_blocks.front().get(), _t->_st);
|
|
||||||
if (_y + firstBlockHeight >= _yToElide) {
|
|
||||||
_wLeft -= _elideRemoveFromEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_str = _t->_text.unicode();
|
_str = _t->_text.unicode();
|
||||||
|
|
||||||
if (_p) {
|
if (_p) {
|
||||||
auto clip = _p->hasClipping() ? _p->clipBoundingRect() : QRect();
|
const auto clip = _p->hasClipping() ? _p->clipBoundingRect() : QRect();
|
||||||
if (clip.width() > 0 || clip.height() > 0) {
|
if (clip.width() > 0 || clip.height() > 0) {
|
||||||
if (_yFrom < clip.y()) _yFrom = clip.y();
|
if (_yFrom < clip.y()) {
|
||||||
if (_yTo < 0 || _yTo > clip.y() + clip.height()) _yTo = clip.y() + clip.height();
|
_yFrom = clip.y();
|
||||||
|
}
|
||||||
|
if (_yTo < 0 || _yTo > clip.y() + clip.height()) {
|
||||||
|
_yTo = clip.y() + clip.height();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_parDirection = _t->_startDir;
|
_startLeft = _x.toInt();
|
||||||
if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection();
|
_startTop = _y;
|
||||||
if ((*_t->_blocks.cbegin())->type() != TextBlockTNewline) {
|
|
||||||
initNextParagraph(_t->_blocks.cbegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
_lineStart = 0;
|
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
|
||||||
_lineStartBlock = 0;
|
initNextParagraph(_t->_blocks.cbegin(), _t->_startDirection);
|
||||||
|
}
|
||||||
|
|
||||||
_lineHeight = 0;
|
_lineHeight = 0;
|
||||||
_fontHeight = _t->_st->font->height;
|
_fontHeight = _t->_st->font->height;
|
||||||
|
|
@ -257,27 +251,23 @@ void Renderer::enumerate() {
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
auto blockHeight = CountBlockHeight(b, _t->_st);
|
auto blockHeight = CountBlockHeight(b, _t->_st);
|
||||||
|
|
||||||
if (_btype == TextBlockTNewline) {
|
if (_btype == TextBlockType::Newline) {
|
||||||
if (!_lineHeight) _lineHeight = blockHeight;
|
if (!_lineHeight) {
|
||||||
if (!drawLine((*i)->from(), i, e)) {
|
_lineHeight = blockHeight;
|
||||||
|
}
|
||||||
|
if (!drawLine((*i)->position(), i, e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_y += _lineHeight;
|
_y += _lineHeight;
|
||||||
_lineHeight = 0;
|
_lineHeight = 0;
|
||||||
_lineStart = _t->countBlockEnd(i, e);
|
|
||||||
_lineStartBlock = blockIndex + 1;
|
|
||||||
|
|
||||||
last_rBearing = b->f_rbearing();
|
last_rBearing = 0;
|
||||||
_last_rPadding = b->f_rpadding();
|
_last_rPadding = 0;
|
||||||
_wLeft = _w - (b->f_width() - last_rBearing);
|
|
||||||
if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) {
|
|
||||||
_wLeft -= _elideRemoveFromEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
_parDirection = static_cast<const NewlineBlock*>(b)->nextDirection();
|
initNextParagraph(
|
||||||
if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection();
|
i + 1,
|
||||||
initNextParagraph(i + 1);
|
static_cast<const NewlineBlock*>(b)->nextDirection());
|
||||||
|
|
||||||
longWordLine = true;
|
longWordLine = true;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -296,7 +286,7 @@ void Renderer::enumerate() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_btype == TextBlockTText) {
|
if (_btype == TextBlockType::Text) {
|
||||||
auto t = static_cast<const TextBlock*>(b);
|
auto t = static_cast<const TextBlock*>(b);
|
||||||
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
||||||
_last_rPadding += b->f_rpadding();
|
_last_rPadding += b->f_rpadding();
|
||||||
|
|
@ -332,10 +322,8 @@ void Renderer::enumerate() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto elidedLineHeight = qMax(_lineHeight, blockHeight);
|
if (_elidedLine) {
|
||||||
auto elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide);
|
_lineHeight = qMax(_lineHeight, blockHeight);
|
||||||
if (elidedLine) {
|
|
||||||
_lineHeight = elidedLineHeight;
|
|
||||||
} else if (f != j && !_breakEverywhere) {
|
} else if (f != j && !_breakEverywhere) {
|
||||||
// word did not fit completely, so we roll back the state to the beginning of this long word
|
// word did not fit completely, so we roll back the state to the beginning of this long word
|
||||||
j = f;
|
j = f;
|
||||||
|
|
@ -343,20 +331,24 @@ void Renderer::enumerate() {
|
||||||
_lineHeight = f_lineHeight;
|
_lineHeight = f_lineHeight;
|
||||||
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
|
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
|
||||||
}
|
}
|
||||||
if (!drawLine(elidedLine ? ((j + 1 == en) ? _t->countBlockEnd(i, e) : (j + 1)->from()) : j->from(), i, e)) {
|
const auto lineEnd = !_elidedLine
|
||||||
|
? j->position()
|
||||||
|
: (j + 1 != en)
|
||||||
|
? (j + 1)->position()
|
||||||
|
: _t->countBlockEnd(i, e);
|
||||||
|
if (!drawLine(lineEnd, i, e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_y += _lineHeight;
|
_y += _lineHeight;
|
||||||
_lineHeight = qMax(0, blockHeight);
|
_lineHeight = qMax(0, blockHeight);
|
||||||
_lineStart = j->from();
|
_lineStart = j->position();
|
||||||
_lineStartBlock = blockIndex;
|
_lineStartBlock = blockIndex;
|
||||||
|
_paragraphWidthRemaining -= (_lineWidth - _wLeft) - _last_rPadding + last_rBearing;
|
||||||
|
initNextLine();
|
||||||
|
|
||||||
last_rBearing = j->f_rbearing();
|
last_rBearing = j->f_rbearing();
|
||||||
_last_rPadding = j->f_rpadding();
|
_last_rPadding = j->f_rpadding();
|
||||||
_wLeft = _w - (j_width - last_rBearing);
|
_wLeft -= j_width - last_rBearing;
|
||||||
if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) {
|
|
||||||
_wLeft -= _elideRemoveFromEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
longWordLine = !wordEndsHere;
|
longWordLine = !wordEndsHere;
|
||||||
f = j + 1;
|
f = j + 1;
|
||||||
|
|
@ -366,31 +358,33 @@ void Renderer::enumerate() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto elidedLineHeight = qMax(_lineHeight, blockHeight);
|
if (_elidedLine) {
|
||||||
auto elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide);
|
_lineHeight = qMax(_lineHeight, blockHeight);
|
||||||
if (elidedLine) {
|
|
||||||
_lineHeight = elidedLineHeight;
|
|
||||||
}
|
}
|
||||||
if (!drawLine(elidedLine ? _t->countBlockEnd(i, e) : b->from(), i, e)) {
|
const auto lineEnd = !_elidedLine
|
||||||
|
? b->position()
|
||||||
|
: _t->countBlockEnd(i, e);
|
||||||
|
if (!drawLine(lineEnd, i, e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_y += _lineHeight;
|
_y += _lineHeight;
|
||||||
_lineHeight = qMax(0, blockHeight);
|
_lineHeight = qMax(0, blockHeight);
|
||||||
_lineStart = b->from();
|
_lineStart = b->position();
|
||||||
_lineStartBlock = blockIndex;
|
_lineStartBlock = blockIndex;
|
||||||
|
_paragraphWidthRemaining -= (_lineWidth - _wLeft) - _last_rPadding + last_rBearing;
|
||||||
|
initNextLine();
|
||||||
|
|
||||||
last_rBearing = b__f_rbearing;
|
last_rBearing = b__f_rbearing;
|
||||||
_last_rPadding = b->f_rpadding();
|
_last_rPadding = b->f_rpadding();
|
||||||
_wLeft = _w - (b->f_width() - last_rBearing);
|
_wLeft -= b->f_width() - last_rBearing;
|
||||||
if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) {
|
|
||||||
_wLeft -= _elideRemoveFromEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
longWordLine = true;
|
longWordLine = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (_lineStart < _t->_text.size()) {
|
if (_lineStart < _t->_text.size()) {
|
||||||
if (!drawLine(_t->_text.size(), e, e)) return;
|
if (!drawLine(_t->_text.size(), e, e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!_p && _lookupSymbol) {
|
if (!_p && _lookupSymbol) {
|
||||||
_lookupResult.symbol = _t->_text.size();
|
_lookupResult.symbol = _t->_text.size();
|
||||||
|
|
@ -398,7 +392,10 @@ void Renderer::enumerate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StateResult Renderer::getState(QPoint point, int w, StateRequest request) {
|
StateResult Renderer::getState(
|
||||||
|
QPoint point,
|
||||||
|
GeometryDescriptor geometry,
|
||||||
|
StateRequest request) {
|
||||||
if (_t->isEmpty() || point.y() < 0) {
|
if (_t->isEmpty() || point.y() < 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -406,41 +403,13 @@ StateResult Renderer::getState(QPoint point, int w, StateRequest request) {
|
||||||
_lookupX = point.x();
|
_lookupX = point.x();
|
||||||
_lookupY = point.y();
|
_lookupY = point.y();
|
||||||
|
|
||||||
_breakEverywhere = (_lookupRequest.flags & StateRequest::Flag::BreakEverywhere);
|
|
||||||
_lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol);
|
_lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol);
|
||||||
_lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink);
|
_lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink);
|
||||||
if (!_lookupSymbol && (_lookupX < 0 || _lookupX >= w)) {
|
if (!_lookupSymbol && _lookupX < 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
_w = w;
|
_geometry = std::move(geometry);
|
||||||
_yFrom = _lookupY;
|
_breakEverywhere = _geometry.breakEverywhere;
|
||||||
_yTo = _lookupY + 1;
|
|
||||||
_align = _lookupRequest.align;
|
|
||||||
enumerate();
|
|
||||||
return _lookupResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
StateResult Renderer::getStateElided(QPoint point, int w, StateRequestElided request) {
|
|
||||||
if (_t->isEmpty() || point.y() < 0 || request.lines <= 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
_lookupRequest = request;
|
|
||||||
_lookupX = point.x();
|
|
||||||
_lookupY = point.y();
|
|
||||||
|
|
||||||
_breakEverywhere = (_lookupRequest.flags & StateRequest::Flag::BreakEverywhere);
|
|
||||||
_lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol);
|
|
||||||
_lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink);
|
|
||||||
if (!_lookupSymbol && (_lookupX < 0 || _lookupX >= w)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
int yTo = _lookupY + 1;
|
|
||||||
if (yTo < 0 || (request.lines - 1) * _t->_st->font->height < yTo) {
|
|
||||||
yTo = request.lines * _t->_st->font->height;
|
|
||||||
_elideLast = true;
|
|
||||||
_elideRemoveFromEnd = request.removeFromEnd;
|
|
||||||
}
|
|
||||||
_w = w;
|
|
||||||
_yFrom = _lookupY;
|
_yFrom = _lookupY;
|
||||||
_yTo = _lookupY + 1;
|
_yTo = _lookupY + 1;
|
||||||
_align = _lookupRequest.align;
|
_align = _lookupRequest.align;
|
||||||
|
|
@ -455,26 +424,58 @@ crl::time Renderer::now() const {
|
||||||
return _cachedNow;
|
return _cachedNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::initNextParagraph(String::TextBlocks::const_iterator i) {
|
void Renderer::initNextParagraph(
|
||||||
|
String::TextBlocks::const_iterator i,
|
||||||
|
Qt::LayoutDirection direction) {
|
||||||
|
_parDirection = (direction == Qt::LayoutDirectionAuto)
|
||||||
|
? style::LayoutDirection()
|
||||||
|
: direction;
|
||||||
_parStartBlock = i;
|
_parStartBlock = i;
|
||||||
|
_paragraphWidthRemaining = 0;
|
||||||
const auto e = _t->_blocks.cend();
|
const auto e = _t->_blocks.cend();
|
||||||
if (i == e) {
|
if (i == e) {
|
||||||
_parStart = _t->_text.size();
|
_lineStart = _parStart = _t->_text.size();
|
||||||
|
_lineStartBlock = _t->_blocks.size();
|
||||||
_parLength = 0;
|
_parLength = 0;
|
||||||
} else {
|
} else {
|
||||||
_parStart = (*i)->from();
|
_lineStart = _parStart = (*i)->position();
|
||||||
|
_lineStartBlock = i - _t->_blocks.cbegin();
|
||||||
|
|
||||||
|
auto last_rPadding = QFixed(0);
|
||||||
|
auto last_rBearing = QFixed(0);
|
||||||
for (; i != e; ++i) {
|
for (; i != e; ++i) {
|
||||||
if ((*i)->type() == TextBlockTNewline) {
|
if ((*i)->type() == TextBlockType::Newline) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
const auto rBearing = (*i)->f_rbearing();
|
||||||
|
_paragraphWidthRemaining += last_rBearing
|
||||||
|
+ last_rPadding
|
||||||
|
+ (*i)->f_width()
|
||||||
|
- rBearing;
|
||||||
|
last_rBearing = rBearing;
|
||||||
}
|
}
|
||||||
_parLength = ((i == e) ? _t->_text.size() : (*i)->from()) - _parStart;
|
_parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart;
|
||||||
}
|
}
|
||||||
_parAnalysis.resize(0);
|
_parAnalysis.resize(0);
|
||||||
|
initNextLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::initNextLine() {
|
||||||
|
const auto line = _geometry.layout({
|
||||||
|
.left = 0,
|
||||||
|
.top = (_y - _startTop),
|
||||||
|
.width = _paragraphWidthRemaining.ceil().toInt(),
|
||||||
|
}, _lineStart);
|
||||||
|
_x = _startLeft + line.left;
|
||||||
|
_y = _startTop + line.top;
|
||||||
|
_lineWidth = _wLeft = line.width;
|
||||||
|
_elidedLine = line.elided;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::initParagraphBidi() {
|
void Renderer::initParagraphBidi() {
|
||||||
if (!_parLength || !_parAnalysis.isEmpty()) return;
|
if (!_parLength || !_parAnalysis.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;
|
String::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;
|
||||||
|
|
||||||
|
|
@ -486,13 +487,13 @@ void Renderer::initParagraphBidi() {
|
||||||
const ushort *curr = start;
|
const ushort *curr = start;
|
||||||
const ushort *end = start + _parLength;
|
const ushort *end = start + _parLength;
|
||||||
while (curr < end) {
|
while (curr < end) {
|
||||||
while (n != e && (*n)->from() <= _parStart + (curr - start)) {
|
while (n != e && (*n)->position() <= _parStart + (curr - start)) {
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
const auto type = (*i)->type();
|
const auto type = (*i)->type();
|
||||||
if (type != TextBlockTEmoji
|
if (type != TextBlockType::Emoji
|
||||||
&& type != TextBlockTCustomEmoji
|
&& type != TextBlockType::CustomEmoji
|
||||||
&& *curr >= 0x590) {
|
&& *curr >= 0x590) {
|
||||||
ignore = false;
|
ignore = false;
|
||||||
break;
|
break;
|
||||||
|
|
@ -521,13 +522,15 @@ void Renderer::initParagraphBidi() {
|
||||||
|
|
||||||
bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterator &_endBlockIter, const String::TextBlocks::const_iterator &_end) {
|
bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterator &_endBlockIter, const String::TextBlocks::const_iterator &_end) {
|
||||||
_yDelta = (_lineHeight - _fontHeight) / 2;
|
_yDelta = (_lineHeight - _fontHeight) / 2;
|
||||||
if (_yTo >= 0 && (_y + _yDelta >= _yTo || _y >= _yTo)) return false;
|
if (_yTo >= 0 && (_y + _yDelta >= _yTo || _y >= _yTo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (_y + _yDelta + _fontHeight <= _yFrom) {
|
if (_y + _yDelta + _fontHeight <= _yFrom) {
|
||||||
if (_lookupSymbol) {
|
if (_lookupSymbol) {
|
||||||
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
||||||
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
||||||
}
|
}
|
||||||
return true;
|
return !_elidedLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trimming pending spaces, because they sometimes don't fit on the line.
|
// Trimming pending spaces, because they sometimes don't fit on the line.
|
||||||
|
|
@ -542,16 +545,15 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
|
|
||||||
auto _endBlock = (_endBlockIter == _end) ? nullptr : _endBlockIter->get();
|
auto _endBlock = (_endBlockIter == _end) ? nullptr : _endBlockIter->get();
|
||||||
auto elidedLine = _elideLast && (_y + _lineHeight >= _yToElide);
|
if (_elidedLine) {
|
||||||
if (elidedLine) {
|
|
||||||
// If we decided to draw the last line elided only because of the skip block
|
// If we decided to draw the last line elided only because of the skip block
|
||||||
// that did not fit on this line, we just draw the line till the very end.
|
// that did not fit on this line, we just draw the line till the very end.
|
||||||
// Skip block is ignored in the elided lines, instead "removeFromEnd" is used.
|
// Skip block is ignored in the elided lines, instead "removeFromEnd" is used.
|
||||||
if (_endBlock && _endBlock->type() == TextBlockTSkip) {
|
if (_endBlock && _endBlock->type() == TextBlockType::Skip) {
|
||||||
_endBlock = nullptr;
|
_endBlock = nullptr;
|
||||||
}
|
}
|
||||||
if (!_endBlock) {
|
if (!_endBlock) {
|
||||||
elidedLine = false;
|
_elidedLine = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,11 +561,11 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
auto currentBlock = _t->_blocks[blockIndex].get();
|
auto currentBlock = _t->_blocks[blockIndex].get();
|
||||||
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
|
|
||||||
const auto extendLeft = (currentBlock->from() < _lineStart)
|
const auto extendLeft = (currentBlock->position() < _lineStart)
|
||||||
? qMin(_lineStart - currentBlock->from(), 2)
|
? qMin(_lineStart - currentBlock->position(), 2)
|
||||||
: 0;
|
: 0;
|
||||||
_localFrom = _lineStart - extendLeft;
|
_localFrom = _lineStart - extendLeft;
|
||||||
const auto extendedLineEnd = (_endBlock && _endBlock->from() < trimmedLineEnd && !elidedLine)
|
const auto extendedLineEnd = (_endBlock && _endBlock->position() < trimmedLineEnd && !_elidedLine)
|
||||||
? qMin(uint16(trimmedLineEnd + 2), _t->countBlockEnd(_endBlockIter, _end))
|
? qMin(uint16(trimmedLineEnd + 2), _t->countBlockEnd(_endBlockIter, _end))
|
||||||
: trimmedLineEnd;
|
: trimmedLineEnd;
|
||||||
|
|
||||||
|
|
@ -571,7 +573,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
auto lineStart = extendLeft;
|
auto lineStart = extendLeft;
|
||||||
auto lineLength = trimmedLineEnd - _lineStart;
|
auto lineLength = trimmedLineEnd - _lineStart;
|
||||||
|
|
||||||
if (elidedLine) {
|
if (_elidedLine) {
|
||||||
initParagraphBidi();
|
initParagraphBidi();
|
||||||
prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
|
prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
|
||||||
}
|
}
|
||||||
|
|
@ -589,7 +591,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
if (_parDirection == Qt::RightToLeft) {
|
if (_parDirection == Qt::RightToLeft) {
|
||||||
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
||||||
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
||||||
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false;
|
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockType::Skip)) ? true : false;
|
||||||
} else {
|
} else {
|
||||||
_lookupResult.symbol = _lineStart;
|
_lookupResult.symbol = _lineStart;
|
||||||
_lookupResult.afterSymbol = false;
|
_lookupResult.afterSymbol = false;
|
||||||
|
|
@ -601,7 +603,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
_lookupResult.uponSymbol = false;
|
_lookupResult.uponSymbol = false;
|
||||||
return false;
|
return false;
|
||||||
} else if (_lookupX >= x + (_w - _wLeft)) {
|
} else if (_lookupX >= x + (_lineWidth - _wLeft)) {
|
||||||
if (_parDirection == Qt::RightToLeft) {
|
if (_parDirection == Qt::RightToLeft) {
|
||||||
_lookupResult.symbol = _lineStart;
|
_lookupResult.symbol = _lineStart;
|
||||||
_lookupResult.afterSymbol = false;
|
_lookupResult.afterSymbol = false;
|
||||||
|
|
@ -609,7 +611,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
} else {
|
} else {
|
||||||
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
||||||
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
||||||
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false;
|
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockType::Skip)) ? true : false;
|
||||||
}
|
}
|
||||||
if (_lookupLink) {
|
if (_lookupLink) {
|
||||||
_lookupResult.link = nullptr;
|
_lookupResult.link = nullptr;
|
||||||
|
|
@ -626,7 +628,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
const auto selectTillEnd = (_selection.to > trimmedLineEnd)
|
const auto selectTillEnd = (_selection.to > trimmedLineEnd)
|
||||||
&& (trimmedLineEnd < _t->_text.size())
|
&& (trimmedLineEnd < _t->_text.size())
|
||||||
&& (_selection.from <= trimmedLineEnd)
|
&& (_selection.from <= trimmedLineEnd)
|
||||||
&& (!_endBlock || _endBlock->type() != TextBlockTSkip);
|
&& (!_endBlock || _endBlock->type() != TextBlockType::Skip);
|
||||||
|
|
||||||
if ((selectFromStart && _parDirection == Qt::LeftToRight)
|
if ((selectFromStart && _parDirection == Qt::LeftToRight)
|
||||||
|| (selectTillEnd && _parDirection == Qt::RightToLeft)) {
|
|| (selectTillEnd && _parDirection == Qt::RightToLeft)) {
|
||||||
|
|
@ -637,15 +639,17 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
if ((selectTillEnd && _parDirection == Qt::LeftToRight)
|
if ((selectTillEnd && _parDirection == Qt::LeftToRight)
|
||||||
|| (selectFromStart && _parDirection == Qt::RightToLeft)) {
|
|| (selectFromStart && _parDirection == Qt::RightToLeft)) {
|
||||||
if (x < _x + _wLeft) {
|
if (x < _x + _wLeft) {
|
||||||
fillSelectRange({ x + _w - _wLeft, _x + _w });
|
fillSelectRange({ x + _lineWidth - _wLeft, _x + _lineWidth });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trimmedLineEnd == _lineStart && !elidedLine) {
|
if (trimmedLineEnd == _lineStart && !_elidedLine) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!elidedLine) initParagraphBidi(); // if was not inited
|
if (!_elidedLine) {
|
||||||
|
initParagraphBidi(); // if was not inited
|
||||||
|
}
|
||||||
|
|
||||||
_f = _t->_st->font;
|
_f = _t->_st->font;
|
||||||
QStackTextEngine engine(lineText, _f->f);
|
QStackTextEngine engine(lineText, _f->f);
|
||||||
|
|
@ -662,7 +666,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
||||||
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0;
|
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0;
|
||||||
if (!nItems) {
|
if (!nItems) {
|
||||||
return true;
|
return !_elidedLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
int skipIndex = -1;
|
int skipIndex = -1;
|
||||||
|
|
@ -670,23 +674,23 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
QVarLengthArray<uchar> levels(nItems);
|
QVarLengthArray<uchar> levels(nItems);
|
||||||
for (int i = 0; i < nItems; ++i) {
|
for (int i = 0; i < nItems; ++i) {
|
||||||
auto &si = engine.layoutData->items[firstItem + i];
|
auto &si = engine.layoutData->items[firstItem + i];
|
||||||
while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
|
while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
|
||||||
currentBlock = nextBlock;
|
currentBlock = nextBlock;
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
}
|
}
|
||||||
auto _type = currentBlock->type();
|
auto _type = currentBlock->type();
|
||||||
if (_type == TextBlockTSkip) {
|
if (_type == TextBlockType::Skip) {
|
||||||
levels[i] = si.analysis.bidiLevel = 0;
|
levels[i] = si.analysis.bidiLevel = 0;
|
||||||
skipIndex = i;
|
skipIndex = i;
|
||||||
} else {
|
} else {
|
||||||
levels[i] = si.analysis.bidiLevel;
|
levels[i] = si.analysis.bidiLevel;
|
||||||
}
|
}
|
||||||
if (si.analysis.flags == QScriptAnalysis::Object) {
|
if (si.analysis.flags == QScriptAnalysis::Object) {
|
||||||
if (_type == TextBlockTEmoji
|
if (_type == TextBlockType::Emoji
|
||||||
|| _type == TextBlockTCustomEmoji
|
|| _type == TextBlockType::CustomEmoji
|
||||||
|| _type == TextBlockTSkip) {
|
|| _type == TextBlockType::Skip) {
|
||||||
si.width = currentBlock->f_width()
|
si.width = currentBlock->f_width()
|
||||||
+ (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd)
|
+ (nextBlock == _endBlock && (!nextBlock || nextBlock->position() >= trimmedLineEnd)
|
||||||
? 0
|
? 0
|
||||||
: currentBlock->f_rpadding());
|
: currentBlock->f_rpadding());
|
||||||
}
|
}
|
||||||
|
|
@ -714,12 +718,12 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
const auto &si = engine.layoutData->items.at(item);
|
const auto &si = engine.layoutData->items.at(item);
|
||||||
const auto rtl = (si.analysis.bidiLevel % 2);
|
const auto rtl = (si.analysis.bidiLevel % 2);
|
||||||
|
|
||||||
while (blockIndex > _lineStartBlock + 1 && _t->_blocks[blockIndex - 1]->from() > _localFrom + si.position) {
|
while (blockIndex > _lineStartBlock + 1 && _t->_blocks[blockIndex - 1]->position() > _localFrom + si.position) {
|
||||||
nextBlock = currentBlock;
|
nextBlock = currentBlock;
|
||||||
currentBlock = _t->_blocks[--blockIndex - 1].get();
|
currentBlock = _t->_blocks[--blockIndex - 1].get();
|
||||||
applyBlockProperties(currentBlock);
|
applyBlockProperties(currentBlock);
|
||||||
}
|
}
|
||||||
while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
|
while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
|
||||||
currentBlock = nextBlock;
|
currentBlock = nextBlock;
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
applyBlockProperties(currentBlock);
|
applyBlockProperties(currentBlock);
|
||||||
|
|
@ -734,11 +738,11 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_type != TextBlockTSkip) {
|
if (_type != TextBlockType::Skip) {
|
||||||
_lookupResult.uponSymbol = true;
|
_lookupResult.uponSymbol = true;
|
||||||
}
|
}
|
||||||
if (_lookupSymbol) {
|
if (_lookupSymbol) {
|
||||||
if (_type == TextBlockTSkip) {
|
if (_type == TextBlockType::Skip) {
|
||||||
if (_parDirection == Qt::RightToLeft) {
|
if (_parDirection == Qt::RightToLeft) {
|
||||||
_lookupResult.symbol = _lineStart;
|
_lookupResult.symbol = _lineStart;
|
||||||
_lookupResult.afterSymbol = false;
|
_lookupResult.afterSymbol = false;
|
||||||
|
|
@ -750,8 +754,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emoji with spaces after symbol lookup
|
// Emoji with spaces after symbol lookup
|
||||||
auto chFrom = _str + currentBlock->from();
|
auto chFrom = _str + currentBlock->position();
|
||||||
auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from());
|
auto chTo = chFrom + ((nextBlock ? nextBlock->position() : _t->_text.size()) - currentBlock->position());
|
||||||
auto spacesWidth = (si.width - currentBlock->f_width());
|
auto spacesWidth = (si.width - currentBlock->f_width());
|
||||||
auto spacesCount = 0;
|
auto spacesCount = 0;
|
||||||
while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) {
|
while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) {
|
||||||
|
|
@ -780,7 +784,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (_p && (_type == TextBlockTEmoji || _type == TextBlockTCustomEmoji)) {
|
} else if (_p && (_type == TextBlockType::Emoji || _type == TextBlockType::CustomEmoji)) {
|
||||||
auto glyphX = x;
|
auto glyphX = x;
|
||||||
auto spacesWidth = (si.width - currentBlock->f_width());
|
auto spacesWidth = (si.width - currentBlock->f_width());
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
|
|
@ -791,8 +795,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
if (_background.selectActiveBlock) {
|
if (_background.selectActiveBlock) {
|
||||||
fillSelect = { x, x + si.width };
|
fillSelect = { x, x + si.width };
|
||||||
} else if (_localFrom + si.position < _selection.to) {
|
} else if (_localFrom + si.position < _selection.to) {
|
||||||
auto chFrom = _str + currentBlock->from();
|
auto chFrom = _str + currentBlock->position();
|
||||||
auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from());
|
auto chTo = chFrom + ((nextBlock ? nextBlock->position() : _t->_text.size()) - currentBlock->position());
|
||||||
if (_localFrom + si.position >= _selection.from) { // could be without space
|
if (_localFrom + si.position >= _selection.from) { // could be without space
|
||||||
if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) {
|
if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) {
|
||||||
fillSelect = { x, x + si.width };
|
fillSelect = { x, x + si.width };
|
||||||
|
|
@ -820,7 +824,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
const auto x = (glyphX + st::emojiPadding).toInt();
|
const auto x = (glyphX + st::emojiPadding).toInt();
|
||||||
const auto y = _y + _yDelta + emojiY;
|
const auto y = _y + _yDelta + emojiY;
|
||||||
if (_type == TextBlockTEmoji) {
|
if (_type == TextBlockType::Emoji) {
|
||||||
Emoji::Draw(
|
Emoji::Draw(
|
||||||
*_p,
|
*_p,
|
||||||
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
||||||
|
|
@ -1015,7 +1019,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
: QRegion();
|
: QRegion();
|
||||||
if (complexClipping) {
|
if (complexClipping) {
|
||||||
const auto elided = (_indexOfElidedBlock == blockIndex)
|
const auto elided = (_indexOfElidedBlock == blockIndex)
|
||||||
? (_elideRemoveFromEnd + _f->elidew)
|
? _f->elidew
|
||||||
: 0;
|
: 0;
|
||||||
_p->setClipRect(
|
_p->setClipRect(
|
||||||
QRect(
|
QRect(
|
||||||
|
|
@ -1046,9 +1050,9 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
const auto externalClipping = clippingEnabled
|
const auto externalClipping = clippingEnabled
|
||||||
? clippingRegion
|
? clippingRegion
|
||||||
: QRegion(QRect(
|
: QRegion(QRect(
|
||||||
(_x - _w).toInt(),
|
(_x - _lineWidth).toInt(),
|
||||||
_y - _lineHeight,
|
_y - _lineHeight,
|
||||||
(_x + 2 * _w).toInt(),
|
(_x + 2 * _lineWidth).toInt(),
|
||||||
_y + 2 * _lineHeight));
|
_y + 2 * _lineHeight));
|
||||||
_p->setClipRegion(externalClipping - selectedRect);
|
_p->setClipRegion(externalClipping - selectedRect);
|
||||||
_p->setPen(*_currentPen);
|
_p->setPen(*_currentPen);
|
||||||
|
|
@ -1089,7 +1093,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
x += itemWidth;
|
x += itemWidth;
|
||||||
}
|
}
|
||||||
fillSpoilerRects();
|
fillSpoilerRects();
|
||||||
return true;
|
return !_elidedLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::fillSelectRange(FixedRange range) {
|
void Renderer::fillSelectRange(FixedRange range) {
|
||||||
|
|
@ -1108,9 +1112,7 @@ void Renderer::pushSpoilerRange(
|
||||||
if (!_background.spoiler || !_spoiler) {
|
if (!_background.spoiler || !_spoiler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto elided = isElidedItem
|
const auto elided = isElidedItem ? _f->elidew : 0;
|
||||||
? (_elideRemoveFromEnd + _f->elidew)
|
|
||||||
: 0;
|
|
||||||
range.till -= elided;
|
range.till -= elided;
|
||||||
if (range.empty()) {
|
if (range.empty()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1204,9 +1206,18 @@ void Renderer::elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock,
|
||||||
_elideSavedIndex = blockIndex;
|
_elideSavedIndex = blockIndex;
|
||||||
auto mutableText = const_cast<String*>(_t);
|
auto mutableText = const_cast<String*>(_t);
|
||||||
_elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
|
_elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
|
||||||
mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex(), (*_elideSavedBlock)->spoilerIndex());
|
mutableText->_blocks[blockIndex] = Block::Text(
|
||||||
|
_t->_st->font,
|
||||||
|
_t->_text,
|
||||||
|
elideStart, 0,
|
||||||
|
(*_elideSavedBlock)->flags(),
|
||||||
|
(*_elideSavedBlock)->linkIndex(),
|
||||||
|
(*_elideSavedBlock)->colorIndex(),
|
||||||
|
QFIXED_MAX);
|
||||||
_blocksSize = blockIndex + 1;
|
_blocksSize = blockIndex + 1;
|
||||||
_endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr);
|
_endBlock = (blockIndex + 1 < _t->_blocks.size())
|
||||||
|
? _t->_blocks[blockIndex + 1].get()
|
||||||
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::setElideBidi(int32 elideStart, int32 elideLen) {
|
void Renderer::setElideBidi(int32 elideStart, int32 elideLen) {
|
||||||
|
|
@ -1237,40 +1248,40 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
|
||||||
eShapeLine(line);
|
eShapeLine(line);
|
||||||
|
|
||||||
auto elideWidth = _f->elidew;
|
auto elideWidth = _f->elidew;
|
||||||
_wLeft = _w - elideWidth - _elideRemoveFromEnd;
|
_wLeft = _lineWidth - elideWidth;
|
||||||
|
|
||||||
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
||||||
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;
|
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;
|
||||||
|
|
||||||
for (i = 0; i < nItems; ++i) {
|
for (i = 0; i < nItems; ++i) {
|
||||||
QScriptItem &si(engine.layoutData->items[firstItem + i]);
|
QScriptItem &si(engine.layoutData->items[firstItem + i]);
|
||||||
while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
|
while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
|
||||||
currentBlock = nextBlock;
|
currentBlock = nextBlock;
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
}
|
}
|
||||||
TextBlockType _type = currentBlock->type();
|
TextBlockType _type = currentBlock->type();
|
||||||
if (si.analysis.flags == QScriptAnalysis::Object) {
|
if (si.analysis.flags == QScriptAnalysis::Object) {
|
||||||
if (_type == TextBlockTEmoji
|
if (_type == TextBlockType::Emoji
|
||||||
|| _type == TextBlockTCustomEmoji
|
|| _type == TextBlockType::CustomEmoji
|
||||||
|| _type == TextBlockTSkip) {
|
|| _type == TextBlockType::Skip) {
|
||||||
si.width = currentBlock->f_width() + currentBlock->f_rpadding();
|
si.width = currentBlock->f_width() + currentBlock->f_rpadding();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_type == TextBlockTEmoji
|
if (_type == TextBlockType::Emoji
|
||||||
|| _type == TextBlockTCustomEmoji
|
|| _type == TextBlockType::CustomEmoji
|
||||||
|| _type == TextBlockTSkip
|
|| _type == TextBlockType::Skip
|
||||||
|| _type == TextBlockTNewline) {
|
|| _type == TextBlockType::Newline) {
|
||||||
if (_wLeft < si.width) {
|
if (_wLeft < si.width) {
|
||||||
lineText = lineText.mid(0, currentBlock->from() - _localFrom) + kQEllipsis;
|
lineText = lineText.mid(0, currentBlock->position() - _localFrom) + kQEllipsis;
|
||||||
lineLength = currentBlock->from() + kQEllipsis.size() - _lineStart;
|
lineLength = currentBlock->position() + kQEllipsis.size() - _lineStart;
|
||||||
_selection.to = qMin(_selection.to, currentBlock->from());
|
_selection.to = qMin(_selection.to, currentBlock->position());
|
||||||
_indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0);
|
_indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0);
|
||||||
setElideBidi(currentBlock->from(), kQEllipsis.size());
|
setElideBidi(currentBlock->position(), kQEllipsis.size());
|
||||||
elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth);
|
elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->position(), elideWidth);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_wLeft -= si.width;
|
_wLeft -= si.width;
|
||||||
} else if (_type == TextBlockTText) {
|
} else if (_type == TextBlockType::Text) {
|
||||||
unsigned short *logClusters = engine.logClusters(&si);
|
unsigned short *logClusters = engine.logClusters(&si);
|
||||||
QGlyphLayout glyphs = engine.shapedGlyphs(&si);
|
QGlyphLayout glyphs = engine.shapedGlyphs(&si);
|
||||||
|
|
||||||
|
|
@ -1325,7 +1336,7 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
|
||||||
lineLength += kQEllipsis.size();
|
lineLength += kQEllipsis.size();
|
||||||
|
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) {
|
for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->position() < elideStart; ++blockIndex) {
|
||||||
}
|
}
|
||||||
if (blockIndex < _blocksSize) {
|
if (blockIndex < _blocksSize) {
|
||||||
elideSaveBlock(blockIndex, _endBlock, elideStart, elideWidth);
|
elideSaveBlock(blockIndex, _endBlock, elideStart, elideWidth);
|
||||||
|
|
@ -1371,65 +1382,46 @@ void Renderer::eAppendItems(QScriptAnalysis *analysis, int &start, int &stop, co
|
||||||
|
|
||||||
void Renderer::eShapeLine(const QScriptLine &line) {
|
void Renderer::eShapeLine(const QScriptLine &line) {
|
||||||
int item = _e->findItem(line.from);
|
int item = _e->findItem(line.from);
|
||||||
if (item == -1)
|
if (item == -1) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto end = _e->findItem(line.from + line.length - 1, item);
|
auto end = _e->findItem(line.from + line.length - 1, item);
|
||||||
auto blockIndex = _lineStartBlock;
|
auto blockIndex = _lineStartBlock;
|
||||||
auto currentBlock = _t->_blocks[blockIndex].get();
|
auto currentBlock = _t->_blocks[blockIndex].get();
|
||||||
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
auto nextBlock = (++blockIndex < _blocksSize)
|
||||||
|
? _t->_blocks[blockIndex].get()
|
||||||
|
: nullptr;
|
||||||
eSetFont(currentBlock);
|
eSetFont(currentBlock);
|
||||||
for (; item <= end; ++item) {
|
for (; item <= end; ++item) {
|
||||||
QScriptItem &si = _e->layoutData->items[item];
|
QScriptItem &si = _e->layoutData->items[item];
|
||||||
while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
|
while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
|
||||||
currentBlock = nextBlock;
|
currentBlock = nextBlock;
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize)
|
||||||
|
? _t->_blocks[blockIndex].get()
|
||||||
|
: nullptr;
|
||||||
eSetFont(currentBlock);
|
eSetFont(currentBlock);
|
||||||
}
|
}
|
||||||
_e->shape(item);
|
_e->shape(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style::font Renderer::applyFlags(int32 flags, const style::font &f) {
|
|
||||||
if (!flags) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
auto result = f;
|
|
||||||
if (IsMono(flags)) {
|
|
||||||
result = result->monospace();
|
|
||||||
} else {
|
|
||||||
if (flags & TextBlockFBold) {
|
|
||||||
result = result->bold();
|
|
||||||
} else if (flags & TextBlockFSemibold) {
|
|
||||||
result = result->semibold();
|
|
||||||
}
|
|
||||||
if (flags & TextBlockFItalic) result = result->italic();
|
|
||||||
if (flags & TextBlockFUnderline) result = result->underline();
|
|
||||||
if (flags & TextBlockFStrikeOut) result = result->strikeout();
|
|
||||||
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
|
|
||||||
result = result->semibold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::eSetFont(const AbstractBlock *block) {
|
void Renderer::eSetFont(const AbstractBlock *block) {
|
||||||
const auto flags = block->flags();
|
const auto flags = block->flags();
|
||||||
const auto usedFont = [&] {
|
const auto usedFont = [&] {
|
||||||
if (const auto index = block->lnkIndex()) {
|
if (const auto index = block->linkIndex()) {
|
||||||
const auto active = ClickHandler::showAsActive(
|
const auto active = (_palette && _palette->linkAlwaysActive)
|
||||||
_t->_links.at(index - 1)
|
|| ClickHandler::showAsActive(_t->_links.at(index - 1));
|
||||||
) || (_palette && _palette->linkAlwaysActive > 0);
|
|
||||||
return active
|
return active
|
||||||
? _t->_st->linkFontOver
|
? _t->_st->linkFontOver
|
||||||
: _t->_st->linkFont;
|
: _t->_st->linkFont;
|
||||||
}
|
}
|
||||||
return _t->_st->font;
|
return _t->_st->font;
|
||||||
}();
|
}();
|
||||||
const auto newFont = applyFlags(flags, usedFont);
|
const auto newFont = WithFlags(usedFont, flags);
|
||||||
if (newFont != _f) {
|
if (newFont != _f) {
|
||||||
_f = (newFont->family() == _t->_st->font->family())
|
_f = (newFont->family() == _t->_st->font->family())
|
||||||
? applyFlags(flags | newFont->flags(), _t->_st->font)
|
? WithFlags(_t->_st->font, flags, newFont->flags())
|
||||||
: newFont;
|
: newFont;
|
||||||
_e->fnt = _f->f;
|
_e->fnt = _f->f;
|
||||||
_e->resetFontEngineCache();
|
_e->resetFontEngineCache();
|
||||||
|
|
@ -1479,14 +1471,14 @@ void Renderer::eItemize() {
|
||||||
auto start = string;
|
auto start = string;
|
||||||
auto end = start + length;
|
auto end = start + length;
|
||||||
while (start < end) {
|
while (start < end) {
|
||||||
while (nextBlock && nextBlock->from() <= _localFrom + (start - string)) {
|
while (nextBlock && nextBlock->position() <= _localFrom + (start - string)) {
|
||||||
currentBlock = nextBlock;
|
currentBlock = nextBlock;
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
}
|
}
|
||||||
auto _type = currentBlock->type();
|
auto _type = currentBlock->type();
|
||||||
if (_type == TextBlockTEmoji
|
if (_type == TextBlockType::Emoji
|
||||||
|| _type == TextBlockTCustomEmoji
|
|| _type == TextBlockType::CustomEmoji
|
||||||
|| _type == TextBlockTSkip) {
|
|| _type == TextBlockType::Skip) {
|
||||||
analysis->script = QChar::Script_Common;
|
analysis->script = QChar::Script_Common;
|
||||||
analysis->flags = QScriptAnalysis::Object;
|
analysis->flags = QScriptAnalysis::Object;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1515,7 +1507,7 @@ void Renderer::eItemize() {
|
||||||
auto start = 0;
|
auto start = 0;
|
||||||
auto end = start + length;
|
auto end = start + length;
|
||||||
for (int i = start + 1; i < end; ++i) {
|
for (int i = start + 1; i < end; ++i) {
|
||||||
while (nextBlock && nextBlock->from() <= _localFrom + i) {
|
while (nextBlock && nextBlock->position() <= _localFrom + i) {
|
||||||
currentBlock = nextBlock;
|
currentBlock = nextBlock;
|
||||||
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -1545,20 +1537,23 @@ void Renderer::eItemize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QChar::Direction Renderer::eSkipBoundryNeutrals(QScriptAnalysis *analysis,
|
QChar::Direction Renderer::eSkipBoundryNeutrals(
|
||||||
const ushort *unicode,
|
QScriptAnalysis *analysis,
|
||||||
int &sor, int &eor, BidiControl &control,
|
const ushort *unicode,
|
||||||
String::TextBlocks::const_iterator i) {
|
int &sor,
|
||||||
|
int &eor,
|
||||||
|
BidiControl &control,
|
||||||
|
String::TextBlocks::const_iterator i) {
|
||||||
String::TextBlocks::const_iterator e = _t->_blocks.cend(), n = i + 1;
|
String::TextBlocks::const_iterator e = _t->_blocks.cend(), n = i + 1;
|
||||||
|
|
||||||
QChar::Direction dir = control.basicDirection();
|
QChar::Direction dir = control.basicDirection();
|
||||||
int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level;
|
int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level;
|
||||||
while (sor <= _parLength) {
|
while (sor <= _parLength) {
|
||||||
while (i != _parStartBlock && (*i)->from() > _parStart + sor) {
|
while (i != _parStartBlock && (*i)->position() > _parStart + sor) {
|
||||||
n = i;
|
n = i;
|
||||||
--i;
|
--i;
|
||||||
}
|
}
|
||||||
while (n != e && (*n)->from() <= _parStart + sor) {
|
while (n != e && (*n)->position() <= _parStart + sor) {
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
|
|
@ -1566,10 +1561,10 @@ QChar::Direction Renderer::eSkipBoundryNeutrals(QScriptAnalysis *analysis,
|
||||||
TextBlockType _itype = (*i)->type();
|
TextBlockType _itype = (*i)->type();
|
||||||
if (eor == _parLength)
|
if (eor == _parLength)
|
||||||
dir = control.basicDirection();
|
dir = control.basicDirection();
|
||||||
else if (_itype == TextBlockTEmoji
|
else if (_itype == TextBlockType::Emoji
|
||||||
|| _itype == TextBlockTCustomEmoji)
|
|| _itype == TextBlockType::CustomEmoji)
|
||||||
dir = QChar::DirCS;
|
dir = QChar::DirCS;
|
||||||
else if (_itype == TextBlockTSkip)
|
else if (_itype == TextBlockType::Skip)
|
||||||
dir = QChar::DirCS;
|
dir = QChar::DirCS;
|
||||||
else
|
else
|
||||||
dir = QChar::direction(unicode[sor]);
|
dir = QChar::direction(unicode[sor]);
|
||||||
|
|
@ -1602,9 +1597,9 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
|
|
||||||
QChar::Direction sdir;
|
QChar::Direction sdir;
|
||||||
TextBlockType _stype = (*_parStartBlock)->type();
|
TextBlockType _stype = (*_parStartBlock)->type();
|
||||||
if (_stype == TextBlockTEmoji || _stype == TextBlockTCustomEmoji)
|
if (_stype == TextBlockType::Emoji || _stype == TextBlockType::CustomEmoji)
|
||||||
sdir = QChar::DirCS;
|
sdir = QChar::DirCS;
|
||||||
else if (_stype == TextBlockTSkip)
|
else if (_stype == TextBlockType::Skip)
|
||||||
sdir = QChar::DirCS;
|
sdir = QChar::DirCS;
|
||||||
else
|
else
|
||||||
sdir = QChar::direction(*unicode);
|
sdir = QChar::direction(*unicode);
|
||||||
|
|
@ -1619,7 +1614,7 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
status.dir = sdir;
|
status.dir = sdir;
|
||||||
|
|
||||||
while (current <= _parLength) {
|
while (current <= _parLength) {
|
||||||
while (n != e && (*n)->from() <= _parStart + current) {
|
while (n != e && (*n)->position() <= _parStart + current) {
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
|
|
@ -1628,10 +1623,10 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
TextBlockType _itype = (*i)->type();
|
TextBlockType _itype = (*i)->type();
|
||||||
if (current == (int)_parLength)
|
if (current == (int)_parLength)
|
||||||
dirCurrent = control.basicDirection();
|
dirCurrent = control.basicDirection();
|
||||||
else if (_itype == TextBlockTEmoji
|
else if (_itype == TextBlockType::Emoji
|
||||||
|| _itype == TextBlockTCustomEmoji)
|
|| _itype == TextBlockType::CustomEmoji)
|
||||||
dirCurrent = QChar::DirCS;
|
dirCurrent = QChar::DirCS;
|
||||||
else if (_itype == TextBlockTSkip)
|
else if (_itype == TextBlockType::Skip)
|
||||||
dirCurrent = QChar::DirCS;
|
dirCurrent = QChar::DirCS;
|
||||||
else
|
else
|
||||||
dirCurrent = QChar::direction(unicode[current]);
|
dirCurrent = QChar::direction(unicode[current]);
|
||||||
|
|
@ -2009,23 +2004,34 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
||||||
eSetFont(block);
|
eSetFont(block);
|
||||||
if (_p) {
|
if (_p) {
|
||||||
const auto isMono = IsMono(block->flags());
|
const auto flags = block->flags();
|
||||||
|
const auto isMono = IsMono(flags);
|
||||||
_background = {};
|
_background = {};
|
||||||
if (block->spoilerIndex() && _spoiler) {
|
if ((flags & TextBlockFlag::Spoiler) && _spoiler) {
|
||||||
_background.spoiler = true;
|
_background.spoiler = true;
|
||||||
}
|
}
|
||||||
if (isMono
|
if (isMono
|
||||||
&& block->lnkIndex()
|
&& block->linkIndex()
|
||||||
&& (!_background.spoiler || _spoiler->revealed)) {
|
&& (!_background.spoiler || _spoiler->revealed)) {
|
||||||
_background.selectActiveBlock = ClickHandler::showAsPressed(
|
_background.selectActiveBlock = ClickHandler::showAsPressed(
|
||||||
_t->_links.at(block->lnkIndex() - 1));
|
_t->_links.at(block->linkIndex() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMono) {
|
if (const auto color = block->colorIndex()) {
|
||||||
|
if (color == 1) {
|
||||||
|
_currentPen = &_palette->linkFg->p;
|
||||||
|
_currentPenSelected = &_palette->selectLinkFg->p;
|
||||||
|
} else if (color - 1 <= _colors.size()) {
|
||||||
|
_currentPen = _colors[color - 2].pen;
|
||||||
|
_currentPenSelected = _colors[color - 2].penSelected;
|
||||||
|
} else {
|
||||||
|
_currentPen = &_originalPen;
|
||||||
|
_currentPenSelected = &_originalPenSelected;
|
||||||
|
}
|
||||||
|
} else if (isMono) {
|
||||||
_currentPen = &_palette->monoFg->p;
|
_currentPen = &_palette->monoFg->p;
|
||||||
_currentPenSelected = &_palette->selectMonoFg->p;
|
_currentPenSelected = &_palette->selectMonoFg->p;
|
||||||
} else if (block->lnkIndex()
|
} else if (block->linkIndex()) {
|
||||||
|| (block->flags() & TextBlockFPlainLink)) {
|
|
||||||
_currentPen = &_palette->linkFg->p;
|
_currentPen = &_palette->linkFg->p;
|
||||||
_currentPenSelected = &_palette->selectLinkFg->p;
|
_currentPenSelected = &_palette->selectLinkFg->p;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2038,12 +2044,12 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
||||||
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
|
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
|
||||||
const auto spoilerLink = (_spoiler
|
const auto spoilerLink = (_spoiler
|
||||||
&& !_spoiler->revealed
|
&& !_spoiler->revealed
|
||||||
&& block->spoilerIndex())
|
&& (block->flags() & TextBlockFlag::Spoiler))
|
||||||
? _spoiler->link
|
? _spoiler->link
|
||||||
: ClickHandlerPtr();
|
: ClickHandlerPtr();
|
||||||
return (spoilerLink || !block->lnkIndex())
|
return (spoilerLink || !block->linkIndex())
|
||||||
? spoilerLink
|
? spoilerLink
|
||||||
: _t->_links.at(block->lnkIndex() - 1);
|
: _t->_links.at(block->linkIndex() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ui::Text
|
} // namespace Ui::Text
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,8 @@ public:
|
||||||
void draw(QPainter &p, const PaintContext &context);
|
void draw(QPainter &p, const PaintContext &context);
|
||||||
[[nodiscard]] StateResult getState(
|
[[nodiscard]] StateResult getState(
|
||||||
QPoint point,
|
QPoint point,
|
||||||
int w,
|
GeometryDescriptor geometry,
|
||||||
StateRequest request);
|
StateRequest request);
|
||||||
[[nodiscard]] StateResult getStateElided(
|
|
||||||
QPoint point,
|
|
||||||
int w,
|
|
||||||
StateRequestElided request);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr int kSpoilersRectsSize = 512;
|
static constexpr int kSpoilersRectsSize = 512;
|
||||||
|
|
@ -52,7 +48,10 @@ private:
|
||||||
void enumerate();
|
void enumerate();
|
||||||
|
|
||||||
[[nodiscard]] crl::time now() const;
|
[[nodiscard]] crl::time now() const;
|
||||||
void initNextParagraph(String::TextBlocks::const_iterator i);
|
void initNextParagraph(
|
||||||
|
String::TextBlocks::const_iterator i,
|
||||||
|
Qt::LayoutDirection direction);
|
||||||
|
void initNextLine();
|
||||||
void initParagraphBidi();
|
void initParagraphBidi();
|
||||||
bool drawLine(
|
bool drawLine(
|
||||||
uint16 _lineEnd,
|
uint16 _lineEnd,
|
||||||
|
|
@ -94,7 +93,6 @@ private:
|
||||||
const BidiControl &control,
|
const BidiControl &control,
|
||||||
QChar::Direction dir);
|
QChar::Direction dir);
|
||||||
void eShapeLine(const QScriptLine &line);
|
void eShapeLine(const QScriptLine &line);
|
||||||
[[nodiscard]] style::font applyFlags(int32 flags, const style::font &f);
|
|
||||||
void eSetFont(const AbstractBlock *block);
|
void eSetFont(const AbstractBlock *block);
|
||||||
void eItemize();
|
void eItemize();
|
||||||
QChar::Direction eSkipBoundryNeutrals(
|
QChar::Direction eSkipBoundryNeutrals(
|
||||||
|
|
@ -111,13 +109,12 @@ private:
|
||||||
const AbstractBlock *block) const;
|
const AbstractBlock *block) const;
|
||||||
|
|
||||||
const String *_t = nullptr;
|
const String *_t = nullptr;
|
||||||
|
GeometryDescriptor _geometry;
|
||||||
SpoilerData *_spoiler = nullptr;
|
SpoilerData *_spoiler = nullptr;
|
||||||
SpoilerMessCache *_spoilerCache = nullptr;
|
SpoilerMessCache *_spoilerCache = nullptr;
|
||||||
QPainter *_p = nullptr;
|
QPainter *_p = nullptr;
|
||||||
const style::TextPalette *_palette = nullptr;
|
const style::TextPalette *_palette = nullptr;
|
||||||
bool _elideLast = false;
|
std::span<SpecialColor> _colors;
|
||||||
bool _breakEverywhere = false;
|
|
||||||
int _elideRemoveFromEnd = 0;
|
|
||||||
bool _pausedEmoji = false;
|
bool _pausedEmoji = false;
|
||||||
bool _pausedSpoiler = false;
|
bool _pausedSpoiler = false;
|
||||||
style::align _align = style::al_topleft;
|
style::align _align = style::al_topleft;
|
||||||
|
|
@ -131,7 +128,6 @@ private:
|
||||||
} _background;
|
} _background;
|
||||||
int _yFrom = 0;
|
int _yFrom = 0;
|
||||||
int _yTo = 0;
|
int _yTo = 0;
|
||||||
int _yToElide = 0;
|
|
||||||
TextSelection _selection = { 0, 0 };
|
TextSelection _selection = { 0, 0 };
|
||||||
bool _fullWidthSelection = true;
|
bool _fullWidthSelection = true;
|
||||||
const QChar *_str = nullptr;
|
const QChar *_str = nullptr;
|
||||||
|
|
@ -158,11 +154,15 @@ private:
|
||||||
// current line data
|
// current line data
|
||||||
QTextEngine *_e = nullptr;
|
QTextEngine *_e = nullptr;
|
||||||
style::font _f;
|
style::font _f;
|
||||||
QFixed _x, _w, _wLeft, _last_rPadding;
|
int _startLeft = 0;
|
||||||
|
int _startTop = 0;
|
||||||
|
QFixed _x, _wLeft, _last_rPadding;
|
||||||
int _y = 0;
|
int _y = 0;
|
||||||
int _yDelta = 0;
|
int _yDelta = 0;
|
||||||
int _lineHeight = 0;
|
int _lineHeight = 0;
|
||||||
int _fontHeight = 0;
|
int _fontHeight = 0;
|
||||||
|
bool _breakEverywhere = false;
|
||||||
|
bool _elidedLine = false;
|
||||||
|
|
||||||
// elided hack support
|
// elided hack support
|
||||||
int _blocksSize = 0;
|
int _blocksSize = 0;
|
||||||
|
|
@ -172,6 +172,8 @@ private:
|
||||||
int _lineStart = 0;
|
int _lineStart = 0;
|
||||||
int _localFrom = 0;
|
int _localFrom = 0;
|
||||||
int _lineStartBlock = 0;
|
int _lineStartBlock = 0;
|
||||||
|
QFixed _lineWidth = 0;
|
||||||
|
QFixed _paragraphWidthRemaining = 0;
|
||||||
|
|
||||||
// link and symbol resolve
|
// link and symbol resolve
|
||||||
QFixed _lookupX = 0;
|
QFixed _lookupX = 0;
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,14 @@ TextWithEntities Link(TextWithEntities text, int index) {
|
||||||
return Link(std::move(text), u"internal:index"_q + QChar(index));
|
return Link(std::move(text), u"internal:index"_q + QChar(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities PlainLink(const QString &text) {
|
TextWithEntities Colorized(const QString &text, int index) {
|
||||||
return WithSingleEntity(text, EntityType::PlainLink);
|
const auto data = index ? QString(QChar(index)) : QString();
|
||||||
|
return WithSingleEntity(text, EntityType::Colorized, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities PlainLink(TextWithEntities text) {
|
TextWithEntities Colorized(TextWithEntities text, int index) {
|
||||||
return Wrapped(std::move(text), EntityType::PlainLink, QString());
|
const auto data = index ? QString(QChar(index)) : QString();
|
||||||
|
return Wrapped(std::move(text), EntityType::Colorized, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Wrapped(
|
TextWithEntities Wrapped(
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,12 @@ inline constexpr auto Upper = details::ToUpperType{};
|
||||||
TextWithEntities text,
|
TextWithEntities text,
|
||||||
const QString &url = u"internal:action"_q);
|
const QString &url = u"internal:action"_q);
|
||||||
[[nodiscard]] TextWithEntities Link(TextWithEntities text, int index);
|
[[nodiscard]] TextWithEntities Link(TextWithEntities text, int index);
|
||||||
[[nodiscard]] TextWithEntities PlainLink(const QString &text);
|
[[nodiscard]] TextWithEntities Colorized(
|
||||||
[[nodiscard]] TextWithEntities PlainLink(TextWithEntities text);
|
const QString &text,
|
||||||
|
int index = 0);
|
||||||
|
[[nodiscard]] TextWithEntities Colorized(
|
||||||
|
TextWithEntities text,
|
||||||
|
int index = 0);
|
||||||
[[nodiscard]] TextWithEntities Wrapped(
|
[[nodiscard]] TextWithEntities Wrapped(
|
||||||
TextWithEntities text,
|
TextWithEntities text,
|
||||||
EntityType type,
|
EntityType type,
|
||||||
|
|
|
||||||
|
|
@ -173,14 +173,13 @@ void Widget::paintEvent(QPaintEvent *e) {
|
||||||
width());
|
width());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto lines = _maxTextHeight / _st->style.font->height;
|
|
||||||
p.setPen(st::toastFg);
|
p.setPen(st::toastFg);
|
||||||
_text.draw(p, {
|
_text.draw(p, {
|
||||||
.position = { _st->padding.left(), _textTop },
|
.position = { _st->padding.left(), _textTop },
|
||||||
.availableWidth = _textWidth + 1,
|
.availableWidth = _textWidth + 1,
|
||||||
.palette = &_st->palette,
|
.palette = &_st->palette,
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.elisionLines = lines,
|
.elisionHeight = _maxTextHeight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -587,8 +587,8 @@ void Checkbox::setTextBreakEverywhere(bool allow) {
|
||||||
_textBreakEverywhere = allow;
|
_textBreakEverywhere = allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Checkbox::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
void Checkbox::setLink(uint16 index, const ClickHandlerPtr &lnk) {
|
||||||
_text.setLink(lnkIndex, lnk);
|
_text.setLink(index, lnk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Checkbox::setLinksTrusted() {
|
void Checkbox::setLinksTrusted() {
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ public:
|
||||||
void setAllowTextLines(int lines = 0);
|
void setAllowTextLines(int lines = 0);
|
||||||
void setTextBreakEverywhere(bool allow = true);
|
void setTextBreakEverywhere(bool allow = true);
|
||||||
|
|
||||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
void setLink(uint16 index, const ClickHandlerPtr &lnk);
|
||||||
void setLinksTrusted();
|
void setLinksTrusted();
|
||||||
|
|
||||||
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;
|
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;
|
||||||
|
|
|
||||||
|
|
@ -368,8 +368,8 @@ void FlatLabel::refreshSize() {
|
||||||
resize(fullWidth, fullHeight);
|
resize(fullWidth, fullHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
|
void FlatLabel::setLink(uint16 index, const ClickHandlerPtr &lnk) {
|
||||||
_text.setLink(lnkIndex, lnk);
|
_text.setLink(index, lnk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlatLabel::setLinksTrusted() {
|
void FlatLabel::setLinksTrusted() {
|
||||||
|
|
@ -795,7 +795,9 @@ CrossFadeAnimation::Data FlatLabel::crossFadeData(
|
||||||
auto result = CrossFadeAnimation::Data();
|
auto result = CrossFadeAnimation::Data();
|
||||||
result.full = GrabWidgetToImage(this, QRect(), bg->c);
|
result.full = GrabWidgetToImage(this, QRect(), bg->c);
|
||||||
const auto textWidth = width() - _st.margin.left() - _st.margin.right();
|
const auto textWidth = width() - _st.margin.left() - _st.margin.right();
|
||||||
_text.countLineWidths(textWidth, &result.lineWidths, _breakEverywhere);
|
result.lineWidths = _text.countLineWidths(textWidth, {
|
||||||
|
.breakEverywhere = _breakEverywhere,
|
||||||
|
});
|
||||||
result.lineHeight = _st.style.font->height;
|
result.lineHeight = _st.style.font->height;
|
||||||
const auto addedHeight = (_st.style.lineHeight - result.lineHeight);
|
const auto addedHeight = (_st.style.lineHeight - result.lineHeight);
|
||||||
if (addedHeight > 0) {
|
if (addedHeight > 0) {
|
||||||
|
|
@ -961,11 +963,11 @@ void FlatLabel::paintEvent(QPaintEvent *e) {
|
||||||
&& (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
|
&& (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
|
||||||
const auto renderElided = _breakEverywhere || heightExceeded;
|
const auto renderElided = _breakEverywhere || heightExceeded;
|
||||||
const auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height);
|
const auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height);
|
||||||
const auto lines = !renderElided
|
const auto elisionHeight = !renderElided
|
||||||
? 0
|
? 0
|
||||||
: _st.maxHeight
|
: _st.maxHeight
|
||||||
? qMax(_st.maxHeight / lineHeight, 1)
|
? qMax(_st.maxHeight, lineHeight)
|
||||||
: ((height() / lineHeight) + 2);
|
: height();
|
||||||
const auto paused = _animationsPausedCallback
|
const auto paused = _animationsPausedCallback
|
||||||
? _animationsPausedCallback()
|
? _animationsPausedCallback()
|
||||||
: WhichAnimationsPaused::None;
|
: WhichAnimationsPaused::None;
|
||||||
|
|
@ -982,7 +984,7 @@ void FlatLabel::paintEvent(QPaintEvent *e) {
|
||||||
.pausedSpoiler = (paused == WhichAnimationsPaused::Spoiler
|
.pausedSpoiler = (paused == WhichAnimationsPaused::Spoiler
|
||||||
|| paused == WhichAnimationsPaused::All),
|
|| paused == WhichAnimationsPaused::All),
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.elisionLines = lines,
|
.elisionHeight = elisionHeight,
|
||||||
.elisionBreakEverywhere = renderElided && _breakEverywhere,
|
.elisionBreakEverywhere = renderElided && _breakEverywhere,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class CrossFadeAnimation {
|
||||||
public:
|
public:
|
||||||
struct Data {
|
struct Data {
|
||||||
QImage full;
|
QImage full;
|
||||||
QVector<int> lineWidths;
|
std::vector<int> lineWidths;
|
||||||
QPoint position;
|
QPoint position;
|
||||||
style::align align;
|
style::align align;
|
||||||
style::font font;
|
style::font font;
|
||||||
|
|
@ -143,7 +143,7 @@ public:
|
||||||
int naturalWidth() const override;
|
int naturalWidth() const override;
|
||||||
QMargins getMargins() const override;
|
QMargins getMargins() const override;
|
||||||
|
|
||||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
void setLink(uint16 index, const ClickHandlerPtr &lnk);
|
||||||
void setLinksTrusted();
|
void setLinksTrusted();
|
||||||
|
|
||||||
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;
|
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;
|
||||||
|
|
|
||||||
|
|
@ -166,10 +166,10 @@ void SideBarButton::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_lock.locked) {
|
if (_lock.locked) {
|
||||||
auto lineWidths = QVector<int>();
|
const auto lineWidths = _text.countLineWidths(
|
||||||
lineWidths.reserve(kMaxLabelLines);
|
width() - 2 * _st.textSkip,
|
||||||
_text.countLineWidths(width() - 2 * _st.textSkip, &lineWidths);
|
{ .reserve = kMaxLabelLines });
|
||||||
if (lineWidths.isEmpty()) {
|
if (lineWidths.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
validateLockIconCache();
|
validateLockIconCache();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue