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_