Skip to content

Commit a88cf7a

Browse files
authoredMay 8, 2017
[FEATURE] Save map/canvas as PDF (#4516)
1 parent 01f1222 commit a88cf7a

File tree

10 files changed

+230
-42
lines changed

10 files changed

+230
-42
lines changed
 

‎python/core/qgsmapsettingsutils.sip

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ class QgsMapSettingsUtils
2222
%End
2323
public:
2424

25+
static bool containsAdvancedEffects( const QgsMapSettings &mapSettings );
26+
%Docstring
27+
Checks whether any of the layers attached to a map settings object contain advanced effects
28+
\param mapSettings map settings
29+
:rtype: bool
30+
%End
31+
2532
static QString worldFileContent( const QgsMapSettings &mapSettings );
2633
%Docstring
2734
Creates the content of a world file.

‎src/app/qgisapp.cpp

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,7 @@ void QgisApp::createActions()
17061706
connect( mActionSaveProject, &QAction::triggered, this, &QgisApp::fileSave );
17071707
connect( mActionSaveProjectAs, &QAction::triggered, this, &QgisApp::fileSaveAs );
17081708
connect( mActionSaveMapAsImage, &QAction::triggered, this, [ = ] { saveMapAsImage(); } );
1709+
connect( mActionSaveMapAsPdf, &QAction::triggered, this, [ = ] { saveMapAsPdf(); } );
17091710
connect( mActionNewMapCanvas, &QAction::triggered, this, &QgisApp::newMapCanvas );
17101711
connect( mActionNewPrintComposer, &QAction::triggered, this, &QgisApp::newPrintComposer );
17111712
connect( mActionShowComposerManager, &QAction::triggered, this, &QgisApp::showComposerManager );
@@ -2703,6 +2704,7 @@ void QgisApp::setTheme( const QString &themeName )
27032704
mActionNewPrintComposer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewComposer.svg" ) ) );
27042705
mActionShowComposerManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionComposerManager.svg" ) ) );
27052706
mActionSaveMapAsImage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveMapAsImage.svg" ) ) );
2707+
mActionSaveMapAsPdf->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAsPDF.svg" ) ) );
27062708
mActionExit->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileExit.png" ) ) );
27072709
mActionAddOgrLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOgrLayer.svg" ) ) );
27082710
mActionAddRasterLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRasterLayer.svg" ) ) );
@@ -5801,8 +5803,8 @@ void QgisApp::saveMapAsImage()
58015803
if ( !dlg.exec() )
58025804
return;
58035805

5804-
QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
5805-
if ( myFileNameAndFilter.first != QLatin1String( "" ) )
5806+
QPair< QString, QString> fileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
5807+
if ( fileNameAndFilter.first != QLatin1String( "" ) )
58065808
{
58075809
QgsMapSettings ms = QgsMapSettings();
58085810
ms.setDestinationCrs( QgsProject::instance()->crs() );
@@ -5813,7 +5815,7 @@ void QgisApp::saveMapAsImage()
58135815
ms.setRotation( mMapCanvas->rotation() );
58145816
ms.setLayers( mMapCanvas->layers() );
58155817

5816-
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, myFileNameAndFilter.first, myFileNameAndFilter.second );
5818+
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second );
58175819

58185820
if ( dlg.drawAnnotations() )
58195821
{
@@ -5853,6 +5855,117 @@ void QgisApp::saveMapAsImage()
58535855

58545856
} // saveMapAsImage
58555857

