diff options
Diffstat (limited to 'src/gui/text/qtextdocument.cpp')
-rw-r--r-- | src/gui/text/qtextdocument.cpp | 844 |
1 files changed, 489 insertions, 355 deletions
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index 0fed8c98d3..15a313e13d 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qtextdocument.h" #include <qtextformat.h> @@ -47,6 +11,7 @@ #include "qtexttable.h" #include "qtextlist.h" #include <qdebug.h> +#include <qloggingcategory.h> #if QT_CONFIG(regularexpression) #include <qregularexpression.h> #endif @@ -78,6 +43,10 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcLayout); + +using namespace Qt::StringLiterals; + Q_CORE_EXPORT Q_DECL_CONST_FUNCTION unsigned int qt_int_sqrt(unsigned int n); namespace { @@ -85,6 +54,8 @@ namespace { }; /*! + \fn bool Qt::mightBeRichText(QAnyStringView text) + Returns \c true if the string \a text is likely to be rich text; otherwise returns \c false. @@ -94,57 +65,61 @@ namespace { for common cases, there is no guarantee. This function is defined in the \c <QTextDocument> header file. -*/ -bool Qt::mightBeRichText(const QString& text) + + \note In Qt versions prior to 6.7, this function took QString only. + */ +template <typename T> +static bool mightBeRichTextImpl(T text) { if (text.isEmpty()) return false; - int start = 0; + qsizetype start = 0; - while (start < text.length() && text.at(start).isSpace()) + while (start < text.size() && QChar(text.at(start)).isSpace()) ++start; // skip a leading <?xml ... ?> as for example with xhtml - if (QStringView{text}.mid(start, 5).compare(QLatin1String("<?xml")) == 0) { - while (start < text.length()) { - if (text.at(start) == QLatin1Char('?') - && start + 2 < text.length() - && text.at(start + 1) == QLatin1Char('>')) { + if (text.mid(start, 5).compare("<?xml"_L1) == 0) { + while (start < text.size()) { + if (text.at(start) == u'?' + && start + 2 < text.size() + && text.at(start + 1) == u'>') { start += 2; break; } ++start; } - while (start < text.length() && text.at(start).isSpace()) + while (start < text.size() && QChar(text.at(start)).isSpace()) ++start; } - if (QStringView{text}.mid(start, 5).compare(QLatin1String("<!doc"), Qt::CaseInsensitive) == 0) + if (text.mid(start, 5).compare("<!doc"_L1, Qt::CaseInsensitive) == 0) return true; - int open = start; - while (open < text.length() && text.at(open) != QLatin1Char('<') - && text.at(open) != QLatin1Char('\n')) { - if (text.at(open) == QLatin1Char('&') && QStringView{text}.mid(open + 1, 3) == QLatin1String("lt;")) + qsizetype open = start; + while (open < text.size() && text.at(open) != u'<' + && text.at(open) != u'\n') { + if (text.at(open) == u'&' && text.mid(open + 1, 3) == "lt;"_L1) return true; // support desperate attempt of user to see <...> ++open; } - if (open < text.length() && text.at(open) == QLatin1Char('<')) { - const int close = text.indexOf(QLatin1Char('>'), open); + if (open < text.size() && text.at(open) == u'<') { + const qsizetype close = text.indexOf(u'>', open); if (close > -1) { - QString tag; - for (int i = open+1; i < close; ++i) { - if (text[i].isDigit() || text[i].isLetter()) - tag += text[i]; - else if (!tag.isEmpty() && text[i].isSpace()) + QVarLengthArray<char16_t> tag; + for (qsizetype i = open + 1; i < close; ++i) { + const auto current = QChar(text[i]); + if (current.isDigit() || current.isLetter()) + tag.append(current.toLower().unicode()); + else if (!tag.isEmpty() && current.isSpace()) break; - else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close) + else if (!tag.isEmpty() && current == u'/' && i + 1 == close) break; - else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!'))) + else if (!current.isSpace() && (!tag.isEmpty() || current != u'!')) return false; // that's not a tag } #ifndef QT_NO_TEXTHTMLPARSER - return QTextHtmlParser::lookupElement(std::move(tag).toLower()) != -1; + return QTextHtmlParser::lookupElement(tag) != -1; #else return false; #endif // QT_NO_TEXTHTMLPARSER @@ -153,6 +128,16 @@ bool Qt::mightBeRichText(const QString& text) return false; } +static bool mightBeRichTextImpl(QUtf8StringView text) +{ + return mightBeRichTextImpl(QLatin1StringView(QByteArrayView(text))); +} + +bool Qt::mightBeRichText(QAnyStringView text) +{ + return text.visit([](auto text) { return mightBeRichTextImpl(text); }); +} + /*! Converts the plain text string \a plain to an HTML-formatted paragraph while preserving most of its look. @@ -165,27 +150,27 @@ bool Qt::mightBeRichText(const QString& text) */ QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) { - int col = 0; + qsizetype col = 0; QString rich; - rich += QLatin1String("<p>"); - for (int i = 0; i < plain.length(); ++i) { - if (plain[i] == QLatin1Char('\n')){ - int c = 1; - while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) { + rich += "<p>"_L1; + for (qsizetype i = 0; i < plain.size(); ++i) { + if (plain[i] == u'\n'){ + qsizetype c = 1; + while (i+1 < plain.size() && plain[i+1] == u'\n') { i++; c++; } if (c == 1) - rich += QLatin1String("<br>\n"); + rich += "<br>\n"_L1; else { - rich += QLatin1String("</p>\n"); + rich += "</p>\n"_L1; while (--c > 1) - rich += QLatin1String("<br>\n"); - rich += QLatin1String("<p>"); + rich += "<br>\n"_L1; + rich += "<p>"_L1; } col = 0; } else { - if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){ + if (mode == Qt::WhiteSpacePre && plain[i] == u'\t'){ rich += QChar::Nbsp; ++col; while (col % 8) { @@ -195,19 +180,19 @@ QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) } else if (mode == Qt::WhiteSpacePre && plain[i].isSpace()) rich += QChar::Nbsp; - else if (plain[i] == QLatin1Char('<')) - rich += QLatin1String("<"); - else if (plain[i] == QLatin1Char('>')) - rich += QLatin1String(">"); - else if (plain[i] == QLatin1Char('&')) - rich += QLatin1String("&"); + else if (plain[i] == u'<') + rich += "<"_L1; + else if (plain[i] == u'>') + rich += ">"_L1; + else if (plain[i] == u'&') + rich += "&"_L1; else rich += plain[i]; ++col; } } if (col != 0) - rich += QLatin1String("</p>"); + rich += "</p>"_L1; return rich; } @@ -269,7 +254,7 @@ QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) \li Text block group format changes. \endlist - \sa QTextCursor, QTextEdit, {Rich Text Processing}, {Text Object Example} + \sa QTextCursor, QTextEdit, {Rich Text Processing} */ /*! @@ -669,6 +654,40 @@ bool QTextDocument::useDesignMetrics() const } /*! + \property QTextDocument::layoutEnabled + \since 6.4 + \brief whether QTextDocument should recalculate the layout after every change + + If this property is set to true, any change to the document triggers a layout, + which makes everything work as expected but takes time. + + Temporarily disabling the layout can save time when making multiple changes + (not just text content, but also default font, default text option....) + so that the document is only laid out once at the end. This can be useful when + the text width or page size isn't yet known, for instance. + + By default, this property is \c true. + + \sa setTextWidth +*/ + +void QTextDocument::setLayoutEnabled(bool b) +{ + Q_D(QTextDocument); + if (d->layoutEnabled == b) + return; + d->layoutEnabled = b; + if (b && d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +bool QTextDocument::isLayoutEnabled() const +{ + Q_D(const QTextDocument); + return d->layoutEnabled; +} + +/*! \since 4.2 Draws the content of the document with painter \a p, clipped to \a rect. @@ -711,6 +730,8 @@ void QTextDocument::setTextWidth(qreal width) { Q_D(QTextDocument); QSizeF sz = d->pageSize; + + qCDebug(lcLayout) << "page size" << sz << "-> width" << width; sz.setWidth(width); sz.setHeight(-1); setPageSize(sz); @@ -817,7 +838,7 @@ void QTextDocument::adjustSize() // Pull this private function in from qglobal.cpp QFont f = defaultFont(); QFontMetrics fm(f); - int mw = fm.horizontalAdvance(QLatin1Char('x')) * 80; + int mw = fm.horizontalAdvance(u'x') * 80; int w = mw; setTextWidth(w); QSizeF size = documentLayout()->documentSize(); @@ -1138,6 +1159,8 @@ QString QTextDocument::metaInformation(MetaInformation info) const return d->url; case CssMedia: return d->cssMedia; + case FrontMatter: + return d->frontMatter; } return QString(); } @@ -1161,6 +1184,9 @@ void QTextDocument::setMetaInformation(MetaInformation info, const QString &stri case CssMedia: d->cssMedia = string; break; + case FrontMatter: + d->frontMatter = string; + break; } } @@ -1200,19 +1226,27 @@ QString QTextDocument::toPlainText() const Q_D(const QTextDocument); QString txt = d->plainText(); + constexpr char16_t delims[] = { 0xfdd0, 0xfdd1, + QChar::ParagraphSeparator, QChar::LineSeparator, QChar::Nbsp }; + + const size_t pos = std::u16string_view(txt).find_first_of( + std::u16string_view(delims, std::size(delims))); + if (pos == std::u16string_view::npos) + return txt; + QChar *uc = txt.data(); - QChar *e = uc + txt.size(); + QChar *const e = uc + txt.size(); - for (; uc != e; ++uc) { + for (uc += pos; uc != e; ++uc) { switch (uc->unicode()) { case 0xfdd0: // QTextBeginningOfFrame case 0xfdd1: // QTextEndOfFrame case QChar::ParagraphSeparator: case QChar::LineSeparator: - *uc = QLatin1Char('\n'); + *uc = u'\n'; break; case QChar::Nbsp: - *uc = QLatin1Char(' '); + *uc = u' '; break; default: ; @@ -1267,6 +1301,8 @@ void QTextDocument::setHtml(const QString &html) d->enableUndoRedo(false); d->beginEditBlock(); d->clear(); + // ctor calls parse() to build up QTextHtmlParser::nodes list + // then import() populates the QTextDocument from those QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import(); d->endEditBlock(); d->enableUndoRedo(previousState); @@ -1298,6 +1334,10 @@ void QTextDocument::setHtml(const QString &html) \value CssMedia This value is used to select the corresponding '@media' rule, if any, from a specified CSS stylesheet when setHtml() is called. This enum value has been introduced in Qt 6.3. + \value FrontMatter This value is used to select header material, if any was + extracted during parsing of the source file (currently + only from Markdown format). This enum value has been + introduced in Qt 6.8. \sa metaInformation(), setMetaInformation(), setHtml() */ @@ -1306,11 +1346,11 @@ static bool findInBlock(const QTextBlock &block, const QString &expression, int QTextDocument::FindFlags options, QTextCursor *cursor) { QString text = block.text(); - text.replace(QChar::Nbsp, QLatin1Char(' ')); + text.replace(QChar::Nbsp, u' '); Qt::CaseSensitivity sensitivity = options & QTextDocument::FindCaseSensitively ? Qt::CaseSensitive : Qt::CaseInsensitive; int idx = -1; - while (offset >= 0 && offset <= text.length()) { + while (offset >= 0 && offset <= text.size()) { idx = (options & QTextDocument::FindBackward) ? text.lastIndexOf(expression, offset, sensitivity) : text.indexOf(expression, offset, sensitivity); if (idx == -1) @@ -1318,9 +1358,9 @@ static bool findInBlock(const QTextBlock &block, const QString &expression, int if (options & QTextDocument::FindWholeWords) { const int start = idx; - const int end = start + expression.length(); + const int end = start + expression.size(); if ((start != 0 && text.at(start - 1).isLetterOrNumber()) - || (end != text.length() && text.at(end).isLetterOrNumber())) { + || (end != text.size() && text.at(end).isLetterOrNumber())) { //if this is not a whole word, continue the search in the string offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1; idx = -1; @@ -1330,7 +1370,7 @@ static bool findInBlock(const QTextBlock &block, const QString &expression, int //we have a hit, return the cursor for that. *cursor = QTextCursorPrivate::fromPosition(const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(block)), block.position() + idx); - cursor->setPosition(cursor->position() + expression.length(), QTextCursor::KeepAnchor); + cursor->setPosition(cursor->position() + expression.size(), QTextCursor::KeepAnchor); return true; } return false; @@ -1426,11 +1466,11 @@ static bool findInBlock(const QTextBlock &block, const QRegularExpression &expr, QTextDocument::FindFlags options, QTextCursor *cursor) { QString text = block.text(); - text.replace(QChar::Nbsp, QLatin1Char(' ')); + text.replace(QChar::Nbsp, u' '); QRegularExpressionMatch match; int idx = -1; - while (offset >= 0 && offset <= text.length()) { + while (offset >= 0 && offset <= text.size()) { idx = (options & QTextDocument::FindBackward) ? text.lastIndexOf(expr, offset, &match) : text.indexOf(expr, offset, &match); if (idx == -1) @@ -1440,7 +1480,7 @@ static bool findInBlock(const QTextBlock &block, const QRegularExpression &expr, const int start = idx; const int end = start + match.capturedLength(); if ((start != 0 && text.at(start - 1).isLetterOrNumber()) - || (end != text.length() && text.at(end).isLetterOrNumber())) { + || (end != text.size() && text.at(end).isLetterOrNumber())) { //if this is not a whole word, continue the search in the string offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1; idx = -1; @@ -1471,6 +1511,10 @@ static bool findInBlock(const QTextBlock &block, const QRegularExpression &expr, If the \a from position is 0 (the default) the search begins from the beginning of the document; otherwise it begins at the specified position. + + \warning For historical reasons, the case sensitivity option set on + \a expr is ignored. Instead, the \a options are used to determine + if the search is case sensitive or not. */ QTextCursor QTextDocument::find(const QRegularExpression &expr, int from, FindFlags options) const { @@ -1826,9 +1870,10 @@ void QTextDocument::setBaselineOffset(qreal baseline) \fn qreal QTextDocument::baselineOffset() const \since 6.0 - Returns the the baseline offset in % used in the document layout. + Returns the baseline offset in % used in the document layout. - \sa setBaselineOffset(), setSubScriptBaseline(), subScriptBaseline(), setSuperScriptBaseline(), superScriptBaseline() + \sa setBaselineOffset(), setSubScriptBaseline(), subScriptBaseline(), setSuperScriptBaseline(), + superScriptBaseline() */ qreal QTextDocument::baselineOffset() const { @@ -2235,12 +2280,13 @@ QVariant QTextDocument::loadResource(int type, const QUrl &name) int index = me->indexOfMethod("loadResource(int,QUrl)"); if (index >= 0) { QMetaMethod loader = me->method(index); - loader.invoke(p, Q_RETURN_ARG(QVariant, r), Q_ARG(int, type), Q_ARG(QUrl, name)); + // don't invoke() via a queued connection: this function needs to return a value + loader.invoke(p, Qt::DirectConnection, Q_RETURN_ARG(QVariant, r), Q_ARG(int, type), Q_ARG(QUrl, name)); } } // handle data: URLs - if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) { + if (r.isNull() && name.scheme().compare("data"_L1, Qt::CaseInsensitive) == 0) { QString mimetype; QByteArray payload; if (qDecodeDataUrl(name, mimetype, payload)) @@ -2256,7 +2302,7 @@ QVariant QTextDocument::loadResource(int type, const QUrl &name) // For the second case QUrl can merge "#someanchor" with "foo.html" // correctly to "foo.html#someanchor" if (!(currentURL.isRelative() - || (currentURL.scheme() == QLatin1String("file") + || (currentURL.scheme() == "file"_L1 && !QFileInfo(currentURL.toLocalFile()).isAbsolute())) || (name.hasFragment() && name.path().isEmpty())) { resourceUrl = currentURL.resolved(name); @@ -2269,7 +2315,7 @@ QVariant QTextDocument::loadResource(int type, const QUrl &name) resourceUrl = QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name); } else if (currentURL.isEmpty()) { - resourceUrl.setScheme(QLatin1String("file")); + resourceUrl.setScheme("file"_L1); } } } @@ -2323,16 +2369,16 @@ static QString colorValue(QColor color) result = color.name(); } else if (color.alpha()) { QString alphaValue = QString::number(color.alphaF(), 'f', 6); - while (alphaValue.length() > 1 && alphaValue.at(alphaValue.size() - 1) == QLatin1Char('0')) + while (alphaValue.size() > 1 && alphaValue.at(alphaValue.size() - 1) == u'0') alphaValue.chop(1); - if (alphaValue.at(alphaValue.size() - 1) == QLatin1Char('.')) + if (alphaValue.at(alphaValue.size() - 1) == u'.') alphaValue.chop(1); result = QString::fromLatin1("rgba(%1,%2,%3,%4)").arg(color.red()) .arg(color.green()) .arg(color.blue()) .arg(alphaValue); } else { - result = QLatin1String("transparent"); + result = "transparent"_L1; } return result; @@ -2357,89 +2403,94 @@ static QStringList resolvedFontFamilies(const QTextCharFormat &format) */ QString QTextHtmlExporter::toHtml(ExportMode mode) { - html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " - "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" - "<html><head><meta name=\"qrichtext\" content=\"1\" />"); + html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " + "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" + "<html><head><meta name=\"qrichtext\" content=\"1\" />"_L1; html.reserve(QTextDocumentPrivate::get(doc)->length()); fragmentMarkers = (mode == ExportFragment); - html += QString::fromLatin1("<meta charset=\"utf-8\" />"); + html += "<meta charset=\"utf-8\" />"_L1; QString title = doc->metaInformation(QTextDocument::DocumentTitle); - if (!title.isEmpty()) - html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>"); - html += QLatin1String("<style type=\"text/css\">\n"); - html += QLatin1String("p, li { white-space: pre-wrap; }\n"); - html += QLatin1String("hr { height: 1px; border-width: 0; }\n"); - html += QLatin1String("</style>"); - html += QLatin1String("</head><body"); + if (!title.isEmpty()) { + html += "<title>"_L1; + html += title; + html += "</title>"_L1; + } + html += "<style type=\"text/css\">\n"_L1; + html += "p, li { white-space: pre-wrap; }\n"_L1; + html += "hr { height: 1px; border-width: 0; }\n"_L1; + html += "li.unchecked::marker { content: \"\\2610\"; }\n"_L1; + html += "li.checked::marker { content: \"\\2612\"; }\n"_L1; + html += "</style>"_L1; + html += "</head><body"_L1; if (mode == ExportEntireDocument) { - html += QLatin1String(" style=\""); + html += " style=\""_L1; emitFontFamily(resolvedFontFamilies(defaultCharFormat)); if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) { - html += QLatin1String(" font-size:"); + html += " font-size:"_L1; html += QString::number(defaultCharFormat.fontPointSize()); - html += QLatin1String("pt;"); + html += "pt;"_L1; } else if (defaultCharFormat.hasProperty(QTextFormat::FontPixelSize)) { - html += QLatin1String(" font-size:"); + html += " font-size:"_L1; html += QString::number(defaultCharFormat.intProperty(QTextFormat::FontPixelSize)); - html += QLatin1String("px;"); + html += "px;"_L1; } - html += QLatin1String(" font-weight:"); + html += " font-weight:"_L1; html += QString::number(defaultCharFormat.fontWeight()); - html += QLatin1Char(';'); + html += u';'; - html += QLatin1String(" font-style:"); - html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); - html += QLatin1Char(';'); + html += " font-style:"_L1; + html += (defaultCharFormat.fontItalic() ? "italic"_L1 : "normal"_L1); + html += u';'; const bool percentSpacing = (defaultCharFormat.fontLetterSpacingType() == QFont::PercentageSpacing); if (defaultCharFormat.hasProperty(QTextFormat::FontLetterSpacing) && (!percentSpacing || defaultCharFormat.fontLetterSpacing() != 0.0)) { - html += QLatin1String(" letter-spacing:"); + html += " letter-spacing:"_L1; qreal value = defaultCharFormat.fontLetterSpacing(); if (percentSpacing) // Map to em (100% == 0em) value = (value / 100) - 1; html += QString::number(value); - html += percentSpacing ? QLatin1String("em;") : QLatin1String("px;"); + html += percentSpacing ? "em;"_L1 : "px;"_L1; } if (defaultCharFormat.hasProperty(QTextFormat::FontWordSpacing) && defaultCharFormat.fontWordSpacing() != 0.0) { - html += QLatin1String(" word-spacing:"); + html += " word-spacing:"_L1; html += QString::number(defaultCharFormat.fontWordSpacing()); - html += QLatin1String("px;"); + html += "px;"_L1; } - QString decorationTag(QLatin1String(" text-decoration:")); + QString decorationTag(" text-decoration:"_L1); bool atLeastOneDecorationSet = false; if (defaultCharFormat.hasProperty(QTextFormat::FontUnderline) || defaultCharFormat.hasProperty(QTextFormat::TextUnderlineStyle)) { if (defaultCharFormat.fontUnderline()) { - decorationTag += QLatin1String(" underline"); + decorationTag += " underline"_L1; atLeastOneDecorationSet = true; } } if (defaultCharFormat.hasProperty(QTextFormat::FontOverline)) { if (defaultCharFormat.fontOverline()) { - decorationTag += QLatin1String(" overline"); + decorationTag += " overline"_L1; atLeastOneDecorationSet = true; } } if (defaultCharFormat.hasProperty(QTextFormat::FontStrikeOut)) { if (defaultCharFormat.fontStrikeOut()) { - decorationTag += QLatin1String(" line-through"); + decorationTag += " line-through"_L1; atLeastOneDecorationSet = true; } } if (atLeastOneDecorationSet) - html += decorationTag + QLatin1Char(';'); + html += decorationTag + u';'; - html += QLatin1Char('\"'); + html += u'\"'; const QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); emitBackgroundAttribute(fmt); @@ -2447,7 +2498,7 @@ QString QTextHtmlExporter::toHtml(ExportMode mode) } else { defaultCharFormat = QTextCharFormat(); } - html += QLatin1Char('>'); + html += u'>'; QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat(); rootFmt.clearProperty(QTextFormat::BackgroundBrush); @@ -2460,17 +2511,17 @@ QString QTextHtmlExporter::toHtml(ExportMode mode) else emitTextFrame(doc->rootFrame()); - html += QLatin1String("</body></html>"); + html += "</body></html>"_L1; return html; } void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value) { - html += QLatin1Char(' '); - html += QLatin1String(attribute); - html += QLatin1String("=\""); + html += u' '; + html += QLatin1StringView(attribute); + html += "=\""_L1; html += value.toHtmlEscaped(); - html += QLatin1Char('"'); + html += u'"'; } bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) @@ -2487,9 +2538,9 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) if (format.hasProperty(QTextFormat::FontPointSize) && format.fontPointSize() != defaultCharFormat.fontPointSize()) { - html += QLatin1String(" font-size:"); + html += " font-size:"_L1; html += QString::number(format.fontPointSize()); - html += QLatin1String("pt;"); + html += "pt;"_L1; attributesEmitted = true; } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) { static const char sizeNameData[] = @@ -2509,35 +2560,37 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) name = sizeNameData + sizeNameOffsets[idx]; } if (name) { - html += QLatin1String(" font-size:"); - html += QLatin1String(name); - html += QLatin1Char(';'); + html += " font-size:"_L1; + html += QLatin1StringView(name); + html += u';'; attributesEmitted = true; } - } else if (format.hasProperty(QTextFormat::FontPixelSize)) { - html += QLatin1String(" font-size:"); + } else if (format.hasProperty(QTextFormat::FontPixelSize) + && format.property(QTextFormat::FontPixelSize) + != defaultCharFormat.property(QTextFormat::FontPixelSize)) { + html += " font-size:"_L1; html += QString::number(format.intProperty(QTextFormat::FontPixelSize)); - html += QLatin1String("px;"); + html += "px;"_L1; attributesEmitted = true; } if (format.hasProperty(QTextFormat::FontWeight) && format.fontWeight() != defaultCharFormat.fontWeight()) { - html += QLatin1String(" font-weight:"); + html += " font-weight:"_L1; html += QString::number(format.fontWeight()); - html += QLatin1Char(';'); + html += u';'; attributesEmitted = true; } if (format.hasProperty(QTextFormat::FontItalic) && format.fontItalic() != defaultCharFormat.fontItalic()) { - html += QLatin1String(" font-style:"); - html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); - html += QLatin1Char(';'); + html += " font-style:"_L1; + html += (format.fontItalic() ? "italic"_L1 : "normal"_L1); + html += u';'; attributesEmitted = true; } - QLatin1String decorationTag(" text-decoration:"); + const auto decorationTag = " text-decoration:"_L1; html += decorationTag; bool hasDecoration = false; bool atLeastOneDecorationSet = false; @@ -2546,7 +2599,7 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) && format.fontUnderline() != defaultCharFormat.fontUnderline()) { hasDecoration = true; if (format.fontUnderline()) { - html += QLatin1String(" underline"); + html += " underline"_L1; atLeastOneDecorationSet = true; } } @@ -2555,7 +2608,7 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) && format.fontOverline() != defaultCharFormat.fontOverline()) { hasDecoration = true; if (format.fontOverline()) { - html += QLatin1String(" overline"); + html += " overline"_L1; atLeastOneDecorationSet = true; } } @@ -2564,19 +2617,19 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) { hasDecoration = true; if (format.fontStrikeOut()) { - html += QLatin1String(" line-through"); + html += " line-through"_L1; atLeastOneDecorationSet = true; } } if (hasDecoration) { if (!atLeastOneDecorationSet) - html += QLatin1String("none"); - html += QLatin1Char(';'); + html += "none"_L1; + html += u';'; if (format.hasProperty(QTextFormat::TextUnderlineColor)) { - html += QLatin1String(" text-decoration-color:"); + html += " text-decoration-color:"_L1; html += colorValue(format.underlineColor()); - html += QLatin1Char(';'); + html += u';'; } attributesEmitted = true; } else { @@ -2590,61 +2643,120 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) const bool isPixmap = qHasPixmapTexture(brush); const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey(); - html += QLatin1String(" -qt-fg-texture-cachekey:"); + html += " -qt-fg-texture-cachekey:"_L1; html += QString::number(cacheKey); - html += QLatin1String(";"); + html += ";"_L1; + } else if (brush.style() == Qt::LinearGradientPattern + || brush.style() == Qt::RadialGradientPattern + || brush.style() == Qt::ConicalGradientPattern) { + const QGradient *gradient = brush.gradient(); + if (gradient->type() == QGradient::LinearGradient) { + const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(brush.gradient()); + + html += " -qt-foreground: qlineargradient("_L1; + html += "x1:"_L1 + QString::number(linearGradient->start().x()) + u','; + html += "y1:"_L1 + QString::number(linearGradient->start().y()) + u','; + html += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u','; + html += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u','; + } else if (gradient->type() == QGradient::RadialGradient) { + const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(brush.gradient()); + + html += " -qt-foreground: qradialgradient("_L1; + html += "cx:"_L1 + QString::number(radialGradient->center().x()) + u','; + html += "cy:"_L1 + QString::number(radialGradient->center().y()) + u','; + html += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u','; + html += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u','; + html += "radius:"_L1 + QString::number(radialGradient->radius()) + u','; + } else { + const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(brush.gradient()); + + html += " -qt-foreground: qconicalgradient("_L1; + html += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u','; + html += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u','; + html += "angle:"_L1 + QString::number(conicalGradient->angle()) + u','; + } + + const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 }; + html += "coordinatemode:"_L1; + html += coordinateModes.at(int(gradient->coordinateMode())); + html += u','; + + const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 }; + html += "spread:"_L1; + html += spreads.at(int(gradient->spread())); + + for (const QGradientStop &stop : gradient->stops()) { + html += ",stop:"_L1; + html += QString::number(stop.first); + html += u' '; + html += colorValue(stop.second); + } + + html += ");"_L1; } else { - html += QLatin1String(" color:"); + html += " color:"_L1; html += colorValue(brush.color()); - html += QLatin1Char(';'); + html += u';'; } attributesEmitted = true; } if (format.background() != defaultCharFormat.background() && format.background().style() == Qt::SolidPattern) { - html += QLatin1String(" background-color:"); + html += " background-color:"_L1; html += colorValue(format.background().color()); - html += QLatin1Char(';'); + html += u';'; attributesEmitted = true; } if (format.verticalAlignment() != defaultCharFormat.verticalAlignment() && format.verticalAlignment() != QTextCharFormat::AlignNormal) { - html += QLatin1String(" vertical-align:"); + html += " vertical-align:"_L1; QTextCharFormat::VerticalAlignment valign = format.verticalAlignment(); if (valign == QTextCharFormat::AlignSubScript) - html += QLatin1String("sub"); + html += "sub"_L1; else if (valign == QTextCharFormat::AlignSuperScript) - html += QLatin1String("super"); + html += "super"_L1; else if (valign == QTextCharFormat::AlignMiddle) - html += QLatin1String("middle"); + html += "middle"_L1; else if (valign == QTextCharFormat::AlignTop) - html += QLatin1String("top"); + html += "top"_L1; else if (valign == QTextCharFormat::AlignBottom) - html += QLatin1String("bottom"); + html += "bottom"_L1; - html += QLatin1Char(';'); + html += u';'; attributesEmitted = true; } if (format.fontCapitalization() != QFont::MixedCase) { const QFont::Capitalization caps = format.fontCapitalization(); if (caps == QFont::AllUppercase) - html += QLatin1String(" text-transform:uppercase;"); + html += " text-transform:uppercase;"_L1; else if (caps == QFont::AllLowercase) - html += QLatin1String(" text-transform:lowercase;"); + html += " text-transform:lowercase;"_L1; else if (caps == QFont::SmallCaps) - html += QLatin1String(" font-variant:small-caps;"); + html += " font-variant:small-caps;"_L1; attributesEmitted = true; } if (format.fontWordSpacing() != 0.0) { - html += QLatin1String(" word-spacing:"); + html += " word-spacing:"_L1; html += QString::number(format.fontWordSpacing()); - html += QLatin1String("px;"); + html += "px;"_L1; + attributesEmitted = true; + } + + if (format.hasProperty(QTextFormat::TextOutline)) { + QPen outlinePen = format.textOutline(); + html += " -qt-stroke-color:"_L1; + html += colorValue(outlinePen.color()); + html += u';'; + + html += " -qt-stroke-width:"_L1; + html += QString::number(outlinePen.widthF()); + html += "px;"_L1; attributesEmitted = true; } @@ -2656,15 +2768,15 @@ void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength if (length.type() == QTextLength::VariableLength) // default return; - html += QLatin1Char(' '); - html += QLatin1String(attribute); - html += QLatin1String("=\""); + html += u' '; + html += QLatin1StringView(attribute); + html += "=\""_L1; html += QString::number(length.rawValue()); if (length.type() == QTextLength::PercentageLength) - html += QLatin1String("%\""); + html += "%\""_L1; else - html += QLatin1Char('\"'); + html += u'\"'; } void QTextHtmlExporter::emitAlignment(Qt::Alignment align) @@ -2672,11 +2784,11 @@ void QTextHtmlExporter::emitAlignment(Qt::Alignment align) if (align & Qt::AlignLeft) return; else if (align & Qt::AlignRight) - html += QLatin1String(" align=\"right\""); + html += " align=\"right\""_L1; else if (align & Qt::AlignHCenter) - html += QLatin1String(" align=\"center\""); + html += " align=\"center\""_L1; else if (align & Qt::AlignJustify) - html += QLatin1String(" align=\"justify\""); + html += " align=\"justify\""_L1; } void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode) @@ -2685,108 +2797,108 @@ void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode return; if (mode == EmitStyleTag) - html += QLatin1String(" style=\"float:"); + html += " style=\"float:"_L1; else - html += QLatin1String(" float:"); + html += " float:"_L1; if (pos == QTextFrameFormat::FloatLeft) - html += QLatin1String(" left;"); + html += " left;"_L1; else if (pos == QTextFrameFormat::FloatRight) - html += QLatin1String(" right;"); + html += " right;"_L1; else Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type"); if (mode == EmitStyleTag) - html += QLatin1Char('\"'); + html += u'\"'; } -static QLatin1String richtextBorderStyleToHtmlBorderStyle(QTextFrameFormat::BorderStyle style) +static QLatin1StringView richtextBorderStyleToHtmlBorderStyle(QTextFrameFormat::BorderStyle style) { switch (style) { case QTextFrameFormat::BorderStyle_None: - return QLatin1String("none"); + return "none"_L1; case QTextFrameFormat::BorderStyle_Dotted: - return QLatin1String("dotted"); + return "dotted"_L1; case QTextFrameFormat::BorderStyle_Dashed: - return QLatin1String("dashed"); + return "dashed"_L1; case QTextFrameFormat::BorderStyle_Solid: - return QLatin1String("solid"); + return "solid"_L1; case QTextFrameFormat::BorderStyle_Double: - return QLatin1String("double"); + return "double"_L1; case QTextFrameFormat::BorderStyle_DotDash: - return QLatin1String("dot-dash"); + return "dot-dash"_L1; case QTextFrameFormat::BorderStyle_DotDotDash: - return QLatin1String("dot-dot-dash"); + return "dot-dot-dash"_L1; case QTextFrameFormat::BorderStyle_Groove: - return QLatin1String("groove"); + return "groove"_L1; case QTextFrameFormat::BorderStyle_Ridge: - return QLatin1String("ridge"); + return "ridge"_L1; case QTextFrameFormat::BorderStyle_Inset: - return QLatin1String("inset"); + return "inset"_L1; case QTextFrameFormat::BorderStyle_Outset: - return QLatin1String("outset"); + return "outset"_L1; default: Q_UNREACHABLE(); }; - return QLatin1String(""); + return ""_L1; } void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style) { Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset); - html += QLatin1String(" border-style:"); + html += " border-style:"_L1; html += richtextBorderStyleToHtmlBorderStyle(style); - html += QLatin1Char(';'); + html += u';'; } void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy) { if (policy & QTextFormat::PageBreak_AlwaysBefore) - html += QLatin1String(" page-break-before:always;"); + html += " page-break-before:always;"_L1; if (policy & QTextFormat::PageBreak_AlwaysAfter) - html += QLatin1String(" page-break-after:always;"); + html += " page-break-after:always;"_L1; } void QTextHtmlExporter::emitFontFamily(const QStringList &families) { - html += QLatin1String(" font-family:"); + html += " font-family:"_L1; bool first = true; for (const QString &family : families) { - QLatin1String quote("\'"); - if (family.contains(QLatin1Char('\''))) - quote = QLatin1String("""); + auto quote = "\'"_L1; + if (family.contains(u'\'')) + quote = """_L1; if (!first) - html += QLatin1String(","); + html += ","_L1; else first = false; html += quote; html += family.toHtmlEscaped(); html += quote; } - html += QLatin1Char(';'); + html += u';'; } void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right) { - html += QLatin1String(" margin-top:"); + html += " margin-top:"_L1; html += top; - html += QLatin1String("px;"); + html += "px;"_L1; - html += QLatin1String(" margin-bottom:"); + html += " margin-bottom:"_L1; html += bottom; - html += QLatin1String("px;"); + html += "px;"_L1; - html += QLatin1String(" margin-left:"); + html += " margin-left:"_L1; html += left; - html += QLatin1String("px;"); + html += "px;"_L1; - html += QLatin1String(" margin-right:"); + html += " margin-right:"_L1; html += right; - html += QLatin1String("px;"); + html += "px;"_L1; } void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) @@ -2798,15 +2910,15 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) if (format.isAnchor()) { const auto names = format.anchorNames(); if (!names.isEmpty()) { - html += QLatin1String("<a name=\""); + html += "<a name=\""_L1; html += names.constFirst().toHtmlEscaped(); - html += QLatin1String("\"></a>"); + html += "\"></a>"_L1; } const QString href = format.anchorHref(); if (!href.isEmpty()) { - html += QLatin1String("<a href=\""); + html += "<a href=\""_L1; html += href.toHtmlEscaped(); - html += QLatin1String("\">"); + html += "\">"_L1; closeAnchor = true; } } @@ -2815,22 +2927,22 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) const bool isObject = txt.contains(QChar::ObjectReplacementCharacter); const bool isImage = isObject && format.isImageFormat(); - QLatin1String styleTag("<span style=\""); + const auto styleTag = "<span style=\""_L1; html += styleTag; bool attributesEmitted = false; if (!isImage) attributesEmitted = emitCharFormatStyle(format); if (attributesEmitted) - html += QLatin1String("\">"); + html += "\">"_L1; else html.chop(styleTag.size()); if (isObject) { - for (int i = 0; isImage && i < txt.length(); ++i) { + for (int i = 0; isImage && i < txt.size(); ++i) { QTextImageFormat imgFmt = format.toImageFormat(); - html += QLatin1String("<img"); + html += "<img"_L1; if (imgFmt.hasProperty(QTextFormat::ImageName)) emitAttribute("src", imgFmt.name()); @@ -2848,14 +2960,14 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) emitAttribute("height", QString::number(imgFmt.height())); if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle) - html += QLatin1String(" style=\"vertical-align: middle;\""); + html += " style=\"vertical-align: middle;\""_L1; else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop) - html += QLatin1String(" style=\"vertical-align: top;\""); + html += " style=\"vertical-align: top;\""_L1; if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt))) emitFloatStyle(imageFrame->frameFormat().position()); - html += QLatin1String(" />"); + html += " />"_L1; } } else { Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter)); @@ -2864,16 +2976,16 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) // split for [\n{LineSeparator}] // space in BR on purpose for compatibility with old-fashioned browsers - txt.replace(QLatin1Char('\n'), QLatin1String("<br />")); - txt.replace(QChar::LineSeparator, QLatin1String("<br />")); + txt.replace(u'\n', "<br />"_L1); + txt.replace(QChar::LineSeparator, "<br />"_L1); html += txt; } if (attributesEmitted) - html += QLatin1String("</span>"); + html += "</span>"_L1; if (closeAnchor) - html += QLatin1String("</a>"); + html += "</a>"_L1; } static bool isOrderedList(int style) @@ -2891,16 +3003,16 @@ void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) emitAlignment(format.alignment()); // assume default to not bloat the html too much - // html += QLatin1String(" dir='ltr'"); + // html += " dir='ltr'"_L1; if (block.textDirection() == Qt::RightToLeft) - html += QLatin1String(" dir='rtl'"); + html += " dir='rtl'"_L1; - QLatin1String style(" style=\""); + const auto style = " style=\""_L1; html += style; const bool emptyBlock = block.begin().atEnd(); if (emptyBlock) { - html += QLatin1String("-qt-paragraph-type:empty;"); + html += "-qt-paragraph-type:empty;"_L1; } emitMargins(QString::number(format.topMargin()), @@ -2908,38 +3020,38 @@ void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) QString::number(format.leftMargin()), QString::number(format.rightMargin())); - html += QLatin1String(" -qt-block-indent:"); + html += " -qt-block-indent:"_L1; html += QString::number(format.indent()); - html += QLatin1Char(';'); + html += u';'; - html += QLatin1String(" text-indent:"); + html += " text-indent:"_L1; html += QString::number(format.textIndent()); - html += QLatin1String("px;"); + html += "px;"_L1; if (block.userState() != -1) { - html += QLatin1String(" -qt-user-state:"); + html += " -qt-user-state:"_L1; html += QString::number(block.userState()); - html += QLatin1Char(';'); + html += u';'; } if (format.lineHeightType() != QTextBlockFormat::SingleHeight) { - html += QLatin1String(" line-height:") + html += " line-height:"_L1 + QString::number(format.lineHeight()); switch (format.lineHeightType()) { case QTextBlockFormat::ProportionalHeight: - html += QLatin1String("%;"); + html += "%;"_L1; break; case QTextBlockFormat::FixedHeight: - html += QLatin1String("; -qt-line-height-type: fixed;"); + html += "; -qt-line-height-type: fixed;"_L1; break; case QTextBlockFormat::MinimumHeight: - html += QLatin1String("px;"); + html += "px;"_L1; break; case QTextBlockFormat::LineDistanceHeight: - html += QLatin1String("; -qt-line-height-type: line-distance;"); + html += "; -qt-line-height-type: line-distance;"_L1; break; default: - html += QLatin1String(";"); + html += ";"_L1; break; // Should never reach here } } @@ -2962,7 +3074,7 @@ void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) if (!diff.properties().isEmpty()) emitCharFormatStyle(diff); - html += QLatin1Char('"'); + html += u'"'; } @@ -2981,7 +3093,7 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block) return; } - html += QLatin1Char('\n'); + html += u'\n'; // save and later restore, in case we 'change' the default format by // emitting block char format information @@ -2992,137 +3104,157 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block) if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate const QTextListFormat format = list->format(); const int style = format.style(); + bool ordered = false; switch (style) { - case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break; - case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break; - case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break; - case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break; - case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break; - case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break; - case QTextListFormat::ListLowerRoman: html += QLatin1String("<ol type=\"i\""); break; - case QTextListFormat::ListUpperRoman: html += QLatin1String("<ol type=\"I\""); break; - default: html += QLatin1String("<ul"); // ### should not happen + case QTextListFormat::ListDisc: html += "<ul"_L1; break; + case QTextListFormat::ListCircle: html += "<ul type=\"circle\""_L1; break; + case QTextListFormat::ListSquare: html += "<ul type=\"square\""_L1; break; + case QTextListFormat::ListDecimal: html += "<ol"_L1; ordered = true; break; + case QTextListFormat::ListLowerAlpha: html += "<ol type=\"a\""_L1; ordered = true; break; + case QTextListFormat::ListUpperAlpha: html += "<ol type=\"A\""_L1; ordered = true; break; + case QTextListFormat::ListLowerRoman: html += "<ol type=\"i\""_L1; ordered = true; break; + case QTextListFormat::ListUpperRoman: html += "<ol type=\"I\""_L1; ordered = true; break; + default: html += "<ul"_L1; // ### should not happen } - QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;"); + if (ordered && format.start() != 1) { + html += " start=\""_L1; + html += QString::number(format.start()); + html += u'"'; + } + + QString styleString; + styleString += "margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;"_L1; if (format.hasProperty(QTextFormat::ListIndent)) { - styleString += QLatin1String(" -qt-list-indent: "); + styleString += " -qt-list-indent: "_L1; styleString += QString::number(format.indent()); - styleString += QLatin1Char(';'); + styleString += u';'; } if (format.hasProperty(QTextFormat::ListNumberPrefix)) { QString numberPrefix = format.numberPrefix(); - numberPrefix.replace(QLatin1Char('"'), QLatin1String("\\22")); - numberPrefix.replace(QLatin1Char('\''), QLatin1String("\\27")); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored - styleString += QLatin1String(" -qt-list-number-prefix: "); - styleString += QLatin1Char('\''); + numberPrefix.replace(u'"', "\\22"_L1); + numberPrefix.replace(u'\'', "\\27"_L1); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored + styleString += " -qt-list-number-prefix: "_L1; + styleString += u'\''; styleString += numberPrefix; - styleString += QLatin1Char('\''); - styleString += QLatin1Char(';'); + styleString += u'\''; + styleString += u';'; } if (format.hasProperty(QTextFormat::ListNumberSuffix)) { - if (format.numberSuffix() != QLatin1String(".")) { // this is our default + if (format.numberSuffix() != "."_L1) { // this is our default QString numberSuffix = format.numberSuffix(); - numberSuffix.replace(QLatin1Char('"'), QLatin1String("\\22")); - numberSuffix.replace(QLatin1Char('\''), QLatin1String("\\27")); // see above - styleString += QLatin1String(" -qt-list-number-suffix: "); - styleString += QLatin1Char('\''); + numberSuffix.replace(u'"', "\\22"_L1); + numberSuffix.replace(u'\'', "\\27"_L1); // see above + styleString += " -qt-list-number-suffix: "_L1; + styleString += u'\''; styleString += numberSuffix; - styleString += QLatin1Char('\''); - styleString += QLatin1Char(';'); + styleString += u'\''; + styleString += u';'; } } - html += QLatin1String(" style=\""); + html += " style=\""_L1; html += styleString; - html += QLatin1String("\">"); + html += "\">\n"_L1; } - html += QLatin1String("<li"); + html += "<li"_L1; const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat(); if (!blockFmt.properties().isEmpty()) { - html += QLatin1String(" style=\""); + html += " style=\""_L1; emitCharFormatStyle(blockFmt); - html += QLatin1Char('\"'); + html += u'\"'; defaultCharFormat.merge(block.charFormat()); } + if (block.blockFormat().hasProperty(QTextFormat::BlockMarker)) { + switch (block.blockFormat().marker()) { + case QTextBlockFormat::MarkerType::Checked: + html += " class=\"checked\""_L1; + break; + case QTextBlockFormat::MarkerType::Unchecked: + html += " class=\"unchecked\""_L1; + break; + case QTextBlockFormat::MarkerType::NoMarker: + break; + } + } } const QTextBlockFormat blockFormat = block.blockFormat(); if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { - html += QLatin1String("<hr"); + html += "<hr"_L1; QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth); if (width.type() != QTextLength::VariableLength) emitTextLength("width", width); - html += QLatin1Char(' '); + html += u' '; if (blockFormat.hasProperty(QTextFormat::BackgroundBrush)) { - html += QLatin1String("style=\""); - html += QLatin1String("background-color:"); + html += "style=\""_L1; + html += "background-color:"_L1; html += colorValue(qvariant_cast<QBrush>(blockFormat.property(QTextFormat::BackgroundBrush)).color()); - html += QLatin1Char(';'); - html += QLatin1Char('\"'); + html += u';'; + html += u'\"'; } - html += QLatin1String("/>"); + html += "/>"_L1; return; } const bool pre = blockFormat.nonBreakableLines(); if (pre) { if (list) - html += QLatin1Char('>'); - html += QLatin1String("<pre"); + html += u'>'; + html += "<pre"_L1; } else if (!list) { int headingLevel = blockFormat.headingLevel(); if (headingLevel > 0 && headingLevel <= 6) - html += QLatin1String("<h") + QString::number(headingLevel); + html += "<h"_L1 + QString::number(headingLevel); else - html += QLatin1String("<p"); + html += "<p"_L1; } emitBlockAttributes(block); - html += QLatin1Char('>'); + html += u'>'; if (block.begin().atEnd()) - html += QLatin1String("<br />"); + html += "<br />"_L1; QTextBlock::Iterator it = block.begin(); if (fragmentMarkers && !it.atEnd() && block == doc->begin()) - html += QLatin1String("<!--StartFragment-->"); + html += "<!--StartFragment-->"_L1; for (; !it.atEnd(); ++it) emitFragment(it.fragment()); if (fragmentMarkers && block.position() + block.length() == QTextDocumentPrivate::get(doc)->length()) - html += QLatin1String("<!--EndFragment-->"); + html += "<!--EndFragment-->"_L1; QString closeTags; if (pre) - html += QLatin1String("</pre>"); + html += "</pre>"_L1; else if (list) - closeTags += QLatin1String("</li>"); + closeTags += "</li>"_L1; else { int headingLevel = blockFormat.headingLevel(); if (headingLevel > 0 && headingLevel <= 6) - html += QLatin1String("</h") + QString::number(headingLevel) + QLatin1Char('>'); + html += QString::asprintf("</h%d>", headingLevel); else - html += QLatin1String("</p>"); + html += "</p>"_L1; } if (list) { if (list->itemNumber(block) == list->count() - 1) { // last item? close list if (isOrderedList(list->format().style())) - closeTags += QLatin1String("</ol>"); + closeTags += "</ol>"_L1; else - closeTags += QLatin1String("</ul>"); + closeTags += "</ul>"_L1; } const QTextBlock nextBlock = block.next(); // If the next block is the beginning of a new deeper nested list, then we don't @@ -3218,7 +3350,7 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) { QTextTableFormat format = table->format(); - html += QLatin1String("\n<table"); + html += "\n<table"_L1; if (format.hasProperty(QTextFormat::FrameBorder)) emitAttribute("border", QString::number(format.border())); @@ -3235,7 +3367,7 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) emitBackgroundAttribute(format); - html += QLatin1Char('>'); + html += u'>'; const int rows = table->rows(); const int columns = table->columns(); @@ -3245,7 +3377,7 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) columnWidths.resize(columns); columnWidths.fill(QTextLength()); } - Q_ASSERT(columnWidths.count() == columns); + Q_ASSERT(columnWidths.size() == columns); QVarLengthArray<bool> widthEmittedForColumn(columns); for (int i = 0; i < columns; ++i) @@ -3253,10 +3385,10 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) const int headerRowCount = qMin(format.headerRowCount(), rows); if (headerRowCount > 0) - html += QLatin1String("<thead>"); + html += "<thead>"_L1; for (int row = 0; row < rows; ++row) { - html += QLatin1String("\n<tr>"); + html += "\n<tr>"_L1; for (int col = 0; col < columns; ++col) { const QTextTableCell cell = table->cellAt(row, col); @@ -3268,7 +3400,7 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) if (cell.column() != col) continue; - html += QLatin1String("\n<td"); + html += "\n<td"_L1; if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) { emitTextLength("width", columnWidths.at(col)); @@ -3290,21 +3422,21 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) QString styleString; if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) { - styleString += QLatin1String(" vertical-align:"); + styleString += " vertical-align:"_L1; switch (valign) { case QTextCharFormat::AlignMiddle: - styleString += QLatin1String("middle"); + styleString += "middle"_L1; break; case QTextCharFormat::AlignTop: - styleString += QLatin1String("top"); + styleString += "top"_L1; break; case QTextCharFormat::AlignBottom: - styleString += QLatin1String("bottom"); + styleString += "bottom"_L1; break; default: break; } - styleString += QLatin1Char(';'); + styleString += u';'; QTextCharFormat temp; temp.setVerticalAlignment(valign); @@ -3312,59 +3444,59 @@ void QTextHtmlExporter::emitTable(const QTextTable *table) } if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding)) - styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';'); + styleString += " padding-left:"_L1 + QString::number(cellFormat.leftPadding()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding)) - styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';'); + styleString += " padding-right:"_L1 + QString::number(cellFormat.rightPadding()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding)) - styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';'); + styleString += " padding-top:"_L1 + QString::number(cellFormat.topPadding()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding)) - styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';'); + styleString += " padding-bottom:"_L1 + QString::number(cellFormat.bottomPadding()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellTopBorder)) - styleString += QLatin1String(" border-top:") + QString::number(cellFormat.topBorder()) + QLatin1String("px;"); + styleString += " border-top:"_L1 + QString::number(cellFormat.topBorder()) + "px;"_L1; if (cellFormat.hasProperty(QTextFormat::TableCellRightBorder)) - styleString += QLatin1String(" border-right:") + QString::number(cellFormat.rightBorder()) + QLatin1String("px;"); + styleString += " border-right:"_L1 + QString::number(cellFormat.rightBorder()) + "px;"_L1; if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorder)) - styleString += QLatin1String(" border-bottom:") + QString::number(cellFormat.bottomBorder()) + QLatin1String("px;"); + styleString += " border-bottom:"_L1 + QString::number(cellFormat.bottomBorder()) + "px;"_L1; if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorder)) - styleString += QLatin1String(" border-left:") + QString::number(cellFormat.leftBorder()) + QLatin1String("px;"); + styleString += " border-left:"_L1 + QString::number(cellFormat.leftBorder()) + "px;"_L1; if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderBrush)) - styleString += QLatin1String(" border-top-color:") + cellFormat.topBorderBrush().color().name() + QLatin1Char(';'); + styleString += " border-top-color:"_L1 + cellFormat.topBorderBrush().color().name() + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderBrush)) - styleString += QLatin1String(" border-right-color:") + cellFormat.rightBorderBrush().color().name() + QLatin1Char(';'); + styleString += " border-right-color:"_L1 + cellFormat.rightBorderBrush().color().name() + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderBrush)) - styleString += QLatin1String(" border-bottom-color:") + cellFormat.bottomBorderBrush().color().name() + QLatin1Char(';'); + styleString += " border-bottom-color:"_L1 + cellFormat.bottomBorderBrush().color().name() + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderBrush)) - styleString += QLatin1String(" border-left-color:") + cellFormat.leftBorderBrush().color().name() + QLatin1Char(';'); + styleString += " border-left-color:"_L1 + cellFormat.leftBorderBrush().color().name() + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderStyle)) - styleString += QLatin1String(" border-top-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.topBorderStyle()) + QLatin1Char(';'); + styleString += " border-top-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.topBorderStyle()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderStyle)) - styleString += QLatin1String(" border-right-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.rightBorderStyle()) + QLatin1Char(';'); + styleString += " border-right-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.rightBorderStyle()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderStyle)) - styleString += QLatin1String(" border-bottom-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.bottomBorderStyle()) + QLatin1Char(';'); + styleString += " border-bottom-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.bottomBorderStyle()) + u';'; if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderStyle)) - styleString += QLatin1String(" border-left-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.leftBorderStyle()) + QLatin1Char(';'); + styleString += " border-left-style:"_L1 + richtextBorderStyleToHtmlBorderStyle(cellFormat.leftBorderStyle()) + u';'; if (!styleString.isEmpty()) - html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"'); + html += " style=\""_L1 + styleString + u'\"'; - html += QLatin1Char('>'); + html += u'>'; emitFrame(cell.begin()); - html += QLatin1String("</td>"); + html += "</td>"_L1; defaultCharFormat = oldDefaultCharFormat; } - html += QLatin1String("</tr>"); + html += "</tr>"_L1; if (headerRowCount > 0 && row == headerRowCount - 1) - html += QLatin1String("</thead>"); + html += "</thead>"_L1; } - html += QLatin1String("</table>"); + html += "</table>"_L1; } void QTextHtmlExporter::emitFrame(const QTextFrame::Iterator &frameIt) @@ -3397,7 +3529,7 @@ void QTextHtmlExporter::emitTextFrame(const QTextFrame *f) { FrameType frameType = f->parentFrame() ? TextFrame : RootFrame; - html += QLatin1String("\n<table"); + html += "\n<table"_L1; QTextFrameFormat format = f->frameFormat(); if (format.hasProperty(QTextFormat::FrameBorder)) @@ -3412,22 +3544,22 @@ void QTextHtmlExporter::emitTextFrame(const QTextFrame *f) if (frameType != RootFrame) emitBackgroundAttribute(format); - html += QLatin1Char('>'); - html += QLatin1String("\n<tr>\n<td style=\"border: none;\">"); + html += u'>'; + html += "\n<tr>\n<td style=\"border: none;\">"_L1; emitFrame(f->begin()); - html += QLatin1String("</td></tr></table>"); + html += "</td></tr></table>"_L1; } void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType) { - QLatin1String styleAttribute(" style=\""); + const auto styleAttribute = " style=\""_L1; html += styleAttribute; - const int originalHtmlLength = html.length(); + const qsizetype originalHtmlLength = html.size(); if (frameType == TextFrame) - html += QLatin1String("-qt-table-type: frame;"); + html += "-qt-table-type: frame;"_L1; else if (frameType == RootFrame) - html += QLatin1String("-qt-table-type: root;"); + html += "-qt-table-type: root;"_L1; const QTextFrameFormat defaultFormat; @@ -3435,9 +3567,9 @@ void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType emitPageBreakPolicy(format.pageBreakPolicy()); if (format.borderBrush() != defaultFormat.borderBrush()) { - html += QLatin1String(" border-color:"); + html += " border-color:"_L1; html += colorValue(format.borderBrush().color()); - html += QLatin1Char(';'); + html += u';'; } if (format.borderStyle() != defaultFormat.borderStyle()) @@ -3454,12 +3586,12 @@ void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType QString::number(format.rightMargin())); if (format.property(QTextFormat::TableBorderCollapse).toBool()) - html += QLatin1String(" border-collapse:collapse;"); + html += " border-collapse:collapse;"_L1; - if (html.length() == originalHtmlLength) // nothing emitted? + if (html.size() == originalHtmlLength) // nothing emitted? html.chop(styleAttribute.size()); else - html += QLatin1Char('\"'); + html += u'\"'; } /*! @@ -3531,7 +3663,7 @@ QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) cons #if QT_CONFIG(textmarkdownreader) void QTextDocument::setMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features) { - QTextMarkdownImporter(features).import(this, markdown); + QTextMarkdownImporter(this, features).import(markdown); } #endif @@ -3552,3 +3684,5 @@ QList<QTextFormat> QTextDocument::allFormats() const */ QT_END_NAMESPACE + +#include "moc_qtextdocument.cpp" |