diff options
author | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2011-07-10 15:24:15 -0500 |
---|---|---|
committer | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2011-07-10 15:24:15 -0500 |
commit | bd0f3345a938b35ce6a12f6150373b0955b8dd12 (patch) | |
tree | 7a520322212d48ebcb9fbe1087e7fca28b76185c /src/xml/qsvgdevice.cpp | |
download | qt3-bd0f3345a938b35ce6a12f6150373b0955b8dd12.tar.gz qt3-bd0f3345a938b35ce6a12f6150373b0955b8dd12.zip |
Add Qt3 development HEAD version
Diffstat (limited to 'src/xml/qsvgdevice.cpp')
-rw-r--r-- | src/xml/qsvgdevice.cpp | 1591 |
1 files changed, 1591 insertions, 0 deletions
diff --git a/src/xml/qsvgdevice.cpp b/src/xml/qsvgdevice.cpp new file mode 100644 index 0000000..1f184f1 --- /dev/null +++ b/src/xml/qsvgdevice.cpp @@ -0,0 +1,1591 @@ +/**************************************************************************** +** +** Implementation of the QSvgDevice class +** +** Copyright (C) 2000-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the xml module of the Qt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free Qt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing requirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at sales@trolltech.com. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.QPL +** included in the packaging of this file. Licensees holding valid Qt +** Commercial licenses may use this file in accordance with the Qt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +*****************************************************************************/ + +#include <private/qsvgdevice_p.h> + +#ifndef QT_NO_SVG + +#include "qpainter.h" +#include "qpaintdevicemetrics.h" +#include "qfile.h" +#include "qmap.h" +#include "qregexp.h" +#include "qvaluelist.h" +#include "qtextstream.h" +#include "qimage.h" +#include "qpixmap.h" + +#include <math.h> + +const double deg2rad = 0.017453292519943295769; // pi/180 +const char piData[] = "version=\"1.0\" standalone=\"no\""; +const char publicId[] = "-//W3C//DTD SVG 20001102//EN"; +const char systemId[] = "http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd"; + +struct QM_EXPORT_SVG ImgElement { + QDomElement element; + QImage image; + Q_DUMMY_COMPARISON_OPERATOR( ImgElement ) +}; + +struct QM_EXPORT_SVG PixElement { + QDomElement element; + QPixmap pixmap; + Q_DUMMY_COMPARISON_OPERATOR( PixElement ) +}; + +struct QSvgDeviceState { + int textx, texty; // current text position + int textalign; // text alignment + Q_DUMMY_COMPARISON_OPERATOR( QSvgDeviceState ) +}; + +typedef QValueList<ImgElement> ImageList; +typedef QValueList<PixElement> PixmapList; +typedef QValueList<QSvgDeviceState> StateList; + +class QSvgDevicePrivate { +public: + ImageList images; + PixmapList pixmaps; + StateList stack; + int currentClip; + + uint justRestored : 1; + + QMap<QString, QRegion> clipPathTable; +}; + +enum ElementType { + InvalidElement = 0, + AnchorElement, + CircleElement, + ClipElement, + CommentElement, + DescElement, + EllipseElement, + GroupElement, + ImageElement, + LineElement, + PolylineElement, + PolygonElement, + PathElement, + RectElement, + SvgElement, + TextElement, + TitleElement, + TSpanElement +}; + +typedef QMap<QString,ElementType> QSvgTypeMap; +static QSvgTypeMap *qSvgTypeMap=0; // element types +static QMap<QString,QString> *qSvgColMap=0; // recognized color keyword names + +/*! + \class QSvgDevice qsvgdevice.h + \brief The QSvgDevice class provides a paint device for SVG vector graphics. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">Qt Enterprise Edition</a>. +\endif + + \ingroup xml-tools + \module XML + \internal + + SVG is an XML vector graphics format. This class supports the + loading and saving of SVG files with load() and save(), and the + rendering of an SVG onto a QPainter using play(). Use toString() + to put the SVG into a string. + + \sa QPaintDevice QPainter +*/ + +/*! + Creates a QSvgDevice object. +*/ + +QSvgDevice::QSvgDevice() + : QPaintDevice( QInternal::ExternalDevice ), + pt( 0 ) +{ + d = new QSvgDevicePrivate; + d->currentClip = 0; + d->justRestored = FALSE; +} + +/*! + Destroys the QSvgDevice object and frees the resources it used. +*/ + +QSvgDevice::~QSvgDevice() +{ + delete qSvgTypeMap; qSvgTypeMap = 0; // static + delete qSvgColMap; qSvgColMap = 0; + delete d; +} + +/*! + Loads and parses a SVG from \a dev into the device. Returns TRUE + on success (i.e. loaded and parsed without error); otherwise + returns FALSE. +*/ + +bool QSvgDevice::load( QIODevice *dev ) +{ + return doc.setContent( dev ); +} + +/*! + Renders (replays) the SVG on the \a painter and returns TRUE if + successful (i.e. it is a valid SVG); otherwise returns FALSE. +*/ + +bool QSvgDevice::play( QPainter *painter ) +{ + if ( !painter ) { +#if defined(QT_CHECK_RANGE) + Q_ASSERT( painter ); +#endif + return FALSE; + } + pt = painter; + pt->setPen( Qt::NoPen ); // SVG default pen and brush + pt->setBrush( Qt::black ); + if ( doc.isNull() ) { + qWarning( "QSvgDevice::play: No SVG data set." ); + return FALSE; + } + + QDomNode svg = doc.namedItem( "svg" ); + if ( svg.isNull() || !svg.isElement() ) { + qWarning( "QSvgDevice::play: Couldn't find any svg element." ); + return FALSE; + } + + // force transform to be activated in case our sequences + // are replayed later with a transformed painter + painter->setWorldXForm( TRUE ); + + QDomNamedNodeMap attr = svg.attributes(); + int x = lenToInt( attr, "x" ); + int y = lenToInt( attr, "y" ); + brect.setX( x ); + brect.setY( y ); + QString wstr = attr.contains( "width" ) + ? attr.namedItem( "width" ).nodeValue() : QString( "100%" ); + QString hstr = attr.contains( "height" ) + ? attr.namedItem( "height" ).nodeValue() : QString( "100%" ); + double width = parseLen( wstr, 0, TRUE ); + double height = parseLen( hstr, 0, FALSE ); + // SVG doesn't respect x and y. But we want a proper bounding rect. + brect.setWidth( int(width) - x ); + brect.setHeight( int(height) - y ); + painter->setClipRect( brect, QPainter::CoordPainter ); + + if ( attr.contains( "viewBox" ) ) { + QRegExp re( QString::fromLatin1("\\s*(\\S+)\\s*,?\\s*(\\S+)\\s*,?" + "\\s*(\\S+)\\s*,?\\s*(\\S+)\\s*") ); + if ( re.search( attr.namedItem( "viewBox" ).nodeValue() ) < 0 ) { + qWarning( "QSvgDevice::play: Invalid viewBox attribute."); + return FALSE; + } else { + double x = re.cap( 1 ).toDouble(); + double y = re.cap( 2 ).toDouble(); + double w = re.cap( 3 ).toDouble(); + double h = re.cap( 4 ).toDouble(); + if ( w < 0 || h < 0 ) { + qWarning( "QSvgDevice::play: Invalid viewBox dimension."); + return FALSE; + } else if ( w == 0 || h == 0 ) { + return TRUE; + } + painter->scale( width/w, height/h ); + painter->translate( -x, -y ); + } + } + + const struct ElementTable { + const char *name; + ElementType type; + } etab[] = { + { "a", AnchorElement }, + { "#comment", CommentElement }, + { "circle", CircleElement }, + { "clipPath", ClipElement }, + { "desc", DescElement }, + { "ellipse", EllipseElement }, + { "g", GroupElement }, + { "image", ImageElement }, + { "line", LineElement }, + { "polyline", PolylineElement }, + { "polygon", PolygonElement }, + { "path", PathElement }, + { "rect", RectElement }, + { "svg", SvgElement }, + { "text", TextElement }, + { "tspan", TSpanElement }, + { "title", TitleElement }, + { 0, InvalidElement } + }; + // initialize only once + if ( !qSvgTypeMap ) { + qSvgTypeMap = new QSvgTypeMap; + const ElementTable *t = etab; + while ( t->name ) { + qSvgTypeMap->insert( t->name, t->type ); + t++; + } + } + + // initial state + QSvgDeviceState st; + st.textx = st.texty = 0; + st.textalign = Qt::AlignLeft; + d->stack.append(st); + curr = &d->stack.last(); + // 'play' all elements recursively starting with 'svg' as root + bool b = play( svg ); + d->stack.remove( d->stack.begin() ); + return b; +} + +/*! + Returns the SVG as a single string of XML. +*/ +QString QSvgDevice::toString() const +{ + if ( doc.isNull() ) + return QString(); + + return doc.toString(); +} + +/*! + Saves the SVG to \a fileName. +*/ + +bool QSvgDevice::save( const QString &fileName ) +{ + // guess svg id from fileName + QString svgName = fileName.endsWith( ".svg" ) ? + fileName.left( fileName.length()-4 ) : fileName; + + // now we have the info about name and dimensions available + QDomElement root = doc.documentElement(); + root.setAttribute( "id", svgName ); + // the standard doesn't take respect x and y. But we want a + // proper bounding rect. We make width and height bigger when + // writing out and subtract x and y when reading in. + root.setAttribute( "x", brect.x() ); + root.setAttribute( "y", brect.y() ); + root.setAttribute( "width", brect.width() + brect.x() ); + root.setAttribute( "height", brect.height() + brect.y() ); + + // ... and know how to name any image files to be written out + int icount = 0; + ImageList::Iterator iit = d->images.begin(); + for ( ; iit != d->images.end(); ++iit ) { + QString href = QString( "%1_%2.png" ).arg( svgName ).arg( icount ); + (*iit).image.save( href, "PNG" ); + (*iit).element.setAttribute( "xlink:href", href ); + icount++; + } + PixmapList::Iterator pit = d->pixmaps.begin(); + for ( ; pit != d->pixmaps.end(); ++pit ) { + QString href = QString( "%1_%2.png" ).arg( svgName ).arg( icount ); + (*pit).pixmap.save( href, "PNG" ); + (*pit).element.setAttribute( "xlink:href", href ); + icount++; + } + + QFile f( fileName ); + if ( !f.open ( IO_WriteOnly ) ) + return FALSE; + QTextStream s( &f ); + s.setEncoding( QTextStream::UnicodeUTF8 ); + s << doc; + + return TRUE; +} + +/*! + \overload + + \a dev is the device to use for saving. +*/ + +bool QSvgDevice::save( QIODevice *dev ) +{ +#if defined(CHECK_RANGE) + if ( !d->images.isEmpty() || !d->pixmaps.isEmpty() ) + qWarning( "QSvgDevice::save: skipping external images" ); +#endif + + QTextStream s( dev ); + s.setEncoding( QTextStream::UnicodeUTF8 ); + s << doc; + + return TRUE; +} + +/*! + \fn QRect QSvgDevice::boundingRect() const + + Returns the bounding rectangle of the SVG. +*/ + +/*! + Sets the bounding rectangle of the SVG to rectangle \a r. +*/ + +void QSvgDevice::setBoundingRect( const QRect &r ) +{ + brect = r; +} + +/*! + Internal implementation of the virtual QPaintDevice::metric() + function. + + \warning Use the QPaintDeviceMetrics class instead. + + A QSvgDevice has the following hard coded values: dpi=72, + numcolors=16777216 and depth=24. \a m is the metric to get. +*/ + +int QSvgDevice::metric( int m ) const +{ + int val; + switch ( m ) { + case QPaintDeviceMetrics::PdmWidth: + val = brect.width(); + break; + case QPaintDeviceMetrics::PdmHeight: + val = brect.height(); + break; + case QPaintDeviceMetrics::PdmWidthMM: + val = int(25.4/72.0*brect.width()); + break; + case QPaintDeviceMetrics::PdmHeightMM: + val = int(25.4/72.0*brect.height()); + break; + case QPaintDeviceMetrics::PdmDpiX: + val = 72; + break; + case QPaintDeviceMetrics::PdmDpiY: + val = 72; + break; + case QPaintDeviceMetrics::PdmNumColors: + val = 16777216; + break; + case QPaintDeviceMetrics::PdmDepth: + val = 24; + break; + default: + val = 0; +#if defined(QT_CHECK_RANGE) + qWarning( "QSvgDevice::metric: Invalid metric command" ); +#endif + } + return val; +} + +/*! + \internal + + Records painter commands and stores them in the QDomDocument doc. +*/ + +bool QSvgDevice::cmd ( int c, QPainter *painter, QPDevCmdParam *p ) +{ + pt = painter; + + if ( c == PdcBegin ) { + QDomImplementation domImpl; + QDomDocumentType docType = domImpl.createDocumentType( "svg", + publicId, + systemId ); + doc = domImpl.createDocument( "http://www.w3.org/2000/svg", + "svg", docType ); + doc.insertBefore( doc.createProcessingInstruction( "xml", piData ), + doc.firstChild() ); + current = doc.documentElement(); + d->images.clear(); + d->pixmaps.clear(); + dirtyTransform = dirtyStyle = FALSE; // ### + return TRUE; + } else if ( c == PdcEnd ) { + return TRUE; + } + + QDomElement e; + QString str; + QRect rect; + QPointArray a; + int i, width, height, x, y; + switch ( c ) { + case PdcNOP: + break; + case PdcMoveTo: + curPt = *p[0].point; + break; + case PdcLineTo: + e = doc.createElement( "line" ); + e.setAttribute( "x1", curPt.x() ); + e.setAttribute( "y1", curPt.y() ); + e.setAttribute( "x2", p[0].point->x() ); + e.setAttribute( "y2", p[0].point->y() ); + break; + case PdcDrawPoint: + case PdcDrawLine: + e = doc.createElement( "line" ); + e.setAttribute( "x1", p[0].point->x() ); + e.setAttribute( "y1", p[0].point->y() ); + i = ( c == PdcDrawLine ) ? 1 : 0; + e.setAttribute( "x2", p[i].point->x() ); + e.setAttribute( "y2", p[i].point->y() ); + break; + case PdcDrawRect: + case PdcDrawRoundRect: + e = doc.createElement( "rect" ); + x = p[0].rect->x(); + y = p[0].rect->y(); + width = p[0].rect->width(); + height = p[0].rect->height(); + if ( width < 0 ) { + width = -width; + x -= width - 1; + } + if ( height < 0 ) { + height = -height; + y -= height - 1; + } + e.setAttribute( "x", x ); + e.setAttribute( "y", y ); + e.setAttribute( "width", width ); + e.setAttribute( "height", height ); + if ( c == PdcDrawRoundRect ) { + e.setAttribute( "rx", (p[1].ival*p[0].rect->width())/200 ); + e.setAttribute( "ry", (p[2].ival*p[0].rect->height())/200 ); + } + break; + case PdcDrawEllipse: + rect = *p[0].rect; + if ( rect.width() == rect.height() ) { + e = doc.createElement( "circle" ); + double cx = rect.x() + (rect.width() / 2.0); + double cy = rect.y() + (rect.height() / 2.0); + e.setAttribute( "cx", cx ); + e.setAttribute( "cy", cy ); + e.setAttribute( "r", cx - rect.x() ); + } else { + e = doc.createElement( "ellipse" ); + double cx = rect.x() + (rect.width() / 2.0); + double cy = rect.y() + (rect.height() / 2.0); + e.setAttribute( "cx", cx ); + e.setAttribute( "cy", cy ); + e.setAttribute( "rx", cx - rect.x() ); + e.setAttribute( "ry", cy - rect.y() ); + } + break; + case PdcDrawArc: + case PdcDrawPie: + case PdcDrawChord: { + rect = *p[0].rect; + double a = (double)p[1].ival / 16.0 * deg2rad; + double al = (double)p[2].ival / 16.0 * deg2rad; + double rx = rect.width() / 2.0; + double ry = rect.height() / 2.0; + double x0 = (double)rect.x() + rx; + double y0 = (double)rect.y() + ry; + double x1 = x0 + rx*cos(a); + double y1 = y0 - ry*sin(a); + double x2 = x0 + rx*cos(a+al); + double y2 = y0 - ry*sin(a+al); + int large = QABS( al ) > ( 180.0 * deg2rad ) ? 1 : 0; + int sweep = al < 0.0 ? 1 : 0; + if ( c == PdcDrawPie ) + str = QString( "M %1 %2 L %3 %4 " ).arg( x0 ).arg( y0 ) + .arg( x1 ).arg( y1 ); + else + str = QString( "M %1 %2 " ).arg( x1 ).arg( y1 ); + str += QString( "A %1 %2 %3 %4 %5 %6 %7" ) + .arg( rx ).arg( ry ).arg( a/deg2rad ). arg( large ).arg( sweep ) + .arg( x2 ).arg( y2 ); + if ( c != PdcDrawArc ) + str += "z"; + e = doc.createElement( "path" ); + e.setAttribute( "d", str ); + } + break; + case PdcDrawLineSegments: + { + a = *p[0].ptarr; + uint end = a.size() / 2; + for (uint i = 0; i < end; i++) { + e = doc.createElement( "line" ); + e.setAttribute( "x1", a[int(2*i)].x() ); + e.setAttribute( "y1", a[int(2*i)].y() ); + e.setAttribute( "x2", a[int(2*i+1)].x() ); + e.setAttribute( "y2", a[int(2*i+1)].y() ); + if ( i < end - 1 ) // The last one will be done at the end + appendChild( e, c ); + } + } + break; + case PdcDrawPolyline: + case PdcDrawPolygon: + { + a = *p[0].ptarr; + e = doc.createElement( ( c == PdcDrawPolyline ) ? + "polyline" : "polygon" ); + for (uint i = 0; i < a.size(); i++) { + QString tmp; + tmp.sprintf( "%d %d ", a[ (int)i ].x(), a[ (int)i ].y() ); + str += tmp; + } + e.setAttribute( "points", str.stripWhiteSpace() ); + } + break; +#ifndef QT_NO_BEZIER + case PdcDrawCubicBezier: + a = *p[0].ptarr; + e = doc.createElement( "path" ); + str.sprintf( "M %d %d C %d %d %d %d %d %d", a[0].x(), a[0].y(), + a[1].x(), a[1].y(), a[2].x(), a[2].y(), + a[3].x(), a[3].y() ); + e.setAttribute( "d", str ); + break; +#endif + case PdcDrawText2: + e = doc.createElement( "text" ); + if ( p[0].point->x() ) + e.setAttribute( "x", p[0].point->x() ); + if ( p[0].point->y() ) + e.setAttribute( "y", p[0].point->y() ); + e.appendChild( doc.createTextNode( *p[1].str ) ); + break; + case PdcDrawText2Formatted: { + e = doc.createElement( "text" ); + const QRect *r = p[0].rect; + int tf = p[1].ival; + int x, y; + // horizontal text alignment + if ( ( tf & Qt::AlignHCenter ) != 0 ) { + x = r->x() + r->width() / 2; + e.setAttribute( "text-anchor", "middle" ); + } else if ( ( tf & Qt::AlignRight ) != 0 ) { + x = r->right(); + e.setAttribute( "text-anchor", "end" ); + } else { + x = r->x(); + } + // vertical text alignment + if ( ( tf & Qt::AlignVCenter ) != 0 ) + y = r->y() + ( r->height() + painter->fontMetrics().ascent() ) / 2; + else if ( ( tf & Qt::AlignBottom ) != 0 ) + y = r->bottom(); + else + y = r->y() + painter->fontMetrics().ascent(); + if ( x ) + e.setAttribute( "x", x ); + if ( y ) + e.setAttribute( "y", y ); + e.appendChild( doc.createTextNode( *p[2].str ) ); + } + break; + case PdcDrawPixmap: + case PdcDrawImage: + e = doc.createElement( "image" ); + e.setAttribute( "x", p[0].rect->x() ); + e.setAttribute( "y", p[0].rect->y() ); + e.setAttribute( "width", p[0].rect->width() ); + e.setAttribute( "height", p[0].rect->height() ); + if ( c == PdcDrawImage ) { + ImgElement ie; + ie.element = e; + ie.image = *p[1].image; + d->images.append( ie ); + } else { + PixElement pe; + pe.element = e; + pe.pixmap = *p[1].pixmap; + d->pixmaps.append( pe ); + } + // saving to disk and setting the xlink:href attribute will be + // done later in save() once we now the svg document name. + break; + case PdcSave: + e = doc.createElement( "g" ); + break; + case PdcRestore: + current = current.parentNode(); + dirtyTransform = !pt->worldMatrix().isIdentity(); + d->justRestored = TRUE; + // ### reset dirty flags + break; + case PdcSetBkColor: + case PdcSetBkMode: + case PdcSetROP: + case PdcSetBrushOrigin: + case PdcSetFont: + case PdcSetPen: + case PdcSetBrush: + dirtyStyle = TRUE; + break; + case PdcSetTabStops: + // ### + break; + case PdcSetTabArray: + // ### + break; + case PdcSetVXform: + case PdcSetWindow: + case PdcSetViewport: + case PdcSetWXform: + case PdcSetWMatrix: + case PdcSaveWMatrix: + case PdcRestoreWMatrix: + dirtyTransform = TRUE; + break; + case PdcSetClip: + // ### + break; + case PdcSetClipRegion: + { + // We skip the clip after restore, since restoring the clip is done automatically by + // the viewer as part of the tree structure. It doesn't hurt to write the region + // out, but it doubles the number of clipregions defined in the final svg. + if (d->justRestored) { + d->justRestored = FALSE; + return TRUE; + } + + QMemArray<QRect> rects = p[0].rgn->rects(); + if (rects.count() == 0) + return TRUE; + d->currentClip++; + e = doc.createElement( "clipPath" ); + e.setAttribute( "id", QString("clip%1").arg(d->currentClip) ); + for (int i=0; i<(int)rects.count(); ++i) { + QDomElement ce = doc.createElement("rect"); + ce.setAttribute( "x", rects.at(i).x() ); + ce.setAttribute( "y", rects.at(i).y() ); + ce.setAttribute( "width", rects.at(i).width() ); + ce.setAttribute( "height", rects.at(i).height() ); + e.appendChild(ce); + } + break; + } + default: +#if defined(CHECK_RANGE) + qWarning( "QSVGDevice::cmd: Invalid command %d", c ); +#endif + break; + } + + appendChild( e, c ); + + return TRUE; +} + +/*! + \internal + + Appends the child and applys any style and transformation. + +*/ + +void QSvgDevice::appendChild( QDomElement &e, int c ) +{ + if ( !e.isNull() ) { + current.appendChild( e ); + if ( c == PdcSave ) + current = e; + // ### optimize application of attributes utilizing <g> + if ( c == PdcSetClipRegion ) { + QDomElement ne; + ne = doc.createElement( "g" ); + ne.setAttribute( "style", QString("clip-path:url(#clip%1)").arg(d->currentClip) ); + current.appendChild( ne ); + current = ne; + } else { + if ( dirtyStyle ) // only reset when entering + applyStyle( &e, c ); // or leaving a <g> tag + if ( dirtyTransform && e.tagName() != "g" ) { + // same as above but not for <g> tags + applyTransform( &e ); + if ( c == PdcSave ) + dirtyTransform = FALSE; + } + } + } +} + + +/*! + \internal + + Push the current drawing attributes on a stack. + + \sa restoreAttributes() +*/ + +void QSvgDevice::saveAttributes() +{ + pt->save(); + // copy old state + QSvgDeviceState st( *curr ); + d->stack.append( st ); + curr = &d->stack.last(); +} + +/*! + \internal + + Pop the current drawing attributes off the stack. + + \sa saveAttributes() +*/ + +void QSvgDevice::restoreAttributes() +{ + pt->restore(); + Q_ASSERT( d->stack.count() > 1 ); + d->stack.remove( d->stack.fromLast() ); + curr = &d->stack.last(); +} + +/*! + \internal + + Evaluate \a node, drawing on \a p. Allows recursive calls. +*/ + +bool QSvgDevice::play( const QDomNode &node ) +{ + saveAttributes(); + + ElementType t = (*qSvgTypeMap)[ node.nodeName() ]; + + if ( t == LineElement && pt->pen().style() == Qt::NoPen ) { + QPen p = pt->pen(); + p.setStyle( Qt::SolidLine ); + pt->setPen( p ); + } + QDomNamedNodeMap attr = node.attributes(); + if ( attr.contains( "style" ) ) + setStyle( attr.namedItem( "style" ).nodeValue() ); + // ### might have to exclude more elements from transform + if ( t != SvgElement && attr.contains( "transform" ) ) + setTransform( attr.namedItem( "transform" ).nodeValue() ); + uint i = attr.length(); + if ( i > 0 ) { + QPen pen = pt->pen(); + QFont font = pt->font(); + while ( i-- ) { + QDomNode n = attr.item( i ); + QString a = n.nodeName(); + QString val = n.nodeValue().lower().stripWhiteSpace(); + setStyleProperty( a, val, &pen, &font, &curr->textalign ); + } + pt->setPen( pen ); + pt->setFont( font ); + } + + int x1, y1, x2, y2, rx, ry, w, h; + double cx1, cy1, crx, cry; + switch ( t ) { + case CommentElement: + // ignore + break; + case RectElement: + rx = ry = 0; + x1 = lenToInt( attr, "x" ); + y1 = lenToInt( attr, "y" ); + w = lenToInt( attr, "width" ); + h = lenToInt( attr, "height" ); + if ( w == 0 || h == 0 ) // prevent div by zero below + break; + x2 = (int)attr.contains( "rx" ); // tiny abuse of x2 and y2 + y2 = (int)attr.contains( "ry" ); + if ( x2 ) + rx = lenToInt( attr, "rx" ); + if ( y2 ) + ry = lenToInt( attr, "ry" ); + if ( x2 && !y2 ) + ry = rx; + else if ( !x2 && y2 ) + rx = ry; + rx = int(200.0*double(rx)/double(w)); + ry = int(200.0*double(ry)/double(h)); + pt->drawRoundRect( x1, y1, w, h, rx, ry ); + break; + case CircleElement: + cx1 = lenToDouble( attr, "cx" ) + 0.5; + cy1 = lenToDouble( attr, "cy" ) + 0.5; + crx = lenToDouble( attr, "r" ); + pt->drawEllipse( (int)(cx1-crx), (int)(cy1-crx), (int)(2*crx), (int)(2*crx) ); + break; + case EllipseElement: + cx1 = lenToDouble( attr, "cx" ) + 0.5; + cy1 = lenToDouble( attr, "cy" ) + 0.5; + crx = lenToDouble( attr, "rx" ); + cry = lenToDouble( attr, "ry" ); + pt->drawEllipse( (int)(cx1-crx), (int)(cy1-cry), (int)(2*crx), (int)(2*cry) ); + break; + case LineElement: + { + x1 = lenToInt( attr, "x1" ); + x2 = lenToInt( attr, "x2" ); + y1 = lenToInt( attr, "y1" ); + y2 = lenToInt( attr, "y2" ); + QPen p = pt->pen(); + w = p.width(); + p.setWidth( (unsigned int)(w * (QABS(pt->worldMatrix().m11()) + QABS(pt->worldMatrix().m22())) / 2) ); + pt->setPen( p ); + pt->drawLine( x1, y1, x2, y2 ); + p.setWidth( w ); + pt->setPen( p ); + } + break; + case PolylineElement: + case PolygonElement: + { + QString pts = attr.namedItem( "points" ).nodeValue(); + pts = pts.simplifyWhiteSpace(); + QStringList sl = QStringList::split( QRegExp( QString::fromLatin1("[ ,]") ), pts ); + QPointArray ptarr( (uint)sl.count() / 2); + for ( int i = 0; i < (int)sl.count() / 2; i++ ) { + double dx = sl[2*i].toDouble(); + double dy = sl[2*i+1].toDouble(); + ptarr.setPoint( i, int(dx), int(dy) ); + } + if ( t == PolylineElement ) { + if ( pt->brush().style() != Qt::NoBrush ) { + QPen pn = pt->pen(); + pt->setPen( Qt::NoPen ); + pt->drawPolygon( ptarr ); + pt->setPen( pn ); + } + pt->drawPolyline( ptarr ); // ### closes when filled. bug ? + } else { + pt->drawPolygon( ptarr ); + } + } + break; + case SvgElement: + case GroupElement: + case AnchorElement: + { + QDomNode child = node.firstChild(); + while ( !child.isNull() ) { + play( child ); + child = child.nextSibling(); + } + } + break; + case PathElement: + drawPath( attr.namedItem( "d" ).nodeValue() ); + break; + case TSpanElement: + case TextElement: + { + if ( attr.contains( "x" ) ) + curr->textx = lenToInt( attr, "x" ); + if ( attr.contains( "y" ) ) + curr->texty = lenToInt( attr, "y" ); + if ( t == TSpanElement ) { + curr->textx += lenToInt( attr, "dx" ); + curr->texty += lenToInt( attr, "dy" ); + } + // backup old colors + QPen pn = pt->pen(); + QColor pcolor = pn.color(); + QColor bcolor = pt->brush().color(); + QDomNode c = node.firstChild(); + while ( !c.isNull() ) { + if ( c.isText() ) { + // we have pen and brush reversed for text drawing + pn.setColor( bcolor ); + pt->setPen( pn ); + QString text = c.toText().nodeValue(); + text = text.simplifyWhiteSpace(); // ### 'preserve' + w = pt->fontMetrics().width( text ); + if ( curr->textalign == Qt::AlignHCenter ) + curr->textx -= w / 2; + else if ( curr->textalign == Qt::AlignRight ) + curr->textx -= w; + pt->drawText( curr->textx, curr->texty, text ); + // restore pen + pn.setColor( pcolor ); + pt->setPen( pn ); + curr->textx += w; + } else if ( c.isElement() && + c.toElement().tagName() == "tspan" ) { + play( c ); + + } + c = c.nextSibling(); + } + if ( t == TSpanElement ) { + // move current text position in parent text element + StateList::Iterator it = --d->stack.fromLast(); + (*it).textx = curr->textx; + (*it).texty = curr->texty; + } + } + break; + case ImageElement: + { + x1 = lenToInt( attr, "x" ); + y1 = lenToInt( attr, "y" ); + w = lenToInt( attr, "width" ); + h = lenToInt( attr, "height" ); + QString href = attr.namedItem( "xlink:href" ).nodeValue(); + // ### catch references to embedded .svg files + QPixmap pix; + if ( !pix.load( href ) ) { + qWarning( "QSvgDevice::play: Couldn't load image %s", href.latin1() ); + break; + } + pt->drawPixmap( QRect( x1, y1, w, h ), pix ); + } + break; + case DescElement: + case TitleElement: + // ignored for now + break; + case ClipElement: + { + QDomNode child = node.firstChild(); + QRegion region; + while (!child.isNull()) { + QDomNamedNodeMap childAttr = child.attributes(); + if ( child.nodeName() == "rect" ) { + QRect r; + r.setX(lenToInt( childAttr, "x" )); + r.setY(lenToInt( childAttr, "y" )); + r.setWidth(lenToInt( childAttr, "width" )); + r.setHeight(lenToInt( childAttr, "height" )); + region |= r; + } else if ( child.nodeName() == "ellipse" ) { + QRect r; + int x = lenToInt( childAttr, "cx" ); + int y = lenToInt( childAttr, "cy" ); + int width = lenToInt( childAttr, "rx" ); + int height = lenToInt( childAttr, "ry" ); + r.setX( x - width ); + r.setY( y - height ); + r.setWidth( width * 2 ); + r.setHeight( height * 2 ); + QRegion rgn( r, QRegion::Ellipse ); + region |= rgn; + } + child = child.nextSibling(); + } + // Store the region in a named map so that it can be used when the + // group node is entered. + QString idString = attr.namedItem("id").nodeValue(); + if (!idString.isEmpty()) + d->clipPathTable[idString] = region; + break; + } + case InvalidElement: + qWarning( "QSvgDevice::play: unknown element type %s", + node.nodeName().latin1() ); + break; + }; + + restoreAttributes(); + + return TRUE; +} + +/*! + \internal + + Parses a CSS2-compatible color specification. Either a keyword or + a numerical RGB specification like #ff00ff or rgb(255,0,50%). +*/ + +QColor QSvgDevice::parseColor( const QString &col ) +{ + static const struct ColorTable { + const char *name; + const char *rgb; + } coltab[] = { + { "black", "#000000" }, + { "silver", "#c0c0c0" }, + { "gray", "#808080" }, + { "white", "#ffffff" }, + { "maroon", "#800000" }, + { "red", "#ff0000" }, + { "purple", "#800080" }, + { "fuchsia", "#ff00ff" }, + { "green", "#008000" }, + { "lime", "#00ff00" }, + { "olive", "#808000" }, + { "yellow", "#ffff00" }, + { "navy", "#000080" }, + { "blue", "#0000ff" }, + { "teal", "#008080" }, + { "aqua", "#00ffff" }, + // ### the latest spec has more + { 0, 0 } + }; + + // initialize color map on first use + if ( !qSvgColMap ) { + qSvgColMap = new QMap<QString, QString>; + const struct ColorTable *t = coltab; + while ( t->name ) { + qSvgColMap->insert( t->name, t->rgb ); + t++; + } + } + + // a keyword ? + if ( qSvgColMap->contains ( col ) ) + return QColor( (*qSvgColMap)[ col ] ); + // in rgb(r,g,b) form ? + QString c = col; + c.replace( QRegExp( QString::fromLatin1("\\s*") ), "" ); + QRegExp reg( QString::fromLatin1("^rgb\\((\\d+)(%?),(\\d+)(%?),(\\d+)(%?)\\)$") ); + if ( reg.search( c ) >= 0 ) { + int comp[3]; + for ( int i = 0; i < 3; i++ ) { + comp[ i ] = reg.cap( 2*i+1 ).toInt(); + if ( !reg.cap( 2*i+2 ).isEmpty() ) // percentage ? + comp[ i ] = int((double(255*comp[ i ])/100.0)); + } + return QColor( comp[ 0 ], comp[ 1 ], comp[ 2 ] ); + } + + // check for predefined Qt color objects, #RRGGBB and #RGB + return QColor( col ); +} + +/*! + \internal + + Parse a <length> datatype consisting of a number followed by an + optional unit specifier. Can be used for type <coordinate> as + well. For relative units the value of \a horiz will determine + whether the horizontal or vertical dimension will be used. +*/ + +double QSvgDevice::parseLen( const QString &str, bool *ok, bool horiz ) const +{ + QRegExp reg( QString::fromLatin1("([+-]?\\d*\\.*\\d*[Ee]?[+-]?\\d*)(em|ex|px|%|pt|pc|cm|mm|in|)$") ); + if ( reg.search( str ) == -1 ) { + qWarning( "QSvgDevice::parseLen: couldn't parse %s ", str.latin1() ); + if ( ok ) + *ok = FALSE; + return 0.0; + } + + double dbl = reg.cap( 1 ).toDouble(); + QString u = reg.cap( 2 ); + if ( !u.isEmpty() && u != "px" ) { + QPaintDeviceMetrics m( pt->device() ); + if ( u == "em" ) { + QFontInfo fi( pt->font() ); + dbl *= fi.pixelSize(); + } else if ( u == "ex" ) { + QFontInfo fi( pt->font() ); + dbl *= 0.5 * fi.pixelSize(); + } else if ( u == "%" ) + dbl *= (horiz ? pt->window().width() : pt->window().height())/100.0; + else if ( u == "cm" ) + dbl *= m.logicalDpiX() / 2.54; + else if ( u == "mm" ) + dbl *= m.logicalDpiX() / 25.4; + else if ( u == "in" ) + dbl *= m.logicalDpiX(); + else if ( u == "pt" ) + dbl *= m.logicalDpiX() / 72.0; + else if ( u == "pc" ) + dbl *= m.logicalDpiX() / 6.0; + else + qWarning( "QSvgDevice::parseLen: Unknown unit %s", u.latin1() ); + } + if ( ok ) + *ok = TRUE; + return dbl; +} + +/*! + \internal + + Returns the length specified in attribute \a attr in \a map. If + the specified attribute doesn't exist or can't be parsed \a def is + returned. +*/ + +int QSvgDevice::lenToInt( const QDomNamedNodeMap &map, const QString &attr, + int def ) const +{ + if ( map.contains( attr ) ) { + bool ok; + double dbl = parseLen( map.namedItem( attr ).nodeValue(), &ok ); + if ( ok ) + return qRound( dbl ); + } + return def; +} + +double QSvgDevice::lenToDouble( const QDomNamedNodeMap &map, const QString &attr, + int def ) const +{ + if ( map.contains( attr ) ) { + bool ok; + double d = parseLen( map.namedItem( attr ).nodeValue(), &ok ); + if ( ok ) + return d; + } + return def; +} + +void QSvgDevice::setStyleProperty( const QString &prop, const QString &val, + QPen *pen, QFont *font, int *talign ) +{ + if ( prop == "stroke" ) { + if ( val == "none" ) { + pen->setStyle( Qt::NoPen ); + } else { + pen->setColor( parseColor( val )); + if ( pen->style() == Qt::NoPen ) + pen->setStyle( Qt::SolidLine ); + if ( pen->width() == 0 ) + pen->setWidth( 1 ); + } + } else if ( prop == "stroke-width" ) { + double w = parseLen( val ); + if ( w > 0.0001 ) + pen->setWidth( int(w) ); + else + pen->setStyle( Qt::NoPen ); + } else if ( prop == "stroke-linecap" ) { + if ( val == "butt" ) + pen->setCapStyle( Qt::FlatCap ); + else if ( val == "round" ) + pen->setCapStyle( Qt::RoundCap ); + else if ( val == "square" ) + pen->setCapStyle( Qt::SquareCap ); + } else if ( prop == "stroke-linejoin" ) { + if ( val == "miter" ) + pen->setJoinStyle( Qt::MiterJoin ); + else if ( val == "round" ) + pen->setJoinStyle( Qt::RoundJoin ); + else if ( val == "bevel" ) + pen->setJoinStyle( Qt::BevelJoin ); + } else if ( prop == "stroke-dasharray" ) { + if ( val == "18,6" ) + pen->setStyle( Qt::DashLine ); + else if ( val == "3" ) + pen->setStyle( Qt::DotLine ); + else if ( val == "9,6,3,6" ) + pen->setStyle( Qt::DashDotLine ); + else if ( val == "9,3,3" ) + pen->setStyle( Qt::DashDotDotLine ); + else + pen->setStyle( Qt::DotLine ); + } else if ( prop == "fill" ) { + if ( val == "none" ) + pt->setBrush( Qt::NoBrush ); + else + pt->setBrush( parseColor( val ) ); + } else if ( prop == "font-size" ) { + font->setPointSizeFloat( float(parseLen( val )) ); + } else if ( prop == "font-family" ) { + font->setFamily( val ); + } else if ( prop == "font-style" ) { + if ( val == "normal" ) + font->setItalic( FALSE ); + else if ( val == "italic" ) + font->setItalic( TRUE ); + else + qWarning( "QSvgDevice::setStyleProperty: unhandled " + "font-style: %s", val.latin1() ); + } else if ( prop == "font-weight" ) { + int w = font->weight(); + // no exact equivalents so we have to "round" a little bit + if ( val == "100" || val == "200" ) + w = QFont::Light; + if ( val == "300" || val == "400" || val == "normal" ) + w = QFont::Normal; + else if ( val == "500" || val == "600" ) + w = QFont::DemiBold; + else if ( val == "700" || val == "bold" || val == "800" ) + w = QFont::Bold; + else if ( val == "900" ) + w = QFont::Black; + font->setWeight( w ); + } else if ( prop == "text-anchor" ) { + if ( val == "middle" ) + *talign = Qt::AlignHCenter; + else if ( val == "end" ) + *talign = Qt::AlignRight; + else + *talign = Qt::AlignLeft; + } else if ( prop == "clip-path" ) { + if (val.startsWith("url(#")) { + QString clipName = val.mid(5, val.length() - 6); + if (!clipName.isEmpty()) { + QRegion clipRegion = d->clipPathTable[clipName]; + if (!clipRegion.isEmpty()) + pt->setClipRegion(pt->clipRegion() & clipRegion, QPainter::CoordPainter); + } + } + } +} + +void QSvgDevice::setStyle( const QString &s ) +{ + QStringList rules = QStringList::split( QChar(';'), s ); + + QPen pen = pt->pen(); + QFont font = pt->font(); + + QStringList::ConstIterator it = rules.begin(); + for ( ; it != rules.end(); it++ ) { + int col = (*it).find( ':' ); + if ( col > 0 ) { + QString prop = (*it).left( col ).simplifyWhiteSpace(); + QString val = (*it).right( (*it).length() - col - 1 ); + val = val.lower().stripWhiteSpace(); + setStyleProperty( prop, val, &pen, &font, &curr->textalign ); + } + } + + pt->setPen( pen ); + pt->setFont( font ); +} + +void QSvgDevice::setTransform( const QString &tr ) +{ + QString t = tr.simplifyWhiteSpace(); + + QRegExp reg( QString::fromLatin1("\\s*([\\w]+)\\s*\\(([^\\(]*)\\)") ); + int index = 0; + while ( (index = reg.search(t, index)) >= 0 ) { + QString command = reg.cap( 1 ); + QString params = reg.cap( 2 ); + QStringList plist = QStringList::split( QRegExp(QString::fromLatin1("[,\\s]")), params ); + if ( command == "translate" ) { + double tx = 0, ty = 0; + tx = plist[0].toDouble(); + if ( plist.count() >= 2 ) + ty = plist[1].toDouble(); + pt->translate( tx, ty ); + } else if ( command == "rotate" ) { + pt->rotate( plist[0].toDouble() ); + } else if ( command == "scale" ) { + double sx, sy; + sx = sy = plist[0].toDouble(); + if ( plist.count() >= 2 ) + sy = plist[1].toDouble(); + pt->scale( sx, sy ); + } else if ( command == "matrix" && plist.count() >= 6 ) { + double m[ 6 ]; + for (int i = 0; i < 6; i++) + m[ i ] = plist[ i ].toDouble(); + QWMatrix wm( m[ 0 ], m[ 1 ], m[ 2 ], + m[ 3 ], m[ 4 ], m[ 5 ] ); + pt->setWorldMatrix( wm, TRUE ); + } else if ( command == "skewX" ) { + pt->shear( 0.0, tan( plist[0].toDouble() * deg2rad ) ); + } else if ( command == "skewY" ) { + pt->shear( tan( plist[0].toDouble() * deg2rad ), 0.0 ); + } + + // move on to next command + index += reg.matchedLength(); + } +} + +void QSvgDevice::drawPath( const QString &data ) +{ + double x0 = 0, y0 = 0; // starting point + double x = 0, y = 0; // current point + double controlX = 0, controlY = 0; // last control point for curves + QPointArray path( 500 ); // resulting path + QValueList<int> subIndex; // start indices for subpaths + QPointArray quad( 4 ), bezier; // for curve calculations + int pcount = 0; // current point array index + uint idx = 0; // current data position + int mode = 0, lastMode = 0; // parser state + bool relative = FALSE; // e.g. 'h' vs. 'H' + QString commands( "MZLHVCSQTA" ); // recognized commands + int cmdArgs[] = { 2, 0, 2, 1, 1, 6, 4, 4, 2, 7 }; // no of arguments + QRegExp reg( QString::fromLatin1("\\s*,?\\s*([+-]?\\d*\\.?\\d*)") ); // floating point + + subIndex.append( 0 ); + // detect next command + while ( idx < data.length() ) { + QChar ch = data[ (int)idx++ ]; + if ( ch.isSpace() ) + continue; + QChar chUp = ch.upper(); + int cmd = commands.find( chUp ); + if ( cmd >= 0 ) { + // switch to new command mode + mode = cmd; + relative = ( ch != chUp ); // e.g. 'm' instead of 'M' + } else { + if ( mode && !ch.isLetter() ) { + cmd = mode; // continue in previous mode + idx--; + } else { + qWarning( "QSvgDevice::drawPath: Unknown command" ); + return; + } + } + + // read in the required number of arguments + const int maxArgs = 7; + double arg[ maxArgs ]; + int numArgs = cmdArgs[ cmd ]; + for ( int i = 0; i < numArgs; i++ ) { + int pos = reg.search( data, idx ); + if ( pos == -1 ) { + qWarning( "QSvgDevice::drawPath: Error parsing arguments" ); + return; + } + arg[ i ] = reg.cap( 1 ).toDouble(); + idx = pos + reg.matchedLength(); + }; + + // process command + double offsetX = relative ? x : 0; // correction offsets + double offsetY = relative ? y : 0; // for relative commands + switch ( mode ) { + case 0: // 'M' move to + if ( x != x0 || y != y0 ) + path.setPoint( pcount++, int(x0), int(y0) ); + x = x0 = arg[ 0 ] + offsetX; + y = y0 = arg[ 1 ] + offsetY; + subIndex.append( pcount ); + path.setPoint( pcount++, int(x0), int(y0) ); + mode = 2; // -> 'L' + break; + case 1: // 'Z' close path + path.setPoint( pcount++, int(x0), int(y0) ); + x = x0; + y = y0; + mode = 0; + break; + case 2: // 'L' line to + x = arg[ 0 ] + offsetX; + y = arg[ 1 ] + offsetY; + path.setPoint( pcount++, int(x), int(y) ); + break; + case 3: // 'H' horizontal line + x = arg[ 0 ] + offsetX; + path.setPoint( pcount++, int(x), int(y) ); + break; + case 4: // 'V' vertical line + y = arg[ 0 ] + offsetY; + path.setPoint( pcount++, int(x), int(y) ); + break; +#ifndef QT_NO_BEZIER + case 5: // 'C' cubic bezier curveto + case 6: // 'S' smooth shorthand + case 7: // 'Q' quadratic bezier curves + case 8: { // 'T' smooth shorthand + quad.setPoint( 0, int(x), int(y) ); + // if possible, reflect last control point if smooth shorthand + if ( mode == 6 || mode == 8 ) { // smooth 'S' and 'T' + bool cont = mode == lastMode || + mode == 6 && lastMode == 5 || // 'S' and 'C' + mode == 8 && lastMode == 7; // 'T' and 'Q' + x = cont ? 2*x-controlX : x; + y = cont ? 2*y-controlY : y; + quad.setPoint( 1, int(x), int(y) ); + quad.setPoint( 2, int(x), int(y) ); + } + for ( int j = 0; j < numArgs/2; j++ ) { + x = arg[ 2*j ] + offsetX; + y = arg[ 2*j+1 ] + offsetY; + quad.setPoint( j+4-numArgs/2, int(x), int(y) ); + } + // remember last control point for next shorthand + controlX = quad[ 2 ].x(); + controlY = quad[ 2 ].y(); + // transform quadratic into cubic Bezier + if ( mode == 7 || mode == 8 ) { // cubic 'Q' and 'T' + int x31 = quad[0].x()+int(2.0*(quad[2].x()-quad[0].x())/3.0); + int y31 = quad[0].y()+int(2.0*(quad[2].y()-quad[0].y())/3.0); + int x32 = quad[2].x()+int(2.0*(quad[3].x()-quad[2].x())/3.0); + int y32 = quad[2].y()+int(2.0*(quad[3].y()-quad[2].y())/3.0); + quad.setPoint( 1, x31, y31 ); + quad.setPoint( 2, x32, y32 ); + } + // calculate points on curve + bezier = quad.cubicBezier(); + // reserve more space if needed + if ( bezier.size() > path.size() - pcount ) + path.resize( path.size() - pcount + bezier.size() ); + // copy + for ( int k = 0; k < (int)bezier.size(); k ++ ) + path.setPoint( pcount++, bezier[ k ] ); + break; + } +#endif // QT_NO_BEZIER + case 9: // 'A' elliptical arc curve + // ### just a straight line + x = arg[ 5 ] + offsetX; + y = arg[ 6 ] + offsetY; + path.setPoint( pcount++, int(x), int(y) ); + break; + }; + lastMode = mode; + // array almost full ? expand for next loop + if ( pcount >= (int)path.size() - 4 ) + path.resize( 2 * path.size() ); + } + + subIndex.append( pcount ); // dummy marking the end + if ( pt->brush().style() != Qt::NoBrush ) { + // fill the area without stroke first + if ( x != x0 || y != y0 ) + path.setPoint( pcount++, int(x0), int(y0) ); + QPen pen = pt->pen(); + pt->setPen( Qt::NoPen ); + pt->drawPolygon( path, FALSE, 0, pcount ); + pt->setPen( pen ); + } + // draw each subpath stroke seperately + QValueListConstIterator<int> it = subIndex.begin(); + int start = 0; + while ( it != subIndex.fromLast() ) { + int next = *++it; + // ### always joins ends if first and last point coincide. + // ### 'Z' can't have the desired effect + pt->drawPolyline( path, start, next-start ); + start = next; + } +} + +void QSvgDevice::applyStyle( QDomElement *e, int c ) const +{ + // ### do not write every attribute each time + QColor pcol = pt->pen().color(); + QColor bcol = pt->brush().color(); + QString s; + if ( c == PdcDrawText2 || c == PdcDrawText2Formatted ) { + // QPainter has a reversed understanding of pen/stroke vs. + // brush/fill for text + s += QString( "fill:rgb(%1,%2,%3);" ) + .arg( pcol.red() ).arg( pcol.green() ).arg( pcol.blue() ); + s += QString( "stroke-width:0;" ); + QFont f = pt->font(); + QFontInfo fi( f ); + s += QString( "font-size:%1;" ).arg( fi.pointSize() ); + s += QString( "font-style:%1;" ) + .arg( f.italic() ? "italic" : "normal" ); + // not a very scientific distribution + QString fw; + if ( f.weight() <= QFont::Light ) + fw = "100"; + else if ( f.weight() <= QFont::Normal ) + fw = "400"; + else if ( f.weight() <= QFont::DemiBold ) + fw = "600"; + else if ( f.weight() <= QFont::Bold ) + fw = "700"; + else if ( f.weight() <= QFont::Black ) + fw = "800"; + else + fw = "900"; + s += QString( "font-weight:%1;" ).arg( fw ); + s += QString( "font-family:%1;" ).arg( f.family() ); + } else { + s += QString( "stroke:rgb(%1,%2,%3);" ) + .arg( pcol.red() ).arg( pcol.green() ).arg( pcol.blue() ); + double pw = pt->pen().width(); + if ( pw == 0 && pt->pen().style() != Qt::NoPen ) + pw = 0.9; + if ( c == PdcDrawLine ) + pw /= (QABS(pt->worldMatrix().m11()) + QABS(pt->worldMatrix().m22())) / 2.0; + s += QString( "stroke-width:%1;" ).arg( pw ); + if ( pt->pen().style() == Qt::DashLine ) + s+= QString( "stroke-dasharray:18,6;" ); + else if ( pt->pen().style() == Qt::DotLine ) + s+= QString( "stroke-dasharray:3;" ); + else if ( pt->pen().style() == Qt::DashDotLine ) + s+= QString( "stroke-dasharray:9,6,3,6;" ); + else if ( pt->pen().style() == Qt::DashDotDotLine ) + s+= QString( "stroke-dasharray:9,3,3;" ); + if ( pt->brush().style() == Qt::NoBrush || c == PdcDrawPolyline || + c == PdcDrawCubicBezier ) + s += "fill:none;"; // Qt polylines use no brush, neither do Beziers + else + s += QString( "fill:rgb(%1,%2,%3);" ) + .arg( bcol.red() ).arg( bcol.green() ).arg( bcol.blue() ); + } + e->setAttribute( "style", s ); +} + +void QSvgDevice::applyTransform( QDomElement *e ) const +{ + QWMatrix m = pt->worldMatrix(); + + QString s; + bool rot = ( m.m11() != 1.0 || m.m12() != 0.0 || + m.m21() != 0.0 || m.m22() != 1.0 ); + if ( !rot && ( m.dx() != 0.0 || m.dy() != 0.0 ) ) + s = QString( "translate(%1,%2)" ).arg( m.dx() ).arg( m.dy() ); + else if ( rot ) { + if ( m.m12() == 0.0 && m.m21() == 0.0 && + m.dx() == 0.0 && m.dy() == 0.0 ) + s = QString( "scale(%1,%2)" ).arg( m.m11() ).arg( m.m22() ); + else + s = QString( "matrix(%1,%2,%3,%4,%5,%6)" ) + .arg( m.m11() ).arg( m.m12() ) + .arg( m.m21() ).arg( m.m22() ) + .arg( m.dx() ).arg( m.dy() ); + } + else + return; + + e->setAttribute( "transform", s ); +} + +#endif // QT_NO_SVG |