Preprocess paths in svgtoqml
Add --optimize-paths option to do expensive path manipulation at build time instead of at runtime. This may cause the generation of separate fill and stroke versions of the same source path. Task-number: QTBUG-116883 Task-number: QTBUG-121203 Pick-to: 6.7 Change-Id: Iacda16d8dbddf5b8219c290fac473d78c073576e Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
parent
eb54a00479
commit
bdebe5bc14
|
@ -811,15 +811,16 @@ static QList<TriangleData> customTriangulator2(const QQuadPath &path, float penW
|
|||
|
||||
// TODO: we could optimize by preprocessing e1, since we call this function multiple times on the same
|
||||
// elements
|
||||
static void handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
|
||||
// Returns true if a change was made
|
||||
static bool handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel = 0)
|
||||
{
|
||||
if (!isOverlap(path, e1, e2)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recursionLevel > 8) {
|
||||
qCDebug(lcSGCurveProcessor) << "Triangle overlap: recursion level" << recursionLevel << "aborting!";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path.elementAt(e1).childCount() > 1) {
|
||||
|
@ -839,7 +840,7 @@ static void handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel =
|
|||
bool overlap1 = isOverlap(path, e11, e2);
|
||||
bool overlap2 = isOverlap(path, e12, e2);
|
||||
if (!overlap1 && !overlap2)
|
||||
return; // no more overlap: success!
|
||||
return true; // no more overlap: success!
|
||||
|
||||
// We need to split more:
|
||||
if (path.elementAt(e2).isLine()) {
|
||||
|
@ -863,33 +864,41 @@ static void handleOverlap(QQuadPath &path, int e1, int e2, int recursionLevel =
|
|||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Test if element contains a start point of another element
|
||||
static void handleOverlap(QQuadPath &path, int e1, const QVector2D vertex, int recursionLevel = 0)
|
||||
// Returns true if a change was made
|
||||
static bool handleOverlap(QQuadPath &path, int e1, const QVector2D vertex, int recursionLevel = 0)
|
||||
{
|
||||
// First of all: Ignore the next element: it trivially overlaps (maybe not necessary: we do check for strict containment)
|
||||
if (vertex == path.elementAt(e1).endPoint() || !isOverlap(path, e1, vertex))
|
||||
return;
|
||||
return false;
|
||||
if (recursionLevel > 8) {
|
||||
qCDebug(lcSGCurveIntersectionSolver) << "Vertex overlap: recursion level" << recursionLevel << "aborting!";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
// Don't split if we're already split
|
||||
if (path.elementAt(e1).childCount() == 0)
|
||||
if (path.elementAt(e1).childCount() == 0) {
|
||||
path.splitElementAt(e1);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
handleOverlap(path, path.indexOfChildAt(e1, 0), vertex, recursionLevel + 1);
|
||||
handleOverlap(path, path.indexOfChildAt(e1, 1), vertex, recursionLevel + 1);
|
||||
changed = handleOverlap(path, path.indexOfChildAt(e1, 0), vertex, recursionLevel + 1) || changed; // variable at the end to avoid accidentally short-cutting out the call
|
||||
changed = handleOverlap(path, path.indexOfChildAt(e1, 1), vertex, recursionLevel + 1) || changed;
|
||||
return changed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QSGCurveProcessor::solveOverlaps(QQuadPath &path, OverlapSolveMode mode)
|
||||
// Returns true if the path was changed
|
||||
bool QSGCurveProcessor::solveOverlaps(QQuadPath &path, OverlapSolveMode mode)
|
||||
{
|
||||
bool changed = false;
|
||||
if (path.testHint(QQuadPath::PathNonOverlappingControlPointTriangles))
|
||||
return;
|
||||
return false;
|
||||
for (int i = 0; i < path.elementCount(); i++) {
|
||||
auto &element = path.elementAt(i);
|
||||
// only concave curve overlap is problematic, as long as we don't allow self-intersecting curves
|
||||
|
@ -902,7 +911,7 @@ void QSGCurveProcessor::solveOverlaps(QQuadPath &path, OverlapSolveMode mode)
|
|||
auto &other = path.elementAt(j);
|
||||
if (!other.isConvex() && !other.isLine() && j < i)
|
||||
continue; // We have already tested this combination, so no need to test again
|
||||
handleOverlap(path, i, j);
|
||||
changed = handleOverlap(path, i, j) || changed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -926,11 +935,12 @@ void QSGCurveProcessor::solveOverlaps(QQuadPath &path, OverlapSolveMode mode)
|
|||
if (i == j)
|
||||
continue;
|
||||
const auto &other = path.elementAt(j);
|
||||
handleOverlap(path, i, other.startPoint());
|
||||
changed = handleOverlap(path, i, other.startPoint()) || changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
path.setHint(QQuadPath::PathNonOverlappingControlPointTriangles);
|
||||
return changed;
|
||||
}
|
||||
|
||||
// A fast algorithm to find path elements that might overlap. We will only check the overlap of the
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
Qt::PenCapStyle capStyle,
|
||||
addStrokeTriangleCallback addTriangle,
|
||||
int subdivisions = 3);
|
||||
static void solveOverlaps(QQuadPath &path, OverlapSolveMode mode = SkipConcaveJoinsSolve);
|
||||
static bool solveOverlaps(QQuadPath &path, OverlapSolveMode mode = SkipConcaveJoinsSolve);
|
||||
static QList<QPair<int, int>> findOverlappingCandidates(const QQuadPath &path);
|
||||
static bool solveIntersections(QQuadPath &path, bool alwaysReorder = true);
|
||||
};
|
||||
|
|
|
@ -640,10 +640,10 @@ QString QQuadPath::asSvgString() const
|
|||
// (technically changing it from O(n) to O(n^2))
|
||||
// Note that this function should be called before splitting any elements,
|
||||
// so we can assume that the structure is a list and not a tree
|
||||
QQuadPath QQuadPath::subPathsClosed() const
|
||||
QQuadPath QQuadPath::subPathsClosed(bool *didClose) const
|
||||
{
|
||||
Q_ASSERT(m_childElements.isEmpty());
|
||||
|
||||
bool closed = false;
|
||||
QQuadPath res = *this;
|
||||
res.m_elements = {};
|
||||
res.m_elements.reserve(elementCount());
|
||||
|
@ -655,6 +655,7 @@ QQuadPath QQuadPath::subPathsClosed() const
|
|||
if (subStart >= 0 && m_elements[i - 1].ep != m_elements[subStart].sp) {
|
||||
res.currentPoint = m_elements[i - 1].ep;
|
||||
res.lineTo(m_elements[subStart].sp);
|
||||
closed = true;
|
||||
auto &endElement = res.m_elements.last();
|
||||
endElement.m_isSubpathEnd = true;
|
||||
// lineTo() can bail out if the points are too close.
|
||||
|
@ -673,6 +674,7 @@ QQuadPath QQuadPath::subPathsClosed() const
|
|||
if (subStart >= 0 && m_elements.last().ep != m_elements[subStart].sp) {
|
||||
res.currentPoint = m_elements.last().ep;
|
||||
res.lineTo(m_elements[subStart].sp);
|
||||
closed = true;
|
||||
}
|
||||
if (!res.m_elements.isEmpty()) {
|
||||
auto &endElement = res.m_elements.last();
|
||||
|
@ -687,6 +689,8 @@ QQuadPath QQuadPath::subPathsClosed() const
|
|||
Q_ASSERT(res.elementCount() == 4);
|
||||
}
|
||||
|
||||
if (didClose)
|
||||
*didClose = closed;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -230,7 +230,7 @@ public:
|
|||
QPainterPath toPainterPath() const;
|
||||
QString asSvgString() const;
|
||||
|
||||
QQuadPath subPathsClosed() const;
|
||||
QQuadPath subPathsClosed(bool *didClose = nullptr) const;
|
||||
void addCurvatureData();
|
||||
QQuadPath flattened() const;
|
||||
QQuadPath dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset = 0) const;
|
||||
|
|
|
@ -28,24 +28,22 @@ int main(int argc, char *argv[])
|
|||
parser.addPositionalArgument("input", QCoreApplication::translate("main", "SVG file to read."));
|
||||
parser.addPositionalArgument("output", QCoreApplication::translate("main", "QML file to write."));
|
||||
|
||||
#if 0
|
||||
QCommandLineOption separateOption(QStringList() << "s" << "separate-items",
|
||||
QCoreApplication::translate("main", "Generate separate items for all sub-shapes."));
|
||||
parser.addOption(separateOption);
|
||||
QCommandLineOption optimizeOption("optimize-paths",
|
||||
QCoreApplication::translate("main", "Optimize paths for the curve renderer."));
|
||||
parser.addOption(optimizeOption);
|
||||
|
||||
QCommandLineOption curveRendererOption("curve-renderer",
|
||||
QCoreApplication::translate("main", "Use the curve renderer in generated QML."));
|
||||
parser.addOption(curveRendererOption);
|
||||
|
||||
QCommandLineOption combineOption(QStringList() << "c" << "combine-shapes",
|
||||
QCoreApplication::translate("main", "Combine all sub-shapes into one item."));
|
||||
parser.addOption(combineOption);
|
||||
#endif
|
||||
QCommandLineOption typeNameOption(QStringList() << "t" << "type-name",
|
||||
QCoreApplication::translate("main", "Use <typename> for Shape."),
|
||||
QCoreApplication::translate("main", "typename"));
|
||||
parser.addOption(typeNameOption);
|
||||
|
||||
|
||||
#ifdef ENABLE_GUI
|
||||
QCommandLineOption guiOption(QStringList() << "v" << "view",
|
||||
QCoreApplication::translate("main", "Display the SVG in a window."));
|
||||
QCoreApplication::translate("main", "Display the SVG in a window."));
|
||||
parser.addOption(guiOption);
|
||||
#endif
|
||||
parser.process(app);
|
||||
|
@ -54,14 +52,24 @@ int main(int argc, char *argv[])
|
|||
parser.showHelp(1);
|
||||
}
|
||||
|
||||
auto *doc = QSvgTinyDocument::load(args.at(0));
|
||||
const QString inFileName = args.at(0);
|
||||
|
||||
auto *doc = QSvgTinyDocument::load(inFileName);
|
||||
if (!doc) {
|
||||
fprintf(stderr, "%s is not a valid SVG\n", qPrintable(args.at(0)));
|
||||
fprintf(stderr, "%s is not a valid SVG\n", qPrintable(inFileName));
|
||||
return 2;
|
||||
}
|
||||
|
||||
auto outFileName = args.size() > 1 ? args.at(1) : QString{};
|
||||
auto typeName = parser.value(typeNameOption);
|
||||
const QString commentString = QLatin1String("Generated from SVG file %1").arg(inFileName);
|
||||
|
||||
const auto outFileName = args.size() > 1 ? args.at(1) : QString{};
|
||||
const auto typeName = parser.value(typeNameOption);
|
||||
|
||||
QSvgQmlWriter::GeneratorFlags flags;
|
||||
if (parser.isSet(curveRendererOption))
|
||||
flags |= QSvgQmlWriter::CurveRenderer;
|
||||
if (parser.isSet(optimizeOption))
|
||||
flags |= QSvgQmlWriter::OptimizePaths;
|
||||
|
||||
#ifdef ENABLE_GUI
|
||||
if (parser.isSet(guiOption)) {
|
||||
|
@ -69,12 +77,12 @@ int main(int argc, char *argv[])
|
|||
const QUrl url(QStringLiteral("qrc:/main.qml"));
|
||||
QQmlApplicationEngine engine;
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||
&app, [url, outFileName, doc, typeName](QObject *obj, const QUrl &objUrl){
|
||||
&app, [&](QObject *obj, const QUrl &objUrl){
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
if (obj) {
|
||||
auto *containerItem = obj->findChild<QQuickItem*>(QStringLiteral("svg_item"));
|
||||
auto *contents = QSvgQmlWriter::loadSVG(doc, outFileName, typeName, containerItem);
|
||||
auto *contents = QSvgQmlWriter::loadSVG(doc, outFileName, flags, typeName, containerItem, commentString);
|
||||
contents->setWidth(containerItem->implicitWidth()); // Workaround for runtime loader viewbox size logic. TODO: fix
|
||||
contents->setHeight(containerItem->implicitHeight());
|
||||
}
|
||||
|
@ -84,6 +92,6 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
#endif
|
||||
|
||||
QSvgQmlWriter::loadSVG(doc, outFileName, typeName, nullptr);
|
||||
QSvgQmlWriter::loadSVG(doc, outFileName, flags, typeName, nullptr, commentString);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <private/qquickimagebase_p_p.h>
|
||||
#include <private/qquickimage_p.h>
|
||||
#include <private/qsgcurveprocessor_p.h>
|
||||
|
||||
#include <private/qquadpath_p.h>
|
||||
|
||||
|
@ -52,6 +53,7 @@ public:
|
|||
SvgLoaderVisitor();
|
||||
~SvgLoaderVisitor();
|
||||
void setShapeTypeName(const QString &name) { m_shapeTypeName = name.toLatin1(); }
|
||||
void setFlags(QSvgQmlWriter::GeneratorFlags flags) { m_flags = flags; }
|
||||
QQuickItem *loadQML(QTextStream *stream, const QSvgTinyDocument *doc, QQuickItem *svgItem);
|
||||
|
||||
protected:
|
||||
|
@ -92,6 +94,11 @@ private:
|
|||
void handleBaseNodeEnd(const QSvgNode *node);
|
||||
void handlePathNode(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle = Qt::SquareCap);
|
||||
void outputShapePath(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle);
|
||||
void outputGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect);
|
||||
|
||||
enum PathSelector { FillPath = 0x1, StrokePath = 0x2, FillAndStroke = 0x3 };
|
||||
void outputShapePath(const QSvgNode *node, const QPainterPath *path, const QQuadPath *quadPath, Qt::PenCapStyle capStyle,
|
||||
PathSelector pathSelector, const QRectF &boundingRect);
|
||||
|
||||
QQuickItem *currentItem() { return m_items.top(); }
|
||||
void addCurrentItem(QQuickItem *item, const QSvgNode *node = nullptr) {
|
||||
|
@ -119,6 +126,7 @@ private:
|
|||
QQuickItem *m_loadedItem = nullptr;
|
||||
bool m_generateQML = true;
|
||||
bool m_generateItems = false;
|
||||
QSvgQmlWriter::GeneratorFlags m_flags;
|
||||
};
|
||||
|
||||
SvgLoaderVisitor::SvgLoaderVisitor()
|
||||
|
@ -140,10 +148,14 @@ void SvgLoaderVisitor::handlePathNode(const QSvgNode *node, const QPainterPath &
|
|||
{
|
||||
handleBaseNodeSetup(node);
|
||||
|
||||
QPainterPath pathCopy = path;
|
||||
auto fillStyle = node->style().fill;
|
||||
if (fillStyle)
|
||||
pathCopy.setFillRule(fillStyle->fillRule());
|
||||
if (m_inShapeItem) {
|
||||
if (!node->style().transform.isDefault())
|
||||
qWarning() << "Skipped transform for node" << node->nodeId() << "type" << node->typeName() << "(this is not supposed to happen)";
|
||||
outputShapePath(node, path, capStyle);
|
||||
outputShapePath(node, pathCopy, capStyle);
|
||||
} else {
|
||||
if (m_generateItems) {
|
||||
auto *shapeItem = new QQuickShape;
|
||||
|
@ -156,7 +168,9 @@ void SvgLoaderVisitor::handlePathNode(const QSvgNode *node, const QPainterPath &
|
|||
stream() << shapeName() << " {";
|
||||
handleBaseNode(node);
|
||||
m_indentLevel++;
|
||||
outputShapePath(node, path, capStyle);
|
||||
if (m_flags & QSvgQmlWriter::CurveRenderer)
|
||||
stream() << "preferredRendererType: Shape.CurveRenderer";
|
||||
outputShapePath(node, pathCopy, capStyle);
|
||||
//qDebug() << *node->qpath();
|
||||
m_indentLevel--;
|
||||
stream() << "}";
|
||||
|
@ -171,6 +185,7 @@ void SvgLoaderVisitor::handlePathNode(const QSvgNode *node, const QPainterPath &
|
|||
void SvgLoaderVisitor::visitPathNode(const QSvgPath *node)
|
||||
{
|
||||
stream() << "// PATH visit " << node->nodeId() << " count: " << node->path().elementCount();
|
||||
|
||||
handlePathNode(node, node->path());
|
||||
}
|
||||
|
||||
|
@ -545,145 +560,265 @@ static QRectF mapToQtLogicalMode(const QRectF &objModeRect, const QRectF &boundi
|
|||
return QRectF(pixelRect.topLeft(), QSizeF(x,y));
|
||||
}
|
||||
|
||||
static QString toSvgString(const QPainterPath &path)
|
||||
{
|
||||
QString svgPathString;
|
||||
QTextStream strm(&svgPathString);
|
||||
|
||||
for (int i = 0; i < path.elementCount(); ++i) {
|
||||
QPainterPath::Element element = path.elementAt(i);
|
||||
if (element.isMoveTo()) {
|
||||
strm << "M " << element.x << " " << element.y << " ";
|
||||
} else if (element.isLineTo()) {
|
||||
strm << "L " << element.x << " " << element.y << " ";
|
||||
} else if (element.isCurveTo()) {
|
||||
QPointF c1(element.x, element.y);
|
||||
++i;
|
||||
element = path.elementAt(i);
|
||||
|
||||
QPointF c2(element.x, element.y);
|
||||
++i;
|
||||
element = path.elementAt(i);
|
||||
QPointF ep(element.x, element.y);
|
||||
|
||||
strm << "C "
|
||||
<< c1.x() << " "
|
||||
<< c1.y() << " "
|
||||
<< c2.x() << " "
|
||||
<< c2.y() << " "
|
||||
<< ep.x() << " "
|
||||
<< ep.y() << " ";
|
||||
}
|
||||
}
|
||||
|
||||
return svgPathString;
|
||||
}
|
||||
|
||||
static QString toSvgString(const QQuadPath &path)
|
||||
{
|
||||
QString svgPathString;
|
||||
QTextStream strm(&svgPathString);
|
||||
path.iterateElements([&](const QQuadPath::Element &e){
|
||||
if (e.isSubpathStart())
|
||||
strm << "M " << e.startPoint().x() << " " << e.startPoint().y() << " ";
|
||||
|
||||
if (e.isLine()) {
|
||||
strm << "L " << e.endPoint().x() << " " << e.endPoint().y() << " ";
|
||||
} else {
|
||||
strm << "Q " << e.controlPoint().x() << " " << e.controlPoint().y() << " "
|
||||
<< e.endPoint().x() << " " << e.endPoint().y() << " ";
|
||||
}
|
||||
});
|
||||
|
||||
return svgPathString;
|
||||
}
|
||||
|
||||
void SvgLoaderVisitor::outputGradient(const QGradient *grad, QQuickShapePath *shapePath, const QRectF &boundingRect)
|
||||
{
|
||||
auto setStops = [](QQuickShapeGradient *quickGrad, const QGradientStops &stops) {
|
||||
auto stopsProp = quickGrad->stops();
|
||||
for (auto &stop : stops) {
|
||||
auto *stopObj = new QQuickGradientStop(quickGrad);
|
||||
stopObj->setPosition(stop.first);
|
||||
stopObj->setColor(stop.second);
|
||||
stopsProp.append(&stopsProp, stopObj);
|
||||
}
|
||||
};
|
||||
|
||||
if (grad->type() == QGradient::LinearGradient) {
|
||||
auto *linGrad = static_cast<const QLinearGradient *>(grad);
|
||||
stream() << "fillGradient: LinearGradient {";
|
||||
m_indentLevel++;
|
||||
|
||||
QRectF gradRect(linGrad->start(), linGrad->finalStop());
|
||||
QRectF logRect = linGrad->coordinateMode() == QGradient::LogicalMode ? gradRect : mapToQtLogicalMode(gradRect, boundingRect);
|
||||
|
||||
stream() << "x1: " << logRect.left();
|
||||
stream() << "y1: " << logRect.top();
|
||||
stream() << "x2: " << logRect.right();
|
||||
stream() << "y2: " << logRect.bottom();
|
||||
for (auto &stop : linGrad->stops()) {
|
||||
stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
|
||||
}
|
||||
m_indentLevel--;
|
||||
stream() << "}";
|
||||
|
||||
if (shapePath) {
|
||||
auto *quickGrad = new QQuickShapeLinearGradient(shapePath);
|
||||
|
||||
quickGrad->setX1(logRect.left());
|
||||
quickGrad->setY1(logRect.top());
|
||||
quickGrad->setX2(logRect.right());
|
||||
quickGrad->setY2(logRect.bottom());
|
||||
setStops(quickGrad, linGrad->stops());
|
||||
|
||||
shapePath->setFillGradient(quickGrad);
|
||||
}
|
||||
} else if (grad->type() == QGradient::RadialGradient) {
|
||||
auto *radGrad = static_cast<const QRadialGradient*>(grad);
|
||||
stream() << "fillGradient: RadialGradient {";
|
||||
m_indentLevel++;
|
||||
|
||||
stream() << "centerX: " << radGrad->center().x();
|
||||
stream() << "centerY: " << radGrad->center().y();
|
||||
stream() << "centerRadius: " << radGrad->radius();
|
||||
stream() << "focalX: centerX; focalY: centerY";
|
||||
for (auto &stop : radGrad->stops()) {
|
||||
stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
|
||||
}
|
||||
m_indentLevel--;
|
||||
stream() << "}";
|
||||
|
||||
if (shapePath) {
|
||||
auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
|
||||
quickGrad->setCenterX(radGrad->center().x());
|
||||
quickGrad->setCenterY(radGrad->center().y());
|
||||
quickGrad->setCenterRadius(radGrad->radius());
|
||||
quickGrad->setFocalX(radGrad->center().x());
|
||||
quickGrad->setFocalY(radGrad->center().y());
|
||||
setStops(quickGrad, radGrad->stops());
|
||||
|
||||
shapePath->setFillGradient(quickGrad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SvgLoaderVisitor::outputShapePath(const QSvgNode *node, const QPainterPath &path, Qt::PenCapStyle capStyle)
|
||||
{
|
||||
QPointF offset; // ??? do we need this?
|
||||
const bool optimize = m_flags.testFlag(QSvgQmlWriter::OptimizePaths);
|
||||
QRectF boundingRect = path.boundingRect();
|
||||
if (optimize) {
|
||||
const bool outlineMode = m_flags.testFlag(QSvgQmlWriter::OutlineStrokeMode);
|
||||
QQuadPath strokePath = QQuadPath::fromPainterPath(path);
|
||||
bool fillPathNeededClose;
|
||||
QQuadPath fillPath = strokePath.subPathsClosed(&fillPathNeededClose);
|
||||
const bool intersectionsFound = QSGCurveProcessor::solveIntersections(fillPath, false);
|
||||
fillPath.addCurvatureData();
|
||||
QSGCurveProcessor::solveOverlaps(fillPath);
|
||||
|
||||
const bool compatibleStrokeAndFill = !fillPathNeededClose && !intersectionsFound;
|
||||
|
||||
if (compatibleStrokeAndFill || outlineMode) {
|
||||
outputShapePath(node, nullptr, &fillPath, capStyle, FillAndStroke, boundingRect);
|
||||
} else {
|
||||
outputShapePath(node, nullptr, &fillPath, capStyle, FillPath, boundingRect);
|
||||
outputShapePath(node, nullptr, &strokePath, capStyle, StrokePath, boundingRect);
|
||||
}
|
||||
} else {
|
||||
outputShapePath(node, &path, nullptr, capStyle, FillAndStroke, boundingRect);
|
||||
}
|
||||
}
|
||||
|
||||
static QString pathHintString(const QQuadPath &qp)
|
||||
{
|
||||
QString res;
|
||||
QTextStream str(&res);
|
||||
auto flags = qp.pathHints();
|
||||
if (!flags)
|
||||
return res;
|
||||
str << "pathHints:";
|
||||
bool first = true;
|
||||
|
||||
#define CHECK_PATH_HINT(flagName) \
|
||||
if (flags.testFlag(QQuadPath::flagName)) { \
|
||||
if (!first) \
|
||||
str << " |"; \
|
||||
first = false; \
|
||||
str << " ShapePath." #flagName; \
|
||||
}
|
||||
|
||||
CHECK_PATH_HINT(PathLinear)
|
||||
CHECK_PATH_HINT(PathQuadratic)
|
||||
CHECK_PATH_HINT(PathConvex)
|
||||
CHECK_PATH_HINT(PathFillOnRight)
|
||||
CHECK_PATH_HINT(PathSolid)
|
||||
CHECK_PATH_HINT(PathNonIntersecting)
|
||||
CHECK_PATH_HINT(PathNonOverlappingControlPointTriangles)
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void SvgLoaderVisitor::outputShapePath(const QSvgNode *node, const QPainterPath *painterPath, const QQuadPath *quadPath, Qt::PenCapStyle capStyle, PathSelector pathSelector, const QRectF &boundingRect)
|
||||
{
|
||||
Q_UNUSED(pathSelector)
|
||||
Q_ASSERT(painterPath || quadPath);
|
||||
|
||||
QString penName = currentStrokeColor();
|
||||
const bool noPen = penName.isEmpty() || penName == u"transparent";
|
||||
if (pathSelector == StrokePath && noPen)
|
||||
return;
|
||||
|
||||
const bool noFill = !currentFillGradient() && currentFillColor() == u"transparent";
|
||||
if (pathSelector == FillPath && noFill)
|
||||
return;
|
||||
|
||||
auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
|
||||
stream() << "ShapePath {";
|
||||
m_indentLevel++;
|
||||
auto *shapePath = m_generateItems ? new QQuickShapePath : nullptr;
|
||||
if (!node->nodeId().isEmpty()) {
|
||||
stream() << "objectName: \"svg_path:" << node->nodeId() << "\"";
|
||||
if (m_generateItems)
|
||||
switch (pathSelector) {
|
||||
case FillPath:
|
||||
stream() << "objectName: \"svg_fill_path:" << node->nodeId() << "\"";
|
||||
break;
|
||||
case StrokePath:
|
||||
stream() << "objectName: \"svg_stroke_path:" << node->nodeId() << "\"";
|
||||
break;
|
||||
case FillAndStroke:
|
||||
stream() << "objectName: \"svg_path:" << node->nodeId() << "\"";
|
||||
break;
|
||||
}
|
||||
if (shapePath)
|
||||
shapePath->setObjectName(QStringLiteral("svg_path:") + node->nodeId());
|
||||
}
|
||||
stream() << "// boundingRect: " << path.boundingRect().x() << ", " << path.boundingRect().y() << " " << path.boundingRect().width() << "x" << path.boundingRect().height();
|
||||
QString penName = currentStrokeColor();
|
||||
if (penName.isEmpty()) {
|
||||
stream() << "// boundingRect: " << boundingRect.x() << ", " << boundingRect.y() << " " << boundingRect.width() << "x" << boundingRect.height();
|
||||
|
||||
if (noPen || !(pathSelector & StrokePath)) {
|
||||
stream() << "strokeColor: \"transparent\"";
|
||||
if (m_generateItems)
|
||||
if (shapePath)
|
||||
shapePath->setStrokeColor(Qt::transparent);
|
||||
} else {
|
||||
stream() << "strokeColor: \"" << penName << "\"";
|
||||
stream() << "strokeWidth: " << currentStrokeWidth();
|
||||
if (m_generateItems) {
|
||||
if (shapePath) {
|
||||
shapePath->setStrokeColor(QColor::fromString(penName));
|
||||
shapePath->setStrokeWidth(currentStrokeWidth());
|
||||
}
|
||||
}
|
||||
if (capStyle == Qt::FlatCap)
|
||||
stream() << "capStyle: ShapePath.FlatCap"; //### TODO
|
||||
stream() << "capStyle: ShapePath.FlatCap"; //### TODO Add the rest of the styles, as well as join styles etc.
|
||||
|
||||
if (m_generateItems)
|
||||
if (shapePath)
|
||||
shapePath->setCapStyle(QQuickShapePath::CapStyle(capStyle));
|
||||
|
||||
if (auto *grad = currentFillGradient()) {
|
||||
|
||||
auto setStops = [](QQuickShapeGradient *quickGrad, const QGradientStops &stops) {
|
||||
auto stopsProp = quickGrad->stops();
|
||||
for (auto &stop : stops) {
|
||||
auto *stopObj = new QQuickGradientStop(quickGrad);
|
||||
stopObj->setPosition(stop.first);
|
||||
stopObj->setColor(stop.second);
|
||||
stopsProp.append(&stopsProp, stopObj);
|
||||
}
|
||||
};
|
||||
|
||||
if (grad->type() == QGradient::LinearGradient) {
|
||||
auto *linGrad = static_cast<const QLinearGradient *>(grad);
|
||||
// qDebug() << "grad" << linGrad->start() << linGrad->finalStop() << "mode" << linGrad->coordinateMode();
|
||||
// qDebug() << "path BR" << path.boundingRect();
|
||||
QRectF br = path.boundingRect();
|
||||
stream() << "fillGradient: LinearGradient {";
|
||||
m_indentLevel++;
|
||||
|
||||
QRectF gradRect(linGrad->start(), linGrad->finalStop());
|
||||
QRectF logRect = linGrad->coordinateMode() == QGradient::LogicalMode ? gradRect : mapToQtLogicalMode(gradRect, br);
|
||||
|
||||
stream() << "x1: " << logRect.left();
|
||||
stream() << "y1: " << logRect.top();
|
||||
stream() << "x2: " << logRect.right();
|
||||
stream() << "y2: " << logRect.bottom();
|
||||
for (auto &stop : linGrad->stops()) {
|
||||
stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
|
||||
}
|
||||
m_indentLevel--;
|
||||
stream() << "}";
|
||||
|
||||
if (m_generateItems) {
|
||||
auto *quickGrad = new QQuickShapeLinearGradient(shapePath);
|
||||
|
||||
quickGrad->setX1(logRect.left());
|
||||
quickGrad->setY1(logRect.top());
|
||||
quickGrad->setX2(logRect.right());
|
||||
quickGrad->setY2(logRect.bottom());
|
||||
setStops(quickGrad, linGrad->stops());
|
||||
|
||||
shapePath->setFillGradient(quickGrad);
|
||||
}
|
||||
} else if (grad->type() == QGradient::RadialGradient) {
|
||||
auto *radGrad = static_cast<const QRadialGradient*>(grad);
|
||||
stream() << "fillGradient: RadialGradient {";
|
||||
m_indentLevel++;
|
||||
|
||||
stream() << "centerX: " << radGrad->center().x();
|
||||
stream() << "centerY: " << radGrad->center().y();
|
||||
stream() << "centerRadius: " << radGrad->radius();
|
||||
stream() << "focalX: centerX; focalY: centerY";
|
||||
for (auto &stop : radGrad->stops()) {
|
||||
stream() << "GradientStop { position: " << stop.first << "; color: \"" << stop.second.name(QColor::HexArgb) << "\" }";
|
||||
}
|
||||
m_indentLevel--;
|
||||
stream() << "}";
|
||||
|
||||
if (m_generateItems) {
|
||||
auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
|
||||
quickGrad->setCenterX(radGrad->center().x());
|
||||
quickGrad->setCenterY(radGrad->center().y());
|
||||
quickGrad->setCenterRadius(radGrad->radius());
|
||||
quickGrad->setFocalX(radGrad->center().x());
|
||||
quickGrad->setFocalY(radGrad->center().y());
|
||||
setStops(quickGrad, radGrad->stops());
|
||||
|
||||
shapePath->setFillGradient(quickGrad);
|
||||
}
|
||||
}
|
||||
if (!(pathSelector & FillPath)) {
|
||||
stream() << "fillColor: \"transparent\"";
|
||||
if (shapePath)
|
||||
shapePath->setFillColor(Qt::transparent);
|
||||
} else if (auto *grad = currentFillGradient()) {
|
||||
outputGradient(grad, shapePath, boundingRect);
|
||||
} else {
|
||||
stream() << "fillColor: \"" << currentFillColor() << "\"";
|
||||
if (m_generateItems)
|
||||
if (shapePath)
|
||||
shapePath->setFillColor(QColor::fromString(currentFillColor()));
|
||||
}
|
||||
stream() << "fillRule: ShapePath.WindingFill";
|
||||
if (fillRule == QQuickShapePath::WindingFill)
|
||||
stream() << "fillRule: ShapePath.WindingFill";
|
||||
else
|
||||
stream() << "fillRule: ShapePath.OddEvenFill";
|
||||
if (shapePath)
|
||||
shapePath->setFillRule(fillRule);
|
||||
|
||||
QString svgPathString;
|
||||
QTextStream strm(&svgPathString);
|
||||
for (int i = 0; i < path.elementCount(); ++i) {
|
||||
QPainterPath::Element element = path.elementAt(i);
|
||||
if (element.isMoveTo()) {
|
||||
strm << "M " << (element.x - offset.x()) << " " << (element.y - offset.y()) << " ";
|
||||
} else if (element.isLineTo()) {
|
||||
strm << "L " << (element.x - offset.x()) << " " << (element.y - offset.y()) << " ";
|
||||
} else if (element.isCurveTo()) {
|
||||
QPointF c1((element.x - offset.x()), (element.y - offset.y()));
|
||||
++i;
|
||||
element = path.elementAt(i);
|
||||
|
||||
QPointF c2((element.x - offset.x()), (element.y - offset.y()));
|
||||
++i;
|
||||
element = path.elementAt(i);
|
||||
|
||||
strm<< "C "
|
||||
<< (c1.x() - offset.x()) << " "
|
||||
<< (c1.y() - offset.y()) << " "
|
||||
<< (c2.x() - offset.x()) << " "
|
||||
<< (c2.y() - offset.y()) << " "
|
||||
<< (element.x - offset.x()) << " "
|
||||
<< (element.y - offset.y()) << " ";
|
||||
}
|
||||
if (quadPath) {
|
||||
QString hintStr = pathHintString(*quadPath);
|
||||
if (!hintStr.isEmpty())
|
||||
stream() << hintStr;
|
||||
}
|
||||
|
||||
QString svgPathString = painterPath ? toSvgString(*painterPath) : toSvgString(*quadPath);
|
||||
stream() << "PathSvg { path: \"" << svgPathString << "\" }";
|
||||
|
||||
if (m_generateItems) {
|
||||
if (shapePath) {
|
||||
auto *pathSvg = new QQuickPathSvg;
|
||||
pathSvg->setPath(svgPathString);
|
||||
pathSvg->setParent(shapePath);
|
||||
|
@ -807,10 +942,15 @@ bool SvgLoaderVisitor::visitStructureNodeStart(const QSvgStructureNode *node)
|
|||
|
||||
if (!forceSeparatePaths && !isTopLevel && isPathContainer(node)) {
|
||||
stream() << shapeName() <<" { //combined path container";
|
||||
m_indentLevel++;
|
||||
if (m_flags & QSvgQmlWriter::CurveRenderer)
|
||||
stream() << "preferredRendererType: Shape.CurveRenderer";
|
||||
m_indentLevel--;
|
||||
|
||||
m_inShapeItem = true;
|
||||
if (m_generateItems) {
|
||||
auto *shapeItem = new QQuickShape;
|
||||
shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer); // TODO: settable
|
||||
m_inShapeItem = true;
|
||||
m_parentShapeItem = shapeItem;
|
||||
addCurrentItem(shapeItem, node);
|
||||
}
|
||||
|
@ -908,6 +1048,7 @@ QQuickItem *SvgLoaderVisitor::loadQML(QTextStream *outStream, const QSvgTinyDocu
|
|||
Q_ASSERT(outStream);
|
||||
m_stream = outStream;
|
||||
m_indentLevel = 0;
|
||||
|
||||
stream() << "import QtQuick";
|
||||
stream() << "import QtQuick.Shapes" << Qt::endl;
|
||||
|
||||
|
@ -949,13 +1090,18 @@ void SvgLoaderVisitor::visitNode(const QSvgNode *node)
|
|||
handleBaseNodeEnd(node);
|
||||
}
|
||||
|
||||
QQuickItem *QSvgQmlWriter::loadSVG(const QSvgTinyDocument *doc, const QString &outFileName, const QString &typeName, QQuickItem *parentItem)
|
||||
QQuickItem *QSvgQmlWriter::loadSVG(const QSvgTinyDocument *doc, const QString &outFileName, GeneratorFlags flags, const QString &typeName, QQuickItem *parentItem, const QString &commentString)
|
||||
{
|
||||
SvgLoaderVisitor visitor;
|
||||
if (!typeName.isEmpty())
|
||||
visitor.setShapeTypeName(typeName);
|
||||
visitor.setFlags(flags);
|
||||
QByteArray result;
|
||||
QTextStream str(&result);
|
||||
if (commentString.isEmpty())
|
||||
str << "// Generated from SVG" << Qt::endl;
|
||||
else
|
||||
str << "// " << commentString << Qt::endl;
|
||||
auto *loadedItem = visitor.loadQML(&str, doc, parentItem);
|
||||
if (!outFileName.isEmpty()) {
|
||||
QFile outFile(outFileName);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#define QSVGQMLWRITER_P_H
|
||||
|
||||
#include <QtCore/qtconfigmacros.h>
|
||||
#include <QtCore/qflags.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
@ -16,8 +17,15 @@ class QQuickItem;
|
|||
class QSvgQmlWriter
|
||||
{
|
||||
public:
|
||||
static QQuickItem *loadSVG(const QSvgTinyDocument *doc, const QString &outFileName, const QString &typeName, QQuickItem *parentItem = nullptr);
|
||||
enum GeneratorFlag {
|
||||
OptimizePaths = 0x01,
|
||||
CurveRenderer = 0x02,
|
||||
OutlineStrokeMode = 0x04
|
||||
};
|
||||
Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag);
|
||||
static QQuickItem *loadSVG(const QSvgTinyDocument *doc, const QString &outFileName, GeneratorFlags flags, const QString &typeName, QQuickItem *parentItem, const QString &commentString);
|
||||
};
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(QSvgQmlWriter::GeneratorFlags);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
|
Loading…
Reference in New Issue