summaryrefslogtreecommitdiffstats
path: root/src/gui/text
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/text')
-rw-r--r--src/gui/text/qabstracttextdocumentlayout.cpp31
-rw-r--r--src/gui/text/qabstracttextdocumentlayout.h1
-rw-r--r--src/gui/text/qabstracttextdocumentlayout_p.h6
-rw-r--r--src/gui/text/qcssparser.cpp23
-rw-r--r--src/gui/text/qcssparser_p.h16
-rw-r--r--src/gui/text/qdistancefield.cpp2
-rw-r--r--src/gui/text/qdistancefield_p.h2
-rw-r--r--src/gui/text/qfont.cpp144
-rw-r--r--src/gui/text/qfont.h12
-rw-r--r--src/gui/text/qfont_p.h41
-rw-r--r--src/gui/text/qfontdatabase.cpp41
-rw-r--r--src/gui/text/qfontengine.cpp12
-rw-r--r--src/gui/text/qfontengine_p.h27
-rw-r--r--src/gui/text/qfontengine_qpf2.cpp6
-rw-r--r--src/gui/text/qfontmetrics.cpp27
-rw-r--r--src/gui/text/qfontmetrics.h16
-rw-r--r--src/gui/text/qfragmentmap_p.h2
-rw-r--r--src/gui/text/qglyphrun.cpp2
-rw-r--r--src/gui/text/qglyphrun.h6
-rw-r--r--src/gui/text/qglyphrun_p.h2
-rw-r--r--src/gui/text/qplatformfontdatabase.cpp13
-rw-r--r--src/gui/text/qplatformfontdatabase.h4
-rw-r--r--src/gui/text/qrawfont.cpp2
-rw-r--r--src/gui/text/qrawfont.h8
-rw-r--r--src/gui/text/qrawfont_p.h24
-rw-r--r--src/gui/text/qstatictext.cpp4
-rw-r--r--src/gui/text/qstatictext.h6
-rw-r--r--src/gui/text/qstatictext_p.h2
-rw-r--r--src/gui/text/qsyntaxhighlighter.cpp7
-rw-r--r--src/gui/text/qtextcursor.h6
-rw-r--r--src/gui/text/qtextdocument.cpp198
-rw-r--r--src/gui/text/qtextdocument.h24
-rw-r--r--src/gui/text/qtextdocument_p.cpp18
-rw-r--r--src/gui/text/qtextdocument_p.h7
-rw-r--r--src/gui/text/qtextdocumentfragment.cpp41
-rw-r--r--src/gui/text/qtextdocumentfragment_p.h6
-rw-r--r--src/gui/text/qtextdocumentlayout.cpp1027
-rw-r--r--src/gui/text/qtextdocumentwriter.cpp19
-rw-r--r--src/gui/text/qtextengine.cpp36
-rw-r--r--src/gui/text/qtextengine_p.h18
-rw-r--r--src/gui/text/qtextformat.cpp344
-rw-r--r--src/gui/text/qtextformat.h127
-rw-r--r--src/gui/text/qtexthtmlparser.cpp94
-rw-r--r--src/gui/text/qtexthtmlparser_p.h11
-rw-r--r--src/gui/text/qtextimagehandler_p.h2
-rw-r--r--src/gui/text/qtextmarkdownimporter.cpp568
-rw-r--r--src/gui/text/qtextmarkdownimporter_p.h143
-rw-r--r--src/gui/text/qtextmarkdownwriter.cpp564
-rw-r--r--src/gui/text/qtextmarkdownwriter_p.h98
-rw-r--r--src/gui/text/qtextobject.cpp4
-rw-r--r--src/gui/text/qtextobject.h8
-rw-r--r--src/gui/text/qtextobject_p.h2
-rw-r--r--src/gui/text/qtextodfwriter.cpp6
-rw-r--r--src/gui/text/qtexttable_p.h2
-rw-r--r--src/gui/text/qzipreader_p.h4
-rw-r--r--src/gui/text/text.pri19
56 files changed, 3516 insertions, 369 deletions
diff --git a/src/gui/text/qabstracttextdocumentlayout.cpp b/src/gui/text/qabstracttextdocumentlayout.cpp
index 2278378613..8b8f3e28ac 100644
--- a/src/gui/text/qabstracttextdocumentlayout.cpp
+++ b/src/gui/text/qabstracttextdocumentlayout.cpp
@@ -41,6 +41,7 @@
#include <qtextformat.h>
#include "qtextdocument_p.h"
#include "qtextengine_p.h"
+#include "qtextlist.h"
#include "qabstracttextdocumentlayout_p.h"
@@ -650,6 +651,36 @@ QTextFormat QAbstractTextDocumentLayout::formatAt(const QPointF &pos) const
}
/*!
+ \since 5.14
+
+ Returns the block (probably a list item) whose \l{QTextBlockFormat::marker()}{marker}
+ is found at the given position \a pos.
+*/
+QTextBlock QAbstractTextDocumentLayout::blockWithMarkerAt(const QPointF &pos) const
+{
+ QTextBlock block = document()->firstBlock();
+ while (block.isValid()) {
+ if (block.blockFormat().marker() != QTextBlockFormat::MarkerType::NoMarker) {
+ QRectF blockBr = blockBoundingRect(block);
+ QTextBlockFormat blockFmt = block.blockFormat();
+ QFontMetrics fm(block.charFormat().font());
+ qreal totalIndent = blockFmt.indent() + blockFmt.leftMargin() + blockFmt.textIndent();
+ if (block.textList())
+ totalIndent += block.textList()->format().indent() * 40;
+ QRectF adjustedBr = blockBr.adjusted(totalIndent - fm.height(), 0, totalIndent - blockBr.width(), fm.height() - blockBr.height());
+ if (adjustedBr.contains(pos)) {
+ //qDebug() << "hit block" << block.text() << blockBr << adjustedBr << "marker" << block.blockFormat().marker()
+ // << "font" << block.charFormat().font() << "adj" << lineHeight << totalIndent;
+ if (block.blockFormat().hasProperty(QTextFormat::BlockMarker))
+ return block;
+ }
+ }
+ block = block.next();
+ }
+ return QTextBlock();
+}
+
+/*!
\fn QRectF QAbstractTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
Returns the bounding rectangle of \a frame.
diff --git a/src/gui/text/qabstracttextdocumentlayout.h b/src/gui/text/qabstracttextdocumentlayout.h
index 3371401420..397dcd37d4 100644
--- a/src/gui/text/qabstracttextdocumentlayout.h
+++ b/src/gui/text/qabstracttextdocumentlayout.h
@@ -87,6 +87,7 @@ public:
QString anchorAt(const QPointF& pos) const;
QString imageAt(const QPointF &pos) const;
QTextFormat formatAt(const QPointF &pos) const;
+ QTextBlock blockWithMarkerAt(const QPointF &pos) const;
virtual int pageCount() const = 0;
virtual QSizeF documentSize() const = 0;
diff --git a/src/gui/text/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.cpp b/src/gui/text/qcssparser.cpp
index dc7e128bcd..ce7c7610c1 100644
--- a/src/gui/text/qcssparser.cpp
+++ b/src/gui/text/qcssparser.cpp
@@ -67,6 +67,7 @@ struct QCssKnownValue
static const QCssKnownValue properties[NumProperties - 1] = {
{ "-qt-background-role", QtBackgroundRole },
{ "-qt-block-indent", QtBlockIndent },
+ { "-qt-fg-texture-cachekey", QtForegroundTextureCacheKey },
{ "-qt-line-height-type", QtLineHeightType },
{ "-qt-list-indent", QtListIndent },
{ "-qt-list-number-prefix", QtListNumberPrefix },
@@ -91,6 +92,7 @@ static const QCssKnownValue properties[NumProperties - 1] = {
{ "border-bottom-right-radius", BorderBottomRightRadius },
{ "border-bottom-style", BorderBottomStyle },
{ "border-bottom-width", BorderBottomWidth },
+ { "border-collapse", BorderCollapse },
{ "border-color", BorderColor },
{ "border-image", BorderImage },
{ "border-left", BorderLeft },
@@ -221,6 +223,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "outset", Value_Outset },
{ "overline", Value_Overline },
{ "pre", Value_Pre },
+ { "pre-line", Value_PreLine },
{ "pre-wrap", Value_PreWrap },
{ "ridge", Value_Ridge },
{ "right", Value_Right },
@@ -247,10 +250,10 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
};
//Map id to strings as they appears in the 'values' array above
-static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 54, 35, 26, 70, 71, 25, 43, 5, 63, 47,
- 29, 58, 59, 27, 51, 61, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 50, 24, 46, 67, 37, 3, 2, 40, 62, 16,
- 11, 57, 14, 32, 64, 33, 65, 55, 66, 34, 69, 8, 28, 38, 12, 36, 60, 7, 9, 4, 68, 53, 22, 23, 30, 31,
- 1, 15, 0, 52, 45, 44 };
+static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 50, 55, 35, 26, 71, 72, 25, 43, 5, 64, 48,
+ 29, 59, 60, 27, 52, 62, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 51, 24, 46, 68, 37, 3, 2, 40, 63, 16,
+ 11, 58, 14, 32, 65, 33, 66, 56, 67, 34, 70, 8, 28, 38, 12, 36, 61, 7, 9, 4, 69, 54, 22, 23, 30, 31,
+ 1, 15, 0, 53, 45, 44 };
QString Value::toString() const
{
@@ -609,11 +612,7 @@ bool ValueExtractor::extractBorder(int *borders, QBrush *colors, BorderStyle *st
case BorderRightStyle: styles[RightEdge] = decl.styleValue(); break;
case BorderStyles: decl.styleValues(styles); break;
-#ifndef QT_OS_ANDROID_GCC_48_WORKAROUND
case BorderTopLeftRadius: radii[0] = sizeValue(decl); break;
-#else
- case BorderTopLeftRadius: new(radii)QSize(sizeValue(decl)); break;
-#endif
case BorderTopRightRadius: radii[1] = sizeValue(decl); break;
case BorderBottomLeftRadius: radii[2] = sizeValue(decl); break;
case BorderBottomRightRadius: radii[3] = sizeValue(decl); break;
@@ -1730,6 +1729,14 @@ void Declaration::borderImageValue(QString *image, int *cuts,
*h = *v;
}
+bool Declaration::borderCollapseValue() const
+{
+ if (d->values.count() != 1)
+ return false;
+ else
+ return d->values.at(0).toString() == QLatin1String("collapse");
+}
+
QIcon Declaration::iconValue() const
{
if (d->parsed.isValid())
diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h
index 860bbe382a..ab85e76cf3 100644
--- a/src/gui/text/qcssparser_p.h
+++ b/src/gui/text/qcssparser_p.h
@@ -122,6 +122,7 @@ enum Property {
BorderRight,
BorderTop,
BorderBottom,
+ BorderCollapse,
Padding,
PaddingLeft,
PaddingRight,
@@ -196,6 +197,7 @@ enum Property {
LineHeight,
QtLineHeightType,
FontKerning,
+ QtForegroundTextureCacheKey,
NumProperties
};
@@ -204,6 +206,7 @@ enum KnownValue {
Value_Normal,
Value_Pre,
Value_NoWrap,
+ Value_PreLine,
Value_PreWrap,
Value_Small,
Value_Medium,
@@ -467,8 +470,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;
@@ -476,6 +479,7 @@ struct Q_GUI_EXPORT Declaration
QIcon iconValue() const;
void borderImageValue(QString *image, int *cuts, TileMode *h, TileMode *v) const;
+ bool borderCollapseValue() const;
};
QT_CSS_DECLARE_TYPEINFO(Declaration, Q_MOVABLE_TYPE)
@@ -584,7 +588,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 +660,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 +748,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 +849,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.cpp b/src/gui/text/qdistancefield.cpp
index 75e2e4e745..d8a971c7b7 100644
--- a/src/gui/text/qdistancefield.cpp
+++ b/src/gui/text/qdistancefield.cpp
@@ -782,7 +782,7 @@ bool qt_fontHasNarrowOutlines(QFontEngine *fontEngine)
if (glyph != 0)
im = fe->alphaMapForGlyph(glyph, QFixed(), QTransform());
- Q_ASSERT(fe->ref.load() == 0);
+ Q_ASSERT(fe->ref.loadRelaxed() == 0);
delete fe;
return imageHasNarrowOutlines(im);
diff --git a/src/gui/text/qdistancefield_p.h b/src/gui/text/qdistancefield_p.h
index c0873cedab..823bfaf1c6 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 84c5be60b1..76fde5388c 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -180,14 +180,14 @@ Q_GUI_EXPORT int qt_defaultDpi()
}
QFontPrivate::QFontPrivate()
- : engineData(0), dpi(qt_defaultDpi()), screen(0),
+ : engineData(0), dpi(qt_defaultDpi()),
underline(false), overline(false), strikeOut(false), kerning(true),
capital(0), letterSpacingIsAbsolute(false), scFont(0)
{
}
QFontPrivate::QFontPrivate(const QFontPrivate &other)
- : request(other.request), engineData(0), dpi(other.dpi), screen(other.screen),
+ : request(other.request), engineData(0), dpi(other.dpi),
underline(other.underline), overline(other.overline),
strikeOut(other.strikeOut), kerning(other.kerning),
capital(other.capital), letterSpacingIsAbsolute(other.letterSpacingIsAbsolute),
@@ -208,7 +208,7 @@ QFontPrivate::~QFontPrivate()
scFont = 0;
}
-extern QMutex *qt_fontdatabase_mutex();
+extern QRecursiveMutex *qt_fontdatabase_mutex();
#define QT_FONT_ENGINE_FROM_DATA(data, script) data->engines[script]
@@ -336,7 +336,7 @@ QFontEngineData::QFontEngineData()
QFontEngineData::~QFontEngineData()
{
- Q_ASSERT(ref.load() == 0);
+ Q_ASSERT(ref.loadRelaxed() == 0);
for (int i = 0; i < QChar::ScriptCount; ++i) {
if (engines[i]) {
if (!engines[i]->ref.deref())
@@ -581,11 +581,9 @@ QFont::QFont(const QFont &font, const QPaintDevice *pd)
{
Q_ASSERT(pd);
const int dpi = pd->logicalDpiY();
- const int screen = 0;
- if (font.d->dpi != dpi || font.d->screen != screen ) {
+ if (font.d->dpi != dpi) {
d = new QFontPrivate(*font.d);
d->dpi = dpi;
- d->screen = screen;
} else {
d = font.d;
}
@@ -604,7 +602,7 @@ QFont::QFont(QFontPrivate *data)
*/
void QFont::detach()
{
- if (d->ref.load() == 1) {
+ if (d->ref.loadRelaxed() == 1) {
if (d->engineData && !d->engineData->ref.deref())
delete d->engineData;
d->engineData = 0;
@@ -625,7 +623,7 @@ void QFont::detach()
*/
void QFontPrivate::detachButKeepEngineData(QFont *font)
{
- if (font->d->ref.load() == 1)
+ if (font->d->ref.loadRelaxed() == 1)
return;
QFontEngineData *engineData = font->d->engineData;
@@ -2083,7 +2081,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);
}
@@ -2800,12 +2798,12 @@ void QFontCache::cleanup()
cache->setLocalData(0);
}
-QBasicAtomicInt font_cache_id = Q_BASIC_ATOMIC_INITIALIZER(1);
+static QBasicAtomicInt font_cache_id = Q_BASIC_ATOMIC_INITIALIZER(0);
QFontCache::QFontCache()
: QObject(), total_cost(0), max_cost(min_cost),
current_timestamp(0), fast(false), timer_id(-1),
- m_id(font_cache_id.fetchAndAddRelaxed(1))
+ m_id(font_cache_id.fetchAndAddRelaxed(1) + 1)
{
}
@@ -2834,7 +2832,7 @@ void QFontCache::clear()
delete data;
} else {
FC_DEBUG("QFontCache::clear: engineData %p still has refcount %d",
- data, data->ref.load());
+ data, data->ref.loadRelaxed());
}
++it;
}
@@ -2858,7 +2856,7 @@ void QFontCache::clear()
delete engine;
} else if (cacheCount == 0) {
FC_DEBUG("QFontCache::clear: engine %p still has refcount %d",
- engine, engine->ref.load());
+ engine, engine->ref.loadRelaxed());
}
it.value().data = 0;
}
@@ -2928,7 +2926,7 @@ void QFontCache::updateHitCountAndTimeStamp(Engine &value)
FC_DEBUG("QFontCache: found font engine\n"
" %p: timestamp %4u hits %3u ref %2d/%2d, type %d",
value.data, value.timestamp, value.hits,
- value.data->ref.load(), engineCacheCount.value(value.data),
+ value.data->ref.loadRelaxed(), engineCacheCount.value(value.data),
value.data->type());
}
@@ -2938,7 +2936,7 @@ void QFontCache::insertEngine(const Key &key, QFontEngine *engine, bool insertMu
Q_ASSERT(key.multi == (engine->type() == QFontEngine::Multi));
#ifdef QFONTCACHE_DEBUG
- FC_DEBUG("QFontCache: inserting new engine %p, refcount %d", engine, engine->ref.load());
+ FC_DEBUG("QFontCache: inserting new engine %p, refcount %d", engine, engine->ref.loadRelaxed());
if (!insertMulti && engineCache.contains(key)) {
FC_DEBUG(" QFontCache already contains engine %p for key=(%g %g %d %d %d)",
engineCache.value(key).data, key.def.pointSize,
@@ -3027,9 +3025,9 @@ void QFontCache::decreaseCache()
EngineDataCache::ConstIterator it = engineDataCache.constBegin(),
end = engineDataCache.constEnd();
for (; it != end; ++it) {
- FC_DEBUG(" %p: ref %2d", it.value(), int(it.value()->ref.load()));
+ FC_DEBUG(" %p: ref %2d", it.value(), int(it.value()->ref.loadRelaxed()));
- if (it.value()->ref.load() != 1)
+ if (it.value()->ref.loadRelaxed() != 1)
in_use_cost += engine_data_cost;
}
}
@@ -3042,10 +3040,10 @@ void QFontCache::decreaseCache()
for (; it != end; ++it) {
FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, cost %u bytes",
it.value().data, it.value().timestamp, it.value().hits,
- it.value().data->ref.load(), engineCacheCount.value(it.value().data),
+ it.value().data->ref.loadRelaxed(), engineCacheCount.value(it.value().data),
it.value().data->cache_cost);
- if (it.value().data->ref.load() > engineCacheCount.value(it.value().data))
+ if (it.value().data->ref.loadRelaxed() > engineCacheCount.value(it.value().data))
in_use_cost += it.value().data->cache_cost / engineCacheCount.value(it.value().data);
}
@@ -3094,7 +3092,7 @@ void QFontCache::decreaseCache()
// clean out all unused engine data
EngineDataCache::Iterator it = engineDataCache.begin();
while (it != engineDataCache.end()) {
- if (it.value()->ref.load() == 1) {
+ if (it.value()->ref.loadRelaxed() == 1) {
FC_DEBUG(" %p", it.value());
decreaseCost(sizeof(QFontEngineData));
it.value()->ref.deref();
@@ -3122,7 +3120,7 @@ void QFontCache::decreaseCache()
EngineCache::Iterator jt = end;
for ( ; it != end; ++it) {
- if (it.value().data->ref.load() != engineCacheCount.value(it.value().data))
+ if (it.value().data->ref.loadRelaxed() != engineCacheCount.value(it.value().data))
continue;
if (it.value().timestamp < oldest && it.value().hits <= least_popular) {
@@ -3136,7 +3134,7 @@ void QFontCache::decreaseCache()
if (it != end) {
FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, type %d",
it.value().data, it.value().timestamp, it.value().hits,
- it.value().data->ref.load(), engineCacheCount.value(it.value().data),
+ it.value().data->ref.loadRelaxed(), engineCacheCount.value(it.value().data),
it.value().data->type());
QFontEngine *fontEngine = it.value().data;
@@ -3151,7 +3149,7 @@ void QFontCache::decreaseCache()
}
}
// and delete the last occurrence
- Q_ASSERT(fontEngine->ref.load() == 0);
+ Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
decreaseCost(fontEngine->cache_cost);
delete fontEngine;
engineCacheCount.remove(fontEngine);
@@ -3165,7 +3163,103 @@ 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();
+
+ const QFont defaultFont(new QFontPrivate);
+
+ 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..adbb7a0121 100644
--- a/src/gui/text/qfont_p.h
+++ b/src/gui/text/qfont_p.h
@@ -136,21 +136,22 @@ 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)
- ^ qHash(fd.style)
- ^ qHash(fd.stretch)
- ^ qHash(fd.styleHint)
- ^ qHash(fd.styleStrategy)
- ^ qHash(fd.ignorePitch)
- ^ qHash(fd.fixedPitch)
- ^ qHash(fd.family, seed)
- ^ qHash(fd.families, seed)
- ^ qHash(fd.styleName)
- ^ qHash(fd.hintingPreference)
- ;
+ QtPrivate::QHashCombine hash;
+ seed = hash(seed, qRound64(fd.pixelSize*10000)); // use only 4 fractional digits
+ seed = hash(seed, fd.weight);
+ seed = hash(seed, fd.style);
+ seed = hash(seed, fd.stretch);
+ seed = hash(seed, fd.styleHint);
+ seed = hash(seed, fd.styleStrategy);
+ seed = hash(seed, fd.ignorePitch);
+ seed = hash(seed, fd.fixedPitch);
+ seed = hash(seed, fd.family);
+ seed = hash(seed, fd.families);
+ seed = hash(seed, fd.styleName);
+ seed = hash(seed, fd.hintingPreference);
+ return seed;
}
class QFontEngineData
@@ -184,7 +185,6 @@ public:
QFontDef request;
mutable QFontEngineData *engineData;
int dpi;
- int screen;
uint underline : 1;
uint overline : 1;
@@ -229,19 +229,17 @@ public:
void clear();
struct Key {
- Key() : script(0), multi(0), screen(0) { }
- Key(const QFontDef &d, uchar c, bool m = 0, uchar s = 0)
- : def(d), script(c), multi(m), screen(s) { }
+ Key() : script(0), multi(0) { }
+ Key(const QFontDef &d, uchar c, bool m = 0)
+ : def(d), script(c), multi(m) { }
QFontDef def;
uchar script;
uchar multi: 1;
- uchar screen: 7;
inline bool operator<(const Key &other) const
{
if (script != other.script) return script < other.script;
- if (screen != other.screen) return screen < other.screen;
if (multi != other.multi) return multi < other.multi;
if (multi && def.fallBackFamilies.size() != other.def.fallBackFamilies.size())
return def.fallBackFamilies.size() < other.def.fallBackFamilies.size();
@@ -250,7 +248,6 @@ public:
inline bool operator==(const Key &other) const
{
return script == other.script
- && screen == other.screen
&& multi == other.multi
&& (!multi || def.fallBackFamilies == other.def.fallBackFamilies)
&& def == other.def;
@@ -266,7 +263,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 fe7dd80e44..261e1d831b 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);
@@ -451,8 +451,7 @@ class QFontDatabasePrivate
public:
QFontDatabasePrivate()
: count(0), families(0),
- fallbacksCache(64),
- reregisterAppFonts(false)
+ fallbacksCache(64)
{ }
~QFontDatabasePrivate() {
@@ -488,7 +487,6 @@ public:
};
QVector<ApplicationFont> applicationFonts;
int addAppFont(const QByteArray &fontData, const QString &fileName);
- bool reregisterAppFonts;
bool isApplicationFont(const QString &fileName);
void invalidate();
@@ -707,7 +705,7 @@ static QStringList familyList(const QFontDef &req)
}
Q_GLOBAL_STATIC(QFontDatabasePrivate, privateDb)
-Q_GLOBAL_STATIC_WITH_ARGS(QMutex, fontDatabaseMutex, (QMutex::Recursive))
+Q_GLOBAL_STATIC(QRecursiveMutex, fontDatabaseMutex)
// used in qguiapplication.cpp
void qt_cleanupFontDatabase()
@@ -719,8 +717,8 @@ void qt_cleanupFontDatabase()
}
}
-// used in qfontengine_x11.cpp
-QMutex *qt_fontdatabase_mutex()
+// used in qfont.cpp
+QRecursiveMutex *qt_fontdatabase_mutex()
{
return fontDatabaseMutex();
}
@@ -793,7 +791,7 @@ QString qt_resolveFontFamilyAlias(const QString &alias)
return alias;
}
-Q_GUI_EXPORT bool qt_isFontFamilyPopulated(const QString &familyName)
+bool qt_isFontFamilyPopulated(const QString &familyName)
{
QFontDatabasePrivate *d = privateDb();
QtFontFamily *f = d->family(familyName, QFontDatabasePrivate::RequestFamily);
@@ -897,15 +895,12 @@ static void initializeDb()
QFontDatabasePrivate *db = privateDb();
// init by asking for the platformfontdb for the first time or after invalidation
- if (!db->count)
+ if (!db->count) {
QGuiApplicationPrivate::platformIntegration()->fontDatabase()->populateFontDatabase();
-
- if (db->reregisterAppFonts) {
for (int i = 0; i < db->applicationFonts.count(); i++) {
if (!db->applicationFonts.at(i).families.isEmpty())
registerFont(&db->applicationFonts[i]);
}
- db->reregisterAppFonts = false;
}
}
@@ -978,7 +973,7 @@ QFontEngine *loadSingleEngine(int script,
if (!engine->supportsScript(QChar::Script(script))) {
qWarning(" OpenType support missing for \"%s\", script %d",
+ qPrintable(def.family), script);
- if (engine->ref.load() == 0)
+ if (engine->ref.loadRelaxed() == 0)
delete engine;
return 0;
}
@@ -1033,11 +1028,7 @@ QFontEngine *loadEngine(int script, const QFontDef &request,
static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt)
{
- QFontDatabasePrivate *db = privateDb();
-
fnt->families = QGuiApplicationPrivate::platformIntegration()->fontDatabase()->addApplicationFont(fnt->data,fnt->fileName);
-
- db->reregisterAppFonts = true;
}
static QtFontStyle *bestStyle(QtFontFoundry *foundry, const QtFontStyle::Key &styleKey,
@@ -2451,13 +2442,18 @@ int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString &
if (font.fileName.isEmpty() && !fontData.isEmpty())
font.fileName = QLatin1String(":qmemoryfonts/") + QString::number(i);
+ bool wasEmpty = privateDb()->count == 0;
registerFont(&font);
if (font.families.isEmpty())
return -1;
applicationFonts[i] = font;
- invalidate();
+ // If the cache has not yet been populated, we need to reload the application font later
+ if (wasEmpty)
+ invalidate();
+ else
+ emit qApp->fontDatabaseChanged();
return i;
}
@@ -2598,7 +2594,6 @@ bool QFontDatabase::removeApplicationFont(int handle)
db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont();
- db->reregisterAppFonts = true;
db->invalidate();
return true;
}
@@ -2689,7 +2684,7 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script)
QtFontDesc desc;
QList<int> blackListed;
int index = match(multi ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed);
- if (index < 0 && QGuiApplicationPrivate::platformIntegration()->fontDatabase()->populateFamilyAliases()) {
+ if (index < 0 && QGuiApplicationPrivate::platformIntegration()->fontDatabase()->populateFamilyAliases(family_name)) {
// We populated familiy aliases (e.g. localized families), so try again
index = match(multi ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed);
}
@@ -2827,7 +2822,7 @@ void QFontDatabase::load(const QFontPrivate *d, int script)
fe = QFontDatabase::findFont(req, script);
if (fe) {
if (fe->type() == QFontEngine::Box && !req.families.at(0).isEmpty()) {
- if (fe->ref.load() == 0)
+ if (fe->ref.loadRelaxed() == 0)
delete fe;
fe = 0;
} else {
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp
index e59d425cb6..403a0510fa 100644
--- a/src/gui/text/qfontengine.cpp
+++ b/src/gui/text/qfontengine.cpp
@@ -1040,15 +1040,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;
@@ -1065,8 +1065,8 @@ QFontEngineGlyphCache *QFontEngine::glyphCache(const void *context,
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()
&& (format != Format_ARGB || color == cache->color())
&& qtransform_equals_no_translate(cache->m_transform, transform)) {
@@ -1200,7 +1200,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 48dcdbeff7..a5c78d5372 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"
@@ -236,7 +235,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;
@@ -246,7 +245,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;
@@ -304,33 +303,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_
@@ -381,7 +380,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:
@@ -401,7 +400,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);
@@ -469,7 +468,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.cpp b/src/gui/text/qfontmetrics.cpp
index c8dc8d676e..d3e4f11e8c 100644
--- a/src/gui/text/qfontmetrics.cpp
+++ b/src/gui/text/qfontmetrics.cpp
@@ -185,11 +185,9 @@ QFontMetrics::QFontMetrics(const QFont &font, const QPaintDevice *paintdevice)
#endif
{
const int dpi = paintdevice ? paintdevice->logicalDpiY() : qt_defaultDpi();
- const int screen = 0;
- if (font.d->dpi != dpi || font.d->screen != screen ) {
+ if (font.d->dpi != dpi) {
d = new QFontPrivate(*font.d);
d->dpi = dpi;
- d->screen = screen;
} else {
d = font.d;
}
@@ -1036,8 +1034,15 @@ int QFontMetrics::lineWidth() const
return qRound(engine->lineThickness());
}
+/*!
+ \since 5.14
-
+ Returns the font DPI.
+*/
+qreal QFontMetrics::fontDpi() const
+{
+ return d->dpi;
+}
/*****************************************************************************
QFontMetricsF member functions
@@ -1171,11 +1176,9 @@ QFontMetricsF::QFontMetricsF(const QFont &font, const QPaintDevice *paintdevice)
#endif
{
int dpi = paintdevice ? paintdevice->logicalDpiY() : qt_defaultDpi();
- const int screen = 0;
- if (font.d->dpi != dpi || font.d->screen != screen ) {
+ if (font.d->dpi != dpi) {
d = new QFontPrivate(*font.d);
d->dpi = dpi;
- d->screen = screen;
} else {
d = font.d;
}
@@ -1913,4 +1916,14 @@ qreal QFontMetricsF::lineWidth() const
return engine->lineThickness().toReal();
}
+/*!
+ \since 5.14
+
+ Returns the font DPI.
+*/
+qreal QFontMetricsF::fontDpi() const
+{
+ return d->dpi;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h
index 61931fa9bc..e92a1514a1 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;
@@ -137,6 +135,8 @@ public:
int strikeOutPos() const;
int lineWidth() const;
+ qreal fontDpi() const;
+
bool operator==(const QFontMetrics &other) const;
inline bool operator !=(const QFontMetrics &other) const { return !operator==(other); }
@@ -172,12 +172,10 @@ public:
QFontMetricsF &operator=(const QFontMetricsF &);
QFontMetricsF &operator=(const QFontMetrics &);
-#ifdef Q_COMPILER_RVALUE_REFS
- inline QFontMetricsF &operator=(QFontMetricsF &&other)
+ inline QFontMetricsF &operator=(QFontMetricsF &&other) noexcept
{ qSwap(d, other.d); return *this; }
-#endif
- void swap(QFontMetricsF &other) { qSwap(d, other.d); }
+ void swap(QFontMetricsF &other) noexcept { qSwap(d, other.d); }
qreal ascent() const;
qreal capHeight() const;
@@ -220,6 +218,8 @@ public:
qreal strikeOutPos() const;
qreal lineWidth() const;
+ qreal fontDpi() const;
+
bool operator==(const QFontMetricsF &other) const;
inline bool operator !=(const QFontMetricsF &other) const { return !operator==(other); }
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.cpp b/src/gui/text/qglyphrun.cpp
index bd44e11dce..3c16c3bf62 100644
--- a/src/gui/text/qglyphrun.cpp
+++ b/src/gui/text/qglyphrun.cpp
@@ -137,7 +137,7 @@ QGlyphRun::~QGlyphRun()
*/
void QGlyphRun::detach()
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d.detach();
}
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/qplatformfontdatabase.cpp b/src/gui/text/qplatformfontdatabase.cpp
index a911014a19..90322b24da 100644
--- a/src/gui/text/qplatformfontdatabase.cpp
+++ b/src/gui/text/qplatformfontdatabase.cpp
@@ -60,6 +60,7 @@ void qt_registerFont(const QString &familyname, const QString &stylename,
void qt_registerFontFamily(const QString &familyName);
void qt_registerAliasToFontFamily(const QString &familyName, const QString &alias);
+bool qt_isFontFamilyPopulated(const QString &familyName);
/*!
Registers the pre-rendered QPF2 font contained in the given \a dataArray.
@@ -234,7 +235,7 @@ QSupportedWritingSystems::~QSupportedWritingSystems()
*/
void QSupportedWritingSystems::detach()
{
- if (d->ref.load() != 1) {
+ if (d->ref.loadRelaxed() != 1) {
QWritingSystemsPrivate *newd = new QWritingSystemsPrivate(d);
if (!d->ref.deref())
delete d;
@@ -666,6 +667,16 @@ void QPlatformFontDatabase::registerAliasToFontFamily(const QString &familyName,
}
/*!
+ Helper function that returns true if the font family has already been registered and populated.
+
+ \since 5.14
+*/
+bool QPlatformFontDatabase::isFamilyPopulated(const QString &familyName)
+{
+ return qt_isFontFamilyPopulated(familyName);
+}
+
+/*!
\class QPlatformFontDatabase
\since 5.0
\internal
diff --git a/src/gui/text/qplatformfontdatabase.h b/src/gui/text/qplatformfontdatabase.h
index f4558129a7..f79c5db625 100644
--- a/src/gui/text/qplatformfontdatabase.h
+++ b/src/gui/text/qplatformfontdatabase.h
@@ -104,7 +104,7 @@ class Q_GUI_EXPORT QPlatformFontDatabase
public:
virtual ~QPlatformFontDatabase();
virtual void populateFontDatabase();
- virtual bool populateFamilyAliases() { return false; }
+ virtual bool populateFamilyAliases(const QString &missingFamily) { Q_UNUSED(missingFamily); return false; }
virtual void populateFamily(const QString &familyName);
virtual void invalidate();
@@ -139,6 +139,8 @@ public:
static void registerFontFamily(const QString &familyName);
static void registerAliasToFontFamily(const QString &familyName, const QString &alias);
+
+ static bool isFamilyPopulated(const QString &familyName);
};
QT_END_NAMESPACE
diff --git a/src/gui/text/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..03259a94ed 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,53 +78,53 @@ 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();
}
~QRawFontPrivate()
{
#ifndef QT_NO_DEBUG
- Q_ASSERT(ref.load() == 0);
+ Q_ASSERT(ref.loadRelaxed() == 0);
#endif
cleanUp();
}
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.cpp b/src/gui/text/qstatictext.cpp
index dd894f4d32..490e0b6b8f 100644
--- a/src/gui/text/qstatictext.cpp
+++ b/src/gui/text/qstatictext.cpp
@@ -181,7 +181,7 @@ QStaticText::QStaticText(const QStaticText &other)
*/
QStaticText::~QStaticText()
{
- Q_ASSERT(!data || data->ref.load() >= 1);
+ Q_ASSERT(!data || data->ref.loadRelaxed() >= 1);
}
/*!
@@ -189,7 +189,7 @@ QStaticText::~QStaticText()
*/
void QStaticText::detach()
{
- if (data->ref.load() != 1)
+ if (data->ref.loadRelaxed() != 1)
data.detach();
}
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 2c677dffe0..22c249d604 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>
@@ -1964,9 +1970,12 @@ void QTextDocument::print(QPagedPaintDevice *printer) const
QRectF body = QRectF(QPointF(0, 0), d->pageSize);
QPointF pageNumberPos;
+ qreal sourceDpiX = qt_defaultDpiX();
+ qreal sourceDpiY = qt_defaultDpiY();
+ const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
+ const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
+
if (documentPaginated) {
- qreal sourceDpiX = qt_defaultDpi();
- qreal sourceDpiY = sourceDpiX;
QPaintDevice *dev = doc->documentLayout()->paintDevice();
if (dev) {
@@ -1974,9 +1983,6 @@ void QTextDocument::print(QPagedPaintDevice *printer) const
sourceDpiY = dev->logicalDpiY();
}
- const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
- const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;
-
// scale to dpi
p.scale(dpiScaleX, dpiScaleY);
@@ -2005,15 +2011,21 @@ void QTextDocument::print(QPagedPaintDevice *printer) const
// copy the custom object handlers
layout->d_func()->handlers = documentLayout()->d_func()->handlers;
- int dpiy = p.device()->logicalDpiY();
- int margin = (int) ((2/2.54)*dpiy); // 2 cm margins
+ // 2 cm margins, scaled to device in QTextDocumentLayoutPrivate::layoutFrame
+ const int horizontalMargin = int((2/2.54)*sourceDpiX);
+ const int verticalMargin = int((2/2.54)*sourceDpiY);
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
- fmt.setMargin(margin);
+ fmt.setLeftMargin(horizontalMargin);
+ fmt.setRightMargin(horizontalMargin);
+ fmt.setTopMargin(verticalMargin);
+ fmt.setBottomMargin(verticalMargin);
doc->rootFrame()->setFrameFormat(fmt);
+ // pageNumberPos must be in device coordinates, so scale to device here
+ const int dpiy = p.device()->logicalDpiY();
body = QRectF(0, 0, printer->width(), printer->height());
- pageNumberPos = QPointF(body.width() - margin,
- body.height() - margin
+ pageNumberPos = QPointF(body.width() - horizontalMargin * dpiScaleX,
+ body.height() - verticalMargin * dpiScaleY
+ QFontMetrics(doc->defaultFont(), p.device()).ascent()
+ 5 * dpiy / 72.0);
clonedDoc->setPageSize(body.size());
@@ -2061,8 +2073,9 @@ void QTextDocument::print(QPagedPaintDevice *printer) const
\enum QTextDocument::ResourceType
This enum describes the types of resources that can be loaded by
- QTextDocument's loadResource() function.
+ QTextDocument's loadResource() function or by QTextBrowser::setSource().
+ \value UnknownResource No resource is loaded, or the resource type is not known.
\value HtmlResource The resource contains HTML.
\value ImageResource The resource contains image data.
Currently supported data types are QVariant::Pixmap and
@@ -2072,10 +2085,11 @@ void QTextDocument::print(QPagedPaintDevice *printer) const
The icon needs to be converted to one of the supported types first,
for example using QIcon::pixmap.
\value StyleSheetResource The resource contains CSS.
+ \value MarkdownResource The resource contains Markdown.
\value UserResource The first available value for user defined
resource types.
- \sa loadResource()
+ \sa loadResource(), QTextBrowser::sourceType()
*/
/*!
@@ -2471,9 +2485,19 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
if (format.foreground() != defaultCharFormat.foreground()
&& format.foreground().style() != Qt::NoBrush) {
- html += QLatin1String(" color:");
- html += colorValue(format.foreground().color());
- html += QLatin1Char(';');
+ QBrush brush = format.foreground();
+ if (brush.style() == Qt::TexturePattern) {
+ const bool isPixmap = qHasPixmapTexture(brush);
+ const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
+
+ html += QLatin1String(" -qt-fg-texture-cachekey:");
+ html += QString::number(cacheKey);
+ html += QLatin1String(";");
+ } else {
+ html += QLatin1String(" color:");
+ html += colorValue(brush.color());
+ html += QLatin1Char(';');
+ }
attributesEmitted = true;
}
@@ -2576,51 +2600,43 @@ void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode
html += QLatin1Char('\"');
}
-void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
+static QLatin1String richtextBorderStyleToHtmlBorderStyle(QTextFrameFormat::BorderStyle style)
{
- Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
-
- html += QLatin1String(" border-style:");
-
switch (style) {
case QTextFrameFormat::BorderStyle_None:
- html += QLatin1String("none");
- break;
+ return QLatin1String("none");
case QTextFrameFormat::BorderStyle_Dotted:
- html += QLatin1String("dotted");
- break;
+ return QLatin1String("dotted");
case QTextFrameFormat::BorderStyle_Dashed:
- html += QLatin1String("dashed");
- break;
+ return QLatin1String("dashed");
case QTextFrameFormat::BorderStyle_Solid:
- html += QLatin1String("solid");
- break;
+ return QLatin1String("solid");
case QTextFrameFormat::BorderStyle_Double:
- html += QLatin1String("double");
- break;
+ return QLatin1String("double");
case QTextFrameFormat::BorderStyle_DotDash:
- html += QLatin1String("dot-dash");
- break;
+ return QLatin1String("dot-dash");
case QTextFrameFormat::BorderStyle_DotDotDash:
- html += QLatin1String("dot-dot-dash");
- break;
+ return QLatin1String("dot-dot-dash");
case QTextFrameFormat::BorderStyle_Groove:
- html += QLatin1String("groove");
- break;
+ return QLatin1String("groove");
case QTextFrameFormat::BorderStyle_Ridge:
- html += QLatin1String("ridge");
- break;
+ return QLatin1String("ridge");
case QTextFrameFormat::BorderStyle_Inset:
- html += QLatin1String("inset");
- break;
+ return QLatin1String("inset");
case QTextFrameFormat::BorderStyle_Outset:
- html += QLatin1String("outset");
- break;
+ return QLatin1String("outset");
default:
- Q_ASSERT(false);
- break;
+ Q_UNREACHABLE();
};
+ return QLatin1String("");
+}
+void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style)
+{
+ Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset);
+
+ html += QLatin1String(" border-style:");
+ html += richtextBorderStyleToHtmlBorderStyle(style);
html += QLatin1Char(';');
}
@@ -2733,6 +2749,12 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
if (imgFmt.hasProperty(QTextFormat::ImageName))
emitAttribute("src", imgFmt.name());
+ if (imgFmt.hasProperty(QTextFormat::ImageAltText))
+ emitAttribute("alt", imgFmt.stringProperty(QTextFormat::ImageAltText));
+
+ if (imgFmt.hasProperty(QTextFormat::ImageTitle))
+ emitAttribute("title", imgFmt.stringProperty(QTextFormat::ImageTitle));
+
if (imgFmt.hasProperty(QTextFormat::ImageWidth))
emitAttribute("width", QString::number(imgFmt.width()));
@@ -3181,6 +3203,33 @@ void QTextHtmlExporter::emitTable(const QTextTable *table)
if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding))
styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellTopBorder))
+ styleString += QLatin1String(" border-top:") + QString::number(cellFormat.topBorder()) + QLatin1String("px;");
+ if (cellFormat.hasProperty(QTextFormat::TableCellRightBorder))
+ styleString += QLatin1String(" border-right:") + QString::number(cellFormat.rightBorder()) + QLatin1String("px;");
+ if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorder))
+ styleString += QLatin1String(" border-bottom:") + QString::number(cellFormat.bottomBorder()) + QLatin1String("px;");
+ if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorder))
+ styleString += QLatin1String(" border-left:") + QString::number(cellFormat.leftBorder()) + QLatin1String("px;");
+
+ if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderBrush))
+ styleString += QLatin1String(" border-top-color:") + cellFormat.topBorderBrush().color().name() + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderBrush))
+ styleString += QLatin1String(" border-right-color:") + cellFormat.rightBorderBrush().color().name() + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderBrush))
+ styleString += QLatin1String(" border-bottom-color:") + cellFormat.bottomBorderBrush().color().name() + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderBrush))
+ styleString += QLatin1String(" border-left-color:") + cellFormat.leftBorderBrush().color().name() + QLatin1Char(';');
+
+ if (cellFormat.hasProperty(QTextFormat::TableCellTopBorderStyle))
+ styleString += QLatin1String(" border-top-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.topBorderStyle()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellRightBorderStyle))
+ styleString += QLatin1String(" border-right-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.rightBorderStyle()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellBottomBorderStyle))
+ styleString += QLatin1String(" border-bottom-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.bottomBorderStyle()) + QLatin1Char(';');
+ if (cellFormat.hasProperty(QTextFormat::TableCellLeftBorderStyle))
+ styleString += QLatin1String(" border-left-style:") + richtextBorderStyleToHtmlBorderStyle(cellFormat.leftBorderStyle()) + QLatin1Char(';');
+
if (!styleString.isEmpty())
html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"');
@@ -3287,6 +3336,9 @@ void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType
QString::number(format.leftMargin()),
QString::number(format.rightMargin()));
+ if (format.property(QTextFormat::TableBorderCollapse).toBool())
+ html += QLatin1String(" border-collapse:collapse;");
+
if (html.length() == originalHtmlLength) // nothing emitted?
html.chop(styleAttribute.size());
else
@@ -3317,6 +3369,62 @@ QString QTextDocument::toHtml(const QByteArray &encoding) const
#endif // QT_NO_TEXTHTMLPARSER
/*!
+ \since 5.14
+ Returns a string containing a Markdown representation of the document with
+ the given \a features, or an empty string if writing fails for any reason.
+
+ \sa setMarkdown
+*/
+#if QT_CONFIG(textmarkdownwriter)
+QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const
+{
+ QString ret;
+ QTextStream s(&ret);
+ QTextMarkdownWriter w(s, features);
+ if (w.writeAll(this))
+ return ret;
+ return QString();
+}
+#endif
+
+/*!
+ \since 5.14
+ Replaces the entire contents of the document with the given
+ Markdown-formatted text in the \a markdown string, with the given
+ \a features supported. By default, all supported GitHub-style
+ 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..2fadc40cd2 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);
@@ -206,12 +225,15 @@ public:
void print(QPagedPaintDevice *printer) const;
enum ResourceType {
+ UnknownResource = 0,
HtmlResource = 1,
ImageResource = 2,
StyleSheetResource = 3,
+ MarkdownResource = 4,
UserResource = 100
};
+ Q_ENUM(ResourceType)
QVariant resource(int type, const QUrl &name) const;
void addResource(int type, const QUrl &name, const QVariant &resource);
diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp
index 6e535c12b3..a1b1c2e92b 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());
}
@@ -1212,9 +1215,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 d668066091..f4e7a25f22 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;
@@ -357,6 +357,7 @@ public:
void mergeCachedResources(const QTextDocumentPrivate *priv);
+ friend struct QTextHtmlParserNode;
friend class QTextHtmlExporter;
friend class QTextCursor;
};
diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp
index aef4ea1522..742c56382d 100644
--- a/src/gui/text/qtextdocumentfragment.cpp
+++ b/src/gui/text/qtextdocumentfragment.cpp
@@ -576,6 +576,9 @@ bool QTextHtmlImporter::appendNodeText()
&& ch != QChar::Nbsp
&& ch != QChar::ParagraphSeparator) {
+ if (wsm == QTextHtmlParserNode::WhiteSpacePreLine && (ch == QLatin1Char('\n') || ch == QLatin1Char('\r')))
+ compressNextWhitespace = PreserveWhiteSpace;
+
if (compressNextWhitespace == CollapseWhiteSpace)
compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
else if(compressNextWhitespace == RemoveWhiteSpace)
@@ -592,7 +595,9 @@ bool QTextHtmlImporter::appendNodeText()
}
} else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
compressNextWhitespace = RemoveWhiteSpace;
- if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
+ if (wsm == QTextHtmlParserNode::WhiteSpacePreLine && (ch == QLatin1Char('\n') || ch == QLatin1Char('\r')))
+ { }
+ else if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
ch = QChar::Nbsp;
else
ch = QLatin1Char(' ');
@@ -605,6 +610,8 @@ bool QTextHtmlImporter::appendNodeText()
|| ch == QChar::ParagraphSeparator) {
if (!textToInsert.isEmpty()) {
+ if (wsm == QTextHtmlParserNode::WhiteSpacePreLine && textToInsert.at(textToInsert.length() - 1) == QLatin1Char(' '))
+ textToInsert = textToInsert.chopped(1);
cursor.insertText(textToInsert, format);
textToInsert.clear();
}
@@ -715,6 +722,10 @@ QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
case Html_img: {
QTextImageFormat fmt;
fmt.setName(currentNode->imageName);
+ if (!currentNode->text.isEmpty())
+ fmt.setProperty(QTextFormat::ImageTitle, currentNode->text);
+ if (!currentNode->imageAlt.isEmpty())
+ fmt.setProperty(QTextFormat::ImageAltText, currentNode->imageAlt);
fmt.merge(currentNode->charFormat);
@@ -975,6 +986,7 @@ QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
tableFmt.setColumns(table.columns);
tableFmt.setColumnWidthConstraints(columnWidths);
tableFmt.setHeaderRowCount(tableHeaderRowCount);
+ tableFmt.setBorderCollapse(node.borderCollapse);
fmt = tableFmt;
}
@@ -1050,6 +1062,33 @@ QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
fmt.setLeftPadding(leftPadding(currentNodeIdx));
if (rightPadding(currentNodeIdx) >= 0)
fmt.setRightPadding(rightPadding(currentNodeIdx));
+#ifndef QT_NO_CSSPARSER
+ if (tableCellBorder(currentNodeIdx, QCss::TopEdge) > 0)
+ fmt.setTopBorder(tableCellBorder(currentNodeIdx, QCss::TopEdge));
+ if (tableCellBorder(currentNodeIdx, QCss::RightEdge) > 0)
+ fmt.setRightBorder(tableCellBorder(currentNodeIdx, QCss::RightEdge));
+ if (tableCellBorder(currentNodeIdx, QCss::BottomEdge) > 0)
+ fmt.setBottomBorder(tableCellBorder(currentNodeIdx, QCss::BottomEdge));
+ if (tableCellBorder(currentNodeIdx, QCss::LeftEdge) > 0)
+ fmt.setLeftBorder(tableCellBorder(currentNodeIdx, QCss::LeftEdge));
+ if (tableCellBorderStyle(currentNodeIdx, QCss::TopEdge) != QTextFrameFormat::BorderStyle_None)
+ fmt.setTopBorderStyle(tableCellBorderStyle(currentNodeIdx, QCss::TopEdge));
+ if (tableCellBorderStyle(currentNodeIdx, QCss::RightEdge) != QTextFrameFormat::BorderStyle_None)
+ fmt.setRightBorderStyle(tableCellBorderStyle(currentNodeIdx, QCss::RightEdge));
+ if (tableCellBorderStyle(currentNodeIdx, QCss::BottomEdge) != QTextFrameFormat::BorderStyle_None)
+ fmt.setBottomBorderStyle(tableCellBorderStyle(currentNodeIdx, QCss::BottomEdge));
+ if (tableCellBorderStyle(currentNodeIdx, QCss::LeftEdge) != QTextFrameFormat::BorderStyle_None)
+ fmt.setLeftBorderStyle(tableCellBorderStyle(currentNodeIdx, QCss::LeftEdge));
+ if (tableCellBorderBrush(currentNodeIdx, QCss::TopEdge) != Qt::NoBrush)
+ fmt.setTopBorderBrush(tableCellBorderBrush(currentNodeIdx, QCss::TopEdge));
+ if (tableCellBorderBrush(currentNodeIdx, QCss::RightEdge) != Qt::NoBrush)
+ fmt.setRightBorderBrush(tableCellBorderBrush(currentNodeIdx, QCss::RightEdge));
+ if (tableCellBorderBrush(currentNodeIdx, QCss::BottomEdge) != Qt::NoBrush)
+ fmt.setBottomBorderBrush(tableCellBorderBrush(currentNodeIdx, QCss::BottomEdge));
+ if (tableCellBorderBrush(currentNodeIdx, QCss::LeftEdge) != Qt::NoBrush)
+ fmt.setLeftBorderBrush(tableCellBorderBrush(currentNodeIdx, QCss::LeftEdge));
+#endif
+
cell.setFormat(fmt);
cursor.setPosition(cell.firstPosition());
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 65cafc7bc2..ed23a4d8d9 100644
--- a/src/gui/text/qtextdocumentlayout.cpp
+++ b/src/gui/text/qtextdocumentlayout.cpp
@@ -155,6 +155,50 @@ struct QTextLayoutStruct {
{ if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = qMax(y, pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY); }
};
+#ifndef QT_NO_CSSPARSER
+// helper struct to collect edge data and priorize edges for border-collapse mode
+struct EdgeData {
+
+ enum EdgeClass {
+ // don't change order, used for comparison
+ ClassInvalid, // queried (adjacent) cell does not exist
+ ClassNone, // no explicit border, no grid, no table border
+ ClassGrid, // 1px grid if drawGrid is true
+ ClassTableBorder, // an outermost edge
+ ClassExplicit // set in cell's format
+ };
+
+ EdgeData(qreal width, const QTextTableCell &cell, QCss::Edge edge, EdgeClass edgeClass) :
+ width(width), cell(cell), edge(edge), edgeClass(edgeClass) {}
+ EdgeData() :
+ width(0), edge(QCss::NumEdges), edgeClass(ClassInvalid) {}
+
+ // used for priorization with qMax
+ bool operator< (const EdgeData &other) const {
+ if (width < other.width) return true;
+ if (width > other.width) return false;
+ if (edgeClass < other.edgeClass) return true;
+ if (edgeClass > other.edgeClass) return false;
+ if (edge == QCss::TopEdge && other.edge == QCss::BottomEdge) return true;
+ if (edge == QCss::BottomEdge && other.edge == QCss::TopEdge) return false;
+ if (edge == QCss::LeftEdge && other.edge == QCss::RightEdge) return true;
+ return false;
+ }
+ bool operator> (const EdgeData &other) const {
+ return other < *this;
+ }
+
+ qreal width;
+ QTextTableCell cell;
+ QCss::Edge edge;
+ EdgeClass edgeClass;
+};
+
+// axisEdgeData is referenced by QTextTableData's inline methods, so predeclare
+class QTextTableData;
+static inline EdgeData axisEdgeData(QTextTable *table, const QTextTableData *td, const QTextTableCell &cell, QCss::Edge edge);
+#endif
+
class QTextTableData : public QTextFrameData
{
public:
@@ -169,8 +213,19 @@ public:
QVector<QFixed> cellVerticalOffsets;
+ // without borderCollapse, those equal QTextFrameData::border;
+ // otherwise the widest outermost cell edge will be used
+ QFixed effectiveLeftBorder;
+ QFixed effectiveTopBorder;
+ QFixed effectiveRightBorder;
+ QFixed effectiveBottomBorder;
+
QFixed headerHeight;
+ QFixed borderCell; // 0 if borderCollapse is enabled, QTextFrameData::border otherwise
+ bool borderCollapse;
+ bool drawGrid;
+
// maps from cell index (row + col * rowCount) to child frames belonging to
// the specific cell
QMultiHash<int, QTextFrame *> childFrameMap;
@@ -182,7 +237,7 @@ public:
inline void calcRowPosition(int row)
{
if (row > 0)
- rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border;
+ rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + borderCell + cellSpacing + borderCell;
}
QRectF cellRect(const QTextTableCell &cell) const;
@@ -198,30 +253,67 @@ public:
}
}
- inline QFixed topPadding(const QTextFormat &format) const
+#ifndef QT_NO_CSSPARSER
+ inline QFixed cellBorderWidth(QTextTable *table, const QTextTableCell &cell, QCss::Edge edge) const
+ {
+ qreal rv = axisEdgeData(table, this, cell, edge).width;
+ if (borderCollapse)
+ rv /= 2; // each cell has to add half of the border's width to its own padding
+ return QFixed::fromReal(rv * deviceScale);
+ }
+#endif
+
+ inline QFixed topPadding(QTextTable *table, const QTextTableCell &cell) const
{
- return paddingProperty(format, QTextFormat::TableCellTopPadding);
+#ifdef QT_NO_CSSPARSER
+ Q_UNUSED(table);
+#endif
+ return paddingProperty(cell.format(), QTextFormat::TableCellTopPadding)
+#ifndef QT_NO_CSSPARSER
+ + cellBorderWidth(table, cell, QCss::TopEdge)
+#endif
+ ;
}
- inline QFixed bottomPadding(const QTextFormat &format) const
+ inline QFixed bottomPadding(QTextTable *table, const QTextTableCell &cell) const
{
- return paddingProperty(format, QTextFormat::TableCellBottomPadding);
+#ifdef QT_NO_CSSPARSER
+ Q_UNUSED(table);
+#endif
+ return paddingProperty(cell.format(), QTextFormat::TableCellBottomPadding)
+#ifndef QT_NO_CSSPARSER
+ + cellBorderWidth(table, cell, QCss::BottomEdge)
+#endif
+ ;
}
- inline QFixed leftPadding(const QTextFormat &format) const
+ inline QFixed leftPadding(QTextTable *table, const QTextTableCell &cell) const
{
- return paddingProperty(format, QTextFormat::TableCellLeftPadding);
+#ifdef QT_NO_CSSPARSER
+ Q_UNUSED(table);
+#endif
+ return paddingProperty(cell.format(), QTextFormat::TableCellLeftPadding)
+#ifndef QT_NO_CSSPARSER
+ + cellBorderWidth(table, cell, QCss::LeftEdge)
+#endif
+ ;
}
- inline QFixed rightPadding(const QTextFormat &format) const
+ inline QFixed rightPadding(QTextTable *table, const QTextTableCell &cell) const
{
- return paddingProperty(format, QTextFormat::TableCellRightPadding);
+#ifdef QT_NO_CSSPARSER
+ Q_UNUSED(table);
+#endif
+ return paddingProperty(cell.format(), QTextFormat::TableCellRightPadding)
+#ifndef QT_NO_CSSPARSER
+ + cellBorderWidth(table, cell, QCss::RightEdge)
+#endif
+ ;
}
- inline QFixedPoint cellPosition(const QTextTableCell &cell) const
+ inline QFixedPoint cellPosition(QTextTable *table, const QTextTableCell &cell) const
{
- const QTextFormat fmt = cell.format();
- return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt));
+ return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(table, cell), topPadding(table, cell));
}
void updateTableSize();
@@ -257,10 +349,10 @@ static bool isFrameFromInlineObject(QTextFrame *f)
void QTextTableData::updateTableSize()
{
- const QFixed effectiveTopMargin = this->topMargin + border + padding;
- const QFixed effectiveBottomMargin = this->bottomMargin + border + padding;
- const QFixed effectiveLeftMargin = this->leftMargin + border + padding;
- const QFixed effectiveRightMargin = this->rightMargin + border + padding;
+ const QFixed effectiveTopMargin = this->topMargin + effectiveTopBorder + padding;
+ const QFixed effectiveBottomMargin = this->bottomMargin + effectiveBottomBorder + padding;
+ const QFixed effectiveLeftMargin = this->leftMargin + effectiveLeftBorder + padding;
+ const QFixed effectiveRightMargin = this->rightMargin + effectiveRightBorder + padding;
size.height = contentsHeight == -1
? rowPositions.constLast() + heights.constLast() + padding + border + cellSpacing + effectiveBottomMargin
: effectiveTopMargin + contentsHeight + effectiveBottomMargin;
@@ -453,6 +545,7 @@ public:
const QTextBlock &bl, bool inRootFrame) const;
void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
const QTextBlock &bl, const QTextCharFormat *selectionFormat) const;
+ void drawTableCellBorder(const QRectF &cellRect, QPainter *painter, QTextTable *table, QTextTableData *td, const QTextTableCell &cell) const;
void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
QTextTable *table, QTextTableData *td, int r, int c,
QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
@@ -719,7 +812,7 @@ QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
*position = cell.firstPosition();
- HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy);
+ HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(table, cell), position, l, accuracy);
if (hp == PointExact)
return hp;
@@ -798,12 +891,45 @@ QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockForm
return QFixed::fromReal(indent * scale * document->indentWidth());
}
+struct BorderPaginator
+{
+ BorderPaginator(QTextDocument *document, const QRectF &rect, qreal topMarginAfterPageBreak, qreal bottomMargin, qreal border) :
+ pageHeight(document->pageSize().height()),
+ topPage(pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0),
+ bottomPage(pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0),
+ rect(rect),
+ topMarginAfterPageBreak(topMarginAfterPageBreak),
+ bottomMargin(bottomMargin), border(border)
+ {}
+
+ QRectF clipRect(int page) const
+ {
+ QRectF clipped = rect.toRect();
+
+ if (topPage != bottomPage) {
+ clipped.setTop(qMax(clipped.top(), page * pageHeight + topMarginAfterPageBreak - border));
+ clipped.setBottom(qMin(clipped.bottom(), (page + 1) * pageHeight - bottomMargin));
+
+ if (clipped.bottom() <= clipped.top())
+ return QRectF();
+ }
+
+ return clipped;
+ }
+
+ qreal pageHeight;
+ int topPage;
+ int bottomPage;
+ QRectF rect;
+ qreal topMarginAfterPageBreak;
+ qreal bottomMargin;
+ qreal border;
+};
+
void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
{
- const qreal pageHeight = document->pageSize().height();
- const int topPage = pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0;
- const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
+ BorderPaginator paginator(document, rect, topMargin, bottomMargin, border);
#ifndef QT_NO_CSSPARSER
QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
@@ -814,16 +940,11 @@ void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rec
bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
painter->setRenderHint(QPainter::Antialiasing);
- for (int i = topPage; i <= bottomPage; ++i) {
- QRectF clipped = rect.toRect();
-
- if (topPage != bottomPage) {
- clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border));
- clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin));
+ for (int i = paginator.topPage; i <= paginator.bottomPage; ++i) {
+ QRectF clipped = paginator.clipRect(i);
+ if (!clipped.isValid())
+ continue;
- if (clipped.bottom() <= clipped.top())
- continue;
- }
#ifndef QT_NO_CSSPARSER
qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
@@ -920,6 +1041,38 @@ static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintCon
}
}
+static bool cellClipTest(QTextTable *table, QTextTableData *td,
+ const QAbstractTextDocumentLayout::PaintContext &cell_context,
+ const QTextTableCell &cell,
+ QRectF cellRect)
+{
+#ifdef QT_NO_CSSPARSER
+ Q_UNUSED(table);
+ Q_UNUSED(cell);
+#endif
+
+ if (!cell_context.clip.isValid())
+ return false;
+
+ if (td->borderCollapse) {
+ // we need to account for the cell borders in the clipping test
+#ifndef QT_NO_CSSPARSER
+ cellRect.adjust(-axisEdgeData(table, td, cell, QCss::LeftEdge).width / 2,
+ -axisEdgeData(table, td, cell, QCss::TopEdge).width / 2,
+ axisEdgeData(table, td, cell, QCss::RightEdge).width / 2,
+ axisEdgeData(table, td, cell, QCss::BottomEdge).width / 2);
+#endif
+ } else {
+ qreal border = td->border.toReal();
+ cellRect.adjust(-border, -border, border, border);
+ }
+
+ if (!cellRect.intersects(cell_context.clip))
+ return true;
+
+ return false;
+}
+
void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
const QAbstractTextDocumentLayout::PaintContext &context,
QTextFrame *frame) const
@@ -931,7 +1084,10 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain
Q_ASSERT(!fd->sizeDirty);
Q_ASSERT(!fd->layoutDirty);
- const QPointF off = offset + fd->position.toPointF();
+ // floor the offset to avoid painting artefacts when drawing adjacent borders
+ // we later also round table cell heights and widths
+ const QPointF off = QPointF(QPointF(offset + fd->position.toPointF()).toPoint());
+
if (context.clip.isValid()
&& (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
|| off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
@@ -979,10 +1135,12 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain
const int tableStartPage = (absYPos / pageHeight).truncate();
const int tableEndPage = ((absYPos + td->size.height) / pageHeight).truncate();
- qreal border = td->border.toReal();
- drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
+ // for borderCollapse draw frame decoration by drawing the outermost
+ // cell edges with width = td->border
+ if (!td->borderCollapse)
+ drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
- // draw the table headers
+ // draw the repeated table headers for table continuation after page breaks
const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
int page = tableStartPage + 1;
while (page <= tableEndPage) {
@@ -996,9 +1154,7 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain
QRectF cellRect = td->cellRect(cell);
cellRect.translate(off.x(), headerOffset);
- // we need to account for the cell border in the clipping test
- int leftAdjust = qMin(qreal(0), 1 - border);
- if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
+ if (cellClipTest(table, td, cell_context, cell, cellRect))
continue;
drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
@@ -1038,9 +1194,7 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain
QRectF cellRect = td->cellRect(cell);
cellRect.translate(off);
- // we need to account for the cell border in the clipping test
- int leftAdjust = qMin(qreal(0), 1 - border);
- if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
+ if (cellClipTest(table, td, cell_context, cell, cellRect))
continue;
drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
@@ -1077,6 +1231,602 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain
return;
}
+#ifndef QT_NO_CSSPARSER
+
+static inline QTextFormat::Property borderPropertyForEdge(QCss::Edge edge)
+{
+ switch (edge) {
+ case QCss::TopEdge:
+ return QTextFormat::TableCellTopBorder;
+ case QCss::BottomEdge:
+ return QTextFormat::TableCellBottomBorder;
+ case QCss::LeftEdge:
+ return QTextFormat::TableCellLeftBorder;
+ case QCss::RightEdge:
+ return QTextFormat::TableCellRightBorder;
+ default:
+ Q_UNREACHABLE();
+ return QTextFormat::UserProperty;
+ }
+}
+
+static inline QTextFormat::Property borderStylePropertyForEdge(QCss::Edge edge)
+{
+ switch (edge) {
+ case QCss::TopEdge:
+ return QTextFormat::TableCellTopBorderStyle;
+ case QCss::BottomEdge:
+ return QTextFormat::TableCellBottomBorderStyle;
+ case QCss::LeftEdge:
+ return QTextFormat::TableCellLeftBorderStyle;
+ case QCss::RightEdge:
+ return QTextFormat::TableCellRightBorderStyle;
+ default:
+ Q_UNREACHABLE();
+ return QTextFormat::UserProperty;
+ }
+}
+
+static inline QCss::Edge adjacentEdge(QCss::Edge edge)
+{
+ switch (edge) {
+ case QCss::TopEdge:
+ return QCss::BottomEdge;
+ case QCss::RightEdge:
+ return QCss::LeftEdge;
+ case QCss::BottomEdge:
+ return QCss::TopEdge;
+ case QCss::LeftEdge:
+ return QCss::RightEdge;
+ default:
+ Q_UNREACHABLE();
+ return QCss::NumEdges;
+ }
+}
+
+static inline bool isSameAxis(QCss::Edge e1, QCss::Edge e2)
+{
+ return e1 == e2 || e1 == adjacentEdge(e2);
+}
+
+static inline bool isVerticalAxis(QCss::Edge e)
+{
+ return e % 2 > 0;
+}
+
+static inline QTextTableCell adjacentCell(QTextTable *table, const QTextTableCell &cell,
+ QCss::Edge edge)
+{
+ int dc = 0;
+ int dr = 0;
+
+ switch (edge) {
+ case QCss::LeftEdge:
+ dc = -1;
+ break;
+ case QCss::RightEdge:
+ dc = cell.columnSpan();
+ break;
+ case QCss::TopEdge:
+ dr = -1;
+ break;
+ case QCss::BottomEdge:
+ dr = cell.rowSpan();
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ // get sibling cell
+ int col = cell.column() + dc;
+ int row = cell.row() + dr;
+
+ if (col < 0 || row < 0 || col >= table->columns() || row >= table->rows())
+ return QTextTableCell();
+ else
+ return table->cellAt(cell.row() + dr, cell.column() + dc);
+}
+
+// returns true if the specified edges of both cells
+// are "one the same line" aka axis.
+//
+// | C0
+// |-----|-----|----|----- < "axis"
+// | C1 | C2 | C3 | C4
+//
+// cell edge competingCell competingEdge result
+// C0 Left C1 Left true
+// C0 Left C2 Left false
+// C0 Bottom C2 Top true
+// C0 Bottom C4 Left INVALID
+static inline bool sharesAxis(const QTextTableCell &cell, QCss::Edge edge,
+ const QTextTableCell &competingCell, QCss::Edge competingCellEdge)
+{
+ Q_ASSERT(isVerticalAxis(edge) == isVerticalAxis(competingCellEdge));
+
+ switch (edge) {
+ case QCss::TopEdge:
+ return cell.row() ==
+ competingCell.row() + (competingCellEdge == QCss::BottomEdge ? competingCell.rowSpan() : 0);
+ case QCss::BottomEdge:
+ return cell.row() + cell.rowSpan() ==
+ competingCell.row() + (competingCellEdge == QCss::TopEdge ? 0 : competingCell.rowSpan());
+ case QCss::LeftEdge:
+ return cell.column() ==
+ competingCell.column() + (competingCellEdge == QCss::RightEdge ? competingCell.columnSpan() : 0);
+ case QCss::RightEdge:
+ return cell.column() + cell.columnSpan() ==
+ competingCell.column() + (competingCellEdge == QCss::LeftEdge ? 0 : competingCell.columnSpan());
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+// returns the applicable EdgeData for the given cell and edge.
+// this is either set explicitly by the cell's format, an activated grid
+// or the general table border width for outermost edges.
+static inline EdgeData cellEdgeData(QTextTable *table, const QTextTableData *td,
+ const QTextTableCell &cell, QCss::Edge edge)
+{
+ if (!cell.isValid()) {
+ // e.g. non-existing adjacent cell
+ return EdgeData();
+ }
+
+ QTextTableCellFormat f = cell.format().toTableCellFormat();
+ if (f.hasProperty(borderStylePropertyForEdge(edge))) {
+ // border style is set
+ double width = 3; // default to 3 like browsers do
+ if (f.hasProperty(borderPropertyForEdge(edge)))
+ width = f.property(borderPropertyForEdge(edge)).toDouble();
+ return EdgeData(width, cell, edge, EdgeData::ClassExplicit);
+ } else if (td->drawGrid) {
+ const bool outermost =
+ (edge == QCss::LeftEdge && cell.column() == 0) ||
+ (edge == QCss::TopEdge && cell.row() == 0) ||
+ (edge == QCss::RightEdge && cell.column() + cell.columnSpan() >= table->columns()) ||
+ (edge == QCss::BottomEdge && cell.row() + cell.rowSpan() >= table->rows());
+
+ if (outermost) {
+ qreal border = table->format().border();
+ if (border > 1.0) {
+ // table border
+ return EdgeData(border, cell, edge, EdgeData::ClassTableBorder);
+ }
+ }
+ // 1px clean grid
+ return EdgeData(1.0, cell, edge, EdgeData::ClassGrid);
+ }
+ else {
+ return EdgeData(0, cell, edge, EdgeData::ClassNone);
+ }
+}
+
+// returns the EdgeData with the larger width of either the cell's edge its adjacent cell's edge
+static inline EdgeData axisEdgeData(QTextTable *table, const QTextTableData *td,
+ const QTextTableCell &cell, QCss::Edge edge)
+{
+ Q_ASSERT(cell.isValid());
+
+ EdgeData result = cellEdgeData(table, td, cell, edge);
+ if (!td->borderCollapse)
+ return result;
+
+ QTextTableCell ac = adjacentCell(table, cell, edge);
+ result = qMax(result, cellEdgeData(table, td, ac, adjacentEdge(edge)));
+
+ bool mustCheckThirdCell = false;
+ if (ac.isValid()) {
+ /* if C0 and C3 don't share the left/top axis, we must
+ * also check C1.
+ *
+ * C0 and C4 don't share the left axis so we have
+ * to take the top edge of C1 (T1) into account
+ * because this might be wider than C0's bottom
+ * edge (B0). For the sake of simplicity we skip
+ * checking T2 and T3.
+ *
+ * | C0
+ * |-----|-----|----|-----
+ * | C1 | C2 | C3 | C4
+ *
+ * width(T4) = max(T4, B0, T1) (T2 and T3 won't be checked)
+ */
+ switch (edge) {
+ case QCss::TopEdge:
+ case QCss::BottomEdge:
+ mustCheckThirdCell = !sharesAxis(cell, QCss::LeftEdge, ac, QCss::LeftEdge);
+ break;
+ case QCss::LeftEdge:
+ case QCss::RightEdge:
+ mustCheckThirdCell = !sharesAxis(cell, QCss::TopEdge, ac, QCss::TopEdge);
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (mustCheckThirdCell)
+ result = qMax(result, cellEdgeData(table, td, adjacentCell(table, ac, adjacentEdge(edge)), edge));
+
+ return result;
+}
+
+// checks an edge's joined competing edge according to priority rules and
+// adjusts maxCompetingEdgeData and maxOrthogonalEdgeData
+static inline void checkJoinedEdge(QTextTable *table, const QTextTableData *td, const QTextTableCell &cell,
+ QCss::Edge competingEdge,
+ const EdgeData &edgeData,
+ bool couldHaveContinuation,
+ EdgeData *maxCompetingEdgeData,
+ EdgeData *maxOrthogonalEdgeData)
+{
+ EdgeData competingEdgeData = axisEdgeData(table, td, cell, competingEdge);
+
+ if (competingEdgeData > edgeData) {
+ *maxCompetingEdgeData = competingEdgeData;
+ } else if (competingEdgeData.width == edgeData.width) {
+ if ((isSameAxis(edgeData.edge, competingEdge) && couldHaveContinuation)
+ || (!isVerticalAxis(edgeData.edge) && isVerticalAxis(competingEdge)) /* both widths are equal, vertical edge has priority */ ) {
+ *maxCompetingEdgeData = competingEdgeData;
+ }
+ }
+
+ if (maxOrthogonalEdgeData && competingEdgeData.width > maxOrthogonalEdgeData->width)
+ *maxOrthogonalEdgeData = competingEdgeData;
+}
+
+// the offset to make adjacent edges overlap in border collapse mode
+static inline qreal collapseOffset(const QTextDocumentLayoutPrivate *p, const EdgeData &w)
+{
+ return p->scaleToDevice(w.width) / 2.0;
+}
+
+// returns the offset that must be applied to the edge's
+// anchor (start point or end point) to avoid overlapping edges.
+//
+// Example 1:
+// 2
+// 2
+// 11111144444444 4 = top edge of cell, 4 pixels width
+// 3 3 = right edge of cell, 3 pixels width
+// 3 cell 4
+//
+// cell 4's top border is the widest border and will be
+// drawn with horiz. offset = -3/2 whereas its left border
+// of width 3 will be drawn with vert. offset = +4/2.
+//
+// Example 2:
+// 2
+// 2
+// 11111143333333
+// 4
+// 4 cell 4
+//
+// cell 4's left border is the widest and will be drawn
+// with vert. offset = -3/2 whereas its top border
+// of of width 3 will be drawn with hor. offset = +4/2.
+//
+// couldHaveContinuation: true for "end" anchor of an edge:
+// C
+// AAAAABBBBBB
+// D
+// width(A) == width(B) we consider B to be a continuation of A, so that B wins
+// and will be painted. A would only be painted including the right anchor if
+// there was no edge B (due to a rowspan or the axis C-D being the table's right
+// border).
+//
+// ignoreEdgesAbove: true if an egde (left, right or top) for the first row
+// after a table page break should be painted. In this case the edges of the
+// row above must be ignored.
+static inline double prioritizedEdgeAnchorOffset(const QTextDocumentLayoutPrivate *p,
+ QTextTable *table, const QTextTableData *td,
+ const QTextTableCell &cell,
+ const EdgeData &edgeData,
+ QCss::Edge orthogonalEdge,
+ bool couldHaveContinuation,
+ bool ignoreEdgesAbove)
+{
+ EdgeData maxCompetingEdgeData;
+ EdgeData maxOrthogonalEdgeData;
+ QTextTableCell competingCell;
+
+ // reference scenario for the inline comments:
+ // - edgeData being the top "T0" edge of C0
+ // - right anchor is '+', orthogonal edge is "R0"
+ // B C3 R|L C2 B
+ // ------+------
+ // T C0 R|L C1 T
+
+ // C0: T0/B3
+ // this is "edgeData"
+
+ // C0: R0/L1
+ checkJoinedEdge(table, td, cell, orthogonalEdge, edgeData, false,
+ &maxCompetingEdgeData, &maxOrthogonalEdgeData);
+
+ if (td->borderCollapse) {
+ // C1: T1/B2
+ if (!isVerticalAxis(edgeData.edge) || !ignoreEdgesAbove) {
+ competingCell = adjacentCell(table, cell, orthogonalEdge);
+ if (competingCell.isValid()) {
+ checkJoinedEdge(table, td, competingCell, edgeData.edge, edgeData, couldHaveContinuation,
+ &maxCompetingEdgeData, 0);
+ }
+ }
+
+ // C3: R3/L2
+ if (edgeData.edge != QCss::TopEdge || !ignoreEdgesAbove) {
+ competingCell = adjacentCell(table, cell, edgeData.edge);
+ if (competingCell.isValid() && sharesAxis(cell, orthogonalEdge, competingCell, orthogonalEdge)) {
+ checkJoinedEdge(table, td, competingCell, orthogonalEdge, edgeData, false,
+ &maxCompetingEdgeData, &maxOrthogonalEdgeData);
+ }
+ }
+ }
+
+ // wider edge has priority
+ bool hasPriority = edgeData > maxCompetingEdgeData;
+
+ if (td->borderCollapse) {
+ qreal offset = collapseOffset(p, maxOrthogonalEdgeData);
+ return hasPriority ? -offset : offset;
+ }
+ else
+ return hasPriority ? 0 : p->scaleToDevice(maxOrthogonalEdgeData.width);
+}
+
+// draw one edge of the given cell
+//
+// these options are for pagination / pagebreak handling:
+//
+// forceHeaderRow: true for all rows directly below a (repeated) header row.
+// if the table has headers the first row after a page break must check against
+// the last table header's row, not its actual predecessor.
+//
+// adjustTopAnchor: false for rows that are a continuation of a row after a page break
+// only evaluated for left/right edges
+//
+// adjustBottomAnchor: false for rows that will continue after a page break
+// only evaluated for left/right edges
+//
+// ignoreEdgesAbove: true if a row starts on top of the page and the
+// bottom edges of the prior row can therefore be ignored.
+static inline
+void drawCellBorder(const QTextDocumentLayoutPrivate *p, QPainter *painter,
+ QTextTable *table, const QTextTableData *td, const QTextTableCell &cell,
+ const QRectF &borderRect, QCss::Edge edge,
+ int forceHeaderRow, bool adjustTopAnchor, bool adjustBottomAnchor,
+ bool ignoreEdgesAbove)
+{
+ QPointF p1, p2;
+ qreal wh = 0;
+ qreal wv = 0;
+ EdgeData edgeData = axisEdgeData(table, td, cell, edge);
+
+ if (edgeData.width == 0)
+ return;
+
+ QTextTableCellFormat fmt = edgeData.cell.format().toTableCellFormat();
+ QTextFrameFormat::BorderStyle borderStyle = QTextFrameFormat::BorderStyle_None;
+ QBrush brush;
+
+ if (edgeData.edgeClass != EdgeData::ClassExplicit && td->drawGrid) {
+ borderStyle = QTextFrameFormat::BorderStyle_Solid;
+ brush = table->format().borderBrush();
+ }
+ else {
+ switch (edgeData.edge) {
+ case QCss::TopEdge:
+ brush = fmt.topBorderBrush();
+ borderStyle = fmt.topBorderStyle();
+ break;
+ case QCss::BottomEdge:
+ brush = fmt.bottomBorderBrush();
+ borderStyle = fmt.bottomBorderStyle();
+ break;
+ case QCss::LeftEdge:
+ brush = fmt.leftBorderBrush();
+ borderStyle = fmt.leftBorderStyle();
+ break;
+ case QCss::RightEdge:
+ brush = fmt.rightBorderBrush();
+ borderStyle = fmt.rightBorderStyle();
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (borderStyle == QTextFrameFormat::BorderStyle_None)
+ return;
+
+ // assume black if not explicit brush is set
+ if (brush.style() == Qt::NoBrush)
+ brush = Qt::black;
+
+ QTextTableCell cellOrHeader = cell;
+ if (forceHeaderRow != -1)
+ cellOrHeader = table->cellAt(forceHeaderRow, cell.column());
+
+ // adjust start and end anchors (e.g. left/right for top) according to priority rules
+ switch (edge) {
+ case QCss::TopEdge:
+ wv = p->scaleToDevice(edgeData.width);
+ p1 = borderRect.topLeft()
+ + QPointF(qFloor(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::LeftEdge, false, ignoreEdgesAbove)), 0);
+ p2 = borderRect.topRight()
+ + QPointF(-qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::RightEdge, true, ignoreEdgesAbove)), 0);
+ break;
+ case QCss::BottomEdge:
+ wv = p->scaleToDevice(edgeData.width);
+ p1 = borderRect.bottomLeft()
+ + QPointF(qFloor(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::LeftEdge, false, false)), -wv);
+ p2 = borderRect.bottomRight()
+ + QPointF(-qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::RightEdge, true, false)), -wv);
+ break;
+ case QCss::LeftEdge:
+ wh = p->scaleToDevice(edgeData.width);
+ p1 = borderRect.topLeft()
+ + QPointF(0, adjustTopAnchor ? qFloor(prioritizedEdgeAnchorOffset(p, table, td, cellOrHeader, edgeData,
+ forceHeaderRow != -1 ? QCss::BottomEdge : QCss::TopEdge,
+ false, ignoreEdgesAbove))
+ : 0);
+ p2 = borderRect.bottomLeft()
+ + QPointF(0, adjustBottomAnchor ? -qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::BottomEdge, true, false))
+ : 0);
+ break;
+ case QCss::RightEdge:
+ wh = p->scaleToDevice(edgeData.width);
+ p1 = borderRect.topRight()
+ + QPointF(-wh, adjustTopAnchor ? qFloor(prioritizedEdgeAnchorOffset(p, table, td, cellOrHeader, edgeData,
+ forceHeaderRow != -1 ? QCss::BottomEdge : QCss::TopEdge,
+ false, ignoreEdgesAbove))
+ : 0);
+ p2 = borderRect.bottomRight()
+ + QPointF(-wh, adjustBottomAnchor ? -qCeil(prioritizedEdgeAnchorOffset(p, table, td, cell, edgeData, QCss::BottomEdge, true, false))
+ : 0);
+ break;
+ default: break;
+ }
+
+ // for borderCollapse move edge width/2 pixel out of the borderRect
+ // so that it shares space with the adjacent cell's edge.
+ // to avoid fractional offsets, qCeil/qFloor is used
+ if (td->borderCollapse) {
+ QPointF offset;
+ switch (edge) {
+ case QCss::TopEdge:
+ offset = QPointF(0, -qCeil(collapseOffset(p, edgeData)));
+ break;
+ case QCss::BottomEdge:
+ offset = QPointF(0, qFloor(collapseOffset(p, edgeData)));
+ break;
+ case QCss::LeftEdge:
+ offset = QPointF(-qCeil(collapseOffset(p, edgeData)), 0);
+ break;
+ case QCss::RightEdge:
+ offset = QPointF(qFloor(collapseOffset(p, edgeData)), 0);
+ break;
+ default: break;
+ }
+ p1 += offset;
+ p2 += offset;
+ }
+
+ QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(borderStyle + 1);
+
+// this reveals errors in the drawing logic
+#ifdef COLLAPSE_DEBUG
+ QColor c = brush.color();
+ c.setAlpha(150);
+ brush.setColor(c);
+#endif
+
+ qDrawEdge(painter, p1.x(), p1.y(), p2.x() + wh, p2.y() + wv, 0, 0, edge, cssStyle, brush);
+}
+#endif
+
+void QTextDocumentLayoutPrivate::drawTableCellBorder(const QRectF &cellRect, QPainter *painter,
+ QTextTable *table, QTextTableData *td,
+ const QTextTableCell &cell) const
+{
+#ifndef QT_NO_CSSPARSER
+ qreal topMarginAfterPageBreak = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
+ qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
+
+ const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
+ if (headerRowCount > 0 && cell.row() >= headerRowCount)
+ topMarginAfterPageBreak += td->headerHeight.toReal();
+
+ BorderPaginator paginator(document, cellRect, topMarginAfterPageBreak, bottomMargin, 0);
+
+ bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ // paint cell borders for every page the cell appears on
+ for (int page = paginator.topPage; page <= paginator.bottomPage; ++page) {
+ const QRectF clipped = paginator.clipRect(page);
+ if (!clipped.isValid())
+ continue;
+
+ const qreal offset = cellRect.top() - td->rowPositions.at(cell.row()).toReal();
+ const int lastHeaderRow = table->format().headerRowCount() - 1;
+ const bool tableHasHeader = table->format().headerRowCount() > 0;
+ const bool isHeaderRow = cell.row() < table->format().headerRowCount();
+ const bool isFirstRow = cell.row() == lastHeaderRow + 1;
+ const bool isLastRow = cell.row() + cell.rowSpan() >= table->rows();
+ const bool previousRowOnPreviousPage = !isFirstRow
+ && !isHeaderRow
+ && BorderPaginator(document,
+ td->cellRect(adjacentCell(table, cell, QCss::TopEdge)).translated(0, offset),
+ topMarginAfterPageBreak,
+ bottomMargin,
+ 0).bottomPage < page;
+ const bool nextRowOnNextPage = !isLastRow
+ && BorderPaginator(document,
+ td->cellRect(adjacentCell(table, cell, QCss::BottomEdge)).translated(0, offset),
+ topMarginAfterPageBreak,
+ bottomMargin,
+ 0).topPage > page;
+ const bool rowStartsOnPage = page == paginator.topPage;
+ const bool rowEndsOnPage = page == paginator.bottomPage;
+ const bool rowStartsOnPageTop = !tableHasHeader
+ && rowStartsOnPage
+ && previousRowOnPreviousPage;
+ const bool rowStartsOnPageBelowHeader = tableHasHeader
+ && rowStartsOnPage
+ && previousRowOnPreviousPage;
+
+ const bool suppressTopBorder = td->borderCollapse
+ ? !isHeaderRow && (!rowStartsOnPage || rowStartsOnPageBelowHeader)
+ : !rowStartsOnPage;
+ const bool suppressBottomBorder = td->borderCollapse
+ ? !isHeaderRow && (!rowEndsOnPage || nextRowOnNextPage)
+ : !rowEndsOnPage;
+ const bool doNotAdjustTopAnchor = td->borderCollapse
+ ? !tableHasHeader && !rowStartsOnPage
+ : !rowStartsOnPage;
+ const bool doNotAdjustBottomAnchor = suppressBottomBorder;
+
+ if (!suppressTopBorder) {
+ drawCellBorder(this, painter, table, td, cell, clipped, QCss::TopEdge,
+ -1, true, true, rowStartsOnPageTop);
+ }
+
+ drawCellBorder(this, painter, table, td, cell, clipped, QCss::LeftEdge,
+ suppressTopBorder ? lastHeaderRow : -1,
+ !doNotAdjustTopAnchor,
+ !doNotAdjustBottomAnchor,
+ rowStartsOnPageTop);
+ drawCellBorder(this, painter, table, td, cell, clipped, QCss::RightEdge,
+ suppressTopBorder ? lastHeaderRow : -1,
+ !doNotAdjustTopAnchor,
+ !doNotAdjustBottomAnchor,
+ rowStartsOnPageTop);
+
+ if (!suppressBottomBorder) {
+ drawCellBorder(this, painter, table, td, cell, clipped, QCss::BottomEdge,
+ -1, true, true, false);
+ }
+ }
+
+ if (turn_off_antialiasing)
+ painter->setRenderHint(QPainter::Antialiasing, false);
+#else
+ Q_UNUSED(cell);
+ Q_UNUSED(cellRect);
+ Q_UNUSED(painter);
+ Q_UNUSED(table);
+ Q_UNUSED(td);
+ Q_UNUSED(cell);
+#endif
+}
+
void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
QTextTable *table, QTextTableData *td, int r, int c,
QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
@@ -1095,9 +1845,8 @@ void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter
return;
}
- QTextFormat fmt = cell.format();
- const QFixed leftPadding = td->leftPadding(fmt);
- const QFixed topPadding = td->topPadding(fmt);
+ const QFixed leftPadding = td->leftPadding(table, cell);
+ const QFixed topPadding = td->topPadding(table, cell);
qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
@@ -1106,7 +1855,7 @@ void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter
if (r >= headerRowCount)
topMargin += td->headerHeight.toReal();
- if (td->border != 0) {
+ if (!td->borderCollapse && td->border != 0) {
const QBrush oldBrush = painter->brush();
const QPen oldPen = painter->pen();
@@ -1172,6 +1921,9 @@ void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter
painter->setBrushOrigin(cellRect.topLeft());
}
+ // paint over the background - otherwise we would have to adjust the background paint cellRect for the border values
+ drawTableCellBorder(cellRect, painter, table, td, cell);
+
const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
@@ -1446,6 +2198,21 @@ void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *p
QBrush brush = context.palette.brush(QPalette::Text);
+ bool marker = bl.blockFormat().marker() != QTextBlockFormat::MarkerType::NoMarker;
+ if (marker) {
+ int adj = fontMetrics.lineSpacing() / 6;
+ r.adjust(-adj, 0, -adj, 0);
+ if (bl.blockFormat().marker() == QTextBlockFormat::MarkerType::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:
@@ -1466,16 +2233,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;
@@ -1519,8 +2291,7 @@ QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QT
layoutStruct.maximumWidth = QFIXED_MAX;
layoutStruct.y = 0;
- const QTextFormat fmt = cell.format();
- const QFixed topPadding = td->topPadding(fmt);
+ const QFixed topPadding = td->topPadding(t, cell);
if (withPageBreaks) {
layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
}
@@ -1538,8 +2309,22 @@ QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QT
if (layoutStruct.pageHeight < 0 || !withPageBreaks)
layoutStruct.pageHeight = QFIXED_MAX;
const int currentPage = layoutStruct.currentPage();
- layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
- layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
+
+ layoutStruct.pageTopMargin = td->effectiveTopMargin
+ + td->cellSpacing
+ + td->border
+ + td->paddingProperty(cell.format(), QTextFormat::TableCellTopPadding); // top cell-border is not repeated
+
+#ifndef QT_NO_CSSPARSER
+ const int headerRowCount = t->format().headerRowCount();
+ if (td->borderCollapse && headerRowCount > 0) {
+ // consider the header row's bottom edge width
+ qreal headerRowBottomBorderWidth = axisEdgeData(t, td, t->cellAt(headerRowCount - 1, cell.column()), QCss::BottomEdge).width;
+ layoutStruct.pageTopMargin += QFixed::fromReal(scaleToDevice(headerRowBottomBorderWidth) / 2);
+ }
+#endif
+
+ layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->effectiveBottomBorder + td->bottomPadding(t, cell);
layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
layoutStruct.fullLayout = true;
@@ -1585,6 +2370,17 @@ QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QT
return layoutStruct;
}
+#ifndef QT_NO_CSSPARSER
+static inline void findWidestOutermostBorder(QTextTable *table, QTextTableData *td,
+ const QTextTableCell &cell, QCss::Edge edge,
+ qreal *outerBorders)
+{
+ EdgeData w = cellEdgeData(table, td, cell, edge);
+ if (w.width > outerBorders[edge])
+ outerBorders[edge] = w.width;
+}
+#endif
+
QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
{
qCDebug(lcTable) << "layoutTable from" << layoutFrom << "to" << layoutTo << "parentY" << parentY;
@@ -1601,7 +2397,7 @@ QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom
for (int i = 0; i < children.count(); ++i) {
QTextFrame *frame = children.at(i);
QTextTableCell cell = table->cellAt(frame->firstPosition());
- td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
+ td->childFrameMap.insert(cell.row() + cell.column() * rows, frame);
}
}
@@ -1610,12 +2406,49 @@ QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom
columnWidthConstraints.resize(columns);
Q_ASSERT(columnWidthConstraints.count() == columns);
- const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
+ // borderCollapse will disable drawing the html4 style table cell borders
+ // and draw a 1px grid instead. This also sets a fixed cellspacing
+ // of 1px if border > 0 (for the grid) and ignore any explicitly set
+ // cellspacing.
+ td->borderCollapse = fmt.borderCollapse();
+ td->borderCell = td->borderCollapse ? 0 : td->border;
+ const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(td->borderCollapse ? 0 : fmt.cellSpacing())).round();
+
+ td->drawGrid = (td->borderCollapse && fmt.border() >= 1);
+
+ td->effectiveTopBorder = td->effectiveBottomBorder = td->effectiveLeftBorder = td->effectiveRightBorder = td->border;
+
+#ifndef QT_NO_CSSPARSER
+ if (td->borderCollapse) {
+ // find the widest borders of the outermost cells
+ qreal outerBorders[QCss::NumEdges];
+ for (int i = 0; i < QCss::NumEdges; ++i)
+ outerBorders[i] = 0;
+
+ for (int r = 0; r < rows; ++r) {
+ if (r == 0) {
+ for (int c = 0; c < columns; ++c)
+ findWidestOutermostBorder(table, td, table->cellAt(r, c), QCss::TopEdge, outerBorders);
+ }
+ if (r == rows - 1) {
+ for (int c = 0; c < columns; ++c)
+ findWidestOutermostBorder(table, td, table->cellAt(r, c), QCss::BottomEdge, outerBorders);
+ }
+ findWidestOutermostBorder(table, td, table->cellAt(r, 0), QCss::LeftEdge, outerBorders);
+ findWidestOutermostBorder(table, td, table->cellAt(r, columns - 1), QCss::RightEdge, outerBorders);
+ }
+ td->effectiveTopBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::TopEdge] / 2)).round();
+ td->effectiveBottomBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::BottomEdge] / 2)).round();
+ td->effectiveLeftBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::LeftEdge] / 2)).round();
+ td->effectiveRightBorder = QFixed::fromReal(scaleToDevice(outerBorders[QCss::RightEdge] / 2)).round();
+ }
+#endif
+
td->deviceScale = scaleToDevice(qreal(1));
td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
- const QFixed leftMargin = td->leftMargin + td->border + td->padding;
- const QFixed rightMargin = td->rightMargin + td->border + td->padding;
- const QFixed topMargin = td->topMargin + td->border + td->padding;
+ const QFixed leftMargin = td->leftMargin + td->padding + td->effectiveLeftBorder;
+ const QFixed rightMargin = td->rightMargin + td->padding + td->effectiveRightBorder;
+ const QFixed topMargin = td->topMargin + td->padding + td->effectiveTopBorder;
const QFixed absoluteTableY = parentY + td->position.y;
@@ -1625,11 +2458,17 @@ recalc_minmax_widths:
QFixed remainingWidth = td->contentsWidth;
// two (vertical) borders per cell per column
- remainingWidth -= columns * 2 * td->border;
+ remainingWidth -= columns * 2 * td->borderCell;
// inter-cell spacing
remainingWidth -= (columns - 1) * cellSpacing;
// cell spacing at the left and right hand side
remainingWidth -= 2 * cellSpacing;
+
+ if (td->borderCollapse) {
+ remainingWidth -= td->effectiveLeftBorder;
+ remainingWidth -= td->effectiveRightBorder;
+ }
+
// remember the width used to distribute to percentaged columns
const QFixed initialTotalWidth = remainingWidth;
@@ -1654,9 +2493,8 @@ recalc_minmax_widths:
if (cspan > 1 && i != cell.column())
continue;
- const QTextFormat fmt = cell.format();
- const QFixed leftPadding = td->leftPadding(fmt);
- const QFixed rightPadding = td->rightPadding(fmt);
+ const QFixed leftPadding = td->leftPadding(table, cell);
+ const QFixed rightPadding = td->rightPadding(table, cell);
const QFixed widthPadding = leftPadding + rightPadding;
// to figure out the min and the max width lay out the cell at
@@ -1671,7 +2509,8 @@ recalc_minmax_widths:
for (int n = 0; n < cspan; ++n) {
const int col = i + n;
QFixed w = widthToDistribute / (cspan - n);
- td->minWidths[col] = qMax(td->minWidths.at(col), w);
+ // ceil to avoid going below minWidth when rounding all column widths later
+ td->minWidths[col] = qMax(td->minWidths.at(col), w).ceil();
widthToDistribute -= td->minWidths.at(col);
if (widthToDistribute <= 0)
break;
@@ -1687,6 +2526,12 @@ recalc_minmax_widths:
if (maxW == QFIXED_MAX)
continue;
+ // for variable columns the maxWidth will later be considered as the
+ // column width (column width = content width). We must avoid that the
+ // pixel-alignment rounding step floors this value and thus the text
+ // rendering later erroneously wraps the content.
+ maxW = maxW.ceil();
+
widthToDistribute = maxW;
for (int n = 0; n < cspan; ++n) {
const int col = i + n;
@@ -1777,11 +2622,23 @@ recalc_minmax_widths:
}
}
+ // in order to get a correct border rendering we must ensure that the distance between
+ // two cells is exactly 2 * td->cellBorder pixel. we do this by rounding the calculated width
+ // values here.
+ // to minimize the total rounding error we propagate the rounding error for each width
+ // to its successor.
+ QFixed error = 0;
+ for (int i = 0; i < columns; ++i) {
+ QFixed orig = td->widths[i];
+ td->widths[i] = (td->widths[i] - error).round();
+ error = td->widths[i] - orig;
+ }
+
td->columnPositions.resize(columns);
td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
for (int i = 1; i < columns; ++i)
- td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
+ td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->borderCell + cellSpacing;
// - margin to compensate the + margin in columnPositions[0]
const QFixed contentsWidth = td->columnPositions.constLast() + td->widths.constLast() + td->padding + td->border + cellSpacing - leftMargin;
@@ -1877,17 +2734,15 @@ relayout:
if (cellRow != r) {
// the last row gets all the remaining space
if (cellRow + rspan - 1 == r)
- td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance);
+ td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance).round();
continue;
}
}
- const QTextFormat fmt = cell.format();
-
- const QFixed topPadding = td->topPadding(fmt);
- const QFixed bottomPadding = td->bottomPadding(fmt);
- const QFixed leftPadding = td->leftPadding(fmt);
- const QFixed rightPadding = td->rightPadding(fmt);
+ const QFixed topPadding = td->topPadding(table, cell);
+ const QFixed bottomPadding = td->bottomPadding(table, cell);
+ const QFixed leftPadding = td->leftPadding(table, cell);
+ const QFixed rightPadding = td->rightPadding(table, cell);
const QFixed widthPadding = leftPadding + rightPadding;
++rowCellCount;
@@ -1898,7 +2753,7 @@ relayout:
td, absoluteTableY,
/*withPageBreaks =*/true);
- const QFixed height = layoutStruct.y + bottomPadding + topPadding;
+ const QFixed height = (layoutStruct.y + bottomPadding + topPadding).round();
if (rspan > 1)
heightToDistribute[c] = height + dropDistance;
@@ -1924,13 +2779,13 @@ relayout:
}
if (haveRowSpannedCells) {
- const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
+ const QFixed effectiveHeight = td->heights.at(r) + td->borderCell + cellSpacing + td->borderCell;
for (int c = 0; c < columns; ++c)
heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
}
if (r == headerRowCount - 1) {
- td->headerHeight = td->rowPositions.at(r) + td->heights.at(r) - td->rowPositions.at(0) + td->cellSpacing + 2 * td->border;
+ td->headerHeight = td->rowPositions.at(r) + td->heights.at(r) - td->rowPositions.at(0) + td->cellSpacing + 2 * td->borderCell;
td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
td->effectiveTopMargin += td->headerHeight;
}
@@ -1952,7 +2807,7 @@ relayout:
const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
const QTextCharFormat cellFormat = cell.format();
- const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
+ const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(table, cell) + td->bottomPadding(table, cell);
QFixed offset = 0;
switch (cellFormat.verticalAlignment()) {
@@ -1977,14 +2832,14 @@ relayout:
td->minimumWidth = td->columnPositions.at(0);
for (int i = 0; i < columns; ++i) {
- td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing;
+ td->minimumWidth += td->minWidths.at(i) + 2 * td->borderCell + cellSpacing;
}
td->minimumWidth += rightMargin - td->border;
td->maximumWidth = td->columnPositions.at(0);
for (int i = 0; i < columns; ++i) {
if (td->maxWidths.at(i) != QFIXED_MAX)
- td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing;
+ td->maximumWidth += td->maxWidths.at(i) + 2 * td->borderCell + cellSpacing;
qCDebug(lcTable) << "column" << i << "has final width" << td->widths.at(i).toReal()
<< "min" << td->minWidths.at(i).toReal() << "max" << td->maxWidths.at(i).toReal();
}
@@ -2092,24 +2947,24 @@ QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, in
{
QTextFrameFormat fformat = f->frameFormat();
// set sizes of this frame from the format
- QFixed tm = QFixed::fromReal(fformat.topMargin());
+ QFixed tm = QFixed::fromReal(scaleToDevice(fformat.topMargin())).round();
if (tm != fd->topMargin) {
fd->topMargin = tm;
fullLayout = true;
}
- QFixed bm = QFixed::fromReal(fformat.bottomMargin());
+ QFixed bm = QFixed::fromReal(scaleToDevice(fformat.bottomMargin())).round();
if (bm != fd->bottomMargin) {
fd->bottomMargin = bm;
fullLayout = true;
}
- fd->leftMargin = QFixed::fromReal(fformat.leftMargin());
- fd->rightMargin = QFixed::fromReal(fformat.rightMargin());
- QFixed b = QFixed::fromReal(fformat.border());
+ fd->leftMargin = QFixed::fromReal(scaleToDevice(fformat.leftMargin())).round();
+ fd->rightMargin = QFixed::fromReal(scaleToDevice(fformat.rightMargin())).round();
+ QFixed b = QFixed::fromReal(scaleToDevice(fformat.border())).round();
if (b != fd->border) {
fd->border = b;
fullLayout = true;
}
- QFixed p = QFixed::fromReal(fformat.padding());
+ QFixed p = QFixed::fromReal(scaleToDevice(fformat.padding())).round();
if (p != fd->padding) {
fd->padding = p;
fullLayout = true;
@@ -3218,7 +4073,7 @@ QRectF QTextDocumentLayout::tableBoundingRect(QTextTable *table) const
if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
QTextTableCell cell = table->cellAt(framePos);
if (cell.isValid())
- pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
+ pos += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
}
}
@@ -3248,7 +4103,7 @@ QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame)
if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
QTextTableCell cell = table->cellAt(framePos);
if (cell.isValid())
- pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
+ pos += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
}
f = f->parentFrame();
@@ -3273,7 +4128,7 @@ QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
QTextTableCell cell = table->cellAt(blockPos);
if (cell.isValid())
- offset += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
+ offset += static_cast<QTextTableData *>(fd)->cellPosition(table, cell).toPointF();
}
frame = frame->parentFrame();
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 a7834587b1..209433dac5 100644
--- a/src/gui/text/qtextengine.cpp
+++ b/src/gui/text/qtextengine.cpp
@@ -989,7 +989,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;
}
}
@@ -1002,7 +1002,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;
}
}
@@ -1088,7 +1088,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();
}
{
@@ -1157,7 +1157,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;
@@ -1968,7 +1968,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;
@@ -2123,22 +2125,7 @@ void QTextEngine::itemize() const
}
#if QT_CONFIG(harfbuzz)
analysis = scriptAnalysis.data();
- if (qt_useHarfbuzzNG()) {
- // ### pretend HB-old behavior for now
- for (int i = 0; i < length; ++i) {
- switch (analysis[i].script) {
- case QChar::Script_Latin:
- case QChar::Script_Hiragana:
- case QChar::Script_Katakana:
- case QChar::Script_Bopomofo:
- case QChar::Script_Han:
- analysis[i].script = QChar::Script_Common;
- break;
- default:
- break;
- }
- }
- } else {
+ if (!qt_useHarfbuzzNG()) {
for (int i = 0; i < length; ++i)
analysis[i].script = hbscript_to_script(script_to_hbscript(analysis[i].script));
}
@@ -3617,7 +3604,12 @@ int QTextEngine::positionInLigature(const QScriptItem *si, int end,
int clusterLength = 0;
if (si->analysis.script != QChar::Script_Common &&
- si->analysis.script != QChar::Script_Greek) {
+ si->analysis.script != QChar::Script_Greek &&
+ si->analysis.script != QChar::Script_Latin &&
+ si->analysis.script != QChar::Script_Hiragana &&
+ si->analysis.script != QChar::Script_Katakana &&
+ si->analysis.script != QChar::Script_Bopomofo &&
+ si->analysis.script != QChar::Script_Han) {
if (glyph_pos == -1)
return si->position + end;
else {
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.cpp b/src/gui/text/qtextformat.cpp
index 4a2985f74c..e3bd49a15e 100644
--- a/src/gui/text/qtextformat.cpp
+++ b/src/gui/text/qtextformat.cpp
@@ -564,6 +564,18 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt)
\value BlockTrailingHorizontalRulerWidth The width of a horizontal ruler element.
\value HeadingLevel The level of a heading, for example 1 corresponds to an HTML H1 tag; otherwise 0.
This enum value has been added in Qt 5.12.
+ \value BlockCodeFence The character that was used in the "fences" around a Markdown code block.
+ If the code block was indented rather than fenced, the block should not have this property.
+ This enum value has been added in Qt 5.14.
+
+ \value BlockQuoteLevel The depth of nested quoting on this block: 1 means the block is a top-level block quote.
+ Blocks that are not block quotes should not have this property.
+ This enum value has been added in Qt 5.14.
+ \value BlockCodeLanguage The programming language in a preformatted or code block.
+ Blocks that do not contain code should not have this property.
+ This enum value has been added in Qt 5.14.
+ \value BlockMarker The \l{QTextBlockFormat::MarkerType}{type of adornment} to be shown alongside the block.
+ This enum value has been added in Qt 5.14.
Character properties
@@ -638,6 +650,7 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt)
\value TableColumns
\value TableColumnWidthConstraints
\value TableHeaderRowCount
+ \value TableBorderCollapse Specifies the \l QTextTableFormat::borderCollapse property.
Table cell properties
@@ -648,9 +661,32 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt)
\value TableCellTopPadding
\value TableCellBottomPadding
+ Table cell properties intended for use with \l QTextTableFormat::borderCollapse enabled
+
+ \value TableCellTopBorder
+ \value TableCellBottomBorder
+ \value TableCellLeftBorder
+ \value TableCellRightBorder
+
+ \value TableCellTopBorderStyle
+ \value TableCellBottomBorderStyle
+ \value TableCellLeftBorderStyle
+ \value TableCellRightBorderStyle
+
+ \value TableCellTopBorderBrush
+ \value TableCellBottomBorderBrush
+ \value TableCellLeftBorderBrush
+ \value TableCellRightBorderBrush
+
Image properties
- \value ImageName
+ \value ImageName The filename or source of the image.
+ \value ImageTitle The title attribute of an HTML image tag, or
+ the quoted string that comes after the URL in a Markdown image link.
+ This enum value has been added in Qt 5.14.
+ \value ImageAltText The alt attribute of an HTML image tag, or
+ the image description in a Markdown image link.
+ This enum value has been added in Qt 5.14.
\value ImageWidth
\value ImageHeight
\value ImageQuality
@@ -2329,6 +2365,50 @@ QList<QTextOption::Tab> QTextBlockFormat::tabPositions() const
/*!
+ \fn void QTextBlockFormat::setMarker(MarkerType marker)
+ \since 5.14
+
+ Sets the type of adornment that should be rendered alongside the paragraph to \a marker.
+ For example, a list item can be adorned with a checkbox, either checked
+ or unchecked, as a replacement for its bullet. The default is \c NoMarker.
+
+ \sa marker()
+*/
+
+
+/*!
+ \fn MarkerType QTextBlockFormat::marker() const
+ \since 5.14
+
+ Returns the paragraph's marker if one has been set, or \c NoMarker if not.
+
+ \sa setMarker()
+*/
+
+
+/*!
+ \since 5.14
+ \enum QTextBlockFormat::MarkerType
+
+ This enum describes the types of markers a list item can have.
+ If a list item (a paragraph for which \l QTextBlock::textList() returns the list)
+ has a marker, it is rendered instead of the normal bullet.
+ In this way, checkable list items can be mixed with plain list items in the
+ same list, overriding the type of bullet specified by the
+ \l QTextListFormat::style() for the entire list.
+
+ \value NoMarker This is the default: the list item's bullet will be shown.
+ \value Unchecked Instead of the list item's bullet, an unchecked checkbox will be shown.
+ \value Checked Instead of the list item's bullet, a checked checkbox will be shown.
+
+ In the future, this may be extended to specify other types of paragraph
+ decorations.
+
+ \sa QTextListFormat::style()
+*/
+
+
+/*!
\fn void QTextBlockFormat::setLineHeight(qreal height, int heightType)
\since 4.8
@@ -3038,6 +3118,8 @@ QTextTableFormat::QTextTableFormat(const QTextFormat &fmt)
Sets the cell \a spacing for the table. This determines the distance
between adjacent cells.
+
+ This property will be ignored if \l borderCollapse is enabled.
*/
/*!
@@ -3089,6 +3171,44 @@ QTextTableFormat::QTextTableFormat(const QTextFormat &fmt)
*/
/*!
+ \fn void QTextTableFormat::setBorderCollapse(bool borderCollapse)
+ \since 5.14
+
+ Enabling \a borderCollapse will have the following implications:
+ \list
+ \li The borders and grid of the table will be rendered following the
+ CSS table \c border-collapse: \c collapse rules
+ \li Setting the \c border property to a minimum value of \c 1 will render a
+ one pixel solid inner table grid using the \l borderBrush property and an
+ outer border as specified
+ \li The various border style properties of \l QTextTableCellFormat can be used to
+ customize the grid and have precedence over the border and grid of the table
+ \li The \l cellSpacing property will be ignored
+ \li For print pagination:
+ \list
+ \li Columns continued on a page will not have their top cell border rendered
+ \li Repeated header rows will always have their bottom cell border rendered
+ \endlist
+ \endlist
+
+ With borderCollapse disabled, cell borders can still be styled
+ using QTextTableCellFormat but styling will be applied only within
+ the cell's frame, which is probably not very useful in practice.
+
+ \sa setBorder(), setBorderBrush(), setBorderStyle()
+ \sa QTextTableCellFormat
+*/
+
+/*!
+ \fn bool QTextTableFormat::borderCollapse() const
+ \since 5.14
+
+ Returns true if borderCollapse is enabled.
+
+ \sa setBorderCollapse()
+*/
+
+/*!
\fn void QTextFormat::setBackground(const QBrush &brush)
Sets the brush use to paint the document's background to the
@@ -3427,6 +3547,228 @@ QTextImageFormat::QTextImageFormat(const QTextFormat &fmt)
*/
/*!
+ \fn void QTextTableCellFormat::setTopBorder(qreal width)
+ \since 5.14
+
+ Sets the top border \a width of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn qreal QTextTableCellFormat::topBorder() const
+ \since 5.14
+
+ Returns the top border width of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setBottomBorder(qreal width)
+ \since 5.14
+
+ Sets the bottom border \a width of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn qreal QTextTableCellFormat::bottomBorder() const
+ \since 5.14
+
+ Returns the bottom border width of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setLeftBorder(qreal width)
+ \since 5.14
+
+ Sets the left border \a width of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn qreal QTextTableCellFormat::leftBorder() const
+ \since 5.14
+
+ Returns the left border width of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setRightBorder(qreal width)
+ \since 5.14
+
+ Sets the right border \a width of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn qreal QTextTableCellFormat::rightBorder() const
+ \since 5.14
+
+ Returns the right border width of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setBorder(qreal width)
+ \since 5.14
+
+ Sets the left, right, top, and bottom border \a width of the table cell.
+
+ \sa setLeftBorder(), setRightBorder(), setTopBorder(), setBottomBorder()
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setTopBorderStyle(QTextFrameFormat::BorderStyle style)
+ \since 5.14
+
+ Sets the top border \a style of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QTextFrameFormat::BorderStyle QTextTableCellFormat::topBorderStyle() const
+ \since 5.14
+
+ Returns the top border style of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setBottomBorderStyle(QTextFrameFormat::BorderStyle style)
+ \since 5.14
+
+ Sets the bottom border \a style of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QTextFrameFormat::BorderStyle QTextTableCellFormat::bottomBorderStyle() const
+ \since 5.14
+
+ Returns the bottom border style of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setLeftBorderStyle(QTextFrameFormat::BorderStyle style)
+ \since 5.14
+
+ Sets the left border \a style of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QTextFrameFormat::BorderStyle QTextTableCellFormat::leftBorderStyle() const
+ \since 5.14
+
+ Returns the left border style of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setRightBorderStyle(QTextFrameFormat::BorderStyle style)
+ \since 5.14
+
+ Sets the right border \a style of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QTextFrameFormat::BorderStyle QTextTableCellFormat::rightBorderStyle() const
+ \since 5.14
+
+ Returns the right border style of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setBorderStyle(QTextFrameFormat::BorderStyle style)
+ \since 5.14
+
+ Sets the left, right, top, and bottom border \a style of the table cell.
+
+ \sa setLeftBorderStyle(), setRightBorderStyle(), setTopBorderStyle(), setBottomBorderStyle()
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setTopBorderBrush(const QBrush &brush)
+ \since 5.14
+
+ Sets the top border \a brush of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QBrush QTextTableCellFormat::topBorderBrush() const
+ \since 5.14
+
+ Returns the top border brush of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setBottomBorderBrush(const QBrush &brush)
+ \since 5.14
+
+ Sets the bottom border \a brush of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QBrush QTextTableCellFormat::bottomBorderBrush() const
+ \since 5.14
+
+ Returns the bottom border brush of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setLeftBorderBrush(const QBrush &brush)
+ \since 5.14
+
+ Sets the left border \a brush of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QBrush QTextTableCellFormat::leftBorderBrush() const
+ \since 5.14
+
+ Returns the left border brush of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setRightBorderBrush(const QBrush &brush)
+ \since 5.14
+
+ Sets the right border \a brush of the table cell.
+
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
+ \fn QBrush QTextTableCellFormat::rightBorderBrush() const
+ \since 5.14
+
+ Returns the right border brush of the table cell.
+*/
+
+/*!
+ \fn void QTextTableCellFormat::setBorderBrush(const QBrush &brush)
+ \since 5.14
+
+ Sets the left, right, top, and bottom border \a brush of the table cell.
+
+ \sa setLeftBorderBrush(), setRightBorderBrush(), setTopBorderBrush(), setBottomBorderBrush()
+ \sa QTextTableFormat::setBorderCollapse
+*/
+
+/*!
\fn bool QTextTableCellFormat::isValid() const
\since 4.4
diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h
index 80d8e82694..28da0fe344 100644
--- a/src/gui/text/qtextformat.h
+++ b/src/gui/text/qtextformat.h
@@ -176,6 +176,10 @@ public:
BlockNonBreakableLines = 0x1050,
BlockTrailingHorizontalRulerWidth = 0x1060,
HeadingLevel = 0x1070,
+ BlockQuoteLevel = 0x1080,
+ BlockCodeLanguage = 0x1090,
+ BlockCodeFence = 0x1091,
+ BlockMarker = 0x10A0,
// character properties
FirstFontProperty = 0x1FE0,
@@ -238,6 +242,7 @@ public:
TableCellSpacing = 0x4102,
TableCellPadding = 0x4103,
TableHeaderRowCount = 0x4104,
+ TableBorderCollapse = 0x4105,
// table cell properties
TableCellRowSpan = 0x4810,
@@ -248,8 +253,25 @@ public:
TableCellLeftPadding = 0x4814,
TableCellRightPadding = 0x4815,
+ TableCellTopBorder = 0x4816,
+ TableCellBottomBorder = 0x4817,
+ TableCellLeftBorder = 0x4818,
+ TableCellRightBorder = 0x4819,
+
+ TableCellTopBorderStyle = 0x481a,
+ TableCellBottomBorderStyle = 0x481b,
+ TableCellLeftBorderStyle = 0x481c,
+ TableCellRightBorderStyle = 0x481d,
+
+ TableCellTopBorderBrush = 0x481e,
+ TableCellBottomBorderBrush = 0x481f,
+ TableCellLeftBorderBrush = 0x4820,
+ TableCellRightBorderBrush = 0x4821,
+
// image properties
ImageName = 0x5000,
+ ImageTitle = 0x5001,
+ ImageAltText = 0x5002,
ImageWidth = 0x5010,
ImageHeight = 0x5011,
ImageQuality = 0x5014,
@@ -605,6 +627,12 @@ public:
LineDistanceHeight = 4
};
+ enum class MarkerType {
+ NoMarker = 0,
+ Unchecked = 1,
+ Checked = 2
+ };
+
QTextBlockFormat();
bool isValid() const { return isBlockFormat(); }
@@ -668,6 +696,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;
@@ -949,6 +982,11 @@ public:
inline int headerRowCount() const
{ return intProperty(TableHeaderRowCount); }
+ inline void setBorderCollapse(bool borderCollapse)
+ { setProperty(TableBorderCollapse, borderCollapse); }
+ inline bool borderCollapse() const
+ { return boolProperty(TableBorderCollapse); }
+
protected:
explicit QTextTableFormat(const QTextFormat &fmt);
friend class QTextFormat;
@@ -990,6 +1028,72 @@ public:
inline void setPadding(qreal padding);
+ inline void setTopBorder(qreal width)
+ { setProperty(TableCellTopBorder, width); }
+ inline qreal topBorder() const
+ { return doubleProperty(TableCellTopBorder); }
+
+ inline void setBottomBorder(qreal width)
+ { setProperty(TableCellBottomBorder, width); }
+ inline qreal bottomBorder() const
+ { return doubleProperty(TableCellBottomBorder); }
+
+ inline void setLeftBorder(qreal width)
+ { setProperty(TableCellLeftBorder, width); }
+ inline qreal leftBorder() const
+ { return doubleProperty(TableCellLeftBorder); }
+
+ inline void setRightBorder(qreal width)
+ { setProperty(TableCellRightBorder, width); }
+ inline qreal rightBorder() const
+ { return doubleProperty(TableCellRightBorder); }
+
+ inline void setBorder(qreal width);
+
+ inline void setTopBorderStyle(QTextFrameFormat::BorderStyle style)
+ { setProperty(TableCellTopBorderStyle, style); }
+ inline QTextFrameFormat::BorderStyle topBorderStyle() const
+ { return static_cast<QTextFrameFormat::BorderStyle>(intProperty(TableCellTopBorderStyle)); }
+
+ inline void setBottomBorderStyle(QTextFrameFormat::BorderStyle style)
+ { setProperty(TableCellBottomBorderStyle, style); }
+ inline QTextFrameFormat::BorderStyle bottomBorderStyle() const
+ { return static_cast<QTextFrameFormat::BorderStyle>(intProperty(TableCellBottomBorderStyle)); }
+
+ inline void setLeftBorderStyle(QTextFrameFormat::BorderStyle style)
+ { setProperty(TableCellLeftBorderStyle, style); }
+ inline QTextFrameFormat::BorderStyle leftBorderStyle() const
+ { return static_cast<QTextFrameFormat::BorderStyle>(intProperty(TableCellLeftBorderStyle)); }
+
+ inline void setRightBorderStyle(QTextFrameFormat::BorderStyle style)
+ { setProperty(TableCellRightBorderStyle, style); }
+ inline QTextFrameFormat::BorderStyle rightBorderStyle() const
+ { return static_cast<QTextFrameFormat::BorderStyle>(intProperty(TableCellRightBorderStyle)); }
+
+ inline void setBorderStyle(QTextFrameFormat::BorderStyle style);
+
+ inline void setTopBorderBrush(const QBrush &brush)
+ { setProperty(TableCellTopBorderBrush, brush); }
+ inline QBrush topBorderBrush() const
+ { return brushProperty(TableCellTopBorderBrush); }
+
+ inline void setBottomBorderBrush(const QBrush &brush)
+ { setProperty(TableCellBottomBorderBrush, brush); }
+ inline QBrush bottomBorderBrush() const
+ { return brushProperty(TableCellBottomBorderBrush); }
+
+ inline void setLeftBorderBrush(const QBrush &brush)
+ { setProperty(TableCellLeftBorderBrush, brush); }
+ inline QBrush leftBorderBrush() const
+ { return brushProperty(TableCellLeftBorderBrush); }
+
+ inline void setRightBorderBrush(const QBrush &brush)
+ { setProperty(TableCellRightBorderBrush, brush); }
+ inline QBrush rightBorderBrush() const
+ { return brushProperty(TableCellRightBorderBrush); }
+
+ inline void setBorderBrush(const QBrush &brush);
+
protected:
explicit QTextTableCellFormat(const QTextFormat &fmt);
friend class QTextFormat;
@@ -1045,6 +1149,29 @@ inline void QTextTableCellFormat::setPadding(qreal padding)
setRightPadding(padding);
}
+inline void QTextTableCellFormat::setBorder(qreal width)
+{
+ setTopBorder(width);
+ setBottomBorder(width);
+ setLeftBorder(width);
+ setRightBorder(width);
+}
+
+inline void QTextTableCellFormat::setBorderStyle(QTextFrameFormat::BorderStyle style)
+{
+ setTopBorderStyle(style);
+ setBottomBorderStyle(style);
+ setLeftBorderStyle(style);
+ setRightBorderStyle(style);
+}
+
+inline void QTextTableCellFormat::setBorderBrush(const QBrush &brush)
+{
+ setTopBorderBrush(brush);
+ setBottomBorderBrush(brush);
+ setLeftBorderBrush(brush);
+ setRightBorderBrush(brush);
+}
QT_END_NAMESPACE
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
index 929db40e5d..5d37982a8b 100644
--- a/src/gui/text/qtexthtmlparser.cpp
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -491,12 +491,19 @@ QTextHtmlParserNode::QTextHtmlParserNode()
listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0),
tableCellRowSpan(1), tableCellColSpan(1), tableCellSpacing(2), tableCellPadding(0),
borderBrush(Qt::darkGray), borderStyle(QTextFrameFormat::BorderStyle_Outset),
+ borderCollapse(false),
userState(-1), cssListIndent(0), wsm(WhiteSpaceModeUndefined)
{
margin[QTextHtmlParser::MarginLeft] = 0;
margin[QTextHtmlParser::MarginRight] = 0;
margin[QTextHtmlParser::MarginTop] = 0;
margin[QTextHtmlParser::MarginBottom] = 0;
+
+ for (int i = 0; i < 4; ++i) {
+ tableCellBorderStyle[i] = QTextFrameFormat::BorderStyle_None;
+ tableCellBorder[i] = 0;
+ tableCellBorderBrush[i] = Qt::NoBrush;
+ }
}
void QTextHtmlParser::dumpHtml()
@@ -649,7 +656,7 @@ void QTextHtmlParser::parseTag()
parseExclamationTag();
if (nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePre
&& nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePreWrap
- && !textEditMode)
+ && !textEditMode)
eatSpace();
return;
}
@@ -717,7 +724,8 @@ void QTextHtmlParser::parseTag()
// in a white-space preserving environment strip off a initial newline
// since the element itself already generates a newline
if ((node->wsm == QTextHtmlParserNode::WhiteSpacePre
- || node->wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
+ || node->wsm == QTextHtmlParserNode::WhiteSpacePreWrap
+ || node->wsm == QTextHtmlParserNode::WhiteSpacePreLine)
&& node->isBlock()) {
if (pos < len - 1 && txt.at(pos) == QLatin1Char('\n'))
++pos;
@@ -761,7 +769,8 @@ void QTextHtmlParser::parseCloseTag()
// in a new block for elements following the <pre>
// ...foo\n</pre><p>blah -> foo</pre><p>blah
if ((at(p).wsm == QTextHtmlParserNode::WhiteSpacePre
- || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
+ || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreWrap
+ || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreLine)
&& at(p).isBlock()) {
if (at(last()).text.endsWith(QLatin1Char('\n')))
nodes[last()].text.chop(1);
@@ -1125,6 +1134,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;
@@ -1166,6 +1176,25 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
QCss::ValueExtractor extractor(declarations);
extractor.extractBox(margin, padding);
+ if (id == Html_td || id == Html_th) {
+ QCss::BorderStyle cssStyles[4];
+ int cssBorder[4];
+ QSize cssRadii[4]; // unused
+ for (int i = 0; i < 4; ++i) {
+ cssStyles[i] = QCss::BorderStyle_None;
+ cssBorder[i] = 0;
+ }
+ // this will parse (and cache) "border-width" as a list so the
+ // QCss::BorderWidth parsing below which expects a single value
+ // will not work as expected - which in this case does not matter
+ // because tableBorder is not relevant for cells.
+ extractor.extractBorder(cssBorder, tableCellBorderBrush, cssStyles, cssRadii);
+ for (int i = 0; i < 4; ++i) {
+ tableCellBorderStyle[i] = static_cast<QTextFrameFormat::BorderStyle>(cssStyles[i] - 1);
+ tableCellBorder[i] = static_cast<qreal>(cssBorder[i]);
+ }
+ }
+
for (int i = 0; i < declarations.count(); ++i) {
const QCss::Declaration &decl = declarations.at(i);
if (decl.d->values.isEmpty()) continue;
@@ -1183,6 +1212,9 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
case QCss::BorderWidth:
tableBorder = extractor.lengthValue(decl);
break;
+ case QCss::BorderCollapse:
+ borderCollapse = decl.borderCollapseValue();
+ break;
case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
case QCss::Float:
cssFloat = QTextFrameFormat::InFlow;
@@ -1277,6 +1309,7 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
+ case QCss::Value_PreLine: wsm = QTextHtmlParserNode::WhiteSpacePreLine; break;
default: break;
}
break;
@@ -1334,6 +1367,17 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
default: break;
}
break;
+
+ case QCss::QtForegroundTextureCacheKey:
+ {
+ if (resourceProvider != nullptr && resourceProvider->docHandle() != nullptr) {
+ bool ok;
+ qint64 searchKey = decl.d->values.first().variant.toLongLong(&ok);
+ if (ok)
+ applyForegroundImage(searchKey, resourceProvider);
+ }
+ break;
+ }
default: break;
}
}
@@ -1366,6 +1410,37 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
#endif // QT_NO_CSSPARSER
+void QTextHtmlParserNode::applyForegroundImage(qint64 searchKey, const QTextDocument *resourceProvider)
+{
+ QTextDocumentPrivate *priv = resourceProvider->docHandle();
+ for (int i = 0; i < priv->formats.numFormats(); ++i) {
+ QTextCharFormat format = priv->formats.charFormat(i);
+ if (format.isValid()) {
+ QBrush brush = format.foreground();
+ if (brush.style() == Qt::TexturePattern) {
+ const bool isPixmap = qHasPixmapTexture(brush);
+
+ if (isPixmap && QCoreApplication::instance()->thread() != QThread::currentThread()) {
+ qWarning("Can't apply QPixmap outside of GUI thread");
+ return;
+ }
+
+ const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
+ if (cacheKey == searchKey) {
+ QBrush b;
+ if (isPixmap)
+ b.setTexture(brush.texture());
+ else
+ b.setTextureImage(brush.textureImage());
+ b.setStyle(Qt::TexturePattern);
+ charFormat.setForeground(b);
+ }
+ }
+ }
+ }
+
+}
+
void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
{
if (!url.isEmpty() && resourceProvider) {
@@ -1564,6 +1639,10 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
} else if (key == QLatin1String("height")) {
node->imageHeight = -2; // register that there is a value for it.
setFloatAttribute(&node->imageHeight, value);
+ } else if (key == QLatin1String("alt")) {
+ node->imageAlt = value;
+ } else if (key == QLatin1String("title")) {
+ node->text = value;
}
break;
case Html_tr:
@@ -1604,6 +1683,11 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
if (!c.isValid())
qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("bordercolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->borderBrush = c;
} else if (key == QLatin1String("background")) {
node->applyBackgroundImage(value, resourceProvider);
} else if (key == QLatin1String("cellspacing")) {
@@ -1639,6 +1723,10 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
else if (key == QLatin1String("type"))
linkType = value;
break;
+ case Html_pre:
+ if (key == QLatin1String("class") && value.startsWith(QLatin1String("language-")))
+ node->blockFormat.setProperty(QTextFormat::BlockCodeLanguage, value.mid(9));
+ break;
default:
break;
}
diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h
index 73dac38b82..31f558709f 100644
--- a/src/gui/text/qtexthtmlparser_p.h
+++ b/src/gui/text/qtexthtmlparser_p.h
@@ -158,6 +158,7 @@ struct QTextHtmlParserNode {
WhiteSpacePre,
WhiteSpaceNoWrap,
WhiteSpacePreWrap,
+ WhiteSpacePreLine,
WhiteSpaceModeUndefined = -1
};
@@ -184,6 +185,7 @@ struct QTextHtmlParserNode {
QString textListNumberPrefix;
QString textListNumberSuffix;
QString imageName;
+ QString imageAlt;
qreal imageWidth;
qreal imageHeight;
QTextLength width;
@@ -193,8 +195,12 @@ struct QTextHtmlParserNode {
int tableCellColSpan;
qreal tableCellSpacing;
qreal tableCellPadding;
+ qreal tableCellBorder[4];
+ QBrush tableCellBorderBrush[4];
+ QTextFrameFormat::BorderStyle tableCellBorderStyle[4];
QBrush borderBrush;
QTextFrameFormat::BorderStyle borderStyle;
+ bool borderCollapse;
int userState;
int cssListIndent;
@@ -250,6 +256,7 @@ struct QTextHtmlParserNode {
void setListStyle(const QVector<QCss::Value> &cssValues);
#endif
+ void applyForegroundImage(qint64 cacheKey, const QTextDocument *resourceProvider);
void applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider);
bool hasOnlyWhitespace() const;
@@ -287,6 +294,10 @@ public:
inline int leftPadding(int i) const { return at(i).padding[MarginLeft]; }
inline int rightPadding(int i) const { return at(i).padding[MarginRight]; }
+ inline qreal tableCellBorder(int i, int edge) const { return at(i).tableCellBorder[edge]; }
+ inline QTextFrameFormat::BorderStyle tableCellBorderStyle(int i, int edge) const { return at(i).tableCellBorderStyle[edge]; }
+ inline QBrush tableCellBorderBrush(int i, int edge) const { return at(i).tableCellBorderBrush[edge]; }
+
void dumpHtml();
void parse(const QString &text, const QTextDocument *resourceProvider);
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..fe7e422923
--- /dev/null
+++ b/src/gui/text/qtextmarkdownimporter.cpp
@@ -0,0 +1,568 @@
+/****************************************************************************
+**
+** 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>
+#if QT_CONFIG(regularexpression)
+#include <QRegularExpression>
+#endif
+#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())
+ qCDebug(lcMD, m_listItem ? "P of LI at level %d" : "P continuation inside LI at level %d", m_listStack.count());
+ else
+ qCDebug(lcMD, "P");
+ 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));
+ m_blockCodeFence = detail->fence_char;
+ QString info = QLatin1String(detail->info.text, int(detail->info.size));
+ m_needsInsertBlock = true;
+ if (m_blockQuoteDepth)
+ qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c' inside QUOTE %d", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence, m_blockQuoteDepth);
+ else
+ qCDebug(lcMD, "CODE lang '%s' info '%s' fenced with '%c'", qPrintable(m_blockCodeLanguage), qPrintable(info), m_blockCodeFence);
+ } 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 = true;
+ m_listItem = true;
+ MD_BLOCK_LI_DETAIL *detail = static_cast<MD_BLOCK_LI_DETAIL *>(det);
+ m_markerType = detail->is_task ?
+ (detail->task_mark == ' ' ? QTextBlockFormat::MarkerType::Unchecked : QTextBlockFormat::MarkerType::Checked) :
+ QTextBlockFormat::MarkerType::NoMarker;
+ qCDebug(lcMD) << "LI";
+ } break;
+ case MD_BLOCK_UL: {
+ MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det);
+ m_listFormat = QTextListFormat();
+ m_listFormat.setIndent(m_listStack.count() + 1);
+ switch (detail->mark) {
+ case '*':
+ m_listFormat.setStyle(QTextListFormat::ListCircle);
+ break;
+ case '+':
+ m_listFormat.setStyle(QTextListFormat::ListSquare);
+ break;
+ default: // including '-'
+ m_listFormat.setStyle(QTextListFormat::ListDisc);
+ break;
+ }
+ qCDebug(lcMD, "UL %c level %d", detail->mark, m_listStack.count());
+ m_needsInsertList = true;
+ } break;
+ case MD_BLOCK_OL: {
+ MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det);
+ m_listFormat = QTextListFormat();
+ m_listFormat.setIndent(m_listStack.count() + 1);
+ m_listFormat.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter));
+ m_listFormat.setStyle(QTextListFormat::ListDecimal);
+ qCDebug(lcMD, "OL xx%d level %d", detail->mark_delimiter, m_listStack.count());
+ m_needsInsertList = 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_P:
+ m_listItem = false;
+ break;
+ 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();
+ m_blockCodeFence = 0;
+ 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;
+ m_imageFormat = QTextImageFormat();
+ MD_SPAN_IMG_DETAIL *detail = static_cast<MD_SPAN_IMG_DETAIL *>(det);
+ m_imageFormat.setName(QString::fromUtf8(detail->src.text, int(detail->src.size)));
+ m_imageFormat.setProperty(QTextFormat::ImageTitle, QString::fromUtf8(detail->title.text, int(detail->title.size)));
+ break;
+ }
+ case MD_SPAN_CODE:
+ 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_needsInsertBlock)
+ insertBlock();
+#if QT_CONFIG(regularexpression)
+ static const QRegularExpression openingBracket(QStringLiteral("<[a-zA-Z]"));
+ static const QRegularExpression closingBracket(QStringLiteral("(/>|</)"));
+#endif
+ QString s = QString::fromUtf8(text, int(size));
+
+ switch (textType) {
+ case MD_TEXT_NORMAL:
+#if QT_CONFIG(regularexpression)
+ if (m_htmlTagDepth) {
+ m_htmlAccumulator += s;
+ s = QString();
+ }
+#endif
+ break;
+ case MD_TEXT_NULLCHAR:
+ s = QString(QChar(0xFFFD)); // CommonMark-required replacement for null
+ 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;
+#if QT_CONFIG(texthtmlparser)
+ case MD_TEXT_ENTITY:
+ m_cursor->insertHtml(s);
+ s = QString();
+ break;
+#endif
+ case MD_TEXT_HTML:
+ // count how many tags are opened and how many are closed
+#if QT_CONFIG(regularexpression) && QT_CONFIG(texthtmlparser)
+ {
+ 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;
+ 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();
+ }
+#endif
+ s = QString();
+ break;
+ }
+
+ switch (m_blockType) {
+ case MD_BLOCK_TD:
+ m_nonEmptyTableCells.append(m_tableCol);
+ break;
+ default:
+ break;
+ }
+
+ if (m_imageSpan) {
+ // TODO we don't yet support alt text with formatting, because of the cases where m_cursor
+ // already inserted the text above. Rather need to accumulate it in case we need it here.
+ m_imageFormat.setProperty(QTextFormat::ImageAltText, s);
+ qCDebug(lcMD) << "image" << m_imageFormat.name()
+ << "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle)
+ << "alt" << s << "relative to" << m_doc->baseUrl();
+ m_cursor->insertImage(m_imageFormat);
+ return 0; // no error
+ }
+
+ if (!s.isEmpty())
+ m_cursor->insertText(s);
+ if (m_cursor->currentList()) {
+ // 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
+}
+
+/*!
+ Insert a new block based on stored state.
+
+ m_cursor cannot store the state for the _next_ block ahead of time, because
+ m_cursor->setBlockFormat() controls the format of the block that the cursor
+ is already in; so cbLeaveBlock() cannot call setBlockFormat() without
+ altering the block that was just added. Therefore cbLeaveBlock() and the
+ following cbEnterBlock() set variables to remember what formatting should
+ come next, and insertBlock() is called just before the actual text
+ insertion, to create a new block with the right formatting.
+*/
+void QTextMarkdownImporter::insertBlock()
+{
+ QTextCharFormat charFormat;
+ if (!m_spanFormatStack.isEmpty())
+ charFormat = m_spanFormatStack.top();
+ QTextBlockFormat blockFormat;
+ if (!m_listStack.isEmpty() && !m_needsInsertList && m_listItem) {
+ QTextList *list = m_listStack.top();
+ blockFormat = list->item(list->count() - 1).blockFormat();
+ }
+ if (m_blockQuoteDepth) {
+ blockFormat.setProperty(QTextFormat::BlockQuoteLevel, m_blockQuoteDepth);
+ blockFormat.setLeftMargin(BlockQuoteIndent * m_blockQuoteDepth);
+ blockFormat.setRightMargin(BlockQuoteIndent);
+ }
+ if (m_codeBlock) {
+ blockFormat.setProperty(QTextFormat::BlockCodeLanguage, m_blockCodeLanguage);
+ if (m_blockCodeFence)
+ blockFormat.setProperty(QTextFormat::BlockCodeFence, QString(QLatin1Char(m_blockCodeFence)));
+ charFormat.setFont(m_monoFont);
+ } else {
+ blockFormat.setTopMargin(m_paragraphMargin);
+ blockFormat.setBottomMargin(m_paragraphMargin);
+ }
+ if (m_markerType == QTextBlockFormat::MarkerType::NoMarker)
+ blockFormat.clearProperty(QTextFormat::BlockMarker);
+ else
+ blockFormat.setMarker(m_markerType);
+ if (!m_listStack.isEmpty())
+ blockFormat.setIndent(m_listStack.count());
+ m_cursor->insertBlock(blockFormat, charFormat);
+ if (m_needsInsertList) {
+ m_listStack.push(m_cursor->createList(m_listFormat));
+ } else if (!m_listStack.isEmpty() && m_listItem) {
+ m_listStack.top()->add(m_cursor->block());
+ }
+ m_needsInsertList = false;
+ m_needsInsertBlock = false;
+}
+
+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..35655aff8a
--- /dev/null
+++ b/src/gui/text/qtextmarkdownimporter_p.h
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** 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
+#if QT_CONFIG(regularexpression)
+ QString m_htmlAccumulator;
+#endif
+ QString m_blockCodeLanguage;
+ QVector<int> m_nonEmptyTableCells; // in the current row
+ QStack<QTextList *> m_listStack;
+ QStack<QTextCharFormat> m_spanFormatStack;
+ QFont m_monoFont;
+ QPalette m_palette;
+#if QT_CONFIG(regularexpression)
+ int m_htmlTagDepth = 0;
+#endif
+ int m_blockQuoteDepth = 0;
+ int m_tableColumnCount = 0;
+ int m_tableRowCount = 0;
+ int m_tableCol = -1; // because relative cell movements (e.g. m_cursor->movePosition(QTextCursor::NextCell)) don't work
+ int m_paragraphMargin = 0;
+ int m_blockType = 0;
+ char m_blockCodeFence = 0;
+ Features m_features;
+ QTextImageFormat m_imageFormat;
+ QTextListFormat m_listFormat;
+ QTextBlockFormat::MarkerType m_markerType = QTextBlockFormat::MarkerType::NoMarker;
+ bool m_needsInsertBlock = false;
+ bool m_needsInsertList = false;
+ bool m_listItem = false; // true from the beginning of LI to the end of the first P
+ bool m_codeBlock = false;
+ bool m_imageSpan = false;
+};
+
+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..764c64aead
--- /dev/null
+++ b/src/gui/text/qtextmarkdownwriter.cpp
@@ -0,0 +1,564 @@
+/****************************************************************************
+**
+** 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"
+#if QT_CONFIG(itemmodel)
+#include "qabstractitemmodel.h"
+#endif
+
+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 DoubleQuote = QLatin1Char('"');
+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;
+}
+
+#if QT_CONFIG(itemmodel)
+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();
+}
+#endif
+
+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();
+ // Look ahead and detect some cases when we should
+ // suppress needless blank lines, when there will be a big change in block format
+ bool nextIsDifferent = false;
+ bool ending = false;
+ {
+ QTextFrame::iterator next = iterator;
+ ++next;
+ if (next.atEnd()) {
+ nextIsDifferent = true;
+ ending = true;
+ } else {
+ QTextBlockFormat format = iterator.currentBlock().blockFormat();
+ QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
+ if (nextFormat.indent() != format.indent() ||
+ nextFormat.property(QTextFormat::BlockCodeLanguage) != format.property(QTextFormat::BlockCodeLanguage))
+ nextIsDifferent = true;
+ }
+ }
+ 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, nextIsDifferent);
+ 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 (m_fencedCodeBlock && ending) {
+ m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space)
+ << m_codeBlockFence << Newline << Newline;
+ m_codeBlockFence.clear();
+ } else if (m_indentedCodeBlock && nextIsDifferent) {
+ m_stream << Newline;
+ } else if (endingCol > 0) {
+ if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
+ m_stream << Newline;
+ } else {
+ 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());
+ int fragBegin = qMax(before - 15, 0);
+ if (lcMDW().isDebugEnabled()) {
+ QString frag = s.mid(fragBegin, 30);
+ qCDebug(lcMDW) << frag << before;
+ qCDebug(lcMDW) << QString(before - fragBegin, Period) + QLatin1Char('<');
+ }
+ for (int i = before - 1; i >= 0; --i) {
+ if (s.at(i).isSpace()) {
+ qCDebug(lcMDW) << QString(i - fragBegin, Period) + QLatin1Char('^') << i;
+ return i;
+ }
+ }
+ qCDebug(lcMDW, "not possible");
+ 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, bool ignoreEmpty)
+{
+ if (block.text().isEmpty() && ignoreEmpty)
+ return 0;
+ const int ColumnLimit = 80;
+ QTextBlockFormat blockFmt = block.blockFormat();
+ bool missedBlankCodeBlockLine = 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::MarkerType::Checked:
+ bullet += " [x]";
+ break;
+ case QTextBlockFormat::MarkerType::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.hasProperty(QTextFormat::BlockCodeFence) || blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).length() > 0) {
+ // It's important to preserve blank lines in code blocks. But blank lines in code blocks
+ // inside block quotes are getting preserved anyway (along with the "> " prefix).
+ if (!blockFmt.hasProperty(QTextFormat::BlockQuoteLevel))
+ missedBlankCodeBlockLine = true; // only if we don't get any fragments below
+ if (!m_fencedCodeBlock) {
+ QString fenceChar = blockFmt.stringProperty(QTextFormat::BlockCodeFence);
+ if (fenceChar.isEmpty())
+ fenceChar = QLatin1String("`");
+ m_codeBlockFence = QString(3, fenceChar.at(0));
+ // A block quote can contain an indented code block, but not vice-versa.
+ m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space) << m_codeBlockFence
+ << Space << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << Newline;
+ m_fencedCodeBlock = true;
+ }
+ } else if (!blockFmt.indent()) {
+ if (m_fencedCodeBlock) {
+ m_stream << m_linePrefix << QString(m_wrappedLineIndent, Space)
+ << m_codeBlockFence << Newline;
+ m_fencedCodeBlock = false;
+ m_codeBlockFence.clear();
+ }
+ 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);
+ m_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) {
+ missedBlankCodeBlockLine = false;
+ 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 desc = ifmt.stringProperty(QTextFormat::ImageAltText);
+ if (desc.isEmpty())
+ desc = QLatin1String("image");
+ QString s = QLatin1String("![") + desc + QLatin1String("](") + ifmt.name();
+ QString title = ifmt.stringProperty(QTextFormat::ImageTitle);
+ if (!title.isEmpty())
+ s += Space + DoubleQuote + title + DoubleQuote;
+ s += QLatin1Char(')');
+ if (wrap && col + s.length() > ColumnLimit) {
+ m_stream << Newline << wrapIndentString;
+ col = m_wrappedLineIndent;
+ }
+ 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 && !m_indentedCodeBlock && !m_fencedCodeBlock) {
+ 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) {
+ if (col >= ColumnLimit) {
+ m_stream << Newline << wrapIndentString;
+ col = m_wrappedLineIndent;
+ while (fragmentText[i].isSpace())
+ ++i;
+ }
+ int j = i + ColumnLimit - col;
+ if (j < fragLen) {
+ int wi = nearestWordWrapIndex(fragmentText, j);
+ if (wi < 0) {
+ j = fragLen;
+ } else if (wi >= i) {
+ 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;
+ }
+ if (missedBlankCodeBlockLine)
+ m_stream << Newline;
+ 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..c3076155d0
--- /dev/null
+++ b/src/gui/text/qtextmarkdownwriter_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** 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"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractItemModel;
+
+class Q_GUI_EXPORT QTextMarkdownWriter
+{
+public:
+ QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features);
+ bool writeAll(const QTextDocument *document);
+#if QT_CONFIG(itemmodel)
+ void writeTable(const QAbstractItemModel *table);
+#endif
+
+ int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat, bool ignoreEmpty);
+ 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;
+ QString m_codeBlockFence;
+ int m_wrappedLineIndent = 0;
+ int m_lastListIndent = 1;
+ bool m_doubleNewlineWritten = false;
+ bool m_indentedCodeBlock = false;
+ bool m_fencedCodeBlock = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // QTEXTMARKDOWNWRITER_P_H
diff --git a/src/gui/text/qtextobject.cpp b/src/gui/text/qtextobject.cpp
index 40960c910e..b845889c3d 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 6d23866392..0e8666565f 100644
--- a/src/gui/text/qtextodfwriter.cpp
+++ b/src/gui/text/qtextodfwriter.cpp
@@ -523,9 +523,7 @@ void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, const QSet<int> &for
{
writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles"));
QVector<QTextFormat> allStyles = m_document->allFormats();
- QSetIterator<int> formatId(formats);
- while(formatId.hasNext()) {
- int formatIndex = formatId.next();
+ for (int formatIndex : formats) {
QTextFormat textFormat = allStyles.at(formatIndex);
switch (textFormat.type()) {
case QTextFormat::CharFormat:
@@ -1057,7 +1055,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