5858+
void QgisApp::saveMapAsPdf()
5859+
{
5860+
QList< QgsMapDecoration * > decorations;
5861+
QString activeDecorations;
5862+
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
5863+
{
5864+
if ( decoration->enabled() )
5865+
{
5866+
decorations << decoration;
5867+
if ( activeDecorations.isEmpty() )
5868+
activeDecorations = decoration->name().toLower();
5869+
else
5870+
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
5871+
}
5872+
}
5873+
5874+
QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations, QgsMapSaveDialog::Pdf );
5875+
if ( !dlg.exec() )
5876+
return;
5877+
5878+
QgsSettings settings;
5879+
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
5880+
QString fileName = QFileDialog::getSaveFileName( this, tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" );
5881+
if ( !fileName.isEmpty() )
5882+
{
5883+
QgsMapSettings ms = QgsMapSettings();
5884+
ms.setDestinationCrs( QgsProject::instance()->crs() );
5885+
ms.setExtent( dlg.extent() );
5886+
ms.setOutputSize( dlg.size() );
5887+
ms.setOutputDpi( dlg.dpi() );
5888+
ms.setBackgroundColor( mMapCanvas->canvasColor() );
5889+
ms.setRotation( mMapCanvas->rotation() );
5890+
ms.setLayers( mMapCanvas->layers() );
5891+
5892+
QPrinter *printer = new QPrinter();
5893+
printer->setOutputFileName( fileName );
5894+
printer->setOutputFormat( QPrinter::PdfFormat );
5895+
printer->setOrientation( QPrinter::Portrait );
5896+
printer->setPaperSize( dlg.size(), QPrinter::DevicePixel );
5897+
printer->setPageMargins( 0, 0, 0, 0, QPrinter::DevicePixel );
5898+
5899+
QPainter *p = new QPainter();
5900+
QImage *image = nullptr;
5901+
if ( dlg.saveAsRaster() )
5902+
{
5903+
image = new QImage( dlg.size(), QImage::Format_ARGB32 );
5904+
p->begin( image );
5905+
}
5906+
else
5907+
{
5908+
p->begin( printer );
5909+
}
5910+
5911+
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, p );
5912+
5913+
if ( dlg.drawAnnotations() )
5914+
{
5915+
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
5916+
}
5917+
5918+
if ( dlg.drawDecorations() )
5919+
{
5920+
mapRendererTask->addDecorations( decorations );
5921+
}
5922+
5923+
mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() );
5924+
5925+
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ this, p, image, printer ]
5926+
{
5927+
messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) );
5928+
p->end();
5929+
5930+
if ( image )
5931+
{
5932+
QPainter pp;
5933+
pp.begin( printer );
5934+
QRectF rect( 0, 0, image->width(), image->height() );
5935+
pp.drawImage( rect, *image, rect );
5936+
pp.end();
5937+
}
5938+
5939+
delete p;
5940+
delete image;
5941+
delete printer;
5942+
} );
5943+
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ this, p, image, printer ]( int error )
5944+
{
5945+
switch ( error )
5946+
{
5947+
case QgsMapRendererTask::ImageAllocationFail:
5948+
{
5949+
messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) );
5950+
break;
5951+
}
5952+
case QgsMapRendererTask::ImageSaveFail:
5953+
{
5954+
messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the image to file" ) );
5955+
break;
5956+
}
5957+
}
5958+
5959+
delete p;
5960+
delete image;
5961+
delete printer;
5962+
} );
5963+
5964+
QgsApplication::taskManager()->addTask( mapRendererTask );
5965+
}
5966+
5967+
} // saveMapAsPdf
5968+
58565969
//overloaded version of the above function
58575970
void QgisApp::saveMapAsImage( const QString &imageFileNameQString, QPixmap *theQPixmap )
58585971
{

‎src/app/qgisapp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
391391
QAction *actionSaveProject() { return mActionSaveProject; }
392392
QAction *actionSaveProjectAs() { return mActionSaveProjectAs; }
393393
QAction *actionSaveMapAsImage() { return mActionSaveMapAsImage; }
394+
QAction *actionSaveMapAsPdf() { return mActionSaveMapAsPdf; }
394395
QAction *actionProjectProperties() { return mActionProjectProperties; }
395396
QAction *actionShowComposerManager() { return mActionShowComposerManager; }
396397
QAction *actionNewPrintComposer() { return mActionNewPrintComposer; }
@@ -1080,6 +1081,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
10801081
void runScript( const QString &filePath );
10811082
//! Save the map view as an image - user is prompted for image name using a dialog
10821083
void saveMapAsImage();
1084+
//! Save the map view as a pdf - user is prompted for image name using a dialog
1085+
void saveMapAsPdf();
10831086
//! Open a project
10841087
void fileOpen();
10851088
//! Create a new project

‎src/app/qgsmapsavedialog.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
#include "qgsdecorationitem.h"
2323
#include "qgsextentgroupbox.h"
2424
#include "qgsmapsettings.h"
25+
#include "qgsmapsettingsutils.h"
2526

2627
#include <QCheckBox>
2728
#include <QSpinBox>
2829
#include <QList>
2930

3031
Q_GUI_EXPORT extern int qt_defaultDpiX();
3132

32-
QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations )
33+
QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations, DialogType type )
3334
: QDialog( parent )
3435
{
3536
setupUi( this );
@@ -60,6 +61,16 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co
6061
connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale );
6162

6263
updateOutputSize();
64+
65+
if ( type == QgsMapSaveDialog::Pdf )
66+
{
67+
mSaveWorldFile->setVisible( false );
68+
69+
mSaveAsRaster->setChecked( QgsMapSettingsUtils::containsAdvancedEffects( mapCanvas->mapSettings() ) );
70+
mSaveAsRaster->setVisible( true );
71+
72+
this->setWindowTitle( tr( "Save map as PDF" ) );
73+
}
6374
}
6475

6576
void QgsMapSaveDialog::updateDpi( int dpi )
@@ -152,3 +163,8 @@ bool QgsMapSaveDialog::saveWorldFile() const
152163
{
153164
return mSaveWorldFile->isChecked();
154165
}
166+
167+
bool QgsMapSaveDialog::saveAsRaster() const
168+
{
169+
return mSaveAsRaster->isChecked();
170+
}

‎src/app/qgsmapsavedialog.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
3737

3838
public:
3939

40+
enum DialogType
41+
{
42+
Image = 1, // Image-specific dialog
43+
Pdf // PDF-specific dialog
44+
};
45+
4046
/** Constructor for QgsMapSaveDialog
4147
*/
42-
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString() );
48+
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString(), DialogType type = Image );
4349

4450
//! returns extent rectangle
4551
QgsRectangle extent() const;
@@ -59,6 +65,9 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
5965
//! returns whether a world file will be created
6066
bool saveWorldFile() const;
6167

68+
//! returns whether the map will be rasterized
69+
bool saveAsRaster() const;
70+
6271
private:
6372

6473
void updateDpi( int dpi );

‎src/core/composer/qgscomposermap.cpp

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsmaplayerlistutils.h"
2626
#include "qgsmaplayerstylemanager.h"
2727
#include "qgsmaptopixel.h"
28+
#include "qgsmapsettingsutils.h"
2829
#include "qgspainting.h"
2930
#include "qgsproject.h"
3031
#include "qgsrasterdataprovider.h"
@@ -1071,43 +1072,10 @@ bool QgsComposerMap::containsAdvancedEffects() const
10711072
return true;
10721073
}
10731074

1074-
// check if map contains advanced effects like blend modes, or flattened layers for transparency
10751075

1076-
QgsTextFormat layerFormat;
1077-
Q_FOREACH ( QgsMapLayer *layer, layersToRender() )
1078-
{
1079-
if ( layer )
1080-
{
1081-
if ( layer->blendMode() != QPainter::CompositionMode_SourceOver )
1082-
{
1083-
return true;
1084-
}
1085-
// if vector layer, check labels and feature blend mode
1086-
QgsVectorLayer *currentVectorLayer = qobject_cast<QgsVectorLayer *>( layer );
1087-
if ( currentVectorLayer )
1088-
{
1089-
if ( currentVectorLayer->layerTransparency() != 0 )
1090-
{
1091-
return true;
1092-
}
1093-
if ( currentVectorLayer->featureBlendMode() != QPainter::CompositionMode_SourceOver )
1094-
{
1095-
return true;
1096-
}
1097-
// check label blend modes
1098-
if ( QgsPalLabeling::staticWillUseLayer( currentVectorLayer ) )
1099-
{
1100-
// Check all label blending properties
1101-
1102-
layerFormat.readFromLayer( currentVectorLayer );
1103-
if ( layerFormat.containsAdvancedEffects() )
1104-
return true;
1105-
}
1106-
}
1107-
}
1108-
}
1109-
1110-
return false;
1076+
QgsMapSettings ms;
1077+
ms.setLayers( layersToRender() );
1078+
return QgsMapSettingsUtils::containsAdvancedEffects( ms );
11111079
}
11121080

11131081
void QgsComposerMap::connectUpdateSlot()

‎src/core/qgsmapsettingsutils.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,49 @@
1717

1818
#include "qgsmapsettings.h"
1919
#include "qgsmapsettingsutils.h"
20+
#include "qgspallabeling.h"
21+
#include "qgstextrenderer.h"
22+
#include "qgsvectorlayer.h"
2023

2124
#include <QString>
2225

26+
bool QgsMapSettingsUtils::containsAdvancedEffects( const QgsMapSettings &mapSettings )
27+
{
28+
QgsTextFormat layerFormat;
29+
Q_FOREACH ( QgsMapLayer *layer, mapSettings.layers() )
30+
{
31+
if ( layer )
32+
{
33+
if ( layer->blendMode() != QPainter::CompositionMode_SourceOver )
34+
{
35+
return true;
36+
}
37+
// if vector layer, check labels and feature blend mode
38+
QgsVectorLayer *currentVectorLayer = qobject_cast<QgsVectorLayer *>( layer );
39+
if ( currentVectorLayer )
40+
{
41+
if ( currentVectorLayer->layerTransparency() != 0 )
42+
{
43+
return true;
44+
}
45+
if ( currentVectorLayer->featureBlendMode() != QPainter::CompositionMode_SourceOver )
46+
{
47+
return true;
48+
}
49+
// check label blend modes
50+
if ( QgsPalLabeling::staticWillUseLayer( currentVectorLayer ) )
51+
{
52+
// Check all label blending properties
53+
layerFormat.readFromLayer( currentVectorLayer );
54+
if ( layerFormat.containsAdvancedEffects() )
55+
return true;
56+
}
57+
}
58+
}
59+
}
60+
return false;
61+
}
62+
2363
QString QgsMapSettingsUtils::worldFileContent( const QgsMapSettings &mapSettings )
2464
{
2565
QgsMapSettings ms = mapSettings;

‎src/core/qgsmapsettingsutils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ class CORE_EXPORT QgsMapSettingsUtils
3232

3333
public:
3434

35+
/** Checks whether any of the layers attached to a map settings object contain advanced effects
36+
* \param mapSettings map settings
37+
*/
38+
static bool containsAdvancedEffects( const QgsMapSettings &mapSettings );
39+
3540
/** Creates the content of a world file.
3641
* \param mapSettings map settings
3742
* \note Uses 17 places of precision for all numbers output

‎src/ui/qgisapp.ui

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<addaction name="mActionSaveProject"/>
5151
<addaction name="mActionSaveProjectAs"/>
5252
<addaction name="mActionSaveMapAsImage"/>
53+
<addaction name="mActionSaveMapAsPdf"/>
5354
<addaction name="mActionDxfExport"/>
5455
<addaction name="mActionDwgImport"/>
5556
<addaction name="separator"/>
@@ -640,7 +641,16 @@
640641
<normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset>
641642
</property>
642643
<property name="text">
643-
<string>Save as &amp;Image...</string>
644+
<string>Save Map as &amp;Image...</string>
645+
</property>
646+
</action>
647+
<action name="mActionSaveMapAsPdf">
648+
<property name="icon">
649+
<iconset resource="../../images/images.qrc">
650+
<normaloff>:/images/themes/default/mActionSaveAsPDF.svg</normaloff>:/images/themes/default/mActionSaveAsPDF.svg</iconset>
651+
</property>
652+
<property name="text">
653+
<string>Save Map as &amp;PDF...</string>
644654
</property>
645655
</action>
646656
<action name="mActionNewMapCanvas">

‎src/ui/qgsmapsavedialog.ui

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@
1616
<layout class="QVBoxLayout" name="verticalLayout">
1717
<item>
1818
<layout class="QGridLayout" name="gridLayout">
19+
<item row="8" column="0" colspan="2">
20+
<widget class="QCheckBox" name="mSaveAsRaster">
21+
<property name="text">
22+
<string>Rasterize map</string>
23+
</property>
24+
<property name="toolTip">
25+
<string>Advanced effects such as blend modes or vector layer transparency cannot be exported as vectors.
26+
Rasterizing the map is recommended when such effects are used.</string>
27+
</property>
28+
<property name="checked">
29+
<bool>false</bool>
30+
</property>
31+
<property name="visible">
32+
<bool>false</bool>
33+
</property>
34+
</widget>
35+
</item>
1936
<item row="7" column="0" colspan="2">
2037
<widget class="QCheckBox" name="mSaveWorldFile">
2138
<property name="text">

0 commit comments

Comments
 (0)
Please sign in to comment.