104 changes: 104 additions & 0 deletions src/gui/qgssvgannotationitem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/***************************************************************************
qgssvgannotationitem.cpp
------------------------
begin : November, 2012
copyright : (C) 2012 by Marco Hugentobler
email : marco dot hugentobler at sourcepole dot ch
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgssvgannotationitem.h"
#include "qgsproject.h"
#include <QDomDocument>
#include <QDomElement>


QgsSVGAnnotationItem::QgsSVGAnnotationItem( QgsMapCanvas* canvas ): QgsAnnotationItem( canvas )
{

}

QgsSVGAnnotationItem::~QgsSVGAnnotationItem()
{

}

void QgsSVGAnnotationItem::writeXML( QDomDocument& doc ) const
{
QDomElement documentElem = doc.documentElement();
if ( documentElem.isNull() )
{
return;
}

QDomElement svgAnnotationElem = doc.createElement( "SVGAnnotationItem" );
svgAnnotationElem.setAttribute( "file", QgsProject::instance()->writePath( mFilePath ) );
_writeXML( doc, svgAnnotationElem );
documentElem.appendChild( svgAnnotationElem );
}

void QgsSVGAnnotationItem::readXML( const QDomDocument& doc, const QDomElement& itemElem )
{
QString filePath = QgsProject::instance()->readPath( itemElem.attribute( "file" ) );
setFilePath( filePath );
QDomElement annotationElem = itemElem.firstChildElement( "AnnotationItem" );
if ( !annotationElem.isNull() )
{
_readXML( doc, annotationElem );
}
}

void QgsSVGAnnotationItem::paint( QPainter* painter )
{
if( !painter )
{
return;
}

drawFrame( painter );
if ( mMapPositionFixed )
{
drawMarkerSymbol( painter );
}

//keep width/height ratio of svg
QRect viewBox = mSvgRenderer.viewBox();
if( viewBox.isValid() )
{
double widthRatio = mFrameSize.width() / viewBox.width();
double heightRatio = mFrameSize.height() / viewBox.height();
double renderWidth = 0;
double renderHeight = 0;
if( widthRatio <= heightRatio )
{
renderWidth = mFrameSize.width();
renderHeight = viewBox.height() * mFrameSize.width() / viewBox.width() ;
}
else
{
renderHeight = mFrameSize.height();
renderWidth = viewBox.width() * mFrameSize.height() /viewBox.height() ;
}

mSvgRenderer.render( painter, QRectF( mOffsetFromReferencePoint.x(), mOffsetFromReferencePoint.y(), renderWidth,
renderHeight ) );
}
if ( isSelected() )
{
drawSelectionBoxes( painter );
}
}

void QgsSVGAnnotationItem::setFilePath( const QString& file )
{
mFilePath = file;
mSvgRenderer.load( mFilePath );
}
44 changes: 44 additions & 0 deletions src/gui/qgssvgannotationitem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/***************************************************************************
qgssvgannotationitem.h
------------------------
begin : November, 2012
copyright : (C) 2012 by Marco Hugentobler
email : marco dot hugentobler at sourcepole dot ch
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSSVGANNOTATIONITEM_H
#define QGSSVGANNOTATIONITEM_H

#include "qgsannotationitem.h"
#include <QSvgRenderer>

class QgsSVGAnnotationItem: public QgsAnnotationItem
{
public:

QgsSVGAnnotationItem( QgsMapCanvas* canvas );
~QgsSVGAnnotationItem();

void writeXML( QDomDocument& doc ) const;
void readXML( const QDomDocument& doc, const QDomElement& itemElem );

void paint( QPainter* painter );

void setFilePath( const QString& file );
QString filePath() const { return mFilePath; }

private:
QSvgRenderer mSvgRenderer;
QString mFilePath;
};

#endif // QGSSVGANNOTATIONITEM_H
6 changes: 6 additions & 0 deletions src/mapserver/qgsconfigparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ class QgsConfigParser
/**Return feature info in format SIA2045?*/
virtual bool featureInfoFormatSIA2045() const { return false; }

/**Possibility to draw specific items on a WMS image (e.g. annotation items from the QGIS project file)
@param dpi resolution of the output image
@param width width of output image
@param height height of output image*/
virtual void drawOverlays( QPainter* p, int dpi, int width, int height ) const { Q_UNUSED( p ); Q_UNUSED( dpi ); Q_UNUSED( width ); Q_UNUSED( height ); }

