From 51ac1ac709042cb61f122860de5f47c8f82e8e63 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 17 Apr 2022 14:58:46 +0300 Subject: [PATCH] first attempt --- ui/utils/CMakeLists.txt | 2 + ui/utils/textmeter.cpp | 233 +++++++++++++++++++++ ui/utils/textmeter.h | 68 ++++++ ui/widgets/messageline/messagedelegate.cpp | 11 +- ui/widgets/messageline/messagedelegate.h | 2 + 5 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 ui/utils/textmeter.cpp create mode 100644 ui/utils/textmeter.h diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index b46d30d..823287d 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -15,4 +15,6 @@ target_sources(squawk PRIVATE resizer.h shadowoverlay.cpp shadowoverlay.h + textmeter.cpp + textmeter.h ) diff --git a/ui/utils/textmeter.cpp b/ui/utils/textmeter.cpp new file mode 100644 index 0000000..51c6d54 --- /dev/null +++ b/ui/utils/textmeter.cpp @@ -0,0 +1,233 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "textmeter.h" +#include +#include + +TextMeter::TextMeter(): + base(), + fonts() +{ +} + +TextMeter::~TextMeter() +{ +} + +void TextMeter::initializeFonts(const QFont& font) +{ + fonts.clear(); + QList supported = base.writingSystems(font.family()); + std::set sup; + std::set added({font.family()}); + for (const QFontDatabase::WritingSystem& system : supported) { + sup.insert(system); + } + fonts.push_back(QFontMetrics(font)); + QString style = base.styleString(font); + + QList systems = base.writingSystems(); + for (const QFontDatabase::WritingSystem& system : systems) { + if (sup.count(system) == 0) { + QStringList families = base.families(system); + if (!families.empty() && added.count(families.first()) == 0) { + QString family(families.first()); + QFont nfont = base.font(family, style, font.pointSize()); + if (added.count(nfont.family()) == 0) { + added.insert(family); + fonts.push_back(QFontMetrics(nfont)); + qDebug() << "Added font" << nfont.family() << "for" << system; + } + } + } + } +} + +QSize TextMeter::boundingSize(const QString& text, const QSize& limits) const +{ +// QString str("ridiculus mus. Suspendisse potenti. Cras pretium venenatis enim, faucibus accumsan ex"); +// bool first = true; +// int width = 0; +// QStringList list = str.split(" "); +// QFontMetrics m = fonts.front(); +// for (const QString& word : list) { +// if (first) { +// first = false; +// } else { +// width += m.horizontalAdvance(QChar::Space); +// } +// width += m.horizontalAdvance(word); +// for (const QChar& ch : word) { +// width += m.horizontalAdvance(ch); +// } +// } +// qDebug() << "together:" << m.horizontalAdvance(str); +// qDebug() << "apart:" << width; +// I cant measure or wrap text this way, this simple example shows that even this gives differen result +// The Qt implementation under it is thousands and thousands lines of code in QTextEngine +// I simply can't get though it + + if (text.size() == 0) { + return QSize (0, 0); + } + Helper current(limits.width()); + for (const QChar& ch : text) { + if (newLine(ch)) { + current.computeNewWord(); + if (current.height == 0) { + current.height = fonts.front().lineSpacing(); + } + current.beginNewLine(); + } else if (visible(ch)) { + bool found = false; + for (const QFontMetrics& metrics : fonts) { + if (metrics.inFont(ch)) { + current.computeChar(ch, metrics); + found = true; + break; + } + } + + if (!found) { + current.computeChar(ch, fonts.front()); + } + } + } + current.computeNewWord(); + current.beginNewLine(); + + int& height = current.size.rheight(); + if (height > 0) { + height -= fonts.front().leading(); + } + + return current.size; +} + +void TextMeter::Helper::computeChar(const QChar& ch, const QFontMetrics& metrics) +{ + int ha = metrics.horizontalAdvance(ch); + if (newWord(ch)) { + if (printOnLineBreak(ch)) { + if (!lineOverflow(metrics, ha, ch)){ + computeNewWord(); + } + } else { + computeNewWord(); + delayedSpaceWidth = ha; + lastSpace = ch; + } + } else { + lineOverflow(metrics, ha, ch); + } +} + +void TextMeter::Helper::computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch) +{ + if (wordBeganWithTheLine) { + text = word.chopped(1); + width = wordWidth - horizontalAdvance; + height = wordHeight; + } + if (width != metrics.horizontalAdvance(text)) { + qDebug() << "Kerning Error" << width - metrics.horizontalAdvance(text); + } + beginNewLine(); + if (wordBeganWithTheLine) { + word = ch; + wordWidth = horizontalAdvance; + wordHeight = metrics.lineSpacing(); + } + + wordBeganWithTheLine = true; + delayedSpaceWidth = 0; + lastSpace = QChar::Null; +} + +void TextMeter::Helper::beginNewLine() +{ + size.rheight() += height; + size.rwidth() = qMax(size.width(), width); + qDebug() << text; + text = ""; + width = 0; + height = 0; +} + +void TextMeter::Helper::computeNewWord() +{ + width += wordWidth + delayedSpaceWidth; + height = qMax(height, wordHeight); + if (lastSpace != QChar::Null) { + text += lastSpace; + } + text += word; + word = ""; + wordWidth = 0; + wordHeight = 0; + delayedSpaceWidth = 0; + lastSpace = QChar::Null; + wordBeganWithTheLine = false; +} + +bool TextMeter::Helper::lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch) +{ + wordHeight = qMax(wordHeight, metrics.lineSpacing()); + wordWidth += horizontalAdvance; + word += ch; + if (width + delayedSpaceWidth + wordWidth > maxWidth) { + computeNewLine(metrics, horizontalAdvance, ch); + return true; + } + return false; +} + + +bool TextMeter::newLine(const QChar& ch) +{ + return ch == QChar::LineFeed; +} + +bool TextMeter::newWord(const QChar& ch) +{ + return ch.isSpace() || ch.isPunct(); +} + +bool TextMeter::printOnLineBreak(const QChar& ch) +{ + return ch != QChar::Space; +} + +bool TextMeter::visible(const QChar& ch) +{ + return true; +} + +TextMeter::Helper::Helper(int p_maxWidth): + width(0), + height(0), + wordWidth(0), + wordHeight(0), + delayedSpaceWidth(0), + maxWidth(p_maxWidth), + wordBeganWithTheLine(true), + text(""), + word(""), + lastSpace(QChar::Null), + size(0, 0) +{ +} diff --git a/ui/utils/textmeter.h b/ui/utils/textmeter.h new file mode 100644 index 0000000..243d989 --- /dev/null +++ b/ui/utils/textmeter.h @@ -0,0 +1,68 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef TEXTMETER_H +#define TEXTMETER_H + +#include +#include + +#include +#include +#include +#include + +class TextMeter +{ +public: + TextMeter(); + ~TextMeter(); + void initializeFonts(const QFont& font); + QSize boundingSize(const QString& text, const QSize& limits) const; + +private: + QFontDatabase base; + std::list fonts; + + struct Helper { + Helper(int maxWidth); + int width; + int height; + int wordWidth; + int wordHeight; + int delayedSpaceWidth; + int maxWidth; + bool wordBeganWithTheLine; + QString text; + QString word; + QChar lastSpace; + QSize size; + + void computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch); + void computeChar(const QChar& ch, const QFontMetrics& metrics); + void computeNewWord(); + void beginNewLine(); + bool lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch); + }; + + static bool newLine(const QChar& ch); + static bool newWord(const QChar& ch); + static bool visible(const QChar& ch); + static bool printOnLineBreak(const QChar& ch); + +}; + +#endif // TEXTMETER_H diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 15a5e46..4ddecee 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -42,6 +42,7 @@ MessageDelegate::MessageDelegate(QObject* parent): nickFont(), dateFont(), bodyMetrics(bodyFont), + bodyMeter(), nickMetrics(nickFont), dateMetrics(dateFont), buttonHeight(0), @@ -123,10 +124,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; } - QSize bodySize(0, 0); - if (data.text.size() > 0) { - bodySize = bodyMetrics.boundingRect(opt.rect, Qt::TextWordWrap, data.text).size(); - } + QSize bodySize = bodyMeter.boundingSize(data.text, opt.rect.size()); QRect rect; if (ntds) { @@ -306,7 +304,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel Models::FeedItem data = qvariant_cast(vi); QSize messageSize(0, 0); if (data.text.size() > 0) { - messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); + messageSize = bodyMeter.boundingSize(data.text, messageRect.size()); messageSize.rheight() += textMargin; } @@ -390,9 +388,12 @@ void MessageDelegate::initializeFonts(const QFont& font) dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier); } + bodyFont.setKerning(false); bodyMetrics = QFontMetrics(bodyFont); nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); + + bodyMeter.initializeFonts(bodyFont); Preview::initializeFont(bodyFont); } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 9225412..38ec0ee 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -34,6 +34,7 @@ #include "shared/global.h" #include "shared/utils.h" #include "shared/pathcheck.h" +#include "ui/utils/textmeter.h" #include "preview.h" @@ -94,6 +95,7 @@ private: QFont nickFont; QFont dateFont; QFontMetrics bodyMetrics; + TextMeter bodyMeter; QFontMetrics nickMetrics; QFontMetrics dateMetrics;