diff options
Diffstat (limited to 'src/gui/text')
44 files changed, 1631 insertions, 134 deletions
diff --git a/src/gui/text/qabstracttextdocumentlayout_p.h b/src/gui/text/qabstracttextdocumentlayout_p.h index 191c463dc6..d631ce3197 100644 --- a/src/gui/text/qabstracttextdocumentlayout_p.h +++ b/src/gui/text/qabstracttextdocumentlayout_p.h @@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE struct QTextObjectHandler { - QTextObjectHandler() : iface(0) {} + QTextObjectHandler() : iface(nullptr) {} QTextObjectInterface *iface; QPointer<QObject> component; }; @@ -71,12 +71,12 @@ public: Q_DECLARE_PUBLIC(QAbstractTextDocumentLayout) inline QAbstractTextDocumentLayoutPrivate() - : paintDevice(0) {} + : paintDevice(nullptr) {} ~QAbstractTextDocumentLayoutPrivate(); inline void setDocument(QTextDocument *doc) { document = doc; - docPrivate = 0; + docPrivate = nullptr; if (doc) docPrivate = doc->docHandle(); } diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h index 860bbe382a..62578f75e5 100644 --- a/src/gui/text/qcssparser_p.h +++ b/src/gui/text/qcssparser_p.h @@ -467,8 +467,8 @@ struct Q_GUI_EXPORT Declaration Attachment attachmentValue() const; int styleFeaturesValue() const; - bool intValue(int *i, const char *unit = 0) const; - bool realValue(qreal *r, const char *unit = 0) const; + bool intValue(int *i, const char *unit = nullptr) const; + bool realValue(qreal *r, const char *unit = nullptr) const; QSize sizeValue() const; QRect rectValue() const; @@ -584,7 +584,7 @@ struct Q_GUI_EXPORT Selector { QVector<BasicSelector> basicSelectors; int specificity() const; - quint64 pseudoClass(quint64 *negated = 0) const; + quint64 pseudoClass(quint64 *negated = nullptr) const; QString pseudoElement() const; }; QT_CSS_DECLARE_TYPEINFO(Selector, Q_MOVABLE_TYPE) @@ -656,7 +656,7 @@ public: }; QVector<StyleRule> styleRulesForNode(NodePtr node); - QVector<Declaration> declarationsForNode(NodePtr node, const char *extraPseudo = 0); + QVector<Declaration> declarationsForNode(NodePtr node, const char *extraPseudo = nullptr); virtual bool nodeNameEquals(NodePtr node, const QString& nodeName) const; virtual QString attribute(NodePtr node, const QString &name) const = 0; @@ -744,7 +744,7 @@ QT_CSS_DECLARE_TYPEINFO(Symbol, Q_MOVABLE_TYPE) class Q_GUI_EXPORT Scanner { public: - static QString preprocess(const QString &input, bool *hasEscapeSequences = 0); + static QString preprocess(const QString &input, bool *hasEscapeSequences = nullptr); static void scan(const QString &preprocessedInput, QVector<Symbol> *symbols); }; @@ -845,7 +845,7 @@ struct Q_GUI_EXPORT ValueExtractor bool extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh); bool extractPosition(int *l, int *t, int *r, int *b, QCss::Origin *, Qt::Alignment *, QCss::PositionMode *, Qt::Alignment *); - bool extractBox(int *margins, int *paddings, int *spacing = 0); + bool extractBox(int *margins, int *paddings, int *spacing = nullptr); bool extractBorder(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii); bool extractOutline(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii, int *offsets); bool extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg); diff --git a/src/gui/text/qdistancefield_p.h b/src/gui/text/qdistancefield_p.h index 31cdf7edd2..1a1b6866a2 100644 --- a/src/gui/text/qdistancefield_p.h +++ b/src/gui/text/qdistancefield_p.h @@ -72,7 +72,7 @@ int Q_GUI_EXPORT QT_DISTANCEFIELD_HIGHGLYPHCOUNT(); class Q_GUI_EXPORT QDistanceFieldData : public QSharedData { public: - QDistanceFieldData() : glyph(0), width(0), height(0), nbytes(0), data(0) {} + QDistanceFieldData() : glyph(0), width(0), height(0), nbytes(0), data(nullptr) {} QDistanceFieldData(const QDistanceFieldData &other); ~QDistanceFieldData(); diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index a51e98ce85..164a2f60ab 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -2083,7 +2083,7 @@ QString QFont::toString() const \relates QFont \since 5.3 */ -uint qHash(const QFont &font, uint seed) Q_DECL_NOTHROW +uint qHash(const QFont &font, uint seed) noexcept { return qHash(QFontPrivate::get(font)->request, seed); } @@ -3164,7 +3164,104 @@ void QFontCache::decreaseCache() #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug stream, const QFont &font) { - return stream << "QFont(" << font.toString() << ')'; + QDebugStateSaver saver(stream); + stream.nospace().noquote(); + stream << "QFont("; + + if (stream.verbosity() == QDebug::DefaultVerbosity) { + stream << font.toString() << ")"; + return stream; + } + + QString fontDescription; + QDebug debug(&fontDescription); + debug.nospace(); + + QFontPrivate priv; + const QFont defaultFont(&priv); + + for (int property = QFont::FamilyResolved; property < QFont::AllPropertiesResolved; property <<= 1) { + const bool resolved = (font.resolve_mask & property) != 0; + if (!resolved && stream.verbosity() == QDebug::MinimumVerbosity) + continue; + + #define QFONT_DEBUG_SKIP_DEFAULT(prop) \ + if ((font.prop() == defaultFont.prop()) && stream.verbosity() == 1) \ + continue; + + QDebugStateSaver saver(debug); + + switch (property) { + case QFont::FamilyResolved: + debug << font.family(); break; + case QFont::SizeResolved: + if (font.pointSizeF() >= 0) + debug << font.pointSizeF() << "pt"; + else if (font.pixelSize() >= 0) + debug << font.pixelSize() << "px"; + else + Q_UNREACHABLE(); + break; + case QFont::StyleHintResolved: + QFONT_DEBUG_SKIP_DEFAULT(styleHint); + debug.verbosity(1) << font.styleHint(); break; + case QFont::StyleStrategyResolved: + QFONT_DEBUG_SKIP_DEFAULT(styleStrategy); + debug.verbosity(1) << font.styleStrategy(); break; + case QFont::WeightResolved: + debug.verbosity(1) << QFont::Weight(font.weight()); break; + case QFont::StyleResolved: + QFONT_DEBUG_SKIP_DEFAULT(style); + debug.verbosity(0) << font.style(); break; + case QFont::UnderlineResolved: + QFONT_DEBUG_SKIP_DEFAULT(underline); + debug << "underline=" << font.underline(); break; + case QFont::OverlineResolved: + QFONT_DEBUG_SKIP_DEFAULT(overline); + debug << "overline=" << font.overline(); break; + case QFont::StrikeOutResolved: + QFONT_DEBUG_SKIP_DEFAULT(strikeOut); + debug << "strikeOut=" << font.strikeOut(); break; + case QFont::FixedPitchResolved: + QFONT_DEBUG_SKIP_DEFAULT(fixedPitch); + debug << "fixedPitch=" << font.fixedPitch(); break; + case QFont::StretchResolved: + QFONT_DEBUG_SKIP_DEFAULT(stretch); + debug.verbosity(0) << QFont::Stretch(font.stretch()); break; + case QFont::KerningResolved: + QFONT_DEBUG_SKIP_DEFAULT(kerning); + debug << "kerning=" << font.kerning(); break; + case QFont::CapitalizationResolved: + QFONT_DEBUG_SKIP_DEFAULT(capitalization); + debug.verbosity(0) << font.capitalization(); break; + case QFont::LetterSpacingResolved: + QFONT_DEBUG_SKIP_DEFAULT(letterSpacing); + debug << "letterSpacing=" << font.letterSpacing(); + debug.verbosity(0) << " (" << font.letterSpacingType() << ")"; + break; + case QFont::HintingPreferenceResolved: + QFONT_DEBUG_SKIP_DEFAULT(hintingPreference); + debug.verbosity(0) << font.hintingPreference(); break; + case QFont::StyleNameResolved: + QFONT_DEBUG_SKIP_DEFAULT(styleName); + debug << "styleName=" << font.styleName(); break; + default: + continue; + }; + + #undef QFONT_DEBUG_SKIP_DEFAULT + + debug << ", "; + } + + if (stream.verbosity() > QDebug::MinimumVerbosity) + debug.verbosity(0) << "resolveMask=" << QFlags<QFont::ResolveProperties>(font.resolve_mask); + else + fontDescription.chop(2); // Last ', ' + + stream << fontDescription << ')'; + + return stream; } #endif diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h index e86f06353a..683aa3bf65 100644 --- a/src/gui/text/qfont.h +++ b/src/gui/text/qfont.h @@ -147,6 +147,7 @@ public: Q_ENUM(SpacingType) enum ResolveProperties { + NoPropertiesResolved = 0x0000, FamilyResolved = 0x0001, SizeResolved = 0x0002, StyleHintResolved = 0x0004, @@ -167,6 +168,7 @@ public: FamiliesResolved = 0x20000, AllPropertiesResolved = 0x3ffff }; + Q_ENUM(ResolveProperties) QFont(); QFont(const QString &family, int pointSize = -1, int weight = -1, bool italic = false); @@ -259,10 +261,8 @@ public: bool operator<(const QFont &) const; operator QVariant() const; bool isCopyOf(const QFont &) const; -#ifdef Q_COMPILER_RVALUE_REFS - inline QFont &operator=(QFont &&other) Q_DECL_NOEXCEPT + inline QFont &operator=(QFont &&other) noexcept { qSwap(d, other.d); qSwap(resolve_mask, other.resolve_mask); return *this; } -#endif #if QT_DEPRECATED_SINCE(5, 3) // needed for X11 @@ -335,13 +335,17 @@ private: friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QFont &); #endif +#ifndef QT_NO_DEBUG_STREAM + friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QFont &); +#endif + QExplicitlySharedDataPointer<QFontPrivate> d; uint resolve_mask; }; Q_DECLARE_SHARED(QFont) -Q_GUI_EXPORT uint qHash(const QFont &font, uint seed = 0) Q_DECL_NOTHROW; +Q_GUI_EXPORT uint qHash(const QFont &font, uint seed = 0) noexcept; inline bool QFont::bold() const { return weight() > Medium; } diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index e86ec31e47..db74ab0b65 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -136,7 +136,7 @@ struct QFontDef } }; -inline uint qHash(const QFontDef &fd, uint seed = 0) Q_DECL_NOTHROW +inline uint qHash(const QFontDef &fd, uint seed = 0) noexcept { return qHash(qRound64(fd.pixelSize*10000)) // use only 4 fractional digits ^ qHash(fd.weight) @@ -266,7 +266,7 @@ public: // QFontEngine cache struct Engine { - Engine() : data(0), timestamp(0), hits(0) { } + Engine() : data(nullptr), timestamp(0), hits(0) { } Engine(QFontEngine *d) : data(d), timestamp(0), hits(0) { } QFontEngine *data; diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp index 778214128e..82b9bb810a 100644 --- a/src/gui/text/qfontdatabase.cpp +++ b/src/gui/text/qfontdatabase.cpp @@ -422,7 +422,7 @@ struct FallbacksCacheKey { QChar::Script script; }; -inline bool operator==(const FallbacksCacheKey &lhs, const FallbacksCacheKey &rhs) Q_DECL_NOTHROW +inline bool operator==(const FallbacksCacheKey &lhs, const FallbacksCacheKey &rhs) noexcept { return lhs.script == rhs.script && lhs.styleHint == rhs.styleHint && @@ -430,12 +430,12 @@ inline bool operator==(const FallbacksCacheKey &lhs, const FallbacksCacheKey &rh lhs.family == rhs.family; } -inline bool operator!=(const FallbacksCacheKey &lhs, const FallbacksCacheKey &rhs) Q_DECL_NOTHROW +inline bool operator!=(const FallbacksCacheKey &lhs, const FallbacksCacheKey &rhs) noexcept { return !operator==(lhs, rhs); } -inline uint qHash(const FallbacksCacheKey &key, uint seed = 0) Q_DECL_NOTHROW +inline uint qHash(const FallbacksCacheKey &key, uint seed = 0) noexcept { QtPrivate::QHashCombine hash; seed = hash(seed, key.family); diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 1719855e68..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; } @@ -1213,7 +1213,7 @@ void QFontEngine::loadKerningPairs(QFixed scalingFactor) end: std::sort(kerning_pairs.begin(), kerning_pairs.end()); // for (int i = 0; i < kerning_pairs.count(); ++i) -// qDebug() << 'i' << i << "left_right" << hex << kerning_pairs.at(i).left_right; +// qDebug() << 'i' << i << "left_right" << Qt::hex << kerning_pairs.at(i).left_right; } diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index 708c79c2ae..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" @@ -194,7 +193,7 @@ public: virtual QImage *lockedAlphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, GlyphFormat neededFormat, const QTransform &t = QTransform(), - QPoint *offset = 0); + QPoint *offset = nullptr); virtual void unlockAlphaMapForGlyph(); virtual bool hasInternalCaching() const { return false; } @@ -224,7 +223,7 @@ public: virtual qreal minLeftBearing() const; virtual qreal minRightBearing() const; - virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = nullptr, qreal *rightBearing = nullptr); inline bool canRender(uint ucs4) const { return glyphIndex(ucs4) != 0; } virtual bool canRender(const QChar *str, int len) const; @@ -234,7 +233,7 @@ public: virtual int glyphCount() const; virtual int glyphMargin(GlyphFormat format) { return format == Format_A32 ? 2 : 0; } - virtual QFontEngine *cloneWithSize(qreal /*pixelSize*/) const { return 0; } + virtual QFontEngine *cloneWithSize(qreal /*pixelSize*/) const { return nullptr; } virtual Qt::HANDLE handle() const; @@ -292,33 +291,33 @@ public: Holder() : ptr(nullptr), destroy_func(nullptr) {} explicit Holder(void *p, qt_destroy_func_t d) : ptr(p), destroy_func(d) {} ~Holder() { if (ptr && destroy_func) destroy_func(ptr); } - Holder(Holder &&other) Q_DECL_NOTHROW + Holder(Holder &&other) noexcept : ptr(other.ptr), destroy_func(other.destroy_func) { other.ptr = nullptr; other.destroy_func = nullptr; } - Holder &operator=(Holder &&other) Q_DECL_NOTHROW + Holder &operator=(Holder &&other) noexcept { swap(other); return *this; } - void swap(Holder &other) Q_DECL_NOTHROW + void swap(Holder &other) noexcept { qSwap(ptr, other.ptr); qSwap(destroy_func, other.destroy_func); } - void *get() const Q_DECL_NOTHROW { return ptr; } - void *release() Q_DECL_NOTHROW { + void *get() const noexcept { return ptr; } + void *release() noexcept { void *result = ptr; ptr = nullptr; destroy_func = nullptr; return result; } - void reset() Q_DECL_NOTHROW { Holder().swap(*this); } - qt_destroy_func_t get_deleter() const Q_DECL_NOTHROW { return destroy_func; } + void reset() noexcept { Holder().swap(*this); } + qt_destroy_func_t get_deleter() const noexcept { return destroy_func; } - bool operator!() const Q_DECL_NOTHROW { return !ptr; } + bool operator!() const noexcept { return !ptr; } }; mutable Holder font_; // \ NOTE: Declared before m_glyphCaches, so font_, face_ @@ -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: @@ -390,7 +389,7 @@ inline bool operator ==(const QFontEngine::FaceId &f1, const QFontEngine::FaceId } inline uint qHash(const QFontEngine::FaceId &f, uint seed = 0) - Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(f.filename))) + noexcept(noexcept(qHash(f.filename))) { QtPrivate::QHashCombine hash; seed = hash(seed, f.filename); @@ -458,7 +457,7 @@ public: virtual void recalcAdvances(QGlyphLayout *, ShaperFlags) const override; virtual void doKerning(QGlyphLayout *, ShaperFlags) const override; virtual void addOutlineToPath(qreal, qreal, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags flags) override; - virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0) override; + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = nullptr, qreal *rightBearing = nullptr) override; virtual QFixed ascent() const override; virtual QFixed capHeight() const override; diff --git a/src/gui/text/qfontengine_qpf2.cpp b/src/gui/text/qfontengine_qpf2.cpp index 110d512d39..409176d41b 100644 --- a/src/gui/text/qfontengine_qpf2.cpp +++ b/src/gui/text/qfontengine_qpf2.cpp @@ -140,9 +140,9 @@ static inline const uchar *verifyTag(const uchar *tagPtr, const uchar *endPtr) } #if defined(DEBUG_HEADER) if (length == 1) - qDebug() << "tag data" << hex << *tagPtr; + qDebug() << "tag data" << Qt::hex << *tagPtr; else if (length == 4) - qDebug() << "tag data" << hex << tagPtr[0] << tagPtr[1] << tagPtr[2] << tagPtr[3]; + qDebug() << "tag data" << Qt::hex << tagPtr[0] << tagPtr[1] << tagPtr[2] << tagPtr[3]; #endif } return tagPtr + length; @@ -367,7 +367,7 @@ bool QFontEngineQPF2::stringToCMap(const QChar *str, int len, QGlyphLayout *glyp #if 0 && defined(DEBUG_FONTENGINE) QChar c(uc); if (!findGlyph(glyphs[glyph_pos].glyph) && !seenGlyphs.contains(c)) - qDebug() << "glyph for character" << c << '/' << hex << uc << "is" << dec << glyphs[glyph_pos].glyph; + qDebug() << "glyph for character" << c << '/' << Qt::hex << uc << "is" << Qt::dec << glyphs[glyph_pos].glyph; seenGlyphs.insert(c); #endif diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h index 61931fa9bc..a92ee9ed2f 100644 --- a/src/gui/text/qfontmetrics.h +++ b/src/gui/text/qfontmetrics.h @@ -76,12 +76,10 @@ public: ~QFontMetrics(); QFontMetrics &operator=(const QFontMetrics &); -#ifdef Q_COMPILER_RVALUE_REFS - inline QFontMetrics &operator=(QFontMetrics &&other) Q_DECL_NOEXCEPT + inline QFontMetrics &operator=(QFontMetrics &&other) noexcept { qSwap(d, other.d); return *this; } -#endif - void swap(QFontMetrics &other) Q_DECL_NOEXCEPT + void swap(QFontMetrics &other) noexcept { qSwap(d, other.d); } int ascent() const; @@ -172,10 +170,8 @@ public: QFontMetricsF &operator=(const QFontMetricsF &); QFontMetricsF &operator=(const QFontMetrics &); -#ifdef Q_COMPILER_RVALUE_REFS inline QFontMetricsF &operator=(QFontMetricsF &&other) { qSwap(d, other.d); return *this; } -#endif void swap(QFontMetricsF &other) { qSwap(d, other.d); } diff --git a/src/gui/text/qfragmentmap_p.h b/src/gui/text/qfragmentmap_p.h index 35f60ac961..1d781352f8 100644 --- a/src/gui/text/qfragmentmap_p.h +++ b/src/gui/text/qfragmentmap_p.h @@ -216,7 +216,7 @@ private: template <class Fragment> QFragmentMapData<Fragment>::QFragmentMapData() - : fragments(0) + : fragments(nullptr) { init(); } diff --git a/src/gui/text/qglyphrun.h b/src/gui/text/qglyphrun.h index 6182c4f749..15e315bea2 100644 --- a/src/gui/text/qglyphrun.h +++ b/src/gui/text/qglyphrun.h @@ -66,13 +66,11 @@ public: QGlyphRun(); QGlyphRun(const QGlyphRun &other); -#ifdef Q_COMPILER_RVALUE_REFS - QGlyphRun &operator=(QGlyphRun &&other) Q_DECL_NOTHROW { swap(other); return *this; } -#endif + QGlyphRun &operator=(QGlyphRun &&other) noexcept { swap(other); return *this; } QGlyphRun &operator=(const QGlyphRun &other); ~QGlyphRun(); - void swap(QGlyphRun &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + void swap(QGlyphRun &other) noexcept { qSwap(d, other.d); } QRawFont rawFont() const; void setRawFont(const QRawFont &rawFont); diff --git a/src/gui/text/qglyphrun_p.h b/src/gui/text/qglyphrun_p.h index 5b6bdad648..465c3c7000 100644 --- a/src/gui/text/qglyphrun_p.h +++ b/src/gui/text/qglyphrun_p.h @@ -65,7 +65,7 @@ class QGlyphRunPrivate: public QSharedData { public: QGlyphRunPrivate() - : flags(0) + : flags(nullptr) , glyphIndexData(glyphIndexes.constData()) , glyphIndexDataSize(0) , glyphPositionData(glyphPositions.constData()) diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp index b2d8bf01af..a060448924 100644 --- a/src/gui/text/qrawfont.cpp +++ b/src/gui/text/qrawfont.cpp @@ -322,7 +322,7 @@ bool QRawFont::operator==(const QRawFont &other) const \relates QRawFont \since 5.8 */ -uint qHash(const QRawFont &font, uint seed) Q_DECL_NOTHROW +uint qHash(const QRawFont &font, uint seed) noexcept { return qHash(QRawFontPrivate::get(font)->fontEngine, seed); } diff --git a/src/gui/text/qrawfont.h b/src/gui/text/qrawfont.h index 470f2694e4..c6289d6c93 100644 --- a/src/gui/text/qrawfont.h +++ b/src/gui/text/qrawfont.h @@ -79,13 +79,11 @@ public: qreal pixelSize, QFont::HintingPreference hintingPreference = QFont::PreferDefaultHinting); QRawFont(const QRawFont &other); -#ifdef Q_COMPILER_RVALUE_REFS - QRawFont &operator=(QRawFont &&other) Q_DECL_NOTHROW { swap(other); return *this; } -#endif + QRawFont &operator=(QRawFont &&other) noexcept { swap(other); return *this; } QRawFont &operator=(const QRawFont &other); ~QRawFont(); - void swap(QRawFont &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + void swap(QRawFont &other) noexcept { qSwap(d, other.d); } bool isValid() const; @@ -158,7 +156,7 @@ Q_DECLARE_SHARED(QRawFont) Q_DECLARE_OPERATORS_FOR_FLAGS(QRawFont::LayoutFlags) -Q_GUI_EXPORT uint qHash(const QRawFont &font, uint seed = 0) Q_DECL_NOTHROW; +Q_GUI_EXPORT uint qHash(const QRawFont &font, uint seed = 0) noexcept; inline QVector<QPointF> QRawFont::advancesForGlyphIndexes(const QVector<quint32> &glyphIndexes, QRawFont::LayoutFlags layoutFlags) const { diff --git a/src/gui/text/qrawfont_p.h b/src/gui/text/qrawfont_p.h index 0fc8739bfb..dced165475 100644 --- a/src/gui/text/qrawfont_p.h +++ b/src/gui/text/qrawfont_p.h @@ -67,9 +67,9 @@ class Q_GUI_EXPORT QRawFontPrivate { public: QRawFontPrivate() - : fontEngine(0) + : fontEngine(nullptr) , hintingPreference(QFont::PreferDefaultHinting) - , thread(0) + , thread(nullptr) {} QRawFontPrivate(const QRawFontPrivate &other) @@ -78,9 +78,9 @@ public: , thread(other.thread) { #ifndef QT_NO_DEBUG - Q_ASSERT(fontEngine == 0 || thread == QThread::currentThread()); + Q_ASSERT(fontEngine == nullptr || thread == QThread::currentThread()); #endif - if (fontEngine != 0) + if (fontEngine != nullptr) fontEngine->ref.ref(); } @@ -94,37 +94,37 @@ public: inline void cleanUp() { - setFontEngine(0); + setFontEngine(nullptr); hintingPreference = QFont::PreferDefaultHinting; } inline bool isValid() const { #ifndef QT_NO_DEBUG - Q_ASSERT(fontEngine == 0 || thread == QThread::currentThread()); + Q_ASSERT(fontEngine == nullptr || thread == QThread::currentThread()); #endif - return fontEngine != 0; + return fontEngine != nullptr; } inline void setFontEngine(QFontEngine *engine) { #ifndef QT_NO_DEBUG - Q_ASSERT(fontEngine == 0 || thread == QThread::currentThread()); + Q_ASSERT(fontEngine == nullptr || thread == QThread::currentThread()); #endif if (fontEngine == engine) return; - if (fontEngine != 0) { + if (fontEngine != nullptr) { if (!fontEngine->ref.deref()) delete fontEngine; #ifndef QT_NO_DEBUG - thread = 0; + thread = nullptr; #endif } fontEngine = engine; - if (fontEngine != 0) { + if (fontEngine != nullptr) { fontEngine->ref.ref(); #ifndef QT_NO_DEBUG thread = QThread::currentThread(); diff --git a/src/gui/text/qstatictext.h b/src/gui/text/qstatictext.h index ada0456b8f..e8c94a6add 100644 --- a/src/gui/text/qstatictext.h +++ b/src/gui/text/qstatictext.h @@ -64,13 +64,11 @@ public: QStaticText(); explicit QStaticText(const QString &text); QStaticText(const QStaticText &other); -#ifdef Q_COMPILER_RVALUE_REFS - QStaticText &operator=(QStaticText &&other) Q_DECL_NOTHROW { swap(other); return *this; } -#endif + QStaticText &operator=(QStaticText &&other) noexcept { swap(other); return *this; } QStaticText &operator=(const QStaticText &); ~QStaticText(); - void swap(QStaticText &other) Q_DECL_NOTHROW { qSwap(data, other.data); } + void swap(QStaticText &other) noexcept { qSwap(data, other.data); } void setText(const QString &text); QString text() const; diff --git a/src/gui/text/qstatictext_p.h b/src/gui/text/qstatictext_p.h index 4ec09297c5..8d6792216d 100644 --- a/src/gui/text/qstatictext_p.h +++ b/src/gui/text/qstatictext_p.h @@ -80,7 +80,7 @@ class Q_GUI_EXPORT QStaticTextItem public: QStaticTextItem() : useBackendOptimizations(false), userDataNeedsUpdate(0), usesRawFont(0), - m_fontEngine(0), m_userData(0) {} + m_fontEngine(nullptr), m_userData(nullptr) {} void setUserData(QStaticTextUserData *newUserData) { diff --git a/src/gui/text/qsyntaxhighlighter.cpp b/src/gui/text/qsyntaxhighlighter.cpp index 102a776ed3..cf584f6980 100644 --- a/src/gui/text/qsyntaxhighlighter.cpp +++ b/src/gui/text/qsyntaxhighlighter.cpp @@ -45,6 +45,7 @@ #include <private/qtextdocument_p.h> #include <qtextlayout.h> #include <qpointer.h> +#include <qscopedvaluerollback.h> #include <qtextobject.h> #include <qtextcursor.h> #include <qdebug.h> @@ -68,14 +69,14 @@ public: void reformatBlocks(int from, int charsRemoved, int charsAdded); void reformatBlock(const QTextBlock &block); - inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) { - inReformatBlocks = true; + inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) + { + QScopedValueRollback<bool> bg(inReformatBlocks, true); cursor.beginEditBlock(); int from = cursor.position(); cursor.movePosition(operation); reformatBlocks(from, 0, cursor.position() - from); cursor.endEditBlock(); - inReformatBlocks = false; } inline void _q_delayedRehighlight() { diff --git a/src/gui/text/qtextcursor.h b/src/gui/text/qtextcursor.h index 1a00b753ad..7cad3cc5e8 100644 --- a/src/gui/text/qtextcursor.h +++ b/src/gui/text/qtextcursor.h @@ -73,13 +73,11 @@ public: explicit QTextCursor(QTextFrame *frame); explicit QTextCursor(const QTextBlock &block); QTextCursor(const QTextCursor &cursor); -#ifdef Q_COMPILER_RVALUE_REFS - QTextCursor &operator=(QTextCursor &&other) Q_DECL_NOTHROW { swap(other); return *this; } -#endif + QTextCursor &operator=(QTextCursor &&other) noexcept { swap(other); return *this; } QTextCursor &operator=(const QTextCursor &other); ~QTextCursor(); - void swap(QTextCursor &other) Q_DECL_NOTHROW { qSwap(d, other.d); } + void swap(QTextCursor &other) noexcept { qSwap(d, other.d); } bool isNull() const; diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index fd3473b32e..b5ff72e706 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** 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. @@ -70,6 +70,12 @@ #include <private/qabstracttextdocumentlayout_p.h> #include "qpagedpaintdevice.h" #include "private/qpagedpaintdevice_p.h" +#if QT_CONFIG(textmarkdownreader) +#include <private/qtextmarkdownimporter_p.h> +#endif +#if QT_CONFIG(textmarkdownwriter) +#include <private/qtextmarkdownwriter_p.h> +#endif #include <limits.h> @@ -3286,6 +3292,58 @@ 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. +*/ +#if QT_CONFIG(textmarkdownwriter) +QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const +{ + QString ret; + QTextStream s(&ret); + QTextMarkdownWriter w(s, features); + if (w.writeAll(this)) + return ret; + return QString(); +} +#endif + +/*! + 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 + Markdown features are included; pass \c MarkdownDialectCommonMark + for a more basic parse. + + The Markdown formatting is respected as much as possible; for example, + "*bold* text" will produce text where the first word has a font weight that + gives it an emphasized appearance. + + Parsing of HTML included in the \a markdown string is handled in the same + way as in \l setHtml; however, Markdown formatting inside HTML blocks is + not supported. + + Some features of the parser can be enabled or disabled via the \a features + argument: + + \value MarkdownNoHTML + Any HTML tags in the Markdown text will be discarded + \value MarkdownDialectCommonMark + The parser supports only the features standardized by CommonMark + \value MarkdownDialectGitHub + The parser supports the GitHub dialect + + The default is \c MarkdownDialectGitHub. + + The undo/redo history is reset when this function is called. +*/ +#if QT_CONFIG(textmarkdownreader) +void QTextDocument::setMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features) +{ + QTextMarkdownImporter(static_cast<QTextMarkdownImporter::Features>(int(features))).import(this, markdown); +} +#endif + +/*! Returns a vector of text formats for all the formats used in the document. */ QVector<QTextFormat> QTextDocument::allFormats() const diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h index c9b22e053b..31c06976a5 100644 --- a/src/gui/text/qtextdocument.h +++ b/src/gui/text/qtextdocument.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** 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. @@ -151,6 +151,25 @@ public: void setHtml(const QString &html); #endif +#if QT_CONFIG(textmarkdownwriter) || QT_CONFIG(textmarkdownreader) + // Must be in sync with QTextMarkdownImporter::Features, should be in sync with #define MD_FLAG_* in md4c + enum MarkdownFeature { + MarkdownNoHTML = 0x0020 | 0x0040, + MarkdownDialectCommonMark = 0, + MarkdownDialectGitHub = 0x0004 | 0x0008 | 0x0400 | 0x0100 | 0x0200 | 0x0800 + }; + Q_DECLARE_FLAGS(MarkdownFeatures, MarkdownFeature) + Q_FLAG(MarkdownFeatures) +#endif + +#if QT_CONFIG(textmarkdownwriter) + QString toMarkdown(MarkdownFeatures features = MarkdownDialectGitHub) const; +#endif + +#if QT_CONFIG(textmarkdownreader) + void setMarkdown(const QString &markdown, MarkdownFeatures features = MarkdownDialectGitHub); +#endif + QString toRawText() const; QString toPlainText() const; void setPlainText(const QString &text); diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp index c0a0c1a177..0e3c8d0e83 100644 --- a/src/gui/text/qtextdocument_p.cpp +++ b/src/gui/text/qtextdocument_p.cpp @@ -40,6 +40,7 @@ #include <private/qtools_p.h> #include <qdebug.h> +#include <qscopedvaluerollback.h> #include "qtextdocument_p.h" #include "qtextdocument.h" #include <qtextformat.h> @@ -274,9 +275,10 @@ void QTextDocumentPrivate::clear() rtFrame = 0; init(); cursors = oldCursors; - inContentsChange = true; - emit q->contentsChange(0, len, 0); - inContentsChange = false; + { + QScopedValueRollback<bool> bg(inContentsChange, true); + emit q->contentsChange(0, len, 0); + } if (lout) lout->documentChanged(0, len, 0); } QT_CATCH(...) { @@ -309,9 +311,10 @@ void QTextDocumentPrivate::setLayout(QAbstractTextDocumentLayout *layout) it->free(); emit q->documentLayoutChanged(); - inContentsChange = true; - emit q->contentsChange(0, 0, length()); - inContentsChange = false; + { + QScopedValueRollback<bool> bg(inContentsChange, true); + emit q->contentsChange(0, 0, length()); + } if (lout) lout->documentChanged(0, 0, length()); } @@ -1213,9 +1216,8 @@ void QTextDocumentPrivate::finishEdit() if (lout && docChangeFrom >= 0) { if (!inContentsChange) { - inContentsChange = true; + QScopedValueRollback<bool> bg(inContentsChange, true); emit q->contentsChange(docChangeFrom, docChangeOldLength, docChangeLength); - inContentsChange = false; } lout->documentChanged(docChangeFrom, docChangeOldLength, docChangeLength); } diff --git a/src/gui/text/qtextdocument_p.h b/src/gui/text/qtextdocument_p.h index cad9131fbf..a8e17bfc08 100644 --- a/src/gui/text/qtextdocument_p.h +++ b/src/gui/text/qtextdocument_p.h @@ -101,10 +101,10 @@ class QTextBlockData : public QFragment<3> { public: inline void initialize() - { layout = 0; userData = 0; userState = -1; revision = 0; hidden = 0; } + { layout = nullptr; userData = nullptr; userState = -1; revision = 0; hidden = 0; } void invalidate() const; inline void free() - { delete layout; layout = 0; delete userData; userData = 0; } + { delete layout; layout = nullptr; delete userData; userData = nullptr; } mutable int format; // ##### probably store a QTextEngine * here! @@ -339,6 +339,7 @@ private: int lastBlockCount; public: + bool inContentsChange; QTextOption defaultTextOption; Qt::CursorMoveStyle defaultCursorMoveStyle; #ifndef QT_NO_CSSPARSER @@ -346,7 +347,6 @@ public: #endif int maximumBlockCount; uint needsEnsureMaximumBlockCount : 1; - uint inContentsChange : 1; uint blockCursorAdjustment : 1; QSizeF pageSize; QString title; diff --git a/src/gui/text/qtextdocumentfragment_p.h b/src/gui/text/qtextdocumentfragment_p.h index de01a02fbb..67b0c2c600 100644 --- a/src/gui/text/qtextdocumentfragment_p.h +++ b/src/gui/text/qtextdocumentfragment_p.h @@ -125,7 +125,7 @@ public: QTextHtmlImporter(QTextDocument *_doc, const QString &html, ImportMode mode, - const QTextDocument *resourceProvider = 0); + const QTextDocument *resourceProvider = nullptr); void import(); @@ -163,7 +163,7 @@ private: #endif struct TableCellIterator { - inline TableCellIterator(QTextTable *t = 0) : table(t), row(0), column(0) {} + inline TableCellIterator(QTextTable *t = nullptr) : table(t), row(0), column(0) {} inline TableCellIterator &operator++() { if (atEnd()) @@ -182,7 +182,7 @@ private: return *this; } - inline bool atEnd() const { return table == 0 || row >= table->rows(); } + inline bool atEnd() const { return table == nullptr || row >= table->rows(); } QTextTableCell cell() const { return table->cellAt(row, column); } diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp index bed0a2c450..7873faf2cb 100644 --- a/src/gui/text/qtextdocumentlayout.cpp +++ b/src/gui/text/qtextdocumentlayout.cpp @@ -1436,6 +1436,21 @@ void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *p QBrush brush = context.palette.brush(QPalette::Text); + bool marker = bl.blockFormat().marker() != QTextBlockFormat::NoMarker; + if (marker) { + int adj = fontMetrics.lineSpacing() / 6; + r.adjust(-adj, 0, -adj, 0); + if (bl.blockFormat().marker() == QTextBlockFormat::Checked) { + // ### Qt6: render with QStyle / PE_IndicatorCheckBox. We don't currently + // have access to that here, because it would be a widget dependency. + painter->setPen(QPen(painter->pen().color(), 2)); + painter->drawLine(r.topLeft(), r.bottomRight()); + painter->drawLine(r.topRight(), r.bottomLeft()); + painter->setPen(QPen(painter->pen().color(), 0)); + } + painter->drawRect(r.adjusted(-adj, -adj, adj, adj)); + } + switch (style) { case QTextListFormat::ListDecimal: case QTextListFormat::ListLowerAlpha: @@ -1456,16 +1471,21 @@ void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *p break; } case QTextListFormat::ListSquare: - painter->fillRect(r, brush); + if (!marker) + painter->fillRect(r, brush); break; case QTextListFormat::ListCircle: - painter->setPen(QPen(brush, 0)); - painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering + if (!marker) { + painter->setPen(QPen(brush, 0)); + painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering + } break; case QTextListFormat::ListDisc: - painter->setBrush(brush); - painter->setPen(Qt::NoPen); - painter->drawEllipse(r); + if (!marker) { + painter->setBrush(brush); + painter->setPen(Qt::NoPen); + painter->drawEllipse(r); + } break; case QTextListFormat::ListStyleUndefined: break; diff --git a/src/gui/text/qtextdocumentwriter.cpp b/src/gui/text/qtextdocumentwriter.cpp index 42e623153a..193d2c0dd3 100644 --- a/src/gui/text/qtextdocumentwriter.cpp +++ b/src/gui/text/qtextdocumentwriter.cpp @@ -51,6 +51,9 @@ #include "qtextdocumentfragment_p.h" #include "qtextodfwriter_p.h" +#if QT_CONFIG(textmarkdownwriter) +#include "qtextmarkdownwriter_p.h" +#endif #include <algorithm> @@ -267,6 +270,18 @@ bool QTextDocumentWriter::write(const QTextDocument *document) } #endif // QT_NO_TEXTODFWRITER +#if QT_CONFIG(textmarkdownwriter) + if (format == "md" || format == "mkd" || format == "markdown") { + if (!d->device->isWritable() && !d->device->open(QIODevice::WriteOnly)) { + qWarning("QTextDocumentWriter::write: the device can not be opened for writing"); + return false; + } + QTextStream s(d->device); + QTextMarkdownWriter writer(s, QTextDocument::MarkdownDialectGitHub); + return writer.writeAll(document); + } +#endif // textmarkdownwriter + #ifndef QT_NO_TEXTHTMLPARSER if (format == "html" || format == "htm") { if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) { @@ -348,6 +363,7 @@ QTextCodec *QTextDocumentWriter::codec() const \header \li Format \li Description \row \li plaintext \li Plain text \row \li HTML \li HyperText Markup Language + \row \li markdown \li Markdown (CommonMark or GitHub dialects) \row \li ODF \li OpenDocument Format \endtable @@ -364,6 +380,9 @@ QList<QByteArray> QTextDocumentWriter::supportedDocumentFormats() #ifndef QT_NO_TEXTODFWRITER answer << "ODF"; #endif // QT_NO_TEXTODFWRITER +#if QT_CONFIG(textmarkdownwriter) + answer << "markdown"; +#endif std::sort(answer.begin(), answer.end()); return answer; diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 2da13289bf..c267ade0c2 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -990,7 +990,7 @@ struct QBidiAlgorithm { BIDI_DEBUG() << "before implicit level processing:"; IsolatedRunSequenceIterator it(runs, i); while (!it.atEnd()) { - BIDI_DEBUG() << " " << *it << hex << text[*it].unicode() << analysis[*it].bidiDirection; + BIDI_DEBUG() << " " << *it << Qt::hex << text[*it].unicode() << analysis[*it].bidiDirection; ++it; } } @@ -1003,7 +1003,7 @@ struct QBidiAlgorithm { BIDI_DEBUG() << "after W4/W5"; IsolatedRunSequenceIterator it(runs, i); while (!it.atEnd()) { - BIDI_DEBUG() << " " << *it << hex << text[*it].unicode() << analysis[*it].bidiDirection; + BIDI_DEBUG() << " " << *it << Qt::hex << text[*it].unicode() << analysis[*it].bidiDirection; ++it; } } @@ -1089,7 +1089,7 @@ struct QBidiAlgorithm { if (BidiDebugEnabled) { BIDI_DEBUG() << ">>>> start bidi, text length" << length; for (int i = 0; i < length; ++i) - BIDI_DEBUG() << hex << " (" << i << ")" << text[i].unicode() << text[i].direction(); + BIDI_DEBUG() << Qt::hex << " (" << i << ")" << text[i].unicode() << text[i].direction(); } { @@ -1158,7 +1158,7 @@ struct QBidiAlgorithm { if (BidiDebugEnabled) { BIDI_DEBUG() << "final resolved levels:"; for (int i = 0; i < length; ++i) - BIDI_DEBUG() << " " << i << hex << text[i].unicode() << dec << (int)analysis[i].bidiLevel; + BIDI_DEBUG() << " " << i << Qt::hex << text[i].unicode() << Qt::dec << (int)analysis[i].bidiLevel; } return true; @@ -1969,7 +1969,9 @@ const QCharAttributes *QTextEngine::attributes() const QUnicodeTools::initCharAttributes(reinterpret_cast<const ushort *>(layoutData->string.constData()), layoutData->string.length(), scriptItems.data(), scriptItems.size(), - (QCharAttributes *)layoutData->memory); + (QCharAttributes *)layoutData->memory, + QUnicodeTools::CharAttributeOptions(QUnicodeTools::DefaultOptionsCompat + | QUnicodeTools::HangulLineBreakTailoring)); layoutData->haveCharAttributes = true; diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h index e9187ea605..fddda7f2f8 100644 --- a/src/gui/text/qtextengine_p.h +++ b/src/gui/text/qtextengine_p.h @@ -304,8 +304,8 @@ class QTextItemInt : public QTextItem { public: inline QTextItemInt() - : justified(false), underlineStyle(QTextCharFormat::NoUnderline), num_chars(0), chars(0), - logClusters(0), f(0), fontEngine(0) + : justified(false), underlineStyle(QTextCharFormat::NoUnderline), num_chars(0), chars(nullptr), + logClusters(nullptr), f(nullptr), fontEngine(nullptr) {} QTextItemInt(const QScriptItem &si, QFont *font, const QTextCharFormat &format = QTextCharFormat()); QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars, int numChars, QFontEngine *fe, @@ -335,7 +335,7 @@ public: struct QScriptItem { - Q_DECL_CONSTEXPR QScriptItem(int p, QScriptAnalysis a) Q_DECL_NOTHROW + Q_DECL_CONSTEXPR QScriptItem(int p, QScriptAnalysis a) noexcept : position(p), analysis(a), num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), glyph_data_offset(0) {} @@ -348,7 +348,7 @@ struct QScriptItem QFixed leading; QFixed width; int glyph_data_offset; - Q_DECL_CONSTEXPR QFixed height() const Q_DECL_NOTHROW { return ascent + descent; } + Q_DECL_CONSTEXPR QFixed height() const noexcept { return ascent + descent; } private: friend class QVector<QScriptItem>; QScriptItem() {}; // for QVector, don't use @@ -484,7 +484,7 @@ public: return end - si->position; } - QFontEngine *fontEngine(const QScriptItem &si, QFixed *ascent = 0, QFixed *descent = 0, QFixed *leading = 0) const; + QFontEngine *fontEngine(const QScriptItem &si, QFixed *ascent = nullptr, QFixed *descent = nullptr, QFixed *leading = nullptr) const; QFont font(const QScriptItem &si) const; inline QFont font() const { return fnt; } @@ -530,7 +530,7 @@ public: inline QTextFormatCollection *formatCollection() const { if (block.docHandle()) return block.docHandle()->formatCollection(); - return specialData ? specialData->formatCollection.data() : 0; + return specialData ? specialData->formatCollection.data() : nullptr; } QTextCharFormat format(const QScriptItem *si) const; inline QAbstractTextDocumentLayout *docLayout() const { @@ -553,8 +553,8 @@ private: mutable int prevPosition; mutable int prevLength; inline void reset() { - prevFontEngine = 0; - prevScaledFontEngine = 0; + prevFontEngine = nullptr; + prevScaledFontEngine = nullptr; prevScript = -1; prevPosition = -1; prevLength = -1; @@ -684,7 +684,7 @@ Q_DECLARE_TYPEINFO(QTextEngine::ItemDecoration, Q_MOVABLE_TYPE); struct QTextLineItemIterator { QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(), - const QTextLayout::FormatRange *_selection = 0); + const QTextLayout::FormatRange *_selection = nullptr); inline bool atEnd() const { return logicalItem >= nItems - 1; } inline bool atBeginning() const { return logicalItem <= 0; } diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h index 80d8e82694..a631309ae0 100644 --- a/src/gui/text/qtextformat.h +++ b/src/gui/text/qtextformat.h @@ -176,6 +176,9 @@ public: BlockNonBreakableLines = 0x1050, BlockTrailingHorizontalRulerWidth = 0x1060, HeadingLevel = 0x1070, + BlockQuoteLevel = 0x1080, + BlockCodeLanguage = 0x1090, + BlockMarker = 0x10A0, // character properties FirstFontProperty = 0x1FE0, @@ -605,6 +608,12 @@ public: LineDistanceHeight = 4 }; + enum MarkerType { + NoMarker = 0, + Unchecked = 1, + Checked = 2 + }; + QTextBlockFormat(); bool isValid() const { return isBlockFormat(); } @@ -668,6 +677,11 @@ public: void setTabPositions(const QList<QTextOption::Tab> &tabs); QList<QTextOption::Tab> tabPositions() const; + inline void setMarker(MarkerType marker) + { setProperty(BlockMarker, int(marker)); } + inline MarkerType marker() const + { return MarkerType(intProperty(BlockMarker)); } + protected: explicit QTextBlockFormat(const QTextFormat &fmt); friend class QTextFormat; diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index 895232e4c7..37051502fa 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -1125,6 +1125,7 @@ void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent margin[QTextHtmlParser::MarginBottom] = 12; margin[QTextHtmlParser::MarginLeft] = 40; margin[QTextHtmlParser::MarginRight] = 40; + blockFormat.setProperty(QTextFormat::BlockQuoteLevel, 1); break; case Html_dl: margin[QTextHtmlParser::MarginTop] = 8; diff --git a/src/gui/text/qtextimagehandler_p.h b/src/gui/text/qtextimagehandler_p.h index 339ef0af4f..fafd394ad3 100644 --- a/src/gui/text/qtextimagehandler_p.h +++ b/src/gui/text/qtextimagehandler_p.h @@ -65,7 +65,7 @@ class Q_GUI_EXPORT QTextImageHandler : public QObject, Q_OBJECT Q_INTERFACES(QTextObjectInterface) public: - explicit QTextImageHandler(QObject *parent = 0); + explicit QTextImageHandler(QObject *parent = nullptr); virtual QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) override; virtual void drawObject(QPainter *p, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) override; diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp new file mode 100644 index 0000000000..d8ffec2496 --- /dev/null +++ b/src/gui/text/qtextmarkdownimporter.cpp @@ -0,0 +1,542 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtextmarkdownimporter_p.h" +#include "qtextdocumentfragment_p.h" +#include <QLoggingCategory> +#include <QRegularExpression> +#include <QTextCursor> +#include <QTextDocument> +#include <QTextDocumentFragment> +#include <QTextList> +#include <QTextTable> +#include "../../3rdparty/md4c/md4c.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcMD, "qt.text.markdown") + +static const QChar Newline = QLatin1Char('\n'); +static const QChar Space = QLatin1Char(' '); + +// TODO maybe eliminate the margins after all views recognize BlockQuoteLevel, CSS can format it, etc. +static const int BlockQuoteIndent = 40; // pixels, same as in QTextHtmlParserNode::initializeProperties + +// -------------------------------------------------------- +// MD4C callback function wrappers + +static int CbEnterBlock(MD_BLOCKTYPE type, void *detail, void *userdata) +{ + QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata); + return mdi->cbEnterBlock(int(type), detail); +} + +static int CbLeaveBlock(MD_BLOCKTYPE type, void *detail, void *userdata) +{ + QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata); + return mdi->cbLeaveBlock(int(type), detail); +} + +static int CbEnterSpan(MD_SPANTYPE type, void *detail, void *userdata) +{ + QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata); + return mdi->cbEnterSpan(int(type), detail); +} + +static int CbLeaveSpan(MD_SPANTYPE type, void *detail, void *userdata) +{ + QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata); + return mdi->cbLeaveSpan(int(type), detail); +} + +static int CbText(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata) +{ + QTextMarkdownImporter *mdi = static_cast<QTextMarkdownImporter *>(userdata); + return mdi->cbText(int(type), text, size); +} + +static void CbDebugLog(const char *msg, void *userdata) +{ + Q_UNUSED(userdata) + qCDebug(lcMD) << msg; +} + +// MD4C callback function wrappers +// -------------------------------------------------------- + +static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter) +{ + switch (a) { + case MD_ALIGN_LEFT: + return Qt::AlignLeft | Qt::AlignVCenter; + case MD_ALIGN_CENTER: + return Qt::AlignHCenter | Qt::AlignVCenter; + case MD_ALIGN_RIGHT: + return Qt::AlignRight | Qt::AlignVCenter; + default: // including MD_ALIGN_DEFAULT + return defaultAlignment; + } +} + +QTextMarkdownImporter::QTextMarkdownImporter(QTextMarkdownImporter::Features features) + : m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)) + , m_features(features) +{ +} + +void QTextMarkdownImporter::import(QTextDocument *doc, const QString &markdown) +{ + MD_PARSER callbacks = { + 0, // abi_version + unsigned(m_features), + &CbEnterBlock, + &CbLeaveBlock, + &CbEnterSpan, + &CbLeaveSpan, + &CbText, + &CbDebugLog, + nullptr // syntax + }; + m_doc = doc; + m_paragraphMargin = m_doc->defaultFont().pointSize() * 2 / 3; + m_cursor = new QTextCursor(doc); + doc->clear(); + qCDebug(lcMD) << "default font" << doc->defaultFont() << "mono font" << m_monoFont; + QByteArray md = markdown.toUtf8(); + md_parse(md.constData(), MD_SIZE(md.size()), &callbacks, this); + delete m_cursor; + m_cursor = nullptr; +} + +int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det) +{ + m_blockType = blockType; + switch (blockType) { + case MD_BLOCK_P: + if (m_listStack.isEmpty()) { + m_needsInsertBlock = true; + 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; + } + } + break; + 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; + m_blockCodeLanguage = QLatin1String(detail->lang.text, int(detail->lang.size)); + QString info = QLatin1String(detail->info.text, int(detail->info.size)); + m_needsInsertBlock = true; + if (m_blockQuoteDepth) + qCDebug(lcMD, "CODE lang '%s' info '%s' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockQuoteDepth); + else + qCDebug(lcMD, "CODE lang '%s' info '%s'", qPrintable(m_blockCodeLanguage), qPrintable(info)); + } break; + case MD_BLOCK_H: { + MD_BLOCK_H_DETAIL *detail = static_cast<MD_BLOCK_H_DETAIL *>(det); + QTextBlockFormat blockFmt; + QTextCharFormat charFmt; + int sizeAdjustment = 4 - int(detail->level); // H1 to H6: +3 to -2 + charFmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment); + charFmt.setFontWeight(QFont::Bold); + blockFmt.setHeadingLevel(int(detail->level)); + m_needsInsertBlock = false; + m_cursor->insertBlock(blockFmt, charFmt); + 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_listItem = true; + m_emptyListItem = true; + } 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); + switch (detail->mark) { + case '*': + fmt.setStyle(QTextListFormat::ListCircle); + break; + case '+': + fmt.setStyle(QTextListFormat::ListSquare); + break; + default: // including '-' + fmt.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; + } 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); + qCDebug(lcMD, "OL xx%d level %d", detail->mark_delimiter, m_listStack.count()); + m_listStack.push(m_cursor->insertList(fmt)); + m_emptyList = true; + } break; + case MD_BLOCK_TD: { + MD_BLOCK_TD_DETAIL *detail = static_cast<MD_BLOCK_TD_DETAIL *>(det); + ++m_tableCol; + // absolute movement (and storage of m_tableCol) shouldn't be necessary, but + // movePosition(QTextCursor::NextCell) doesn't work + QTextTableCell cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol); + if (!cell.isValid()) { + qWarning("malformed table in Markdown input"); + return 1; + } + *m_cursor = cell.firstCursorPosition(); + QTextBlockFormat blockFmt = m_cursor->blockFormat(); + blockFmt.setAlignment(MdAlignment(detail->align)); + m_cursor->setBlockFormat(blockFmt); + qCDebug(lcMD) << "TD; align" << detail->align << MdAlignment(detail->align) << "col" << m_tableCol; + } break; + case MD_BLOCK_TH: { + ++m_tableColumnCount; + ++m_tableCol; + if (m_currentTable->columns() < m_tableColumnCount) + m_currentTable->appendColumns(1); + auto cell = m_currentTable->cellAt(m_tableRowCount - 1, m_tableCol); + if (!cell.isValid()) { + qWarning("malformed table in Markdown input"); + return 1; + } + auto fmt = cell.format(); + fmt.setFontWeight(QFont::Bold); + cell.setFormat(fmt); + } break; + case MD_BLOCK_TR: { + ++m_tableRowCount; + m_nonEmptyTableCells.clear(); + if (m_currentTable->rows() < m_tableRowCount) + m_currentTable->appendRows(1); + m_tableCol = -1; + qCDebug(lcMD) << "TR" << m_currentTable->rows(); + } break; + case MD_BLOCK_TABLE: + m_tableColumnCount = 0; + m_tableRowCount = 0; + m_currentTable = m_cursor->insertTable(1, 1); // we don't know the dimensions yet + break; + case MD_BLOCK_HR: { + qCDebug(lcMD, "HR"); + QTextBlockFormat blockFmt; + blockFmt.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, 1); + m_cursor->insertBlock(blockFmt, QTextCharFormat()); + } break; + default: + break; // nothing to do for now + } + return 0; // no error +} + +int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail) +{ + Q_UNUSED(detail) + switch (blockType) { + case MD_BLOCK_UL: + case MD_BLOCK_OL: + qCDebug(lcMD, "list at level %d ended", m_listStack.count()); + m_listStack.pop(); + break; + case MD_BLOCK_TR: { + // https://github.com/mity/md4c/issues/29 + // MD4C doesn't tell us explicitly which cells are merged, so merge empty cells + // with previous non-empty ones + int mergeEnd = -1; + int mergeBegin = -1; + for (int col = m_tableCol; col >= 0; --col) { + if (m_nonEmptyTableCells.contains(col)) { + if (mergeEnd >= 0 && mergeBegin >= 0) { + qCDebug(lcMD) << "merging cells" << mergeBegin << "to" << mergeEnd << "inclusive, on row" << m_currentTable->rows() - 1; + m_currentTable->mergeCells(m_currentTable->rows() - 1, mergeBegin - 1, 1, mergeEnd - mergeBegin + 2); + } + mergeEnd = -1; + mergeBegin = -1; + } else { + if (mergeEnd < 0) + mergeEnd = col; + else + mergeBegin = col; + } + } + } break; + case MD_BLOCK_QUOTE: { + qCDebug(lcMD, "QUOTE level %d ended", m_blockQuoteDepth); + --m_blockQuoteDepth; + m_needsInsertBlock = true; + } break; + case MD_BLOCK_TABLE: + qCDebug(lcMD) << "table ended with" << m_currentTable->columns() << "cols and" << m_currentTable->rows() << "rows"; + m_currentTable = nullptr; + m_cursor->movePosition(QTextCursor::End); + break; + case MD_BLOCK_LI: + qCDebug(lcMD, "LI at level %d ended", m_listStack.count()); + m_listItem = false; + break; + case MD_BLOCK_CODE: { + m_codeBlock = false; + m_blockCodeLanguage.clear(); + if (m_blockQuoteDepth) + qCDebug(lcMD, "CODE ended inside QUOTE %d", m_blockQuoteDepth); + else + qCDebug(lcMD, "CODE ended"); + m_needsInsertBlock = true; + } break; + case MD_BLOCK_H: + m_cursor->setCharFormat(QTextCharFormat()); + break; + default: + break; + } + return 0; // no error +} + +int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det) +{ + QTextCharFormat charFmt; + switch (spanType) { + case MD_SPAN_EM: + charFmt.setFontItalic(true); + break; + case MD_SPAN_STRONG: + charFmt.setFontWeight(QFont::Bold); + break; + case MD_SPAN_A: { + MD_SPAN_A_DETAIL *detail = static_cast<MD_SPAN_A_DETAIL *>(det); + QString url = QString::fromLatin1(detail->href.text, int(detail->href.size)); + QString title = QString::fromLatin1(detail->title.text, int(detail->title.size)); + charFmt.setAnchorHref(url); + charFmt.setAnchorNames(QStringList(title)); + charFmt.setForeground(m_palette.link()); + qCDebug(lcMD) << "anchor" << url << title; + } break; + case MD_SPAN_IMG: { + m_imageSpan = true; + 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); + break; + } + case MD_SPAN_CODE: + charFmt.setFont(m_monoFont); + break; + case MD_SPAN_DEL: + charFmt.setFontStrikeOut(true); + break; + } + m_spanFormatStack.push(charFmt); + qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().family() << charFmt.fontWeight() + << (charFmt.fontItalic() ? "italic" : "") << charFmt.foreground().color().name(); + m_cursor->setCharFormat(charFmt); + return 0; // no error +} + +int QTextMarkdownImporter::cbLeaveSpan(int spanType, void *detail) +{ + Q_UNUSED(detail) + QTextCharFormat charFmt; + if (!m_spanFormatStack.isEmpty()) { + m_spanFormatStack.pop(); + if (!m_spanFormatStack.isEmpty()) + charFmt = m_spanFormatStack.top(); + } + m_cursor->setCharFormat(charFmt); + qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().family() << charFmt.fontWeight() + << (charFmt.fontItalic() ? "italic" : "") << charFmt.foreground().color().name(); + if (spanType == int(MD_SPAN_IMG)) + m_imageSpan = false; + return 0; // no error +} + +int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size) +{ + if (m_imageSpan) + return 0; // it's the alt-text + if (m_needsInsertBlock) + insertBlock(); + static const QRegularExpression openingBracket(QStringLiteral("<[a-zA-Z]")); + static const QRegularExpression closingBracket(QStringLiteral("(/>|</)")); + QString s = QString::fromUtf8(text, int(size)); + + switch (textType) { + case MD_TEXT_NORMAL: + if (m_htmlTagDepth) { + m_htmlAccumulator += s; + s = QString(); + } + break; + case MD_TEXT_NULLCHAR: + s = QString(QChar(0xFFFD)); // CommonMark-required replacement for null + break; + case MD_TEXT_BR: + s = QString(Newline); + break; + case MD_TEXT_SOFTBR: + s = QString(Space); + break; + case MD_TEXT_CODE: + // We'll see MD_SPAN_CODE too, which will set the char format, and that's enough. + break; + case MD_TEXT_ENTITY: + m_cursor->insertHtml(s); + s = QString(); + break; + case MD_TEXT_HTML: + // count how many tags are opened and how many are closed + { + int startIdx = 0; + while ((startIdx = s.indexOf(openingBracket, startIdx)) >= 0) { + ++m_htmlTagDepth; + startIdx += 2; + } + startIdx = 0; + while ((startIdx = s.indexOf(closingBracket, startIdx)) >= 0) { + --m_htmlTagDepth; + startIdx += 2; + } + } + m_htmlAccumulator += s; + s = QString(); + if (!m_htmlTagDepth) { // all open tags are now closed + qCDebug(lcMD) << "HTML" << m_htmlAccumulator; + m_cursor->insertHtml(m_htmlAccumulator); + if (m_spanFormatStack.isEmpty()) + m_cursor->setCharFormat(QTextCharFormat()); + else + m_cursor->setCharFormat(m_spanFormatStack.top()); + m_htmlAccumulator = QString(); + } + break; + } + + switch (m_blockType) { + case MD_BLOCK_TD: + m_nonEmptyTableCells.append(m_tableCol); + break; + default: + break; + } + + if (!s.isEmpty()) + m_cursor->insertText(s); + if (m_cursor->currentList()) { + // The list item will indent the list item's text, so we don't need indentation on the block. + QTextBlockFormat bfmt = m_cursor->blockFormat(); + bfmt.setIndent(0); + m_cursor->setBlockFormat(bfmt); + } + if (lcMD().isEnabled(QtDebugMsg)) { + QTextBlockFormat bfmt = m_cursor->blockFormat(); + QString debugInfo; + if (m_cursor->currentList()) + debugInfo = QLatin1String("in list at depth ") + QString::number(m_cursor->currentList()->format().indent()); + if (bfmt.hasProperty(QTextFormat::BlockQuoteLevel)) + debugInfo += QLatin1String("in blockquote at depth ") + + QString::number(bfmt.intProperty(QTextFormat::BlockQuoteLevel)); + if (bfmt.hasProperty(QTextFormat::BlockCodeLanguage)) + debugInfo += QLatin1String("in a code block"); + qCDebug(lcMD) << textType << "in block" << m_blockType << s << qPrintable(debugInfo) + << "bindent" << bfmt.indent() << "tindent" << bfmt.textIndent() + << "margins" << bfmt.leftMargin() << bfmt.topMargin() << bfmt.bottomMargin() << bfmt.rightMargin(); + } + qCDebug(lcMD) << textType << "in block" << m_blockType << s << "in list?" << m_cursor->currentList() + << "indent" << m_cursor->blockFormat().indent(); + return 0; // no error +} + +void QTextMarkdownImporter::insertBlock() +{ + QTextCharFormat charFormat; + if (!m_spanFormatStack.isEmpty()) + charFormat = m_spanFormatStack.top(); + QTextBlockFormat 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); + } else { + blockFormat.setTopMargin(m_paragraphMargin); + blockFormat.setBottomMargin(m_paragraphMargin); + } + m_cursor->insertBlock(blockFormat, charFormat); + m_needsInsertBlock = false; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextmarkdownimporter_p.h b/src/gui/text/qtextmarkdownimporter_p.h new file mode 100644 index 0000000000..1716530b1d --- /dev/null +++ b/src/gui/text/qtextmarkdownimporter_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTEXTMARKDOWNIMPORTER_H +#define QTEXTMARKDOWNIMPORTER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qfont.h> +#include <QtGui/qtguiglobal.h> +#include <QtGui/qpalette.h> +#include <QtGui/qtextlist.h> +#include <QtCore/qstack.h> + +QT_BEGIN_NAMESPACE + +class QTextCursor; +class QTextDocument; +class QTextTable; + +class Q_GUI_EXPORT QTextMarkdownImporter +{ +public: + enum Feature { + // Must be kept in sync with MD_FLAG_* in md4c.h + FeatureCollapseWhitespace = 0x0001, // MD_FLAG_COLLAPSEWHITESPACE + FeaturePermissiveATXHeaders = 0x0002, // MD_FLAG_PERMISSIVEATXHEADERS + FeaturePermissiveURLAutoLinks = 0x0004, // MD_FLAG_PERMISSIVEURLAUTOLINKS + FeaturePermissiveMailAutoLinks = 0x0008, // MD_FLAG_PERMISSIVEEMAILAUTOLINKS + FeatureNoIndentedCodeBlocks = 0x0010, // MD_FLAG_NOINDENTEDCODEBLOCKS + FeatureNoHTMLBlocks = 0x0020, // MD_FLAG_NOHTMLBLOCKS + FeatureNoHTMLSpans = 0x0040, // MD_FLAG_NOHTMLSPANS + FeatureTables = 0x0100, // MD_FLAG_TABLES + FeatureStrikeThrough = 0x0200, // MD_FLAG_STRIKETHROUGH + FeaturePermissiveWWWAutoLinks = 0x0400, // MD_FLAG_PERMISSIVEWWWAUTOLINKS + FeatureTasklists = 0x0800, // MD_FLAG_TASKLISTS + // composite flags + FeaturePermissiveAutoLinks = FeaturePermissiveMailAutoLinks | FeaturePermissiveURLAutoLinks | FeaturePermissiveWWWAutoLinks, // MD_FLAG_PERMISSIVEAUTOLINKS + FeatureNoHTML = FeatureNoHTMLBlocks | FeatureNoHTMLSpans, // MD_FLAG_NOHTML + DialectCommonMark = 0, // MD_DIALECT_COMMONMARK + DialectGitHub = FeaturePermissiveAutoLinks | FeatureTables | FeatureStrikeThrough | FeatureTasklists // MD_DIALECT_GITHUB + }; + Q_DECLARE_FLAGS(Features, Feature) + + QTextMarkdownImporter(Features features); + + void import(QTextDocument *doc, const QString &markdown); + +public: + // MD4C callbacks + int cbEnterBlock(int blockType, void* detail); + int cbLeaveBlock(int blockType, void* detail); + int cbEnterSpan(int spanType, void* detail); + int cbLeaveSpan(int spanType, void* detail); + int cbText(int textType, const char* text, unsigned size); + +private: + void insertBlock(); + +private: + QTextDocument *m_doc = nullptr; + QTextCursor *m_cursor = nullptr; + QTextTable *m_currentTable = nullptr; // because m_cursor->currentTable() doesn't work + QString m_htmlAccumulator; + 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; + int m_htmlTagDepth = 0; + 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; + bool m_codeBlock = false; + bool m_imageSpan = false; + bool m_needsInsertBlock = false; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextMarkdownImporter::Features) + +QT_END_NAMESPACE + +#endif // QTEXTMARKDOWNIMPORTER_H diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp new file mode 100644 index 0000000000..f180098db2 --- /dev/null +++ b/src/gui/text/qtextmarkdownwriter.cpp @@ -0,0 +1,484 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtextmarkdownwriter_p.h" +#include "qtextdocumentlayout_p.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qtextdocument_p.h" +#include "qtextlist.h" +#include "qtexttable.h" +#include "qtextcursor.h" +#include "qtextimagehandler_p.h" +#include "qloggingcategory.h" + +QT_BEGIN_NAMESPACE + +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 Backtick = QLatin1Char('`'); +static const QChar Period = QLatin1Char('.'); + +QTextMarkdownWriter::QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features) + : m_stream(stream), m_features(features) +{ +} + +bool QTextMarkdownWriter::writeAll(const QTextDocument *document) +{ + writeFrame(document->rootFrame()); + return true; +} + +void QTextMarkdownWriter::writeTable(const QAbstractItemModel *table) +{ + QVector<int> tableColumnWidths(table->columnCount()); + for (int col = 0; col < table->columnCount(); ++col) { + tableColumnWidths[col] = table->headerData(col, Qt::Horizontal).toString().length(); + for (int row = 0; row < table->rowCount(); ++row) { + tableColumnWidths[col] = qMax(tableColumnWidths[col], + table->data(table->index(row, col)).toString().length()); + } + } + + // write the header and separator + for (int col = 0; col < table->columnCount(); ++col) { + QString s = table->headerData(col, Qt::Horizontal).toString(); + m_stream << "|" << s << QString(tableColumnWidths[col] - s.length(), Space); + } + m_stream << "|" << Qt::endl; + for (int col = 0; col < tableColumnWidths.length(); ++col) + m_stream << '|' << QString(tableColumnWidths[col], QLatin1Char('-')); + m_stream << '|'<< Qt::endl; + + // write the body + for (int row = 0; row < table->rowCount(); ++row) { + for (int col = 0; col < table->columnCount(); ++col) { + QString s = table->data(table->index(row, col)).toString(); + m_stream << "|" << s << QString(tableColumnWidths[col] - s.length(), Space); + } + m_stream << '|'<< Qt::endl; + } + m_listInfo.clear(); +} + +void QTextMarkdownWriter::writeFrame(const QTextFrame *frame) +{ + Q_ASSERT(frame); + const QTextTable *table = qobject_cast<const QTextTable*> (frame); + QTextFrame::iterator iterator = frame->begin(); + QTextFrame *child = nullptr; + int tableRow = -1; + bool lastWasList = false; + QVector<int> tableColumnWidths; + if (table) { + tableColumnWidths.resize(table->columns()); + for (int col = 0; col < table->columns(); ++col) { + for (int row = 0; row < table->rows(); ++ row) { + QTextTableCell cell = table->cellAt(row, col); + int cellTextLen = 0; + auto it = cell.begin(); + while (it != cell.end()) { + QTextBlock block = it.currentBlock(); + if (block.isValid()) + cellTextLen += block.text().length(); + ++it; + } + if (cell.columnSpan() == 1 && tableColumnWidths[col] < cellTextLen) + tableColumnWidths[col] = cellTextLen; + } + } + } + while (!iterator.atEnd()) { + if (iterator.currentFrame() && child != iterator.currentFrame()) + writeFrame(iterator.currentFrame()); + else { // no frame, it's a block + QTextBlock block = iterator.currentBlock(); + if (table) { + QTextTableCell cell = table->cellAt(block.position()); + if (tableRow < cell.row()) { + if (tableRow == 0) { + m_stream << Newline; + for (int col = 0; col < tableColumnWidths.length(); ++col) + m_stream << '|' << QString(tableColumnWidths[col], QLatin1Char('-')); + m_stream << '|'; + } + m_stream << Newline << "|"; + tableRow = cell.row(); + } + } else if (!block.textList()) { + if (lastWasList) + m_stream << Newline; + } + int endingCol = writeBlock(block, !table, table && tableRow == 0); + m_doubleNewlineWritten = false; + if (table) { + QTextTableCell cell = table->cellAt(block.position()); + int paddingLen = -endingCol; + int spanEndCol = cell.column() + cell.columnSpan(); + for (int col = cell.column(); col < spanEndCol; ++col) + paddingLen += tableColumnWidths[col]; + if (paddingLen > 0) + m_stream << QString(paddingLen, Space); + for (int col = cell.column(); col < spanEndCol; ++col) + m_stream << "|"; + } else if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) { + m_stream << Newline; + } else if (endingCol > 0) { + m_stream << Newline << Newline; + m_doubleNewlineWritten = true; + } + lastWasList = block.textList(); + } + child = iterator.currentFrame(); + ++iterator; + } + if (table) { + m_stream << Newline << Newline; + m_doubleNewlineWritten = true; + } + m_listInfo.clear(); +} + +QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list) +{ + if (!m_listInfo.contains(list)) { + // decide whether this list is loose or tight + ListInfo info; + info.loose = false; + if (list->count() > 1) { + QTextBlock first = list->item(0); + QTextBlock last = list->item(list->count() - 1); + QTextBlock next = first.next(); + while (next.isValid()) { + if (next == last) + break; + qCDebug(lcMDW) << "next block in list" << list << next.text() << "part of list?" << next.textList(); + if (!next.textList()) { + // If we find a continuation paragraph, this list is "loose" + // because it will need a blank line to separate that paragraph. + qCDebug(lcMDW) << "decided list beginning with" << first.text() << "is loose after" << next.text(); + info.loose = true; + break; + } + next = next.next(); + } + } + m_listInfo.insert(list, info); + return info; + } + return m_listInfo.value(list); +} + +static int nearestWordWrapIndex(const QString &s, int before) +{ + before = qMin(before, s.length()); + for (int i = before - 1; i >= 0; --i) { + if (s.at(i).isSpace()) + return i; + } + return -1; +} + +static int adjacentBackticksCount(const QString &s) +{ + int start = -1, len = s.length(); + int ret = 0; + for (int i = 0; i < len; ++i) { + if (s.at(i) == Backtick) { + if (start < 0) + start = i; + } else if (start >= 0) { + ret = qMax(ret, i - start); + start = -1; + } + } + if (s.at(len - 1) == Backtick) + ret = qMax(ret, len - start); + return ret; +} + +static void maybeEscapeFirstChar(QString &s) +{ + QString sTrimmed = s.trimmed(); + if (sTrimmed.isEmpty()) + return; + char firstChar = sTrimmed.at(0).toLatin1(); + if (firstChar == '*' || firstChar == '+' || firstChar == '-') { + int i = s.indexOf(QLatin1Char(firstChar)); + s.insert(i, QLatin1Char('\\')); + } +} + +int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat) +{ + int ColumnLimit = 80; + QTextBlockFormat blockFmt = block.blockFormat(); + bool indentedCodeBlock = false; + if (block.textList()) { // it's a list-item + auto fmt = block.textList()->format(); + const int listLevel = fmt.indent(); + const int number = block.textList()->itemNumber(block) + 1; + QByteArray bullet = " "; + bool numeric = false; + switch (fmt.style()) { + case QTextListFormat::ListDisc: + bullet = "-"; + m_wrappedLineIndent = 2; + break; + case QTextListFormat::ListCircle: + bullet = "*"; + m_wrappedLineIndent = 2; + break; + case QTextListFormat::ListSquare: + bullet = "+"; + m_wrappedLineIndent = 2; + break; + case QTextListFormat::ListStyleUndefined: break; + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: + numeric = true; + m_wrappedLineIndent = 4; + break; + } + switch (blockFmt.marker()) { + case QTextBlockFormat::Checked: + bullet += " [x]"; + break; + case QTextBlockFormat::Unchecked: + bullet += " [ ]"; + break; + default: + break; + } + int indentFirstLine = (listLevel - 1) * (numeric ? 4 : 2); + m_wrappedLineIndent += indentFirstLine; + if (m_lastListIndent != listLevel && !m_doubleNewlineWritten && listInfo(block.textList()).loose) + m_stream << Newline; + m_lastListIndent = listLevel; + QString prefix(indentFirstLine, Space); + if (numeric) { + QString suffix = fmt.numberSuffix(); + if (suffix.isEmpty()) + suffix = QString(Period); + QString numberStr = QString::number(number) + suffix + Space; + if (numberStr.length() == 3) + numberStr += Space; + prefix += numberStr; + } else { + prefix += QLatin1String(bullet) + Space; + } + m_stream << prefix; + } else if (blockFmt.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { + m_stream << "- - -\n"; // unambiguous horizontal rule, not an underline under a heading + return 0; + } else if (!blockFmt.indent()) { + m_wrappedLineIndent = 0; + m_linePrefix.clear(); + if (blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) { + int level = blockFmt.intProperty(QTextFormat::BlockQuoteLevel); + QString quoteMarker = QStringLiteral("> "); + m_linePrefix.reserve(level * 2); + for (int i = 0; i < level; ++i) + m_linePrefix += quoteMarker; + } + if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) { + // A block quote can contain an indented code block, but not vice-versa. + m_linePrefix += QString(4, Space); + indentedCodeBlock = true; + } + } + if (blockFmt.headingLevel()) + m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' '; + else + m_stream << m_linePrefix; + + QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, Space); + // It would be convenient if QTextStream had a lineCharPos() accessor, + // to keep track of how many characters (not bytes) have been written on the current line, + // but it doesn't. So we have to keep track with this col variable. + int col = wrapIndentString.length(); + bool mono = false; + bool startsOrEndsWithBacktick = false; + bool bold = false; + bool italic = false; + bool underline = false; + bool strikeOut = false; + QString backticks(Backtick); + for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) { + QString fragmentText = frag.fragment().text(); + while (fragmentText.endsWith(Newline)) + fragmentText.chop(1); + if (block.textList()) { // <li>first line</br>continuation</li> + QString newlineIndent = QString(Newline) + QString(m_wrappedLineIndent, Space); + fragmentText.replace(QString(LineBreak), newlineIndent); + } else if (blockFmt.indent() > 0) { // <li>first line<p>continuation</p></li> + m_stream << QString(m_wrappedLineIndent, Space); + } else { + fragmentText.replace(LineBreak, Newline); + } + startsOrEndsWithBacktick |= fragmentText.startsWith(Backtick) || fragmentText.endsWith(Backtick); + QTextCharFormat fmt = frag.fragment().charFormat(); + if (fmt.isImageFormat()) { + QTextImageFormat ifmt = fmt.toImageFormat(); + QString s = QLatin1String("![image](") + ifmt.name() + QLatin1Char(')'); + if (wrap && col + s.length() > ColumnLimit) { + m_stream << Newline << wrapIndentString; + col = m_wrappedLineIndent; + } + m_stream << s; + col += s.length(); + } else if (fmt.hasProperty(QTextFormat::AnchorHref)) { + QString s = QLatin1Char('[') + fragmentText + QLatin1String("](") + + fmt.property(QTextFormat::AnchorHref).toString() + QLatin1Char(')'); + if (wrap && col + s.length() > ColumnLimit) { + m_stream << Newline << wrapIndentString; + col = m_wrappedLineIndent; + } + m_stream << s; + col += s.length(); + } else { + QFontInfo fontInfo(fmt.font()); + bool monoFrag = fontInfo.fixedPitch(); + QString markers; + if (!ignoreFormat) { + if (monoFrag != mono && !indentedCodeBlock) { + if (monoFrag) + backticks = QString(adjacentBackticksCount(fragmentText) + 1, Backtick); + markers += backticks; + if (startsOrEndsWithBacktick) + markers += Space; + mono = monoFrag; + } + if (!blockFmt.headingLevel() && !mono) { + if (fontInfo.bold() != bold) { + markers += QLatin1String("**"); + bold = fontInfo.bold(); + } + if (fontInfo.italic() != italic) { + markers += QLatin1Char('*'); + italic = fontInfo.italic(); + } + if (fontInfo.strikeOut() != strikeOut) { + markers += QLatin1String("~~"); + strikeOut = fontInfo.strikeOut(); + } + if (fontInfo.underline() != underline) { + // Markdown doesn't support underline, but the parser will treat a single underline + // the same as a single asterisk, and the marked fragment will be rendered in italics. + // That will have to do. + markers += QLatin1Char('_'); + underline = fontInfo.underline(); + } + } + } + if (wrap && col + markers.length() * 2 + fragmentText.length() > ColumnLimit) { + int i = 0; + int fragLen = fragmentText.length(); + bool breakingLine = false; + while (i < fragLen) { + int j = i + ColumnLimit - col; + if (j < fragLen) { + int wi = nearestWordWrapIndex(fragmentText, j); + if (wi < 0) { + j = fragLen; + } else { + j = wi; + breakingLine = true; + } + } else { + j = fragLen; + breakingLine = false; + } + QString subfrag = fragmentText.mid(i, j - i); + if (!i) { + m_stream << markers; + col += markers.length(); + } + if (col == m_wrappedLineIndent) + maybeEscapeFirstChar(subfrag); + m_stream << subfrag; + if (breakingLine) { + m_stream << Newline << wrapIndentString; + col = m_wrappedLineIndent; + } else { + col += subfrag.length(); + } + i = j + 1; + } + } else { + m_stream << markers << fragmentText; + col += markers.length() + fragmentText.length(); + } + } + } + if (mono) { + if (startsOrEndsWithBacktick) { + m_stream << Space; + col += 1; + } + m_stream << backticks; + col += backticks.size(); + } + if (bold) { + m_stream << "**"; + col += 2; + } + if (italic) { + m_stream << "*"; + col += 1; + } + if (underline) { + m_stream << "_"; + col += 1; + } + if (strikeOut) { + m_stream << "~~"; + col += 2; + } + return col; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextmarkdownwriter_p.h b/src/gui/text/qtextmarkdownwriter_p.h new file mode 100644 index 0000000000..96ceb445cd --- /dev/null +++ b/src/gui/text/qtextmarkdownwriter_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTEXTMARKDOWNWRITER_P_H +#define QTEXTMARKDOWNWRITER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/private/qtguiglobal_p.h> +#include <QtCore/QTextStream> + +#include "qtextdocument_p.h" +#include "qtextdocumentwriter.h" +#include "QAbstractTableModel" + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QTextMarkdownWriter +{ +public: + QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features); + bool writeAll(const QTextDocument *document); + void writeTable(const QAbstractItemModel *table); + + int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat); + void writeFrame(const QTextFrame *frame); + +private: + struct ListInfo { + bool loose; + }; + + ListInfo listInfo(QTextList *list); + +private: + QTextStream &m_stream; + QTextDocument::MarkdownFeatures m_features; + QMap<QTextList *, ListInfo> m_listInfo; + QString m_linePrefix; + int m_wrappedLineIndent = 0; + int m_lastListIndent = 1; + bool m_doubleNewlineWritten = false; +}; + +QT_END_NAMESPACE + +#endif // QTEXTMARKDOWNWRITER_P_H diff --git a/src/gui/text/qtextobject.cpp b/src/gui/text/qtextobject.cpp index 547b21c02e..1302bd66bb 100644 --- a/src/gui/text/qtextobject.cpp +++ b/src/gui/text/qtextobject.cpp @@ -678,7 +678,7 @@ QTextFrame::iterator::iterator(QTextFrame *frame, int block, int begin, int end) /*! Copy constructor. Constructs a copy of the \a other iterator. */ -QTextFrame::iterator::iterator(const iterator &other) Q_DECL_NOTHROW +QTextFrame::iterator::iterator(const iterator &other) noexcept { f = other.f; b = other.b; @@ -691,7 +691,7 @@ QTextFrame::iterator::iterator(const iterator &other) Q_DECL_NOTHROW Assigns \a other to this iterator and returns a reference to this iterator. */ -QTextFrame::iterator &QTextFrame::iterator::operator=(const iterator &other) Q_DECL_NOTHROW +QTextFrame::iterator &QTextFrame::iterator::operator=(const iterator &other) noexcept { f = other.f; b = other.b; diff --git a/src/gui/text/qtextobject.h b/src/gui/text/qtextobject.h index 694eb729d5..215a76fb4d 100644 --- a/src/gui/text/qtextobject.h +++ b/src/gui/text/qtextobject.h @@ -151,11 +151,11 @@ public: public: iterator(); // ### Qt 6: inline #if QT_VERSION < QT_VERSION_CHECK(6,0,0) - iterator(const iterator &o) Q_DECL_NOTHROW; // = default - iterator &operator=(const iterator &o) Q_DECL_NOTHROW; // = default - iterator(iterator &&other) Q_DECL_NOTHROW // = default + iterator(const iterator &o) noexcept; // = default + iterator &operator=(const iterator &o) noexcept; // = default + iterator(iterator &&other) noexcept // = default { memcpy(static_cast<void *>(this), static_cast<void *>(&other), sizeof(iterator)); } - iterator &operator=(iterator &&other) Q_DECL_NOTHROW // = default + iterator &operator=(iterator &&other) noexcept // = default { memcpy(static_cast<void *>(this), static_cast<void *>(&other), sizeof(iterator)); return *this; } #endif diff --git a/src/gui/text/qtextobject_p.h b/src/gui/text/qtextobject_p.h index 81ab023cc3..87c83868da 100644 --- a/src/gui/text/qtextobject_p.h +++ b/src/gui/text/qtextobject_p.h @@ -93,7 +93,7 @@ class QTextFramePrivate : public QTextObjectPrivate Q_DECLARE_PUBLIC(QTextFrame) public: QTextFramePrivate(QTextDocument *doc) - : QTextObjectPrivate(doc), fragment_start(0), fragment_end(0), parentFrame(0), layoutData(0) + : QTextObjectPrivate(doc), fragment_start(0), fragment_end(0), parentFrame(nullptr), layoutData(nullptr) { } virtual void fragmentAdded(QChar type, uint fragment); diff --git a/src/gui/text/qtextodfwriter.cpp b/src/gui/text/qtextodfwriter.cpp index a62e7e425a..8eaad403d0 100644 --- a/src/gui/text/qtextodfwriter.cpp +++ b/src/gui/text/qtextodfwriter.cpp @@ -1007,7 +1007,7 @@ bool QTextOdfWriter::writeAll() // add objects for lists, frames and tables const QVector<QTextFormat> allFormats = m_document->allFormats(); - const QList<int> copy = formats.toList(); + const QList<int> copy = formats.values(); for (auto index : copy) { QTextObject *object = m_document->objectForFormat(allFormats[index]); if (object) { diff --git a/src/gui/text/qtexttable_p.h b/src/gui/text/qtexttable_p.h index c969e1d5bc..5c05611009 100644 --- a/src/gui/text/qtexttable_p.h +++ b/src/gui/text/qtexttable_p.h @@ -61,7 +61,7 @@ class QTextTablePrivate : public QTextFramePrivate { Q_DECLARE_PUBLIC(QTextTable) public: - QTextTablePrivate(QTextDocument *document) : QTextFramePrivate(document), grid(0), nRows(0), nCols(0), dirty(true), blockFragmentUpdates(false) {} + QTextTablePrivate(QTextDocument *document) : QTextFramePrivate(document), grid(nullptr), nRows(0), nCols(0), dirty(true), blockFragmentUpdates(false) {} ~QTextTablePrivate(); static QTextTable *createTable(QTextDocumentPrivate *, int pos, int rows, int cols, const QTextTableFormat &tableFormat); diff --git a/src/gui/text/qzipreader_p.h b/src/gui/text/qzipreader_p.h index 378072b366..6fec8d7719 100644 --- a/src/gui/text/qzipreader_p.h +++ b/src/gui/text/qzipreader_p.h @@ -79,11 +79,11 @@ public: struct FileInfo { - FileInfo() Q_DECL_NOTHROW + FileInfo() noexcept : isDir(false), isFile(false), isSymLink(false), crc(0), size(0) {} - bool isValid() const Q_DECL_NOTHROW { return isDir || isFile || isSymLink; } + bool isValid() const noexcept { return isDir || isFile || isSymLink; } QString filePath; uint isDir : 1; diff --git a/src/gui/text/text.pri b/src/gui/text/text.pri index abe20abe02..5e97b312f1 100644 --- a/src/gui/text/text.pri +++ b/src/gui/text/text.pri @@ -97,6 +97,25 @@ qtConfig(textodfwriter) { text/qzip.cpp } +qtConfig(textmarkdownreader) { + qtConfig(system-textmarkdownreader) { + QMAKE_USE += libmd4c + } else { + include($$PWD/../../3rdparty/md4c.pri) + } + HEADERS += \ + text/qtextmarkdownimporter_p.h + SOURCES += \ + text/qtextmarkdownimporter.cpp +} + +qtConfig(textmarkdownwriter) { + HEADERS += \ + text/qtextmarkdownwriter_p.h + SOURCES += \ + text/qtextmarkdownwriter.cpp +} + qtConfig(cssparser) { HEADERS += \ text/qcssparser_p.h |