summaryrefslogtreecommitdiffstats
path: root/src/gui/text/qtextdocument.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/text/qtextdocument.cpp')
-rw-r--r--src/gui/text/qtextdocument.cpp226
1 files changed, 172 insertions, 54 deletions
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp
index 463026ed23..15a313e13d 100644
--- a/src/gui/text/qtextdocument.cpp
+++ b/src/gui/text/qtextdocument.cpp
@@ -11,6 +11,7 @@
#include "qtexttable.h"
#include "qtextlist.h"
#include <qdebug.h>
+#include <qloggingcategory.h>
#if QT_CONFIG(regularexpression)
#include <qregularexpression.h>
#endif
@@ -42,6 +43,8 @@
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);
@@ -51,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.
@@ -60,21 +65,24 @@ 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("<?xml"_L1) == 0) {
- while (start < text.length()) {
+ if (text.mid(start, 5).compare("<?xml"_L1) == 0) {
+ while (start < text.size()) {
if (text.at(start) == u'?'
- && start + 2 < text.length()
+ && start + 2 < text.size()
&& text.at(start + 1) == u'>') {
start += 2;
break;
@@ -82,35 +90,36 @@ bool Qt::mightBeRichText(const QString& text)
++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("<!doc"_L1, 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) != u'<'
+ qsizetype open = start;
+ while (open < text.size() && text.at(open) != u'<'
&& text.at(open) != u'\n') {
- if (text.at(open) == u'&' && QStringView{text}.mid(open + 1, 3) == "lt;"_L1)
+ 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) == u'<') {
- const int close = text.indexOf(u'>', 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] == u'/' && i + 1 == close)
+ else if (!tag.isEmpty() && current == u'/' && i + 1 == close)
break;
- else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != u'!'))
+ 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
@@ -119,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.
@@ -131,13 +150,13 @@ bool Qt::mightBeRichText(const QString& text)
*/
QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
{
- int col = 0;
+ qsizetype col = 0;
QString rich;
rich += "<p>"_L1;
- for (int i = 0; i < plain.length(); ++i) {
+ for (qsizetype i = 0; i < plain.size(); ++i) {
if (plain[i] == u'\n'){
- int c = 1;
- while (i+1 < plain.length() && plain[i+1] == u'\n') {
+ qsizetype c = 1;
+ while (i+1 < plain.size() && plain[i+1] == u'\n') {
i++;
c++;
}
@@ -235,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}
*/
/*!
@@ -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);
@@ -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,10 +1226,18 @@ 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
@@ -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()
*/
@@ -1310,7 +1350,7 @@ static bool findInBlock(const QTextBlock &block, const QString &expression, int
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;
@@ -1430,7 +1470,7 @@ static bool findInBlock(const QTextBlock &block, const QRegularExpression &expr,
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,7 +2280,8 @@ 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));
}
}
@@ -2323,7 +2369,7 @@ 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) == u'0')
+ while (alphaValue.size() > 1 && alphaValue.at(alphaValue.size() - 1) == u'0')
alphaValue.chop(1);
if (alphaValue.at(alphaValue.size() - 1) == u'.')
alphaValue.chop(1);
@@ -2364,11 +2410,14 @@ QString QTextHtmlExporter::toHtml(ExportMode mode)
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>");
+ 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;
@@ -2516,7 +2565,9 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
html += u';';
attributesEmitted = true;
}
- } else if (format.hasProperty(QTextFormat::FontPixelSize)) {
+ } 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 += "px;"_L1;
@@ -2595,6 +2646,53 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
html += " -qt-fg-texture-cachekey:"_L1;
html += QString::number(cacheKey);
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 += " color:"_L1;
html += colorValue(brush.color());
@@ -2650,6 +2748,18 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
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;
+ }
+
return attributesEmitted;
}
@@ -2829,7 +2939,7 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
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 += "<img"_L1;
@@ -2994,19 +3104,27 @@ 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 += "<ol"_L1; break;
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::ListLowerAlpha: html += "<ol type=\"a\""_L1; break;
- case QTextListFormat::ListUpperAlpha: html += "<ol type=\"A\""_L1; break;
- case QTextListFormat::ListLowerRoman: html += "<ol type=\"i\""_L1; break;
- case QTextListFormat::ListUpperRoman: html += "<ol type=\"I\""_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 += " -qt-list-indent: "_L1;
@@ -3259,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)
@@ -3436,7 +3554,7 @@ void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType
{
const auto styleAttribute = " style=\""_L1;
html += styleAttribute;
- const int originalHtmlLength = html.length();
+ const qsizetype originalHtmlLength = html.size();
if (frameType == TextFrame)
html += "-qt-table-type: frame;"_L1;
@@ -3470,7 +3588,7 @@ void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType
if (format.property(QTextFormat::TableBorderCollapse).toBool())
html += " border-collapse:collapse;"_L1;
- if (html.length() == originalHtmlLength) // nothing emitted?
+ if (html.size() == originalHtmlLength) // nothing emitted?
html.chop(styleAttribute.size());
else
html += u'\"';
@@ -3545,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