kdecore Library API Documentation

ksvgiconengine.cpp

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