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