• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdecore
 

tdecore

  • tdecore
  • svgicons
ksvgiconengine.cpp
1 /*
2  Copyright (C) 2002 Nikolas Zimmermann <wildfox@kde.org>
3  This file is part of the KDE project
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include <tqdom.h>
22 #include <tqfile.h>
23 #include <tqcolor.h>
24 #include <tqimage.h>
25 #include <tqwmatrix.h>
26 #include <tqregexp.h>
27 
28 #include <kmdcodec.h>
29 
30 #include <zlib.h>
31 
32 #include "ksvgiconpainter.h"
33 #include "ksvgiconengine.h"
34 
35 class KSVGIconEngineHelper
36 {
37 public:
38  KSVGIconEngineHelper(KSVGIconEngine *engine)
39  {
40  m_engine = engine;
41  }
42 
43  ~KSVGIconEngineHelper()
44  {
45  }
46 
47  double toPixel(const TQString &s, bool hmode)
48  {
49  return m_engine->painter()->toPixel(s, hmode);
50  }
51 
52  ArtGradientStop *parseGradientStops(TQDomElement element, int &offsets)
53  {
54  if (!element.hasChildNodes())
55  return 0;
56 
57  TQValueList<ArtGradientStop> stopList;
58 
59  float oldOffset = -1, newOffset = -1;
60  for(TQDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling())
61  {
62  TQDomElement element = node.toElement();
63 
64  oldOffset = newOffset;
65  TQString temp = element.attribute("offset");
66 
67  if(temp.contains("%"))
68  {
69  temp = temp.left(temp.length() - 1);
70  newOffset = temp.toFloat() / 100.0;
71  }
72  else
73  newOffset = temp.toFloat();
74 
75  // Spec skip double offset specifications
76  if(oldOffset == newOffset)
77  continue;
78 
79  offsets++;
80  stopList.append(ArtGradientStop());
81 
82  ArtGradientStop &stop = stopList.last();
83 
84  stop.offset = newOffset;
85 
86  TQString parseOpacity;
87  TQString parseColor;
88 
89  if(element.hasAttribute("stop-opacity"))
90  parseOpacity = element.attribute("stop-opacity");
91 
92  if(element.hasAttribute("stop-color"))
93  parseColor = element.attribute("stop-color");
94 
95  if(parseOpacity.isEmpty() || parseColor.isEmpty())
96  {
97  TQString style = element.attribute("style");
98 
99  TQStringList substyles = TQStringList::split(';', style);
100  for(TQStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it)
101  {
102  TQStringList substyle = TQStringList::split(':', (*it));
103  TQString command = substyle[0];
104  TQString params = substyle[1];
105  command = command.stripWhiteSpace();
106  params = params.stripWhiteSpace();
107 
108  if(command == "stop-color")
109  {
110  parseColor = params;
111 
112  if(!parseOpacity.isEmpty())
113  break;
114  }
115  else if(command == "stop-opacity")
116  {
117  parseOpacity = params;
118 
119  if(!parseColor.isEmpty())
120  break;
121  }
122  }
123  }
124 
125  // Parse color using KSVGIconPainter (which uses Qt)
126  // Supports all svg-needed color formats
127  TQColor qStopColor = m_engine->painter()->parseColor(parseColor);
128 
129  // Convert in a libart suitable form
130  TQ_UINT32 stopColor = m_engine->painter()->toArtColor(qStopColor);
131 
132  int opacity = m_engine->painter()->parseOpacity(parseOpacity);
133 
134  TQ_UINT32 rgba = (stopColor << 8) | opacity;
135  TQ_UINT32 r, g, b, a;
136 
137  // Convert from separated to premultiplied alpha
138  a = rgba & 0xff;
139  r = (rgba >> 24) * a + 0x80;
140  r = (r + (r >> 8)) >> 8;
141  g = ((rgba >> 16) & 0xff) * a + 0x80;
142  g = (g + (g >> 8)) >> 8;
143  b = ((rgba >> 8) & 0xff) * a + 0x80;
144  b = (b + (b >> 8)) >> 8;
145 
146  stop.color[0] = ART_PIX_MAX_FROM_8(r);
147  stop.color[1] = ART_PIX_MAX_FROM_8(g);
148  stop.color[2] = ART_PIX_MAX_FROM_8(b);
149  stop.color[3] = ART_PIX_MAX_FROM_8(a);
150  }
151 
152  if (stopList.isEmpty())
153  return 0;
154 
155  ArtGradientStop *stops = new ArtGradientStop[stopList.count()];
156 
157  TQValueList<ArtGradientStop>::iterator it = stopList.begin();
158  TQValueList<ArtGradientStop>::iterator end = stopList.end();
159 
160  for (int i = 0; it != end; ++i, ++it)
161  stops[i] = *it;
162 
163  return stops;
164  }
165 
166  TQPointArray parsePoints(TQString points)
167  {
168  if(points.isEmpty())
169  return TQPointArray();
170 
171  points = points.simplifyWhiteSpace();
172 
173  if(points.contains(",,") || points.contains(", ,"))
174  return TQPointArray();
175 
176  points.replace(',', ' ');
177  points.replace('\r', TQString());
178  points.replace('\n', TQString());
179 
180  points = points.simplifyWhiteSpace();
181 
182  TQStringList pointList = TQStringList::split(' ', points);
183 
184  TQPointArray array(pointList.count() / 2);
185  int i = 0;
186 
187  for(TQStringList::Iterator it = pointList.begin(); it != pointList.end(); it++)
188  {
189  float x = (*(it++)).toFloat();
190  float y = (*(it)).toFloat();
191 
192  array.setPoint(i, static_cast<int>(x), static_cast<int>(y));
193  i++;
194  }
195 
196  return array;
197  }
198 
199  void parseTransform(const TQString &transform)
200  {
201  // Combine new and old matrix
202  TQWMatrix matrix = m_engine->painter()->parseTransform(transform);
203 
204  TQWMatrix *current = m_engine->painter()->worldMatrix();
205  *current = matrix * *current;
206  }
207 
208  void parseCommonAttributes(TQDomNode &node)
209  {
210  // Set important default attributes
211  m_engine->painter()->setFillColor("black");
212  m_engine->painter()->setStrokeColor("none");
213  m_engine->painter()->setStrokeDashArray("");
214  m_engine->painter()->setStrokeWidth(1);
215  m_engine->painter()->setJoinStyle("");
216  m_engine->painter()->setCapStyle("");
217  // m_engine->painter()->setFillOpacity(255, true);
218  // m_engine->painter()->setStrokeOpacity(255, true);
219 
220  // Collect parent node's attributes
221  TQPtrList<TQDomNamedNodeMap> applyList;
222  applyList.setAutoDelete(true);
223 
224  TQDomNode shape = node.parentNode();
225  for(; !shape.isNull() ; shape = shape.parentNode())
226  applyList.prepend(new TQDomNamedNodeMap(shape.attributes()));
227 
228  // Apply parent attributes
229  for(TQDomNamedNodeMap *map = applyList.first(); map != 0; map = applyList.next())
230  {
231  TQDomNamedNodeMap attr = *map;
232 
233  for(unsigned int i = 0; i < attr.count(); i++)
234  {
235  TQString name, value;
236 
237  name = attr.item(i).nodeName().lower();
238  value = attr.item(i).nodeValue();
239 
240  if(name == "transform")
241  parseTransform(value);
242  else if(name == "style")
243  parseStyle(value);
244  else
245  parsePA(name, value);
246  }
247  }
248 
249  // Apply local attributes
250  TQDomNamedNodeMap attr = node.attributes();
251 
252  for(unsigned int i = 0; i < attr.count(); i++)
253  {
254  TQDomNode current = attr.item(i);
255 
256  if(current.nodeName().lower() == "transform")
257  parseTransform(current.nodeValue());
258  else if(current.nodeName().lower() == "style")
259  parseStyle(current.nodeValue());
260  else
261  parsePA(current.nodeName().lower(), current.nodeValue());
262  }
263  }
264 
265  bool handleTags(TQDomElement element, bool paint)
266  {
267  if(element.attribute("display") == "none")
268  return false;
269  if(element.tagName() == "linearGradient")
270  {
271  ArtGradientLinear *gradient = new ArtGradientLinear();
272 
273  int offsets = -1;
274  gradient->stops = parseGradientStops(element, offsets);
275  gradient->n_stops = offsets + 1;
276 
277  TQString spread = element.attribute("spreadMethod");
278  if(spread == "repeat")
279  gradient->spread = ART_GRADIENT_REPEAT;
280  else if(spread == "reflect")
281  gradient->spread = ART_GRADIENT_REFLECT;
282  else
283  gradient->spread = ART_GRADIENT_PAD;
284 
285  m_engine->painter()->addLinearGradient(element.attribute("id"), gradient);
286  m_engine->painter()->addLinearGradientElement(gradient, element);
287  return true;
288  }
289  else if(element.tagName() == "radialGradient")
290  {
291  ArtGradientRadial *gradient = new ArtGradientRadial();
292 
293  int offsets = -1;
294  gradient->stops = parseGradientStops(element, offsets);
295  gradient->n_stops = offsets + 1;
296 
297  m_engine->painter()->addRadialGradient(element.attribute("id"), gradient);
298  m_engine->painter()->addRadialGradientElement(gradient, element);
299  return true;
300  }
301 
302  if(!paint)
303  return true;
304 
305  // TODO: Default attribute values
306  if(element.tagName() == "rect")
307  {
308  double x = toPixel(element.attribute("x"), true);
309  double y = toPixel(element.attribute("y"), false);
310  double w = toPixel(element.attribute("width"), true);
311  double h = toPixel(element.attribute("height"), false);
312 
313  double rx = 0.0;
314  double ry = 0.0;
315 
316  if(element.hasAttribute("rx"))
317  rx = toPixel(element.attribute("rx"), true);
318 
319  if(element.hasAttribute("ry"))
320  ry = toPixel(element.attribute("ry"), false);
321 
322  m_engine->painter()->drawRectangle(x, y, w, h, rx, ry);
323  }
324  else if(element.tagName() == "switch")
325  {
326  TQDomNode iterate = element.firstChild();
327 
328  while(!iterate.isNull())
329  {
330  // Reset matrix
331  m_engine->painter()->setWorldMatrix(new TQWMatrix(m_initialMatrix));
332 
333  // Parse common attributes, style / transform
334  parseCommonAttributes(iterate);
335 
336  if(handleTags(iterate.toElement(), true))
337  return true;
338  iterate = iterate.nextSibling();
339  }
340  return true;
341  }
342  else if(element.tagName() == "g" || element.tagName() == "defs")
343  {
344  TQDomNode iterate = element.firstChild();
345 
346  while(!iterate.isNull())
347  {
348  // Reset matrix
349  m_engine->painter()->setWorldMatrix(new TQWMatrix(m_initialMatrix));
350 
351  // Parse common attributes, style / transform
352  parseCommonAttributes(iterate);
353 
354  handleTags(iterate.toElement(), (element.tagName() == "defs") ? false : true);
355  iterate = iterate.nextSibling();
356  }
357  return true;
358  }
359  else if(element.tagName() == "line")
360  {
361  double x1 = toPixel(element.attribute("x1"), true);
362  double y1 = toPixel(element.attribute("y1"), false);
363  double x2 = toPixel(element.attribute("x2"), true);
364  double y2 = toPixel(element.attribute("y2"), false);
365 
366  m_engine->painter()->drawLine(x1, y1, x2, y2);
367  return true;
368  }
369  else if(element.tagName() == "circle")
370  {
371  double cx = toPixel(element.attribute("cx"), true);
372  double cy = toPixel(element.attribute("cy"), false);
373 
374  double r = toPixel(element.attribute("r"), true); // TODO: horiz correct?
375 
376  m_engine->painter()->drawEllipse(cx, cy, r, r);
377  return true;
378  }
379  else if(element.tagName() == "ellipse")
380  {
381  double cx = toPixel(element.attribute("cx"), true);
382  double cy = toPixel(element.attribute("cy"), false);
383 
384  double rx = toPixel(element.attribute("rx"), true);
385  double ry = toPixel(element.attribute("ry"), false);
386 
387  m_engine->painter()->drawEllipse(cx, cy, rx, ry);
388  return true;
389  }
390  else if(element.tagName() == "polyline")
391  {
392  TQPointArray polyline = parsePoints(element.attribute("points"));
393  m_engine->painter()->drawPolyline(polyline);
394  return true;
395  }
396  else if(element.tagName() == "polygon")
397  {
398  TQPointArray polygon = parsePoints(element.attribute("points"));
399  m_engine->painter()->drawPolygon(polygon);
400  return true;
401  }
402  else if(element.tagName() == "path")
403  {
404  bool filled = true;
405 
406  if(element.hasAttribute("fill") && element.attribute("fill").contains("none"))
407  filled = false;
408 
409  if(element.attribute("style").contains("fill") && element.attribute("style").stripWhiteSpace().contains("fill:none"))
410  filled = false;
411 
412  m_engine->painter()->drawPath(element.attribute("d"), filled);
413  return true;
414  }
415  else if(element.tagName() == "image")
416  {
417  double x = toPixel(element.attribute("x"), true);
418  double y = toPixel(element.attribute("y"), false);
419  double w = toPixel(element.attribute("width"), true);
420  double h = toPixel(element.attribute("height"), false);
421 
422  TQString href = element.attribute("xlink:href");
423 
424  TQImage image;
425  if(href.startsWith("data:"))
426  {
427  // Get input
428  TQCString input = TQString(href.remove(TQRegExp("^data:image/.*;base64,"))).utf8();
429 
430  // Decode into 'output'
431  TQByteArray output;
432  KCodecs::base64Decode(input, output);
433 
434  // Display
435  image.loadFromData(output);
436  }
437  else
438  image.load(href);
439 
440  if (!image.isNull())
441  {
442  // Scale, if needed
443  if(image.width() != (int) w || image.height() != (int) h)
444  image = image.smoothScale((int) w, (int) h, TQImage::ScaleFree);
445 
446  m_engine->painter()->drawImage(x, y, image);
447  }
448 
449  return true;
450  }
451  return false;
452  }
453 
454  void parseStyle(const TQString &style)
455  {
456  TQStringList substyles = TQStringList::split(';', style);
457  for(TQStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it)
458  {
459  TQStringList substyle = TQStringList::split(':', (*it));
460  TQString command = substyle[0];
461  TQString params = substyle[1];
462  command = command.stripWhiteSpace();
463  params = params.stripWhiteSpace();
464 
465  parsePA(command, params);
466  }
467  }
468 
469  void parsePA(const TQString &command, const TQString &value)
470  {
471  if(command == "stroke-width") // TODO: horiz:false correct?
472  m_engine->painter()->setStrokeWidth(toPixel(value, false));
473  else if(command == "stroke-miterlimit")
474  m_engine->painter()->setStrokeMiterLimit(value);
475  else if(command == "stroke-linecap")
476  m_engine->painter()->setCapStyle(value);
477  else if(command == "stroke-linejoin")
478  m_engine->painter()->setJoinStyle(value);
479  else if(command == "stroke-dashoffset")
480  m_engine->painter()->setStrokeDashOffset(value);
481  else if(command == "stroke-dasharray" && value != "none")
482  m_engine->painter()->setStrokeDashArray(value);
483  else if(command == "stroke")
484  m_engine->painter()->setStrokeColor(value);
485  else if(command == "fill")
486  m_engine->painter()->setFillColor(value);
487  else if(command == "fill-rule")
488  m_engine->painter()->setFillRule(value);
489  else if(command == "fill-opacity" || command == "stroke-opacity" || command == "opacity")
490  {
491  if(command == "fill-opacity")
492  m_engine->painter()->setFillOpacity(value);
493  else if(command == "stroke-value")
494  m_engine->painter()->setStrokeOpacity(value);
495  else
496  {
497  m_engine->painter()->setOpacity(value);
498  m_engine->painter()->setFillOpacity(value);
499  m_engine->painter()->setStrokeOpacity(value);
500  }
501  }
502  }
503 
504 private:
505  friend class KSVGIconEngine;
506 
507  KSVGIconEngine *m_engine;
508  TQWMatrix m_initialMatrix;
509 };
510 
511 struct KSVGIconEngine::Private
512 {
513  KSVGIconPainter *painter;
514  KSVGIconEngineHelper *helper;
515 
516  double width;
517  double height;
518 };
519 
520 KSVGIconEngine::KSVGIconEngine() : d(new Private())
521 {
522  d->painter = 0;
523  d->helper = new KSVGIconEngineHelper(this);
524 
525  d->width = 0.0;
526  d->height = 0.0;
527 }
528 
529 KSVGIconEngine::~KSVGIconEngine()
530 {
531  if(d->painter)
532  delete d->painter;
533 
534  delete d->helper;
535 
536  delete d;
537 }
538 
539 bool KSVGIconEngine::load(int width, int height, const TQString &path)
540 {
541  if(path.isNull()) return false;
542 
543  TQDomDocument svgDocument("svg");
544  TQFile file(path);
545 
546  if(path.right(3).upper() == "SVG")
547  {
548  // Open SVG Icon
549  if(!file.open(IO_ReadOnly))
550  return false;
551 
552  svgDocument.setContent(&file);
553  }
554  else // SVGZ
555  {
556  gzFile svgz = gzopen(path.latin1(), "ro");
557  if(!svgz)
558  return false;
559 
560  TQString data;
561  bool done = false;
562 
563  TQCString buffer(1024);
564  int length = 0;
565 
566  while(!done)
567  {
568  int ret = gzread(svgz, buffer.data() + length, 1024);
569  if(ret == 0)
570  done = true;
571  else if(ret == -1)
572  return false;
573  else {
574  buffer.resize(buffer.size()+1024);
575  length += ret;
576  }
577  }
578 
579  gzclose(svgz);
580 
581  svgDocument.setContent(buffer);
582  }
583 
584  if(svgDocument.isNull())
585  return false;
586 
587  // Check for root element
588  TQDomNode rootNode = svgDocument.namedItem("svg");
589  if(rootNode.isNull() || !rootNode.isElement())
590  return false;
591 
592  // Detect width and height
593  TQDomElement rootElement = rootNode.toElement();
594 
595  // Create icon painter
596  d->painter = new KSVGIconPainter(width, height);
597 
598  d->width = width; // this sets default for no width -> 100% case
599  if(rootElement.hasAttribute("width"))
600  d->width = d->helper->toPixel(rootElement.attribute("width"), true);
601 
602  d->height = height; // this sets default for no height -> 100% case
603  if(rootElement.hasAttribute("height"))
604  d->height = d->helper->toPixel(rootElement.attribute("height"), false);
605 
606  // Create icon painter
607  d->painter->setDrawWidth(static_cast<int>(d->width));
608  d->painter->setDrawHeight(static_cast<int>(d->height));
609 
610  // Set viewport clipping rect
611  d->painter->setClippingRect(0, 0, width, height);
612 
613  // Apply viewbox
614  if(rootElement.hasAttribute("viewBox"))
615  {
616  TQStringList points = TQStringList::split(' ', rootElement.attribute("viewBox").simplifyWhiteSpace());
617 
618  float w = points[2].toFloat();
619  float h = points[3].toFloat();
620 
621  double vratiow = width / w;
622  double vratioh = height / h;
623 
624  d->width = w;
625  d->height = h;
626 
627  d->painter->worldMatrix()->scale(vratiow, vratioh);
628  }
629  else
630  {
631  // Fit into 'width' and 'height'
632  // FIXME: Use an aspect ratio
633  double ratiow = width / d->width;
634  double ratioh = height / d->height;
635 
636  d->painter->worldMatrix()->scale(ratiow, ratioh);
637  }
638 
639  TQWMatrix initialMatrix = *d->painter->worldMatrix();
640  d->helper->m_initialMatrix = initialMatrix;
641 
642  // Apply transform
643  if(rootElement.hasAttribute("transform"))
644  d->helper->parseTransform(rootElement.attribute("transform"));
645 
646  // Go through all elements
647  TQDomNode svgNode = rootElement.firstChild();
648  while(!svgNode.isNull())
649  {
650  TQDomElement svgChild = svgNode.toElement();
651  if(!svgChild.isNull())
652  {
653  d->helper->parseCommonAttributes(svgNode);
654  d->helper->handleTags(svgChild, true);
655  }
656 
657  svgNode = svgNode.nextSibling();
658 
659  // Reset matrix
660  d->painter->setWorldMatrix(new TQWMatrix(initialMatrix));
661  }
662 
663  d->painter->finish();
664 
665  return true;
666 }
667 
668 KSVGIconPainter *KSVGIconEngine::painter()
669 {
670  return d->painter;
671 }
672 
673 TQImage *KSVGIconEngine::image()
674 {
675  return d->painter->image();
676 }
677 
678 double KSVGIconEngine::width()
679 {
680  return d->width;
681 }
682 
683 double KSVGIconEngine::height()
684 {
685  return d->height;
686 }
KCodecs::base64Decode
static TQCString base64Decode(const TQByteArray &in)
Decodes the given data that was encoded using the base64 algorithm.
Definition: kmdcodec.cpp:462
KStdAction::name
const char * name(StdAction id)
TDEStdAccel::end
const TDEShortcut & end()
Goto end of the document.
Definition: tdestdaccel.cpp:289

tdecore

Skip menu "tdecore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

tdecore

Skip menu "tdecore"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdeioslave
  •   http
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdecore by doxygen 1.9.1
This website is maintained by Timothy Pearson.