diff options
author | Eskil Abrahamsen Blomfeldt <[email protected]> | 2024-03-15 15:42:51 +0100 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <[email protected]> | 2024-03-18 20:28:36 +0100 |
commit | 6ee5fcc45637bf45ecd7d5412db8ab58bde7fe39 (patch) | |
tree | 2a3c4ee8672f6411dba33d978d0306dd999baa6f | |
parent | e205edfff611922ddf04d8de71ed9cb92704eafc (diff) |
Support foreground gradient in CSS parser and HTML generator
Qt supports some complex foreground brushes which we cannot
express using normal CSS, so we introduce a Qt-specific property
for this. We already had some support for background gradients
in widget style sheets, but this expands support to foreground
brushes of text when converting a QTextDocument from and to HTML.
It also adds an optional "coordinatemode" attribute to the
gradient functions so that this can be faithfully restored from HTML.
Task-number: QTBUG-123357
Change-Id: I3d6dd828f68272995c8525bec5a7b421fdbed670
Reviewed-by: Eirik Aavitsland <[email protected]>
-rw-r--r-- | src/gui/text/qcssparser.cpp | 18 | ||||
-rw-r--r-- | src/gui/text/qcssparser_p.h | 1 | ||||
-rw-r--r-- | src/gui/text/qtextdocument.cpp | 47 | ||||
-rw-r--r-- | src/gui/text/qtexthtmlparser.cpp | 6 | ||||
-rw-r--r-- | tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp | 90 |
5 files changed, 155 insertions, 7 deletions
diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp index 5861003de6c..ac6ddc69a83 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -39,6 +39,7 @@ static const QCssKnownValue properties[NumProperties - 1] = { { "-qt-background-role", QtBackgroundRole }, { "-qt-block-indent", QtBlockIndent }, { "-qt-fg-texture-cachekey", QtForegroundTextureCacheKey }, + { "-qt-foreground", QtForeground }, { "-qt-line-height-type", QtLineHeightType }, { "-qt-list-indent", QtListIndent }, { "-qt-list-number-prefix", QtListNumberPrefix }, @@ -814,6 +815,10 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) QStringList spreads; spreads << "pad"_L1 << "reflect"_L1 << "repeat"_L1; + int coordinateMode = -1; + QStringList coordinateModes; + coordinateModes << "logical"_L1 << "stretchtodevice"_L1 << "objectbounding"_L1 << "object"_L1; + bool dependsOnThePalette = false; Parser parser(lst.at(1)); while (parser.hasNext()) { @@ -840,11 +845,12 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) parser.next(); QCss::Value value; (void)parser.parseTerm(&value); - if (attr.compare("spread"_L1, Qt::CaseInsensitive) == 0) { + if (attr.compare("spread"_L1, Qt::CaseInsensitive) == 0) spread = spreads.indexOf(value.variant.toString()); - } else { + else if (attr.compare("coordinatemode"_L1, Qt::CaseInsensitive) == 0) + coordinateMode = coordinateModes.indexOf(value.variant.toString()); + else vars[attr] = value.variant.toReal(); - } } parser.skipSpace(); (void)parser.test(COMMA); @@ -853,7 +859,7 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) if (gradType == 0) { QLinearGradient lg(vars.value("x1"_L1), vars.value("y1"_L1), vars.value("x2"_L1), vars.value("y2"_L1)); - lg.setCoordinateMode(QGradient::ObjectBoundingMode); + lg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode)); lg.setStops(stops); if (spread != -1) lg.setSpread(QGradient::Spread(spread)); @@ -867,7 +873,7 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) QRadialGradient rg(vars.value("cx"_L1), vars.value("cy"_L1), vars.value("radius"_L1), vars.value("fx"_L1), vars.value("fy"_L1)); - rg.setCoordinateMode(QGradient::ObjectBoundingMode); + rg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode)); rg.setStops(stops); if (spread != -1) rg.setSpread(QGradient::Spread(spread)); @@ -879,7 +885,7 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) if (gradType == 2) { QConicalGradient cg(vars.value("cx"_L1), vars.value("cy"_L1), vars.value("angle"_L1)); - cg.setCoordinateMode(QGradient::ObjectBoundingMode); + cg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode)); cg.setStops(stops); if (spread != -1) cg.setSpread(QGradient::Spread(spread)); diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h index a2d3622c7d7..c1cfb1ac9b2 100644 --- a/src/gui/text/qcssparser_p.h +++ b/src/gui/text/qcssparser_p.h @@ -169,6 +169,7 @@ enum Property { QtAccent, QtStrokeWidth, QtStrokeColor, + QtForeground, NumProperties }; diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index dc94643e8ae..15a313e13da 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2646,6 +2646,53 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) html += " -qt-fg-texture-cachekey:"_L1; html += QString::number(cacheKey); html += ";"_L1; + } else if (brush.style() == Qt::LinearGradientPattern + || brush.style() == Qt::RadialGradientPattern + || brush.style() == Qt::ConicalGradientPattern) { + const QGradient *gradient = brush.gradient(); + if (gradient->type() == QGradient::LinearGradient) { + const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(brush.gradient()); + + html += " -qt-foreground: qlineargradient("_L1; + html += "x1:"_L1 + QString::number(linearGradient->start().x()) + u','; + html += "y1:"_L1 + QString::number(linearGradient->start().y()) + u','; + html += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u','; + html += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u','; + } else if (gradient->type() == QGradient::RadialGradient) { + const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(brush.gradient()); + + html += " -qt-foreground: qradialgradient("_L1; + html += "cx:"_L1 + QString::number(radialGradient->center().x()) + u','; + html += "cy:"_L1 + QString::number(radialGradient->center().y()) + u','; + html += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u','; + html += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u','; + html += "radius:"_L1 + QString::number(radialGradient->radius()) + u','; + } else { + const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(brush.gradient()); + + html += " -qt-foreground: qconicalgradient("_L1; + html += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u','; + html += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u','; + html += "angle:"_L1 + QString::number(conicalGradient->angle()) + u','; + } + + const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 }; + html += "coordinatemode:"_L1; + html += coordinateModes.at(int(gradient->coordinateMode())); + html += u','; + + const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 }; + html += "spread:"_L1; + html += spreads.at(int(gradient->spread())); + + for (const QGradientStop &stop : gradient->stops()) { + html += ",stop:"_L1; + html += QString::number(stop.first); + html += u' '; + html += colorValue(stop.second); + } + + html += ");"_L1; } else { html += " color:"_L1; html += colorValue(brush.color()); diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index df6e6788277..05b9a1385f1 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -1405,6 +1405,12 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d } break; } + case QCss::QtForeground: + { + QBrush brush = decl.brushValue(); + charFormat.setForeground(brush); + break; + } default: break; } } diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp index ae6b0b28df3..40f78ed7787 100644 --- a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp +++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp @@ -184,6 +184,7 @@ private slots: void undoContentChangeIndices(); void restoreStrokeFromHtml(); + void restoreForegroundGradientFromHtml(); private: void backgroundImage_checkExpectedHtml(const QTextDocument &doc); @@ -4054,7 +4055,6 @@ void tst_QTextDocument::restoreStrokeFromHtml() QTextCharFormat textOutline; textOutline.setTextOutline(QPen(Qt::red, 2.3)); textCursor.insertText("Outlined text", textOutline); - { QTextDocument otherDocument; otherDocument.setHtml(document.toHtml()); @@ -4070,5 +4070,93 @@ void tst_QTextDocument::restoreStrokeFromHtml() } } +void tst_QTextDocument::restoreForegroundGradientFromHtml() +{ + QTextDocument document; + + QTextCursor textCursor(&document); + + QTextCharFormat foregroundGradient; + QLinearGradient lg; + lg.setColorAt(0.0, Qt::green); + lg.setColorAt(1.0, Qt::blue); + lg.setStart(QPointF(0,0)); + lg.setFinalStop(QPointF(800, 1000)); + foregroundGradient.setForeground(QBrush(lg)); + textCursor.insertText("Linear gradient text\n", foregroundGradient); + + QRadialGradient rg; + rg.setCoordinateMode(QGradient::ObjectBoundingMode); + rg.setSpread(QGradient::ReflectSpread); + rg.setColorAt(0.0, Qt::green); + rg.setColorAt(1.0, Qt::blue); + QPointF center(0.5, 0.5); + rg.setCenter(center); + rg.setFocalPoint(center); + rg.setRadius(0.5); + foregroundGradient.setForeground(QBrush(rg)); + textCursor.insertText("Radial gradient text\n", foregroundGradient); + + QConicalGradient cg; + cg.setCoordinateMode(QGradient::ObjectMode); + cg.setSpread(QGradient::RepeatSpread); + cg.setColorAt(0.0, Qt::green); + cg.setColorAt(1.0, Qt::blue); + cg.setCenter(QPointF(0.5, 0.5)); + cg.setAngle(0.0); + foregroundGradient.setForeground(QBrush(cg)); + textCursor.insertText("Conical gradient text\n", foregroundGradient); + + { + QTextDocument otherDocument; + otherDocument.setHtml(document.toHtml()); + + QCOMPARE(otherDocument.blockCount(), document.blockCount()); + + QTextBlock block = otherDocument.firstBlock(); + QTextFragment fragment = block.begin().fragment(); + + QCOMPARE(fragment.text(), QStringLiteral("Linear gradient text")); + + QTextCharFormat fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::ForegroundBrush)); + + QBrush brush = fmt.foreground(); + QCOMPARE(brush.style(), Qt::LinearGradientPattern); + QCOMPARE(brush.gradient()->coordinateMode(), lg.coordinateMode()); + QCOMPARE(brush.gradient()->spread(), lg.spread()); + QCOMPARE(brush.gradient()->stops().size(), lg.stops().size()); + QCOMPARE(static_cast<const QLinearGradient *>(brush.gradient())->start(), lg.start()); + QCOMPARE(static_cast<const QLinearGradient *>(brush.gradient())->finalStop(), lg.finalStop()); + + block = block.next(); + fragment = block.begin().fragment(); + + fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::ForegroundBrush)); + + brush = fmt.foreground(); + QCOMPARE(brush.style(), Qt::RadialGradientPattern); + QCOMPARE(brush.gradient()->coordinateMode(), rg.coordinateMode()); + QCOMPARE(brush.gradient()->spread(), rg.spread()); + QCOMPARE(static_cast<const QRadialGradient *>(brush.gradient())->center(), rg.center()); + QCOMPARE(static_cast<const QRadialGradient *>(brush.gradient())->focalPoint(), rg.focalPoint()); + QCOMPARE(static_cast<const QRadialGradient *>(brush.gradient())->radius(), rg.radius()); + + block = block.next(); + fragment = block.begin().fragment(); + + fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::ForegroundBrush)); + + brush = fmt.foreground(); + QCOMPARE(brush.style(), Qt::ConicalGradientPattern); + QCOMPARE(brush.gradient()->coordinateMode(), cg.coordinateMode()); + QCOMPARE(brush.gradient()->spread(), cg.spread()); + QCOMPARE(static_cast<const QConicalGradient *>(brush.gradient())->center(), cg.center()); + QCOMPARE(static_cast<const QConicalGradient *>(brush.gradient())->angle(), cg.angle()); + } +} + QTEST_MAIN(tst_QTextDocument) #include "tst_qtextdocument.moc" |