• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

libkdegames

kgamesvgdocument.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (C) 2007 Mark A. Taff <kde@marktaff.com>                    *
00003  *                                                                         *
00004  *   This program is free software; you can redistribute it and/or modify  *
00005  *   it under the terms of the GNU Library General Public License          *
00006  *   version 2 as published by the Free Software Foundation                *
00007  *                                                                         *
00008  *   This program is distributed in the hope that it will be useful,       *
00009  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00010  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00011  *   GNU Library General Public License for more details.                  *
00012  *                                                                         *
00013  *   You should have received a copy of the GNU Library General Public     *
00014  *   License along with this program; if not, write to the                 *
00015  *   Free Software Foundation, Inc.,                                       *
00016  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
00017  ***************************************************************************/
00018 
00019 #include "kgamesvgdocument.h"
00020 #include "kgamesvgdocument_p.h"
00021 
00022 #include <kfilterdev.h>
00023 #include <kdebug.h>
00024 
00025 #include <QtCore/QFile>
00026 #include <QtCore/QString>
00027 #include <QtCore/QStringList>
00028 #include <QtXml/QDomElement>
00029 #include <QtXml/QDomNode>
00030 
00031 #include <math.h>
00032 
00033 //
00034 // Public
00035 //
00036 
00044 class KGameSvgDocumentPrivate
00045 {
00046     public:
00047 
00051     KGameSvgDocumentPrivate()
00052     {}
00053 
00054     ~KGameSvgDocumentPrivate()
00055     {}
00056 
00065     QDomNode findElementById(const QString& attributeName, const QString& attributeValue, const QDomNode& node);
00066 
00071     QDomElement currentElement() const;
00072 
00078     void setCurrentElement();
00079 
00084     bool styleHasTrailingSemicolon() const;
00085 
00092     void setStyleHasTrailingSemicolon(bool hasSemicolon);
00093 
00097     QDomNode m_currentNode;
00098 
00102     QDomElement m_currentElement;
00103 
00111     QStringList m_inkscapeOrder;
00112 
00118     static const QString SVG_XML_PREPEND;
00119 
00125     static const QString SVG_XML_APPEND;
00126 
00130     QString m_svgFilename;
00131 
00135     bool m_hasSemicolon;
00136 
00137 
00138 };
00139 
00140 const QString KGameSvgDocumentPrivate::SVG_XML_PREPEND = QString("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><svg>");
00141 const QString KGameSvgDocumentPrivate::SVG_XML_APPEND = QString("</svg>");
00142 
00143 KGameSvgDocument::KGameSvgDocument()
00144     : QDomDocument(), d(new KGameSvgDocumentPrivate)
00145 {}
00146 
00147 KGameSvgDocument::KGameSvgDocument(const KGameSvgDocument &doc)
00148     : QDomDocument(), d(new KGameSvgDocumentPrivate(*doc.d))
00149 {
00150 }
00151 
00152 KGameSvgDocument::~KGameSvgDocument()
00153 {
00154     delete d;
00155 }
00156 
00157 KGameSvgDocument& KGameSvgDocument::operator=(const KGameSvgDocument &doc)
00158 {
00159     QDomDocument::operator=(doc);
00160     *d = *doc.d;
00161     return *this;
00162 }
00163 
00164 QDomNode KGameSvgDocument::elementByUniqueAttributeValue(const QString& attributeName, const QString& attributeValue)
00165 {
00166     /* DOM is always "live", so there maybe a new root node.  We always have to ask for the
00167      * root node instead of keeping a pointer to it.
00168      */
00169     QDomElement docElem = documentElement();
00170     QDomNode n = docElem.firstChild();
00171 
00172     QDomNode node = d->findElementById(attributeName, attributeValue, n);
00173     setCurrentNode(node);
00174     return node;
00175 }
00176 
00177 QDomNode KGameSvgDocument::elementById(const QString& attributeValue)
00178 {
00179     return elementByUniqueAttributeValue("id", attributeValue);
00180 }
00181 
00182 void KGameSvgDocument::load()
00183 {
00184     if (d->m_svgFilename.isNull())
00185     {
00186         kDebug(11000) << "KGameSvgDocument::load(): Filename not specified.";
00187         return;
00188     }
00189 
00190     QFile file(d->m_svgFilename);
00191     if (!file.open(QIODevice::ReadOnly))
00192     {
00193         return;
00194     }
00195 
00196     // Reads file whether it is compressed or not
00197     QIODevice *filter = KFilterDev::device( &file, QString::fromUtf8("application/x-gzip"), false);
00198     if (!filter)
00199     {
00200         return;
00201     }
00202     delete filter;
00203 
00204     if (!setContent(&file))
00205     {
00206         file.close();
00207         kDebug(11000) << "DOM content not set.";
00208         return;
00209     }
00210     file.close();
00211 }
00212 
00213 void KGameSvgDocument::load(const QString& svgFilename)
00214 {
00215     setSvgFilename(svgFilename);
00216     load();
00217 }
00218 
00219 void KGameSvgDocument::rotate(double degrees, const MatrixOptions& options)
00220 {
00221     QMatrix matrix;
00222 
00223     if (options == ApplyToCurrentMatrix)
00224     {
00225         matrix = transformMatrix().QMatrix::rotate(degrees);
00226     }
00227     else
00228     {
00229         matrix = QMatrix();
00230         matrix.QMatrix::rotate(degrees);
00231     }
00232     setTransformMatrix(matrix, ReplaceCurrentMatrix);
00233 }
00234 
00235 void KGameSvgDocument::translate(int xPixels, int yPixels, const MatrixOptions& options)
00236 {
00237     QMatrix matrix;
00238 
00239     if (options == ApplyToCurrentMatrix)
00240     {
00241         matrix = transformMatrix().QMatrix::translate(xPixels, yPixels);
00242     }
00243     else
00244     {
00245         matrix = QMatrix();
00246         matrix.QMatrix::translate(xPixels, yPixels);
00247     }
00248     setTransformMatrix(matrix, ReplaceCurrentMatrix);
00249 }
00250 
00251 void KGameSvgDocument::shear(double xRadians, double yRadians, const MatrixOptions& options)
00252 {
00253     QMatrix matrix;
00254 
00255     if (options == ApplyToCurrentMatrix)
00256     {
00257         matrix = transformMatrix().QMatrix::shear(xRadians, yRadians);
00258     }
00259     else
00260     {
00261         matrix = QMatrix();
00262         matrix.QMatrix::shear(xRadians, yRadians);
00263     }
00264     setTransformMatrix(matrix, ReplaceCurrentMatrix);
00265 }
00266 
00267 void KGameSvgDocument::skew(double xDegrees, double yDegrees, const MatrixOptions& options)
00268 {
00269     double xRadians = xDegrees * (M_PI / 180);
00270     double yRadians = yDegrees * (M_PI / 180);
00271 
00272     shear(xRadians, yRadians, options);
00273 }
00274 
00275 void KGameSvgDocument::scale(double xFactor, double yFactor, const MatrixOptions& options)
00276 {
00277     QMatrix matrix;
00278     if ((xFactor == 0) || (yFactor == 0))
00279     {
00280         kWarning () << "KGameSvgDocument::scale: You cannnot scale by zero";
00281     }
00282 
00283     if (options == ApplyToCurrentMatrix)
00284     {
00285         matrix = transformMatrix().QMatrix::scale(xFactor, yFactor);
00286     }
00287     else
00288     {
00289         matrix = QMatrix();
00290         matrix.QMatrix::scale(xFactor, yFactor);
00291     }
00292     setTransformMatrix(matrix, ReplaceCurrentMatrix);
00293 }
00294 
00295 QDomNode KGameSvgDocument::currentNode() const
00296 {
00297     return d->m_currentNode;
00298 }
00299 
00300 void KGameSvgDocument::setCurrentNode(const QDomNode& node)
00301 {
00302     d->m_currentNode = node;
00303     d->setCurrentElement();
00304 }
00305 
00306 QString KGameSvgDocument::svgFilename() const
00307 {
00308     return d->m_svgFilename;
00309 }
00310 
00311 void KGameSvgDocument::setSvgFilename(const QString& svgFilename)
00312 {
00313     d->m_svgFilename = svgFilename;
00314 }
00315 
00316 QString KGameSvgDocument::styleProperty(const QString& propertyName) const
00317 {
00318     return styleProperties().value(propertyName);
00319 }
00320 
00321 void KGameSvgDocument::setStyleProperty(const QString& propertyName, const QString& propertyValue)
00322 {
00323     QHash<QString, QString> properties;
00324 
00325     properties = styleProperties();
00326     properties.insert(propertyName, propertyValue);
00327 
00328     setStyleProperties(properties, UseInkscapeOrder);
00329 }
00330 
00331 QString KGameSvgDocument::nodeToSvg() const
00332 {
00333     QString s, t, xml, defs, pattern;
00334     QTextStream str(&s);
00335     QTextStream str_t(&t);
00336     QStringList defsAdded;
00337     int result = 0;
00338     QRegExp rx;
00339 
00340     currentNode().save(str, 1);
00341     xml = *str.string();
00342 
00343     // Find and add any required gradients or patterns
00344     pattern = "url" + WSP_ASTERISK + OPEN_PARENS + WSP_ASTERISK + "#(.*)" + WSP_ASTERISK + CLOSE_PARENS;
00345     rx.setPattern(pattern);
00346     if (rx.indexIn(xml, result) != -1)
00347     {
00348         QDomNode node, nodeBase;
00349         QString baseId;
00350         QDomNode n = def();
00351 
00352         result = 0;
00353         while ((result = rx.indexIn(xml, result)) != -1)
00354         {
00355             // Find the pattern or gradient referenced
00356             result += rx.matchedLength();
00357             if (!defsAdded.contains(rx.cap(1)))
00358             {
00359                 node = d->findElementById("id", rx.cap(1), n);
00360                 node.save(str_t, 1);
00361                 defsAdded.append(rx.cap(1));
00362             }
00363 
00364             // Find the gradient the above gradient is based on
00365             baseId = node.toElement().attribute("xlink:href").mid(1);
00366             if (!defsAdded.contains(baseId))
00367             {
00368                 nodeBase = d->findElementById("id", baseId, n);
00369                 nodeBase.save(str_t, 1);
00370                 defsAdded.append(baseId);
00371             }
00372         }
00373         defs = *str_t.string();
00374         defs = "<defs>" + defs + "</defs>";
00375     }
00376 
00377     // Need to make node be a real svg document, so prepend and append required tags.
00378     xml = d->SVG_XML_PREPEND + defs + xml + d->SVG_XML_APPEND;
00379     return xml;
00380 }
00381 
00382 QByteArray KGameSvgDocument::nodeToByteArray() const
00383 {
00384     return nodeToSvg().toUtf8();
00385 }
00386 
00387 QString KGameSvgDocument::style() const
00388 {
00389     return d->m_currentElement.attribute( "style", "Element has no style attribute.");
00390 }
00391 
00392 void KGameSvgDocument::setStyle(const QString& styleAttribute)
00393 {
00394     d->m_currentElement.setAttribute("style", styleAttribute);
00395 }
00396 
00397 QDomNodeList KGameSvgDocument::patterns() const
00398 {
00399     return elementsByTagName("pattern");
00400 }
00401 
00402 QDomNodeList KGameSvgDocument::linearGradients() const
00403 {
00404     return elementsByTagName("linearGradient");
00405 }
00406 
00407 QDomNodeList KGameSvgDocument::radialGradients() const
00408 {
00409     return elementsByTagName("radialGradient");
00410 }
00411 
00412 QDomNodeList KGameSvgDocument::defs() const
00413 {
00414     return elementsByTagName("defs");
00415 }
00416 
00417 QDomNode KGameSvgDocument::def() const
00418 {
00419     return defs().at(0);
00420 }
00421 
00422 QString KGameSvgDocument::transform() const
00423 {
00424     return d->m_currentElement.attribute( "transform", "Element has no transform attribute.");
00425 }
00426 
00427 void KGameSvgDocument::setTransform(const QString& transformAttribute)
00428 {
00429     d->m_currentElement.setAttribute("transform", transformAttribute);
00430 }
00431 
00432 QHash<QString, QString> KGameSvgDocument::styleProperties() const
00433 {
00434     QHash<QString, QString> stylePropertiesHash;
00435     QStringList styleProperties, keyValuePair;
00436     QString styleProperty;
00437 
00438     styleProperties = style().split(";");
00439 
00440     /* The style attr may have a trailing semi-colon.  If it does, split()
00441      * gives us an empty final element.  Remove it or we get 'index out of range' errors
00442      */
00443     if (styleProperties.at((styleProperties.count()-1)).isEmpty())
00444     {
00445         styleProperties.removeAt((styleProperties.count()-1));
00446         d->setStyleHasTrailingSemicolon(true);
00447     }
00448     else {d->setStyleHasTrailingSemicolon(false);}
00449 
00450     for (int i = 0; i < styleProperties.size(); i++)
00451     {
00452         styleProperty = styleProperties.at(i);
00453         keyValuePair = styleProperty.split(":");
00454         stylePropertiesHash.insert(keyValuePair.at(0), keyValuePair.at(1));
00455     }
00456     return stylePropertiesHash;
00457 }
00458 
00459 void KGameSvgDocument::setStyleProperties(const QHash<QString, QString>& _styleProperties, const StylePropertySortOptions& options)
00460 {
00461     QHash<QString, QString> styleProperties = _styleProperties;
00462     QString styleBuffer, property;
00463 
00464     d->m_inkscapeOrder << "fill" << "fill-opacity" << "fill-rule" << "stroke" << "stroke-width" << "stroke-linecap"
00465                        << "stroke-linejoin" << "stroke-miterlimit" << "stroke-dasharray" << "stroke-opacity";
00466 
00467     if (options == UseInkscapeOrder)
00468     {
00469         for (int i = 0; i < d->m_inkscapeOrder.size(); i++)
00470         {
00471             property = d->m_inkscapeOrder.at(i);
00472             if (styleProperties.contains(property))
00473             {
00474                 styleBuffer += property + ':' + styleProperties.take(property) + ';';
00475             }
00476             else
00477             {
00478                 // Do Nothing
00479             }
00480         }
00481     }
00482 
00483     // Append any style properties
00484     if (!styleProperties.isEmpty())
00485     {
00486         QHashIterator<QString,  QString> it(styleProperties);
00487         while (it.hasNext())
00488         {
00489             it.next();
00490             styleBuffer += it.key() + ':' + it.value() + ';';
00491         }
00492     }
00493 
00494     // Remove trailing semicolon if original didn't have one
00495     if (!d->styleHasTrailingSemicolon()) {styleBuffer.chop(1);}
00496     setStyle(styleBuffer);
00497 }
00498 
00499 QMatrix KGameSvgDocument::transformMatrix() const
00500 {
00501     /*
00502      * Transform attributes can be quite complex.  Here, we assemble this tangled web of
00503      * complexity into an single matrix.
00504      * 
00505      * The regex's that make this bearable live in kgamesvgdocument_p.h.  As these regex's
00506      * get quite complex, we have some code in tests/kgamesvgdocumenttest.cpp to help verify 
00507      * they are still correct after being edited.
00508      *
00509      * Warning: This code depends on the capturing parenthesis in the regex's not changing.
00510      *
00511      * For all the gory details, see http://www.w3.org/TR/SVG/coords.html#TransformAttribute
00512      */
00513     QRegExp rx;
00514     QString transformAttribute;
00515     int result;
00516     int i = 0;
00517     QMatrix baseMatrix = QMatrix();
00518 
00519     transformAttribute = transform();
00520     if (transformAttribute == "Element has no transform attribute.")
00521     {
00522         return QMatrix();
00523     }
00524     transformAttribute.trimmed();
00525 
00526     rx.setPattern(TRANSFORMS);
00527     if (!rx.exactMatch(transformAttribute))
00528     {
00529         kWarning () << "Transform attribute seems to be invalid. Check your SVG file.";
00530         return QMatrix();
00531     }
00532 
00533     rx.setPattern(TRANSFORM);
00534 
00535     while (transformAttribute.size() > 0 && i < 32) // 32 is an arbitrary limit for the number of transforms for a single node
00536     {
00537         result = rx.indexIn(transformAttribute);
00538         if (result != -1) // Found left-most transform
00539         {
00540             if (rx.cap(1) == "matrix")
00541             {
00542                 // If the first transform found is a matrix, use it as the base,
00543                 // else we use a null matrix.
00544                 if (i == 0)
00545                 {
00546                     baseMatrix = QMatrix(rx.cap(2).toDouble(), rx.cap(3).toDouble(), rx.cap(4).toDouble(),
00547                                          rx.cap(5).toDouble(), rx.cap(6).toDouble(), rx.cap(7).toDouble());
00548                 }
00549                 else
00550                 {
00551                     baseMatrix = QMatrix(rx.cap(2).toDouble(), rx.cap(3).toDouble(), rx.cap(4).toDouble(),
00552                                          rx.cap(5).toDouble(), rx.cap(6).toDouble(), rx.cap(7).toDouble()) * baseMatrix;
00553                 }
00554             }
00555 
00556             if (rx.cap(8) == "translate")
00557             {
00558                 double x = rx.cap(9).toDouble();
00559                 double y = rx.cap(10).toDouble();
00560                 if (rx.cap(10).isEmpty()) // y defaults to zero per SVG standard
00561                 {
00562                     y = 0;
00563                 }
00564                 baseMatrix = baseMatrix.translate(x, y);
00565             }
00566 
00567             if (rx.cap(11) == "scale")
00568             {
00569                 double x = rx.cap(12).toDouble();
00570                 double y = rx.cap(12).toDouble();
00571                 if (rx.cap(13).isEmpty()) // y defaults to x per SVG standard
00572                 {
00573                     y = x;
00574                 }
00575                 baseMatrix = baseMatrix.scale(x, y);
00576             }
00577 
00578             if (rx.cap(14) == "rotate")
00579             {
00580                 double a = rx.cap(15).toDouble();
00581                 double cx = rx.cap(16).toDouble();
00582                 double cy = rx.cap(17).toDouble();
00583 
00584                 if ((cx > 0) || (cy > 0)) // rotate around point (cx, cy)
00585                 {
00586                     baseMatrix.translate(cx, cy);
00587                     baseMatrix.rotate(a);
00588                     baseMatrix.translate((cx * -1), (cy * -1));
00589                 }
00590                 else
00591                 {
00592                     baseMatrix = baseMatrix.rotate(a); // rotate around origin
00593                 }
00594             }
00595 
00596             if (rx.cap(18) == "skewX")
00597             {
00598                 baseMatrix = baseMatrix.shear(rx.cap(19).toDouble() * (M_PI / 180), 0);
00599             }
00600 
00601             if (rx.cap(20) == "skewY")
00602             {
00603                 baseMatrix = baseMatrix.shear(0, rx.cap(21).toDouble() * (M_PI / 180));
00604             }
00605         }
00606         transformAttribute = transformAttribute.mid(rx.matchedLength() + result);
00607         i++;
00608     }
00609 
00610     return baseMatrix;
00611 }
00612 
00613 void KGameSvgDocument::setTransformMatrix(QMatrix& matrix, const MatrixOptions& options)
00614 {
00615     QString transformBuffer, tmp;
00616     QMatrix null = QMatrix();
00617 
00618     if (options == ApplyToCurrentMatrix)
00619     {
00620         matrix = transformMatrix() * matrix;
00621     }
00622 
00623     transformBuffer = "matrix(";
00624     transformBuffer += tmp.setNum(matrix.m11(),'g',7) + ',';
00625     transformBuffer += tmp.setNum(matrix.m12(),'g',7) + ',';
00626     transformBuffer += tmp.setNum(matrix.m21(),'g',7) + ',';
00627     transformBuffer += tmp.setNum(matrix.m22(),'g',7) + ',';
00628     transformBuffer += tmp.setNum(matrix.dx(),'g',7) + ',';
00629     transformBuffer += tmp.setNum(matrix.dy(),'g',7) + ')';
00630 
00631     if ((transform() == "Element has no transform attribute.") && (matrix == null))
00632     {
00633         // Do not write a meaningless matrix to DOM
00634     }
00635     else
00636     {
00637         setTransform(transformBuffer);
00638     }
00639 }
00640 
00641 
00642 //
00643 // Private
00644 //
00645 
00646 QDomNode KGameSvgDocumentPrivate::findElementById(const QString& attributeName, const QString& attributeValue, const QDomNode& node)
00647 {
00648     QDomElement e = node.toElement(); // try to convert the node to an element.
00649     QString value = e.attribute( attributeName, "Element has no attribute with that name.");
00650 
00651     if (value == attributeValue)
00652     {
00653         // We found our node.  Stop recursion and return it.
00654         return node;
00655     }
00656 
00657     if (!node.firstChild().isNull())
00658     {
00659         QDomNode result = findElementById(attributeName, attributeValue, node.firstChild());
00663         if (!result.isNull()) return result; // If we found the node with id, then return it
00664     }
00665     if (!node.nextSibling().isNull())
00666     {
00667         QDomNode result = findElementById(attributeName, attributeValue, node.nextSibling());
00670         if (!result.isNull()) return result;
00671     }
00672     if (!node.firstChild().isNull() && !node.nextSibling().isNull())
00673     {
00674         // Do Nothing
00675         //kDebug(11000) << "No children or siblings.";
00676     }
00677 
00678     // Matching node not found, so return a null node.
00679     return QDomNode();
00680 }
00681 
00682 QDomElement KGameSvgDocumentPrivate::currentElement() const
00683 {
00684     return m_currentElement;
00685 }
00686 
00687 void KGameSvgDocumentPrivate::setCurrentElement()
00688 {
00689     m_currentElement = m_currentNode.toElement();
00690 }
00691 
00692 bool KGameSvgDocumentPrivate::styleHasTrailingSemicolon() const
00693 {
00694     return m_hasSemicolon;
00695 }
00696 
00697 void KGameSvgDocumentPrivate::setStyleHasTrailingSemicolon(bool hasSemicolon)
00698 {
00699     m_hasSemicolon = hasSemicolon;
00700 }
00701 

libkdegames

Skip menu "libkdegames"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • kblackbox
  • kgoldrunner
  • kmahjongg
  • ksquares
  • libkdegames
  •   highscore
  •   kgame
  •   kggzgames
  •   kggzmod
  •   kggznet
  • libkmahjongg
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal