• 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
35class KSVGIconEngineHelper
36{
37public:
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
504private:
505 friend class KSVGIconEngine;
506
507 KSVGIconEngine *m_engine;
508 TQWMatrix m_initialMatrix;
509};
510
511struct KSVGIconEngine::Private
512{
513 KSVGIconPainter *painter;
514 KSVGIconEngineHelper *helper;
515
516 double width;
517 double height;
518};
519
520KSVGIconEngine::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
529KSVGIconEngine::~KSVGIconEngine()
530{
531 if(d->painter)
532 delete d->painter;
533
534 delete d->helper;
535
536 delete d;
537}
538
539bool 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
668KSVGIconPainter *KSVGIconEngine::painter()
669{
670 return d->painter;
671}
672
673TQImage *KSVGIconEngine::image()
674{
675 return d->painter->image();
676}
677
678double KSVGIconEngine::width()
679{
680 return d->width;
681}
682
683double 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.4
This website is maintained by Timothy Pearson.