mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
[FEATURE] Save map/canvas as PDF (#4516)
This commit is contained in:
parent
01f12221ad
commit
a88cf7ad31
@ -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.
|
||||
|
@ -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 );
|
||||
@ -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" ) ) );
|
||||
@ -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() );
|
||||
@ -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() )
|
||||
{
|
||||
@ -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 )
|
||||
{
|
||||
|
@ -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; }
|
||||
@ -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
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "qgsdecorationitem.h"
|
||||
#include "qgsextentgroupbox.h"
|
||||
#include "qgsmapsettings.h"
|
||||
#include "qgsmapsettingsutils.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
@ -29,7 +30,7 @@
|
||||
|
||||
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 );
|
||||
@ -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 )
|
||||
@ -152,3 +163,8 @@ bool QgsMapSaveDialog::saveWorldFile() const
|
||||
{
|
||||
return mSaveWorldFile->isChecked();
|
||||
}
|
||||
|
||||
bool QgsMapSaveDialog::saveAsRaster() const
|
||||
{
|
||||
return mSaveAsRaster->isChecked();
|
||||
}
|
||||
|
@ -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;
|
||||
@ -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 );
|
||||
|
@ -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"
|
||||
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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"/>
|
||||
@ -640,7 +641,16 @@
|
||||
<normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save as &Image...</string>
|
||||
<string>Save Map as &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 &PDF...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionNewMapCanvas">
|
||||
|
@ -16,6 +16,23 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="mSaveAsRaster">
|
||||
<property name="text">
|
||||
<string>Rasterize map</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Advanced effects such as blend modes or vector layer transparency cannot be exported as vectors.
|
||||
Rasterizing the map is recommended when such effects are used.</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="mSaveWorldFile">
|
||||
<property name="text">
|
||||
|
Loading…
x
Reference in New Issue
Block a user