Skip to content

Commit

Permalink
Start restoring SVG export
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Dec 19, 2017
1 parent cbc8570 commit 613b158
Show file tree
Hide file tree
Showing 6 changed files with 386 additions and 1 deletion.
49 changes: 49 additions & 0 deletions python/core/layout/qgslayoutexporter.sip
Expand Up @@ -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
Expand Down
176 changes: 176 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Expand Up @@ -53,6 +53,7 @@
#include "qgsbusyindicatordialog.h"
#include "qgslayoutundostack.h"
#include "qgslayoutpagecollection.h"
#include "ui_qgssvgexportoptions.h"
#include <QShortcut>
#include <QComboBox>
#include <QLineEdit>
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.h
Expand Up @@ -284,6 +284,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void deleteLayout();
void exportToRaster();
void exportToPdf();
void exportToSvg();

private:

Expand Down Expand Up @@ -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;

Expand Down
98 changes: 98 additions & 0 deletions src/core/layout/qgslayoutexporter.cpp
Expand Up @@ -23,6 +23,7 @@
#include "qgslayoutguidecollection.h"
#include <QImageWriter>
#include <QSize>
#include <QSvgGenerator>

#include "gdal.h"
#include "cpl_conv.h"
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit 613b158

Please sign in to comment.