00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
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
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
00167
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
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
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
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
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
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
00441
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
00479 }
00480 }
00481 }
00482
00483
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
00495 if (!d->styleHasTrailingSemicolon()) {styleBuffer.chop(1);}
00496 setStyle(styleBuffer);
00497 }
00498
00499 QMatrix KGameSvgDocument::transformMatrix() const
00500 {
00501
00502
00503
00504
00505
00506
00507
00508
00509
00510
00511
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)
00536 {
00537 result = rx.indexIn(transformAttribute);
00538 if (result != -1)
00539 {
00540 if (rx.cap(1) == "matrix")
00541 {
00542
00543
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())
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())
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))
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);
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
00634 }
00635 else
00636 {
00637 setTransform(transformBuffer);
00638 }
00639 }
00640
00641
00642
00643
00644
00645
00646 QDomNode KGameSvgDocumentPrivate::findElementById(const QString& attributeName, const QString& attributeValue, const QDomNode& node)
00647 {
00648 QDomElement e = node.toElement();
00649 QString value = e.attribute( attributeName, "Element has no attribute with that name.");
00650
00651 if (value == attributeValue)
00652 {
00653
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;
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
00675
00676 }
00677
00678
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