diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-06-06 15:35:06 +0200 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2019-06-06 19:00:41 +0200 |
commit | 6e42979518aa0697ff31706616ddbc05486f1864 (patch) | |
tree | 06de55baaed993b51af517dba8f230fab749882c /src/gui/text | |
parent | 5505d25be972948db1621e1511b89b4144aa8bfc (diff) | |
parent | 2a302a73613d68475e667f69b8e36ce07853c813 (diff) |
Merge "Merge remote-tracking branch 'origin/dev' into wip/qt6"
Diffstat (limited to 'src/gui/text')
23 files changed, 388 insertions, 150 deletions
diff --git a/src/gui/text/qabstracttextdocumentlayout.cpp b/src/gui/text/qabstracttextdocumentlayout.cpp index 2278378613..5263ece87c 100644 --- a/src/gui/text/qabstracttextdocumentlayout.cpp +++ b/src/gui/text/qabstracttextdocumentlayout.cpp @@ -41,6 +41,7 @@ #include <qtextformat.h> #include "qtextdocument_p.h" #include "qtextengine_p.h" +#include "qtextlist.h" #include "qabstracttextdocumentlayout_p.h" @@ -650,6 +651,36 @@ QTextFormat QAbstractTextDocumentLayout::formatAt(const QPointF &pos) const } /*! + \since 5.14 + + Returns the block (probably a list item) whose \l{QTextBlockFormat::marker()}{marker} + is found at the given position \a pos. +*/ +QTextBlock QAbstractTextDocumentLayout::blockWithMarkerAt(const QPointF &pos) const +{ + QTextBlock block = document()->firstBlock(); + while (block.isValid()) { + if (block.blockFormat().marker() != QTextBlockFormat::NoMarker) { + QRectF blockBr = blockBoundingRect(block); + QTextBlockFormat blockFmt = block.blockFormat(); + QFontMetrics fm(block.charFormat().font()); + qreal totalIndent = blockFmt.indent() + blockFmt.leftMargin() + blockFmt.textIndent(); + if (block.textList()) + totalIndent += block.textList()->format().indent() * 40; + QRectF adjustedBr = blockBr.adjusted(totalIndent - fm.height(), 0, totalIndent - blockBr.width(), fm.height() - blockBr.height()); + if (adjustedBr.contains(pos)) { + //qDebug() << "hit block" << block.text() << blockBr << adjustedBr << "marker" << block.blockFormat().marker() + // << "font" << block.charFormat().font() << "adj" << lineHeight << totalIndent; + if (block.blockFormat().hasProperty(QTextFormat::BlockMarker)) + return block; + } + } + block = block.next(); + } + return QTextBlock(); +} + +/*! \fn QRectF QAbstractTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const Returns the bounding rectangle of \a frame. diff --git a/src/gui/text/qabstracttextdocumentlayout.h b/src/gui/text/qabstracttextdocumentlayout.h index 3371401420..397dcd37d4 100644 --- a/src/gui/text/qabstracttextdocumentlayout.h +++ b/src/gui/text/qabstracttextdocumentlayout.h @@ -87,6 +87,7 @@ public: QString anchorAt(const QPointF& pos) const; QString imageAt(const QPointF &pos) const; QTextFormat formatAt(const QPointF &pos) const; + QTextBlock blockWithMarkerAt(const QPointF &pos) const; virtual int pageCount() const = 0; virtual QSizeF documentSize() const = 0; diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index db74ab0b65..466e19e9cc 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -138,19 +138,20 @@ struct QFontDef inline uint qHash(const QFontDef &fd, uint seed = 0) noexcept { - return qHash(qRound64(fd.pixelSize*10000)) // use only 4 fractional digits - ^ qHash(fd.weight) - ^ qHash(fd.style) - ^ qHash(fd.stretch) - ^ qHash(fd.styleHint) - ^ qHash(fd.styleStrategy) - ^ qHash(fd.ignorePitch) - ^ qHash(fd.fixedPitch) - ^ qHash(fd.family, seed) - ^ qHash(fd.families, seed) - ^ qHash(fd.styleName) - ^ qHash(fd.hintingPreference) - ; + QtPrivate::QHashCombine hash; + seed = hash(seed, qRound64(fd.pixelSize*10000)); // use only 4 fractional digits + seed = hash(seed, fd.weight); + seed = hash(seed, fd.style); + seed = hash(seed, fd.stretch); + seed = hash(seed, fd.styleHint); + seed = hash(seed, fd.styleStrategy); + seed = hash(seed, fd.ignorePitch); + seed = hash(seed, fd.fixedPitch); + seed = hash(seed, fd.family); + seed = hash(seed, fd.families); + seed = hash(seed, fd.styleName); + seed = hash(seed, fd.hintingPreference); + return seed; } class QFontEngineData diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp index 82e40510ae..3cbda0facd 100644 --- a/src/gui/text/qfontdatabase.cpp +++ b/src/gui/text/qfontdatabase.cpp @@ -38,7 +38,7 @@ ****************************************************************************/ #include "qfontdatabase.h" -#include "qdebug.h" +#include "qloggingcategory.h" #include "qalgorithms.h" #include "qguiapplication.h" #include "qvarlengtharray.h" // here or earlier - workaround for VC++6 @@ -59,25 +59,13 @@ #include <stdlib.h> #include <algorithm> - -// #define QFONTDATABASE_DEBUG -#ifdef QFONTDATABASE_DEBUG -# define FD_DEBUG qDebug -#else -# define FD_DEBUG if (false) qDebug -#endif - -// #define FONT_MATCH_DEBUG -#ifdef FONT_MATCH_DEBUG -# define FM_DEBUG qDebug -#else -# define FM_DEBUG if (false) qDebug -#endif - #include <qtgui_tracepoints_p.h> QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcFontDb, "qt.text.font.db") +Q_LOGGING_CATEGORY(lcFontMatch, "qt.text.font.match") + #define SMOOTH_SCALABLE 0xffff #if defined(QT_BUILD_INTERNAL) @@ -744,7 +732,7 @@ void qt_registerFont(const QString &familyName, const QString &stylename, const QSupportedWritingSystems &writingSystems, void *handle) { QFontDatabasePrivate *d = privateDb(); -// qDebug() << "Adding font" << familyName << weight << style << pixelSize << antialiased; + qCDebug(lcFontDb) << "Adding font" << familyName << weight << style << pixelSize << "aa" << antialiased << "fixed" << fixedPitch; QtFontStyle::Key styleKey; styleKey.style = style; styleKey.weight = weight; @@ -804,6 +792,13 @@ QString qt_resolveFontFamilyAlias(const QString &alias) return alias; } +bool qt_isFontFamilyPopulated(const QString &familyName) +{ + QFontDatabasePrivate *d = privateDb(); + QtFontFamily *f = d->family(familyName, QFontDatabasePrivate::RequestFamily); + return f != nullptr && f->populated; +} + /*! Returns a list of alternative fonts for the specified \a family and \a style and \a script using the \a styleHint given. @@ -1079,7 +1074,7 @@ static QtFontStyle *bestStyle(QtFontFoundry *foundry, const QtFontStyle::Key &st } } - FM_DEBUG( " best style has distance 0x%x", dist ); + qCDebug(lcFontMatch, " best style has distance 0x%x", dist ); return foundry->styles[best]; } @@ -1098,20 +1093,20 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, desc->size = 0; - FM_DEBUG(" REMARK: looking for best foundry for family '%s' [%d]", family->name.toLatin1().constData(), family->count); + qCDebug(lcFontMatch, " REMARK: looking for best foundry for family '%s' [%d]", family->name.toLatin1().constData(), family->count); for (int x = 0; x < family->count; ++x) { QtFontFoundry *foundry = family->foundries[x]; if (!foundry_name.isEmpty() && foundry->name.compare(foundry_name, Qt::CaseInsensitive) != 0) continue; - FM_DEBUG(" looking for matching style in foundry '%s' %d", + qCDebug(lcFontMatch, " looking for matching style in foundry '%s' %d", foundry->name.isEmpty() ? "-- none --" : foundry->name.toLatin1().constData(), foundry->count); QtFontStyle *style = bestStyle(foundry, styleKey, styleName); if (!style->smoothScalable && (styleStrategy & QFont::ForceOutline)) { - FM_DEBUG(" ForceOutline set, but not smoothly scalable"); + qCDebug(lcFontMatch, " ForceOutline set, but not smoothly scalable"); continue; } @@ -1122,7 +1117,7 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, if (!(styleStrategy & QFont::ForceOutline)) { size = style->pixelSize(pixelSize); if (size) { - FM_DEBUG(" found exact size match (%d pixels)", size->pixelSize); + qCDebug(lcFontMatch, " found exact size match (%d pixels)", size->pixelSize); px = size->pixelSize; } } @@ -1131,7 +1126,7 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, if (!size && style->smoothScalable && ! (styleStrategy & QFont::PreferBitmap)) { size = style->pixelSize(SMOOTH_SCALABLE); if (size) { - FM_DEBUG(" found smoothly scalable font (%d pixels)", pixelSize); + qCDebug(lcFontMatch, " found smoothly scalable font (%d pixels)", pixelSize); px = pixelSize; } } @@ -1140,7 +1135,7 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, if (!size && style->bitmapScalable && (styleStrategy & QFont::PreferMatch)) { size = style->pixelSize(0); if (size) { - FM_DEBUG(" found bitmap scalable font (%d pixels)", pixelSize); + qCDebug(lcFontMatch, " found bitmap scalable font (%d pixels)", pixelSize); px = pixelSize; } } @@ -1164,12 +1159,12 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, if (d < distance) { distance = d; size = style->pixelSizes + x; - FM_DEBUG(" best size so far: %3d (%d)", size->pixelSize, pixelSize); + qCDebug(lcFontMatch, " best size so far: %3d (%d)", size->pixelSize, pixelSize); } } if (!size) { - FM_DEBUG(" no size supports the script we want"); + qCDebug(lcFontMatch, " no size supports the script we want"); continue; } @@ -1204,7 +1199,7 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, this_score += qAbs(px - pixelSize); if (this_score < score) { - FM_DEBUG(" found a match: score %x best score so far %x", + qCDebug(lcFontMatch, " found a match: score %x best score so far %x", this_score, score); score = this_score; @@ -1212,7 +1207,7 @@ unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, desc->style = style; desc->size = size; } else { - FM_DEBUG(" score %x no better than best %x", this_score, score); + qCDebug(lcFontMatch, " score %x no better than best %x", this_score, score); } } @@ -1245,7 +1240,7 @@ static int match(int script, const QFontDef &request, char pitch = request.ignorePitch ? '*' : request.fixedPitch ? 'm' : 'p'; - FM_DEBUG("QFontDatabase::match\n" + qCDebug(lcFontMatch, "QFontDatabase::match\n" " request:\n" " family: %s [%s], script: %d\n" " weight: %d, style: %d\n" @@ -2683,7 +2678,7 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) QFontCache::Key key(request, script, multi ? 1 : 0); engine = fontCache->findEngine(key); if (engine) { - FM_DEBUG("Cache hit level 1"); + qCDebug(lcFontMatch, "Cache hit level 1"); return engine; } @@ -2712,7 +2707,7 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) else blackListed.append(index); } else { - FM_DEBUG(" NO MATCH FOUND\n"); + qCDebug(lcFontMatch, " NO MATCH FOUND\n"); } if (!engine) { @@ -2756,7 +2751,7 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) if (!engine) engine = new QFontEngineBox(request.pixelSize); - FM_DEBUG("returning box engine"); + qCDebug(lcFontMatch, "returning box engine"); } return engine; diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 99c9e1bfdc..537d4bcefd 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -1059,15 +1059,15 @@ void QFontEngine::setGlyphCache(const void *context, QFontEngineGlyphCache *cach Q_ASSERT(cache); GlyphCaches &caches = m_glyphCaches[context]; - for (GlyphCaches::const_iterator it = caches.constBegin(), end = caches.constEnd(); it != end; ++it) { - if (cache == it->cache.data()) + for (auto & e : caches) { + if (cache == e.cache.data()) return; } // Limit the glyph caches to 4 per context. This covers all 90 degree rotations, // and limits memory use when there is continuous or random rotation if (caches.size() == 4) - caches.removeLast(); + caches.pop_back(); GlyphCacheEntry entry; entry.cache = cache; @@ -1081,8 +1081,8 @@ QFontEngineGlyphCache *QFontEngine::glyphCache(const void *context, GlyphFormat if (caches == m_glyphCaches.cend()) return nullptr; - for (GlyphCaches::const_iterator it = caches->begin(), end = caches->end(); it != end; ++it) { - QFontEngineGlyphCache *cache = it->cache.data(); + for (auto &e : *caches) { + QFontEngineGlyphCache *cache = e.cache.data(); if (format == cache->glyphFormat() && qtransform_equals_no_translate(cache->m_transform, transform)) return cache; } diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index b922d50b4c..8dcfd7d66c 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -54,7 +54,6 @@ #include <QtGui/private/qtguiglobal_p.h> #include "QtCore/qatomic.h" #include <QtCore/qvarlengtharray.h> -#include <QtCore/QLinkedList> #include <QtCore/qhashfunctions.h> #include "private/qtextengine_p.h" #include "private/qfont_p.h" @@ -370,7 +369,7 @@ private: QExplicitlySharedDataPointer<QFontEngineGlyphCache> cache; bool operator==(const GlyphCacheEntry &other) const { return cache == other.cache; } }; - typedef QLinkedList<GlyphCacheEntry> GlyphCaches; + typedef std::list<GlyphCacheEntry> GlyphCaches; mutable QHash<const void *, GlyphCaches> m_glyphCaches; private: diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h index a92ee9ed2f..02ff335e68 100644 --- a/src/gui/text/qfontmetrics.h +++ b/src/gui/text/qfontmetrics.h @@ -170,10 +170,10 @@ public: QFontMetricsF &operator=(const QFontMetricsF &); QFontMetricsF &operator=(const QFontMetrics &); - inline QFontMetricsF &operator=(QFontMetricsF &&other) + inline QFontMetricsF &operator=(QFontMetricsF &&other) noexcept { qSwap(d, other.d); return *this; } - void swap(QFontMetricsF &other) { qSwap(d, other.d); } + void swap(QFontMetricsF &other) noexcept { qSwap(d, other.d); } qreal ascent() const; qreal capHeight() const; diff --git a/src/gui/text/qplatformfontdatabase.cpp b/src/gui/text/qplatformfontdatabase.cpp index a911014a19..715b00d838 100644 --- a/src/gui/text/qplatformfontdatabase.cpp +++ b/src/gui/text/qplatformfontdatabase.cpp @@ -60,6 +60,7 @@ void qt_registerFont(const QString &familyname, const QString &stylename, void qt_registerFontFamily(const QString &familyName); void qt_registerAliasToFontFamily(const QString &familyName, const QString &alias); +bool qt_isFontFamilyPopulated(const QString &familyName); /*! Registers the pre-rendered QPF2 font contained in the given \a dataArray. @@ -666,6 +667,16 @@ void QPlatformFontDatabase::registerAliasToFontFamily(const QString &familyName, } /*! + Helper function that returns true if the font family has already been registered and populated. + + \since 5.14 +*/ +bool QPlatformFontDatabase::isFamilyPopulated(const QString &familyName) +{ + return qt_isFontFamilyPopulated(familyName); +} + +/*! \class QPlatformFontDatabase \since 5.0 \internal diff --git a/src/gui/text/qplatformfontdatabase.h b/src/gui/text/qplatformfontdatabase.h index f4558129a7..38ba7f10b2 100644 --- a/src/gui/text/qplatformfontdatabase.h +++ b/src/gui/text/qplatformfontdatabase.h @@ -139,6 +139,8 @@ public: static void registerFontFamily(const QString &familyName); static void registerAliasToFontFamily(const QString &familyName, const QString &alias); + + static bool isFamilyPopulated(const QString &familyName); }; QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index b5ff72e706..899801ca39 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2076,6 +2076,7 @@ void QTextDocument::print(QPagedPaintDevice *printer) const The icon needs to be converted to one of the supported types first, for example using QIcon::pixmap. \value StyleSheetResource The resource contains CSS. + \value MarkdownResource The resource contains Markdown. \value UserResource The first available value for user defined resource types. @@ -2708,6 +2709,12 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) if (imgFmt.hasProperty(QTextFormat::ImageName)) emitAttribute("src", imgFmt.name()); + if (imgFmt.hasProperty(QTextFormat::ImageAltText)) + emitAttribute("alt", imgFmt.stringProperty(QTextFormat::ImageAltText)); + + if (imgFmt.hasProperty(QTextFormat::ImageTitle)) + emitAttribute("title", imgFmt.stringProperty(QTextFormat::ImageTitle)); + if (imgFmt.hasProperty(QTextFormat::ImageWidth)) emitAttribute("width", QString::number(imgFmt.width())); @@ -3292,8 +3299,11 @@ QString QTextDocument::toHtml(const QByteArray &encoding) const #endif // QT_NO_TEXTHTMLPARSER /*! - Returns a string containing a Markdown representation of the document, - or an empty string if writing fails for any reason. + \since 5.14 + Returns a string containing a Markdown representation of the document with + the given \a features, or an empty string if writing fails for any reason. + + \sa setMarkdown */ #if QT_CONFIG(textmarkdownwriter) QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const @@ -3308,6 +3318,7 @@ QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) cons #endif /*! + \since 5.14 Replaces the entire contents of the document with the given Markdown-formatted text in the \a markdown string, with the given \a features supported. By default, all supported GitHub-style diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h index 31c06976a5..edb6bd9310 100644 --- a/src/gui/text/qtextdocument.h +++ b/src/gui/text/qtextdocument.h @@ -228,6 +228,7 @@ public: HtmlResource = 1, ImageResource = 2, StyleSheetResource = 3, + MarkdownResource = 4, UserResource = 100 }; diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp index aef4ea1522..1905d9a1b1 100644 --- a/src/gui/text/qtextdocumentfragment.cpp +++ b/src/gui/text/qtextdocumentfragment.cpp @@ -715,6 +715,10 @@ QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes() case Html_img: { QTextImageFormat fmt; fmt.setName(currentNode->imageName); + if (!currentNode->text.isEmpty()) + fmt.setProperty(QTextFormat::ImageTitle, currentNode->text); + if (!currentNode->imageAlt.isEmpty()) + fmt.setProperty(QTextFormat::ImageAltText, currentNode->imageAlt); fmt.merge(currentNode->charFormat); diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp index 7873faf2cb..e5cfa7f46e 100644 --- a/src/gui/text/qtextdocumentlayout.cpp +++ b/src/gui/text/qtextdocumentlayout.cpp @@ -931,7 +931,10 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain Q_ASSERT(!fd->sizeDirty); Q_ASSERT(!fd->layoutDirty); - const QPointF off = offset + fd->position.toPointF(); + // floor the offset to avoid painting artefacts when drawing adjacent borders + // we later also round table cell heights and widths + const QPointF off = QPointF(QPointF(offset + fd->position.toPointF()).toPoint()); + if (context.clip.isValid() && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top() || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left())) @@ -1681,7 +1684,8 @@ recalc_minmax_widths: for (int n = 0; n < cspan; ++n) { const int col = i + n; QFixed w = widthToDistribute / (cspan - n); - td->minWidths[col] = qMax(td->minWidths.at(col), w); + // ceil to avoid going below minWidth when rounding all column widths later + td->minWidths[col] = qMax(td->minWidths.at(col), w).ceil(); widthToDistribute -= td->minWidths.at(col); if (widthToDistribute <= 0) break; @@ -1787,6 +1791,18 @@ recalc_minmax_widths: } } + // in order to get a correct border rendering we must ensure that the distance between + // two cells is exactly 2 * td->border pixel. we do this by rounding the calculated width + // values here. + // to minimize the total rounding error we propagate the rounding error for each width + // to its successor. + QFixed error = 0; + for (int i = 0; i < columns; ++i) { + QFixed orig = td->widths[i]; + td->widths[i] = (td->widths[i] - error).round(); + error = td->widths[i] - orig; + } + td->columnPositions.resize(columns); td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border; @@ -1887,7 +1903,7 @@ relayout: if (cellRow != r) { // the last row gets all the remaining space if (cellRow + rspan - 1 == r) - td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance); + td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance).round(); continue; } } @@ -1908,7 +1924,7 @@ relayout: td, absoluteTableY, /*withPageBreaks =*/true); - const QFixed height = layoutStruct.y + bottomPadding + topPadding; + const QFixed height = (layoutStruct.y + bottomPadding + topPadding).round(); if (rspan > 1) heightToDistribute[c] = height + dropDistance; diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 840448152f..c267ade0c2 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -1383,11 +1383,12 @@ void QTextEngine::shapeText(int item) const if (QChar::isHighSurrogate(ucs4) && i + 1 < itemLength) { uint low = string[i + 1]; if (QChar::isLowSurrogate(low)) { + // high part never changes in simple casing + uc[i] = ucs4; ++i; ucs4 = QChar::surrogateToUcs4(ucs4, low); ucs4 = si.analysis.flags == QScriptAnalysis::Lowercase ? QChar::toLower(ucs4) : QChar::toUpper(ucs4); - // high part never changes in simple casing uc[i] = QChar::lowSurrogate(ucs4); } } else { diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp index 4a2985f74c..644dd5558d 100644 --- a/src/gui/text/qtextformat.cpp +++ b/src/gui/text/qtextformat.cpp @@ -565,6 +565,15 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) \value HeadingLevel The level of a heading, for example 1 corresponds to an HTML H1 tag; otherwise 0. This enum value has been added in Qt 5.12. + \value BlockQuoteLevel The depth of nested quoting on this block: 1 means the block is a top-level block quote. + Blocks that are not block quotes should not have this property. + This enum value has been added in Qt 5.14. + \value BlockCodeLanguage The programming language in a preformatted or code block. + Blocks that do not contain code should not have this property. + This enum value has been added in Qt 5.14. + \value BlockMarker The \l{QTextBlockFormat::MarkerType}{type of adornment} to be shown alongside the block. + This enum value has been added in Qt 5.14. + Character properties \value FontFamily @@ -650,7 +659,13 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) Image properties - \value ImageName + \value ImageName The filename or source of the image. + \value ImageTitle The title attribute of an HTML image tag, or + the quoted string that comes after the URL in a Markdown image link. + This enum value has been added in Qt 5.14. + \value ImageAltText The alt attribute of an HTML image tag, or + the image description in a Markdown image link. + This enum value has been added in Qt 5.14. \value ImageWidth \value ImageHeight \value ImageQuality @@ -2329,6 +2344,50 @@ QList<QTextOption::Tab> QTextBlockFormat::tabPositions() const /*! + \fn void QTextBlockFormat::setMarker(MarkerType marker) + \since 5.14 + + Sets the type of adornment that should be rendered alongside the paragraph to \a marker. + For example, a list item can be adorned with a checkbox, either checked + or unchecked, as a replacement for its bullet. The default is \c NoMarker. + + \sa marker() +*/ + + +/*! + \fn MarkerType QTextBlockFormat::marker() const + \since 5.14 + + Returns the paragraph's marker if one has been set, or \c NoMarker if not. + + \sa setMarker() +*/ + + +/*! + \since 5.14 + \enum QTextBlockFormat::MarkerType + + This enum describes the types of markers a list item can have. + If a list item (a paragraph for which \l QTextBlock::textList() returns the list) + has a marker, it is rendered instead of the normal bullet. + In this way, checkable list items can be mixed with plain list items in the + same list, overriding the type of bullet specified by the + \l QTextListFormat::style() for the entire list. + + \value NoMarker This is the default: the list item's bullet will be shown. + \value Unchecked Instead of the list item's bullet, an unchecked checkbox will be shown. + \value Checked Instead of the list item's bullet, a checked checkbox will be shown. + + In the future, this may be extended to specify other types of paragraph + decorations. + + \sa QTextListFormat::style() +*/ + + +/*! \fn void QTextBlockFormat::setLineHeight(qreal height, int heightType) \since 4.8 diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h index a631309ae0..4f534fb65d 100644 --- a/src/gui/text/qtextformat.h +++ b/src/gui/text/qtextformat.h @@ -253,6 +253,8 @@ public: // image properties ImageName = 0x5000, + ImageTitle = 0x5001, + ImageAltText = 0x5002, ImageWidth = 0x5010, ImageHeight = 0x5011, ImageQuality = 0x5014, diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index 37051502fa..49ee6394ee 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -1556,6 +1556,10 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes) } else if (key == QLatin1String("height")) { node->imageHeight = -2; // register that there is a value for it. setFloatAttribute(&node->imageHeight, value); + } else if (key == QLatin1String("alt")) { + node->imageAlt = value; + } else if (key == QLatin1String("title")) { + node->text = value; } break; case Html_tr: diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h index 73dac38b82..6ce294d211 100644 --- a/src/gui/text/qtexthtmlparser_p.h +++ b/src/gui/text/qtexthtmlparser_p.h @@ -184,6 +184,7 @@ struct QTextHtmlParserNode { QString textListNumberPrefix; QString textListNumberSuffix; QString imageName; + QString imageAlt; qreal imageWidth; qreal imageHeight; QTextLength width; diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp index d8ffec2496..223eb01e55 100644 --- a/src/gui/text/qtextmarkdownimporter.cpp +++ b/src/gui/text/qtextmarkdownimporter.cpp @@ -40,7 +40,9 @@ #include "qtextmarkdownimporter_p.h" #include "qtextdocumentfragment_p.h" #include <QLoggingCategory> +#if QT_CONFIG(regularexpression) #include <QRegularExpression> +#endif #include <QTextCursor> #include <QTextDocument> #include <QTextDocumentFragment> @@ -149,25 +151,16 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det) m_blockType = blockType; switch (blockType) { case MD_BLOCK_P: - if (m_listStack.isEmpty()) { - m_needsInsertBlock = true; + if (!m_listStack.isEmpty()) + qCDebug(lcMD, m_listItem ? "P of LI at level %d" : "P continuation inside LI at level %d", m_listStack.count()); + else qCDebug(lcMD, "P"); - } else { - if (m_emptyListItem) { - qCDebug(lcMD, "LI text block at level %d -> BlockIndent %d", - m_listStack.count(), m_cursor->blockFormat().indent()); - m_emptyListItem = false; - } else { - qCDebug(lcMD, "P inside LI at level %d", m_listStack.count()); - m_needsInsertBlock = true; - } - } + m_needsInsertBlock = true; break; - case MD_BLOCK_QUOTE: { + case MD_BLOCK_QUOTE: ++m_blockQuoteDepth; qCDebug(lcMD, "QUOTE level %d", m_blockQuoteDepth); break; - } case MD_BLOCK_CODE: { MD_BLOCK_CODE_DETAIL *detail = static_cast<MD_BLOCK_CODE_DETAIL *>(det); m_codeBlock = true; @@ -192,51 +185,40 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det) qCDebug(lcMD, "H%d", detail->level); } break; case MD_BLOCK_LI: { - m_needsInsertBlock = false; - MD_BLOCK_LI_DETAIL *detail = static_cast<MD_BLOCK_LI_DETAIL *>(det); - QTextList *list = m_listStack.top(); - QTextBlockFormat bfmt = list->item(list->count() - 1).blockFormat(); - bfmt.setMarker(detail->is_task ? - (detail->task_mark == ' ' ? QTextBlockFormat::Unchecked : QTextBlockFormat::Checked) : - QTextBlockFormat::NoMarker); - if (!m_emptyList) { - m_cursor->insertBlock(bfmt, QTextCharFormat()); - list->add(m_cursor->block()); - } - m_cursor->setBlockFormat(bfmt); - qCDebug(lcMD) << (m_emptyList ? "LI (first in list)" : "LI"); - m_emptyList = false; // Avoid insertBlock for the first item (because insertList already did that) + m_needsInsertBlock = true; m_listItem = true; - m_emptyListItem = true; + MD_BLOCK_LI_DETAIL *detail = static_cast<MD_BLOCK_LI_DETAIL *>(det); + m_markerType = detail->is_task ? + (detail->task_mark == ' ' ? QTextBlockFormat::Unchecked : QTextBlockFormat::Checked) : + QTextBlockFormat::NoMarker; + qCDebug(lcMD) << "LI"; } break; case MD_BLOCK_UL: { MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det); - QTextListFormat fmt; - fmt.setIndent(m_listStack.count() + 1); + m_listFormat = QTextListFormat(); + m_listFormat.setIndent(m_listStack.count() + 1); switch (detail->mark) { case '*': - fmt.setStyle(QTextListFormat::ListCircle); + m_listFormat.setStyle(QTextListFormat::ListCircle); break; case '+': - fmt.setStyle(QTextListFormat::ListSquare); + m_listFormat.setStyle(QTextListFormat::ListSquare); break; default: // including '-' - fmt.setStyle(QTextListFormat::ListDisc); + m_listFormat.setStyle(QTextListFormat::ListDisc); break; } qCDebug(lcMD, "UL %c level %d", detail->mark, m_listStack.count()); - m_listStack.push(m_cursor->insertList(fmt)); - m_emptyList = true; + m_needsInsertList = true; } break; case MD_BLOCK_OL: { MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det); - QTextListFormat fmt; - fmt.setIndent(m_listStack.count() + 1); - fmt.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter)); - fmt.setStyle(QTextListFormat::ListDecimal); + m_listFormat = QTextListFormat(); + m_listFormat.setIndent(m_listStack.count() + 1); + m_listFormat.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter)); + m_listFormat.setStyle(QTextListFormat::ListDecimal); qCDebug(lcMD, "OL xx%d level %d", detail->mark_delimiter, m_listStack.count()); - m_listStack.push(m_cursor->insertList(fmt)); - m_emptyList = true; + m_needsInsertList = true; } break; case MD_BLOCK_TD: { MD_BLOCK_TD_DETAIL *detail = static_cast<MD_BLOCK_TD_DETAIL *>(det); @@ -297,6 +279,9 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail) { Q_UNUSED(detail) switch (blockType) { + case MD_BLOCK_P: + m_listItem = false; + break; case MD_BLOCK_UL: case MD_BLOCK_OL: qCDebug(lcMD, "list at level %d ended", m_listStack.count()); @@ -377,15 +362,10 @@ int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det) } break; case MD_SPAN_IMG: { m_imageSpan = true; + m_imageFormat = QTextImageFormat(); MD_SPAN_IMG_DETAIL *detail = static_cast<MD_SPAN_IMG_DETAIL *>(det); - QString src = QString::fromUtf8(detail->src.text, int(detail->src.size)); - QString title = QString::fromUtf8(detail->title.text, int(detail->title.size)); - QTextImageFormat img; - img.setName(src); - if (m_needsInsertBlock) - insertBlock(); - qCDebug(lcMD) << "image" << src << "title" << title << "relative to" << m_doc->baseUrl(); - m_cursor->insertImage(img); + m_imageFormat.setName(QString::fromUtf8(detail->src.text, int(detail->src.size))); + m_imageFormat.setProperty(QTextFormat::ImageTitle, QString::fromUtf8(detail->title.text, int(detail->title.size))); break; } case MD_SPAN_CODE: @@ -421,20 +401,22 @@ int QTextMarkdownImporter::cbLeaveSpan(int spanType, void *detail) int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) { - if (m_imageSpan) - return 0; // it's the alt-text if (m_needsInsertBlock) insertBlock(); +#if QT_CONFIG(regularexpression) static const QRegularExpression openingBracket(QStringLiteral("<[a-zA-Z]")); static const QRegularExpression closingBracket(QStringLiteral("(/>|</)")); +#endif QString s = QString::fromUtf8(text, int(size)); switch (textType) { case MD_TEXT_NORMAL: +#if QT_CONFIG(regularexpression) if (m_htmlTagDepth) { m_htmlAccumulator += s; s = QString(); } +#endif break; case MD_TEXT_NULLCHAR: s = QString(QChar(0xFFFD)); // CommonMark-required replacement for null @@ -448,12 +430,15 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) case MD_TEXT_CODE: // We'll see MD_SPAN_CODE too, which will set the char format, and that's enough. break; +#if QT_CONFIG(texthtmlparser) case MD_TEXT_ENTITY: m_cursor->insertHtml(s); s = QString(); break; +#endif case MD_TEXT_HTML: // count how many tags are opened and how many are closed +#if QT_CONFIG(regularexpression) { int startIdx = 0; while ((startIdx = s.indexOf(openingBracket, startIdx)) >= 0) { @@ -467,7 +452,6 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) } } m_htmlAccumulator += s; - s = QString(); if (!m_htmlTagDepth) { // all open tags are now closed qCDebug(lcMD) << "HTML" << m_htmlAccumulator; m_cursor->insertHtml(m_htmlAccumulator); @@ -477,6 +461,8 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) m_cursor->setCharFormat(m_spanFormatStack.top()); m_htmlAccumulator = QString(); } +#endif + s = QString(); break; } @@ -488,6 +474,17 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) break; } + if (m_imageSpan) { + // TODO we don't yet support alt text with formatting, because of the cases where m_cursor + // already inserted the text above. Rather need to accumulate it in case we need it here. + m_imageFormat.setProperty(QTextFormat::ImageAltText, s); + qCDebug(lcMD) << "image" << m_imageFormat.name() + << "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle) + << "alt" << s << "relative to" << m_doc->baseUrl(); + m_cursor->insertImage(m_imageFormat); + return 0; // no error + } + if (!s.isEmpty()) m_cursor->insertText(s); if (m_cursor->currentList()) { @@ -515,19 +512,32 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) return 0; // no error } +/*! + Insert a new block based on stored state. + + m_cursor cannot store the state for the _next_ block ahead of time, because + m_cursor->setBlockFormat() controls the format of the block that the cursor + is already in; so cbLeaveBlock() cannot call setBlockFormat() without + altering the block that was just added. Therefore cbLeaveBlock() and the + following cbEnterBlock() set variables to remember what formatting should + come next, and insertBlock() is called just before the actual text + insertion, to create a new block with the right formatting. +*/ void QTextMarkdownImporter::insertBlock() { QTextCharFormat charFormat; if (!m_spanFormatStack.isEmpty()) charFormat = m_spanFormatStack.top(); QTextBlockFormat blockFormat; + if (!m_listStack.isEmpty() && !m_needsInsertList && m_listItem) { + QTextList *list = m_listStack.top(); + blockFormat = list->item(list->count() - 1).blockFormat(); + } if (m_blockQuoteDepth) { blockFormat.setProperty(QTextFormat::BlockQuoteLevel, m_blockQuoteDepth); blockFormat.setLeftMargin(BlockQuoteIndent * m_blockQuoteDepth); blockFormat.setRightMargin(BlockQuoteIndent); } - if (m_listStack.count()) - blockFormat.setIndent(m_listStack.count()); if (m_codeBlock) { blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage); charFormat.setFont(m_monoFont); @@ -535,7 +545,19 @@ void QTextMarkdownImporter::insertBlock() blockFormat.setTopMargin(m_paragraphMargin); blockFormat.setBottomMargin(m_paragraphMargin); } + if (m_markerType == QTextBlockFormat::NoMarker) + blockFormat.clearProperty(QTextFormat::BlockMarker); + else + blockFormat.setMarker(m_markerType); + if (!m_listStack.isEmpty()) + blockFormat.setIndent(m_listStack.count()); m_cursor->insertBlock(blockFormat, charFormat); + if (m_needsInsertList) { + m_listStack.push(m_cursor->createList(m_listFormat)); + } else if (!m_listStack.isEmpty() && m_listItem) { + m_listStack.top()->add(m_cursor->block()); + } + m_needsInsertList = false; m_needsInsertBlock = false; } diff --git a/src/gui/text/qtextmarkdownimporter_p.h b/src/gui/text/qtextmarkdownimporter_p.h index 1716530b1d..fdce74483b 100644 --- a/src/gui/text/qtextmarkdownimporter_p.h +++ b/src/gui/text/qtextmarkdownimporter_p.h @@ -106,27 +106,33 @@ private: QTextDocument *m_doc = nullptr; QTextCursor *m_cursor = nullptr; QTextTable *m_currentTable = nullptr; // because m_cursor->currentTable() doesn't work +#if QT_CONFIG(regularexpression) QString m_htmlAccumulator; +#endif QString m_blockCodeLanguage; QVector<int> m_nonEmptyTableCells; // in the current row QStack<QTextList *> m_listStack; QStack<QTextCharFormat> m_spanFormatStack; QFont m_monoFont; QPalette m_palette; +#if QT_CONFIG(regularexpression) int m_htmlTagDepth = 0; +#endif int m_blockQuoteDepth = 0; int m_tableColumnCount = 0; int m_tableRowCount = 0; int m_tableCol = -1; // because relative cell movements (e.g. m_cursor->movePosition(QTextCursor::NextCell)) don't work int m_paragraphMargin = 0; - Features m_features; int m_blockType = 0; - bool m_emptyList = false; // true when the last thing we did was insertList - bool m_listItem = false; - bool m_emptyListItem = false; + Features m_features; + QTextImageFormat m_imageFormat; + QTextListFormat m_listFormat; + QTextBlockFormat::MarkerType m_markerType = QTextBlockFormat::NoMarker; + bool m_needsInsertBlock = false; + bool m_needsInsertList = false; + bool m_listItem = false; // true from the beginning of LI to the end of the first P bool m_codeBlock = false; bool m_imageSpan = false; - bool m_needsInsertBlock = false; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QTextMarkdownImporter::Features) diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp index f180098db2..58e0c86b95 100644 --- a/src/gui/text/qtextmarkdownwriter.cpp +++ b/src/gui/text/qtextmarkdownwriter.cpp @@ -55,6 +55,7 @@ Q_LOGGING_CATEGORY(lcMDW, "qt.text.markdown.writer") static const QChar Space = QLatin1Char(' '); static const QChar Newline = QLatin1Char('\n'); static const QChar LineBreak = QChar(0x2028); +static const QChar DoubleQuote = QLatin1Char('"'); static const QChar Backtick = QLatin1Char('`'); static const QChar Period = QLatin1Char('.'); @@ -212,10 +213,19 @@ QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list) static int nearestWordWrapIndex(const QString &s, int before) { before = qMin(before, s.length()); + int fragBegin = qMax(before - 15, 0); + if (lcMDW().isDebugEnabled()) { + QString frag = s.mid(fragBegin, 30); + qCDebug(lcMDW) << frag << before; + qCDebug(lcMDW) << QString(before - fragBegin, Period) + QLatin1Char('<'); + } for (int i = before - 1; i >= 0; --i) { - if (s.at(i).isSpace()) + if (s.at(i).isSpace()) { + qCDebug(lcMDW) << QString(i - fragBegin, Period) + QLatin1Char('^') << i; return i; + } } + qCDebug(lcMDW, "not possible"); return -1; } @@ -251,7 +261,7 @@ static void maybeEscapeFirstChar(QString &s) int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat) { - int ColumnLimit = 80; + const int ColumnLimit = 80; QTextBlockFormat blockFmt = block.blockFormat(); bool indentedCodeBlock = false; if (block.textList()) { // it's a list-item @@ -363,7 +373,14 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign QTextCharFormat fmt = frag.fragment().charFormat(); if (fmt.isImageFormat()) { QTextImageFormat ifmt = fmt.toImageFormat(); - QString s = QLatin1String("![image](") + ifmt.name() + QLatin1Char(')'); + QString desc = ifmt.stringProperty(QTextFormat::ImageAltText); + if (desc.isEmpty()) + desc = QLatin1String("image"); + QString s = QLatin1String("![") + desc + QLatin1String("](") + ifmt.name(); + QString title = ifmt.stringProperty(QTextFormat::ImageTitle); + if (!title.isEmpty()) + s += Space + DoubleQuote + title + DoubleQuote; + s += QLatin1Char(')'); if (wrap && col + s.length() > ColumnLimit) { m_stream << Newline << wrapIndentString; col = m_wrappedLineIndent; @@ -419,12 +436,18 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign int fragLen = fragmentText.length(); bool breakingLine = false; while (i < fragLen) { + if (col >= ColumnLimit) { + m_stream << Newline << wrapIndentString; + col = m_wrappedLineIndent; + while (fragmentText[i].isSpace()) + ++i; + } int j = i + ColumnLimit - col; if (j < fragLen) { int wi = nearestWordWrapIndex(fragmentText, j); if (wi < 0) { j = fragLen; - } else { + } else if (wi >= i) { j = wi; breakingLine = true; } diff --git a/src/gui/text/qtextobject.cpp b/src/gui/text/qtextobject.cpp index 9156ee6af4..1302bd66bb 100644 --- a/src/gui/text/qtextobject.cpp +++ b/src/gui/text/qtextobject.cpp @@ -1316,8 +1316,8 @@ QTextList *QTextBlock::textList() const /*! \since 4.1 - Returns a pointer to a QTextBlockUserData object if previously set with - setUserData() or a null pointer. + Returns a pointer to a QTextBlockUserData object, + if one has been set with setUserData(), or \nullptr. */ QTextBlockUserData *QTextBlock::userData() const { diff --git a/src/gui/text/qtextodfwriter.cpp b/src/gui/text/qtextodfwriter.cpp index 8eaad403d0..3561c185a6 100644 --- a/src/gui/text/qtextodfwriter.cpp +++ b/src/gui/text/qtextodfwriter.cpp @@ -43,6 +43,7 @@ #include "qtextodfwriter_p.h" +#include <QImageReader> #include <QImageWriter> #include <QTextListFormat> #include <QTextList> @@ -410,6 +411,29 @@ void QTextOdfWriter::writeBlock(QXmlStreamWriter &writer, const QTextBlock &bloc writer.writeEndElement(); // list-item } +static bool probeImageData(QIODevice *device, QImage *image, QString *mimeType, qreal *width, qreal *height) +{ + QImageReader reader(device); + const QByteArray format = reader.format().toLower(); + if (format == "png") { + *mimeType = QStringLiteral("image/png"); + } else if (format == "jpg") { + *mimeType = QStringLiteral("image/jpg"); + } else if (format == "svg") { + *mimeType = QStringLiteral("image/svg+xml"); + } else { + *image = reader.read(); + return false; + } + + const QSize size = reader.size(); + + *width = size.width(); + *height = size.height(); + + return true; +} + void QTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer, const QTextFragment &fragment) const { writer.writeStartElement(drawNS, QString::fromLatin1("frame")); @@ -420,47 +444,73 @@ void QTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer, const QTextF QTextImageFormat imageFormat = fragment.charFormat().toImageFormat(); writer.writeAttribute(drawNS, QString::fromLatin1("name"), imageFormat.name()); + QByteArray data; + QString mimeType; + qreal width = 0; + qreal height = 0; + QImage image; QString name = imageFormat.name(); if (name.startsWith(QLatin1String(":/"))) // auto-detect resources name.prepend(QLatin1String("qrc")); QUrl url = QUrl(name); - const QVariant data = m_document->resource(QTextDocument::ImageResource, url); - if (data.type() == QVariant::Image) { - image = qvariant_cast<QImage>(data); - } else if (data.type() == QVariant::ByteArray) { - image.loadFromData(data.toByteArray()); - } - - if (image.isNull()) { - if (image.isNull()) { // try direct loading - name = imageFormat.name(); // remove qrc:/ prefix again - image.load(name); + const QVariant variant = m_document->resource(QTextDocument::ImageResource, url); + if (variant.type() == QVariant::Image) { + image = qvariant_cast<QImage>(variant); + } else if (variant.type() == QVariant::ByteArray) { + data = variant.toByteArray(); + + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); + probeImageData(&buffer, &image, &mimeType, &width, &height); + } else { + // try direct loading + QFile file(imageFormat.name()); + if (file.open(QIODevice::ReadOnly) && !probeImageData(&file, &image, &mimeType, &width, &height)) { + file.seek(0); + data = file.readAll(); } } if (! image.isNull()) { QBuffer imageBytes; - QString filename = m_strategy->createUniqueImageName(); + int imgQuality = imageFormat.quality(); if (imgQuality >= 100 || imgQuality < 0 || image.hasAlphaChannel()) { QImageWriter imageWriter(&imageBytes, "png"); imageWriter.write(image); - m_strategy->addFile(filename, QString::fromLatin1("image/png"), imageBytes.data()); + + data = imageBytes.data(); + mimeType = QStringLiteral("image/png"); } else { // Write images without alpha channel as jpg with quality set by QTextImageFormat QImageWriter imageWriter(&imageBytes, "jpg"); imageWriter.setQuality(imgQuality); imageWriter.write(image); - m_strategy->addFile(filename, QString::fromLatin1("image/jpg"), imageBytes.data()); + + data = imageBytes.data(); + mimeType = QStringLiteral("image/jpg"); } - // get the width/height from the format. - qreal width = imageFormat.hasProperty(QTextFormat::ImageWidth) - ? imageFormat.width() : image.width(); + + width = image.width(); + height = image.height(); + } + + if (!data.isEmpty()) { + if (imageFormat.hasProperty(QTextFormat::ImageWidth)) { + width = imageFormat.width(); + } + if (imageFormat.hasProperty(QTextFormat::ImageHeight)) { + height = imageFormat.height(); + } + + QString filename = m_strategy->createUniqueImageName(); + + m_strategy->addFile(filename, mimeType, data); + writer.writeAttribute(svgNS, QString::fromLatin1("width"), pixelToPoint(width)); - qreal height = imageFormat.hasProperty(QTextFormat::ImageHeight) - ? imageFormat.height() : image.height(); writer.writeAttribute(svgNS, QString::fromLatin1("height"), pixelToPoint(height)); + writer.writeAttribute(textNS, QStringLiteral("anchor-type"), QStringLiteral("as-char")); writer.writeStartElement(drawNS, QString::fromLatin1("image")); writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), filename); writer.writeEndElement(); // image @@ -473,9 +523,7 @@ void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, const QSet<int> &for { writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles")); QVector<QTextFormat> allStyles = m_document->allFormats(); - QSetIterator<int> formatId(formats); - while(formatId.hasNext()) { - int formatIndex = formatId.next(); + for (int formatIndex : formats) { QTextFormat textFormat = allStyles.at(formatIndex); switch (textFormat.type()) { case QTextFormat::CharFormat: |