// 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) { }