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