protected:
/**Parser to forward not resolved requests (e.g. SLD parser based on user request might have a fallback parser with admin configuration)*/
QgsConfigParser* mFallbackParser;
Expand Down
146 changes: 145 additions & 1 deletion src/mapserver/qgsprojectparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "qgscomposershape.h"

#include "QFileInfo"
#include <QTextDocument>
#include "QTextStream"


Expand All @@ -48,7 +49,7 @@ QgsProjectParser::QgsProjectParser( QDomDocument* xmlDoc, const QString& filePat
setSelectionColor();
setMaxWidthHeight();

//accelerate search for layers and groups
//accelerate the search for layers, groups and the creation of annotation items
if ( mXMLDoc )
{
QDomNodeList layerNodeList = mXMLDoc->elementsByTagName( "maplayer" );
Expand All @@ -73,6 +74,7 @@ QgsProjectParser::QgsProjectParser( QDomDocument* xmlDoc, const QString& filePat
}

mRestrictedLayers = restrictedLayers();
createTextAnnotationItems();
}
}

Expand All @@ -83,6 +85,7 @@ QgsProjectParser::QgsProjectParser(): mXMLDoc( 0 )
QgsProjectParser::~QgsProjectParser()
{
delete mXMLDoc;
cleanupTextAnnotationItems();
}

int QgsProjectParser::numberOfLayers() const
Expand Down Expand Up @@ -2396,3 +2399,144 @@ void QgsProjectParser::sublayersOfEmbeddedGroup( const QString& projectFilePath,
}
}
}

QgsRectangle QgsProjectParser::projectExtent() const
{
QgsRectangle extent;
if ( !mXMLDoc )
{
return extent;
}

QDomElement qgisElem = mXMLDoc->documentElement();
QDomElement mapCanvasElem = qgisElem.firstChildElement( "mapcanvas" );
if ( mapCanvasElem.isNull() )
{
return extent;
}

QDomElement extentElem = mapCanvasElem.firstChildElement( "extent" );
bool xminOk, xmaxOk, yminOk, ymaxOk;
double xMin = extentElem.firstChildElement( "xmin" ).text().toDouble( &xminOk );
double xMax = extentElem.firstChildElement( "xmax" ).text().toDouble( &xmaxOk );
double yMin = extentElem.firstChildElement( "ymin" ).text().toDouble( &yminOk );
double yMax = extentElem.firstChildElement( "ymax" ).text().toDouble( &ymaxOk );

if ( xminOk && xmaxOk && yminOk && ymaxOk )
{
extent = QgsRectangle( xMin, yMin, xMax, yMax );
}

return extent;
}

void QgsProjectParser::drawOverlays( QPainter* p, int dpi, int width, int height ) const
{
//consider DPI
double scaleFactor = dpi / 88.0; //assume 88 as standard dpi
QgsRectangle prjExtent = projectExtent();

//text annotations
QList< QPair< QTextDocument*, QDomElement > >::const_iterator textIt = mTextAnnotationItems.constBegin();
for ( ; textIt != mTextAnnotationItems.constEnd(); ++textIt )
{
QDomElement annotationElem = textIt->second;
if ( annotationElem.isNull() )
{
continue;
}

int itemWidth = annotationElem.attribute( "frameWidth", "0" ).toInt();
int itemHeight = annotationElem.attribute( "frameHeight", "0" ).toInt();

//calculate item position
double xPos, yPos;
if ( !annotationPosition( annotationElem, scaleFactor, prjExtent, width, height, itemWidth, itemHeight, xPos, yPos ) )
{
continue;
}

drawAnnotationRectangle( p, annotationElem, scaleFactor, xPos, yPos, itemWidth, itemHeight );

//draw annotation contents
p->translate( xPos / scaleFactor, yPos / scaleFactor );
textIt->first->drawContents( p, QRectF( 0, 0, itemWidth, itemHeight ) );
}
}

