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.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 12:46:57 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001