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 <eirik.aavitsland@qt.io>
This commit is contained in:
parent
e205edfff6
commit
6ee5fcc456
@ -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));
|
||||
|
@ -169,6 +169,7 @@ enum Property {
|
||||
QtAccent,
|
||||
QtStrokeWidth,
|
||||
QtStrokeColor,
|
||||
QtForeground,
|
||||
NumProperties
|
||||
};
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user