From 65a0edaedf9cd241cfbaaecadd15434003113463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Thu, 5 Feb 2026 12:24:10 +0100 Subject: [PATCH 1/3] SVG Import: fix gradient points --- src/core/svgimporter.cpp | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/core/svgimporter.cpp b/src/core/svgimporter.cpp index 15fc59c4a..c85754d94 100644 --- a/src/core/svgimporter.cpp +++ b/src/core/svgimporter.cpp @@ -47,6 +47,17 @@ #define RGXS REGEX_SPACES +static qreal parseSvgUnit(const QString &str, + qreal relativeTo) +{ + QString trimmed = str.trimmed(); + if (trimmed.endsWith("%")) { + trimmed.remove("%"); + return (trimmed.toDouble() / 100.0) * relativeTo; + } + return trimmed.toDouble(); +} + class TextSvgAttributes { public: TextSvgAttributes() {} @@ -779,10 +790,26 @@ void loadElement(const QDomElement &element, ContainerBox *parentGroup, const QString x2s = element.attribute("x2"); const QString y2s = element.attribute("y2"); - x1 = toDouble(x1s); - y1 = toDouble(y1s), - x2 = toDouble(x2s); - y2 = toDouble(y2s); + // get viewbox w/h + QDomElement svgRoot = element.ownerDocument().documentElement(); + QStringList viewBox = svgRoot.attribute("viewBox").split(QRegularExpression("\\s+"), + Qt::SkipEmptyParts); + qreal viewW = 1.0; + qreal viewH = 1.0; + + if (viewBox.size() >= 4) { + viewW = viewBox.at(2).toDouble(); + viewH = viewBox.at(3).toDouble(); + } else { // fallback + viewW = svgRoot.attribute("width", "1").toDouble(); + viewH = svgRoot.attribute("height", "1").toDouble(); + } + + x1 = parseSvgUnit(x1s, viewW); + y1 = parseSvgUnit(y1s, viewH); + x2 = parseSvgUnit(x2s, viewW); + y2 = parseSvgUnit(y2s, viewH); + break; } case GradientType::RADIAL: From 478fe38235ee857c3ed8dea5bf94a09e81de9b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Thu, 5 Feb 2026 14:07:15 +0100 Subject: [PATCH 2/3] SVG Import: fix gradient point v2 --- src/core/svgimporter.cpp | 109 ++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/src/core/svgimporter.cpp b/src/core/svgimporter.cpp index c85754d94..98f84f129 100644 --- a/src/core/svgimporter.cpp +++ b/src/core/svgimporter.cpp @@ -644,14 +644,31 @@ bool extractScale(const QString& str, QMatrix& target) { return false; } -bool extractRotate(const QString& str, QMatrix& target) { - const QRegExp rx5(RGXS "rotate\\(" REGEX_SINGLE_FLOAT "\\)" RGXS, - Qt::CaseInsensitive); - if(rx5.exactMatch(str)) { - rx5.indexIn(str); - const QStringList capturedTxt = rx5.capturedTexts(); - target.rotate(capturedTxt.at(1).toDouble()); - return true; +bool extractRotate(const QString& str, + QMatrix& target) +{ + const QRegExp rxRotate("rotate\\s*\\(\\s*([^\\s,)]+)(?:[\\s,]+([^\\s,)]+)[\\s,]+([^\\s,)]+))?\\s*\\)", + Qt::CaseInsensitive); + + int pos = rxRotate.indexIn(str); + if (pos != -1) { + const QStringList captured = rxRotate.capturedTexts(); + bool ok; + double angle = captured.at(1).toDouble(&ok); + + if (ok) { + if (!captured.at(2).isEmpty() && !captured.at(3).isEmpty()) { + double cx = captured.at(2).toDouble(); + double cy = captured.at(3).toDouble(); + + target.translate(cx, cy); + target.rotate(angle); + target.translate(-cx, -cy); + } else { + target.rotate(angle); + } + return true; + } } return false; } @@ -778,19 +795,10 @@ void loadElement(const QDomElement &element, ContainerBox *parentGroup, } } - double x1; - double x2; - double y1; - double y2; switch(type) { case GradientType::LINEAR: + case GradientType::RADIAL: { - const QString x1s = element.attribute("x1"); - const QString y1s = element.attribute("y1"); - const QString x2s = element.attribute("x2"); - const QString y2s = element.attribute("y2"); - - // get viewbox w/h QDomElement svgRoot = element.ownerDocument().documentElement(); QStringList viewBox = svgRoot.attribute("viewBox").split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); @@ -800,42 +808,51 @@ void loadElement(const QDomElement &element, ContainerBox *parentGroup, if (viewBox.size() >= 4) { viewW = viewBox.at(2).toDouble(); viewH = viewBox.at(3).toDouble(); - } else { // fallback + } else { viewW = svgRoot.attribute("width", "1").toDouble(); viewH = svgRoot.attribute("height", "1").toDouble(); } + if (viewW <= 0) { viewW = 1.0; } + if (viewH <= 0) { viewH = 1.0; } + + QPointF p1, p2; + if (type == GradientType::LINEAR) { + p1 = QPointF(parseSvgUnit(element.attribute("x1"), viewW), + parseSvgUnit(element.attribute("y1"), viewH)); + p2 = QPointF(parseSvgUnit(element.attribute("x2"), viewW), + parseSvgUnit(element.attribute("y2"), viewH)); + } else { + const qreal cx = parseSvgUnit(element.attribute("cx"), viewW); + const qreal cy = parseSvgUnit(element.attribute("cy"), viewH); + const qreal r = parseSvgUnit(element.attribute("r"), (viewW + viewH) * 0.5); + p1 = QPointF(cx, cy); + p2 = QPointF(cx + r, cy + r); + } - x1 = parseSvgUnit(x1s, viewW); - y1 = parseSvgUnit(y1s, viewH); - x2 = parseSvgUnit(x2s, viewW); - y2 = parseSvgUnit(y2s, viewH); + const QString gradTrans = element.attribute("gradientTransform"); + QMatrix trans = getMatrixFromString(gradTrans); + QString units = element.attribute("gradientUnits"); - break; - } - case GradientType::RADIAL: - { - const QString cxs = element.attribute("cx"); - const QString cys = element.attribute("cy"); - const QString rs = element.attribute("r"); - - const double cx = toDouble(cxs); - const double cy = toDouble(cys); - const double r = toDouble(rs); - - x1 = cx; - y1 = cy; - x2 = cx + r; - y2 = cy + r; + if (!gradTrans.isEmpty()) { + if (units == "objectBoundingBox" || units.isEmpty()) { + p1 = QPointF(p1.x() / viewW, p1.y() / viewH); + p2 = QPointF(p2.x() / viewW, p2.y() / viewH); + + p1 = trans.map(p1); + p2 = trans.map(p2); + + p1 = QPointF(p1.x() * viewW, p1.y() * viewH); + p2 = QPointF(p2.x() * viewW, p2.y() * viewH); + } else { + p1 = trans.map(p1); + p2 = trans.map(p2); + } + } + + gGradients.insert(id, {gradient, p1.x(), p1.y(), p2.x(), p2.y(), QMatrix(), type}); break; } } - - const QString gradTrans = element.attribute("gradientTransform"); - const QMatrix trans = getMatrixFromString(gradTrans); - gGradients.insert(id, {gradient, - x1, y1, - x2, y2, - trans, type}); } else if(tagName == "path" || tagName == "polyline" || tagName == "polygon" || tagName == "line") { VectorPathSvgAttributes attributes; attributes.setParent(parentGroupAttributes); From dc80705c62e6a484904a90748179cf7f94f30b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Thu, 5 Feb 2026 14:42:21 +0100 Subject: [PATCH 3/3] SVG Import: fix gradient opacity --- src/core/svgimporter.cpp | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/core/svgimporter.cpp b/src/core/svgimporter.cpp index 98f84f129..fdfc7f43d 100644 --- a/src/core/svgimporter.cpp +++ b/src/core/svgimporter.cpp @@ -712,6 +712,54 @@ QMatrix getMatrixFromString(const QString &str) { static QMap gGradients; // to from static QMap gUnresolvedGradientLinks; + +void applyGradientToAttributes(const QDomElement &element, + BoxSvgAttributes &attributes, + const GradientCreator& gradientCreator) +{ + QString fillAttr = element.attribute("fill").trimmed(); + if (!fillAttr.startsWith("url(#")) { return; } + + int start = fillAttr.indexOf('#') + 1; + int end = fillAttr.lastIndexOf(')'); + if (fillAttr.at(end-1) == '\'' || fillAttr.at(end-1) == '\"') { end--; } + QString gradId = fillAttr.mid(start, end - start); + + if (gGradients.contains(gradId)) { + const SvgGradient &templateGrad = gGradients[gradId]; + + qreal opacity = 1.0; + if (element.hasAttribute("fill-opacity")) { + opacity = element.attribute("fill-opacity").toDouble(); + } else if (element.hasAttribute("opacity")) { + opacity = element.attribute("opacity").toDouble(); + } + + Gradient* newGradInstance = gradientCreator(); + + const QGradientStops stops = templateGrad.fGradient->getQGradientStops(); + for (const QGradientStop &stop : stops) { + QColor c = stop.second; + c.setAlphaF(c.alphaF() * opacity); + newGradInstance->addColor(c); + } + + newGradInstance->updateQGradientStops(); + + SvgGradient instance = { + newGradInstance, + templateGrad.fX1, templateGrad.fY1, + templateGrad.fX2, templateGrad.fY2, + templateGrad.fTrans, + templateGrad.fType + }; + + auto& fill = const_cast(attributes.getFillAttributes()); + fill.setGradient(instance); + fill.setPaintType(GRADIENTPAINT); + } +} + void loadElement(const QDomElement &element, ContainerBox *parentGroup, const BoxSvgAttributes &parentGroupAttributes, const GradientCreator& gradientCreator) { @@ -857,6 +905,7 @@ void loadElement(const QDomElement &element, ContainerBox *parentGroup, VectorPathSvgAttributes attributes; attributes.setParent(parentGroupAttributes); attributes.loadBoundingBoxAttributes(element); + applyGradientToAttributes(element, attributes, gradientCreator); if(tagName == "path") { loadVectorPath(element, parentGroup, attributes); } else if(tagName == "polyline") { @@ -872,6 +921,7 @@ void loadElement(const QDomElement &element, ContainerBox *parentGroup, BoxSvgAttributes attributes; attributes.setParent(parentGroupAttributes); attributes.loadBoundingBoxAttributes(element); + applyGradientToAttributes(element, attributes, gradientCreator); if(tagName == "g" || tagName == "text") { const auto group = loadBoxesGroup(element, parentGroup, attributes, gradientCreator);