mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Start restoring SVG export
This commit is contained in:
parent
cbc8570ae7
commit
613b1584d1
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 );
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user