Improve custom fonts support in the InputField.

This commit is contained in:
John Preston 2024-05-08 17:51:13 +04:00
parent f76ddd841c
commit 41d1fe9232
2 changed files with 95 additions and 22 deletions

View file

@ -698,16 +698,16 @@ QString AccumulateText(Iterator begin, Iterator end) {
return result;
}
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const QFont &font) {
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, int lineHeight) {
const auto factor = style::DevicePixelRatio();
const auto size = Emoji::GetSizeNormal();
const auto width = size + st::emojiPadding * factor * 2;
const auto height = std::max(QFontMetrics(font).height() * factor, size);
const auto height = std::max(lineHeight * factor, size);
auto result = QTextImageFormat();
result.setWidth(width / factor);
result.setHeight(height / factor);
result.setName(emoji->toUrl());
result.setVerticalAlignment(QTextCharFormat::AlignBottom);
result.setVerticalAlignment(QTextCharFormat::AlignTop);
return result;
}
@ -747,6 +747,7 @@ QTextCharFormat PrepareTagFormat(
result.setProperty(
kCustomEmojiId,
CustomEmojiIdFromLink(replaceWith));
result.setVerticalAlignment(QTextCharFormat::AlignTop);
} else if (IsValidMarkdownLink(tag)) {
color = st::defaultTextPalette.linkFg;
} else if (tag == kTagBold) {
@ -919,6 +920,7 @@ struct FormattingAction {
RemoveTag,
RemoveNewline,
ClearInstantReplace,
FixLineHeight,
};
Type type = Type::Invalid;
@ -1018,7 +1020,12 @@ private:
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji) {
const auto currentFormat = cursor.charFormat();
auto format = PrepareEmojiFormat(emoji, currentFormat.font());
const auto blockFormat = cursor.blockFormat();
const auto type = blockFormat.lineHeightType();
const auto height = (type == QTextBlockFormat::FixedHeight)
? blockFormat.lineHeight()
: QFontMetrics(cursor.charFormat().font()).height();
auto format = PrepareEmojiFormat(emoji, height);
ApplyTagFormat(format, currentFormat);
cursor.insertText(kObjectReplacement, format);
}
@ -1035,7 +1042,7 @@ void InsertCustomEmojiAtCursor(
format.setProperty(kCustomEmojiText, text);
format.setProperty(kCustomEmojiLink, unique);
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
format.setVerticalAlignment(QTextCharFormat::AlignBottom);
format.setVerticalAlignment(QTextCharFormat::AlignTop);
format.setFont(field->st().font);
format.setForeground(field->st().textFg);
format.setBackground(QBrush());
@ -1097,8 +1104,12 @@ const InstantReplaces &InstantReplaces::TextOnly() {
return result;
}
CustomEmojiObject::CustomEmojiObject(Factory factory, Fn<bool()> paused)
: _factory(std::move(factory))
CustomEmojiObject::CustomEmojiObject(
const style::font &font,
Factory factory,
Fn<bool()> paused)
: _font(font)
, _factory(std::move(factory))
, _paused(std::move(paused))
, _now(crl::now()) {
}
@ -1118,8 +1129,7 @@ QSizeF CustomEmojiObject::intrinsicSize(
const QTextFormat &format) {
const auto size = st::emojiSize * 1.;
const auto width = size + st::emojiPadding * 2.;
const auto font = format.toCharFormat().font();
const auto height = std::min(QFontMetrics(font).height() * 1., size);
const auto height = std::max(_font->height * 1., size);
if (!_skip) {
const auto emoji = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
_skip = (st::emojiSize - emoji) / 2;
@ -1211,6 +1221,30 @@ InputField::InputField(
_inner->setAcceptRichText(false);
resize(_st.width, _minHeight);
{ // In case of default fonts all those should be zero.
const auto metrics = QFontMetricsF(_st.font->f);
const auto realAscent = int(base::SafeRound(metrics.ascent()));
const auto ascentAdd = _st.font->ascent - realAscent;
//const auto realHeight = int(base::SafeRound(metrics.height()));
//const auto heightAdd = _st.font->height - realHeight - ascentAdd;
//_customFontMargins = QMargins(0, ascentAdd, 0, heightAdd);
_customFontMargins = QMargins(0, ascentAdd, 0, -ascentAdd);
// We move _inner down by ascentAdd for the first line to look
// at the same vertical position as in the default font.
//
// But we don't want to get vertical scroll in case the field
// fits pixel-perfect with the default font, so we allow the
// bottom margin to be the same shift, but negative.
if (_mode != Mode::SingleLine) {
const auto metrics = QFontMetricsF(_st.font->f);
const auto leading = qMax(metrics.leading(), qreal(0.0));
const auto adjustment = (metrics.ascent() + leading)
- ((_st.font->height * 4) / 5);
_placeholderCustomFontSkip = int(base::SafeRound(-adjustment));
}
}
if (_st.textBg->c.alphaF() >= 1.
&& !_st.borderRadius) {
setAttribute(Qt::WA_OpaquePaintEvent);
@ -1231,11 +1265,21 @@ InputField::InputField(
) | rpl::start_with_next([=] {
updatePalette();
}, lifetime());
{
auto cursor = _inner->textCursor();
_defaultCharFormat = _inner->textCursor().charFormat();
updatePalette();
_inner->textCursor().setCharFormat(_defaultCharFormat);
_defaultCharFormat = cursor.charFormat();
updatePalette();
cursor.setCharFormat(_defaultCharFormat);
_defaultBlockFormat = cursor.blockFormat();
_defaultBlockFormat.setLineHeight(
_st.font->height,
QTextBlockFormat::FixedHeight);
cursor.setBlockFormat(_defaultBlockFormat);
_inner->setTextCursor(cursor);
}
_inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@ -1277,6 +1321,7 @@ InputField::InputField(
auto cursor = textCursor();
if (!cursor.hasSelection() && !cursor.position()) {
cursor.setCharFormat(_defaultCharFormat);
cursor.setBlockFormat(_defaultBlockFormat);
setTextCursor(cursor);
}
}, lifetime());
@ -1435,7 +1480,7 @@ void InputField::setTagMimeProcessor(Fn<QString(QStringView)> processor) {
void InputField::setCustomEmojiFactory(
CustomEmojiFactory factory,
Fn<bool()> paused) {
_customEmojiObject = std::make_unique<CustomEmojiObject>([=](
_customEmojiObject = std::make_unique<CustomEmojiObject>(_st.font, [=](
QStringView data) {
return factory(data, [=] { customEmojiRepaint(); });
}, std::move(paused));
@ -1740,6 +1785,13 @@ void InputField::paintEvent(QPaintEvent *e) {
const auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
paintSurrounding(p, r, errorDegree, focusedDegree);
const auto skip = int(base::SafeRound(_inner->document()->documentMargin()));
const auto margins = _st.textMargins
+ _st.placeholderMargins
+ QMargins(skip, skip + _placeholderCustomFontSkip, skip, 0)
+ _additionalMargins
+ _customFontMargins;
if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
p.save();
@ -1747,7 +1799,7 @@ void InputField::paintEvent(QPaintEvent *e) {
auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins));
QRect r(rect().marginsRemoved(margins));
r.moveTop(r.top() + placeholderTop);
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
@ -1774,15 +1826,14 @@ void InputField::paintEvent(QPaintEvent *e) {
p.setFont(_st.placeholderFont);
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
if (_st.placeholderAlign == style::al_topleft && _placeholderAfterSymbols > 0) {
const auto skipWidth = placeholderSkipWidth();
p.drawText(
_st.textMargins.left() + _st.placeholderMargins.left() + skipWidth,
_st.textMargins.top() + _st.placeholderMargins.top() + _st.placeholderFont->ascent,
margins.left() + skipWidth,
margins.top() + _st.placeholderFont->ascent,
_placeholder);
} else {
auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins);
auto r = rect().marginsRemoved(margins);
r.moveLeft(r.left() + placeholderLeft);
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
p.drawText(r, _placeholder, _st.placeholderAlign);
@ -2117,6 +2168,10 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
if (tillBlock.isValid()) tillBlock = tillBlock.next();
for (auto block = fromBlock; block != tillBlock; block = block.next()) {
if (block.blockFormat().lineHeightType() != QTextBlockFormat::FixedHeight) {
action.type = ActionType::FixLineHeight;
break;
}
for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) {
auto fragment = fragmentIt.fragment();
Assert(fragment.isValid());
@ -2298,6 +2353,11 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
PrepareFormattingOptimization(document);
auto cursor = QTextCursor(document);
if (action.type == ActionType::FixLineHeight) {
cursor.select(QTextCursor::Document);
cursor.setBlockFormat(_defaultBlockFormat);
continue;
}
cursor.setPosition(action.intervalStart);
cursor.setPosition(action.intervalEnd, QTextCursor::KeepAnchor);
if (action.type == ActionType::InsertEmoji
@ -2627,6 +2687,7 @@ void InputField::setTextWithTags(
_realCharsAdded = textWithTags.text.size();
const auto document = _inner->document();
auto cursor = QTextCursor(document);
cursor.setBlockFormat(_defaultBlockFormat);
if (historyAction == HistoryAction::Clear) {
document->setUndoRedoEnabled(false);
cursor.beginEditBlock();
@ -3303,7 +3364,7 @@ void InputField::commitInstantReplacement(
result.setProperty(kCustomEmojiText, with);
result.setProperty(kCustomEmojiLink, unique);
result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
result.setVerticalAlignment(QTextCharFormat::AlignBottom);
result.setVerticalAlignment(QTextCharFormat::AlignTop);
return result;
}
const auto use = Integration::Instance().defaultEmojiVariant(
@ -3567,6 +3628,7 @@ void InputField::commitMarkdownLinkEdit(
_reverseMarkdownReplacement = false;
cursor.setCharFormat(_defaultCharFormat);
cursor.setBlockFormat(_defaultBlockFormat);
_inner->setTextCursor(cursor);
}
@ -3897,14 +3959,18 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
void InputField::resizeEvent(QResizeEvent *e) {
refreshPlaceholder(_placeholderFull.current());
_inner->setGeometry(rect().marginsRemoved(
_st.textMargins + _additionalMargins));
_st.textMargins + _additionalMargins + _customFontMargins));
_borderAnimationStart = width() / 2;
RpWidget::resizeEvent(e);
checkContentHeight();
}
void InputField::refreshPlaceholder(const QString &text) {
const auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right();
const auto margins = _st.textMargins
+ _st.placeholderMargins
+ _additionalMargins
+ _customFontMargins;
const auto availableWidth = rect().marginsRemoved(margins).width();
if (_st.placeholderScale > 0.) {
auto placeholderFont = _st.placeholderFont->f;
placeholderFont.setStyleStrategy(QFont::PreferMatch);

View file

@ -77,7 +77,10 @@ class CustomEmojiObject : public QObject, public QTextObjectInterface {
public:
using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>;
CustomEmojiObject(Factory factory, Fn<bool()> paused);
CustomEmojiObject(
const style::font &font,
Factory factory,
Fn<bool()> paused);
~CustomEmojiObject();
void *qt_metacast(const char *iid) override;
@ -97,6 +100,7 @@ public:
void clear();
private:
const style::font _font;
Factory _factory;
Fn<bool()> _paused;
base::flat_map<uint64, std::unique_ptr<Text::CustomEmoji>> _emoji;
@ -495,6 +499,8 @@ private:
std::optional<QString> _inputMethodCommit;
QMargins _additionalMargins;
QMargins _customFontMargins;
int _placeholderCustomFontSkip = 0;
bool _forcePlaceholderHidden = false;
bool _reverseMarkdownReplacement = false;
@ -557,6 +563,7 @@ private:
base::unique_qptr<PopupMenu> _contextMenu;
QTextCharFormat _defaultCharFormat;
QTextBlockFormat _defaultBlockFormat;
rpl::variable<int> _scrollTop;