// // Copyright (C) 2017 James Turner zakalawe@mac.com // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "fgcanvaspath.h" #include #include #include #include #include #include #include "fgcanvaspaintcontext.h" #include "localprop.h" #include "canvasitem.h" #include "private/qtriangulator_p.h" // private QtGui header #include "private/qtriangulatingstroker_p.h" // private QtGui header #include "private/qvectorpath_p.h" // private QtGui header #include #include #include class PathQuickItem : public CanvasItem { Q_OBJECT Q_PROPERTY(QColor fillColor READ fillColor WRITE setFillColor NOTIFY fillColorChanged) Q_PROPERTY(QPen stroke READ stroke WRITE setStroke NOTIFY strokeChanged) public: PathQuickItem(QQuickItem* parent) : CanvasItem(parent) { setFlag(ItemHasContents); } void setPath(QPainterPath pp) { m_path = pp; QRectF pathBounds = pp.boundingRect(); setImplicitSize(pathBounds.width(), pathBounds.height()); update(); // request a paint node update } virtual QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData *) { if (m_path.isEmpty()) { return nullptr; } delete oldNode; QSGGeometryNode* fillGeom = nullptr; QSGGeometryNode* strokeGeom = nullptr; if (m_fillColor.isValid()) { // TODO: compute LOD for qTriangulate based on world transform QTransform transform; QTriangleSet triangles = qTriangulate(m_path, transform); int indexType = GL_UNSIGNED_SHORT; if (triangles.indices.type() == QVertexIndexVector::UnsignedShort) { // default is fine } else if (triangles.indices.type() == QVertexIndexVector::UnsignedInt) { indexType = GL_UNSIGNED_INT; } else { qFatal("Unsupported triangle index type"); } QSGGeometry* sgGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), triangles.vertices.size() >> 1, triangles.indices.size(), indexType); sgGeom->setIndexDataPattern(QSGGeometry::StaticPattern); sgGeom->setDrawingMode(GL_TRIANGLES); // QSGGeometry::Point2D *points = sgGeom->vertexDataAsPoint2D(); for (int v=0; v < triangles.vertices.size(); ) { const float vx = triangles.vertices.at(v++); const float vy = triangles.vertices.at(v++); (points++)->set(vx, vy); } if (triangles.indices.type() == QVertexIndexVector::UnsignedShort) { quint16* indices = sgGeom->indexDataAsUShort(); ::memcpy(indices, triangles.indices.data(), sizeof(unsigned short) * triangles.indices.size()); } else { quint32* indices = sgGeom->indexDataAsUInt(); ::memcpy(indices, triangles.indices.data(), sizeof(quint32) * triangles.indices.size()); } // create the node now, pretty trivial fillGeom = new QSGGeometryNode; fillGeom->setGeometry(sgGeom); fillGeom->setFlag(QSGNode::OwnsGeometry); QSGFlatColorMaterial* mat = new QSGFlatColorMaterial(); mat->setColor(m_fillColor); fillGeom->setMaterial(mat); fillGeom->setFlag(QSGNode::OwnsMaterial); } if (m_stroke.style() != Qt::NoPen) { const QVectorPath& vp = qtVectorPathForPath(m_path); QRectF clipBounds; QTriangulatingStroker ts; QPainter::RenderHints renderHints; if (m_stroke.style() == Qt::SolidLine) { ts.process(vp, m_stroke, clipBounds, renderHints); #if 0 inline int vertexCount() const { return m_vertices.size(); } inline const float *vertices() const { return m_vertices.data(); } #endif } else { QDashedStrokeProcessor dasher; dasher.process(vp, m_stroke, clipBounds, renderHints); QVectorPath dashStroke(dasher.points(), dasher.elementCount(), dasher.elementTypes(), renderHints); ts.process(dashStroke, m_stroke, clipBounds, renderHints); } QSGGeometry* sgGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), ts.vertexCount() >> 1); sgGeom->setVertexDataPattern(QSGGeometry::StaticPattern); sgGeom->setDrawingMode(GL_TRIANGLE_STRIP); QSGGeometry::Point2D *points = sgGeom->vertexDataAsPoint2D(); const float* vPtr = ts.vertices(); for (int v=0; v < ts.vertexCount(); v += 2) { const float vx = *vPtr++; const float vy = *vPtr++; (points++)->set(vx, vy); } // create the node now, pretty trivial strokeGeom = new QSGGeometryNode; strokeGeom->setGeometry(sgGeom); strokeGeom->setFlag(QSGNode::OwnsGeometry); QSGFlatColorMaterial* mat = new QSGFlatColorMaterial(); mat->setColor(m_stroke.color()); strokeGeom->setMaterial(mat); strokeGeom->setFlag(QSGNode::OwnsMaterial); } QSGClipNode* clip = updateClipNode(); if (clip) { if (fillGeom) clip->appendChildNode(fillGeom); if (strokeGeom) clip->appendChildNode(strokeGeom); return clip; } if (fillGeom && strokeGeom) { QSGNode* groupNode = new QSGNode; groupNode->appendChildNode(fillGeom); groupNode->appendChildNode(strokeGeom); return groupNode; } else if (fillGeom) { return fillGeom; } return strokeGeom; } QColor fillColor() const { return m_fillColor; } QPen stroke() const { return m_stroke; } public slots: void setFillColor(QColor fillColor) { if (m_fillColor == fillColor) return; m_fillColor = fillColor; emit fillColorChanged(fillColor); update(); } void setStroke(QPen stroke) { if (m_stroke == stroke) return; m_stroke = stroke; emit strokeChanged(stroke); update(); } signals: void fillColorChanged(QColor fillColor); void strokeChanged(QPen stroke); protected: void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { QQuickItem::geometryChanged(newGeometry, oldGeometry); update(); } QRectF boundingRect() const { if ((width() == 0.0) || (height() == 0.0)) { return QRectF(0.0, 0.0, implicitWidth(), implicitHeight()); } return QQuickItem::boundingRect(); } private: QPainterPath m_path; QColor m_fillColor; QPen m_stroke; }; static void pathArcSegment(QPainterPath &path, qreal xc, qreal yc, qreal th0, qreal th1, qreal rx, qreal ry, qreal xAxisRotation) { qreal sinTh, cosTh; qreal a00, a01, a10, a11; qreal x1, y1, x2, y2, x3, y3; qreal t; qreal thHalf; sinTh = qSin(xAxisRotation * (M_PI / 180.0)); cosTh = qCos(xAxisRotation * (M_PI / 180.0)); a00 = cosTh * rx; a01 = -sinTh * ry; a10 = sinTh * rx; a11 = cosTh * ry; thHalf = 0.5 * (th1 - th0); t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf); x1 = xc + qCos(th0) - t * qSin(th0); y1 = yc + qSin(th0) + t * qCos(th0); x3 = xc + qCos(th1); y3 = yc + qSin(th1); x2 = x3 + t * qSin(th1); y2 = y3 - t * qCos(th1); path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); } // the arc handling code underneath is from XSVG (BSD license) /* * Copyright 2002 USC/Information Sciences Institute * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Information Sciences Institute not be used in advertising or * publicity pertaining to distribution of the software without * specific, written prior permission. Information Sciences Institute * makes no representations about the suitability of this software for * any purpose. It is provided "as is" without express or implied * warranty. * * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * */ static void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation, int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx, qreal cury) { qreal sin_th, cos_th; qreal a00, a01, a10, a11; qreal x0, y0, x1, y1, xc, yc; qreal d, sfactor, sfactor_sq; qreal th0, th1, th_arc; int i, n_segs; qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; rx = qAbs(rx); ry = qAbs(ry); sin_th = qSin(x_axis_rotation * (M_PI / 180.0)); cos_th = qCos(x_axis_rotation * (M_PI / 180.0)); dx = (curx - x) / 2.0; dy = (cury - y) / 2.0; dx1 = cos_th * dx + sin_th * dy; dy1 = -sin_th * dx + cos_th * dy; Pr1 = rx * rx; Pr2 = ry * ry; Px = dx1 * dx1; Py = dy1 * dy1; /* Spec : check if radii are large enough */ check = Px / Pr1 + Py / Pr2; if (check > 1) { rx = rx * qSqrt(check); ry = ry * qSqrt(check); } a00 = cos_th / rx; a01 = sin_th / rx; a10 = -sin_th / ry; a11 = cos_th / ry; x0 = a00 * curx + a01 * cury; y0 = a10 * curx + a11 * cury; x1 = a00 * x + a01 * y; y1 = a10 * x + a11 * y; /* (x0, y0) is current point in transformed coordinate space. (x1, y1) is new point in transformed coordinate space. The arc fits a unit-radius circle in this space. */ d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); sfactor_sq = 1.0 / d - 0.25; if (sfactor_sq < 0) sfactor_sq = 0; sfactor = qSqrt(sfactor_sq); if (sweep_flag == large_arc_flag) sfactor = -sfactor; xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); /* (xc, yc) is center of the circle. */ th0 = qAtan2(y0 - yc, x0 - xc); th1 = qAtan2(y1 - yc, x1 - xc); th_arc = th1 - th0; if (th_arc < 0 && sweep_flag) th_arc += 2 * M_PI; else if (th_arc > 0 && !sweep_flag) th_arc -= 2 * M_PI; n_segs = qCeil(qAbs(th_arc / (M_PI * 0.5 + 0.001))); for (i = 0; i < n_segs; i++) { pathArcSegment(path, xc, yc, th0 + i * th_arc / n_segs, th0 + (i + 1) * th_arc / n_segs, rx, ry, x_axis_rotation); } } /////////////////////////////////////////////////////////////////////////////// FGCanvasPath::FGCanvasPath(FGCanvasGroup* pr, LocalProp* prop) : FGCanvasElement(pr, prop) { } void FGCanvasPath::doPaint(FGCanvasPaintContext *context) const { context->painter()->setPen(_stroke); switch (_paintType) { case Rect: context->painter()->drawRect(_rect); break; case RoundRect: context->painter()->drawRoundRect(_rect, _roundRectRadius.width(), _roundRectRadius.height()); break; case Path: context->painter()->drawPath(_painterPath); break; } if (isHighlighted()) { context->painter()->setPen(QPen(Qt::red, 1)); context->painter()->setBrush(Qt::NoBrush); switch (_paintType) { case Rect: case RoundRect: context->painter()->drawRect(_rect); break; case Path: context->painter()->drawRect(_painterPath.boundingRect()); break; } } } void FGCanvasPath::doPolish() { if (_pathDirty) { rebuildPath(); if (_quickPath) { _quickPath->setPath(_painterPath); } _pathDirty = false; } if (_penDirty) { rebuildPen(); if (_quickPath) { _quickPath->setStroke(_stroke); } _penDirty = false; } } void FGCanvasPath::markStyleDirty() { _penDirty = true; } CanvasItem *FGCanvasPath::createQuickItem(QQuickItem *parent) { _quickPath = new PathQuickItem(parent); _quickPath->setPath(_painterPath); _quickPath->setStroke(_stroke); _quickPath->setAntialiasing(true); return _quickPath; } CanvasItem *FGCanvasPath::quickItem() const { return _quickPath; } void FGCanvasPath::markPathDirty() { _pathDirty = true; requestPolish(); } void FGCanvasPath::markStrokeDirty() { _penDirty = true; requestPolish(); } bool FGCanvasPath::onChildAdded(LocalProp *prop) { if (FGCanvasElement::onChildAdded(prop)) { return true; } if ((prop->name() == "cmd") || (prop->name() == "coord") || (prop->name() == "svg")) { connect(prop, &LocalProp::valueChanged, this, &FGCanvasPath::markPathDirty); return true; } if (prop->name() == "rect") { _isRect = true; connect(prop, &LocalProp::childAdded, this, &FGCanvasPath::onChildAdded); return true; } // handle rect property changes if (prop->parent()->name() == "rect") { connect(prop, &LocalProp::valueChanged, this, &FGCanvasPath::markPathDirty); return true; } if (prop->name().startsWith("border-")) { connect(prop, &LocalProp::valueChanged, this, &FGCanvasPath::markPathDirty); return true; } if ((prop->name() == "cmd-geo") || (prop->name() == "coord-geo")) { // ignore for now, we let the server-side transform down to cartesian. // if we move that work to client side we could skip sending the cmd/coord data return true; } if (prop->name().startsWith("stroke")) { connect(prop, &LocalProp::valueChanged, this, &FGCanvasPath::markStrokeDirty); return true; } qDebug() << "path saw child:" << prop->name() << prop->index(); return false; } typedef enum { PathClose = ( 0 << 1), PathMoveTo = ( 1 << 1), PathLineTo = ( 2 << 1), PathHLineTo = ( 3 << 1), PathVLineTo = ( 4 << 1), PathQuadTo = ( 5 << 1), PathCubicTo = ( 6 << 1), PathSmoothQuadTo = ( 7 << 1), PathSmoothCubicTo = ( 8 << 1), PathShortCCWArc = ( 9 << 1), PathShortCWArc = (10 << 1), PathLongCCWArc = (11 << 1), PathLongCWArc = (12 << 1) } PathCommands; static const quint8 CoordsPerCommand[] = { 0, /* VG_CLOSE_PATH */ 2, /* VG_MOVE_TO */ 2, /* VG_LINE_TO */ 1, /* VG_HLINE_TO */ 1, /* VG_VLINE_TO */ 4, /* VG_QUAD_TO */ 6, /* VG_CUBIC_TO */ 2, /* VG_SQUAD_TO */ 4, /* VG_SCUBIC_TO */ 5, /* VG_SCCWARC_TO */ 5, /* VG_SCWARC_TO */ 5, /* VG_LCCWARC_TO */ 5 /* VG_LCWARC_TO */ }; void FGCanvasPath::rebuildPath() const { std::vector coords; std::vector commands; if (_isRect) { rebuildFromRect(commands, coords); } else if (_propertyRoot->hasChild("svg")) { if (!rebuildFromSVGData(commands, coords)) { qWarning() << "failed to parse SVG path data" << _propertyRoot->value("svg", QVariant()); } } else { for (QVariant v : _propertyRoot->valuesOfChildren("coord")) { coords.push_back(v.toFloat()); } for (QVariant v : _propertyRoot->valuesOfChildren("cmd")) { commands.push_back(v.toInt()); } } rebuildPathFromCommands(commands, coords); } QByteArrayList splitSVGPathData(QByteArray d) { QByteArrayList result; size_t pos = 0; std::string strData(d.data()); const char* seperators = "\n\r\t ,"; size_t startPos = strData.find_first_not_of(seperators, 0); for(;;) { pos = strData.find_first_of(seperators, startPos); if (pos == std::string::npos) { result.push_back(QByteArray::fromStdString(strData.substr(startPos))); break; } result.push_back(QByteArray::fromStdString(strData.substr(startPos, pos - startPos))); startPos = strData.find_first_not_of(seperators, pos); if (startPos == std::string::npos) { break; } } return result; } bool hasComplexBorderRadius(const LocalProp* prop) { for (auto childProp : prop->children()) { QByteArray name = childProp->name(); if (!name.startsWith("border-") || !name.endsWith("-radius")) { continue; } if (name != "border-radius") { return true; } } // of child prop iteration return false; } bool FGCanvasPath::rebuildFromRect(std::vector& commands, std::vector& coords) const { LocalProp* rectProp = _propertyRoot->getWithPath("rect"); if (hasComplexBorderRadius(_propertyRoot)) { // build a full path qWarning() << Q_FUNC_INFO << "implement me"; _paintType = Path; } else { float top = rectProp->value("top", 0.0).toFloat(); float left = rectProp->value("left", 0.0).toFloat(); float width = rectProp->value("width", 0.0).toFloat(); float height = rectProp->value("height", 0.0).toFloat(); if (rectProp->hasChild("right")) { width = rectProp->value("right", 0.0).toFloat() - left; } if (rectProp->hasChild("bottom")) { height = rectProp->value("bottom", 0.0).toFloat() - top; } _rect = QRectF(left, top, width, height); if (_propertyRoot->hasChild("border-radius")) { // round-rect float xR = _propertyRoot->value("border-radius", 0.0).toFloat(); float yR = xR; if (_propertyRoot->hasChild("border-radius[1]")) { yR = _propertyRoot->value("border-radius[1]", 0.0).toFloat(); } _roundRectRadius = QSizeF(xR, yR); _paintType = RoundRect; } else { // simple rect _paintType = Rect; } } return true; } bool FGCanvasPath::rebuildFromSVGData(std::vector& commands, std::vector& coords) const { QByteArrayList tokens = splitSVGPathData(_propertyRoot->value("svg", QVariant()).toByteArray()); PathCommands currentCommand = PathClose; bool isRelative = false; int numCoordsTokens = 0; const int totalTokens = tokens.size(); for (int index = 0; index < totalTokens; /* no increment */) { const QByteArray& tk = tokens.at(index); if ((tk.length() == 1) && std::isalpha(tk.at(0))) { // new command token const char svgCommand = std::toupper(tk.at(0)); isRelative = std::islower(tk.at(0)); switch (svgCommand) { case 'Z': currentCommand = PathClose; numCoordsTokens = 0; break; case 'M': currentCommand = PathMoveTo; numCoordsTokens = 2; break; case 'L': currentCommand = PathLineTo; numCoordsTokens = 2; break; case 'H': currentCommand = PathHLineTo; numCoordsTokens = 1; break; case 'V': currentCommand = PathVLineTo; numCoordsTokens = 1; break; case 'C': currentCommand = PathCubicTo; numCoordsTokens = 6; break; case 'S': currentCommand = PathSmoothCubicTo; numCoordsTokens = 4; break; case 'Q': currentCommand = PathQuadTo; numCoordsTokens = 4; break; case 'T': currentCommand = PathSmoothQuadTo; numCoordsTokens = 2; break; case 'A': currentCommand = PathShortCWArc; numCoordsTokens = 0; // handled specially below break; default: qWarning() << "unrecognized SVG command" << svgCommand; return false; } ++index; } switch (currentCommand) { case PathMoveTo: commands.push_back(PathMoveTo | (isRelative ? 1 : 0)); currentCommand = PathLineTo; break; case PathClose: case PathLineTo: case PathHLineTo: case PathVLineTo: case PathQuadTo: case PathCubicTo: case PathSmoothQuadTo: case PathSmoothCubicTo: commands.push_back(currentCommand | (isRelative ? 1 : 0)); break; case PathShortCWArc: case PathShortCCWArc: case PathLongCWArc: case PathLongCCWArc: { // decode the actual arc type coords.push_back(tokens.at(index++).toFloat()); // rx coords.push_back(tokens.at(index++).toFloat()); // ry coords.push_back(tokens.at(index++).toFloat()); // x-axis rotation const bool isLargeArc = (tokens.at(index++).toInt() != 0); // large-angle const bool isCCW = (tokens.at(index++).toInt() != 0); // sweep-flag if (isLargeArc) { commands.push_back(isCCW ? PathLongCCWArc : PathLongCWArc); } else { commands.push_back(isCCW ? PathShortCCWArc : PathShortCWArc); } if (isRelative) { commands.back() |= 1; } coords.push_back(tokens.at(index++).toFloat()); coords.push_back(tokens.at(index++).toFloat()); break; } default: qWarning() << "invalid path command"; return false; } // of current command switch // copy over tokens according to the active command. if (index + numCoordsTokens > totalTokens) { qWarning() << "insufficent remaining tokens for SVG command" << currentCommand; qWarning() << index << numCoordsTokens << totalTokens; return false; } for (int c = 0; c < numCoordsTokens; ++c) { coords.push_back(tokens.at(index + c).toFloat()); } index += numCoordsTokens; } // of tokens iteration return true; } void FGCanvasPath::rebuildPathFromCommands(const std::vector& commands, const std::vector& coords) const { QPainterPath newPath; const float* coord = coords.data(); QPointF lastControlPoint; // for smooth cubics / quadric size_t currentCoord = 0; for (int cmd : commands) { bool isRelative = cmd & 0x1; const int op = cmd & ~0x1; const int cmdIndex = op >> 1; const float baseX = isRelative ? newPath.currentPosition().x() : 0.0f; const float baseY = isRelative ? newPath.currentPosition().y() : 0.0f; if ((currentCoord + CoordsPerCommand[cmdIndex]) > coords.size()) { qWarning() << "insufficient path data" << currentCoord << cmdIndex << CoordsPerCommand[cmdIndex] << coords.size(); break; } switch (op) { case PathClose: newPath.closeSubpath(); break; case PathMoveTo: newPath.moveTo(coord[0] + baseX, coord[1] + baseY); break; case PathLineTo: newPath.lineTo(coord[0] + baseX, coord[1] + baseY); break; case PathHLineTo: newPath.lineTo(coord[0] + baseX, newPath.currentPosition().y()); break; case PathVLineTo: newPath.lineTo(newPath.currentPosition().x(), coord[0] + baseY); break; case PathQuadTo: newPath.quadTo(coord[0] + baseX, coord[1] + baseY, coord[2] + baseX, coord[3] + baseY); lastControlPoint = QPointF(coord[0] + baseX, coord[1] + baseY); break; case PathCubicTo: newPath.cubicTo(coord[0] + baseX, coord[1] + baseY, coord[2] + baseX, coord[3] + baseY, coord[4] + baseX, coord[5] + baseY); lastControlPoint = QPointF(coord[2] + baseX, coord[3] + baseY); break; case PathSmoothQuadTo: { QPointF smoothControlPoint = (newPath.currentPosition() - lastControlPoint) * 2.0; newPath.quadTo(smoothControlPoint.x(), smoothControlPoint.y(), coord[0] + baseX, coord[1] + baseY); lastControlPoint = smoothControlPoint; break; } case PathSmoothCubicTo: { QPointF smoothControlPoint = (newPath.currentPosition() - lastControlPoint) * 2.0; newPath.cubicTo(smoothControlPoint.x(), smoothControlPoint.y(), coord[0] + baseX, coord[1] + baseY, coord[2] + baseX, coord[3] + baseY); lastControlPoint = QPointF(coord[0] + baseX, coord[1] + baseY); break; } #if 0 qreal rx, qreal ry, qreal x_axis_rotation, int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx, qreal cury) #endif case PathLongCCWArc: case PathLongCWArc: pathArc(newPath, coord[0], coord[1], coord[2], true, (op == PathLongCCWArc), coord[3] + baseX, coord[4] + baseY, newPath.currentPosition().x(), newPath.currentPosition().y()); break; case PathShortCCWArc: case PathShortCWArc: pathArc(newPath, coord[0], coord[1], coord[2], false, (op == PathShortCCWArc), coord[3] + baseX, coord[4] + baseY, newPath.currentPosition().x(), newPath.currentPosition().y()); break; default: qWarning() << "uhandled path command type:" << cmdIndex; } if ((op < PathQuadTo) || (op > PathSmoothCubicTo)) { lastControlPoint = newPath.currentPosition(); } coord += CoordsPerCommand[cmdIndex]; currentCoord += CoordsPerCommand[cmdIndex]; } // of commands iteration // qDebug() << _propertyRoot->path() << "path" << newPath; _painterPath = newPath; } static Qt::PenCapStyle qtCapFromCanvas(QString s) { if (s.isEmpty() || (s == "butt")) { return Qt::FlatCap; } else if (s == "round") { return Qt::RoundCap; } else if (s == "square") { return Qt::SquareCap; } else { qDebug() << Q_FUNC_INFO << s; } return Qt::FlatCap; } static Qt::PenJoinStyle qtJoinFromCanvas(QString s) { if (s.isEmpty() || (s == "miter")) { return Qt::MiterJoin; } else if (s == "round") { return Qt::RoundJoin; } else { qDebug() << Q_FUNC_INFO << s; } return Qt::MiterJoin; } static QVector qtPenDashesFromCanvas(QString s, double penWidth) { QVector result; Q_FOREACH(QString v, s.split(',')) { result.push_back(v.toFloat() / penWidth); } // https://developer.mozilla.org/en/docs/Web/SVG/Attribute/stroke-dasharray // odd number = double it if ((result.size() % 2) == 1) { result += result; } return result; } void FGCanvasPath::rebuildPen() const { QPen p; QVariant strokeColor = getCascadedStyle("stroke"); p.setColor(parseColorValue(strokeColor)); p.setWidthF(getCascadedStyle("stroke-width", 1.0).toFloat()); p.setCapStyle(qtCapFromCanvas(_propertyRoot->value("stroke-linecap", QString()).toString())); p.setJoinStyle(qtJoinFromCanvas(_propertyRoot->value("stroke-linejoin", QString()).toString())); QString dashArray = _propertyRoot->value("stroke-dasharray", QVariant()).toString(); if (!dashArray.isEmpty() && (dashArray != "none")) { p.setDashPattern(qtPenDashesFromCanvas(dashArray, p.widthF())); } _stroke = p; } #include "fgcanvaspath.moc"