VectorImage: support stroke styling for paths
SVG different stroke attibutes can be easily mapped to QQuickShapePath properties. Task-number: QTBUG-121650 Change-Id: Id52f3e7d99a81c84851b7a7645f75fdee1efbaeb Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
parent
de436b8525
commit
645e1ee76b
|
@ -123,7 +123,7 @@ void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPaint
|
|||
Q_UNUSED(pathSelector)
|
||||
Q_ASSERT(painterPath || quadPath);
|
||||
|
||||
const bool noPen = info.strokeColor == QColorConstants::Transparent;
|
||||
const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
|
||||
if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
|
||||
return;
|
||||
|
||||
|
@ -145,12 +145,18 @@ void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPaint
|
|||
if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
|
||||
shapePath->setStrokeColor(Qt::transparent);
|
||||
} else {
|
||||
shapePath->setStrokeColor(info.strokeColor);
|
||||
shapePath->setStrokeWidth(info.strokeWidth);
|
||||
shapePath->setStrokeColor(info.strokeStyle.color);
|
||||
shapePath->setStrokeWidth(info.strokeStyle.width);
|
||||
shapePath->setCapStyle(QQuickShapePath::CapStyle(info.strokeStyle.lineCapStyle));
|
||||
shapePath->setJoinStyle(QQuickShapePath::JoinStyle(info.strokeStyle.lineJoinStyle));
|
||||
shapePath->setMiterLimit(info.strokeStyle.miterLimit);
|
||||
if (info.strokeStyle.dashArray.length() != 0) {
|
||||
shapePath->setStrokeStyle(QQuickShapePath::DashLine);
|
||||
shapePath->setDashPattern(info.strokeStyle.dashArray.toVector());
|
||||
shapePath->setDashOffset(info.strokeStyle.dashOffset);
|
||||
}
|
||||
}
|
||||
|
||||
shapePath->setCapStyle(QQuickShapePath::CapStyle(info.capStyle));
|
||||
|
||||
if (!(pathSelector & QQuickVectorImageGenerator::FillPath))
|
||||
shapePath->setFillColor(Qt::transparent);
|
||||
else if (info.grad.type() != QGradient::NoGradient)
|
||||
|
|
|
@ -44,14 +44,23 @@ struct ImageNodeInfo : NodeInfo
|
|||
QRectF rect;
|
||||
};
|
||||
|
||||
struct StrokeStyle
|
||||
{
|
||||
Qt::PenCapStyle lineCapStyle = Qt::SquareCap;
|
||||
Qt::PenJoinStyle lineJoinStyle = Qt::MiterJoin;
|
||||
qreal miterLimit = 4;
|
||||
qreal dashOffset = 0;
|
||||
QList<qreal> dashArray;
|
||||
QColor color = QColorConstants::Transparent;
|
||||
qreal width = 1.0;
|
||||
};
|
||||
|
||||
struct PathNodeInfo : NodeInfo
|
||||
{
|
||||
QPainterPath painterPath;
|
||||
Qt::FillRule fillRule = Qt::FillRule::WindingFill;
|
||||
Qt::PenCapStyle capStyle = Qt::SquareCap;
|
||||
QColor strokeColor;
|
||||
qreal strokeWidth;
|
||||
QColor fillColor;
|
||||
StrokeStyle strokeStyle;
|
||||
QGradient grad;
|
||||
};
|
||||
|
||||
|
|
|
@ -246,7 +246,7 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte
|
|||
Q_UNUSED(pathSelector)
|
||||
Q_ASSERT(painterPath || quadPath);
|
||||
|
||||
const bool noPen = info.strokeColor == QColorConstants::Transparent;
|
||||
const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
|
||||
if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
|
||||
return;
|
||||
|
||||
|
@ -275,11 +275,17 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte
|
|||
if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
|
||||
stream() << "strokeColor: \"transparent\"";
|
||||
} else {
|
||||
stream() << "strokeColor: \"" << info.strokeColor.name(QColor::HexArgb) << "\"";
|
||||
stream() << "strokeWidth: " << info.strokeWidth;
|
||||
stream() << "strokeColor: \"" << info.strokeStyle.color.name(QColor::HexArgb) << "\"";
|
||||
stream() << "strokeWidth: " << info.strokeStyle.width;
|
||||
stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
|
||||
stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
|
||||
stream() << "miterLimit: " << info.strokeStyle.miterLimit;
|
||||
if (info.strokeStyle.dashArray.length() != 0) {
|
||||
stream() << "strokeStyle: " << "ShapePath.DashLine";
|
||||
stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
|
||||
stream() << "dashOffset: " << info.strokeStyle.dashOffset;
|
||||
}
|
||||
}
|
||||
if (info.capStyle == Qt::FlatCap)
|
||||
stream() << "capStyle: ShapePath.FlatCap"; //### TODO Add the rest of the styles, as well as join styles etc.
|
||||
|
||||
if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
|
||||
stream() << "fillColor: \"transparent\"";
|
||||
|
|
|
@ -34,52 +34,6 @@ using namespace Qt::StringLiterals;
|
|||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage)
|
||||
|
||||
static inline bool isPathContainer(const QSvgStructureNode *node)
|
||||
{
|
||||
bool foundPath = false;
|
||||
for (const auto *child : node->renderers()) {
|
||||
switch (child->type()) {
|
||||
// nodes that shouldn't go inside Shape{}
|
||||
case QSvgNode::Switch:
|
||||
case QSvgNode::Doc:
|
||||
case QSvgNode::Group:
|
||||
case QSvgNode::Animation:
|
||||
case QSvgNode::Use:
|
||||
case QSvgNode::Video:
|
||||
//qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
|
||||
return false;
|
||||
|
||||
// nodes that could go inside Shape{}
|
||||
case QSvgNode::Defs:
|
||||
case QSvgNode::Image:
|
||||
case QSvgNode::Textarea:
|
||||
case QSvgNode::Text:
|
||||
case QSvgNode::Tspan:
|
||||
break;
|
||||
|
||||
// nodes that are done as pure ShapePath{}
|
||||
case QSvgNode::Rect:
|
||||
case QSvgNode::Circle:
|
||||
case QSvgNode::Ellipse:
|
||||
case QSvgNode::Line:
|
||||
case QSvgNode::Path:
|
||||
case QSvgNode::Polygon:
|
||||
case QSvgNode::Polyline:
|
||||
if (!child->style().transform.isDefault()) {
|
||||
//qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
|
||||
return false;
|
||||
}
|
||||
foundPath = true;
|
||||
break;
|
||||
default:
|
||||
qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
|
||||
break;
|
||||
}
|
||||
}
|
||||
//qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
class QSvgStyleResolver
|
||||
{
|
||||
public:
|
||||
|
@ -141,17 +95,6 @@ public:
|
|||
return strokeColor;
|
||||
}
|
||||
|
||||
qreal currentStrokeOpacity() const
|
||||
{
|
||||
return m_svgState.strokeOpacity;
|
||||
}
|
||||
|
||||
float currentStrokeWidth() const
|
||||
{
|
||||
float penWidth = m_dummyPainter.pen().widthF();
|
||||
return penWidth ? penWidth : 1;
|
||||
}
|
||||
|
||||
static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity)
|
||||
{
|
||||
QGradient grad = gradient;
|
||||
|
@ -166,6 +109,17 @@ public:
|
|||
return grad;
|
||||
}
|
||||
|
||||
float currentStrokeWidth() const
|
||||
{
|
||||
float penWidth = m_dummyPainter.pen().widthF();
|
||||
return penWidth ? penWidth : 1;
|
||||
}
|
||||
|
||||
QPen currentStroke() const
|
||||
{
|
||||
return m_dummyPainter.pen();
|
||||
}
|
||||
|
||||
protected:
|
||||
QPainter m_dummyPainter;
|
||||
QImage m_dummyImage;
|
||||
|
@ -175,6 +129,67 @@ protected:
|
|||
|
||||
Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver)
|
||||
|
||||
namespace {
|
||||
inline bool isPathContainer(const QSvgStructureNode *node)
|
||||
{
|
||||
bool foundPath = false;
|
||||
for (const auto *child : node->renderers()) {
|
||||
switch (child->type()) {
|
||||
// nodes that shouldn't go inside Shape{}
|
||||
case QSvgNode::Switch:
|
||||
case QSvgNode::Doc:
|
||||
case QSvgNode::Group:
|
||||
case QSvgNode::Animation:
|
||||
case QSvgNode::Use:
|
||||
case QSvgNode::Video:
|
||||
//qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ;
|
||||
return false;
|
||||
|
||||
// nodes that could go inside Shape{}
|
||||
case QSvgNode::Defs:
|
||||
case QSvgNode::Image:
|
||||
case QSvgNode::Textarea:
|
||||
case QSvgNode::Text:
|
||||
case QSvgNode::Tspan:
|
||||
break;
|
||||
|
||||
// nodes that are done as pure ShapePath{}
|
||||
case QSvgNode::Rect:
|
||||
case QSvgNode::Circle:
|
||||
case QSvgNode::Ellipse:
|
||||
case QSvgNode::Line:
|
||||
case QSvgNode::Path:
|
||||
case QSvgNode::Polygon:
|
||||
case QSvgNode::Polyline:
|
||||
if (!child->style().transform.isDefault()) {
|
||||
//qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
|
||||
return false;
|
||||
}
|
||||
foundPath = true;
|
||||
break;
|
||||
default:
|
||||
qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
|
||||
break;
|
||||
}
|
||||
}
|
||||
//qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName() << "is" << foundPath;
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
void populateStrokeStyle(StrokeStyle &srokeStyle)
|
||||
{
|
||||
QPen p = styleResolver->currentStroke();
|
||||
srokeStyle.lineCapStyle = p.capStyle();
|
||||
srokeStyle.lineJoinStyle = p.joinStyle() == Qt::SvgMiterJoin ? Qt::MiterJoin : p.joinStyle(); //TODO support SvgMiterJoin
|
||||
srokeStyle.miterLimit = p.miterLimit();
|
||||
srokeStyle.dashOffset = p.dashOffset();
|
||||
srokeStyle.dashArray = p.dashPattern();
|
||||
srokeStyle.color = styleResolver->currentStrokeColor();
|
||||
srokeStyle.width = p.widthF();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator)
|
||||
: m_svgFileName(svgFileName)
|
||||
, m_generator(generator)
|
||||
|
@ -277,11 +292,10 @@ void QSvgVisitorImpl::visitPathNode(const QSvgPath *node)
|
|||
|
||||
void QSvgVisitorImpl::visitLineNode(const QSvgLine *node)
|
||||
{
|
||||
// TODO: proper end caps (should be flat by default?)
|
||||
QPainterPath p;
|
||||
p.moveTo(node->line().p1());
|
||||
p.lineTo(node->line().p2());
|
||||
handlePathNode(node, p, Qt::FlatCap);
|
||||
handlePathNode(node, p);
|
||||
}
|
||||
|
||||
void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
|
||||
|
@ -293,7 +307,7 @@ void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node)
|
|||
void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node)
|
||||
{
|
||||
QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(node->polygon(), false);
|
||||
handlePathNode(node, p, Qt::FlatCap);
|
||||
handlePathNode(node, p);
|
||||
}
|
||||
|
||||
QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient)
|
||||
|
@ -526,11 +540,11 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
|
|||
info.painterPath = p;
|
||||
|
||||
if (fmt.hasProperty(QTextCharFormat::TextOutline)) {
|
||||
info.strokeWidth = fmt.textOutline().widthF();
|
||||
info.strokeColor = fmt.textOutline().color();
|
||||
info.strokeStyle.width = fmt.textOutline().widthF();
|
||||
info.strokeStyle.color = fmt.textOutline().color();
|
||||
} else {
|
||||
info.strokeColor = styleResolver->currentStrokeColor();
|
||||
info.strokeWidth = styleResolver->currentStrokeWidth();
|
||||
info.strokeStyle.color = styleResolver->currentStrokeColor();
|
||||
info.strokeStyle.width = styleResolver->currentStrokeWidth();
|
||||
}
|
||||
|
||||
if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() != nullptr)
|
||||
|
@ -738,7 +752,7 @@ void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node)
|
|||
|
||||
}
|
||||
|
||||
void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle)
|
||||
void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path)
|
||||
{
|
||||
handleBaseNodeSetup(node);
|
||||
|
||||
|
@ -749,10 +763,8 @@ void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &p
|
|||
info.fillRule = fillStyle->fillRule();
|
||||
|
||||
info.painterPath = path;
|
||||
info.capStyle = capStyle;
|
||||
info.fillColor = styleResolver->currentFillColor();
|
||||
info.strokeColor = styleResolver->currentStrokeColor();
|
||||
info.strokeWidth = styleResolver->currentStrokeWidth();
|
||||
populateStrokeStyle(info.strokeStyle);
|
||||
if (styleResolver->currentFillGradient() != nullptr)
|
||||
info.grad = styleResolver->applyOpacityToGradient(*styleResolver->currentFillGradient(), styleResolver->currentFillOpacity());
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ private:
|
|||
void handleBaseNodeSetup(const QSvgNode *node);
|
||||
void handleBaseNode(const QSvgNode *node);
|
||||
void handleBaseNodeEnd(const QSvgNode *node);
|
||||
void handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle = Qt::SquareCap);
|
||||
void handlePathNode(const QSvgNode *node, const QPainterPath &path);
|
||||
void outputShapePath(QPainterPath pathCopy, const PathNodeInfo &info);
|
||||
static QString gradientCssDescription(const QGradient *gradient);
|
||||
static QString colorCssDescription(QColor color);
|
||||
|
|
|
@ -193,6 +193,70 @@ inline QString toSvgString(const QQuadPath &path)
|
|||
return svgPathString;
|
||||
}
|
||||
|
||||
inline QString strokeCapStyleString(Qt::PenCapStyle strokeCapStyle)
|
||||
{
|
||||
QString capStyle;
|
||||
switch (strokeCapStyle) {
|
||||
case Qt::FlatCap:
|
||||
capStyle = QStringLiteral("ShapePath.FlatCap");
|
||||
break;
|
||||
case Qt::SquareCap:
|
||||
capStyle = QStringLiteral("ShapePath.SquareCap");
|
||||
break;
|
||||
case Qt::RoundCap:
|
||||
capStyle = QStringLiteral("ShapePath.RoundCap");
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
return capStyle;
|
||||
}
|
||||
|
||||
inline QString strokeJoinStyleString(Qt::PenJoinStyle strokeJoinStyle)
|
||||
{
|
||||
QString joinStyle;
|
||||
switch (strokeJoinStyle) {
|
||||
case Qt::MiterJoin:
|
||||
joinStyle = QStringLiteral("ShapePath.MiterJoin");
|
||||
break;
|
||||
case Qt::BevelJoin:
|
||||
joinStyle = QStringLiteral("ShapePath.BevelJoin");
|
||||
break;
|
||||
case Qt::RoundJoin:
|
||||
joinStyle = QStringLiteral("ShapePath.RoundJoin");
|
||||
break;
|
||||
default:
|
||||
//TODO: Add support for SvgMiter case
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
return joinStyle;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline QString listString(QList<T> list)
|
||||
{
|
||||
if (list.isEmpty())
|
||||
return QStringLiteral("[]");
|
||||
|
||||
QString l;
|
||||
QTextStream stream(&l);
|
||||
stream << "[";
|
||||
|
||||
if (list.length() > 1) {
|
||||
for (int i = 0; i < list.length() - 1; i++) {
|
||||
T v = list[i];
|
||||
stream << v << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
stream << list.last() << "]";
|
||||
return l;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="900" height="600" viewBox="0 0 90 60" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<g id="capTest">
|
||||
<polyline points="10,10 80,10" fill="none" stroke-width="8" stroke="black"/>
|
||||
<polyline points="10,10 80,10" fill="none" stroke-width="0.5" stroke-linecap="butt" stroke="yellow"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use href="#capTest" stroke-linecap="butt"/>
|
||||
<use href="#capTest" y="20" stroke-linecap="round"/>
|
||||
<use href="#capTest" y=" 40" stroke-linecap="square"/>
|
||||
</svg>
|
After Width: | Height: | Size: 492 B |
|
@ -0,0 +1,12 @@
|
|||
<svg width="900" height="300" viewBox="0 0 130 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<polyline id="letterz" points="10,10 40,10 10,30 40,30" fill="none" stroke-width="2.5" stroke="black"/>
|
||||
<polyline id="highlight" points="10,10 40,10 10,30 40,30" fill="none" stroke-width="0.1" stroke="yellow"/>
|
||||
</defs>
|
||||
<use href="#letterz" stroke-linecap="butt"/>
|
||||
<use href="#highlight"/>
|
||||
<use href="#letterz" x="40" stroke-linecap="round"/>
|
||||
<use href="#highlight" x="40"/>
|
||||
<use href="#letterz" x=" 80" stroke-linecap="square"/>
|
||||
<use href="#highlight" x="80"/>
|
||||
</svg>
|
After Width: | Height: | Size: 586 B |
|
@ -0,0 +1,10 @@
|
|||
<svg width="460" height="200" viewBox="0 0 30 13" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="0" y1="1" x2="30" y2="1" stroke="black" stroke-dashoffset="2"/>
|
||||
<line x1="0" y1="3" x2="30" y2="3" stroke="black" stroke-dasharray="4 2"/>
|
||||
<!--positive dashoffset pulls the dashes-->
|
||||
<line x1="0" y1="5" x2="30" y2="5" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="2"/>
|
||||
<line x1="0" y1="7" x2="30" y2="7" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="4"/>
|
||||
<!--negative dashoffset pushs the dashes-->
|
||||
<line x1="0" y1="9" x2="30" y2="9" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="-2"/>
|
||||
<line x1="0" y1="11" x2="30" y2="11" stroke="black" stroke-dasharray="4 2" stroke-dashoffset="-4"/>
|
||||
</svg>
|
After Width: | Height: | Size: 736 B |
|
@ -0,0 +1,12 @@
|
|||
<svg width="600" height="200" viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<g id="capTest">
|
||||
<polyline points="1,8 1,2 7,2" fill="none" stroke-width="1" stroke="black"/>
|
||||
<polyline points="1,8 1,2 7,2" fill="none" stroke-width="0.03" stroke="yellow"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<use href="#capTest" x="1" stroke-linejoin="bevel"/>
|
||||
<use href="#capTest" x="11" stroke-linejoin="round"/>
|
||||
<use href="#capTest" x="21" stroke-linejoin="miter"/>
|
||||
</svg>
|
After Width: | Height: | Size: 482 B |
Loading…
Reference in New Issue