| 1 | // Copyright (C) 2024 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #ifndef UTILS_P_H | 
| 5 | #define UTILS_P_H | 
| 6 |  | 
| 7 | // | 
| 8 | //  W A R N I N G | 
| 9 | //  ------------- | 
| 10 | // | 
| 11 | // This file is not part of the Qt API.  It exists purely as an | 
| 12 | // implementation detail.  This header file may change from version to | 
| 13 | // version without notice, or even be removed. | 
| 14 | // | 
| 15 | // We mean it. | 
| 16 | // | 
| 17 |  | 
| 18 | #include <private/qquicktranslate_p.h> | 
| 19 | #include <private/qquickitem_p.h> | 
| 20 | #include <private/qsvgnode_p.h> | 
| 21 |  | 
| 22 | #include <private/qquadpath_p.h> | 
| 23 | #include <private/qsvgvisitor_p.h> | 
| 24 |  | 
| 25 | QT_BEGIN_NAMESPACE | 
| 26 |  | 
| 27 | namespace QQuickVectorImageGenerator::Utils | 
| 28 | { | 
| 29 |  | 
| 30 | class ViewBoxItem : public QQuickItem | 
| 31 | { | 
| 32 | public: | 
| 33 |     ViewBoxItem(const QRectF viewBox, QQuickItem *parent = nullptr) : QQuickItem(parent), m_viewBox(viewBox) { setXForm(); } | 
| 34 |  | 
| 35 | protected: | 
| 36 |     void geometryChange(const QRectF &/*newGeometry*/, const QRectF &/*oldGeometry*/) override | 
| 37 |     { | 
| 38 |         setXForm(); | 
| 39 |     } | 
| 40 |  | 
| 41 | private: | 
| 42 |     void setXForm() | 
| 43 |     { | 
| 44 |         auto xformProp = transform(); | 
| 45 |         xformProp.clear(&xformProp); | 
| 46 |         bool translate = !qFuzzyIsNull(d: m_viewBox.x()) || !qFuzzyIsNull(d: m_viewBox.y()); | 
| 47 |         if (translate) { | 
| 48 |             auto *tr = new QQuickTranslate(this); | 
| 49 |             tr->setX(-m_viewBox.x()); | 
| 50 |             tr->setY(-m_viewBox.y()); | 
| 51 |             xformProp.append(&xformProp, tr); | 
| 52 |         } | 
| 53 |         if (!m_viewBox.isEmpty() && width() && height()) { | 
| 54 |             auto *scale = new QQuickScale(this); | 
| 55 |             qreal sx = width() / m_viewBox.width(); | 
| 56 |             qreal sy = height() / m_viewBox.height(); | 
| 57 |  | 
| 58 |             scale->setXScale(sx); | 
| 59 |             scale->setYScale(sy); | 
| 60 |             xformProp.append(&xformProp, scale); | 
| 61 |         } | 
| 62 |     } | 
| 63 |     QRectF m_viewBox; | 
| 64 | }; | 
| 65 |  | 
| 66 | inline QPainterPath polygonToPath(const QPolygonF &poly, bool closed) | 
| 67 | { | 
| 68 |     QPainterPath path; | 
| 69 |     if (poly.isEmpty()) | 
| 70 |         return path; | 
| 71 |     bool first = true; | 
| 72 |     for (const auto &p : poly) { | 
| 73 |         if (first) | 
| 74 |             path.moveTo(p); | 
| 75 |         else | 
| 76 |             path.lineTo(p); | 
| 77 |         first = false; | 
| 78 |     } | 
| 79 |     if (closed) | 
| 80 |         path.closeSubpath(); | 
| 81 |     return path; | 
| 82 | } | 
| 83 |  | 
| 84 | inline QString pathHintString(const QQuadPath &qp) | 
| 85 | { | 
| 86 |     QString res; | 
| 87 |     QTextStream str(&res); | 
| 88 |     auto flags = qp.pathHints(); | 
| 89 |     if (!flags) | 
| 90 |         return res; | 
| 91 |     str << "pathHints:" ; | 
| 92 |     bool first = true; | 
| 93 |  | 
| 94 | #define CHECK_PATH_HINT(flagName)              \ | 
| 95 |     if (flags.testFlag(QQuadPath::flagName)) { \ | 
| 96 |             if (!first)                            \ | 
| 97 |             str << " |";                       \ | 
| 98 |             first = false;                         \ | 
| 99 |             str << " ShapePath." #flagName;        \ | 
| 100 |     } | 
| 101 |  | 
| 102 |     CHECK_PATH_HINT(PathLinear) | 
| 103 |     CHECK_PATH_HINT(PathQuadratic) | 
| 104 |     CHECK_PATH_HINT(PathConvex) | 
| 105 |     CHECK_PATH_HINT(PathFillOnRight) | 
| 106 |     CHECK_PATH_HINT(PathSolid) | 
| 107 |     CHECK_PATH_HINT(PathNonIntersecting) | 
| 108 |     CHECK_PATH_HINT(PathNonOverlappingControlPointTriangles) | 
| 109 |  | 
| 110 |     return res; | 
| 111 | } | 
| 112 |  | 
| 113 | // Find the square that gives the same gradient in QGradient::LogicalMode as | 
| 114 | // objModeRect does in QGradient::ObjectMode | 
| 115 |  | 
| 116 | // When the object's bounding box is not square, the stripes that are conceptually | 
| 117 | // perpendicular to the gradient vector within object bounding box space shall render | 
| 118 | // non-perpendicular relative to the gradient vector in user space due to application | 
| 119 | // of the non-uniform scaling transformation from bounding box space to user space. | 
| 120 | inline QRectF mapToQtLogicalMode(const QRectF &objModeRect, const QRectF &boundingRect) | 
| 121 | { | 
| 122 |  | 
| 123 |     QRect pixelRect(objModeRect.x() * boundingRect.width() + boundingRect.left(), | 
| 124 |                     objModeRect.y() * boundingRect.height() + boundingRect.top(), | 
| 125 |                     objModeRect.width() * boundingRect.width(), | 
| 126 |                     objModeRect.height() * boundingRect.height()); | 
| 127 |  | 
| 128 |     if (pixelRect.isEmpty()) // pure horizontal/vertical gradient | 
| 129 |         return pixelRect; | 
| 130 |  | 
| 131 |     double w = boundingRect.width(); | 
| 132 |     double h = boundingRect.height(); | 
| 133 |     double objModeSlope = objModeRect.height() / objModeRect.width(); | 
| 134 |     double a = objModeSlope * w / h; | 
| 135 |  | 
| 136 |     // do calculation with origin == pixelRect.topLeft | 
| 137 |     double x2 = pixelRect.width(); | 
| 138 |     double y2 = pixelRect.height(); | 
| 139 |     double x = (x2 + a * y2) / (1 + a * a); | 
| 140 |     double y = y2 - (x - x2)/a; | 
| 141 |  | 
| 142 |     return QRectF(pixelRect.topLeft(), QSizeF(x,y)); | 
| 143 | } | 
| 144 |  | 
| 145 | inline QString toSvgString(const QPainterPath &path) | 
| 146 | { | 
| 147 |     QString svgPathString; | 
| 148 |     QTextStream strm(&svgPathString); | 
| 149 |  | 
| 150 |     for (int i = 0; i < path.elementCount(); ++i) { | 
| 151 |         QPainterPath::Element element = path.elementAt(i); | 
| 152 |         if (element.isMoveTo()) { | 
| 153 |             strm << "M "  << element.x << " "  << element.y << " " ; | 
| 154 |         } else if (element.isLineTo()) { | 
| 155 |             strm << "L "  << element.x << " "  << element.y << " " ; | 
| 156 |         } else if (element.isCurveTo()) { | 
| 157 |             QPointF c1(element.x, element.y); | 
| 158 |             ++i; | 
| 159 |             element = path.elementAt(i); | 
| 160 |  | 
| 161 |             QPointF c2(element.x, element.y); | 
| 162 |             ++i; | 
| 163 |             element = path.elementAt(i); | 
| 164 |             QPointF ep(element.x, element.y); | 
| 165 |  | 
| 166 |             strm <<  "C "  | 
| 167 |                  <<  c1.x() << " "  | 
| 168 |                  <<  c1.y() << " "  | 
| 169 |                  <<  c2.x() << " "  | 
| 170 |                  <<  c2.y() << " "  | 
| 171 |                  <<  ep.x() << " "  | 
| 172 |                  <<  ep.y() << " " ; | 
| 173 |         } | 
| 174 |     } | 
| 175 |  | 
| 176 |     return svgPathString; | 
| 177 | } | 
| 178 |  | 
| 179 | inline QString toSvgString(const QQuadPath &path) | 
| 180 | { | 
| 181 |     QString svgPathString; | 
| 182 |     QTextStream strm(&svgPathString); | 
| 183 |     path.iterateElements(lambda: [&](const QQuadPath::Element &e, int) { | 
| 184 |         if (e.isSubpathStart()) | 
| 185 |             strm << "M "  << e.startPoint().x() << " "  << e.startPoint().y() << " " ; | 
| 186 |         if (e.isLine()) | 
| 187 |             strm << "L "  << e.endPoint().x() << " "  << e.endPoint().y() << " " ; | 
| 188 |         else | 
| 189 |             strm << "Q "  << e.controlPoint().x() << " "  << e.controlPoint().y() << " "  | 
| 190 |                  << e.endPoint().x() << " "  << e.endPoint().y() << " " ; | 
| 191 |     }); | 
| 192 |  | 
| 193 |     return svgPathString; | 
| 194 | } | 
| 195 |  | 
| 196 | inline QString strokeCapStyleString(Qt::PenCapStyle strokeCapStyle) | 
| 197 | { | 
| 198 |     QString capStyle; | 
| 199 |     switch (strokeCapStyle) { | 
| 200 |     case Qt::FlatCap: | 
| 201 |         capStyle = QStringLiteral("ShapePath.FlatCap" ); | 
| 202 |         break; | 
| 203 |     case Qt::SquareCap: | 
| 204 |         capStyle = QStringLiteral("ShapePath.SquareCap" ); | 
| 205 |         break; | 
| 206 |     case Qt::RoundCap: | 
| 207 |         capStyle = QStringLiteral("ShapePath.RoundCap" ); | 
| 208 |         break; | 
| 209 |     default: | 
| 210 |         Q_UNREACHABLE(); | 
| 211 |         break; | 
| 212 |     } | 
| 213 |  | 
| 214 |     return capStyle; | 
| 215 | } | 
| 216 |  | 
| 217 | inline QString strokeJoinStyleString(Qt::PenJoinStyle strokeJoinStyle) | 
| 218 | { | 
| 219 |     QString joinStyle; | 
| 220 |     switch (strokeJoinStyle) { | 
| 221 |     case Qt::MiterJoin: | 
| 222 |         joinStyle = QStringLiteral("ShapePath.MiterJoin" ); | 
| 223 |         break; | 
| 224 |     case Qt::BevelJoin: | 
| 225 |         joinStyle = QStringLiteral("ShapePath.BevelJoin" ); | 
| 226 |         break; | 
| 227 |     case Qt::RoundJoin: | 
| 228 |         joinStyle = QStringLiteral("ShapePath.RoundJoin" ); | 
| 229 |         break; | 
| 230 |     default: | 
| 231 |         //TODO: Add support for SvgMiter case | 
| 232 |         Q_UNREACHABLE(); | 
| 233 |         break; | 
| 234 |     } | 
| 235 |  | 
| 236 |     return joinStyle; | 
| 237 | } | 
| 238 |  | 
| 239 | template<typename T> | 
| 240 | inline QString listString(QList<T> list) | 
| 241 | { | 
| 242 |     if (list.isEmpty()) | 
| 243 |         return QStringLiteral("[]" ); | 
| 244 |  | 
| 245 |     QString listString; | 
| 246 |     QTextStream stream(&listString); | 
| 247 |     stream << "[" ; | 
| 248 |  | 
| 249 |     if (list.length() > 1) { | 
| 250 |         for (int i = 0; i < list.length() - 1; i++) { | 
| 251 |             T v = list[i]; | 
| 252 |             stream << v << ", " ; | 
| 253 |         } | 
| 254 |     } | 
| 255 |  | 
| 256 |     stream << list.last() << "]" ; | 
| 257 |     return listString; | 
| 258 | } | 
| 259 |  | 
| 260 | } | 
| 261 |  | 
| 262 | QT_END_NAMESPACE | 
| 263 |  | 
| 264 | #endif // UTILS_P_H | 
| 265 |  |