Start restoring SVG export

This commit is contained in:
Nyall Dawson 2017-12-18 21:11:43 +10:00
parent cbc8570ae7
commit 613b1584d1
6 changed files with 386 additions and 1 deletions

View File

@ -236,6 +236,55 @@ Layout context flags, which control how the export will be created.
%Docstring
Exports the layout as a PDF to the a ``filePath``, using the specified export ``settings``.
Returns a result code indicating whether the export was successful or an
error was encountered.
%End
struct SvgExportSettings
{
SvgExportSettings();
%Docstring
Constructor for SvgExportSettings
%End
double dpi;
%Docstring
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
%End
bool forceVectorOutput;
%Docstring
Set to true to force vector object exports, even when the resultant appearance will differ
from the layout. If false, some items may be rasterized in order to maintain their
correct appearance in the output.
This option is mutually exclusive with rasterizeWholeImage.
%End
bool cropToContents;
%Docstring
Set to true if image should be cropped so only parts of the layout
containing items are exported.
%End
QgsMargins cropMargins;
%Docstring
Crop to content margins, in layout units. These margins will be added
to the bounds of the exported layout if cropToContents is true.
%End
QgsLayoutContext::Flags flags;
%Docstring
Layout context flags, which control how the export will be created.
%End
};
ExportResult exportToSvg( const QString &filePath, const QgsLayoutExporter::SvgExportSettings &settings );
%Docstring
Exports the layout as an SVG to the a ``filePath``, using the specified export ``settings``.
Returns a result code indicating whether the export was successful or an
error was encountered.
%End

View File

