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