diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp index 916e96ea63..7886d9ba91 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -36,6 +36,7 @@ struct QCssKnownValue quint64 id; }; +// This array is sorted alphabetically. static const QCssKnownValue properties[NumProperties - 1] = { { "-qt-background-role", QtBackgroundRole }, { "-qt-block-indent", QtBlockIndent }, @@ -47,6 +48,11 @@ static const QCssKnownValue properties[NumProperties - 1] = { { "-qt-list-number-suffix", QtListNumberSuffix }, { "-qt-paragraph-type", QtParagraphType }, { "-qt-stroke-color", QtStrokeColor }, + { "-qt-stroke-dasharray", QtStrokeDashArray }, + { "-qt-stroke-dashoffset", QtStrokeDashOffset }, + { "-qt-stroke-linecap", QtStrokeLineCap }, + { "-qt-stroke-linejoin", QtStrokeLineJoin }, + { "-qt-stroke-miterlimit", QtStrokeMiterLimit }, { "-qt-stroke-width", QtStrokeWidth }, { "-qt-style-features", QtStyleFeatures }, { "-qt-table-type", QtTableType }, @@ -160,6 +166,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = { { "always", Value_Always }, { "auto", Value_Auto }, { "base", Value_Base }, + { "beveljoin", Value_BevelJoin}, { "bold", Value_Bold }, { "bottom", Value_Bottom }, { "bright-text", Value_BrightText }, @@ -176,6 +183,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = { { "dot-dot-dash", Value_DotDotDash }, { "dotted", Value_Dotted }, { "double", Value_Double }, + { "flatcap", Value_FlatCap}, { "groove", Value_Groove }, { "highlight", Value_Highlight }, { "highlighted-text", Value_HighlightedText }, @@ -194,6 +202,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = { { "mid", Value_Mid }, { "middle", Value_Middle }, { "midlight", Value_Midlight }, + { "miterjoin", Value_MiterJoin}, { "native", Value_Native }, { "none", Value_None }, { "normal", Value_Normal }, @@ -208,14 +217,18 @@ static const QCssKnownValue values[NumKnownValues - 1] = { { "pre-wrap", Value_PreWrap }, { "ridge", Value_Ridge }, { "right", Value_Right }, + { "roundcap", Value_RoundCap}, + { "roundjoin", Value_RoundJoin}, { "selected", Value_Selected }, { "shadow", Value_Shadow }, { "small" , Value_Small }, { "small-caps", Value_SmallCaps }, { "solid", Value_Solid }, { "square", Value_Square }, + { "squarecap", Value_SquareCap}, { "sub", Value_Sub }, { "super", Value_Super }, + { "svgmiterjoin", Value_SvgMiterJoin}, { "text", Value_Text }, { "top", Value_Top }, { "transparent", Value_Transparent }, @@ -231,10 +244,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, 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 }; +static const short indexOfId[NumKnownValues] = { 0, 44, 51, 45, 52, 53, 60, 37, 28, 78, 79, 27, 46, 6, 71, 50, + 31, 65, 66, 29, 55, 69, 7, 11, 42, 62, 20, 14, 18, 19, 21, 23, 54, 26, 49, 75, 39, 3, 2, 43, 70, 17, 12, + 63, 15, 34, 72, 35, 73, 61, 74, 36, 64, 22, 56, 41, 5, 57, 67, 77, 9, 30, 40, 13, 38, 68, 8, 10, 4, 76, + 59, 24, 25, 32, 33, 1, 16, 0, 58, 48, 47 }; QString Value::toString() const { @@ -1835,6 +1848,35 @@ bool Declaration::borderCollapseValue() const return d->values.at(0).toString() == "collapse"_L1; } +QList Declaration::dashArray() const +{ + if (d->propertyId != Property::QtStrokeDashArray || d->values.empty()) + return QList(); + + bool isValid = true; + QList dashes; + for (int i = 0; i < d->values.size(); i++) { + Value v = d->values[i]; + // Separators must be at odd indices and Numbers at even indices. + bool isValidSeparator = (i & 1) && v.type == Value::TermOperatorComma; + bool isValidNumber = !(i & 1) && v.type == Value::Number; + if (!isValidNumber && !isValidSeparator) { + isValid = false; + break; + } else if (isValidNumber) { + bool ok; + dashes.append(v.variant.toReal(&ok)); + if (!ok) { + isValid = false; + break; + } + } + } + + isValid &= !(dashes.size() & 1); + return isValid ? dashes : QList(); +} + 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 7742271e41..ba4a611df3 100644 --- a/src/gui/text/qcssparser_p.h +++ b/src/gui/text/qcssparser_p.h @@ -169,6 +169,11 @@ enum Property { QtAccent, QtStrokeWidth, QtStrokeColor, + QtStrokeLineCap, + QtStrokeLineJoin, + QtStrokeMiterLimit, + QtStrokeDashArray, + QtStrokeDashOffset, QtForeground, NumProperties }; @@ -226,6 +231,13 @@ enum KnownValue { Value_SmallCaps, Value_Uppercase, Value_Lowercase, + Value_SquareCap, + Value_FlatCap, + Value_RoundCap, + Value_MiterJoin, + Value_BevelJoin, + Value_RoundJoin, + Value_SvgMiterJoin, /* keep these in same order as QPalette::ColorRole */ Value_FirstColorRole, @@ -451,6 +463,8 @@ struct Q_GUI_EXPORT Declaration void borderImageValue(QString *image, int *cuts, TileMode *h, TileMode *v) const; bool borderCollapseValue() const; + + QList dashArray() const; }; QT_CSS_DECLARE_TYPEINFO(Declaration, Q_RELOCATABLE_TYPE) diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp index ab788a5f9b..31cb3a526a 100644 --- a/src/gui/text/qtextdocument.cpp +++ b/src/gui/text/qtextdocument.cpp @@ -2757,6 +2757,51 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) html += " -qt-stroke-width:"_L1; html += QString::number(outlinePen.widthF()); html += "px;"_L1; + + html += " -qt-stroke-linecap:"_L1; + if (outlinePen.capStyle() == Qt::SquareCap) + html += "squarecap;"_L1; + else if (outlinePen.capStyle() == Qt::FlatCap) + html += "flatcap;"_L1; + else if (outlinePen.capStyle() == Qt::RoundCap) + html += "roundcap;"_L1; + + html += " -qt-stroke-linejoin:"_L1; + if (outlinePen.joinStyle() == Qt::MiterJoin) + html += "miterjoin;"_L1; + else if (outlinePen.joinStyle() == Qt::SvgMiterJoin) + html += "svgmiterjoin;"_L1; + else if (outlinePen.joinStyle() == Qt::BevelJoin) + html += "beveljoin;"_L1; + else if (outlinePen.joinStyle() == Qt::RoundJoin) + html += "roundjoin;"_L1; + + if (outlinePen.joinStyle() == Qt::MiterJoin || + outlinePen.joinStyle() == Qt::SvgMiterJoin) { + html += " -qt-stroke-miterlimit:"_L1; + html += QString::number(outlinePen.miterLimit()); + html += u';'; + } + + if (outlinePen.style() == Qt::CustomDashLine && !outlinePen.dashPattern().empty()) { + html += " -qt-stroke-dasharray:"_L1; + QString dashArrayString; + QList dashes = outlinePen.dashPattern(); + + for (int i = 0; i < dashes.length() - 1; i++) { + qreal dash = dashes[i]; + dashArrayString += QString::number(dash) + u','; + } + + dashArrayString += QString::number(dashes.last()); + html += dashArrayString; + html += u';'; + + html += " -qt-stroke-dashoffset:"_L1; + html += QString::number(outlinePen.dashOffset()); + html += u';'; + } + attributesEmitted = true; } diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index bc2200697d..54c291b82e 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -1422,6 +1422,61 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList &d } break; } + case QCss::QtStrokeLineCap: + { + QPen pen = charFormat.textOutline(); + switch (identifier) { + case QCss::Value_SquareCap: pen.setCapStyle(Qt::SquareCap); break; + case QCss::Value_FlatCap: pen.setCapStyle(Qt::FlatCap); break; + case QCss::Value_RoundCap: pen.setCapStyle(Qt::RoundCap); break; + default: break; + } + charFormat.setTextOutline(pen); + break; + } + case QCss::QtStrokeLineJoin: + { + QPen pen = charFormat.textOutline(); + switch (identifier) { + case QCss::Value_MiterJoin: pen.setJoinStyle(Qt::MiterJoin); break; + case QCss::Value_BevelJoin: pen.setJoinStyle(Qt::BevelJoin); break; + case QCss::Value_RoundJoin: pen.setJoinStyle(Qt::RoundJoin); break; + case QCss::Value_SvgMiterJoin: pen.setJoinStyle(Qt::SvgMiterJoin); break; + default: break; + } + charFormat.setTextOutline(pen); + break; + } + case QCss::QtStrokeMiterLimit: + { + qreal miterLimit; + if (decl.realValue(&miterLimit)) { + QPen pen = charFormat.textOutline(); + pen.setMiterLimit(miterLimit); + charFormat.setTextOutline(pen); + } + break; + } + case QCss::QtStrokeDashArray: + { + QList dashes = decl.dashArray(); + if (!dashes.empty()) { + QPen pen = charFormat.textOutline(); + pen.setDashPattern(dashes); + charFormat.setTextOutline(pen); + } + break; + } + case QCss::QtStrokeDashOffset: + { + qreal dashOffset; + if (decl.realValue(&dashOffset)) { + QPen pen = charFormat.textOutline(); + pen.setDashOffset(dashOffset); + charFormat.setTextOutline(pen); + } + break; + } case QCss::QtForeground: { QBrush brush = decl.brushValue(); diff --git a/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp b/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp index a438d7ebc8..203fe003a0 100644 --- a/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp +++ b/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp @@ -56,6 +56,10 @@ private slots: void quotedAndUnquotedIdentifiers(); void whitespaceValues_data(); void whitespaceValues(); + void strokeLineCapValues_data(); + void strokeLineCapValues(); + void strokeLineJoinValues_data(); + void strokeLineJoinValues(); }; void tst_QCssParser::scanner_data() @@ -1759,6 +1763,57 @@ void tst_QCssParser::whitespaceValues() QCOMPARE(rule.declarations.at(0).d->values.first().toString(), value); } +void tst_QCssParser::strokeLineCapValues_data() +{ + QTest::addColumn("value"); + + QTest::newRow("flatcap") << "flatcap"; + QTest::newRow("roundcap") << "roundcap"; + QTest::newRow("squarecap") << "squarecap"; +} + +void tst_QCssParser::strokeLineCapValues() +{ + QFETCH(QString, value); + QCss::Parser parser(QString("foo { -qt-stroke-linecap: %1 }").arg(value)); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + QCOMPARE(rule.declarations.size(), 1); + + QCOMPARE(rule.declarations.at(0).d->property, QLatin1String("-qt-stroke-linecap")); + QCOMPARE(rule.declarations.at(0).d->values.first().type, QCss::Value::KnownIdentifier); + QCOMPARE(rule.declarations.at(0).d->values.first().toString(), value); +} + +void tst_QCssParser::strokeLineJoinValues_data() +{ + QTest::addColumn("value"); + + QTest::newRow("beveljoin") << "beveljoin"; + QTest::newRow("miterjoin") << "miterjoin"; + QTest::newRow("roundjoin") << "roundjoin"; + QTest::newRow("svgmiterjoin") << "svgmiterjoin"; +} + +void tst_QCssParser::strokeLineJoinValues() +{ + QFETCH(QString, value); + QCss::Parser parser(QString("foo { -qt-stroke-linejoin: %1 }").arg(value)); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + QCOMPARE(rule.declarations.size(), 1); + + QCOMPARE(rule.declarations.at(0).d->property, QLatin1String("-qt-stroke-linejoin")); + QCOMPARE(rule.declarations.at(0).d->values.first().type, QCss::Value::KnownIdentifier); + QCOMPARE(rule.declarations.at(0).d->values.first().toString(), value); +} + QTEST_MAIN(tst_QCssParser) #include "tst_qcssparser.moc" diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp index 335ee06e2f..600b45575f 100644 --- a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp +++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp @@ -4053,20 +4053,107 @@ void tst_QTextDocument::restoreStrokeFromHtml() QTextDocument document; QTextCursor textCursor(&document); QTextCharFormat textOutline; - textOutline.setTextOutline(QPen(Qt::red, 2.3)); - textCursor.insertText("Outlined text", textOutline); + + // Set stroke color and width + { + QPen pen(Qt::red, 2.3, Qt::SolidLine); + textOutline.setTextOutline(pen); + textCursor.insertText("Outlined text", textOutline); + } + + // Set Cap and Join styles + { + QPen pen; + pen.setCapStyle(Qt::FlatCap); + pen.setJoinStyle(Qt::RoundJoin); + textOutline.setTextOutline(pen); + textCursor.insertBlock(); + textCursor.insertText("Cap and Join Style", textOutline); + } + + // Set Miter limit + { + QPen pen; + pen.setJoinStyle(Qt::MiterJoin); + pen.setMiterLimit(4); + textOutline.setTextOutline(pen); + textCursor.insertBlock(); + textCursor.insertText("Miter Limit", textOutline); + } + + // Set Dash Array and Dash Offset + { + QPen pen; + QList pattern; + const int dash = 2; + const int gap = 4; + pattern << dash << gap << dash << gap << dash << gap; + pen.setDashPattern(pattern); + pen.setDashOffset(3); + textOutline.setTextOutline(pen); + textCursor.insertBlock(); + textCursor.insertText("Dash Pattern", textOutline); + } + { QTextDocument otherDocument; otherDocument.setHtml(document.toHtml()); - QCOMPARE(otherDocument.blockCount(), 1); - QTextBlock block = otherDocument.firstBlock(); - QTextFragment fragment = block.begin().fragment(); - QCOMPARE(fragment.text(), QStringLiteral("Outlined text")); - QTextCharFormat fmt = fragment.charFormat(); - QVERIFY(fmt.hasProperty(QTextCharFormat::TextOutline)); - QPen pen = fmt.textOutline(); - QCOMPARE(pen.color(), QColor(Qt::red)); - QCOMPARE(pen.widthF(), 2.3); + QCOMPARE(otherDocument.blockCount(), document.blockCount()); + + QTextBlock block; + QTextFragment fragment; + QTextCharFormat fmt; + QPen pen; + + { + block = otherDocument.findBlockByNumber(0); + fragment = block.begin().fragment(); + QCOMPARE(fragment.text(), QStringLiteral("Outlined text")); + fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::TextOutline)); + pen = fmt.textOutline(); + QCOMPARE(pen.color(), QColor(Qt::red)); + QCOMPARE(pen.widthF(), 2.3); + } + + { + block = otherDocument.findBlockByNumber(1); + qDebug() << block.text(); + fragment = block.begin().fragment(); + QCOMPARE(fragment.text(), QStringLiteral("Cap and Join Style")); + fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::TextOutline)); + pen = fmt.textOutline(); + QCOMPARE(pen.capStyle(), Qt::FlatCap); + QCOMPARE(pen.joinStyle(), Qt::RoundJoin); + } + + { + block = otherDocument.findBlockByNumber(2); + fragment = block.begin().fragment(); + QCOMPARE(fragment.text(), QStringLiteral("Miter Limit")); + fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::TextOutline)); + pen = fmt.textOutline(); + QCOMPARE(pen.joinStyle(), Qt::MiterJoin); + QCOMPARE(pen.miterLimit(), 4); + } + + + { + block = otherDocument.findBlockByNumber(3); + fragment = block.begin().fragment(); + QCOMPARE(fragment.text(), QStringLiteral("Dash Pattern")); + fmt = fragment.charFormat(); + QVERIFY(fmt.hasProperty(QTextCharFormat::TextOutline)); + pen = fmt.textOutline(); + QCOMPARE(pen.dashOffset(), 3); + QList pattern; + const int dash = 2; + const int gap = 4; + pattern << dash << gap << dash << gap << dash << gap; + QCOMPARE(pen.dashPattern(), pattern); + } } }