File indexing completed on 2024-05-12 04:06:10

0001 /*
0002     SPDX-FileCopyrightText: 2007 Mark A. Taff <kde@marktaff.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #ifndef _KGAMESVGDOCUMENT_H_
0008 #define _KGAMESVGDOCUMENT_H_
0009 
0010 // own
0011 #include "kdegamesprivate_export.h"
0012 // Qt
0013 #include <QDomDocument>
0014 #include <QHash>
0015 #include <QTransform>
0016 // Std
0017 #include <memory>
0018 
0019 class KGameSvgDocumentPrivate;
0020 
0021 /**
0022  * \class KGameSvgDocument kgamesvgdocument.h <KGameSvgDocument>
0023  *
0024  * @brief A class for manipulating an SVG file using DOM
0025  *
0026  * This class is a wrapper around QDomDocument for SVG files.
0027  * It:
0028  * @li implements elementById();
0029  * @li manipulates a node's style properties; and,
0030  * @li manipulates a node's transform properties.
0031  *
0032  * @note The DOM standard requires all changes to be "live", so we cannot cache any values
0033  *     from the file; instead, we always have to query the DOM for the current value.  This also
0034  *     means that style & matrix changes we make happen to the DOM immediately.
0035  *
0036  * A typical use is to read in an SVG file, edit the style or transform attributes
0037  * in DOM as desired, and then output a QByteArray suitable for being loaded with
0038  * QSvgRenderer::load().
0039  *
0040  * To read an SVG file into DOM:
0041  * @code
0042  * KGameSvgDocument svgDom;
0043  * svgDom.load("/path/to/svgFile.svg");
0044  * @endcode
0045  *
0046  * To find a node with a specific value in its id attribute, for example where id="playerOneGamepiece":
0047  * @code
0048  * QDomNode playerOneGamepiece = svgDom.elementById("playerOneGamepiece");
0049  *
0050  * // This works too
0051  * QDomNode playerOneGamepiece = svgDom.elementByUniqueAttributeValue("id", "playerOneGamepiece");
0052  * @endcode
0053  *
0054  * Most methods operate on the last node found by @c elementById() or @c elementByUniqueAttributeValue().
0055  * If the methods are working on the wrong node, then you are mistaken about which node was
0056  * the last node (or you found a bug).  Try calling @c setCurrentNode() with the node you are
0057  * wanting to modify to see if this is the issue.  Consider the following code for example:
0058  * @code
0059  * QDomNode playerOneGamepiece = svgDom.elementById("playerOneGamepiece");
0060  * QDomNode playerTwoGamepiece = svgDom.elementById("playerTwoGamepiece");
0061  *
0062  * // Set player one's game piece to have a white fill
0063  * svgDom.setStyleProperty("fill", "#ffffff");  // INCORRECT: playerTwoGamepiece is the last node, not playerOneGamepiece
0064  *
0065  * svgDom.setCurrentNode(playerOneGamepiece);   // CORRECT: Set current node to the node we want,
0066  * svgDom.setStyleProperty("fill", "#ffffff");  // then we modify the node
0067  * @endcode
0068  *
0069  * To skew the @c currentNode():
0070  * @code
0071  * // Skew the horizontal axis 7.5 degrees
0072  * svgDom.skew(-7.5, 0, KGameSvgDocument::ReplaceCurrentMatrix);
0073  * @endcode
0074  *
0075  * @warning Be careful when using the KGameSvgDocument::ApplyToCurrentMatrix flag. It multiplies the matrices,
0076  *     so if you repeatedly apply the same matrix to a node, you have a polynomial series @c x^2, and you will
0077  *     very quickly run into overflow issues.
0078  *
0079  * To output @c currentNode() to be rendered:
0080  * @code
0081  * QSvgRenderer svgRenderer;
0082  * QByteArray svg = svgDom.nodeToByteArray();
0083  * svgRenderer.load(svg);
0084  * @endcode
0085  *
0086  * To output the whole document to be rendered (See QDomDocument::toByteArray()):
0087  * @code
0088  * QSvgRenderer svgRenderer;
0089  * QByteArray svg = svgDom.toByteArray();
0090  * svgRenderer.load(svg);
0091  * @endcode
0092  *
0093  * @see QDomDocument, QSvgRenderer
0094  * @author Mark A. Taff \<kde@marktaff.com\>
0095  * @version 0.1
0096  *
0097  * @todo Add convenience functions for getting/setting individual style properties.
0098  *     I haven't completely convinced myself of the utility of this, so don't hold your breathe. ;-)
0099  */
0100 class KDEGAMESPRIVATE_EXPORT KGameSvgDocument : public QDomDocument
0101 {
0102 public:
0103     /**
0104      * @brief Constructor
0105      */
0106     explicit KGameSvgDocument();
0107 
0108     /**
0109      * @brief Copy Constructor
0110      */
0111     KGameSvgDocument(const KGameSvgDocument &doc);
0112 
0113     /**
0114      * @brief Destructor
0115      */
0116     virtual ~KGameSvgDocument();
0117 
0118     /**
0119      * @brief Assignment Operator
0120      */
0121     KGameSvgDocument &operator=(const KGameSvgDocument &doc);
0122 
0123     /**
0124      * @brief Options for applying (multiplying) or replacing the current matrix
0125      */
0126     enum MatrixOption {
0127         /**
0128          * Apply to current matrix
0129          */
0130         ApplyToCurrentMatrix = 0x01,
0131         /**
0132          * Replace the current matrix
0133          */
0134         ReplaceCurrentMatrix = 0x02
0135     };
0136     /** @brief Q_DECLARE_FLAGS macro confuses doxygen, so create typedef's manually */
0137     typedef QFlags<MatrixOption> MatrixOptions;
0138 
0139     /**
0140      * Options for sorting style properties when building a style attribute
0141      */
0142     enum StylePropertySortOption {
0143         /**
0144          * When building a style attribute, do not sort
0145          */
0146         Unsorted = 0x01,
0147         /**
0148          * When building a style attribute, sort properties the same way Inkscape does
0149          */
0150         UseInkscapeOrder = 0x02
0151     };
0152     /** @brief Q_DECLARE_FLAGS macro confuses doxygen, so create typedef's manually */
0153     typedef QFlags<StylePropertySortOption> StylePropertySortOptions;
0154 
0155     /**
0156      * @brief Returns the node with the given value for the given attribute.
0157      *
0158      * Returns the element whose attribute given in @p attributeName is equal to the value
0159      * given in @p attributeValue.
0160      *
0161      * QDomDocument::elementById() always returns a null node because TT says they can't know
0162      * which attribute is the id attribute.  Here, we allow the id attribute to be specified.
0163      *
0164      * This function also sets @p m_currentNode to this node.
0165      *
0166      * @param attributeName The name of the identifying attribute, such as "id" to find.
0167      * @param attributeValue The value to look for in the attribute @p attributeName
0168      *     The values held in this attribute must be unique in the document, or the consequences
0169      *     may be unpredictably incorrect.  You've been warned. ;-)
0170      * @returns the matching node, or a null node if no matching node found
0171      */
0172     QDomNode elementByUniqueAttributeValue(const QString &attributeName, const QString &attributeValue);
0173 
0174     /**
0175      * @brief Returns a node with the given id.
0176      *
0177      * This is a convenience function.  We call elementByUniqueAttributeValue(), but we assume
0178      * that the name of the attribute is "id".  This assumption will be correct for valid SVG files.
0179      *
0180      * Returns the element whose ID is equal to elementId. If no element with the ID was found,
0181      * this function returns a null element.
0182      *
0183      * @param attributeValue The value of the id attribute to find
0184      * @returns the matching node, or a null node if no matching node found
0185      * @see elementByUniqueAttributeValue()
0186      */
0187     QDomNode elementById(const QString &attributeValue);
0188 
0189     /**
0190      * @brief Reads the SVG file svgFilename() into DOM.
0191      * @returns nothing
0192      */
0193     void load();
0194 
0195     /**
0196      * @overload
0197      * @brief This function permits specifying the svg file and loading it at once.
0198      *
0199      * @param svgFilename The filename of the SVG file to open.
0200      * @returns nothing
0201      */
0202     void load(const QString &svgFilename);
0203 
0204     /**
0205      * @brief Rotates the origin of the current node counterclockwise.
0206      *
0207      * @param degrees The amount in degrees to rotate by.
0208      * @param options Apply to current matrix or replace current matrix.
0209      * @returns nothing
0210      * @see QTransform#rotate()
0211      */
0212     void rotate(double degrees, MatrixOptions options = ApplyToCurrentMatrix);
0213 
0214     /**
0215      * @brief Moves the origin of the current node
0216      *
0217      * @param xPixels The number of pixels to move the x-axis by.
0218      * @param yPixels The number of pixels to move the y-axis by.
0219      * @param options Apply to current matrix or replace current matrix.
0220      * @returns nothing
0221      * @see QTransform::translate()
0222      */
0223     void translate(int xPixels, int yPixels, MatrixOptions options = ApplyToCurrentMatrix);
0224 
0225     /**
0226      * @brief Shears the origin of the current node.
0227      *
0228      * @param xRadians The amount in radians to shear (skew) the x-axis by.
0229      * @param yRadians The amount in radians to shear (skew) the y-axis by.
0230      * @param options Apply to current matrix or replace current matrix.
0231      * @returns nothing
0232      * @see QTransform::shear()
0233      */
0234     void shear(double xRadians, double yRadians, MatrixOptions options = ApplyToCurrentMatrix);
0235 
0236     /**
0237      * @brief Skews the origin of the current node.
0238      *
0239      * This is a convenience function.  It simply converts its arguments to
0240      * radians, then calls shear().
0241      *
0242      * @param xDegrees The amount in degrees to shear (skew) the x-axis by.
0243      * @param yDegrees The amount in degrees to shear (skew) the y-axis by.
0244      * @param options Apply to current matrix or replace current matrix.
0245      * @returns nothing
0246      * @see shear()
0247      */
0248     void skew(double xDegrees, double yDegrees, MatrixOptions options = ApplyToCurrentMatrix);
0249 
0250     /**
0251      * @brief Scales the origin of the current node.
0252      *
0253      * @note Neither @c xFactor nor @c yFactor may be zero, otherwise you scale
0254      *        the element into nonexistence.
0255      *
0256      * @param xFactor The factor to scale the x-axis by.
0257      * @param yFactor The factor to scale the y-axis by.
0258      * @param options Apply to current matrix or replace current matrix.
0259      * @returns nothing
0260      * @see QTransform::scale()
0261      */
0262     void scale(double xFactor, double yFactor, MatrixOptions options = ApplyToCurrentMatrix);
0263 
0264     /**
0265      * @brief Returns the last node found by elementById, or null if node not found
0266      *
0267      * @returns The current node
0268      * @see setCurrentNode()
0269      */
0270     QDomNode currentNode() const;
0271 
0272     /**
0273      * @brief Sets the current node.
0274      *
0275      * @param node The node to set currentNode to.
0276      * @returns nothing
0277      * @see currentNode()
0278      */
0279     void setCurrentNode(const QDomNode &node);
0280 
0281     /**
0282      * @brief Returns the name of the SVG file this DOM represents.
0283      *
0284      * @returns The current filename.
0285      * @see setSvgFilename()
0286      */
0287     QString svgFilename() const;
0288 
0289     /**
0290      * @brief Sets the current SVG filename.
0291      *
0292      * @param svgFilename The filename of the SVG file to open.
0293      * @returns nothing
0294      * @see svgFilename()
0295      */
0296     void setSvgFilename(const QString &svgFilename);
0297 
0298     /**
0299      * @brief Returns the value of the style property given for the current node.
0300      *
0301      * @note Internally, we create a hash with @c styleProperties, then return the value
0302      *     of the @c propertyName property.  As such, if you need the values of multiple
0303      *     properties, it will be more efficient to call @c styleProperties()
0304      *     and then use the hash directly.
0305      *
0306      * See KGameSvgDocumentPrivate::m_inkscapeOrder for a list of common SVG style properties
0307      *
0308      * @param propertyName the name of the property to return
0309      * @returns The value style property given, or null if no such property for this node.
0310      * @see setStyleProperty(), styleProperties(), setStyleProperties()
0311      */
0312     QString styleProperty(const QString &propertyName) const;
0313 
0314     /**
0315      * @brief Sets the value of the style property given for the current node.
0316      *
0317      * @note Internally, we create a hash with @c styleProperties, then update the
0318      *  @p propertyName to @p propertyValue, before finally applying the hash to
0319      *      DOM via @c setStyleProperties().  Because of this, if you need to set multiple
0320      *      properties per node, it will be more efficient to call @c styleProperties(),
0321      *      modify the hash it returns, and then apply the hash with @c setStyleProperties().
0322      *
0323      * @param propertyName The name of the property to set.
0324      * @param propertyValue The value of the property to set.
0325      * @returns nothing
0326      * @see styleProperty(), styleProperties(), setStyleProperties()
0327      */
0328     void setStyleProperty(const QString &propertyName, const QString &propertyValue);
0329 
0330     /**
0331      * @brief Returns the current node and its children as a new xml svg document.
0332      *
0333      * @returns The xml for the new svg document
0334      */
0335     QString nodeToSvg() const;
0336 
0337     /**
0338      * @brief Builds a new svg document and returns a QByteArray suitable for passing to QSvgRenderer::load().
0339      *
0340      * Internally, we call @c nodeToSvg() and then convert to a QByteArray, so this method
0341      * should be called @b instead of @c nodeToSvg().
0342      *
0343      * @returns the QByteArray
0344      */
0345     QByteArray nodeToByteArray() const;
0346 
0347     /**
0348      * @brief Returns the style attribute of the current node.
0349      *
0350      * Unless you are parsing your own style attribute for some reason, you probably
0351      * want to use styleProperty() or styleProperties().
0352      *
0353      * @returns The style attribute.
0354      * @see styleProperty() styleProperties()
0355      */
0356     QString style() const;
0357 
0358     /**
0359      * @brief Sets the style attribute of the current node.
0360      *
0361      * Unless you are parsing your own style attribute for some reason, you probably
0362      * want to use setStyleProperty() or setStyleProperties().
0363      *
0364      * @param styleAttribute The style attribute to apply.
0365      * @returns nothing
0366      *
0367      * @see setStyleProperty() setStyleProperties()
0368      */
0369     void setStyle(const QString &styleAttribute);
0370 
0371     /**
0372      * @brief Returns the patterns in the document
0373      *
0374      * @returns The patterns in the document
0375      */
0376     QDomNodeList patterns() const;
0377 
0378     /**
0379      * @brief Returns the linearGradients in the document
0380      *
0381      * @returns The linearGradients in the document
0382      */
0383     QDomNodeList linearGradients() const;
0384 
0385     /**
0386      * @brief Returns the radialGradients in the document
0387      *
0388      * @returns The radialGradients in the document
0389      */
0390     QDomNodeList radialGradients() const;
0391 
0392     /**
0393      * @brief Returns the defs in the document
0394      *
0395      * @returns The defs in the document
0396      */
0397     QDomNodeList defs() const;
0398 
0399     /**
0400      * @brief Returns the first def in the document
0401      *
0402      * @returns The first def in the document
0403      */
0404     QDomNode def() const;
0405 
0406     /**
0407      * @brief Returns the transform attribute of the current node.
0408      * @returns The transform attribute.
0409      * @see setTransform(), transformMatrix(), setTransformMatrix()
0410      */
0411     QString transform() const;
0412 
0413     /**
0414      * @brief Sets the transform attribute of the current node.
0415      *
0416      * As this function works on QStrings, it <b>replaces</b> the existing
0417      * transform attribute.  If you need to multiply, use setTransformMatrix() instead.
0418      *
0419      * @param transformAttribute The transform attribute to apply.
0420      * @returns nothing
0421      * @see transform(), transformMatrix(), setTransformMatrix()
0422      */
0423     void setTransform(const QString &transformAttribute);
0424 
0425     /**
0426      * @brief Returns a hash of the style properties of the current node.
0427      * @returns The style properties.
0428      * @see setStyleProperties()
0429      */
0430     QHash<QString, QString> styleProperties() const;
0431 
0432     /**
0433      * @brief Sets the style properties of the current node.
0434      *
0435      * The only(?) reason to set useInkscapeOrder to true is if you are saving the svg xml to a file
0436      * that may be human-edited later, for consistency. There is a performance hit, since hashes store
0437      * their data unsorted.
0438      *
0439      * @param _styleProperties The hash of style properties to apply.
0440      * @param options Apply the hash so the properties are in the same order as Inkscape writes them.
0441      * @returns nothing
0442      * @see styleProperties()
0443      */
0444     void setStyleProperties(const QHash<QString, QString> &_styleProperties, const StylePropertySortOptions &options = Unsorted);
0445 
0446     /**
0447      * @brief Returns the transform attribute of the current node as a matrix.
0448      *
0449      * @returns The matrix for the transform attribute.
0450      * @see setTransformMatrix()
0451      */
0452     QTransform transformMatrix() const;
0453 
0454     /**
0455      * @brief Sets the transform attribute of the current node.
0456      *
0457      * @param matrix The matrix to apply.
0458      * @param options Should we apply matrix to the current matrix?
0459      *     We modify matrix internally if @p options includes ApplyToCurrentMatrix, so it can't
0460      *     be passed as const.
0461      *     Normally we want to apply the existing matrix. If we apply the matrix,
0462      *     we potentially end up squaring with each call, e.g. x^2.
0463      * @returns nothing
0464      * @see transformMatrix()
0465      */
0466     void setTransformMatrix(QTransform &matrix, MatrixOptions options = ApplyToCurrentMatrix);
0467 
0468 private:
0469     /**
0470      * @brief d-pointer
0471      */
0472     std::unique_ptr<KGameSvgDocumentPrivate> const d;
0473 };
0474 Q_DECLARE_OPERATORS_FOR_FLAGS(KGameSvgDocument::MatrixOptions)
0475 Q_DECLARE_OPERATORS_FOR_FLAGS(KGameSvgDocument::StylePropertySortOptions)
0476 
0477 #endif // _KGAMESVGDOCUMENT_H_