diff options
Diffstat (limited to 'src/gui/text')
-rw-r--r-- | src/gui/text/AGLFN_LICENSE.txt | 26 | ||||
-rw-r--r-- | src/gui/text/qcssparser.cpp | 22 | ||||
-rw-r--r-- | src/gui/text/qcssparser_p.h | 3 | ||||
-rw-r--r-- | src/gui/text/qfont.cpp | 8 | ||||
-rw-r--r-- | src/gui/text/qfont_p.h | 10 | ||||
-rw-r--r-- | src/gui/text/qfontengine.cpp | 25 | ||||
-rw-r--r-- | src/gui/text/qfontengine_p.h | 23 | ||||
-rw-r--r-- | src/gui/text/qfontmetrics.cpp | 27 | ||||
-rw-r--r-- | src/gui/text/qfontmetrics.h | 4 | ||||
-rw-r--r-- | src/gui/text/qt_attribution.json | 17 | ||||
-rw-r--r-- | src/gui/text/qtextdocument.cpp | 80 | ||||
-rw-r--r-- | src/gui/text/qtextdocument_p.cpp | 3 | ||||
-rw-r--r-- | src/gui/text/qtextdocumentfragment.cpp | 35 | ||||
-rw-r--r-- | src/gui/text/qtextdocumentlayout.cpp | 931 | ||||
-rw-r--r-- | src/gui/text/qtextformat.cpp | 279 | ||||
-rw-r--r-- | src/gui/text/qtextformat.h | 110 | ||||
-rw-r--r-- | src/gui/text/qtexthtmlparser.cpp | 43 | ||||
-rw-r--r-- | src/gui/text/qtexthtmlparser_p.h | 9 | ||||
-rw-r--r-- | src/gui/text/qtextlayout.h | 4 |
19 files changed, 1497 insertions, 162 deletions
diff --git a/src/gui/text/AGLFN_LICENSE.txt b/src/gui/text/AGLFN_LICENSE.txt new file mode 100644 index 0000000000..50abffca15 --- /dev/null +++ b/src/gui/text/AGLFN_LICENSE.txt @@ -0,0 +1,26 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +Neither the name of Adobe Systems Incorporated nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp index b5489c7ed9..ce7c7610c1 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -92,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 }, @@ -222,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 }, @@ -248,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 { @@ -610,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; @@ -1731,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 b0fa4be682..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, @@ -205,6 +206,7 @@ enum KnownValue { Value_Normal, Value_Pre, Value_NoWrap, + Value_PreLine, Value_PreWrap, Value_Small, Value_Medium, @@ -477,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) diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index 97e73f0723..3c1a052f37 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), @@ -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; } diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index 466e19e9cc..adbb7a0121 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -185,7 +185,6 @@ public: QFontDef request; mutable QFontEngineData *engineData; int dpi; - int screen; uint underline : 1; uint overline : 1; @@ -230,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(); @@ -251,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; diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 4198df6e43..403a0510fa 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -923,29 +923,10 @@ QFixed QFontEngine::subPixelPositionForX(QFixed x) const return subPixelPosition; } -QImage *QFontEngine::lockedAlphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, - QFontEngine::GlyphFormat neededFormat, - const QTransform &t, QPoint *offset) +QFontEngine::Glyph *QFontEngine::glyphData(glyph_t, QFixed, + QFontEngine::GlyphFormat, const QTransform &) { - Q_ASSERT(currentlyLockedAlphaMap.isNull()); - if (neededFormat == Format_None) - neededFormat = Format_A32; - - if (neededFormat != Format_A32) - currentlyLockedAlphaMap = alphaMapForGlyph(glyph, subPixelPosition, t); - else - currentlyLockedAlphaMap = alphaRGBMapForGlyph(glyph, subPixelPosition, t); - - if (offset != 0) - *offset = QPoint(0, 0); - - return ¤tlyLockedAlphaMap; -} - -void QFontEngine::unlockAlphaMapForGlyph() -{ - Q_ASSERT(!currentlyLockedAlphaMap.isNull()); - currentlyLockedAlphaMap = QImage(); + return nullptr; } QImage QFontEngine::alphaMapForGlyph(glyph_t glyph) diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index e20b52cb65..a5c78d5372 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -123,6 +123,22 @@ public: }; Q_DECLARE_FLAGS(ShaperFlags, ShaperFlag) + /* Used with the Freetype font engine. We don't cache glyphs that are too large anyway, so we can make this struct rather small */ + struct Glyph { + Glyph() = default; + ~Glyph() { delete [] data; } + short linearAdvance = 0; + unsigned char width = 0; + unsigned char height = 0; + short x = 0; + short y = 0; + short advance = 0; + signed char format = 0; + uchar *data = nullptr; + private: + Q_DISABLE_COPY(Glyph); + }; + virtual ~QFontEngine(); inline Type type() const { return m_type; } @@ -190,11 +206,7 @@ public: virtual QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t); virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t); virtual QImage bitmapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t, const QColor &color = QColor()); - virtual QImage *lockedAlphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, - GlyphFormat neededFormat, - const QTransform &t = QTransform(), - QPoint *offset = nullptr); - virtual void unlockAlphaMapForGlyph(); + virtual Glyph *glyphData(glyph_t glyph, QFixed subPixelPosition, GlyphFormat neededFormat, const QTransform &t); virtual bool hasInternalCaching() const { return false; } virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, QFixed /*subPixelPosition*/, const QTransform &matrix, GlyphFormat /*format*/) @@ -345,7 +357,6 @@ public: void loadKerningPairs(QFixed scalingFactor); GlyphFormat glyphFormat; - QImage currentlyLockedAlphaMap; int m_subPixelPositionCount; // Number of positions within a single pixel for this cache inline QVariant userData() const { return m_userData; } 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 02ff335e68..e92a1514a1 100644 --- a/src/gui/text/qfontmetrics.h +++ b/src/gui/text/qfontmetrics.h @@ -135,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); } @@ -216,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/qt_attribution.json b/src/gui/text/qt_attribution.json new file mode 100644 index 0000000000..c3a57267e2 --- /dev/null +++ b/src/gui/text/qt_attribution.json @@ -0,0 +1,17 @@ +[ + { + "Id": "aglfn", + "Name": "Adobe Glyph List For New Fonts", + "QDocModule": "qtgui", + "Description": "Provides standardized names for glyphs.", + "QtUsage": "Used by PDF generator to make it easier for reader applications to resolve the original contents of rendered text.", + "Path": "qfontsubset_agl.cpp", + + "Homepage": "https://github.com/adobe-type-tools/agl-aglfn", + "Version": "1.7", + "License": "BSD 3-Clause \"New\" or \"Revised\" License", + "LicenseId": "BSD-3-Clause", + "LicenseFile": "AGLFN_LICENSE.txt", + "Copyright": "Copyright 2002, 2003, 2005, 2006, 2008, 2010, 2015 Adobe Systems" + } +] diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index dc34a96918..c80617f929 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2593,51 +2593,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(';'); } @@ -3204,6 +3196,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('\"'); @@ -3310,6 +3329,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 diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp index 0e3c8d0e83..a1b1c2e92b 100644 --- a/src/gui/text/qtextdocument_p.cpp +++ b/src/gui/text/qtextdocument_p.cpp @@ -1106,12 +1106,11 @@ void QTextDocumentPrivate::clearUndoRedoStacks(QTextDocument::Stacks stacksToCle bool redoCommandsAvailable = undoState != undoStack.size(); if (stacksToClear == QTextDocument::UndoStack && undoCommandsAvailable) { for (int i = 0; i < undoState; ++i) { - QTextUndoCommand c = undoStack.at(undoState); + QTextUndoCommand c = undoStack.at(i); if (c.command & QTextUndoCommand::Custom) delete c.custom; } undoStack.remove(0, undoState); - undoStack.resize(undoStack.size() - undoState); undoState = 0; if (emitSignals) emitUndoAvailable(false); diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp index 1905d9a1b1..723e5c907c 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(); } @@ -979,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; } @@ -1054,6 +1062,31 @@ QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode() fmt.setLeftPadding(leftPadding(currentNodeIdx)); if (rightPadding(currentNodeIdx) >= 0) fmt.setRightPadding(rightPadding(currentNodeIdx)); + 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)); + cell.setFormat(fmt); cursor.setPosition(cell.firstPosition()); diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp index a1b21b111b..b02723c047 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,55 @@ public: } } - inline QFixed topPadding(const QTextFormat &format) const +#ifndef QT_NO_CSSPARSER + inline QFixed cellBorderWidth(QTextTable *table, const QTextTableCell &cell, QCss::Edge edge) const { - return paddingProperty(format, QTextFormat::TableCellTopPadding); + 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 bottomPadding(const QTextFormat &format) const + inline QFixed topPadding(QTextTable *table, const QTextTableCell &cell) const { - return paddingProperty(format, QTextFormat::TableCellBottomPadding); + return paddingProperty(cell.format(), QTextFormat::TableCellTopPadding) +#ifndef QT_NO_CSSPARSER + + cellBorderWidth(table, cell, QCss::TopEdge) +#endif + ; + } + + inline QFixed bottomPadding(QTextTable *table, const QTextTableCell &cell) const + { + 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); + 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); + 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 +337,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 +533,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 +800,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 +879,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 +928,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 +1029,31 @@ static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintCon } } +static bool cellClipTest(QTextTable *table, QTextTableData *td, + const QAbstractTextDocumentLayout::PaintContext &cell_context, + const QTextTableCell &cell, + QRectF cellRect) +{ + if (!cell_context.clip.isValid()) + return false; + + if (td->borderCollapse) { + // we need to account for the cell borders in the clipping test + 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); + } 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 @@ -982,10 +1116,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) { @@ -999,9 +1135,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, @@ -1041,9 +1175,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, @@ -1080,6 +1212,595 @@ 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); +#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 @@ -1098,9 +1819,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(); @@ -1109,7 +1829,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(); @@ -1175,6 +1895,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(), @@ -1538,8 +2261,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; } @@ -1557,8 +2279,20 @@ 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 + + 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); + } + + layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->effectiveBottomBorder + td->bottomPadding(t, cell); layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin; layoutStruct.fullLayout = true; @@ -1604,6 +2338,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; @@ -1629,12 +2374,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; @@ -1644,11 +2426,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; @@ -1673,9 +2461,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 @@ -1707,6 +2494,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; @@ -1798,7 +2591,7 @@ recalc_minmax_widths: } // in order to get a correct border rendering we must ensure that the distance between - // two cells is exactly 2 * td->border pixel. we do this by rounding the calculated width + // 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. @@ -1813,7 +2606,7 @@ recalc_minmax_widths: 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; @@ -1914,12 +2707,10 @@ relayout: } } - 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; @@ -1956,13 +2747,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; } @@ -1984,7 +2775,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()) { @@ -2009,14 +2800,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(); } @@ -3250,7 +4041,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(); } } @@ -3280,7 +4071,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(); @@ -3305,7 +4096,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/qtextformat.cpp b/src/gui/text/qtextformat.cpp index 090c6cc4ce..47a38db3ad 100644 --- a/src/gui/text/qtextformat.cpp +++ b/src/gui/text/qtextformat.cpp @@ -660,6 +660,23 @@ 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 The filename or source of the image. @@ -3100,6 +3117,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. */ /*! @@ -3151,6 +3170,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 @@ -3489,6 +3546,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 a91461dcae..12f14a1555 100644 --- a/src/gui/text/qtextformat.h +++ b/src/gui/text/qtextformat.h @@ -242,6 +242,7 @@ public: TableCellSpacing = 0x4102, TableCellPadding = 0x4103, TableHeaderRowCount = 0x4104, + TableBorderCollapse = 0x4105, // table cell properties TableCellRowSpan = 0x4810, @@ -252,6 +253,21 @@ 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, @@ -966,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; @@ -1007,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; @@ -1062,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 1ee317d27c..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); @@ -1167,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; @@ -1184,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; @@ -1278,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; @@ -1651,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")) { diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h index c174b54a61..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 }; @@ -194,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; @@ -289,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/qtextlayout.h b/src/gui/text/qtextlayout.h index a29791534e..2fc0fcd55a 100644 --- a/src/gui/text/qtextlayout.h +++ b/src/gui/text/qtextlayout.h @@ -114,8 +114,8 @@ public: // not ambiguous. Implementation detail that should not be documented. template<char = 0> #endif - QTextLayout(const QString &text, const QFont &font, const QPaintDevice *paintdevice) - : QTextLayout(text, font, const_cast<QPaintDevice*>(paintdevice)) + QTextLayout(const QString &textData, const QFont &textFont, const QPaintDevice *paintdevice) + : QTextLayout(textData, textFont, const_cast<QPaintDevice*>(paintdevice)) {} #else QTextLayout(const QString &text, const QFont &font, const QPaintDevice *paintdevice = nullptr); |