| # kdecore Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Namespaces | Source Files

# 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(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

This file is part of the documentation for kdecore Library Version 3.4.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat Jun 11 20:18:11 2005 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003