mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 11:52:58 +00:00
686 lines
19 KiB
C++
686 lines
19 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "tests/test_main.h"
|
|
|
|
#include "base/invoke_queued.h"
|
|
#include "base/integration.h"
|
|
#include "ui/effects/animations.h"
|
|
#include "ui/style/style_core.h"
|
|
#include "ui/text/text.h"
|
|
#include "ui/text/text_custom_emoji.h"
|
|
#include "ui/text/text_utilities.h"
|
|
#include "ui/widgets/rp_window.h"
|
|
#include "ui/painter.h"
|
|
|
|
#include <QApplication>
|
|
#include <QAbstractNativeEventFilter>
|
|
#include <QThread>
|
|
#include <QDir>
|
|
#include <QImage>
|
|
|
|
#include <algorithm>
|
|
#include <span>
|
|
#include <utility>
|
|
|
|
namespace Test {
|
|
|
|
namespace {
|
|
|
|
[[nodiscard]] bool HasEntityType(
|
|
const EntitiesInText &entities,
|
|
EntityType type) {
|
|
for (const auto &entity : entities) {
|
|
if (entity.type() == type) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] QImage MakeObjectImage(
|
|
QSize size,
|
|
QColor background,
|
|
const QString &label) {
|
|
auto image = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
auto painter = QPainter(&image);
|
|
auto hq = PainterHighQualityEnabler(painter);
|
|
painter.setPen(Qt::NoPen);
|
|
painter.setBrush(background);
|
|
painter.drawRoundedRect(
|
|
QRect(QPoint(), size),
|
|
scale(6),
|
|
scale(6));
|
|
painter.setPen(Qt::black);
|
|
painter.drawText(QRect(QPoint(), size), Qt::AlignCenter, label);
|
|
return image;
|
|
}
|
|
|
|
[[nodiscard]] int RenderTextPadding() {
|
|
return scale(6);
|
|
}
|
|
|
|
[[nodiscard]] QImage RenderTextOffscreen(
|
|
const Ui::Text::String &text,
|
|
int availableWidth,
|
|
std::optional<TextSelection> selection = std::nullopt,
|
|
std::span<Ui::Text::SpecialColor> colors = {},
|
|
std::optional<QPen> pen = std::nullopt) {
|
|
const auto padding = RenderTextPadding();
|
|
const auto image = QImage(
|
|
QSize(
|
|
std::max(text.maxWidth(), availableWidth) + (2 * padding),
|
|
std::max(text.countHeight(availableWidth), text.minHeight())
|
|
+ (2 * padding)),
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
auto result = image;
|
|
result.fill(Qt::transparent);
|
|
auto painter = QPainter(&result);
|
|
if (pen) {
|
|
painter.setPen(*pen);
|
|
}
|
|
text.draw(painter, {
|
|
.position = QPoint(padding, padding),
|
|
.availableWidth = availableWidth,
|
|
.colors = colors,
|
|
.selection = selection.value_or(TextSelection()),
|
|
});
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<QRect> ChangedBoundsInRect(
|
|
const QImage &first,
|
|
const QImage &second,
|
|
QRect rect) {
|
|
if (first.size() != second.size()) {
|
|
return std::nullopt;
|
|
}
|
|
rect = rect.intersected(QRect(QPoint(), first.size()));
|
|
if (rect.isEmpty()) {
|
|
return std::nullopt;
|
|
}
|
|
auto left = rect.right();
|
|
auto top = rect.bottom();
|
|
auto right = rect.left() - 1;
|
|
auto bottom = rect.top() - 1;
|
|
for (auto y = rect.top(); y <= rect.bottom(); ++y) {
|
|
for (auto x = rect.left(); x <= rect.right(); ++x) {
|
|
if (first.pixel(x, y) == second.pixel(x, y)) {
|
|
continue;
|
|
}
|
|
left = std::min(left, x);
|
|
top = std::min(top, y);
|
|
right = std::max(right, x);
|
|
bottom = std::max(bottom, y);
|
|
}
|
|
}
|
|
return (right >= left) && (bottom >= top)
|
|
? std::make_optional(QRect(QPoint(left, top), QPoint(right, bottom)))
|
|
: std::nullopt;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<QRect> SymbolHitBounds(
|
|
const Ui::Text::String &text,
|
|
int availableWidth,
|
|
int offset) {
|
|
if ((availableWidth <= 0) || (offset < 0)) {
|
|
return std::nullopt;
|
|
}
|
|
const auto padding = RenderTextPadding();
|
|
const auto height = std::max(
|
|
text.countHeight(availableWidth),
|
|
text.minHeight());
|
|
auto flags = Ui::Text::StateRequest::Flags();
|
|
flags |= Ui::Text::StateRequest::Flag::LookupSymbol;
|
|
auto request = Ui::Text::StateRequest();
|
|
request.flags = flags;
|
|
auto left = padding + availableWidth;
|
|
auto top = padding + height;
|
|
auto right = -1;
|
|
auto bottom = -1;
|
|
for (auto y = 0; y != height; ++y) {
|
|
for (auto x = 0; x != availableWidth; ++x) {
|
|
const auto hit = text.getState(QPoint(x, y), availableWidth, request);
|
|
if (!hit.uponSymbol || (int(hit.symbol) != offset)) {
|
|
continue;
|
|
}
|
|
left = std::min(left, x + padding);
|
|
top = std::min(top, y + padding);
|
|
right = std::max(right, x + padding);
|
|
bottom = std::max(bottom, y + padding);
|
|
}
|
|
}
|
|
return (right >= left) && (bottom >= top)
|
|
? std::make_optional(QRect(QPoint(left, top), QPoint(right, bottom)))
|
|
: std::nullopt;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<QRect> SymbolRangeHitBounds(
|
|
const Ui::Text::String &text,
|
|
int availableWidth,
|
|
int offset,
|
|
int length) {
|
|
if ((availableWidth <= 0) || (offset < 0) || (length <= 0)) {
|
|
return std::nullopt;
|
|
}
|
|
const auto padding = RenderTextPadding();
|
|
const auto height = std::max(
|
|
text.countHeight(availableWidth),
|
|
text.minHeight());
|
|
auto flags = Ui::Text::StateRequest::Flags();
|
|
flags |= Ui::Text::StateRequest::Flag::LookupSymbol;
|
|
auto request = Ui::Text::StateRequest();
|
|
request.flags = flags;
|
|
auto left = padding + availableWidth;
|
|
auto top = padding + height;
|
|
auto right = -1;
|
|
auto bottom = -1;
|
|
const auto end = offset + length;
|
|
for (auto y = 0; y != height; ++y) {
|
|
for (auto x = 0; x != availableWidth; ++x) {
|
|
const auto hit = text.getState(QPoint(x, y), availableWidth, request);
|
|
if (!hit.uponSymbol) {
|
|
continue;
|
|
}
|
|
const auto symbol = int(hit.symbol);
|
|
if ((symbol < offset) || (symbol >= end)) {
|
|
continue;
|
|
}
|
|
left = std::min(left, x + padding);
|
|
top = std::min(top, y + padding);
|
|
right = std::max(right, x + padding);
|
|
bottom = std::max(bottom, y + padding);
|
|
}
|
|
}
|
|
return (right >= left) && (bottom >= top)
|
|
? std::make_optional(QRect(QPoint(left, top), QPoint(right, bottom)))
|
|
: std::nullopt;
|
|
}
|
|
|
|
[[nodiscard]] bool ImagesEqualInRect(
|
|
const QImage &first,
|
|
const QImage &second,
|
|
QRect rect) {
|
|
return !ChangedBoundsInRect(first, second, rect).has_value();
|
|
}
|
|
|
|
[[nodiscard]] bool HasPixelColorInRect(
|
|
const QImage &image,
|
|
QRect rect,
|
|
QRgb color) {
|
|
rect = rect.intersected(QRect(QPoint(), image.size()));
|
|
if (rect.isEmpty()) {
|
|
return false;
|
|
}
|
|
for (auto y = rect.top(); y <= rect.bottom(); ++y) {
|
|
for (auto x = rect.left(); x <= rect.right(); ++x) {
|
|
if (image.pixel(x, y) == color) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] bool HasPaintedPixels(const QImage &image) {
|
|
const auto bits = image.constBits();
|
|
if (!bits) {
|
|
return false;
|
|
}
|
|
const auto count = image.sizeInBytes();
|
|
for (auto i = qsizetype(0); i != count; ++i) {
|
|
if (bits[i] != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class FormulaLikeObject final : public Ui::Text::CustomEmoji {
|
|
public:
|
|
FormulaLikeObject(QString entityData, QString replacementText, QImage image);
|
|
|
|
int width() override;
|
|
QString entityData() override;
|
|
std::optional<Ui::Text::CustomEmojiVerticalMetrics> vertical(
|
|
const style::TextStyle &) override;
|
|
QString replacementText() override;
|
|
Ui::Text::CustomEmojiSemantics semantics() override;
|
|
void paint(QPainter &p, const Context &context) override;
|
|
void unload() override;
|
|
bool ready() override;
|
|
bool readyInDefaultState() override;
|
|
|
|
private:
|
|
QString _entityData;
|
|
QString _replacementText;
|
|
QImage _image;
|
|
|
|
};
|
|
|
|
FormulaLikeObject::FormulaLikeObject(
|
|
QString entityData,
|
|
QString replacementText,
|
|
QImage image)
|
|
: _entityData(std::move(entityData))
|
|
, _replacementText(std::move(replacementText))
|
|
, _image(std::move(image)) {
|
|
}
|
|
|
|
int FormulaLikeObject::width() {
|
|
return _image.width();
|
|
}
|
|
|
|
QString FormulaLikeObject::entityData() {
|
|
return _entityData;
|
|
}
|
|
|
|
std::optional<Ui::Text::CustomEmojiVerticalMetrics>
|
|
FormulaLikeObject::vertical(const style::TextStyle &) {
|
|
const auto height = _image.height();
|
|
const auto descent = std::max(height / 5, 1);
|
|
return Ui::Text::CustomEmojiVerticalMetrics{
|
|
.ascent = height - descent,
|
|
.descent = descent,
|
|
};
|
|
}
|
|
|
|
QString FormulaLikeObject::replacementText() {
|
|
return _replacementText;
|
|
}
|
|
|
|
Ui::Text::CustomEmojiSemantics FormulaLikeObject::semantics() {
|
|
return {
|
|
.isEmoji = false,
|
|
.isRealCustomEmoji = false,
|
|
.exportEntity = false,
|
|
.unloadPersistentAnimation = false,
|
|
.allowCustomEmojiClick = false,
|
|
};
|
|
}
|
|
|
|
void FormulaLikeObject::paint(QPainter &p, const Context &context) {
|
|
p.drawImage(context.position, _image);
|
|
}
|
|
|
|
void FormulaLikeObject::unload() {
|
|
}
|
|
|
|
bool FormulaLikeObject::ready() {
|
|
return true;
|
|
}
|
|
|
|
bool FormulaLikeObject::readyInDefaultState() {
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
QString name() {
|
|
return u"text"_q;
|
|
}
|
|
|
|
void test(not_null<Ui::RpWindow*> window, not_null<Ui::RpWidget*> body) {
|
|
(void)window;
|
|
|
|
const auto formulaEntityData = u"test-formula-like-object"_q;
|
|
const auto formulaReplacementText = u"$\\frac{a}{b}$"_q;
|
|
const auto controlEntityData = u"test-custom-emoji"_q;
|
|
const auto formulaImage = MakeObjectImage(
|
|
QSize(scale(64), scale(28)),
|
|
QColor(32, 96, 192, 48),
|
|
u"a / b"_q);
|
|
const auto controlImage = MakeObjectImage(
|
|
QSize(scale(24), scale(24)),
|
|
QColor(224, 176, 32, 160),
|
|
u"*"_q);
|
|
|
|
auto context = Ui::Text::MarkedContext();
|
|
context.customEmojiFactory = [
|
|
formulaEntityData,
|
|
formulaReplacementText,
|
|
formulaImage,
|
|
controlImage
|
|
](QStringView data, const Ui::Text::MarkedContext &)
|
|
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
|
if (data == formulaEntityData) {
|
|
return std::make_unique<FormulaLikeObject>(
|
|
data.toString(),
|
|
formulaReplacementText,
|
|
formulaImage);
|
|
}
|
|
if (data == u"test-custom-emoji"_q) {
|
|
return std::make_unique<Ui::Text::PaletteDependentCustomEmoji>(
|
|
[controlImage] {
|
|
return controlImage;
|
|
},
|
|
data.toString());
|
|
}
|
|
return std::unique_ptr<Ui::Text::CustomEmoji>();
|
|
};
|
|
|
|
auto formulaData = TextWithEntities();
|
|
formulaData.append(u"Alpha "_q);
|
|
const auto formulaPosition = formulaData.text.size();
|
|
formulaData.append(QChar::ObjectReplacementCharacter);
|
|
formulaData.entities.push_back(EntityInText(
|
|
EntityType::CustomEmoji,
|
|
formulaPosition,
|
|
1,
|
|
formulaEntityData));
|
|
formulaData.append(u" omega"_q);
|
|
|
|
const auto formulaText = new Ui::Text::String(scale(64));
|
|
formulaText->setMarkedText(
|
|
st::defaultTextStyle,
|
|
formulaData,
|
|
kMarkupTextOptions,
|
|
context);
|
|
|
|
const auto formulaCollapsedText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
u"Alpha omega"_q,
|
|
kMarkupTextOptions,
|
|
scale(64));
|
|
const auto objectPlaceholderWidth = st::defaultTextStyle.font->width(u" "_q);
|
|
Expects(
|
|
formulaText->maxWidth()
|
|
>= formulaCollapsedText.maxWidth()
|
|
- objectPlaceholderWidth
|
|
+ formulaImage.width());
|
|
|
|
const auto expectedFormulaExport = u"Alpha $\\frac{a}{b}$ omega"_q;
|
|
Expects(formulaText->toString() == expectedFormulaExport);
|
|
const auto formulaMime = formulaText->toTextForMimeData();
|
|
Expects(formulaMime.expanded == expectedFormulaExport);
|
|
Expects(formulaMime.rich.text == expectedFormulaExport);
|
|
Expects(!HasEntityType(formulaMime.rich.entities, EntityType::CustomEmoji));
|
|
const auto formulaRich = formulaText->toTextWithEntities();
|
|
Expects(formulaRich.text == expectedFormulaExport);
|
|
Expects(!HasEntityType(formulaRich.entities, EntityType::CustomEmoji));
|
|
Expects(
|
|
formulaText->adjustSelection(
|
|
TextSelection(uint16(formulaPosition), uint16(formulaPosition)),
|
|
TextSelectType::Words)
|
|
== TextSelection(
|
|
uint16(formulaPosition),
|
|
uint16(formulaPosition + 1)));
|
|
Expects(
|
|
formulaText->adjustSelection(
|
|
TextSelection(uint16(formulaPosition), uint16(formulaPosition + 1)),
|
|
TextSelectType::Paragraphs)
|
|
== TextSelection(
|
|
uint16(formulaPosition),
|
|
uint16(formulaPosition + 1)));
|
|
Expects(!formulaText->hasCustomEmoji());
|
|
Expects(!formulaText->isOnlyCustomEmoji());
|
|
Expects(!formulaText->isIsolatedEmoji());
|
|
|
|
auto controlData = TextWithEntities();
|
|
controlData.append(QChar::ObjectReplacementCharacter);
|
|
controlData.entities.push_back(EntityInText(
|
|
EntityType::CustomEmoji,
|
|
0,
|
|
1,
|
|
controlEntityData));
|
|
|
|
const auto controlText = new Ui::Text::String(scale(64));
|
|
controlText->setMarkedText(
|
|
st::defaultTextStyle,
|
|
controlData,
|
|
kMarkupTextOptions,
|
|
context);
|
|
Expects(controlText->hasCustomEmoji());
|
|
Expects(controlText->isOnlyCustomEmoji());
|
|
Expects(controlText->isIsolatedEmoji());
|
|
Expects(
|
|
HasEntityType(
|
|
controlText->toTextWithEntities().entities,
|
|
EntityType::CustomEmoji));
|
|
const auto controlProbeText = u"a / b"_q;
|
|
const auto controlLineSource = u"Alpha a / b omega"_q;
|
|
const auto controlProbePosition = controlLineSource.indexOf(controlProbeText);
|
|
Expects(controlProbePosition >= 0);
|
|
const auto controlLineText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
controlLineSource,
|
|
kMarkupTextOptions,
|
|
scale(64));
|
|
const auto selectionWidth = std::max(
|
|
formulaText->maxWidth(),
|
|
controlLineText.maxWidth());
|
|
const auto formulaLinesGeometry = formulaText->countLinesGeometry(
|
|
selectionWidth);
|
|
const auto controlLinesGeometry = controlLineText.countLinesGeometry(
|
|
selectionWidth);
|
|
Expects(formulaLinesGeometry.size() == 1);
|
|
Expects(controlLinesGeometry.size() == 1);
|
|
if ((formulaLinesGeometry.size() == 1)
|
|
&& (controlLinesGeometry.size() == 1)) {
|
|
const auto &formulaLine = formulaLinesGeometry.front();
|
|
const auto &controlLine = controlLinesGeometry.front();
|
|
Expects(formulaLine.baseline > controlLine.baseline);
|
|
Expects(formulaLine.baseline < formulaLine.bottom);
|
|
Expects(controlLine.baseline < controlLine.bottom);
|
|
}
|
|
const auto formulaSelection = TextSelection(
|
|
0,
|
|
uint16(formulaData.text.size()));
|
|
const auto formulaUnselected = RenderTextOffscreen(
|
|
*formulaText,
|
|
selectionWidth);
|
|
const auto formulaSelected = RenderTextOffscreen(
|
|
*formulaText,
|
|
selectionWidth,
|
|
formulaSelection);
|
|
const auto formulaHitBounds = SymbolHitBounds(
|
|
*formulaText,
|
|
selectionWidth,
|
|
formulaPosition);
|
|
Expects(formulaHitBounds.has_value());
|
|
const auto controlSelection = TextSelection(
|
|
0,
|
|
uint16(controlLineSource.size()));
|
|
const auto controlUnselected = RenderTextOffscreen(
|
|
controlLineText,
|
|
selectionWidth);
|
|
const auto controlSelected = RenderTextOffscreen(
|
|
controlLineText,
|
|
selectionWidth,
|
|
controlSelection);
|
|
const auto controlHitBounds = SymbolRangeHitBounds(
|
|
controlLineText,
|
|
selectionWidth,
|
|
controlProbePosition,
|
|
controlProbeText.size());
|
|
Expects(controlHitBounds.has_value());
|
|
if (formulaHitBounds && controlHitBounds) {
|
|
const auto formulaChangedBounds = ChangedBoundsInRect(
|
|
formulaUnselected,
|
|
formulaSelected,
|
|
QRect(
|
|
formulaHitBounds->x(),
|
|
0,
|
|
formulaHitBounds->width(),
|
|
formulaSelected.height()));
|
|
const auto controlChangedBounds = ChangedBoundsInRect(
|
|
controlUnselected,
|
|
controlSelected,
|
|
QRect(
|
|
controlHitBounds->x(),
|
|
0,
|
|
controlHitBounds->width(),
|
|
controlSelected.height()));
|
|
Expects(formulaChangedBounds.has_value());
|
|
Expects(controlChangedBounds.has_value());
|
|
if (formulaChangedBounds && controlChangedBounds) {
|
|
Expects(
|
|
formulaChangedBounds->top()
|
|
<= controlChangedBounds->top());
|
|
Expects(
|
|
formulaChangedBounds->bottom()
|
|
>= controlChangedBounds->bottom());
|
|
Expects(
|
|
(formulaChangedBounds->top()
|
|
< controlChangedBounds->top())
|
|
|| (formulaChangedBounds->bottom()
|
|
> controlChangedBounds->bottom()));
|
|
}
|
|
}
|
|
|
|
auto leadingFormulaData = TextWithEntities();
|
|
leadingFormulaData.append(QChar::ObjectReplacementCharacter);
|
|
leadingFormulaData.entities.push_back(EntityInText(
|
|
EntityType::CustomEmoji,
|
|
0,
|
|
1,
|
|
formulaEntityData));
|
|
const auto leadingFormulaText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
leadingFormulaData,
|
|
kMarkupTextOptions,
|
|
scale(32),
|
|
context);
|
|
const auto leadingFormulaRender = RenderTextOffscreen(
|
|
leadingFormulaText,
|
|
std::max(formulaImage.width() / 2, 1));
|
|
Expects(HasPaintedPixels(leadingFormulaRender));
|
|
|
|
auto skipOnlyText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
u""_q,
|
|
kDefaultTextOptions,
|
|
scale(32));
|
|
const auto skipBlockWidth = scale(36);
|
|
const auto skipBlockHeight = scale(7);
|
|
Expects(skipOnlyText.updateSkipBlock(skipBlockWidth, skipBlockHeight));
|
|
Expects(skipOnlyText.countHeight(skipBlockWidth * 2) == skipBlockHeight);
|
|
Expects(
|
|
skipOnlyText.countDimensions(Ui::Text::SimpleGeometry(
|
|
skipBlockWidth * 2,
|
|
0,
|
|
0,
|
|
false)).height == skipBlockHeight);
|
|
|
|
const auto colorizedText = u"Colorized link span"_q;
|
|
const auto colorizedWidth = scale(280);
|
|
const auto plainColorizedText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
colorizedText,
|
|
kMarkupTextOptions,
|
|
scale(96));
|
|
const auto emptyColorizedText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
Ui::Text::Colorized(colorizedText),
|
|
kMarkupTextOptions,
|
|
scale(96));
|
|
const auto emptyColorizedRender = RenderTextOffscreen(
|
|
emptyColorizedText,
|
|
colorizedWidth);
|
|
const auto colorizedBoundsRect = QRect(
|
|
QPoint(),
|
|
emptyColorizedRender.size());
|
|
const auto linkPenRender = RenderTextOffscreen(
|
|
plainColorizedText,
|
|
colorizedWidth,
|
|
std::nullopt,
|
|
{},
|
|
st::defaultTextPalette.linkFg->p);
|
|
const auto plainColorizedRender = RenderTextOffscreen(
|
|
plainColorizedText,
|
|
colorizedWidth);
|
|
Expects(ImagesEqualInRect(
|
|
emptyColorizedRender,
|
|
linkPenRender,
|
|
colorizedBoundsRect));
|
|
Expects(!ImagesEqualInRect(
|
|
emptyColorizedRender,
|
|
plainColorizedRender,
|
|
colorizedBoundsRect));
|
|
|
|
const auto &specialFg = st::defaultTextPalette.linkFg;
|
|
const auto &specialBg = st::defaultTextPalette.markBg;
|
|
auto specialColors = std::vector<Ui::Text::SpecialColor>{
|
|
Ui::Text::SpecialColor{
|
|
&specialFg->p,
|
|
&specialFg->p,
|
|
&specialBg->b,
|
|
&specialBg->b },
|
|
};
|
|
const auto explicitColorizedNoBgText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
Ui::Text::Colorized(colorizedText, 1, 0),
|
|
kMarkupTextOptions,
|
|
scale(96));
|
|
const auto explicitColorizedBgText = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
Ui::Text::Colorized(colorizedText, 1, 1),
|
|
kMarkupTextOptions,
|
|
scale(96));
|
|
const auto explicitColorizedNoBgRender = RenderTextOffscreen(
|
|
explicitColorizedNoBgText,
|
|
colorizedWidth,
|
|
std::nullopt,
|
|
specialColors);
|
|
const auto explicitColorizedBgRender = RenderTextOffscreen(
|
|
explicitColorizedBgText,
|
|
colorizedWidth,
|
|
std::nullopt,
|
|
specialColors);
|
|
const auto specialPenRender = RenderTextOffscreen(
|
|
plainColorizedText,
|
|
colorizedWidth,
|
|
std::nullopt,
|
|
{},
|
|
specialFg->p);
|
|
const auto colorizedSymbolBounds = SymbolRangeHitBounds(
|
|
explicitColorizedBgText,
|
|
colorizedWidth,
|
|
0,
|
|
colorizedText.size());
|
|
Expects(ImagesEqualInRect(
|
|
explicitColorizedNoBgRender,
|
|
specialPenRender,
|
|
colorizedBoundsRect));
|
|
Expects(colorizedSymbolBounds.has_value());
|
|
if (colorizedSymbolBounds) {
|
|
Expects(!ImagesEqualInRect(
|
|
explicitColorizedBgRender,
|
|
explicitColorizedNoBgRender,
|
|
*colorizedSymbolBounds));
|
|
Expects(HasPixelColorInRect(
|
|
explicitColorizedBgRender,
|
|
*colorizedSymbolBounds,
|
|
specialBg->c.rgba()));
|
|
Expects(!HasPixelColorInRect(
|
|
explicitColorizedNoBgRender,
|
|
*colorizedSymbolBounds,
|
|
specialBg->c.rgba()));
|
|
}
|
|
|
|
body->paintRequest() | rpl::on_next([=](QRect clip) {
|
|
auto p = QPainter(body);
|
|
p.fillRect(clip, QColor(255, 255, 255));
|
|
const auto left = scale(24);
|
|
const auto top = scale(24);
|
|
const auto width = body->width() - (2 * left);
|
|
formulaText->draw(p, {
|
|
.position = QPoint(left, top),
|
|
.availableWidth = width,
|
|
});
|
|
controlText->draw(p, {
|
|
.position = QPoint(
|
|
left,
|
|
top + formulaText->countHeight(width) + scale(20)),
|
|
.availableWidth = width,
|
|
});
|
|
}, body->lifetime());
|
|
}
|
|
|
|
} // namespace Test
|