kdecore Library API Documentation

ksvgiconengine.cpp

00001 /* 00002 Copyright (C) 2002 Nikolas Zimmermann <wildfox@kde.org> 00003 This file is part of the KDE project 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00018 Boston, MA 02111-1307, USA. 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 // Spec skip double offset specifications 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 // Parse color using KSVGIconPainter (which uses Qt) 00120 // Supports all svg-needed color formats 00121 QColor qStopColor = m_engine->painter()->parseColor(parseColor); 00122 00123 // Convert in a libart suitable form 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 // Convert from separated to premultiplied alpha 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 // Combine new and old matrix 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 // Set important default attributes 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 // m_engine->painter()->setFillOpacity(255, true); 00201 // m_engine->painter()->setStrokeOpacity(255, true); 00202 00203 // Collect parent node's attributes 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 // Apply parent attributes 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 // Apply local attributes 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 // TODO: Default attribute values 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 // Reset matrix 00314 m_engine->painter()->setWorldMatrix(new QWMatrix(m_initialMatrix)); 00315 00316 // Parse common attributes, style / transform 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 // Reset matrix 00332 m_engine->painter()->setWorldMatrix(new QWMatrix(m_initialMatrix)); 00333 00334 // Parse common attributes, style / transform 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); // TODO: horiz correct? 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 // Get input 00410 QCString input = href.mid(13).utf8(); 00411 00412 // Decode into 'output' 00413 QByteArray output; 00414 KCodecs::base64Decode(input, output); 00415 00416 // Display 00417 QImage *image = new QImage(output); 00418 00419 // Scale, if needed 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") // TODO: horiz:false correct? 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 // Open SVG Icon 00526 if(!file.open(IO_ReadOnly)) 00527 return false; 00528 00529 svgDocument.setContent(&file); 00530 } 00531 else // SVGZ 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 // Check for root element 00565 QDomNode rootNode = svgDocument.namedItem("svg"); 00566 if(rootNode.isNull() || !rootNode.isElement()) 00567 return false; 00568 00569 // Detect width and height 00570 QDomElement rootElement = rootNode.toElement(); 00571 00572 // Create icon painter 00573 d->painter = new KSVGIconPainter(width, height); 00574 00575 d->width = width; // this sets default for no width -> 100% case 00576 if(rootElement.hasAttribute("width")) 00577 d->width = d->helper->toPixel(rootElement.attribute("width"), true); 00578 00579 d->height = height; // this sets default for no height -> 100% case 00580 if(rootElement.hasAttribute("height")) 00581 d->height = d->helper->toPixel(rootElement.attribute("height"), false); 00582 00583 // Create icon painter 00584 d->painter->setDrawWidth(static_cast<int>(d->width)); 00585 d->painter->setDrawHeight(static_cast<int>(d->height)); 00586 00587 // Set viewport clipping rect 00588 d->painter->setClippingRect(0, 0, width, height); 00589 00590 // Apply viewbox 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 // Fit into 'width' and 'height' 00609 // FIXME: Use an aspect ratio 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 // Apply transform 00620 if(rootElement.hasAttribute("transform")) 00621 d->helper->parseTransform(rootElement.attribute("transform")); 00622 00623 // Go through all elements 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 // Reset matrix 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 // vim:ts=4:noet
KDE Logo
This file is part of the documentation for kdecore Library Version 3.3.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Sep 29 09:43:11 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003