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