@ -53,6 +53,7 @@
#include "qgsbusyindicatordialog.h"
#include "qgslayoutundostack.h"
#include "qgslayoutpagecollection.h"
#include "ui_qgssvgexportoptions.h"
#include <QShortcut>
#include <QComboBox>
#include <QLineEdit>
@ -182,6 +183,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionExportAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToRaster );
connect( mActionExportAsPDF, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToPdf );
connect( mActionExportAsSVG, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToSvg );
connect( mActionShowGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::showGrid );
connect( mActionSnapGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToGrid );
@ -1671,6 +1673,152 @@ void QgsLayoutDesignerDialog::exportToPdf()
QApplication::restoreOverrideCursor();
}
void QgsLayoutDesignerDialog::exportToSvg()
{
if ( containsWmsLayers() )
{
showWmsPrintingWarning();
}
showSvgExportWarning();
QgsSettings settings;
QString lastUsedFile = settings.value( QStringLiteral( "UI/lastSaveAsSvgFile" ), QStringLiteral( "qgis.svg" ) ).toString();
QFileInfo file( lastUsedFile );
QString outputFileName;
#if 0// TODO
if ( hasAnAtlas && !atlasOnASingleFile &&
( mode == QgsComposer::Atlas || mComposition->atlasMode() == QgsComposition::PreviewAtlas ) )
{
outputFileName = QDir( file.path() ).filePath( atlasMap->currentFilename() ) + ".pdf";
}
else
{
#endif
outputFileName = file.path();
#if 0 //TODO
}
#endif
#ifdef Q_OS_MAC
QgisApp::instance()->activateWindow();
this->raise();
#endif
outputFileName = QFileDialog::getSaveFileName(
this,
tr( "Export to SVG" ),
outputFileName,
tr( "SVG Format" ) + " (*.svg *.SVG)" );
this->activateWindow();
if ( outputFileName.isEmpty() )
{
return;
}
if ( !outputFileName.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
{
outputFileName += QLatin1String( ".svg" );
}
settings.setValue( QStringLiteral( "UI/lastSaveAsSvgFile" ), outputFileName );
bool groupLayers = false;
bool prevSettingLabelsAsOutlines = mLayout->project()->readBoolEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), true );
bool clipToContent = false;
double marginTop = 0.0;
double marginRight = 0.0;
double marginBottom = 0.0;
double marginLeft = 0.0;
bool previousForceVector = mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool();
// open options dialog
QDialog dialog;
Ui::QgsSvgExportOptionsDialog options;
options.setupUi( &dialog );
options.chkTextAsOutline->setChecked( prevSettingLabelsAsOutlines );
options.chkMapLayersAsGroup->setChecked( mLayout->customProperty( QStringLiteral( "svgGroupLayers" ), false ).toBool() );
options.mClipToContentGroupBox->setChecked( mLayout->customProperty( QStringLiteral( "svgCropToContents" ), false ).toBool() );
options.mForceVectorCheckBox->setChecked( previousForceVector );
options.mTopMarginSpinBox->setValue( mLayout->customProperty( QStringLiteral( "svgCropMarginTop" ), 0 ).toInt() );
options.mRightMarginSpinBox->setValue( mLayout->customProperty( QStringLiteral( "svgCropMarginRight" ), 0 ).toInt() );
options.mBottomMarginSpinBox->setValue( mLayout->customProperty( QStringLiteral( "svgCropMarginBottom" ), 0 ).toInt() );
options.mLeftMarginSpinBox->setValue( mLayout->customProperty( QStringLiteral( "svgCropMarginLeft" ), 0 ).toInt() );
if ( dialog.exec() != QDialog::Accepted )
return;
groupLayers = options.chkMapLayersAsGroup->isChecked();
clipToContent = options.mClipToContentGroupBox->isChecked();
marginTop = options.mTopMarginSpinBox->value();
marginRight = options.mRightMarginSpinBox->value();
marginBottom = options.mBottomMarginSpinBox->value();
marginLeft = options.mLeftMarginSpinBox->value();
//save dialog settings
mLayout->setCustomProperty( QStringLiteral( "svgGroupLayers" ), groupLayers );
mLayout->setCustomProperty( QStringLiteral( "svgCropToContents" ), clipToContent );
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginTop" ), marginTop );
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginRight" ), marginRight );
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginBottom" ), marginBottom );
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginLeft" ), marginLeft );
//temporarily override label draw outlines setting
mLayout->project()->writeEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), options.chkTextAsOutline->isChecked() );
mView->setPaintingEnabled( false );
QApplication::setOverrideCursor( Qt::BusyCursor );
QgsLayoutExporter::SvgExportSettings svgSettings;
svgSettings.forceVectorOutput = mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool();
svgSettings.cropToContents = clipToContent;
svgSettings.cropMargins = QgsMargins( marginLeft, marginTop, marginRight, marginBottom );
svgSettings.forceVectorOutput = options.mForceVectorCheckBox->isChecked();
// force a refresh, to e.g. update data defined properties, tables, etc
mLayout->refresh();
QFileInfo fi( outputFileName );
QgsLayoutExporter exporter( mLayout );
switch ( exporter.exportToSvg( outputFileName, svgSettings ) )
{
case QgsLayoutExporter::Success:
{
mMessageBar->pushMessage( tr( "Export layout" ),
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fi.path() ).toString(), outputFileName ),
QgsMessageBar::INFO, 0 );
break;
}
case QgsLayoutExporter::FileError:
QMessageBox::warning( this, tr( "Export to SVG" ),
tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( outputFileName ),
QMessageBox::Ok,
QMessageBox::Ok );
break;
case QgsLayoutExporter::PrintError:
QMessageBox::warning( this, tr( "Export to SVG" ),
tr( "Could not create print device." ),
QMessageBox::Ok,
QMessageBox::Ok );
break;
case QgsLayoutExporter::MemoryError:
QMessageBox::warning( this, tr( "Memory Allocation Error" ),
tr( "Exporting the SVG "
"resulted in a memory overflow.\n\n"
"Please try a lower resolution or a smaller paper size." ),
QMessageBox::Ok, QMessageBox::Ok );
break;
}
mView->setPaintingEnabled( true );
mLayout->project()->writeEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), prevSettingLabelsAsOutlines );
QApplication::restoreOverrideCursor();
}
void QgsLayoutDesignerDialog::paste()
{
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
@ -1816,6 +1964,34 @@ void QgsLayoutDesignerDialog::showWmsPrintingWarning()
}
}
void QgsLayoutDesignerDialog::showSvgExportWarning()
{
QgsSettings settings;
bool displaySVGWarning = settings.value( QStringLiteral( "/UI/displaySVGWarning" ), true ).toBool();
if ( displaySVGWarning )
{
QgsMessageViewer m( this );
m.setWindowTitle( tr( "Export as SVG" ) );
m.setCheckBoxText( tr( "Don't show this message again" ) );
m.setCheckBoxState( Qt::Unchecked );
m.setCheckBoxVisible( true );
m.setCheckBoxQgsSettingsLabel( QStringLiteral( "/UI/displaySVGWarning" ) );
m.setMessageAsHtml( tr( "<p>The SVG export function in QGIS has several "
"problems due to bugs and deficiencies in the " )
+ tr( "underlying Qt SVG library. In particular, there are problems "
"with layers not being clipped to the map "
"bounding box.</p>" )
+ tr( "If you require a vector-based output file from "
"QGIS it is suggested that you try exporting "
"to PDF if the SVG output is not "
"satisfactory."
"</p>" ) );
m.exec();
}
}
bool QgsLayoutDesignerDialog::requiresRasterization() const
{
QList< QgsLayoutItem *> items;

View File

@ -284,6 +284,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void deleteLayout();
void exportToRaster();
void exportToPdf();
void exportToSvg();
private:
@ -379,6 +380,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
//! Displays a warning because of possible min/max size in WMS
void showWmsPrintingWarning();
void showSvgExportWarning();
//! True if the layout contains advanced effects, such as blend modes
bool requiresRasterization() const;

View File

@ -23,6 +23,7 @@
#include "qgslayoutguidecollection.h"
#include <QImageWriter>
#include <QSize>
#include <QSvgGenerator>
#include "gdal.h"
#include "cpl_conv.h"
@ -401,6 +402,103 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( const QString &f
return result;
}
QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToSvg( const QString &filePath, const QgsLayoutExporter::SvgExportSettings &s )
{
if ( !mLayout )
return PrintError;
SvgExportSettings settings = s;
if ( settings.dpi <= 0 )
settings.dpi = mLayout->context().dpi();
mErrorFileName.clear();
LayoutContextPreviewSettingRestorer restorer( mLayout );
( void )restorer;
LayoutContextSettingsRestorer contextRestorer( mLayout );
( void )contextRestorer;
mLayout->context().setDpi( settings.dpi );
mLayout->context().setFlag( QgsLayoutContext::FlagForceVectorOutput, settings.forceVectorOutput );
QFileInfo fi( filePath );
PageExportDetails pageDetails;
pageDetails.directory = fi.path();
pageDetails.baseName = fi.baseName();
pageDetails.extension = fi.completeSuffix();
double inchesToLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
for ( int i = 0; i < mLayout->pageCollection()->pageCount(); ++i )
{
if ( !mLayout->pageCollection()->shouldExportPage( i ) )
{
continue;
}
pageDetails.page = i;
QString fileName = generateFileName( pageDetails );
QSvgGenerator generator;
generator.setTitle( mLayout->project()->title() );
generator.setFileName( fileName );
QRectF bounds;
if ( settings.cropToContents )
{
if ( mLayout->pageCollection()->pageCount() == 1 )
{
// single page, so include everything
bounds = mLayout->layoutBounds( true );
}
else
{
// multi page, so just clip to items on current page
bounds = mLayout->pageItemBounds( i, true );
}
bounds = bounds.adjusted( -settings.cropMargins.left(),
-settings.cropMargins.top(),
settings.cropMargins.right(),
settings.cropMargins.bottom() );
}
else
{
QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( i );
bounds = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
}
//width in pixel
int width = ( int )( bounds.width() * settings.dpi / inchesToLayoutUnits );
//height in pixel
int height = ( int )( bounds.height() * settings.dpi / inchesToLayoutUnits );
if ( width == 0 || height == 0 )
{
//invalid size, skip this page
continue;
}
generator.setSize( QSize( width, height ) );
generator.setViewBox( QRect( 0, 0, width, height ) );
generator.setResolution( settings.dpi );
QPainter p;
bool createOk = p.begin( &generator );
if ( !createOk )
{
mErrorFileName = fileName;
return FileError;
}
if ( settings.cropToContents )
renderRegion( &p, bounds );
else
renderPage( &p, i );
p.end();
}
return Success;
}
void QgsLayoutExporter::preparePrintAsPdf( QPrinter &printer, const QString &filePath )
{
printer.setOutputFileName( filePath );

View File

@ -247,6 +247,54 @@ class CORE_EXPORT QgsLayoutExporter
*/
ExportResult exportToPdf( const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings );
//! Contains settings relating to exporting layouts to SVG
struct SvgExportSettings
{
//! Constructor for SvgExportSettings
SvgExportSettings()
: flags( QgsLayoutContext::FlagAntialiasing | QgsLayoutContext::FlagUseAdvancedEffects )
{}
//! Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
double dpi = -1;
/**
* Set to true to force vector object exports, even when the resultant appearance will differ
* from the layout. If false, some items may be rasterized in order to maintain their
* correct appearance in the output.
*
* This option is mutually exclusive with rasterizeWholeImage.
*/
bool forceVectorOutput = false;
/**
* Set to true if image should be cropped so only parts of the layout
* containing items are exported.
*/
bool cropToContents = false;
/**
* Crop to content margins, in layout units. These margins will be added
* to the bounds of the exported layout if cropToContents is true.
*/
QgsMargins cropMargins;
/**
* Layout context flags, which control how the export will be created.
*/
QgsLayoutContext::Flags flags = 0;
};
/**
* Exports the layout as an SVG to the a \a filePath, using the specified export \a settings.
*
* Returns a result code indicating whether the export was successful or an
* error was encountered.
*/
ExportResult exportToSvg( const QString &filePath, const QgsLayoutExporter::SvgExportSettings &settings );
/**
* Returns the file name corresponding to the last error encountered during
* an export.

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>489</width>
<height>282</height>
<height>319</height>
</rect>
</property>
<property name="windowTitle">
@ -46,6 +46,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mForceVectorCheckBox">
<property name="toolTip">
<string>If checked, the layout will always be kept as vector objects when exported to a compatible format, even if the appearance of the resultant file does not match the layouts settings. If unchecked, some elements in the layout may be rasterized in order to keep their appearance intact.</string>
</property>
<property name="text">
<string>Always export as vectors</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -196,6 +206,7 @@
<tabstop>chkMapLayersAsGroup</tabstop>
<tabstop>chkTextAsOutline</tabstop>
<tabstop>mClipToContentGroupBox</tabstop>
<tabstop>mForceVectorCheckBox</tabstop>
<tabstop>mTopMarginSpinBox</tabstop>
<tabstop>mLeftMarginSpinBox</tabstop>
<tabstop>mRightMarginSpinBox</tabstop>