source: trunk/src/qt3support/painting/q3paintengine_svg.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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