source: vendor/trolltech/current/src/xml/qsvgdevice.cpp

Last change on this file was 2, checked in by dmik, 20 years ago

Imported xplatform parts of the official release 3.3.1 from Trolltech

  • Property svn:keywords set to Id
File size: 45.2 KB
Line 
1/****************************************************************************
2** $Id: qsvgdevice.cpp 2 2005-11-16 15:49:26Z dmik $
3**
4** Implementation of the QSvgDevice class
5**
6** Created : 20001024
7**
8** Copyright (C) 2000-2002 Trolltech AS. All rights reserved.
9**
10** This file is part of the xml module of the Qt GUI Toolkit.
11**
12** This file may be distributed under the terms of the Q Public License
13** as defined by Trolltech AS of Norway and appearing in the file
14** LICENSE.QPL included in the packaging of this file.
15**
16** This file may be distributed and/or modified under the terms of the
17** GNU General Public License version 2 as published by the Free Software
18** Foundation and appearing in the file LICENSE.GPL included in the
19** packaging of this file.
20**
21** Licensees holding valid Qt Enterprise Edition licenses may use this
22** file in accordance with the Qt Commercial License Agreement provided
23** with the Software.
24**
25** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
26** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27**
28** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
29** information about Qt Commercial License Agreements.
30** See http://www.trolltech.com/qpl/ for QPL licensing information.
31** See http://www.trolltech.com/gpl/ for GPL licensing information.
32**
33** Contact info@trolltech.com if any conditions of this licensing are
34** not clear to you.
35**
36*****************************************************************************/
37
38#include <private/qsvgdevice_p.h>
39
40#ifndef QT_NO_SVG
41
42#include "qpainter.h"
43#include "qpaintdevicemetrics.h"
44#include "qfile.h"
45#include "qmap.h"
46#include "qregexp.h"
47#include "qvaluelist.h"
48#include "qtextstream.h"
49#include "qimage.h"
50#include "qpixmap.h"
51
52#include <math.h>
53
54const double deg2rad = 0.017453292519943295769; // pi/180
55const char piData[] = "version=\"1.0\" standalone=\"no\"";
56const char publicId[] = "-//W3C//DTD SVG 20001102//EN";
57const char systemId[] = "http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd";
58
59struct QM_EXPORT_SVG ImgElement {
60 QDomElement element;
61 QImage image;
62 Q_DUMMY_COMPARISON_OPERATOR( ImgElement )
63};
64
65struct QM_EXPORT_SVG PixElement {
66 QDomElement element;
67 QPixmap pixmap;
68 Q_DUMMY_COMPARISON_OPERATOR( PixElement )
69};
70
71struct QSvgDeviceState {
72 int textx, texty; // current text position
73 int textalign; // text alignment
74 Q_DUMMY_COMPARISON_OPERATOR( QSvgDeviceState )
75};
76
77typedef QValueList<ImgElement> ImageList;
78typedef QValueList<PixElement> PixmapList;
79typedef QValueList<QSvgDeviceState> StateList;
80
81class QSvgDevicePrivate {
82public:
83 ImageList images;
84 PixmapList pixmaps;
85 StateList stack;
86 int currentClip;
87};
88
89enum ElementType {
90 InvalidElement = 0,
91 AnchorElement,
92 CircleElement,
93 ClipElement,
94 CommentElement,
95 DescElement,
96 EllipseElement,
97 GroupElement,
98 ImageElement,
99 LineElement,
100 PolylineElement,
101 PolygonElement,
102 PathElement,
103 RectElement,
104 SvgElement,
105 TextElement,
106 TitleElement,
107 TSpanElement
108};
109
110typedef QMap<QString,ElementType> QSvgTypeMap;
111static QSvgTypeMap *qSvgTypeMap=0; // element types
112static QMap<QString,QString> *qSvgColMap=0; // recognized color keyword names
113
114/*!
115 \class QSvgDevice qsvgdevice.h
116 \brief The QSvgDevice class provides a paint device for SVG vector graphics.
117\if defined(commercial)
118 It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>.
119\endif
120
121 \ingroup xml-tools
122 \module XML
123 \internal
124
125 SVG is an XML vector graphics format. This class supports the
126 loading and saving of SVG files with load() and save(), and the
127 rendering of an SVG onto a QPainter using play(). Use toString()
128 to put the SVG into a string.
129
130 \sa QPaintDevice QPainter
131*/
132
133/*!
134 Creates a QSvgDevice object.
135*/
136
137QSvgDevice::QSvgDevice()
138 : QPaintDevice( QInternal::ExternalDevice ),
139 pt( 0 )
140{
141 d = new QSvgDevicePrivate;
142 d->currentClip = 0;
143}
144
145/*!
146 Destroys the QSvgDevice object and frees the resources it used.
147*/
148
149QSvgDevice::~QSvgDevice()
150{
151 delete qSvgTypeMap; qSvgTypeMap = 0; // static
152 delete qSvgColMap; qSvgColMap = 0;
153 delete d;
154}
155
156/*!
157 Loads and parses a SVG from \a dev into the device. Returns TRUE
158 on success (i.e. loaded and parsed without error); otherwise
159 returns FALSE.
160*/
161
162bool QSvgDevice::load( QIODevice *dev )
163{
164 return doc.setContent( dev );
165}
166
167/*!
168 Renders (replays) the SVG on the \a painter and returns TRUE if
169 successful (i.e. it is a valid SVG); otherwise returns FALSE.
170*/
171
172bool QSvgDevice::play( QPainter *painter )
173{
174 if ( !painter ) {
175#if defined(QT_CHECK_RANGE)
176 Q_ASSERT( painter );
177#endif
178 return FALSE;
179 }
180 pt = painter;
181 pt->setPen( Qt::NoPen ); // SVG default pen and brush
182 pt->setBrush( Qt::black );
183 if ( doc.isNull() ) {
184 qWarning( "QSvgDevice::play: No SVG data set." );
185 return FALSE;
186 }
187
188 QDomNode svg = doc.namedItem( "svg" );
189 if ( svg.isNull() || !svg.isElement() ) {
190 qWarning( "QSvgDevice::play: Couldn't find any svg element." );
191 return FALSE;
192 }
193
194 // force transform to be activated in case our sequences
195 // are replayed later with a transformed painter
196 painter->setWorldXForm( TRUE );
197
198 QDomNamedNodeMap attr = svg.attributes();
199 int x = lenToInt( attr, "x" );
200 int y = lenToInt( attr, "y" );
201 brect.setX( x );
202 brect.setY( y );
203 QString wstr = attr.contains( "width" )
204 ? attr.namedItem( "width" ).nodeValue() : QString( "100%" );
205 QString hstr = attr.contains( "height" )
206 ? attr.namedItem( "height" ).nodeValue() : QString( "100%" );
207 double width = parseLen( wstr, 0, TRUE );
208 double height = parseLen( hstr, 0, FALSE );
209 // SVG doesn't respect x and y. But we want a proper bounding rect.
210 brect.setWidth( int(width) - x );
211 brect.setHeight( int(height) - y );
212 painter->setClipRect( brect, QPainter::CoordPainter );
213
214 if ( attr.contains( "viewBox" ) ) {
215 QRegExp re( QString::fromLatin1("\\s*(\\S+)\\s*,?\\s*(\\S+)\\s*,?"
216 "\\s*(\\S+)\\s*,?\\s*(\\S+)\\s*") );
217 if ( re.search( attr.namedItem( "viewBox" ).nodeValue() ) < 0 ) {
218 qWarning( "QSvgDevice::play: Invalid viewBox attribute.");
219 return FALSE;
220 } else {
221 double x = re.cap( 1 ).toDouble();
222 double y = re.cap( 2 ).toDouble();
223 double w = re.cap( 3 ).toDouble();
224 double h = re.cap( 4 ).toDouble();
225 if ( w < 0 || h < 0 ) {
226 qWarning( "QSvgDevice::play: Invalid viewBox dimension.");
227 return FALSE;
228 } else if ( w == 0 || h == 0 ) {
229 return TRUE;
230 }
231 painter->translate( -x, -y );
232 painter->scale( width/w, height/h );
233 }
234 }
235
236 const struct ElementTable {
237 const char *name;
238 ElementType type;
239 } etab[] = {
240 { "a", AnchorElement },
241 { "#comment", CommentElement },
242 { "circle", CircleElement },
243 { "clipPath", ClipElement },
244 { "desc", DescElement },
245 { "ellipse", EllipseElement },
246 { "g", GroupElement },
247 { "image", ImageElement },
248 { "line", LineElement },
249 { "polyline", PolylineElement },
250 { "polygon", PolygonElement },
251 { "path", PathElement },
252 { "rect", RectElement },
253 { "svg", SvgElement },
254 { "text", TextElement },
255 { "tspan", TSpanElement },
256 { "title", TitleElement },
257 { 0, InvalidElement }
258 };
259 // initialize only once
260 if ( !qSvgTypeMap ) {
261 qSvgTypeMap = new QSvgTypeMap;
262 const ElementTable *t = etab;
263 while ( t->name ) {
264 qSvgTypeMap->insert( t->name, t->type );
265 t++;
266 }
267 }
268
269 // initial state
270 QSvgDeviceState st;
271 st.textx = st.texty = 0;
272 st.textalign = Qt::AlignLeft;
273 d->stack.append(st);
274 curr = &d->stack.last();
275 // 'play' all elements recursively starting with 'svg' as root
276 bool b = play( svg );
277 d->stack.remove( d->stack.begin() );
278 return b;
279}
280
281/*!
282 Returns the SVG as a single string of XML.
283*/
284QString QSvgDevice::toString() const
285{
286 if ( doc.isNull() )
287 return QString();
288
289 return doc.toString();
290}
291
292/*!
293 Saves the SVG to \a fileName.
294*/
295
296bool QSvgDevice::save( const QString &fileName )
297{
298 // guess svg id from fileName
299 QString svgName = fileName.endsWith( ".svg" ) ?
300 fileName.left( fileName.length()-4 ) : fileName;
301
302 // now we have the info about name and dimensions available
303 QDomElement root = doc.documentElement();
304 root.setAttribute( "id", svgName );
305 // the standard doesn't take respect x and y. But we want a
306 // proper bounding rect. We make width and height bigger when
307 // writing out and subtract x and y when reading in.
308 root.setAttribute( "x", brect.x() );
309 root.setAttribute( "y", brect.y() );
310 root.setAttribute( "width", brect.width() + brect.x() );
311 root.setAttribute( "height", brect.height() + brect.y() );
312
313 // ... and know how to name any image files to be written out
314 int icount = 0;
315 ImageList::Iterator iit = d->images.begin();
316 for ( ; iit != d->images.end(); ++iit ) {
317 QString href = QString( "%1_%2.png" ).arg( svgName ).arg( icount );
318 (*iit).image.save( href, "PNG" );
319 (*iit).element.setAttribute( "xlink:href", href );
320 icount++;
321 }
322 PixmapList::Iterator pit = d->pixmaps.begin();
323 for ( ; pit != d->pixmaps.end(); ++pit ) {
324 QString href = QString( "%1_%2.png" ).arg( svgName ).arg( icount );
325 (*pit).pixmap.save( href, "PNG" );
326 (*pit).element.setAttribute( "xlink:href", href );
327 icount++;
328 }
329
330 QFile f( fileName );
331 if ( !f.open ( IO_WriteOnly ) )
332 return FALSE;
333 QTextStream s( &f );
334 s.setEncoding( QTextStream::UnicodeUTF8 );
335 s << doc;
336
337 return TRUE;
338}
339
340/*!
341 \overload
342
343 \a dev is the device to use for saving.
344*/
345
346bool QSvgDevice::save( QIODevice *dev )
347{
348#if defined(CHECK_RANGE)
349 if ( !d->images.isEmpty() || !d->pixmaps.isEmpty() )
350 qWarning( "QSvgDevice::save: skipping external images" );
351#endif
352
353 QTextStream s( dev );
354 s.setEncoding( QTextStream::UnicodeUTF8 );
355 s << doc;
356
357 return TRUE;
358}
359
360/*!
361 \fn QRect QSvgDevice::boundingRect() const
362
363 Returns the bounding rectangle of the SVG.
364*/
365
366/*!
367 Sets the bounding rectangle of the SVG to rectangle \a r.
368*/
369
370void QSvgDevice::setBoundingRect( const QRect &r )
371{
372 brect = r;
373}
374
375/*!
376 Internal implementation of the virtual QPaintDevice::metric()
377 function.
378
379 \warning Use the QPaintDeviceMetrics class instead.
380
381 A QSvgDevice has the following hard coded values: dpi=72,
382 numcolors=16777216 and depth=24. \a m is the metric to get.
383*/
384
385int QSvgDevice::metric( int m ) const
386{
387 int val;
388 switch ( m ) {
389 case QPaintDeviceMetrics::PdmWidth:
390 val = brect.width();
391 break;
392 case QPaintDeviceMetrics::PdmHeight:
393 val = brect.height();
394 break;
395 case QPaintDeviceMetrics::PdmWidthMM:
396 val = int(25.4/72.0*brect.width());
397 break;
398 case QPaintDeviceMetrics::PdmHeightMM:
399 val = int(25.4/72.0*brect.height());
400 break;
401 case QPaintDeviceMetrics::PdmDpiX:
402 val = 72;
403 break;
404 case QPaintDeviceMetrics::PdmDpiY:
405 val = 72;
406 break;
407 case QPaintDeviceMetrics::PdmNumColors:
408 val = 16777216;
409 break;
410 case QPaintDeviceMetrics::PdmDepth:
411 val = 24;
412 break;
413 default:
414 val = 0;
415#if defined(QT_CHECK_RANGE)
416 qWarning( "QSvgDevice::metric: Invalid metric command" );
417#endif
418 }
419 return val;
420}
421
422/*!
423 \internal
424
425 Records painter commands and stores them in the QDomDocument doc.
426*/
427
428bool QSvgDevice::cmd ( int c, QPainter *painter, QPDevCmdParam *p )
429{
430 pt = painter;
431
432 if ( c == PdcBegin ) {
433 QDomImplementation domImpl;
434 QDomDocumentType docType = domImpl.createDocumentType( "svg",
435 publicId,
436 systemId );
437 doc = domImpl.createDocument( "http://www.w3.org/2000/svg",
438 "svg", docType );
439 doc.insertBefore( doc.createProcessingInstruction( "xml", piData ),
440 doc.firstChild() );
441 current = doc.documentElement();
442 d->images.clear();
443 d->pixmaps.clear();
444 dirtyTransform = dirtyStyle = FALSE; // ###
445 return TRUE;
446 } else if ( c == PdcEnd ) {
447 return TRUE;
448 }
449
450 QDomElement e;
451 QString str;
452 QRect rect;
453 QPointArray a;
454 int i, width, height, x, y;
455 switch ( c ) {
456 case PdcNOP:
457 break;
458 case PdcMoveTo:
459 curPt = *p[0].point;
460 break;
461 case PdcLineTo:
462 e = doc.createElement( "line" );
463 e.setAttribute( "x1", curPt.x() );
464 e.setAttribute( "y1", curPt.y() );
465 e.setAttribute( "x2", p[0].point->x() );
466 e.setAttribute( "y2", p[0].point->y() );
467 break;
468 case PdcDrawPoint:
469 case PdcDrawLine:
470 e = doc.createElement( "line" );
471 e.setAttribute( "x1", p[0].point->x() );
472 e.setAttribute( "y1", p[0].point->y() );
473 i = ( c == PdcDrawLine ) ? 1 : 0;
474 e.setAttribute( "x2", p[i].point->x() );
475 e.setAttribute( "y2", p[i].point->y() );
476 break;
477 case PdcDrawRect:
478 case PdcDrawRoundRect:
479 e = doc.createElement( "rect" );
480 x = p[0].rect->x();
481 y = p[0].rect->y();
482 width = p[0].rect->width();
483 height = p[0].rect->height();
484 if ( width < 0 ) {
485 width = -width;
486 x -= width - 1;
487 }
488 if ( height < 0 ) {
489 height = -height;
490 y -= height - 1;
491 }
492 e.setAttribute( "x", x );
493 e.setAttribute( "y", y );
494 e.setAttribute( "width", width );
495 e.setAttribute( "height", height );
496 if ( c == PdcDrawRoundRect ) {
497 e.setAttribute( "rx", (p[1].ival*p[0].rect->width())/200 );
498 e.setAttribute( "ry", (p[2].ival*p[0].rect->height())/200 );
499 }
500 break;
501 case PdcDrawEllipse:
502 rect = *p[0].rect;
503 if ( rect.width() == rect.height() ) {
504 e = doc.createElement( "circle" );
505 double cx = rect.x() + (rect.width() / 2.0);
506 double cy = rect.y() + (rect.height() / 2.0);
507 e.setAttribute( "cx", cx );
508 e.setAttribute( "cy", cy );
509 e.setAttribute( "r", cx - rect.x() );
510 } else {
511 e = doc.createElement( "ellipse" );
512 double cx = rect.x() + (rect.width() / 2.0);
513 double cy = rect.y() + (rect.height() / 2.0);
514 e.setAttribute( "cx", cx );
515 e.setAttribute( "cy", cy );
516 e.setAttribute( "rx", cx - rect.x() );
517 e.setAttribute( "ry", cy - rect.y() );
518 }
519 break;
520 case PdcDrawArc:
521 case PdcDrawPie:
522 case PdcDrawChord: {
523 rect = *p[0].rect;
524 double a = (double)p[1].ival / 16.0 * deg2rad;
525 double al = (double)p[2].ival / 16.0 * deg2rad;
526 double rx = rect.width() / 2.0;
527 double ry = rect.height() / 2.0;
528 double x0 = (double)rect.x() + rx;
529 double y0 = (double)rect.y() + ry;
530 double x1 = x0 + rx*cos(a);
531 double y1 = y0 - ry*sin(a);
532 double x2 = x0 + rx*cos(a+al);
533 double y2 = y0 - ry*sin(a+al);
534 int large = QABS( al ) > ( 180.0 * deg2rad ) ? 1 : 0;
535 int sweep = al < 0.0 ? 1 : 0;
536 if ( c == PdcDrawPie )
537 str = QString( "M %1 %2 L %3 %4 " ).arg( x0 ).arg( y0 )
538 .arg( x1 ).arg( y1 );
539 else
540 str = QString( "M %1 %2 " ).arg( x1 ).arg( y1 );
541 str += QString( "A %1 %2 %3 %4 %5 %6 %7" )
542 .arg( rx ).arg( ry ).arg( a/deg2rad ). arg( large ).arg( sweep )
543 .arg( x2 ).arg( y2 );
544 if ( c != PdcDrawArc )
545 str += "z";
546 e = doc.createElement( "path" );
547 e.setAttribute( "d", str );
548 }
549 break;
550 case PdcDrawLineSegments:
551 {
552 a = *p[0].ptarr;
553 uint end = a.size() / 2;
554 for (uint i = 0; i < end; i++) {
555 e = doc.createElement( "line" );
556 e.setAttribute( "x1", a[int(2*i)].x() );
557 e.setAttribute( "y1", a[int(2*i)].y() );
558 e.setAttribute( "x2", a[int(2*i+1)].x() );
559 e.setAttribute( "y2", a[int(2*i+1)].y() );
560 if ( i < end - 1 ) // The last one will be done at the end
561 appendChild( e, c );
562 }
563 }
564 break;
565 case PdcDrawPolyline:
566 case PdcDrawPolygon:
567 {
568 a = *p[0].ptarr;
569 e = doc.createElement( ( c == PdcDrawPolyline ) ?
570 "polyline" : "polygon" );
571 for (uint i = 0; i < a.size(); i++) {
572 QString tmp;
573 tmp.sprintf( "%d %d ", a[ (int)i ].x(), a[ (int)i ].y() );
574 str += tmp;
575 }
576 e.setAttribute( "points", str.stripWhiteSpace() );
577 }
578 break;
579#ifndef QT_NO_BEZIER
580 case PdcDrawCubicBezier:
581 a = *p[0].ptarr;
582 e = doc.createElement( "path" );
583 str.sprintf( "M %d %d C %d %d %d %d %d %d", a[0].x(), a[0].y(),
584 a[1].x(), a[1].y(), a[2].x(), a[2].y(),
585 a[3].x(), a[3].y() );
586 e.setAttribute( "d", str );
587 break;
588#endif
589 case PdcDrawText2:
590 e = doc.createElement( "text" );
591 if ( p[0].point->x() )
592 e.setAttribute( "x", p[0].point->x() );
593 if ( p[0].point->y() )
594 e.setAttribute( "y", p[0].point->y() );
595 e.appendChild( doc.createTextNode( *p[1].str ) );
596 break;
597 case PdcDrawText2Formatted: {
598 e = doc.createElement( "text" );
599 const QRect *r = p[0].rect;
600 int tf = p[1].ival;
601 int x, y;
602 // horizontal text alignment
603 if ( ( tf & Qt::AlignHCenter ) != 0 ) {
604 x = r->x() + r->width() / 2;
605 e.setAttribute( "text-anchor", "middle" );
606 } else if ( ( tf & Qt::AlignRight ) != 0 ) {
607 x = r->right();
608 e.setAttribute( "text-anchor", "end" );
609 } else {
610 x = r->x();
611 }
612 // vertical text alignment
613 if ( ( tf & Qt::AlignVCenter ) != 0 )
614 y = r->y() + ( r->height() + painter->fontMetrics().ascent() ) / 2;
615 else if ( ( tf & Qt::AlignBottom ) != 0 )
616 y = r->bottom();
617 else
618 y = r->y() + painter->fontMetrics().ascent();
619 if ( x )
620 e.setAttribute( "x", x );
621 if ( y )
622 e.setAttribute( "y", y );
623 e.appendChild( doc.createTextNode( *p[2].str ) );
624 }
625 break;
626 case PdcDrawPixmap:
627 case PdcDrawImage:
628 e = doc.createElement( "image" );
629 e.setAttribute( "x", p[0].rect->x() );
630 e.setAttribute( "y", p[0].rect->y() );
631 e.setAttribute( "width", p[0].rect->width() );
632 e.setAttribute( "height", p[0].rect->height() );
633 if ( c == PdcDrawImage ) {
634 ImgElement ie;
635 ie.element = e;
636 ie.image = *p[1].image;
637 d->images.append( ie );
638 } else {
639 PixElement pe;
640 pe.element = e;
641 pe.pixmap = *p[1].pixmap;
642 d->pixmaps.append( pe );
643 }
644 // saving to disk and setting the xlink:href attribute will be
645 // done later in save() once we now the svg document name.
646 break;
647 case PdcSave:
648 e = doc.createElement( "g" );
649 break;
650 case PdcRestore:
651 current = current.parentNode();
652 dirtyTransform = !pt->worldMatrix().isIdentity();
653 // ### reset dirty flags
654 break;
655 case PdcSetBkColor:
656 case PdcSetBkMode:
657 case PdcSetROP:
658 case PdcSetBrushOrigin:
659 case PdcSetFont:
660 case PdcSetPen:
661 case PdcSetBrush:
662 dirtyStyle = TRUE;
663 break;
664 case PdcSetTabStops:
665 // ###
666 break;
667 case PdcSetTabArray:
668 // ###
669 break;
670 case PdcSetVXform:
671 case PdcSetWindow:
672 case PdcSetViewport:
673 case PdcSetWXform:
674 case PdcSetWMatrix:
675 case PdcSaveWMatrix:
676 case PdcRestoreWMatrix:
677 dirtyTransform = TRUE;
678 break;
679 case PdcSetClip:
680 // ###
681 break;
682 case PdcSetClipRegion:
683 {
684 d->currentClip++;
685 e = doc.createElement( "clipPath" );
686 e.setAttribute( "id", QString("clip%1").arg(d->currentClip) );
687 QRect br = p[0].rgn->boundingRect();
688 QDomElement ce;
689 if ( p[0].rgn->rects().count() == 1 ) {
690 // Then it's just a rect, boundingRect() will do
691 ce = doc.createElement( "rect" );
692 ce.setAttribute( "x", br.x() );
693 ce.setAttribute( "y", br.y() );
694 ce.setAttribute( "width", br.width() );
695 ce.setAttribute( "height", br.height() );
696 } else {
697 // It's an ellipse, calculate the ellipse
698 // from the boundingRect()
699 ce = doc.createElement( "ellipse" );
700 double cx = br.x() + (br.width() / 2.0);
701 double cy = br.y() + (br.height() / 2.0);
702 ce.setAttribute( "cx", cx );
703 ce.setAttribute( "cy", cy );
704 ce.setAttribute( "rx", cx - br.x() );
705 ce.setAttribute( "ry", cy - br.y() );
706 }
707 e.appendChild( ce );
708 break;
709 }
710 default:
711#if defined(CHECK_RANGE)
712 qWarning( "QSVGDevice::cmd: Invalid command %d", c );
713#endif
714 break;
715 }
716
717 appendChild( e, c );
718
719 return TRUE;
720}
721
722/*!
723 \internal
724
725 Appends the child and applys any style and transformation.
726
727*/
728
729void QSvgDevice::appendChild( QDomElement &e, int c )
730{
731 if ( !e.isNull() ) {
732 current.appendChild( e );
733 if ( c == PdcSave )
734 current = e;
735 // ### optimize application of attributes utilizing <g>
736 if ( c == PdcSetClipRegion ) {
737 QDomElement ne;
738 ne = doc.createElement( "g" );
739 ne.setAttribute( "style", QString("clip-path:url(#clip%1)").arg(d->currentClip) );
740 current.appendChild( ne );
741 current = ne;
742 } else {
743 if ( dirtyStyle ) // only reset when entering
744 applyStyle( &e, c ); // or leaving a <g> tag
745 if ( dirtyTransform && e.tagName() != "g" ) {
746 // same as above but not for <g> tags
747 applyTransform( &e );
748 if ( c == PdcSave )
749 dirtyTransform = FALSE;
750 }
751 }
752 }
753}
754
755
756/*!
757 \internal
758
759 Push the current drawing attributes on a stack.
760
761 \sa restoreAttributes()
762*/
763
764void QSvgDevice::saveAttributes()
765{
766 pt->save();
767 // copy old state
768 QSvgDeviceState st( *curr );
769 d->stack.append( st );
770 curr = &d->stack.last();
771}
772
773/*!
774 \internal
775
776 Pop the current drawing attributes off the stack.
777
778 \sa saveAttributes()
779*/
780
781void QSvgDevice::restoreAttributes()
782{
783 pt->restore();
784 Q_ASSERT( d->stack.count() > 1 );
785 d->stack.remove( d->stack.fromLast() );
786 curr = &d->stack.last();
787}
788
789/*!
790 \internal
791
792 Evaluate \a node, drawing on \a p. Allows recursive calls.
793*/
794
795bool QSvgDevice::play( const QDomNode &node )
796{
797 saveAttributes();
798
799 ElementType t = (*qSvgTypeMap)[ node.nodeName() ];
800
801 if ( t == LineElement && pt->pen().style() == Qt::NoPen ) {
802 QPen p = pt->pen();
803 p.setStyle( Qt::SolidLine );
804 pt->setPen( p );
805 }
806 QDomNamedNodeMap attr = node.attributes();
807 if ( attr.contains( "style" ) )
808 setStyle( attr.namedItem( "style" ).nodeValue() );
809 // ### might have to exclude more elements from transform
810 if ( t != SvgElement && attr.contains( "transform" ) )
811 setTransform( attr.namedItem( "transform" ).nodeValue() );
812 uint i = attr.length();
813 if ( i > 0 ) {
814 QPen pen = pt->pen();
815 QFont font = pt->font();
816 while ( i-- ) {
817 QDomNode n = attr.item( i );
818 QString a = n.nodeName();
819 QString val = n.nodeValue().lower().stripWhiteSpace();
820 setStyleProperty( a, val, &pen, &font, &curr->textalign );
821 }
822 pt->setPen( pen );
823 pt->setFont( font );
824 }
825
826 int x1, y1, x2, y2, rx, ry, w, h;
827 double cx1, cy1, crx, cry;
828 switch ( t ) {
829 case CommentElement:
830 // ignore
831 break;
832 case RectElement:
833 rx = ry = 0;
834 x1 = lenToInt( attr, "x" );
835 y1 = lenToInt( attr, "y" );
836 w = lenToInt( attr, "width" );
837 h = lenToInt( attr, "height" );
838 if ( w == 0 || h == 0 ) // prevent div by zero below
839 break;
840 x2 = (int)attr.contains( "rx" ); // tiny abuse of x2 and y2
841 y2 = (int)attr.contains( "ry" );
842 if ( x2 )
843 rx = lenToInt( attr, "rx" );
844 if ( y2 )
845 ry = lenToInt( attr, "ry" );
846 if ( x2 && !y2 )
847 ry = rx;
848 else if ( !x2 && y2 )
849 rx = ry;
850 rx = int(200.0*double(rx)/double(w));
851 ry = int(200.0*double(ry)/double(h));
852 pt->drawRoundRect( x1, y1, w, h, rx, ry );
853 break;
854 case CircleElement:
855 cx1 = lenToDouble( attr, "cx" ) + 0.5;
856 cy1 = lenToDouble( attr, "cy" ) + 0.5;
857 crx = lenToDouble( attr, "r" );
858 pt->drawEllipse( (int)(cx1-crx), (int)(cy1-crx), (int)(2*crx), (int)(2*crx) );
859 break;
860 case EllipseElement:
861 cx1 = lenToDouble( attr, "cx" ) + 0.5;
862 cy1 = lenToDouble( attr, "cy" ) + 0.5;
863 crx = lenToDouble( attr, "rx" );
864 cry = lenToDouble( attr, "ry" );
865 pt->drawEllipse( (int)(cx1-crx), (int)(cy1-cry), (int)(2*crx), (int)(2*cry) );
866 break;
867 case LineElement:
868 {
869 x1 = lenToInt( attr, "x1" );
870 x2 = lenToInt( attr, "x2" );
871 y1 = lenToInt( attr, "y1" );
872 y2 = lenToInt( attr, "y2" );
873 QPen p = pt->pen();
874 w = p.width();
875 p.setWidth( (unsigned int)(w * (QABS(pt->worldMatrix().m11()) + QABS(pt->worldMatrix().m22())) / 2) );
876 pt->setPen( p );
877 pt->drawLine( x1, y1, x2, y2 );
878 p.setWidth( w );
879 pt->setPen( p );
880 }
881 break;
882 case PolylineElement:
883 case PolygonElement:
884 {
885 QString pts = attr.namedItem( "points" ).nodeValue();
886 pts = pts.simplifyWhiteSpace();
887 QStringList sl = QStringList::split( QRegExp( QString::fromLatin1("[ ,]") ), pts );
888 QPointArray ptarr( (uint)sl.count() / 2);
889 for ( int i = 0; i < (int)sl.count() / 2; i++ ) {
890 double dx = sl[2*i].toDouble();
891 double dy = sl[2*i+1].toDouble();
892 ptarr.setPoint( i, int(dx), int(dy) );
893 }
894 if ( t == PolylineElement ) {
895 if ( pt->brush().style() != Qt::NoBrush ) {
896 QPen pn = pt->pen();
897 pt->setPen( Qt::NoPen );
898 pt->drawPolygon( ptarr );
899 pt->setPen( pn );
900 }
901 pt->drawPolyline( ptarr ); // ### closes when filled. bug ?
902 } else {
903 pt->drawPolygon( ptarr );
904 }
905 }
906 break;
907 case SvgElement:
908 case GroupElement:
909 case AnchorElement:
910 {
911 QDomNode child = node.firstChild();
912 while ( !child.isNull() ) {
913 play( child );
914 child = child.nextSibling();
915 }
916 }
917 break;
918 case PathElement:
919 drawPath( attr.namedItem( "d" ).nodeValue() );
920 break;
921 case TSpanElement:
922 case TextElement:
923 {
924 if ( attr.contains( "x" ) )
925 curr->textx = lenToInt( attr, "x" );
926 if ( attr.contains( "y" ) )
927 curr->texty = lenToInt( attr, "y" );
928 if ( t == TSpanElement ) {
929 curr->textx += lenToInt( attr, "dx" );
930 curr->texty += lenToInt( attr, "dy" );
931 }
932 // backup old colors
933 QPen pn = pt->pen();
934 QColor pcolor = pn.color();
935 QColor bcolor = pt->brush().color();
936 QDomNode c = node.firstChild();
937 while ( !c.isNull() ) {
938 if ( c.isText() ) {
939 // we have pen and brush reversed for text drawing
940 pn.setColor( bcolor );
941 pt->setPen( pn );
942 QString text = c.toText().nodeValue();
943 text = text.simplifyWhiteSpace(); // ### 'preserve'
944 w = pt->fontMetrics().width( text );
945 if ( curr->textalign == Qt::AlignHCenter )
946 curr->textx -= w / 2;
947 else if ( curr->textalign == Qt::AlignRight )
948 curr->textx -= w;
949 pt->drawText( curr->textx, curr->texty, text );
950 // restore pen
951 pn.setColor( pcolor );
952 pt->setPen( pn );
953 curr->textx += w;
954 } else if ( c.isElement() &&
955 c.toElement().tagName() == "tspan" ) {
956 play( c );
957
958 }
959 c = c.nextSibling();
960 }
961 if ( t == TSpanElement ) {
962 // move current text position in parent text element
963 StateList::Iterator it = --d->stack.fromLast();
964 (*it).textx = curr->textx;
965 (*it).texty = curr->texty;
966 }
967 }
968 break;
969 case ImageElement:
970 {
971 x1 = lenToInt( attr, "x" );
972 y1 = lenToInt( attr, "y" );
973 w = lenToInt( attr, "width" );
974 h = lenToInt( attr, "height" );
975 QString href = attr.namedItem( "xlink:href" ).nodeValue();
976 // ### catch references to embedded .svg files
977 QPixmap pix;
978 if ( !pix.load( href ) ) {
979 qWarning( "QSvgDevice::play: Couldn't load image "+href );
980 break;
981 }
982 pt->drawPixmap( QRect( x1, y1, w, h ), pix );
983 }
984 break;
985 case DescElement:
986 case TitleElement:
987 // ignored for now
988 break;
989 case ClipElement:
990 {
991 restoreAttributes(); // To ensure the clip rect is saved, we need to restore now
992 QDomNode child = node.firstChild();
993 QDomNamedNodeMap childAttr = child.attributes();
994 if ( child.nodeName() == "rect" ) {
995 QRect r;
996 r.setX(lenToInt( childAttr, "x" ));
997 r.setY(lenToInt( childAttr, "y" ));
998 r.setWidth(lenToInt( childAttr, "width" ));
999 r.setHeight(lenToInt( childAttr, "height" ));
1000 pt->setClipRect( r, QPainter::CoordPainter );
1001 } else if ( child.nodeName() == "ellipse" ) {
1002 QRect r;
1003 int x = lenToInt( childAttr, "cx" );
1004 int y = lenToInt( childAttr, "cy" );
1005 int width = lenToInt( childAttr, "rx" );
1006 int height = lenToInt( childAttr, "ry" );
1007 r.setX( x - width );
1008 r.setY( y - height );
1009 r.setWidth( width * 2 );
1010 r.setHeight( height * 2 );
1011 QRegion rgn( r, QRegion::Ellipse );
1012 pt->setClipRegion( rgn, QPainter::CoordPainter );
1013 }
1014 break;
1015 }
1016 case InvalidElement:
1017 qWarning( "QSvgDevice::play: unknown element type " +
1018 node.nodeName() );
1019 break;
1020 };
1021
1022 if ( t != ClipElement )
1023 restoreAttributes();
1024
1025 return TRUE;
1026}
1027
1028/*!
1029 \internal
1030
1031 Parses a CSS2-compatible color specification. Either a keyword or
1032 a numerical RGB specification like #ff00ff or rgb(255,0,50%).
1033*/
1034
1035QColor QSvgDevice::parseColor( const QString &col )
1036{
1037 static const struct ColorTable {
1038 const char *name;
1039 const char *rgb;
1040 } coltab[] = {
1041 { "black", "#000000" },
1042 { "silver", "#c0c0c0" },
1043 { "gray", "#808080" },
1044 { "white", "#ffffff" },
1045 { "maroon", "#800000" },
1046 { "red", "#ff0000" },
1047 { "purple", "#800080" },
1048 { "fuchsia", "#ff00ff" },
1049 { "green", "#008000" },
1050 { "lime", "#00ff00" },
1051 { "olive", "#808000" },
1052 { "yellow", "#ffff00" },
1053 { "navy", "#000080" },
1054 { "blue", "#0000ff" },
1055 { "teal", "#008080" },
1056 { "aqua", "#00ffff" },
1057 // ### the latest spec has more
1058 { 0, 0 }
1059 };
1060
1061 // initialize color map on first use
1062 if ( !qSvgColMap ) {
1063 qSvgColMap = new QMap<QString, QString>;
1064 const struct ColorTable *t = coltab;
1065 while ( t->name ) {
1066 qSvgColMap->insert( t->name, t->rgb );
1067 t++;
1068 }
1069 }
1070
1071 // a keyword ?
1072 if ( qSvgColMap->contains ( col ) )
1073 return QColor( (*qSvgColMap)[ col ] );
1074 // in rgb(r,g,b) form ?
1075 QString c = col;
1076 c.replace( QRegExp( QString::fromLatin1("\\s*") ), "" );
1077 QRegExp reg( QString::fromLatin1("^rgb\\((\\d+)(%?),(\\d+)(%?),(\\d+)(%?)\\)$") );
1078 if ( reg.search( c ) >= 0 ) {
1079 int comp[3];
1080 for ( int i = 0; i < 3; i++ ) {
1081 comp[ i ] = reg.cap( 2*i+1 ).toInt();
1082 if ( !reg.cap( 2*i+2 ).isEmpty() ) // percentage ?
1083 comp[ i ] = int((double(255*comp[ i ])/100.0));
1084 }
1085 return QColor( comp[ 0 ], comp[ 1 ], comp[ 2 ] );
1086 }
1087
1088 // check for predefined Qt color objects, #RRGGBB and #RGB
1089 return QColor( col );
1090}
1091
1092/*!
1093 \internal
1094
1095 Parse a <length> datatype consisting of a number followed by an
1096 optional unit specifier. Can be used for type <coordinate> as
1097 well. For relative units the value of \a horiz will determine
1098 whether the horizontal or vertical dimension will be used.
1099*/
1100
1101double QSvgDevice::parseLen( const QString &str, bool *ok, bool horiz ) const
1102{
1103 QRegExp reg( QString::fromLatin1("([+-]?\\d*\\.*\\d*[Ee]?[+-]?\\d*)(em|ex|px|%|pt|pc|cm|mm|in|)$") );
1104 if ( reg.search( str ) == -1 ) {
1105 qWarning( "QSvgDevice::parseLen: couldn't parse " + str );
1106 if ( ok )
1107 *ok = FALSE;
1108 return 0.0;
1109 }
1110
1111 double dbl = reg.cap( 1 ).toDouble();
1112 QString u = reg.cap( 2 );
1113 if ( !u.isEmpty() && u != "px" ) {
1114 QPaintDeviceMetrics m( pt->device() );
1115 if ( u == "em" ) {
1116 QFontInfo fi( pt->font() );
1117 dbl *= fi.pixelSize();
1118 } else if ( u == "ex" ) {
1119 QFontInfo fi( pt->font() );
1120 dbl *= 0.5 * fi.pixelSize();
1121 } else if ( u == "%" )
1122 dbl *= (horiz ? pt->window().width() : pt->window().height())/100.0;
1123 else if ( u == "cm" )
1124 dbl *= m.logicalDpiX() / 2.54;
1125 else if ( u == "mm" )
1126 dbl *= m.logicalDpiX() / 25.4;
1127 else if ( u == "in" )
1128 dbl *= m.logicalDpiX();
1129 else if ( u == "pt" )
1130 dbl *= m.logicalDpiX() / 72.0;
1131 else if ( u == "pc" )
1132 dbl *= m.logicalDpiX() / 6.0;
1133 else
1134 qWarning( "QSvgDevice::parseLen: Unknown unit " + u );
1135 }
1136 if ( ok )
1137 *ok = TRUE;
1138 return dbl;
1139}
1140
1141/*!
1142 \internal
1143
1144 Returns the length specified in attribute \a attr in \a map. If
1145 the specified attribute doesn't exist or can't be parsed \a def is
1146 returned.
1147*/
1148
1149int QSvgDevice::lenToInt( const QDomNamedNodeMap &map, const QString &attr,
1150 int def ) const
1151{
1152 if ( map.contains( attr ) ) {
1153 bool ok;
1154 double dbl = parseLen( map.namedItem( attr ).nodeValue(), &ok );
1155 if ( ok )
1156 return qRound( dbl );
1157 }
1158 return def;
1159}
1160
1161double QSvgDevice::lenToDouble( const QDomNamedNodeMap &map, const QString &attr,
1162 int def ) const
1163{
1164 if ( map.contains( attr ) ) {
1165 bool ok;
1166 double d = parseLen( map.namedItem( attr ).nodeValue(), &ok );
1167 if ( ok )
1168 return d;
1169 }
1170 return def;
1171}
1172
1173void QSvgDevice::setStyleProperty( const QString &prop, const QString &val,
1174 QPen *pen, QFont *font, int *talign )
1175{
1176 if ( prop == "stroke" ) {
1177 if ( val == "none" ) {
1178 pen->setStyle( Qt::NoPen );
1179 } else {
1180 pen->setColor( parseColor( val ));
1181 if ( pen->style() == Qt::NoPen )
1182 pen->setStyle( Qt::SolidLine );
1183 if ( pen->width() == 0 )
1184 pen->setWidth( 1 );
1185 }
1186 } else if ( prop == "stroke-width" ) {
1187 double w = parseLen( val );
1188 if ( w > 0.0001 )
1189 pen->setWidth( int(w) );
1190 else
1191 pen->setStyle( Qt::NoPen );
1192 } else if ( prop == "stroke-linecap" ) {
1193 if ( val == "butt" )
1194 pen->setCapStyle( Qt::FlatCap );
1195 else if ( val == "round" )
1196 pen->setCapStyle( Qt::RoundCap );
1197 else if ( val == "square" )
1198 pen->setCapStyle( Qt::SquareCap );
1199 } else if ( prop == "stroke-linejoin" ) {
1200 if ( val == "miter" )
1201 pen->setJoinStyle( Qt::MiterJoin );
1202 else if ( val == "round" )
1203 pen->setJoinStyle( Qt::RoundJoin );
1204 else if ( val == "bevel" )
1205 pen->setJoinStyle( Qt::BevelJoin );
1206 } else if ( prop == "stroke-dasharray" ) {
1207 if ( val == "18,6" )
1208 pen->setStyle( Qt::DashLine );
1209 else if ( val == "3" )
1210 pen->setStyle( Qt::DotLine );
1211 else if ( val == "9,6,3,6" )
1212 pen->setStyle( Qt::DashDotLine );
1213 else if ( val == "9,3,3" )
1214 pen->setStyle( Qt::DashDotDotLine );
1215 } else if ( prop == "fill" ) {
1216 if ( val == "none" )
1217 pt->setBrush( Qt::NoBrush );
1218 else
1219 pt->setBrush( parseColor( val ) );
1220 } else if ( prop == "font-size" ) {
1221 font->setPointSizeFloat( float(parseLen( val )) );
1222 } else if ( prop == "font-family" ) {
1223 font->setFamily( val );
1224 } else if ( prop == "font-style" ) {
1225 if ( val == "normal" )
1226 font->setItalic( FALSE );
1227 else if ( val == "italic" )
1228 font->setItalic( TRUE );
1229 else
1230 qWarning( "QSvgDevice::setStyleProperty: unhandled "
1231 "font-style: %s", val.latin1() );
1232 } else if ( prop == "font-weight" ) {
1233 int w = font->weight();
1234 // no exact equivalents so we have to "round" a little bit
1235 if ( val == "100" || val == "200" )
1236 w = QFont::Light;
1237 if ( val == "300" || val == "400" || val == "normal" )
1238 w = QFont::Normal;
1239 else if ( val == "500" || val == "600" )
1240 w = QFont::DemiBold;
1241 else if ( val == "700" || val == "bold" || val == "800" )
1242 w = QFont::Bold;
1243 else if ( val == "900" )
1244 w = QFont::Black;
1245 font->setWeight( w );
1246 } else if ( prop == "text-anchor" ) {
1247 if ( val == "middle" )
1248 *talign = Qt::AlignHCenter;
1249 else if ( val == "end" )
1250 *talign = Qt::AlignRight;
1251 else
1252 *talign = Qt::AlignLeft;
1253 }
1254}
1255
1256void QSvgDevice::setStyle( const QString &s )
1257{
1258 QStringList rules = QStringList::split( QChar(';'), s );
1259
1260 QPen pen = pt->pen();
1261 QFont font = pt->font();
1262
1263 QStringList::ConstIterator it = rules.begin();
1264 for ( ; it != rules.end(); it++ ) {
1265 int col = (*it).find( ':' );
1266 if ( col > 0 ) {
1267 QString prop = (*it).left( col ).simplifyWhiteSpace();
1268 QString val = (*it).right( (*it).length() - col - 1 );
1269 val = val.lower().stripWhiteSpace();
1270 setStyleProperty( prop, val, &pen, &font, &curr->textalign );
1271 }
1272 }
1273
1274 pt->setPen( pen );
1275 pt->setFont( font );
1276}
1277
1278void QSvgDevice::setTransform( const QString &tr )
1279{
1280 QString t = tr.simplifyWhiteSpace();
1281
1282 QRegExp reg( QString::fromLatin1("\\s*([\\w]+)\\s*\\(([^\\(]*)\\)") );
1283 int index = 0;
1284 while ( (index = reg.search(t, index)) >= 0 ) {
1285 QString command = reg.cap( 1 );
1286 QString params = reg.cap( 2 );
1287 QStringList plist = QStringList::split( QRegExp(QString::fromLatin1("[,\\s]")), params );
1288 if ( command == "translate" ) {
1289 double tx = 0, ty = 0;
1290 tx = plist[0].toDouble();
1291 if ( plist.count() >= 2 )
1292 ty = plist[1].toDouble();
1293 pt->translate( tx, ty );
1294 } else if ( command == "rotate" ) {
1295 pt->rotate( plist[0].toDouble() );
1296 } else if ( command == "scale" ) {
1297 double sx, sy;
1298 sx = sy = plist[0].toDouble();
1299 if ( plist.count() >= 2 )
1300 sy = plist[1].toDouble();
1301 pt->scale( sx, sy );
1302 } else if ( command == "matrix" && plist.count() >= 6 ) {
1303 double m[ 6 ];
1304 for (int i = 0; i < 6; i++)
1305 m[ i ] = plist[ i ].toDouble();
1306 QWMatrix wm( m[ 0 ], m[ 1 ], m[ 2 ],
1307 m[ 3 ], m[ 4 ], m[ 5 ] );
1308 pt->setWorldMatrix( wm, TRUE );
1309 } else if ( command == "skewX" ) {
1310 pt->shear( 0.0, tan( plist[0].toDouble() * deg2rad ) );
1311 } else if ( command == "skewY" ) {
1312 pt->shear( tan( plist[0].toDouble() * deg2rad ), 0.0 );
1313 }
1314
1315 // move on to next command
1316 index += reg.matchedLength();
1317 }
1318}
1319
1320void QSvgDevice::drawPath( const QString &data )
1321{
1322 double x0 = 0, y0 = 0; // starting point
1323 double x = 0, y = 0; // current point
1324 double controlX = 0, controlY = 0; // last control point for curves
1325 QPointArray path( 500 ); // resulting path
1326 QValueList<int> subIndex; // start indices for subpaths
1327 QPointArray quad( 4 ), bezier; // for curve calculations
1328 int pcount = 0; // current point array index
1329 uint idx = 0; // current data position
1330 int mode = 0, lastMode = 0; // parser state
1331 bool relative = FALSE; // e.g. 'h' vs. 'H'
1332 QString commands( "MZLHVCSQTA" ); // recognized commands
1333 int cmdArgs[] = { 2, 0, 2, 1, 1, 6, 4, 4, 2, 7 }; // no of arguments
1334 QRegExp reg( QString::fromLatin1("\\s*,?\\s*([+-]?\\d*\\.?\\d*)") ); // floating point
1335
1336 subIndex.append( 0 );
1337 // detect next command
1338 while ( idx < data.length() ) {
1339 QChar ch = data[ (int)idx++ ];
1340 if ( ch.isSpace() )
1341 continue;
1342 QChar chUp = ch.upper();
1343 int cmd = commands.find( chUp );
1344 if ( cmd >= 0 ) {
1345 // switch to new command mode
1346 mode = cmd;
1347 relative = ( ch != chUp ); // e.g. 'm' instead of 'M'
1348 } else {
1349 if ( mode && !ch.isLetter() ) {
1350 cmd = mode; // continue in previous mode
1351 idx--;
1352 } else {
1353 qWarning( "QSvgDevice::drawPath: Unknown command" );
1354 return;
1355 }
1356 }
1357
1358 // read in the required number of arguments
1359 const int maxArgs = 7;
1360 double arg[ maxArgs ];
1361 int numArgs = cmdArgs[ cmd ];
1362 for ( int i = 0; i < numArgs; i++ ) {
1363 int pos = reg.search( data, idx );
1364 if ( pos == -1 ) {
1365 qWarning( "QSvgDevice::drawPath: Error parsing arguments" );
1366 return;
1367 }
1368 arg[ i ] = reg.cap( 1 ).toDouble();
1369 idx = pos + reg.matchedLength();
1370 };
1371
1372 // process command
1373 double offsetX = relative ? x : 0; // correction offsets
1374 double offsetY = relative ? y : 0; // for relative commands
1375 switch ( mode ) {
1376 case 0: // 'M' move to
1377 if ( x != x0 || y != y0 )
1378 path.setPoint( pcount++, int(x0), int(y0) );
1379 x = x0 = arg[ 0 ] + offsetX;
1380 y = y0 = arg[ 1 ] + offsetY;
1381 subIndex.append( pcount );
1382 path.setPoint( pcount++, int(x0), int(y0) );
1383 mode = 2; // -> 'L'
1384 break;
1385 case 1: // 'Z' close path
1386 path.setPoint( pcount++, int(x0), int(y0) );
1387 x = x0;
1388 y = y0;
1389 mode = 0;
1390 break;
1391 case 2: // 'L' line to
1392 x = arg[ 0 ] + offsetX;
1393 y = arg[ 1 ] + offsetY;
1394 path.setPoint( pcount++, int(x), int(y) );
1395 break;
1396 case 3: // 'H' horizontal line
1397 x = arg[ 0 ] + offsetX;
1398 path.setPoint( pcount++, int(x), int(y) );
1399 break;
1400 case 4: // 'V' vertical line
1401 y = arg[ 0 ] + offsetY;
1402 path.setPoint( pcount++, int(x), int(y) );
1403 break;
1404#ifndef QT_NO_BEZIER
1405 case 5: // 'C' cubic bezier curveto
1406 case 6: // 'S' smooth shorthand
1407 case 7: // 'Q' quadratic bezier curves
1408 case 8: { // 'T' smooth shorthand
1409 quad.setPoint( 0, int(x), int(y) );
1410 // if possible, reflect last control point if smooth shorthand
1411 if ( mode == 6 || mode == 8 ) { // smooth 'S' and 'T'
1412 bool cont = mode == lastMode ||
1413 mode == 6 && lastMode == 5 || // 'S' and 'C'
1414 mode == 8 && lastMode == 7; // 'T' and 'Q'
1415 x = cont ? 2*x-controlX : x;
1416 y = cont ? 2*y-controlY : y;
1417 quad.setPoint( 1, int(x), int(y) );
1418 quad.setPoint( 2, int(x), int(y) );
1419 }
1420 for ( int j = 0; j < numArgs/2; j++ ) {
1421 x = arg[ 2*j ] + offsetX;
1422 y = arg[ 2*j+1 ] + offsetY;
1423 quad.setPoint( j+4-numArgs/2, int(x), int(y) );
1424 }
1425 // remember last control point for next shorthand
1426 controlX = quad[ 2 ].x();
1427 controlY = quad[ 2 ].y();
1428 // transform quadratic into cubic Bezier
1429 if ( mode == 7 || mode == 8 ) { // cubic 'Q' and 'T'
1430 int x31 = quad[0].x()+int(2.0*(quad[2].x()-quad[0].x())/3.0);
1431 int y31 = quad[0].y()+int(2.0*(quad[2].y()-quad[0].y())/3.0);
1432 int x32 = quad[2].x()+int(2.0*(quad[3].x()-quad[2].x())/3.0);
1433 int y32 = quad[2].y()+int(2.0*(quad[3].y()-quad[2].y())/3.0);
1434 quad.setPoint( 1, x31, y31 );
1435 quad.setPoint( 2, x32, y32 );
1436 }
1437 // calculate points on curve
1438 bezier = quad.cubicBezier();
1439 // reserve more space if needed
1440 if ( bezier.size() > path.size() - pcount )
1441 path.resize( path.size() - pcount + bezier.size() );
1442 // copy
1443 for ( int k = 0; k < (int)bezier.size(); k ++ )
1444 path.setPoint( pcount++, bezier[ k ] );
1445 break;
1446 }
1447#endif // QT_NO_BEZIER
1448 case 9: // 'A' elliptical arc curve
1449 // ### just a straight line
1450 x = arg[ 5 ] + offsetX;
1451 y = arg[ 6 ] + offsetY;
1452 path.setPoint( pcount++, int(x), int(y) );
1453 break;
1454 };
1455 lastMode = mode;
1456 // array almost full ? expand for next loop
1457 if ( pcount >= (int)path.size() - 4 )
1458 path.resize( 2 * path.size() );
1459 }
1460
1461 subIndex.append( pcount ); // dummy marking the end
1462 if ( pt->brush().style() != Qt::NoBrush ) {
1463 // fill the area without stroke first
1464 if ( x != x0 || y != y0 )
1465 path.setPoint( pcount++, int(x0), int(y0) );
1466 QPen pen = pt->pen();
1467 pt->setPen( Qt::NoPen );
1468 pt->drawPolygon( path, FALSE, 0, pcount );
1469 pt->setPen( pen );
1470 }
1471 // draw each subpath stroke seperately
1472 QValueListConstIterator<int> it = subIndex.begin();
1473 int start = 0;
1474 while ( it != subIndex.fromLast() ) {
1475 int next = *++it;
1476 // ### always joins ends if first and last point coincide.
1477 // ### 'Z' can't have the desired effect
1478 pt->drawPolyline( path, start, next-start );
1479 start = next;
1480 }
1481}
1482
1483void QSvgDevice::applyStyle( QDomElement *e, int c ) const
1484{
1485 // ### do not write every attribute each time
1486 QColor pcol = pt->pen().color();
1487 QColor bcol = pt->brush().color();
1488 QString s;
1489 if ( c == PdcDrawText2 || c == PdcDrawText2Formatted ) {
1490 // QPainter has a reversed understanding of pen/stroke vs.
1491 // brush/fill for text
1492 s += QString( "fill:rgb(%1,%2,%3);" )
1493 .arg( pcol.red() ).arg( pcol.green() ).arg( pcol.blue() );
1494 s += QString( "stroke-width:0;" );
1495 QFont f = pt->font();
1496 QFontInfo fi( f );
1497 s += QString( "font-size:%1;" ).arg( fi.pointSize() );
1498 s += QString( "font-style:%1;" )
1499 .arg( f.italic() ? "italic" : "normal" );
1500 // not a very scientific distribution
1501 QString fw;
1502 if ( f.weight() <= QFont::Light )
1503 fw = "100";
1504 else if ( f.weight() <= QFont::Normal )
1505 fw = "400";
1506 else if ( f.weight() <= QFont::DemiBold )
1507 fw = "600";
1508 else if ( f.weight() <= QFont::Bold )
1509 fw = "700";
1510 else if ( f.weight() <= QFont::Black )
1511 fw = "800";
1512 else
1513 fw = "900";
1514 s += QString( "font-weight:%1;" ).arg( fw );
1515 s += QString( "font-family:%1;" ).arg( f.family() );
1516 } else {
1517 s += QString( "stroke:rgb(%1,%2,%3);" )
1518 .arg( pcol.red() ).arg( pcol.green() ).arg( pcol.blue() );
1519 double pw = pt->pen().width();
1520 if ( pw == 0 && pt->pen().style() != Qt::NoPen )
1521 pw = 0.9;
1522 if ( c == PdcDrawLine )
1523 pw /= (QABS(pt->worldMatrix().m11()) + QABS(pt->worldMatrix().m22())) / 2.0;
1524 s += QString( "stroke-width:%1;" ).arg( pw );
1525 if ( pt->pen().style() == Qt::DashLine )
1526 s+= QString( "stroke-dasharray:18,6;" );
1527 else if ( pt->pen().style() == Qt::DotLine )
1528 s+= QString( "stroke-dasharray:3;" );
1529 else if ( pt->pen().style() == Qt::DashDotLine )
1530 s+= QString( "stroke-dasharray:9,6,3,6;" );
1531 else if ( pt->pen().style() == Qt::DashDotDotLine )
1532 s+= QString( "stroke-dasharray:9,3,3;" );
1533 if ( pt->brush().style() == Qt::NoBrush || c == PdcDrawPolyline ||
1534 c == PdcDrawCubicBezier )
1535 s += "fill:none;"; // Qt polylines use no brush, neither do Beziers
1536 else
1537 s += QString( "fill:rgb(%1,%2,%3);" )
1538 .arg( bcol.red() ).arg( bcol.green() ).arg( bcol.blue() );
1539 }
1540 e->setAttribute( "style", s );
1541}
1542
1543void QSvgDevice::applyTransform( QDomElement *e ) const
1544{
1545 QWMatrix m = pt->worldMatrix();
1546
1547 QString s;
1548 bool rot = ( m.m11() != 1.0 || m.m12() != 0.0 ||
1549 m.m21() != 0.0 || m.m22() != 1.0 );
1550 if ( !rot && ( m.dx() != 0.0 || m.dy() != 0.0 ) )
1551 s = QString( "translate(%1,%2)" ).arg( m.dx() ).arg( m.dy() );
1552 else if ( rot ) {
1553 if ( m.m12() == 0.0 && m.m21() == 0.0 &&
1554 m.dx() == 0.0 && m.dy() == 0.0 )
1555 s = QString( "scale(%1,%2)" ).arg( m.m11() ).arg( m.m22() );
1556 else
1557 s = QString( "matrix(%1,%2,%3,%4,%5,%6)" )
1558 .arg( m.m11() ).arg( m.m12() )
1559 .arg( m.m21() ).arg( m.m22() )
1560 .arg( m.dx() ).arg( m.dy() );
1561 }
1562 else
1563 return;
1564
1565 e->setAttribute( "transform", s );
1566}
1567
1568#endif // QT_NO_SVG
Note: See TracBrowser for help on using the repository browser.