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         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
KDE Home | KDE Accessibility Home | Description of Access Keys