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

View file

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