void QgsProjectParser::createTextAnnotationItems()
{
cleanupTextAnnotationItems();

if ( !mXMLDoc )
{
return;
}

//text annotations
QDomElement qgisElem = mXMLDoc->documentElement();
QDomNodeList textAnnotationList = qgisElem.elementsByTagName( "TextAnnotationItem" );
QDomElement textAnnotationElem;
QDomElement annotationElem;
for ( int i = 0; i < textAnnotationList.size(); ++i )
{
textAnnotationElem = textAnnotationList.at( i ).toElement();
annotationElem = textAnnotationElem.firstChildElement( "AnnotationItem" );
if ( !annotationElem.isNull() && annotationElem.attribute( "mapPositionFixed" ) != "1" )
{
QTextDocument* textDoc = new QTextDocument();
textDoc->setHtml( textAnnotationElem.attribute( "document" ) );
mTextAnnotationItems.push_back( qMakePair( textDoc, annotationElem ) );
}
}
}

void QgsProjectParser::cleanupTextAnnotationItems()
{
QList< QPair< QTextDocument*, QDomElement > >::const_iterator it = mTextAnnotationItems.constBegin();
for ( ; it != mTextAnnotationItems.constEnd(); ++it )
{
delete it->first;
}
mTextAnnotationItems.clear();
}

bool QgsProjectParser::annotationPosition( const QDomElement& elem, double scaleFactor, const QgsRectangle& projectExtent, int width, int height,
int itemWidth, int itemHeight, double& xPos, double& yPos )
{
if ( projectExtent.isEmpty() )
{
return false;
}

double itemMapPosX = elem.attribute( "mapPosX" ).toDouble();
double itemCanvasPosX = elem.attribute( "canvasPosX" ).toDouble();
int maxCanvasX = itemCanvasPosX / ( itemMapPosX - projectExtent.xMinimum() ) * projectExtent.width();
xPos = width - ( maxCanvasX - itemCanvasPosX ) * scaleFactor - itemWidth / 2.0 + 100;

double itemMapPosY = elem.attribute( "mapPosY" ).toDouble();
double itemCanvasPosY = elem.attribute( "canvasPosY" ).toDouble();
int maxCanvasY = itemCanvasPosY / ( projectExtent.yMaximum() - itemMapPosY ) * projectExtent.height();
yPos = height - ( maxCanvasY - itemCanvasPosY ) * scaleFactor - itemHeight + 100;
return true;
}

void QgsProjectParser::drawAnnotationRectangle( QPainter* p, const QDomElement& elem, double scaleFactor, double xPos, double yPos, int itemWidth, int itemHeight )
{
if ( !p )
{
return;
}

QColor backgroundColor( elem.attribute( "frameBackgroundColor", "#000000" ) );
backgroundColor.setAlpha( elem.attribute( "frameBackgroundColorAlpha", "255" ).toInt() );
p->setBrush( QBrush( backgroundColor ) );
QPen framePen( QColor( elem.attribute( "frameColor", "#000000" ) ) );
framePen.setWidth( elem.attribute( "frameBorderWidth", "1" ).toInt() );
p->setPen( framePen );

p->save();
p->scale( scaleFactor, scaleFactor );
p->drawRect( QRectF( xPos / scaleFactor, yPos / scaleFactor, itemWidth, itemHeight ) );
p->restore();
}
30 changes: 30 additions & 0 deletions src/mapserver/qgsprojectparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <QList>
#include <QPair>

class QTextDocument;

//Information about relationship between groups and layers
//key: group name (or null strings for single layers without groups)
//value: containter with layer ids contained in the group
Expand Down Expand Up @@ -123,6 +125,9 @@ class QgsProjectParser: public QgsConfigParser
/**Return feature info in format SIA2045?*/
bool featureInfoFormatSIA2045() const;

/**Draw text annotation items from the QGIS projectfile*/
void drawOverlays( QPainter* p, int dpi, int width, int height ) const;

private:

//forbidden
Expand All @@ -144,6 +149,8 @@ class QgsProjectParser: public QgsConfigParser
QHash< QString, QDomElement > mProjectLayerElementsByName;
/**Names of layers and groups which should not be published*/
QSet<QString> mRestrictedLayers;
/**Watermark text items*/
QList< QPair< QTextDocument*, QDomElement > > mTextAnnotationItems;

/**Creates a maplayer object from <maplayer> element. The layer cash owns the maplayer, so don't delete it
@return the maplayer or 0 in case of error*/
Expand Down Expand Up @@ -206,6 +213,29 @@ class QgsProjectParser: public QgsConfigParser
QSet<QString> restrictedLayers() const;
/**Adds sublayers of an embedded group to layer set*/
static void sublayersOfEmbeddedGroup( const QString& projectFilePath, const QString& groupName, QSet<QString>& layerSet );
/**Returns visible extent from the project file (or an empty rectangle in case of error)*/
QgsRectangle projectExtent() const;

