diff --git a/libs/flake/svg/SvgMeshArray.cpp b/libs/flake/svg/SvgMeshArray.cpp index 14ad3d8e43..a4260a96a5 100644 --- a/libs/flake/svg/SvgMeshArray.cpp +++ b/libs/flake/svg/SvgMeshArray.cpp @@ -1,129 +1,144 @@ /* * Copyright (c) 2020 Sharaf Zaman * * 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 "SvgMeshArray.h" #include #include SvgMeshArray::SvgMeshArray() { } SvgMeshArray::~SvgMeshArray() { for (auto& row: m_array) { for (auto& patch: row) { delete patch; } } } void SvgMeshArray::newRow() { m_array << QVector(); } - -bool SvgMeshArray::addPatch(QList>& stops, const QPointF initialPoint) + +bool SvgMeshArray::addPatch(QList> stops, const QPointF initialPoint) { + // This is function is full of edge-case landmines, please run TestMeshArray after any changes if (stops.size() > 4 || stops.size() < 2) return false; SvgMeshPatch *patch = new SvgMeshPatch(initialPoint); if (m_array.empty()) { patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Top); stops.removeFirst(); m_array.append(QVector() << patch); } else { m_array.last().append(patch); } int irow = m_array.size() - 1; int icol = m_array.last().size() - 1; // first stop, except for the very first in the array if (irow != 0 || icol != 0) { // For first row, parse patches if (irow == 0) { patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Top); stops.removeFirst(); } else { // path is already defined for rows >= 1 QColor color = getStop(SvgMeshPatch::Left, irow - 1, icol).color; QList points = getPath(SvgMeshPatch::Bottom, irow - 1, icol); std::reverse(points.begin(), points.end()); patch->addStop(points, color, SvgMeshPatch::Top); } } - // Right and Bottom, will always be independent - for (int i = 1; i <= 2; ++i) { - patch->addStop(stops[0].first, stops[0].second, static_cast(SvgMeshPatch::Top + i)); + // Right will always be independent + patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Right); + stops.removeFirst(); + + if (icol > 0) { + patch->addStop( + stops[0].first, + stops[0].second, + SvgMeshPatch::Bottom, + true, getStop(SvgMeshPatch::Bottom, irow, icol - 1).point); + stops.removeFirst(); + } else { + patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Bottom); stops.removeFirst(); } // last stop if (icol == 0) { // if stop is in the 0th column, parse path - patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Left); + patch->addStop( + stops[0].first, + stops[0].second, + SvgMeshPatch::Left, + true, getStop(SvgMeshPatch::Top, irow, icol).point); stops.removeFirst(); } else { QColor color = getStop(SvgMeshPatch::Bottom, irow, icol - 1).color; // reuse Right side of the previous patch QList points = getPath(SvgMeshPatch::Right, irow, icol - 1); std::reverse(points.begin(), points.end()); patch->addStop(points, color, SvgMeshPatch::Left); } return true; } SvgMeshStop SvgMeshArray::getStop(const SvgMeshPatch::Type edge, const int row, const int col) const { assert(row < m_array.size() && col < m_array[row].size() && row >= 0 && col >= 0); SvgMeshPatch *patch = m_array[row][col]; SvgMeshStop *node = patch->getStop(edge); if (node != nullptr) { return *node; } switch (patch->countPoints()) { case 3: case 2: if (edge == SvgMeshPatch::Top) return getStop(SvgMeshPatch::Left, row - 1, col); else if (edge == SvgMeshPatch::Left) return getStop(SvgMeshPatch::Bottom, row, col - 1); } assert(false); } QList SvgMeshArray::getPath(const SvgMeshPatch::Type edge, const int row, const int col) const { assert(row < m_array.size() && col < m_array[row].size() && row >= 0 && col >= 0); return m_array[row][col]->getPath(edge).controlPoints(); } diff --git a/libs/flake/svg/SvgMeshArray.h b/libs/flake/svg/SvgMeshArray.h index 9c784d295b..df86170dca 100644 --- a/libs/flake/svg/SvgMeshArray.h +++ b/libs/flake/svg/SvgMeshArray.h @@ -1,47 +1,47 @@ /* * Copyright (c) 2020 Sharaf Zaman * * 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. */ #ifndef SVGMESHARRAY_H #define SVGMESHARRAY_H #include #include "SvgMeshPatch.h" -class SvgMeshArray +class KRITAFLAKE_EXPORT SvgMeshArray { public: SvgMeshArray(); ~SvgMeshArray(); void newRow(); - bool addPatch(QList>& stops, const QPointF initialPoint); + bool addPatch(QList> stops, const QPointF initialPoint); /// Get the point of a node in mesharray SvgMeshStop getStop(const SvgMeshPatch::Type edge, const int row, const int col) const; /// Get the Path Points for a segment of the meshpatch QList getPath(const SvgMeshPatch::Type edge, const int row, const int col) const; private: /// where each vector is a meshrow QVector> m_array; }; #endif // SVGMESHARRAY_H diff --git a/libs/flake/svg/SvgMeshPatch.cpp b/libs/flake/svg/SvgMeshPatch.cpp index 38d27d50c1..a18862b780 100644 --- a/libs/flake/svg/SvgMeshPatch.cpp +++ b/libs/flake/svg/SvgMeshPatch.cpp @@ -1,226 +1,225 @@ /* * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2020 Sharaf Zaman * * 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 "SvgMeshPatch.h" #include #include #include #include SvgMeshPatch::SvgMeshPatch(QPointF startingPoint) : m_newPath(true) , m_startingPoint(startingPoint) , m_path(new KoPathShape) { } SvgMeshPatch::~SvgMeshPatch() { for (auto &node: m_nodes.values()) { delete node; } } SvgMeshStop* SvgMeshPatch::getStop(SvgMeshPatch::Type type) const { if (m_nodes.find(type) == m_nodes.end()) return nullptr; return *m_nodes.find(type); } KoPathSegment SvgMeshPatch::getPath(Type type) const { KoPathPointIndex index(0, type - 1); return m_path->segmentByIndex(index); } -void SvgMeshPatch::addStop(const QString& pathStr, QColor color, Type edge) +void SvgMeshPatch::addStop(const QString& pathStr, QColor color, Type edge, bool pathIncomplete, QPointF lastPoint) { SvgMeshStop *node = new SvgMeshStop(color, m_startingPoint); m_nodes.insert(edge, node); - m_startingPoint = parseMeshPath(pathStr, edge == SvgMeshPatch::Left); + m_startingPoint = parseMeshPath(pathStr, pathIncomplete, lastPoint); } void SvgMeshPatch::addStop(const QList& pathPoints, QColor color, Type edge) { SvgMeshStop *stop = new SvgMeshStop(color, pathPoints.first()); m_nodes.insert(edge, stop); if (edge == SvgMeshPatch::Top) { m_path->moveTo(pathPoints.first()); m_newPath = false; } - + // if path is a line if (pathPoints.size() == 2) { m_path->lineTo(pathPoints.last()); } else if (pathPoints.size() == 4) { // if path is a Bezier curve m_path->curveTo(pathPoints[1], pathPoints[2], pathPoints[3]); } m_startingPoint = pathPoints.last(); } int SvgMeshPatch::countPoints() const { return m_nodes.size(); } -QPointF SvgMeshPatch::parseMeshPath(const QString& s, bool close) +QPointF SvgMeshPatch::parseMeshPath(const QString& s, bool pathIncomplete, const QPointF lastPoint) { // bits and pieces from KoPathShapeLoader, see the copyright above if (!s.isEmpty()) { QString d = s; d.replace(',', ' '); d = d.simplified(); const QByteArray buffer = d.toLatin1(); const char *ptr = buffer.constData(); qreal curx = m_startingPoint.x(); qreal cury = m_startingPoint.y(); qreal tox, toy, x1, y1, x2, y2; bool relative = false; char command = *(ptr++); if (m_newPath) { m_path->moveTo(m_startingPoint); m_newPath = false; } while (*ptr == ' ') ++ptr; switch (command) { case 'l': relative = true; Q_FALLTHROUGH(); case 'L': { ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (relative) { tox = curx + tox; toy = cury + toy; } m_path->lineTo(QPointF(tox, toy)); break; } case 'c': relative = true; Q_FALLTHROUGH(); case 'C': { ptr = getCoord(ptr, x1); ptr = getCoord(ptr, y1); ptr = getCoord(ptr, x2); ptr = getCoord(ptr, y2); ptr = getCoord(ptr, tox); ptr = getCoord(ptr, toy); if (relative) { x1 = curx + x1; y1 = cury + y1; x2 = curx + x2; y2 = cury + y2; tox = curx + tox; toy = cury + toy; } - if (close) { - QPointF start = m_path->pointByIndex(KoPathPointIndex(0, 0))->point(); - tox = start.x(); - toy = start.y(); + if (pathIncomplete) { + tox = lastPoint.x(); + toy = lastPoint.y(); } m_path->curveTo(QPointF(x1, y1), QPointF(x2, y2), QPointF(tox, toy)); break; } default: { qWarning() << "SvgMeshPatch::parseMeshPath: Bad command \"" << command << "\""; return QPointF(); } } return {tox, toy}; } return QPointF(); } const char* SvgMeshPatch::getCoord(const char* ptr, qreal& number) { // copied from KoPathShapeLoader, see the copyright above int integer, exponent; qreal decimal, frac; int sign, expsign; exponent = 0; integer = 0; frac = 1.0; decimal = 0; sign = 1; expsign = 1; // read the sign if (*ptr == '+') ++ptr; else if (*ptr == '-') { ++ptr; sign = -1; } // read the integer part while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') integer = (integer * 10) + *(ptr++) - '0'; if (*ptr == '.') { // read the decimals ++ptr; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') decimal += (*(ptr++) - '0') * (frac *= 0.1); } if (*ptr == 'e' || *ptr == 'E') { // read the exponent part ++ptr; // read the sign of the exponent if (*ptr == '+') ++ptr; else if (*ptr == '-') { ++ptr; expsign = -1; } exponent = 0; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') { exponent *= 10; exponent += *ptr - '0'; ++ptr; } } number = integer + decimal; number *= sign * pow((qreal)10, qreal(expsign * exponent)); // skip the following space if (*ptr == ' ') ++ptr; return ptr; } diff --git a/libs/flake/svg/SvgMeshPatch.h b/libs/flake/svg/SvgMeshPatch.h index 327aa0ca9e..81be7897f3 100644 --- a/libs/flake/svg/SvgMeshPatch.h +++ b/libs/flake/svg/SvgMeshPatch.h @@ -1,83 +1,89 @@ /* * Copyright (c) 2020 Sharaf Zaman * * 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. */ #ifndef SVGMESHPATCH_H #define SVGMESHPATCH_H #include #include #include #include #include #include struct SvgMeshStop { QColor color; QPointF point; SvgMeshStop() {} SvgMeshStop(QColor color, QPointF point) - : color(color), point(point) + : color(color), point(point) {} }; class SvgMeshPatch { public: /// Position of stop in the patch enum Type { Top = 1, Right, Bottom, Left, Size, }; SvgMeshPatch(QPointF startingPoint); ~SvgMeshPatch(); SvgMeshStop* getStop(Type type) const; KoPathSegment getPath(Type type) const; int countPoints() const; - /// Parses raw pathstr and adds path to the shape - void addStop(const QString& pathStr, QColor color, Type edge); + /* Parses raw pathstr and adds path to the shape, if the path isn't + * complete, it will have to be computed and given with pathIncomplete = true + * (Ideal case for std::optional) + */ + void addStop(const QString& pathStr, QColor color, Type edge, bool pathIncomplete = false, QPointF lastPoint = QPointF()); /// Adds path to the shape void addStop(const QList& pathPoints, QColor color, Type edge); private: - QPointF parseMeshPath(const QString& path, bool close = false); + /* Parses path and adds it to m_path and returns the last point of the curve/line + * see also: SvgMeshPatch::addStop + */ + QPointF parseMeshPath(const QString& path, bool pathIncomplete = false, const QPointF lastPoint = QPointF()); const char* getCoord(const char* ptr, qreal& number); private: bool m_newPath; /// This is the starting point for each path QPointF m_startingPoint; QMap m_nodes; QScopedPointer m_path; }; #endif // SVGMESHPATCH_H diff --git a/libs/flake/svg/SvgParser.h b/libs/flake/svg/SvgParser.h index 437e3295d2..8b3354bc84 100644 --- a/libs/flake/svg/SvgParser.h +++ b/libs/flake/svg/SvgParser.h @@ -1,234 +1,233 @@ /* This file is part of the KDE project * Copyright (C) 2002-2003,2005 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005,2007-2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SVGPARSER_H #define SVGPARSER_H #include #include #include #include #include #include #include "kritaflake_export.h" #include "SvgGradientHelper.h" #include "SvgFilterHelper.h" #include "SvgClipPathHelper.h" #include "SvgLoadingContext.h" #include "SvgStyleParser.h" #include "KoClipMask.h" #include -#include "SvgMeshPatch.h" class KoShape; class KoShapeGroup; class KoShapeContainer; class KoDocumentResourceManager; class KoVectorPatternBackground; class KoMarker; class KoPathShape; class KoSvgTextShape; class KRITAFLAKE_EXPORT SvgParser { struct DeferredUseStore; public: explicit SvgParser(KoDocumentResourceManager *documentResourceManager); virtual ~SvgParser(); static KoXmlDocument createDocumentFromSvg(QIODevice *device, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); static KoXmlDocument createDocumentFromSvg(const QByteArray &data, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); static KoXmlDocument createDocumentFromSvg(const QString &data, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); static KoXmlDocument createDocumentFromSvg(QXmlInputSource *source, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); /// Parses a svg fragment, returning the list of top level child shapes QList parseSvg(const KoXmlElement &e, QSizeF * fragmentSize = 0); /// Sets the initial xml base directory (the directory form where the file is read) void setXmlBaseDir(const QString &baseDir); void setResolution(const QRectF boundsInPixels, qreal pixelsPerInch); /// A special workaround coeff for using when loading old ODF-embedded SVG files, /// which used hard-coded 96 ppi for font size void setForcedFontSizeResolution(qreal value); /// Returns the list of all shapes of the svg document QList shapes() const; /// Takes the collection of symbols contained in the svg document. The parser will /// no longer know about the symbols. QVector takeSymbols(); QString documentTitle() const; QString documentDescription() const; typedef std::function FileFetcherFunc; void setFileFetcher(FileFetcherFunc func); QList> knownMarkers() const; void parseDefsElement(const KoXmlElement &e); KoShape* parseTextElement(const KoXmlElement &e, KoSvgTextShape *mergeIntoShape = 0); protected: /// Parses a group-like element element, saving all its topmost properties KoShape* parseGroup(const KoXmlElement &e, const KoXmlElement &overrideChildrenFrom = KoXmlElement(), bool createContext = true); // XXX KoShape* parseTextNode(const KoXmlText &e); /// Parses a container element, returning a list of child shapes QList parseContainer(const KoXmlElement &, bool parseTextNodes = false); /// XXX QList parseSingleElement(const KoXmlElement &b, DeferredUseStore* deferredUseStore = 0); /// Parses a use element, returning a list of child shapes KoShape* parseUse(const KoXmlElement &, DeferredUseStore* deferredUseStore); KoShape* resolveUse(const KoXmlElement &e, const QString& key); /// Parses a gradient element SvgGradientHelper *parseGradient(const KoXmlElement &); /// Parses mesh gradient element SvgGradientHelper* parseMeshGradient(const KoXmlElement&); /// Parses a single meshpatch and returns the pointer QList> parseMeshPatch(const KoXmlNode& meshpatch); /// Parses a pattern element QSharedPointer parsePattern(const KoXmlElement &e, const KoShape *__shape); /// Parses a filter element bool parseFilter(const KoXmlElement &, const KoXmlElement &referencedBy = KoXmlElement()); /// Parses a clip path element bool parseClipPath(const KoXmlElement &); bool parseClipMask(const KoXmlElement &e); bool parseMarker(const KoXmlElement &e); bool parseSymbol(const KoXmlElement &e); /// parses a length attribute qreal parseUnit(const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF()); /// parses a length attribute in x-direction qreal parseUnitX(const QString &unit); /// parses a length attribute in y-direction qreal parseUnitY(const QString &unit); /// parses a length attribute in xy-direction qreal parseUnitXY(const QString &unit); /// parses a angular attribute values, result in radians qreal parseAngular(const QString &unit); KoShape *createObjectDirect(const KoXmlElement &b); /// Creates an object from the given xml element KoShape * createObject(const KoXmlElement &, const SvgStyles &style = SvgStyles()); /// Create path object from the given xml element KoShape * createPath(const KoXmlElement &); /// find gradient with given id in gradient map SvgGradientHelper* findGradient(const QString &id); /// find pattern with given id in pattern map QSharedPointer findPattern(const QString &id, const KoShape *shape); /// find filter with given id in filter map SvgFilterHelper* findFilter(const QString &id, const QString &href = QString()); /// find clip path with given id in clip path map SvgClipPathHelper* findClipPath(const QString &id); /// Adds list of shapes to the given group shape void addToGroup(QList shapes, KoShapeContainer *group); /// creates a shape from the given shape id KoShape * createShape(const QString &shapeID); /// Creates shape from specified svg element KoShape * createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context); /// Builds the document from the given shapes list void buildDocument(QList shapes); void uploadStyleToContext(const KoXmlElement &e); void applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates); void applyCurrentBasicStyle(KoShape *shape); /// Applies styles to the given shape void applyStyle(KoShape *, const KoXmlElement &, const QPointF &shapeToOriginalUserCoordinates); /// Applies styles to the given shape void applyStyle(KoShape *, const SvgStyles &, const QPointF &shapeToOriginalUserCoordinates); /// Applies the current fill style to the object void applyFillStyle(KoShape * shape); /// Applies the current stroke style to the object void applyStrokeStyle(KoShape * shape); /// Applies the current filter to the object void applyFilter(KoShape * shape); /// Applies the current clip path to the object void applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates); void applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates); void applyMarkers(KoPathShape *shape); /// Applies id to specified shape void applyId(const QString &id, KoShape *shape); /// Applies viewBox transformation to the current graphical context /// NOTE: after applying the function currentBoundingBox can become null! void applyViewBoxTransform(const KoXmlElement &element); private: QSizeF m_documentSize; SvgLoadingContext m_context; QMap m_gradients; QMap m_filters; QMap m_clipPaths; QMap> m_clipMasks; QMap> m_markers; KoDocumentResourceManager *m_documentResourceManager; QList m_shapes; QMap m_symbols; QList m_toplevelShapes; QList m_defsShapes; bool m_isInsideTextSubtree = false; QString m_documentTitle; QString m_documentDescription; }; #endif