00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include <qdom.h>
00022 #include <qfile.h>
00023 #include <qcolor.h>
00024 #include <qimage.h>
00025 #include <qwmatrix.h>
00026 #include <qregexp.h>
00027
00028 #include <kmdcodec.h>
00029
00030 #include <zlib.h>
00031
00032 #include "ksvgiconpainter.h"
00033 #include "ksvgiconengine.h"
00034
00035 class KSVGIconEngineHelper
00036 {
00037 public:
00038 KSVGIconEngineHelper(KSVGIconEngine *engine)
00039 {
00040 m_engine = engine;
00041 }
00042
00043 ~KSVGIconEngineHelper()
00044 {
00045 }
00046
00047 double toPixel(const QString &s, bool hmode)
00048 {
00049 return m_engine->painter()->toPixel(s, hmode);
00050 }
00051
00052 ArtGradientStop *parseGradientStops(QDomElement element, int &offsets)
00053 {
00054 if (!element.hasChildNodes())
00055 return 0;
00056
00057 QValueList<ArtGradientStop> stopList;
00058
00059 float oldOffset = -1, newOffset = -1;
00060 for(QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling())
00061 {
00062 QDomElement element = node.toElement();
00063
00064 oldOffset = newOffset;
00065 QString temp = element.attribute("offset");
00066
00067 if(temp.contains("%"))
00068 {
00069 temp = temp.left(temp.length() - 1);
00070 newOffset = temp.toFloat() / 100.0;
00071 }
00072 else
00073 newOffset = temp.toFloat();
00074
00075
00076 if(oldOffset == newOffset)
00077 continue;
00078
00079 offsets++;
00080 stopList.append(ArtGradientStop());
00081
00082 ArtGradientStop &stop = stopList.last();
00083
00084 stop.offset = newOffset;
00085
00086 QString parseOpacity;
00087 QString parseColor;
00088
00089 if(element.hasAttribute("stop-opacity"))
00090 parseOpacity = element.attribute("stop-opacity");
00091
00092 if(element.hasAttribute("stop-color"))
00093 parseColor = element.attribute("stop-color");
00094
00095 if(parseOpacity.isEmpty() || parseColor.isEmpty())
00096 {
00097 QString style = element.attribute("style");
00098
00099 QStringList substyles = QStringList::split(';', style);
00100 for(QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it)
00101 {
00102 QStringList substyle = QStringList::split(':', (*it));
00103 QString command = substyle[0];
00104 QString params = substyle[1];
00105 command = command.stripWhiteSpace();
00106 params = params.stripWhiteSpace();
00107
00108 if(command == "stop-color")
00109 {
00110 parseColor = params;
00111
00112 if(!parseOpacity.isEmpty())
00113 break;
00114 }
00115 else if(command == "stop-opacity")
00116 {
00117 parseOpacity = params;
00118
00119 if(!parseColor.isEmpty())
00120 break;
00121 }
00122 }
00123 }
00124
00125
00126
00127 QColor qStopColor = m_engine->painter()->parseColor(parseColor);
00128
00129
00130 Q_UINT32 stopColor = m_engine->painter()->toArtColor(qStopColor);
00131
00132 int opacity = m_engine->painter()->parseOpacity(parseOpacity);
00133
00134 Q_UINT32 rgba = (stopColor << 8) | opacity;
00135 Q_UINT32 r, g, b, a;
00136
00137
00138 a = rgba & 0xff;
00139 r = (rgba >> 24) * a + 0x80;
00140 r = (r + (r >> 8)) >> 8;
00141 g = ((rgba >> 16) & 0xff) * a + 0x80;
00142 g = (g + (g >> 8)) >> 8;
00143 b = ((rgba >> 8) & 0xff) * a + 0x80;
00144 b = (b + (b >> 8)) >> 8;
00145
00146 stop.color[0] = ART_PIX_MAX_FROM_8(r);
00147 stop.color[1] = ART_PIX_MAX_FROM_8(g);
00148 stop.color[2] = ART_PIX_MAX_FROM_8(b);
00149 stop.color[3] = ART_PIX_MAX_FROM_8(a);
00150 }
00151
00152 if (stopList.isEmpty())
00153 return 0;
00154
00155 ArtGradientStop *stops = new ArtGradientStop[stopList.count()];
00156
00157 QValueList<ArtGradientStop>::iterator it = stopList.begin();
00158 QValueList<ArtGradientStop>::iterator end = stopList.end();
00159
00160 for (int i = 0; it != end; ++i, ++it)
00161 stops[i] = *it;
00162
00163 return stops;
00164 }
00165
00166 QPointArray parsePoints(QString points)
00167 {
00168 if(points.isEmpty())
00169 return QPointArray();
00170
00171 points = points.simplifyWhiteSpace();
00172
00173 if(points.contains(",,") || points.contains(", ,"))
00174 return QPointArray();
00175
00176 points.replace(',', ' ');
00177 points.replace('\r', QString::null);
00178 points.replace('\n', QString::null);
00179
00180 points = points.simplifyWhiteSpace();
00181
00182 QStringList pointList = QStringList::split(' ', points);
00183
00184 QPointArray array(pointList.count() / 2);
00185 int i = 0;
00186
00187 for(QStringList::Iterator it = pointList.begin(); it != pointList.end(); it++)
00188 {
00189 float x = (*(it++)).toFloat();
00190 float y = (*(it)).toFloat();
00191
00192 array.setPoint(i, static_cast<int>(x), static_cast<int>(y));
00193 i++;
00194 }
00195
00196 return array;
00197 }
00198
00199 void parseTransform(const QString &transform)
00200 {
00201
00202 QWMatrix matrix = m_engine->painter()->parseTransform(transform);
00203
00204 QWMatrix *current = m_engine->painter()->worldMatrix();
00205 *current = matrix * *current;
00206 }
00207
00208 void parseCommonAttributes(QDomNode &node)
00209 {
00210
00211 m_engine->painter()->setFillColor("black");
00212 m_engine->painter()->setStrokeColor("none");
00213 m_engine->painter()->setStrokeDashArray("");
00214 m_engine->painter()->setStrokeWidth(1);
00215 m_engine->painter()->setJoinStyle("");
00216 m_engine->painter()->setCapStyle("");
00217
00218
00219
00220
00221 QPtrList<QDomNamedNodeMap> applyList;
00222 applyList.setAutoDelete(true);
00223
00224 QDomNode shape = node.parentNode();
00225 for(; !shape.isNull() ; shape = shape.parentNode())
00226 applyList.prepend(new QDomNamedNodeMap(shape.attributes()));
00227
00228
00229 for(QDomNamedNodeMap *map = applyList.first(); map != 0; map = applyList.next())
00230 {
00231 QDomNamedNodeMap attr = *map;
00232
00233 for(unsigned int i = 0; i < attr.count(); i++)
00234 {
00235 QString name, value;
00236
00237 name = attr.item(i).nodeName().lower();
00238 value = attr.item(i).nodeValue();
00239
00240 if(name == "transform")
00241 parseTransform(value);
00242 else if(name == "style")
00243 parseStyle(value);
00244 else
00245 parsePA(name, value);
00246 }
00247 }
00248
00249
00250 QDomNamedNodeMap attr = node.attributes();
00251
00252 for(unsigned int i = 0; i < attr.count(); i++)
00253 {
00254 QDomNode current = attr.item(i);
00255
00256 if(current.nodeName().lower() == "transform")
00257 parseTransform(current.nodeValue());
00258 else if(current.nodeName().lower() == "style")
00259 parseStyle(current.nodeValue());
00260 else
00261 parsePA(current.nodeName().lower(), current.nodeValue());
00262 }
00263 }
00264
00265 bool handleTags(QDomElement element, bool paint)
00266 {
00267 if(element.attribute("display") == "none")
00268 return false;
00269 if(element.tagName() == "linearGradient")
00270 {
00271 ArtGradientLinear *gradient = new ArtGradientLinear();
00272
00273 int offsets = -1;
00274 gradient->stops = parseGradientStops(element, offsets);
00275 gradient->n_stops = offsets + 1;
00276
00277 QString spread = element.attribute("spreadMethod");
00278 if(spread == "repeat")
00279 gradient->spread = ART_GRADIENT_REPEAT;
00280 else if(spread == "reflect")
00281 gradient->spread = ART_GRADIENT_REFLECT;
00282 else
00283 gradient->spread = ART_GRADIENT_PAD;
00284
00285 m_engine->painter()->addLinearGradient(element.attribute("id"), gradient);
00286 m_engine->painter()->addLinearGradientElement(gradient, element);
00287 return true;
00288 }
00289 else if(element.tagName() == "radialGradient")
00290 {
00291 ArtGradientRadial *gradient = new ArtGradientRadial();
00292
00293 int offsets = -1;
00294 gradient->stops = parseGradientStops(element, offsets);
00295 gradient->n_stops = offsets + 1;
00296
00297 m_engine->painter()->addRadialGradient(element.attribute("id"), gradient);
00298 m_engine->painter()->addRadialGradientElement(gradient, element);
00299 return true;
00300 }
00301
00302 if(!paint)
00303 return true;
00304
00305
00306 if(element.tagName() == "rect")
00307 {
00308 double x = toPixel(element.attribute("x"), true);
00309 double y = toPixel(element.attribute("y"), false);
00310 double w = toPixel(element.attribute("width"), true);
00311 double h = toPixel(element.attribute("height"), false);
00312
00313 double rx = 0.0;
00314 double ry = 0.0;
00315
00316 if(element.hasAttribute("rx"))
00317 rx = toPixel(element.attribute("rx"), true);
00318
00319 if(element.hasAttribute("ry"))
00320 ry = toPixel(element.attribute("ry"), false);
00321
00322 m_engine->painter()->drawRectangle(x, y, w, h, rx, ry);
00323 }
00324 else if(element.tagName() == "switch")
00325 {
00326 QDomNode iterate = element.firstChild();
00327
00328 while(!iterate.isNull())
00329 {
00330
00331 m_engine->painter()->setWorldMatrix(new QWMatrix(m_initialMatrix));
00332
00333
00334 parseCommonAttributes(iterate);
00335
00336 if(handleTags(iterate.toElement(), true))
00337 return true;
00338 iterate = iterate.nextSibling();
00339 }
00340 return true;
00341 }
00342 else if(element.tagName() == "g" || element.tagName() == "defs")
00343 {
00344 QDomNode iterate = element.firstChild();
00345
00346 while(!iterate.isNull())
00347 {
00348
00349 m_engine->painter()->setWorldMatrix(new QWMatrix(m_initialMatrix));
00350
00351
00352 parseCommonAttributes(iterate);
00353
00354 handleTags(iterate.toElement(), (element.tagName() == "defs") ? false : true);
00355 iterate = iterate.nextSibling();
00356 }
00357 return true;
00358 }
00359 else if(element.tagName() == "line")
00360 {
00361 double x1 = toPixel(element.attribute("x1"), true);
00362 double y1 = toPixel(element.attribute("y1"), false);
00363 double x2 = toPixel(element.attribute("x2"), true);
00364 double y2 = toPixel(element.attribute("y2"), false);
00365
00366 m_engine->painter()->drawLine(x1, y1, x2, y2);
00367 return true;
00368 }
00369 else if(element.tagName() == "circle")
00370 {
00371 double cx = toPixel(element.attribute("cx"), true);
00372 double cy = toPixel(element.attribute("cy"), false);
00373
00374 double r = toPixel(element.attribute("r"), true);
00375
00376 m_engine->painter()->drawEllipse(cx, cy, r, r);
00377 return true;
00378 }
00379 else if(element.tagName() == "ellipse")
00380 {
00381 double cx = toPixel(element.attribute("cx"), true);
00382 double cy = toPixel(element.attribute("cy"), false);
00383
00384 double rx = toPixel(element.attribute("rx"), true);
00385 double ry = toPixel(element.attribute("ry"), false);
00386
00387 m_engine->painter()->drawEllipse(cx, cy, rx, ry);
00388 return true;
00389 }
00390 else if(element.tagName() == "polyline")
00391 {
00392 QPointArray polyline = parsePoints(element.attribute("points"));
00393 m_engine->painter()->drawPolyline(polyline);
00394 return true;
00395 }
00396 else if(element.tagName() == "polygon")
00397 {
00398 QPointArray polygon = parsePoints(element.attribute("points"));
00399 m_engine->painter()->drawPolygon(polygon);
00400 return true;
00401 }
00402 else if(element.tagName() == "path")
00403 {
00404 bool filled = true;
00405
00406 if(element.hasAttribute("fill") && element.attribute("fill").contains("none"))
00407 filled = false;
00408
00409 if(element.attribute("style").contains("fill") && element.attribute("style").stripWhiteSpace().contains("fill:none"))
00410 filled = false;
00411
00412 m_engine->painter()->drawPath(element.attribute("d"), filled);
00413 return true;
00414 }
00415 else if(element.tagName() == "image")
00416 {
00417 double x = toPixel(element.attribute("x"), true);
00418 double y = toPixel(element.attribute("y"), false);
00419 double w = toPixel(element.attribute("width"), true);
00420 double h = toPixel(element.attribute("height"), false);
00421
00422 QString href = element.attribute("xlink:href");
00423
00424 QImage image;
00425 if(href.startsWith("data:"))
00426 {
00427
00428 QCString input = href.remove(QRegExp("^data:image/.*;base64,")).utf8();
00429
00430
00431 QByteArray output;
00432 KCodecs::base64Decode(input, output);
00433
00434
00435 image.loadFromData(output);
00436 }
00437 else
00438 image.load(href);
00439
00440 if (!image.isNull())
00441 {
00442
00443 if(image.width() != (int) w || image.height() != (int) h)
00444 image = image.smoothScale((int) w, (int) h, QImage::ScaleFree);
00445
00446 m_engine->painter()->drawImage(x, y, image);
00447 }
00448
00449 return true;
00450 }
00451 return false;
00452 }
00453
00454 void parseStyle(const QString &style)
00455 {
00456 QStringList substyles = QStringList::split(';', style);
00457 for(QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it)
00458 {
00459 QStringList substyle = QStringList::split(':', (*it));
00460 QString command = substyle[0];
00461 QString params = substyle[1];
00462 command = command.stripWhiteSpace();
00463 params = params.stripWhiteSpace();
00464
00465 parsePA(command, params);
00466 }
00467 }
00468
00469 void parsePA(const QString &command, const QString &value)
00470 {
00471 if(command == "stroke-width")
00472 m_engine->painter()->setStrokeWidth(toPixel(value, false));
00473 else if(command == "stroke-miterlimit")
00474 m_engine->painter()->setStrokeMiterLimit(value);
00475 else if(command == "stroke-linecap")
00476 m_engine->painter()->setCapStyle(value);
00477 else if(command == "stroke-linejoin")
00478 m_engine->painter()->setJoinStyle(value);
00479 else if(command == "stroke-dashoffset")
00480 m_engine->painter()->setStrokeDashOffset(value);
00481 else if(command == "stroke-dasharray" && value != "none")
00482 m_engine->painter()->setStrokeDashArray(value);
00483 else if(command == "stroke")
00484 m_engine->painter()->setStrokeColor(value);
00485 else if(command == "fill")
00486 m_engine->painter()->setFillColor(value);
00487 else if(command == "fill-rule")
00488 m_engine->painter()->setFillRule(value);
00489 else if(command == "fill-opacity" || command == "stroke-opacity" || command == "opacity")
00490 {
00491 if(command == "fill-opacity")
00492 m_engine->painter()->setFillOpacity(value);
00493 else if(command == "stroke-value")
00494 m_engine->painter()->setStrokeOpacity(value);
00495 else
00496 {
00497 m_engine->painter()->setOpacity(value);
00498 m_engine->painter()->setFillOpacity(value);
00499 m_engine->painter()->setStrokeOpacity(value);
00500 }
00501 }
00502 }
00503
00504 private:
00505 friend class KSVGIconEngine;
00506
00507 KSVGIconEngine *m_engine;
00508 QWMatrix m_initialMatrix;
00509 };
00510
00511 struct KSVGIconEngine::Private
00512 {
00513 KSVGIconPainter *painter;
00514 KSVGIconEngineHelper *helper;
00515
00516 double width;
00517 double height;
00518 };
00519
00520 KSVGIconEngine::KSVGIconEngine() : d(new Private())
00521 {
00522 d->painter = 0;
00523 d->helper = new KSVGIconEngineHelper(this);
00524
00525 d->width = 0.0;
00526 d->height = 0.0;
00527 }
00528
00529 KSVGIconEngine::~KSVGIconEngine()
00530 {
00531 if(d->painter)
00532 delete d->painter;
00533
00534 delete d->helper;
00535
00536 delete d;
00537 }
00538
00539 bool KSVGIconEngine::load(int width, int height, const QString &path)
00540 {
00541 QDomDocument svgDocument("svg");
00542 QFile file(path);
00543
00544 if(path.right(3).upper() == "SVG")
00545 {
00546
00547 if(!file.open(IO_ReadOnly))
00548 return false;
00549
00550 svgDocument.setContent(&file);
00551 }
00552 else
00553 {
00554 gzFile svgz = gzopen(path.latin1(), "ro");
00555 if(!svgz)
00556 return false;
00557
00558 QString data;
00559 bool done = false;
00560
00561 QCString buffer(1024);
00562 int length = 0;
00563
00564 while(!done)
00565 {
00566 int ret = gzread(svgz, buffer.data() + length, 1024);
00567 if(ret == 0)
00568 done = true;
00569 else if(ret == -1)
00570 return false;
00571 else {
00572 buffer.resize(buffer.size()+1024);
00573 length += ret;
00574 }
00575 }
00576
00577 gzclose(svgz);
00578
00579 svgDocument.setContent(buffer);
00580 }
00581
00582 if(svgDocument.isNull())
00583 return false;
00584
00585
00586 QDomNode rootNode = svgDocument.namedItem("svg");
00587 if(rootNode.isNull() || !rootNode.isElement())
00588 return false;
00589
00590
00591 QDomElement rootElement = rootNode.toElement();
00592
00593
00594 d->painter = new KSVGIconPainter(width, height);
00595
00596 d->width = width;
00597 if(rootElement.hasAttribute("width"))
00598 d->width = d->helper->toPixel(rootElement.attribute("width"), true);
00599
00600 d->height = height;
00601 if(rootElement.hasAttribute("height"))
00602 d->height = d->helper->toPixel(rootElement.attribute("height"), false);
00603
00604
00605 d->painter->setDrawWidth(static_cast<int>(d->width));
00606 d->painter->setDrawHeight(static_cast<int>(d->height));
00607
00608
00609 d->painter->setClippingRect(0, 0, width, height);
00610
00611
00612 if(rootElement.hasAttribute("viewBox"))
00613 {
00614 QStringList points = QStringList::split(' ', rootElement.attribute("viewBox").simplifyWhiteSpace());
00615
00616 float w = points[2].toFloat();
00617 float h = points[3].toFloat();
00618
00619 double vratiow = width / w;
00620 double vratioh = height / h;
00621
00622 d->width = w;
00623 d->height = h;
00624
00625 d->painter->worldMatrix()->scale(vratiow, vratioh);
00626 }
00627 else
00628 {
00629
00630
00631 double ratiow = width / d->width;
00632 double ratioh = height / d->height;
00633
00634 d->painter->worldMatrix()->scale(ratiow, ratioh);
00635 }
00636
00637 QWMatrix initialMatrix = *d->painter->worldMatrix();
00638 d->helper->m_initialMatrix = initialMatrix;
00639
00640
00641 if(rootElement.hasAttribute("transform"))
00642 d->helper->parseTransform(rootElement.attribute("transform"));
00643
00644
00645 QDomNode svgNode = rootElement.firstChild();
00646 while(!svgNode.isNull())
00647 {
00648 QDomElement svgChild = svgNode.toElement();
00649 if(!svgChild.isNull())
00650 {
00651 d->helper->parseCommonAttributes(svgNode);
00652 d->helper->handleTags(svgChild, true);
00653 }
00654
00655 svgNode = svgNode.nextSibling();
00656
00657
00658 d->painter->setWorldMatrix(new QWMatrix(initialMatrix));
00659 }
00660
00661 d->painter->finish();
00662
00663 return true;
00664 }
00665
00666 KSVGIconPainter *KSVGIconEngine::painter()
00667 {
00668 return d->painter;
00669 }
00670
00671 QImage *KSVGIconEngine::image()
00672 {
00673 return d->painter->image();
00674 }
00675
00676 double KSVGIconEngine::width()
00677 {
00678 return d->width;
00679 }
00680
00681 double KSVGIconEngine::height()
00682 {
00683 return d->height;
00684 }
00685
00686