void createTextAnnotationItems();
void cleanupTextAnnotationItems();
/**Calculates annotation position to provide the same distance to the lower right corner as in the QGIS project file
@param width output image pixel width
@param height output image pixel height
@param itemWidth item width in pixels in the QGIS project (screen pixels)
@param itemHeight item height in pixels in the QGIS project (screen pixels)
@param xPos out: x-coordinate of the item in the output image
@param yPos out: y-coordinate of the item in the output image*/
static bool annotationPosition( const QDomElement& elem, double scaleFactor, const QgsRectangle& projectExtent, int width, int height,
int itemWidth, int itemHeight, double& xPos, double& yPos );

/**Draws background rectangle and frame for an annotation
@param elem <Annotation> xml element
@param scaleFactor dpi related scale factor
@param xPos x-position of the item
@param yPos y-position of the item
@param itemWidth item width in pixels in the QGIS project (screen pixels)
@param itemHeight item height in pixels in the QGIS project (screen pixels)*/
static void drawAnnotationRectangle( QPainter* p, const QDomElement& elem, double scaleFactor, double xPos, double yPos, int itemWidth, int itemHeight );
};

#endif // QGSPROJECTPARSER_H
8 changes: 8 additions & 0 deletions src/mapserver/qgssldparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,14 @@ bool QgsSLDParser::featureInfoFormatSIA2045() const
return false;
}

void QgsSLDParser::drawOverlays( QPainter* p, int dpi, int width, int height ) const
{
if ( mFallbackParser )
{
mFallbackParser->drawOverlays( p, dpi, width, height );
}
}

#ifdef DIAGRAMSERVER
int QgsSLDParser::overlaysFromUserStyle( const QDomElement& userStyleElement, QgsVectorLayer* vec ) const
{
Expand Down
3 changes: 3 additions & 0 deletions src/mapserver/qgssldparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class QgsSLDParser: public QgsConfigParser
/**Return feature info in format SIA2045?*/
bool featureInfoFormatSIA2045() const;

/**Forward to fallback parser*/
void drawOverlays( QPainter* p, int dpi, int width, int height ) const;

private:
/**Don't use the default constructor*/
QgsSLDParser();
Expand Down
6 changes: 6 additions & 0 deletions src/mapserver/qgswmsserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ QByteArray* QgsWMSServer::getPrint( const QString& formatString )
else if ( formatString.compare( "png", Qt::CaseInsensitive ) == 0 || formatString.compare( "jpg", Qt::CaseInsensitive ) == 0 )
{
QImage image = c->printPageAsRaster( 0 ); //can only return the first page if pixmap is requested

ba = new QByteArray();
QBuffer buffer( ba );
buffer.open( QIODevice::WriteOnly );
Expand Down Expand Up @@ -601,6 +602,11 @@ QImage* QgsWMSServer::getMap()
QStringList selectedLayerIdList = applyFeatureSelections( layersList );

mMapRenderer->render( &thePainter );
if ( mConfigParser )
{
//draw configuration format specific overlay items
mConfigParser->drawOverlays( &thePainter, theImage->dotsPerMeterX() / 1000.0 * 25.4, theImage->width(), theImage->height() );
}

restoreLayerFilters( originalLayerFilters );
clearFeatureSelections( selectedLayerIdList );
Expand Down
17 changes: 16 additions & 1 deletion src/ui/qgisapp.ui
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<x>0</x>
<y>0</y>
<width>1052</width>
<height>21</height>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="mEditMenu">
Expand Down Expand Up @@ -1810,6 +1810,9 @@ Acts on currently active editable layer</string>
</property>
</action>
<action name="mActionHtmlAnnotation">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionFormAnnotation.png</normaloff>:/images/themes/default/mActionFormAnnotation.png</iconset>
Expand All @@ -1821,6 +1824,18 @@ Acts on currently active editable layer</string>
<string>Html Annotation</string>
</property>
</action>
<action name="mActionSvgAnnotation">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveAsSVG.png</normaloff>:/images/themes/default/mActionSaveAsSVG.png</iconset>
</property>
<property name="text">
<string>SVG annotation</string>
</property>
</action>
</widget>
<resources>
<include location="../../images/images.qrc"/>
Expand Down