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