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