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