• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

KDECore

ksvgiconengine.cpp

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

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal