Skip to content

Commit

Permalink
[FEATURE] Save map/canvas as PDF (#4516)
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn committed May 8, 2017
1 parent 01f1222 commit a88cf7a
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 42 deletions.
7 changes: 7 additions & 0 deletions python/core/qgsmapsettingsutils.sip
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ class QgsMapSettingsUtils
%End
public:

static bool containsAdvancedEffects( const QgsMapSettings &mapSettings );
%Docstring
Checks whether any of the layers attached to a map settings object contain advanced effects
\param mapSettings map settings
:rtype: bool
%End

static QString worldFileContent( const QgsMapSettings &mapSettings );
%Docstring
Creates the content of a world file.
Expand Down
119 changes: 116 additions & 3 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,7 @@ void QgisApp::createActions()
connect( mActionSaveProject, &QAction::triggered, this, &QgisApp::fileSave );
connect( mActionSaveProjectAs, &QAction::triggered, this, &QgisApp::fileSaveAs );
connect( mActionSaveMapAsImage, &QAction::triggered, this, [ = ] { saveMapAsImage(); } );
connect( mActionSaveMapAsPdf, &QAction::triggered, this, [ = ] { saveMapAsPdf(); } );
connect( mActionNewMapCanvas, &QAction::triggered, this, &QgisApp::newMapCanvas );
connect( mActionNewPrintComposer, &QAction::triggered, this, &QgisApp::newPrintComposer );
connect( mActionShowComposerManager, &QAction::triggered, this, &QgisApp::showComposerManager );
Expand Down Expand Up @@ -2703,6 +2704,7 @@ void QgisApp::setTheme( const QString &themeName )
mActionNewPrintComposer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewComposer.svg" ) ) );
mActionShowComposerManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionComposerManager.svg" ) ) );
mActionSaveMapAsImage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveMapAsImage.svg" ) ) );
mActionSaveMapAsPdf->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAsPDF.svg" ) ) );
mActionExit->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileExit.png" ) ) );
mActionAddOgrLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOgrLayer.svg" ) ) );
mActionAddRasterLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRasterLayer.svg" ) ) );
Expand Down Expand Up @@ -5801,8 +5803,8 @@ void QgisApp::saveMapAsImage()
if ( !dlg.exec() )
return;

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

QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, myFileNameAndFilter.first, myFileNameAndFilter.second );
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second );

if ( dlg.drawAnnotations() )
{
Expand Down Expand Up @@ -5853,6 +5855,117 @@ void QgisApp::saveMapAsImage()

} // saveMapAsImage

void QgisApp::saveMapAsPdf()
{
QList< QgsMapDecoration * > decorations;
QString activeDecorations;
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
{
if ( decoration->enabled() )
{
decorations << decoration;
if ( activeDecorations.isEmpty() )
activeDecorations = decoration->name().toLower();
else
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
}
}

QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations, QgsMapSaveDialog::Pdf );
if ( !dlg.exec() )
return;

QgsSettings settings;
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
QString fileName = QFileDialog::getSaveFileName( this, tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" );
if ( !fileName.isEmpty() )
{
QgsMapSettings ms = QgsMapSettings();
ms.setDestinationCrs( QgsProject::instance()->crs() );
ms.setExtent( dlg.extent() );
ms.setOutputSize( dlg.size() );
ms.setOutputDpi( dlg.dpi() );
ms.setBackgroundColor( mMapCanvas->canvasColor() );
ms.setRotation( mMapCanvas->rotation() );
ms.setLayers( mMapCanvas->layers() );

QPrinter *printer = new QPrinter();
printer->setOutputFileName( fileName );
printer->setOutputFormat( QPrinter::PdfFormat );
printer->setOrientation( QPrinter::Portrait );
printer->setPaperSize( dlg.size(), QPrinter::DevicePixel );
printer->setPageMargins( 0, 0, 0, 0, QPrinter::DevicePixel );

QPainter *p = new QPainter();
QImage *image = nullptr;
if ( dlg.saveAsRaster() )
{
image = new QImage( dlg.size(), QImage::Format_ARGB32 );
p->begin( image );
}
else
{
p->begin( printer );
}

QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, p );

if ( dlg.drawAnnotations() )
{
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
}

if ( dlg.drawDecorations() )
{
mapRendererTask->addDecorations( decorations );
}

mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() );

connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ this, p, image, printer ]
{
messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) );
p->end();

if ( image )
{
QPainter pp;
pp.begin( printer );
QRectF rect( 0, 0, image->width(), image->height() );
pp.drawImage( rect, *image, rect );
pp.end();
}

delete p;
delete image;
delete printer;
} );
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ this, p, image, printer ]( int error )
{
switch ( error )
{
case QgsMapRendererTask::ImageAllocationFail:
{
messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) );
break;
}
case QgsMapRendererTask::ImageSaveFail:
{
messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the image to file" ) );
break;
}
}

delete p;
delete image;
delete printer;
} );

QgsApplication::taskManager()->addTask( mapRendererTask );
}

} // saveMapAsPdf

//overloaded version of the above function
void QgisApp::saveMapAsImage( const QString &imageFileNameQString, QPixmap *theQPixmap )
{
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QAction *actionSaveProject() { return mActionSaveProject; }
QAction *actionSaveProjectAs() { return mActionSaveProjectAs; }
QAction *actionSaveMapAsImage() { return mActionSaveMapAsImage; }
QAction *actionSaveMapAsPdf() { return mActionSaveMapAsPdf; }
QAction *actionProjectProperties() { return mActionProjectProperties; }
QAction *actionShowComposerManager() { return mActionShowComposerManager; }
QAction *actionNewPrintComposer() { return mActionNewPrintComposer; }
Expand Down Expand Up @@ -1080,6 +1081,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void runScript( const QString &filePath );
//! Save the map view as an image - user is prompted for image name using a dialog
void saveMapAsImage();
//! Save the map view as a pdf - user is prompted for image name using a dialog
void saveMapAsPdf();
//! Open a project
void fileOpen();
//! Create a new project
Expand Down
18 changes: 17 additions & 1 deletion src/app/qgsmapsavedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
#include "qgsdecorationitem.h"
#include "qgsextentgroupbox.h"
#include "qgsmapsettings.h"
#include "qgsmapsettingsutils.h"

#include <QCheckBox>
#include <QSpinBox>
#include <QList>

Q_GUI_EXPORT extern int qt_defaultDpiX();

QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations )
QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations, DialogType type )
: QDialog( parent )
{
setupUi( this );
Expand Down Expand Up @@ -60,6 +61,16 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co
connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale );

updateOutputSize();

if ( type == QgsMapSaveDialog::Pdf )
{
mSaveWorldFile->setVisible( false );

mSaveAsRaster->setChecked( QgsMapSettingsUtils::containsAdvancedEffects( mapCanvas->mapSettings() ) );
mSaveAsRaster->setVisible( true );

this->setWindowTitle( tr( "Save map as PDF" ) );
}
}

void QgsMapSaveDialog::updateDpi( int dpi )
Expand Down Expand Up @@ -152,3 +163,8 @@ bool QgsMapSaveDialog::saveWorldFile() const
{
return mSaveWorldFile->isChecked();
}

bool QgsMapSaveDialog::saveAsRaster() const
{
return mSaveAsRaster->isChecked();
}
11 changes: 10 additions & 1 deletion src/app/qgsmapsavedialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog

public:

enum DialogType
{
Image = 1, // Image-specific dialog
Pdf // PDF-specific dialog
};

/** Constructor for QgsMapSaveDialog
*/
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString() );
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString(), DialogType type = Image );

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

//! returns whether the map will be rasterized
bool saveAsRaster() const;

private:

void updateDpi( int dpi );
Expand Down
40 changes: 4 additions & 36 deletions src/core/composer/qgscomposermap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "qgsmaplayerlistutils.h"
#include "qgsmaplayerstylemanager.h"
#include "qgsmaptopixel.h"
#include "qgsmapsettingsutils.h"
#include "qgspainting.h"
#include "qgsproject.h"
#include "qgsrasterdataprovider.h"
Expand Down Expand Up @@ -1071,43 +1072,10 @@ bool QgsComposerMap::containsAdvancedEffects() const
return true;
}

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

QgsTextFormat layerFormat;
Q_FOREACH ( QgsMapLayer *layer, layersToRender() )
{
if ( layer )
{
if ( layer->blendMode() != QPainter::CompositionMode_SourceOver )
{
return true;
}
// if vector layer, check labels and feature blend mode
QgsVectorLayer *currentVectorLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( currentVectorLayer )
{
if ( currentVectorLayer->layerTransparency() != 0 )
{
return true;
}
if ( currentVectorLayer->featureBlendMode() != QPainter::CompositionMode_SourceOver )
{
return true;
}
// check label blend modes
if ( QgsPalLabeling::staticWillUseLayer( currentVectorLayer ) )
{
// Check all label blending properties

layerFormat.readFromLayer( currentVectorLayer );
if ( layerFormat.containsAdvancedEffects() )
return true;
}
}
}
}

return false;
QgsMapSettings ms;
ms.setLayers( layersToRender() );
return QgsMapSettingsUtils::containsAdvancedEffects( ms );
}

void QgsComposerMap::connectUpdateSlot()
Expand Down
40 changes: 40 additions & 0 deletions src/core/qgsmapsettingsutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,49 @@

#include "qgsmapsettings.h"
#include "qgsmapsettingsutils.h"
#include "qgspallabeling.h"
#include "qgstextrenderer.h"
#include "qgsvectorlayer.h"

#include <QString>

bool QgsMapSettingsUtils::containsAdvancedEffects( const QgsMapSettings &mapSettings )
{
QgsTextFormat layerFormat;
Q_FOREACH ( QgsMapLayer *layer, mapSettings.layers() )
{
if ( layer )
{
if ( layer->blendMode() != QPainter::CompositionMode_SourceOver )
{
return true;
}
// if vector layer, check labels and feature blend mode
QgsVectorLayer *currentVectorLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( currentVectorLayer )
{
if ( currentVectorLayer->layerTransparency() != 0 )
{
return true;
}
if ( currentVectorLayer->featureBlendMode() != QPainter::CompositionMode_SourceOver )
{
return true;
}
// check label blend modes
if ( QgsPalLabeling::staticWillUseLayer( currentVectorLayer ) )
{
// Check all label blending properties
layerFormat.readFromLayer( currentVectorLayer );
if ( layerFormat.containsAdvancedEffects() )
return true;
}
}
}
}
return false;
}

QString QgsMapSettingsUtils::worldFileContent( const QgsMapSettings &mapSettings )
{
QgsMapSettings ms = mapSettings;
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsmapsettingsutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ class CORE_EXPORT QgsMapSettingsUtils

public:

/** Checks whether any of the layers attached to a map settings object contain advanced effects
* \param mapSettings map settings
*/
static bool containsAdvancedEffects( const QgsMapSettings &mapSettings );

/** Creates the content of a world file.
* \param mapSettings map settings
* \note Uses 17 places of precision for all numbers output
Expand Down
12 changes: 11 additions & 1 deletion src/ui/qgisapp.ui
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<addaction name="mActionSaveProject"/>
<addaction name="mActionSaveProjectAs"/>
<addaction name="mActionSaveMapAsImage"/>
<addaction name="mActionSaveMapAsPdf"/>
<addaction name="mActionDxfExport"/>
<addaction name="mActionDwgImport"/>
<addaction name="separator"/>
Expand Down Expand Up @@ -640,7 +641,16 @@
<normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset>
</property>
<property name="text">
<string>Save as &amp;Image...</string>
<string>Save Map as &amp;Image...</string>
</property>
</action>
<action name="mActionSaveMapAsPdf">
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveAsPDF.svg</normaloff>:/images/themes/default/mActionSaveAsPDF.svg</iconset>
</property>
<property name="text">
<string>Save Map as &amp;PDF...</string>
</property>
</action>
<action name="mActionNewMapCanvas">
Expand Down

0 comments on commit a88cf7a

Please sign in to comment.