248 changes: 246 additions & 2 deletions python/core/layout/qgslayoutexporter.sip
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,42 @@ class QgsLayoutExporter
%End
public:

struct PageExportDetails
{
QString directory;
%Docstring
Target folder
%End

QString baseName;
%Docstring
Base part of filename (i.e. file name without extension or '.')
%End

QString extension;
%Docstring
File suffix/extension (without the leading '.')
%End

int page;
%Docstring
Page number, where 0 = first page.
%End
};

QgsLayoutExporter( QgsLayout *layout );
%Docstring
Constructor for QgsLayoutExporter, for the specified ``layout``.
%End

void renderPage( QPainter *painter, int page );
virtual ~QgsLayoutExporter();

QgsLayout *layout() const;
%Docstring
Returns the layout linked to this exporter.
%End

void renderPage( QPainter *painter, int page ) const;
%Docstring
Renders a full page to a destination ``painter``.

Expand All @@ -36,12 +66,226 @@ are 0 based, such that the first page in a layout is page 0.
.. seealso:: :py:func:`renderRect()`
%End

void renderRegion( QPainter *painter, const QRectF &region );
QImage renderPageToImage( int page, QSize imageSize = QSize(), double dpi = 0 ) const;
%Docstring
Renders a full page to an image.

The ``page`` argument specifies the page number to render. Page numbers
are 0 based, such that the first page in a layout is page 0.

The optional ``imageSize`` parameter can specify the target image size, in pixels.
It is the caller's responsibility to ensure that the ratio of the target image size
matches the ratio of the corresponding layout page size.

The ``dpi`` parameter is an optional dpi override. Set to 0 to use the default layout print
resolution. This parameter has no effect if ``imageSize`` is specified.

Returns the rendered image, or a null QImage if the image does not fit into available memory.

.. seealso:: :py:func:`renderPage()`

.. seealso:: :py:func:`renderRegionToImage()`
%End

void renderRegion( QPainter *painter, const QRectF &region ) const;
%Docstring
Renders a ``region`` from the layout to a ``painter``. This method can be used
to render sections of pages rather than full pages.

.. seealso:: :py:func:`renderPage()`

.. seealso:: :py:func:`renderRegionToImage()`
%End

QImage renderRegionToImage( const QRectF &region, QSize imageSize = QSize(), double dpi = 0 ) const;
%Docstring
Renders a ``region`` of the layout to an image. This method can be used to render
sections of pages rather than full pages.

The optional ``imageSize`` parameter can specify the target image size, in pixels.
It is the caller's responsibility to ensure that the ratio of the target image size
matches the ratio of the specified region of the layout.

The ``dpi`` parameter is an optional dpi override. Set to 0 to use the default layout print
resolution. This parameter has no effect if ``imageSize`` is specified.

Returns the rendered image, or a null QImage if the image does not fit into available memory.

.. seealso:: :py:func:`renderRegion()`

.. seealso:: :py:func:`renderPageToImage()`
%End


enum ExportResult
{
Success,
MemoryError,
FileError,
PrintError,
};

struct ImageExportSettings
{
ImageExportSettings();
%Docstring
Constructor for ImageExportSettings
%End

double dpi;
%Docstring
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
%End

QSize imageSize;
%Docstring
Manual size in pixels for output image. If imageSize is not
set then it will be automatically calculated based on the
output dpi and layout size.

If cropToContents is true then imageSize has no effect.

Be careful when specifying manual sizes if pages in the layout
have differing sizes! It's likely not going to give a reasonable
output in this case, and the automatic dpi-based image size should be
used instead.
%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 pixels. These margins will be added
to the bounds of the exported layout if cropToContents is true.
%End

QList< int > pages;
%Docstring
List of specific pages to export, or an empty list to
export all pages.

Page numbers are 0 index based, so the first page in the
layout corresponds to page 0.
%End

bool generateWorldFile;
%Docstring
Set to true to generate an external world file alongside
exported images.
%End

QgsLayoutContext::Flags flags;
%Docstring
Layout context flags, which control how the export will be created.
%End

};

ExportResult exportToImage( const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings );
%Docstring
Exports the layout to the a ``filePath``, using the specified export ``settings``.

If the layout is a multi-page layout, then filenames for each page will automatically
be generated by appending "_1", "_2", etc to the image file's base name.

Returns a result code indicating whether the export was successful or an
error was encountered. If an error code is returned, errorFile() can be called
to determine the filename for the export which encountered the error.
%End

struct PdfExportSettings
{
PdfExportSettings();
%Docstring
Constructor for PdfExportSettings
%End

double dpi;
%Docstring
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
%End

bool rasterizeWholeImage;
%Docstring
Set to true to force whole layout to be rasterized while exporting.

This option is mutually exclusive with forceVectorOutput.
%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

QgsLayoutContext::Flags flags;
%Docstring
Layout context flags, which control how the export will be created.
%End

};

ExportResult exportToPdf( const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings );
%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

QString errorFile() const;
%Docstring
Returns the file name corresponding to the last error encountered during
an export.
%End

bool georeferenceOutput( const QString &file, QgsLayoutItemMap *referenceMap = 0,
const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;
%Docstring
Georeferences a ``file`` (image of PDF) exported from the layout.

The ``referenceMap`` argument specifies a map item to use for georeferencing. If left as None, the
default layout QgsLayout.referenceMap() will be used.

The ``exportRegion`` argument can be set to a valid rectangle to indicate that only part of the layout was
exported.

Similarly, the ``dpi`` can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.

The function will return true if the output was successfully georeferenced.

.. seealso:: :py:func:`computeGeoTransform()`
%End

void computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi = -1 ) const;
%Docstring
Compute world file parameters. Assumes the whole page containing the reference map item
will be exported.

The ``dpi`` argument can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
%End

void computeWorldFileParameters( const QRectF &region, double &a, double &b, double &c, double &d, double &e, double &f, double dpi = -1 ) const;
%Docstring
Computes the world file parameters for a specified ``region`` of the layout.

The ``dpi`` argument can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
%End

protected:

virtual QString generateFileName( const PageExportDetails &details ) const;
%Docstring
Generates the file name for a page during export.

Subclasses can override this method to customise page file naming.
%End

};
Expand Down
29 changes: 24 additions & 5 deletions python/core/layout/qgslayoutitem.sip
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,25 @@ Returns whether the item should be excluded from layout exports and prints.
Sets whether the item should be excluded from composer exports and prints.

.. seealso:: :py:func:`excludeFromExports()`
%End

virtual bool containsAdvancedEffects() const;
%Docstring
Returns true if the item contains contents with blend modes or transparency
effects which can only be reproduced by rastering the item.

Subclasses should ensure that implemented overrides of this method
also check the base class result.

.. seealso:: :py:func:`requiresRasterization()`
%End

virtual bool requiresRasterization() const;
%Docstring
Returns true if the item is drawn in such a way that forces the whole layout
to be rasterized when exporting to vector formats.

.. seealso:: :py:func:`containsAdvancedEffects()`
%End

virtual double estimatedFrameBleed() const;
Expand Down Expand Up @@ -900,6 +919,11 @@ Cancels the current item command and discards it.
.. seealso:: :py:func:`beginCommand()`

.. seealso:: :py:func:`endCommand()`
%End

bool shouldDrawItem() const;
%Docstring
Returns whether the item should be drawn in the current context.
%End

public slots:
Expand Down Expand Up @@ -1141,11 +1165,6 @@ in finalizeRestoreFromXml(), not readPropertiesFromElement().
.. seealso:: :py:func:`writePropertiesToElement()`

.. seealso:: :py:func:`readXml()`
%End

bool shouldDrawItem() const;
%Docstring
Returns whether the item should be drawn in the current context.
%End

QgsLayoutSize applyDataDefinedSize( const QgsLayoutSize &size );
Expand Down
11 changes: 5 additions & 6 deletions python/core/layout/qgslayoutitemmap.sip
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ The caller takes responsibility for deleting the returned object.
virtual void setFrameStrokeWidth( const QgsLayoutMeasurement &width );



double scale() const;
%Docstring
Returns the map scale.
Expand Down Expand Up @@ -280,10 +279,10 @@ Sets preset name for map rendering. See followVisibilityPresetName() for more de
Returns true if the map contains a WMS layer.
%End

bool containsAdvancedEffects() const;
%Docstring
Returns true if the map contains layers with blend modes or flattened layers for vectors
%End
virtual bool requiresRasterization() const;

virtual bool containsAdvancedEffects() const;


void setMapRotation( double rotation );
%Docstring
Expand Down Expand Up @@ -457,7 +456,7 @@ This is calculated using the width of the map item and the width of the
current visible map extent.
%End

QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const;
%Docstring
Return map settings that will be used for drawing of the map.
%End
Expand Down
2 changes: 1 addition & 1 deletion python/core/layout/qgslayoutitempage.sip
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class QgsLayoutItemPage : QgsLayoutItem
UndoPageSymbol,
};

explicit QgsLayoutItemPage( QgsLayout *layout /TransferThis/ );
explicit QgsLayoutItemPage( QgsLayout *layout );
%Docstring
Constructor for QgsLayoutItemPage, with the specified parent ``layout``.
%End
Expand Down
2 changes: 2 additions & 0 deletions python/core/layout/qgslayoutitempicture.sip
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ Forces a recalculation of the picture's frame size

virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );

virtual bool containsAdvancedEffects() const;


signals:
void pictureRotationChanged( double newRotation );
Expand Down
56 changes: 56 additions & 0 deletions python/core/layout/qgslayoutpagecollection.sip
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,23 @@ Returns a list of the page numbers which are visible within the specified
%Docstring
Returns whether a given ``page`` index is empty, ie, it contains no items except for the background
paper item.

.. seealso:: :py:func:`shouldExportPage()`
%End

QList< QgsLayoutItem *> itemsOnPage( int page ) const;
%Docstring
Returns a list of layout items on the specified ``page`` index.
%End


bool shouldExportPage( int page ) const;
%Docstring
Returns whether the specified ``page`` number should be included in exports of the layouts.

.. seealso:: :py:func:`pageIsEmpty()`
%End

void addPage( QgsLayoutItemPage *page /Transfer/ );
%Docstring
Adds a ``page`` to the collection. Ownership of the ``page`` is transferred
Expand Down Expand Up @@ -184,6 +194,24 @@ Ownership is not transferred, and a copy of the symbol is created internally.
Returns the symbol to use for drawing pages in the collection.

.. seealso:: :py:func:`setPageStyleSymbol()`
%End

void beginPageSizeChange();
%Docstring
Should be called before changing any page item sizes, and followed by a call to
endPageSizeChange(). If page size changes are wrapped in these calls, then items
will maintain their same relative position on pages after the page sizes are updated.

.. seealso:: :py:func:`endPageSizeChange()`
%End

void endPageSizeChange();
%Docstring
Should be called after changing any page item sizes, and preceded by a call to
beginPageSizeChange(). If page size changes are wrapped in these calls, then items
will maintain their same relative position on pages after the page sizes are updated.

.. seealso:: :py:func:`beginPageSizeChange()`
%End

void reflow();
Expand All @@ -196,6 +224,24 @@ for page size/orientation change.
%Docstring
Returns the maximum width of pages in the collection. The returned value is
in layout units.

.. seealso:: :py:func:`maximumPageSize()`
%End

QSizeF maximumPageSize() const;
%Docstring
Returns the maximum size of any page in the collection, by area. The returned value
is in layout units.

.. seealso:: :py:func:`maximumPageWidth()`
%End

bool hasUniformPageSizes() const;
%Docstring
Returns true if the layout has uniform page sizes, e.g. all pages are the same size.

This method does not consider differing units as non-uniform sizes, only the actual
physical size of the pages.
%End

int pageNumberForPoint( QPointF point ) const;
Expand Down Expand Up @@ -280,6 +326,16 @@ Returns the space between pages, in layout units.
double pageShadowWidth() const;
%Docstring
Returns the size of the page shadow, in layout units.
%End

void resizeToContents( const QgsMargins &margins, QgsUnitTypes::LayoutUnit marginUnits );
%Docstring
Resizes the layout to a single page which fits the current contents of the layout.

Calling this method resets the number of pages to 1, with the size set to the
minimum size required to fit all existing layout items. Items will also be
repositioned so that the new top-left bounds of the layout is at the point
(marginLeft, marginTop). An optional margin can be specified.
%End

virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
Expand Down
6 changes: 6 additions & 0 deletions python/core/layout/qgslayoututils.sip
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ the a specified ``rotation`` amount.
:return: largest scaled version of the rectangle possible
%End

static QgsLayoutItemPage::Orientation decodePaperOrientation( const QString &string, bool &ok );
%Docstring
Decodes a ``string`` representing a paper orientation and returns the
decoded orientation.
If the string was correctly decoded, ``ok`` will be set to true.
%End

};

Expand Down
5 changes: 5 additions & 0 deletions python/gui/layout/qgslayoutdesignerinterface.sip
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ Returns the layout displayed in the designer.
Returns the layout view utilized by the designer.

.. seealso:: :py:func:`layout()`
%End

virtual QgsMessageBar *messageBar() = 0;
%Docstring
Returns the designer's message bar.
%End

virtual void selectItems( const QList< QgsLayoutItem * > items ) = 0;
Expand Down
3 changes: 3 additions & 0 deletions python/gui/layout/qgslayoutview.sip
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ Returns the delta (in layout coordinates) by which to move items
for the given key ``event``.
%End


public slots:

void zoomFull();
Expand Down Expand Up @@ -557,6 +558,8 @@ item and should have its properties displayed in any designer windows.

virtual void dragEnterEvent( QDragEnterEvent *e );

virtual void paintEvent( QPaintEvent *event );


};

Expand Down
2 changes: 2 additions & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ SET(QGIS_APP_SRCS
layout/qgslayoutdesignerdialog.cpp
layout/qgslayoutguidewidget.cpp
layout/qgslayouthtmlwidget.cpp
layout/qgslayoutimageexportoptionsdialog.cpp
layout/qgslayoutitemslistview.cpp
layout/qgslayoutappmenuprovider.cpp
layout/qgslayoutlabelwidget.cpp
Expand Down Expand Up @@ -403,6 +404,7 @@ SET (QGIS_APP_MOC_HDRS
layout/qgslayoutdesignerdialog.h
layout/qgslayoutguidewidget.h
layout/qgslayouthtmlwidget.h
layout/qgslayoutimageexportoptionsdialog.h
layout/qgslayoutitemslistview.h
layout/qgslayoutlabelwidget.h
layout/qgslayoutlegendwidget.h
Expand Down
1 change: 1 addition & 0 deletions src/app/layout/qgslayoutaddpagesdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "qgssettings.h"
#include "qgslayout.h"
#include "qgslayoutmeasurementconverter.h"
#include "qgslayoutpagecollection.h"

QgsLayoutAddPagesDialog::QgsLayoutAddPagesDialog( QWidget *parent, Qt::WindowFlags flags )
: QDialog( parent, flags )
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout/qgslayoutappmenuprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "qgslayoutitemgroup.h"
#include "qgslayoutdesignerdialog.h"
#include "qgslayout.h"
#include "qgslayoutundostack.h"
#include "qgslayoutpagecollection.h"
#include <QMenu>
#include <QMessageBox>

Expand Down
370 changes: 366 additions & 4 deletions src/app/layout/qgslayoutdesignerdialog.cpp

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class QgsDockWidget;
class QUndoView;
class QTreeView;
class QgsLayoutItemsListView;
class QgsLayoutPropertiesWidget;
class QgsMessageBar;

class QgsAppLayoutDesignerInterface : public QgsLayoutDesignerInterface
{
Expand All @@ -50,6 +52,7 @@ class QgsAppLayoutDesignerInterface : public QgsLayoutDesignerInterface
QgsAppLayoutDesignerInterface( QgsLayoutDesignerDialog *dialog );
QgsLayout *layout() override;
QgsLayoutView *view() override;
QgsMessageBar *messageBar() override;
void selectItems( const QList< QgsLayoutItem * > items ) override;

public slots:
Expand Down Expand Up @@ -113,6 +116,11 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
*/
void selectItems( const QList< QgsLayoutItem * > items );

/**
* Returns the designer's message bar.
*/
QgsMessageBar *messageBar();

public slots:

/**
Expand Down Expand Up @@ -274,6 +282,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void showManager();
void renameLayout();
void deleteLayout();
void exportToRaster();
void exportToPdf();

private:

Expand All @@ -283,6 +293,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner

QgsLayout *mLayout = nullptr;

QgsMessageBar *mMessageBar = nullptr;

QActionGroup *mToolsActionGroup = nullptr;

QgsLayoutView *mView = nullptr;
Expand Down Expand Up @@ -321,6 +333,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
QgsDockWidget *mGuideDock = nullptr;
QgsPanelWidgetStack *mGuideStack = nullptr;

QgsLayoutPropertiesWidget *mLayoutPropertiesWidget = nullptr;

QUndoView *mUndoView = nullptr;
QgsDockWidget *mUndoDock = nullptr;

Expand Down Expand Up @@ -360,6 +374,19 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner

void initializeRegistry();

bool containsWmsLayers() const;

//! Displays a warning because of possible min/max size in WMS
void showWmsPrintingWarning();

//! True if the layout contains advanced effects, such as blend modes
bool requiresRasterization() const;

bool containsAdvancedEffects() const;

//! Displays a warning because of incompatibility between blend modes and QPrinter
void showRasterizationWarning();
void showForceVectorWarning();
};

#endif // QGSLAYOUTDESIGNERDIALOG_H
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout/qgslayoutguidewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "qgslayoutview.h"
#include "qgsdoublespinbox.h"
#include "qgslayoutunitscombobox.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"

QgsLayoutGuideWidget::QgsLayoutGuideWidget( QWidget *parent, QgsLayout *layout, QgsLayoutView *layoutView )
: QgsPanelWidget( parent )
Expand Down
1 change: 1 addition & 0 deletions src/app/layout/qgslayouthtmlwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "qgscodeeditorhtml.h"
#include "qgscodeeditorcss.h"
#include "qgssettings.h"
#include "qgslayoutundostack.h"

#include <QFileDialog>

Expand Down
187 changes: 187 additions & 0 deletions src/app/layout/qgslayoutimageexportoptionsdialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/***************************************************************************
qgslayoutimageexportoptionsdialog.cpp
-------------------------------------
begin : December 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgslayoutimageexportoptionsdialog.h"
#include "qgis.h"
#include "qgssettings.h"
#include "qgsgui.h"

#include <QCheckBox>
#include <QPushButton>

QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *parent, Qt::WindowFlags flags )
: QDialog( parent, flags )
{
setupUi( this );
connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged );
connect( mHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged );
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged );

connect( mClipToContentGroupBox, &QGroupBox::toggled, this, &QgsLayoutImageExportOptionsDialog::clipToContentsToggled );

QgsGui::instance()->enableAutoGeometryRestore( this );
}

void QgsLayoutImageExportOptionsDialog::setResolution( double resolution )
{
mResolutionSpinBox->setValue( resolution );

if ( mImageSize.isValid() )
{
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
if ( mClipToContentGroupBox->isChecked() )
{
mWidthSpinBox->setValue( 0 );
mHeightSpinBox->setValue( 0 );
}
else
{
mWidthSpinBox->setValue( mImageSize.width() * resolution / 25.4 );
mHeightSpinBox->setValue( mImageSize.height() * resolution / 25.4 );
}
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}
}

double QgsLayoutImageExportOptionsDialog::resolution() const
{
return mResolutionSpinBox->value();
}

void QgsLayoutImageExportOptionsDialog::setImageSize( QSizeF size )
{
mImageSize = size;
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
mWidthSpinBox->setValue( size.width() * mResolutionSpinBox->value() / 25.4 );
mHeightSpinBox->setValue( size.height() * mResolutionSpinBox->value() / 25.4 );
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}

int QgsLayoutImageExportOptionsDialog::imageWidth() const
{
return mWidthSpinBox->value();
}

int QgsLayoutImageExportOptionsDialog::imageHeight() const
{
return mHeightSpinBox->value();
}

void QgsLayoutImageExportOptionsDialog::setCropToContents( bool crop )
{
mClipToContentGroupBox->setChecked( crop );
}

bool QgsLayoutImageExportOptionsDialog::cropToContents() const
{
return mClipToContentGroupBox->isChecked();
}

void QgsLayoutImageExportOptionsDialog::setGenerateWorldFile( bool generate )
{
mGenerateWorldFile->setChecked( generate );
}

bool QgsLayoutImageExportOptionsDialog::generateWorldFile() const
{
return mGenerateWorldFile->isChecked();
}

void QgsLayoutImageExportOptionsDialog::setAntialiasing( bool antialias )
{
mAntialiasingCheckBox->setChecked( antialias );
}

bool QgsLayoutImageExportOptionsDialog::antialiasing() const
{
return mAntialiasingCheckBox->isChecked();
}

void QgsLayoutImageExportOptionsDialog::getCropMargins( int &topMargin, int &rightMargin, int &bottomMargin, int &leftMargin ) const
{
topMargin = mTopMarginSpinBox->value();
rightMargin = mRightMarginSpinBox->value();
bottomMargin = mBottomMarginSpinBox->value();
leftMargin = mLeftMarginSpinBox->value();
}

void QgsLayoutImageExportOptionsDialog::setCropMargins( int topMargin, int rightMargin, int bottomMargin, int leftMargin )
{
mTopMarginSpinBox->setValue( topMargin );
mRightMarginSpinBox->setValue( rightMargin );
mBottomMarginSpinBox->setValue( bottomMargin );
mLeftMarginSpinBox->setValue( leftMargin );
}

void QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged( int value )
{
mHeightSpinBox->blockSignals( true );
mResolutionSpinBox->blockSignals( true );
mHeightSpinBox->setValue( mImageSize.height() * value / mImageSize.width() );
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.width() );
mHeightSpinBox->blockSignals( false );
mResolutionSpinBox->blockSignals( false );
}

void QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged( int value )
{
mWidthSpinBox->blockSignals( true );
mResolutionSpinBox->blockSignals( true );
mWidthSpinBox->setValue( mImageSize.width() * value / mImageSize.height() );
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.height() );
mWidthSpinBox->blockSignals( false );
mResolutionSpinBox->blockSignals( false );
}

void QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged( int value )
{
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
if ( mClipToContentGroupBox->isChecked() )
{
mWidthSpinBox->setValue( 0 );
mHeightSpinBox->setValue( 0 );
}
else
{
mWidthSpinBox->setValue( mImageSize.width() * value / 25.4 );
mHeightSpinBox->setValue( mImageSize.height() * value / 25.4 );
}
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}

void QgsLayoutImageExportOptionsDialog::clipToContentsToggled( bool state )
{
mWidthSpinBox->setEnabled( !state );
mHeightSpinBox->setEnabled( !state );

if ( state )
{
whileBlocking( mWidthSpinBox )->setValue( 0 );
whileBlocking( mHeightSpinBox )->setValue( 0 );
}
else
{
whileBlocking( mWidthSpinBox )->setValue( mImageSize.width() * mResolutionSpinBox->value() / 25.4 );
whileBlocking( mHeightSpinBox )->setValue( mImageSize.height() * mResolutionSpinBox->value() / 25.4 );
}
}
144 changes: 144 additions & 0 deletions src/app/layout/qgslayoutimageexportoptionsdialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/***************************************************************************
qgslayoutimageexportoptionsdialog.h
-------------------------------------
begin : December 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSLAYOUTIMAGEEXPORTOPTIONSDIALOG_H
#define QGSLAYOUTIMAGEEXPORTOPTIONSDIALOG_H

#include <QDialog>
#include "ui_qgslayoutimageexportoptions.h"


/**
* A dialog for customising the properties of an exported image file.
* \since QGIS 3.0
*/
class QgsLayoutImageExportOptionsDialog: public QDialog, private Ui::QgsLayoutImageExportOptionsDialog
{
Q_OBJECT

public:

/**
* Constructor for QgsLayoutImageExportOptionsDialog
* \param parent parent widget
* \param flags window flags
*/
QgsLayoutImageExportOptionsDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = 0 );

/**
* Sets the initial resolution displayed in the dialog.
* \param resolution default resolution in DPI
* \see resolution()
*/
void setResolution( double resolution );

/**
* Returns the selected resolution from the dialog.
* \returns image resolution in DPI
* \see setResolution()
*/
double resolution() const;

/**
* Sets the target image size. This is used to calculate the default size in pixels
* and also for determining the image's width to height ratio.
* \param size image size
*/
void setImageSize( QSizeF size );

/**
* Returns the user-set image width in pixels.
* \see imageHeight
*/
int imageWidth() const;

/**
* Returns the user-set image height in pixels.
* \see imageWidth
*/
int imageHeight() const;

/**
* Sets whether the crop to contents option should be checked in the dialog
* \param crop set to true to check crop to contents
* \see cropToContents()
*/
void setCropToContents( bool crop );

/**
* Returns whether the crop to contents option is checked in the dialog.
* \see setCropToContents()
*/
bool cropToContents() const;

/**
* Sets whether the generate world file option should be checked.
* \see generateWorldFile()
*/
void setGenerateWorldFile( bool generate );

/**
* Returns whether the generate world file option is checked in the dialog.
* \see setGenerateWorldFile()
*/
bool generateWorldFile() const;

/**
* Sets whether antialiasing should be used in the export.
* \see antialiasing()
*/
void setAntialiasing( bool antialias );

/**
* Returns whether antialiasing should be used in the export.
* \see setAntialiasing()
*/
bool antialiasing() const;

/**
* Fetches the current crop to contents margin values, in pixels.
* \param topMargin destination for top margin
* \param rightMargin destination for right margin
* \param bottomMargin destination for bottom margin
* \param leftMargin destination for left margin
*/
void getCropMargins( int &topMargin, int &rightMargin, int &bottomMargin, int &leftMargin ) const;

/**
* Sets the current crop to contents margin values, in pixels.
* \param topMargin top margin
* \param rightMargin right margin
* \param bottomMargin bottom margin
* \param leftMargin left margin
*/
void setCropMargins( int topMargin, int rightMargin, int bottomMargin, int leftMargin );

private slots:

void mWidthSpinBox_valueChanged( int value );
void mHeightSpinBox_valueChanged( int value );
void mResolutionSpinBox_valueChanged( int value );
void clipToContentsToggled( bool state );

private:

QSizeF mImageSize;


};

#endif // QGSLAYOUTIMAGEEXPORTOPTIONSDIALOG_H
1 change: 1 addition & 0 deletions src/app/layout/qgslayoutmapwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "qgssymbollayerutils.h"
#include "qgslayoutmapgridwidget.h"
#include "qgsstyle.h"
#include "qgslayoutundostack.h"
#include <QMenu>
#include <QMessageBox>

Expand Down
38 changes: 36 additions & 2 deletions src/app/layout/qgslayoutpagepropertieswidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "qgspagesizeregistry.h"
#include "qgslayoutitempage.h"
#include "qgslayout.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"

QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, QgsLayoutItem *layoutItem )
: QgsLayoutItemBaseWidget( parent, layoutItem )
Expand All @@ -37,6 +39,7 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q
mWidthSpin->setValue( mPage->pageSize().width() );
mHeightSpin->setValue( mPage->pageSize().height() );
mSizeUnitsComboBox->setUnit( mPage->pageSize().units() );
mExcludePageCheckBox->setChecked( mPage->excludeFromExports() );

mPageOrientationComboBox->setCurrentIndex( mPageOrientationComboBox->findData( mPage->orientation() ) );

Expand All @@ -57,17 +60,28 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q
connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::updatePageSize );
connect( mWidthSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::setToCustomSize );
connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::setToCustomSize );
connect( mExcludePageCheckBox, &QCheckBox::toggled, this, &QgsLayoutPagePropertiesWidget::excludeExportsToggled );

connect( mSymbolButton, &QgsSymbolButton::changed, this, &QgsLayoutPagePropertiesWidget::symbolChanged );
registerDataDefinedButton( mPaperSizeDDBtn, QgsLayoutObject::PresetPaperSize );
registerDataDefinedButton( mWidthDDBtn, QgsLayoutObject::ItemWidth );
registerDataDefinedButton( mHeightDDBtn, QgsLayoutObject::ItemHeight );
registerDataDefinedButton( mOrientationDDBtn, QgsLayoutObject::PaperOrientation );
registerDataDefinedButton( mExcludePageDDBtn, QgsLayoutObject::ExcludeFromExports );

connect( mPaperSizeDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsLayoutPagePropertiesWidget::refreshLayout );
connect( mWidthDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsLayoutPagePropertiesWidget::refreshLayout );
connect( mHeightDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsLayoutPagePropertiesWidget::refreshLayout );
connect( mOrientationDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsLayoutPagePropertiesWidget::refreshLayout );

mExcludePageDDBtn->registerEnabledWidget( mExcludePageCheckBox, false );

showCurrentPageSize();
}

void QgsLayoutPagePropertiesWidget::pageSizeChanged( int )
{
mBlockPageUpdate = true;
if ( mPageSizeComboBox->currentData().toString().isEmpty() )
{
//custom size
Expand Down Expand Up @@ -98,6 +112,7 @@ void QgsLayoutPagePropertiesWidget::pageSizeChanged( int )
}
mSettingPresetSize = false;
}
mBlockPageUpdate = false;
updatePageSize();
}

Expand Down Expand Up @@ -132,10 +147,17 @@ void QgsLayoutPagePropertiesWidget::orientationChanged( int )

void QgsLayoutPagePropertiesWidget::updatePageSize()
{
if ( mBlockPageUpdate )
return;

mPage->layout()->undoStack()->beginMacro( tr( "Change Page Size" ) );
mPage->layout()->pageCollection()->beginPageSizeChange();
mPage->layout()->undoStack()->beginCommand( mPage, tr( "Change Page Size" ), 1 + mPage->layout()->pageCollection()->pageNumber( mPage ) );
mPage->setPageSize( QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value(), mSizeUnitsComboBox->unit() ) );
mPage->layout()->undoStack()->endCommand();
mPage->layout()->pageCollection()->reflow();
mPage->layout()->pageCollection()->endPageSizeChange();
mPage->layout()->undoStack()->endMacro();
}

void QgsLayoutPagePropertiesWidget::setToCustomSize()
Expand All @@ -153,13 +175,25 @@ void QgsLayoutPagePropertiesWidget::symbolChanged()
mPage->layout()->undoStack()->endCommand();
}

void QgsLayoutPagePropertiesWidget::excludeExportsToggled( bool checked )
{
mPage->beginCommand( !checked ? tr( "Include Page in Exports" ) : tr( "Exclude Page from Exports" ) );
mPage->setExcludeFromExports( checked );
mPage->endCommand();
}

void QgsLayoutPagePropertiesWidget::refreshLayout()
{
mPage->layout()->refresh();
}

void QgsLayoutPagePropertiesWidget::showCurrentPageSize()
{
QgsLayoutSize paperSize = mPage->pageSize();
QString pageSize = QgsApplication::pageSizeRegistry()->find( paperSize );
if ( !pageSize.isEmpty() )
{
mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->findData( pageSize ) );
whileBlocking( mPageSizeComboBox )->setCurrentIndex( mPageSizeComboBox->findData( pageSize ) );
mLockAspectRatio->setEnabled( false );
mLockAspectRatio->setLocked( false );
mSizeUnitsComboBox->setEnabled( false );
Expand All @@ -168,7 +202,7 @@ void QgsLayoutPagePropertiesWidget::showCurrentPageSize()
else
{
// custom
mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->count() - 1 );
whileBlocking( mPageSizeComboBox )->setCurrentIndex( mPageSizeComboBox->count() - 1 );
mLockAspectRatio->setEnabled( true );
mSizeUnitsComboBox->setEnabled( true );
mPageOrientationComboBox->setEnabled( false );
Expand Down
3 changes: 3 additions & 0 deletions src/app/layout/qgslayoutpagepropertieswidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui
void updatePageSize();
void setToCustomSize();
void symbolChanged();
void excludeExportsToggled( bool checked );
void refreshLayout();

private:

Expand All @@ -56,6 +58,7 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui
QgsLayoutMeasurementConverter mConverter;

bool mSettingPresetSize = false;
bool mBlockPageUpdate = false;

void showCurrentPageSize();

Expand Down
1 change: 1 addition & 0 deletions src/app/layout/qgslayoutpolygonwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "qgslayout.h"
#include "qgssymbollayerutils.h"
#include "qgslayoutitemregistry.h"
#include "qgslayoutundostack.h"

QgsLayoutPolygonWidget::QgsLayoutPolygonWidget( QgsLayoutItemPolygon *polygon )
: QgsLayoutItemBaseWidget( nullptr, polygon )
Expand Down
1 change: 1 addition & 0 deletions src/app/layout/qgslayoutpolylinewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "qgssymbollayerutils.h"
#include "qgslayoutitemregistry.h"
#include "qgslayout.h"
#include "qgslayoutundostack.h"
#include <QFileDialog>

QgsLayoutPolylineWidget::QgsLayoutPolylineWidget( QgsLayoutItemPolyline *polyline )
Expand Down
128 changes: 128 additions & 0 deletions src/app/layout/qgslayoutpropertieswidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@
#include "qgslayoutpropertieswidget.h"
#include "qgslayout.h"
#include "qgslayoutsnapper.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"
#include "qgslayoutitemmap.h"

QgsLayoutPropertiesWidget::QgsLayoutPropertiesWidget( QWidget *parent, QgsLayout *layout )
: QgsPanelWidget( parent )
, mLayout( layout )
{
Q_ASSERT( mLayout );

setupUi( this );
setPanelTitle( tr( "Layout properties" ) );
blockSignals( true );
Expand All @@ -41,6 +46,67 @@ QgsLayoutPropertiesWidget::QgsLayoutPropertiesWidget( QWidget *parent, QgsLayout
connect( mGridResolutionSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::gridResolutionChanged );
connect( mOffsetXSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::gridOffsetXChanged );
connect( mOffsetYSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::gridOffsetYChanged );

double leftMargin = mLayout->customProperty( QStringLiteral( "resizeToContentsLeftMargin" ) ).toDouble();
double topMargin = mLayout->customProperty( QStringLiteral( "resizeToContentsTopMargin" ) ).toDouble();
double bottomMargin = mLayout->customProperty( QStringLiteral( "resizeToContentsBottomMargin" ) ).toDouble();
double rightMargin = mLayout->customProperty( QStringLiteral( "resizeToContentsRightMargin" ) ).toDouble();
QgsUnitTypes::LayoutUnit marginUnit = static_cast< QgsUnitTypes::LayoutUnit >(
mLayout->customProperty( QStringLiteral( "imageCropMarginUnit" ), QgsUnitTypes::LayoutMillimeters ).toInt() );

bool exportWorldFile = mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool();
mGenerateWorldFileCheckBox->setChecked( exportWorldFile );
connect( mGenerateWorldFileCheckBox, &QCheckBox::toggled, this, &QgsLayoutPropertiesWidget::worldFileToggled );

connect( mRasterizeCheckBox, &QCheckBox::toggled, this, &QgsLayoutPropertiesWidget::rasterizeToggled );
connect( mForceVectorCheckBox, &QCheckBox::toggled, this, &QgsLayoutPropertiesWidget::forceVectorToggled );

mTopMarginSpinBox->setValue( topMargin );
mMarginUnitsComboBox->linkToWidget( mTopMarginSpinBox );
mRightMarginSpinBox->setValue( rightMargin );
mMarginUnitsComboBox->linkToWidget( mRightMarginSpinBox );
mBottomMarginSpinBox->setValue( bottomMargin );
mMarginUnitsComboBox->linkToWidget( mBottomMarginSpinBox );
mLeftMarginSpinBox->setValue( leftMargin );
mMarginUnitsComboBox->linkToWidget( mLeftMarginSpinBox );
mMarginUnitsComboBox->setUnit( marginUnit );
mMarginUnitsComboBox->setConverter( &mLayout->context().measurementConverter() );

connect( mTopMarginSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::resizeMarginsChanged );
connect( mRightMarginSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::resizeMarginsChanged );
connect( mBottomMarginSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::resizeMarginsChanged );
connect( mLeftMarginSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::resizeMarginsChanged );
connect( mResizePageButton, &QPushButton::clicked, this, &QgsLayoutPropertiesWidget::resizeToContents );

connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutPropertiesWidget::dpiChanged );
connect( mReferenceMapComboBox, &QgsLayoutItemComboBox::itemChanged, this, &QgsLayoutPropertiesWidget::referenceMapChanged );

mReferenceMapComboBox->setCurrentLayout( mLayout );

connect( mLayout, &QgsLayout::changed, this, &QgsLayoutPropertiesWidget::updateGui );
updateGui();
}

void QgsLayoutPropertiesWidget::updateGui()
{
whileBlocking( mReferenceMapComboBox )->setItem( mLayout->referenceMap() );
whileBlocking( mResolutionSpinBox )->setValue( mLayout->context().dpi() );

bool rasterize = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
whileBlocking( mRasterizeCheckBox )->setChecked( rasterize );

bool forceVectors = mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool();
whileBlocking( mForceVectorCheckBox )->setChecked( forceVectors );

if ( rasterize )
{
mForceVectorCheckBox->setChecked( false );
mForceVectorCheckBox->setEnabled( false );
}
else
{
mForceVectorCheckBox->setEnabled( true );
}
}

void QgsLayoutPropertiesWidget::updateSnappingElements()
Expand Down Expand Up @@ -100,6 +166,68 @@ void QgsLayoutPropertiesWidget::snapToleranceChanged( int tolerance )
mLayout->snapper().setSnapTolerance( tolerance );
}

void QgsLayoutPropertiesWidget::resizeMarginsChanged()
{
mLayout->setCustomProperty( QStringLiteral( "resizeToContentsLeftMargin" ), mLeftMarginSpinBox->value() );
mLayout->setCustomProperty( QStringLiteral( "resizeToContentsTopMargin" ), mTopMarginSpinBox->value() );
mLayout->setCustomProperty( QStringLiteral( "resizeToContentsBottomMargin" ), mBottomMarginSpinBox->value() );
mLayout->setCustomProperty( QStringLiteral( "resizeToContentsRightMargin" ), mRightMarginSpinBox->value() );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginUnit" ), mMarginUnitsComboBox->unit() );
}

void QgsLayoutPropertiesWidget::resizeToContents()
{
mLayout->undoStack()->beginMacro( tr( "Resize to Contents" ) );

mLayout->pageCollection()->resizeToContents( QgsMargins( mLeftMarginSpinBox->value(),
mTopMarginSpinBox->value(),
mRightMarginSpinBox->value(),
mBottomMarginSpinBox->value() ),
mMarginUnitsComboBox->unit() );

mLayout->undoStack()->endMacro();
}

void QgsLayoutPropertiesWidget::referenceMapChanged( QgsLayoutItem *item )
{
mLayout->undoStack()->beginCommand( mLayout, tr( "Set Reference Map" ) );
QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( item );
mLayout->setReferenceMap( map );
mLayout->undoStack()->endCommand();
}

void QgsLayoutPropertiesWidget::dpiChanged( int value )
{
mLayout->undoStack()->beginCommand( mLayout, tr( "Set Default DPI" ), QgsLayout::UndoLayoutDpi );
mLayout->context().setDpi( value );
mLayout->undoStack()->endCommand();
}

void QgsLayoutPropertiesWidget::worldFileToggled()
{
mLayout->setCustomProperty( QStringLiteral( "exportWorldFile" ), mGenerateWorldFileCheckBox->isChecked() );
}

void QgsLayoutPropertiesWidget::rasterizeToggled()
{
mLayout->setCustomProperty( QStringLiteral( "rasterize" ), mRasterizeCheckBox->isChecked() );

if ( mRasterizeCheckBox->isChecked() )
{
mForceVectorCheckBox->setChecked( false );
mForceVectorCheckBox->setEnabled( false );
}
else
{
mForceVectorCheckBox->setEnabled( true );
}
}

void QgsLayoutPropertiesWidget::forceVectorToggled()
{
mLayout->setCustomProperty( QStringLiteral( "forceVector" ), mForceVectorCheckBox->isChecked() );
}

void QgsLayoutPropertiesWidget::blockSignals( bool block )
{
mGridResolutionSpinBox->blockSignals( block );
Expand Down
12 changes: 12 additions & 0 deletions src/app/layout/qgslayoutpropertieswidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ class QgsLayoutPropertiesWidget: public QgsPanelWidget, private Ui::QgsLayoutWid
public:
QgsLayoutPropertiesWidget( QWidget *parent, QgsLayout *layout );

public slots:

//! Refreshes the gui to reflect the current layout settings
void updateGui();

private slots:

void gridResolutionChanged( double d );
Expand All @@ -36,6 +41,13 @@ class QgsLayoutPropertiesWidget: public QgsPanelWidget, private Ui::QgsLayoutWid
void gridOffsetYChanged( double d );
void gridOffsetUnitsChanged( QgsUnitTypes::LayoutUnit unit );
void snapToleranceChanged( int tolerance );
void resizeMarginsChanged();
void resizeToContents();
void referenceMapChanged( QgsLayoutItem *item );
void dpiChanged( int value );
void worldFileToggled();
void rasterizeToggled();
void forceVectorToggled();

private:

Expand Down
12 changes: 12 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1855,7 +1855,9 @@ void QgisApp::createActions()
connect( mActionNewMapCanvas, &QAction::triggered, this, &QgisApp::newMapCanvas );
connect( mActionNew3DMapCanvas, &QAction::triggered, this, &QgisApp::new3DMapCanvas );
connect( mActionNewPrintComposer, &QAction::triggered, this, &QgisApp::newPrintComposer );
connect( mActionNewPrintLayout, &QAction::triggered, this, &QgisApp::newPrintLayout );
connect( mActionShowComposerManager, &QAction::triggered, this, &QgisApp::showComposerManager );
connect( mActionShowLayoutManager, &QAction::triggered, this, &QgisApp::showLayoutManager );
connect( mActionExit, &QAction::triggered, this, &QgisApp::fileExit );
connect( mActionDxfExport, &QAction::triggered, this, &QgisApp::dxfExport );
connect( mActionDwgImport, &QAction::triggered, this, &QgisApp::dwgImport );
Expand Down Expand Up @@ -5995,6 +5997,16 @@ void QgisApp::newPrintComposer()
createNewComposer( title );
}

void QgisApp::newPrintLayout()
{
QString title;
if ( !uniqueLayoutTitle( this, title, true ) )
{
return;
}
createNewLayout( title );
}

void QgisApp::showComposerManager()
{
if ( !mComposerManager )
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void newGeoPackageLayer();
//! Print the current map view frame
void newPrintComposer();
//! Create a new print layout
void newPrintLayout();

void showComposerManager();
//! Add all loaded layers into the overview - overrides qgisappbase method
void addAllToOverview();
Expand Down
3 changes: 3 additions & 0 deletions src/core/composer/qgslayoutmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "qgslayout.h"
#include "qgsproject.h"
#include "qgslogger.h"
#include "qgslayoutundostack.h"

QgsLayoutManager::QgsLayoutManager( QgsProject *project )
: QObject( project )
Expand Down Expand Up @@ -193,11 +194,13 @@ bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &
for ( int i = 0; i < layoutNodes.size(); ++i )
{
std::unique_ptr< QgsLayout > l = qgis::make_unique< QgsLayout >( mProject );
l->undoStack()->blockCommands( true );
if ( !l->readXml( layoutNodes.at( i ).toElement(), doc, context ) )
{
result = false;
continue;
}
l->undoStack()->blockCommands( false );
if ( addLayout( l.get() ) )
{
( void )l.release(); // ownership was transferred successfully
Expand Down
76 changes: 66 additions & 10 deletions src/core/layout/qgslayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
#include "qgslayoutitemgroup.h"
#include "qgslayoutitemgroupundocommand.h"
#include "qgslayoutmultiframe.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutundostack.h"

QgsLayout::QgsLayout( QgsProject *project )
: mProject( project )
, mSnapper( QgsLayoutSnapper( this ) )
, mGridSettings( this )
, mPageCollection( new QgsLayoutPageCollection( this ) )
, mUndoStack( new QgsLayoutUndoStack( this ) )
, mExporter( QgsLayoutExporter( this ) )
{
// just to make sure - this should be the default, but maybe it'll change in some future Qt version...
setBackgroundBrush( Qt::NoBrush );
Expand Down Expand Up @@ -112,11 +113,6 @@ QgsLayoutModel *QgsLayout::itemsModel()
return mItemsModel.get();
}

QgsLayoutExporter &QgsLayout::exporter()
{
return mExporter;
}

void QgsLayout::setName( const QString &name )
{
mName = name;
Expand Down Expand Up @@ -219,7 +215,7 @@ bool QgsLayout::moveItemToBottom( QgsLayoutItem *item, bool deferUpdate )
return result;
}

QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid, bool includeTemplateUuids )
QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid, bool includeTemplateUuids ) const
{
QList<QgsLayoutItem *> itemList;
layoutItems( itemList );
Expand Down Expand Up @@ -363,12 +359,31 @@ QStringList QgsLayout::customProperties() const

QgsLayoutItemMap *QgsLayout::referenceMap() const
{
return nullptr;
// prefer explicitly set reference map
if ( QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( itemByUuid( mWorldFileMapId ) ) )
return map;

// else try to find largest map
QList< QgsLayoutItemMap * > maps;
layoutItems( maps );
QgsLayoutItemMap *largestMap = nullptr;
double largestMapArea = 0;
for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
{
double area = map->rect().width() * map->rect().height();
if ( area > largestMapArea )
{
largestMapArea = area;
largestMap = map;
}
}
return largestMap;
}

void QgsLayout::setReferenceMap( QgsLayoutItemMap *map )
{
Q_UNUSED( map );
mWorldFileMapId = map ? map->uuid() : QString();
mProject->setDirty( true );
}

QgsLayoutPageCollection *QgsLayout::pageCollection()
Expand Down Expand Up @@ -425,6 +440,32 @@ QRectF QgsLayout::layoutBounds( bool ignorePages, double margin ) const

}

QRectF QgsLayout::pageItemBounds( int page, bool visibleOnly ) const
{
//start with an empty rectangle
QRectF bounds;

//add all QgsLayoutItems on page
const QList<QGraphicsItem *> itemList = items();
for ( QGraphicsItem *item : itemList )
{
const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
if ( layoutItem && layoutItem->type() != QgsLayoutItemRegistry::LayoutPage && layoutItem->page() == page )
{
if ( visibleOnly && !layoutItem->isVisible() )
continue;

//expand bounds with current item's bounds
if ( bounds.isValid() )
bounds = bounds.united( item->sceneBoundingRect() );
else
bounds = item->sceneBoundingRect();
}
}

return bounds;
}

void QgsLayout::addLayoutItem( QgsLayoutItem *item )
{
addLayoutItemPrivate( item );
Expand Down Expand Up @@ -583,7 +624,7 @@ class QgsLayoutUndoCommand: public QgsAbstractLayoutUndoCommand
return;
}

mLayout->readXmlLayoutSettings( stateDoc.documentElement().firstChild().toElement(), stateDoc, QgsReadWriteContext() );
mLayout->readXmlLayoutSettings( stateDoc.documentElement(), stateDoc, QgsReadWriteContext() );
mLayout->project()->setDirty( true );
}

Expand Down Expand Up @@ -659,7 +700,12 @@ QList<QgsLayoutItem *> QgsLayout::ungroupItems( QgsLayoutItemGroup *group )

void QgsLayout::refresh()
{
mUndoStack->blockCommands( true );
mPageCollection->beginPageSizeChange();
emit refreshed();
mPageCollection->reflow();
mPageCollection->endPageSizeChange();
mUndoStack->blockCommands( false );
update();
}

Expand All @@ -668,6 +714,8 @@ void QgsLayout::writeXmlLayoutSettings( QDomElement &element, QDomDocument &docu
mCustomProperties.writeXml( element, document );
element.setAttribute( QStringLiteral( "name" ), mName );
element.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );
element.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
element.setAttribute( QStringLiteral( "printResolution" ), mContext.dpi() );
}

QDomElement QgsLayout::writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const
Expand Down Expand Up @@ -710,6 +758,10 @@ bool QgsLayout::readXmlLayoutSettings( const QDomElement &layoutElement, const Q
mCustomProperties.readXml( layoutElement );
setName( layoutElement.attribute( QStringLiteral( "name" ) ) );
setUnits( QgsUnitTypes::decodeLayoutUnit( layoutElement.attribute( QStringLiteral( "units" ) ) ) );
mWorldFileMapId = layoutElement.attribute( QStringLiteral( "worldFileMap" ) );
mContext.setDpi( layoutElement.attribute( QStringLiteral( "printResolution" ), "300" ).toDouble() );
emit changed();

return true;
}

Expand Down Expand Up @@ -804,13 +856,17 @@ bool QgsLayout::readXml( const QDomElement &layoutElement, const QDomDocument &d
return object->readXml( layoutElement, document, context );
};

blockSignals( true ); // defer changed signal to end
readXmlLayoutSettings( layoutElement, document, context );
blockSignals( false );

restore( mPageCollection.get() );
restore( &mSnapper );
restore( &mGridSettings );
addItemsFromXml( layoutElement, document, context );

emit changed();

return true;
}

Expand Down
58 changes: 42 additions & 16 deletions src/core/layout/qgslayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,27 @@
#include "qgslayoutcontext.h"
#include "qgslayoutsnapper.h"
#include "qgsexpressioncontextgenerator.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutgridsettings.h"
#include "qgslayoutguidecollection.h"
#include "qgslayoutundostack.h"
#include "qgslayoutexporter.h"

class QgsLayoutItemMap;
class QgsLayoutModel;
class QgsLayoutMultiFrame;
class QgsLayoutPageCollection;
class QgsLayoutUndoStack;

/**
* \ingroup core
* \class QgsLayout
* \brief Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
*
* While the raw QGraphicsScene API can be used to render the contents of a QgsLayout
* to a QPainter, it is recommended to instead use a QgsLayoutExporter to handle rendering
* layouts instead. QgsLayoutExporter automatically takes care of the intracacies of
* preparing the layout and paint devices for correct exports, respecting various
* user settings such as the layout context DPI.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContextGenerator, public QgsLayoutUndoObjectInterface
Expand All @@ -57,6 +64,13 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
ZSnapIndicator = 10002, //!< Z-value for snapping indicator
};

//! Layout undo commands, used for collapsing undo commands
enum UndoCommand
{
UndoLayoutDpi, //!< Change layout default DPI
UndoNone = -1, //!< No command suppression
};

/**
* Construct a new layout linked to the specified \a project.
*
Expand Down Expand Up @@ -93,12 +107,6 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
*/
QgsLayoutModel *itemsModel();

/**
* Returns the layout's exporter, which is used for rendering the layout and exporting
* to various formats.
*/
QgsLayoutExporter &exporter();

/**
* Returns the layout's name.
* \see setName()
Expand All @@ -115,7 +123,7 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
* Returns a list of layout items of a specific type.
* \note not available in Python bindings
*/
template<class T> void layoutItems( QList<T *> &itemList ) SIP_SKIP
template<class T> void layoutItems( QList<T *> &itemList ) const SIP_SKIP
{
itemList.clear();
QList<QGraphicsItem *> graphicsItemList = items();
Expand Down Expand Up @@ -222,7 +230,7 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
*
* \see multiFrameByUuid()
*/
QgsLayoutItem *itemByUuid( const QString &uuid, bool includeTemplateUuids = false );
QgsLayoutItem *itemByUuid( const QString &uuid, bool includeTemplateUuids = false ) const;

/**
* Returns the layout multiframe with matching \a uuid unique identifier, or a nullptr
Expand Down Expand Up @@ -403,7 +411,6 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
* \see setReferenceMap()
* \see generateWorldFile()
*/
//TODO
QgsLayoutItemMap *referenceMap() const;

/**
Expand All @@ -412,7 +419,6 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
* \see referenceMap()
* \see setGenerateWorldFile()
*/
//TODO
void setReferenceMap( QgsLayoutItemMap *map );

/**
Expand All @@ -433,9 +439,23 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
* \param ignorePages set to true to ignore page items
* \param margin optional marginal (in percent, e.g., 0.05 = 5% ) to add around items
* \returns layout bounds, in layout units.
*
* \see pageItemBounds()
*/
QRectF layoutBounds( bool ignorePages = false, double margin = 0.0 ) const;

/**
* Returns the bounding box of the items contained on a specified \a page.
* A page number of 0 represents the first page in the layout.
*
* Set \a visibleOnly to true to only include visible items.
*
* The returned bounds are in layout units.
*
* \see layoutBounds()
*/
QRectF pageItemBounds( int page, bool visibleOnly = false ) const;

/**
* Adds an \a item to the layout. This should be called instead of the base class addItem()
* method. Ownership of the item is transferred to the layout.
Expand Down Expand Up @@ -569,6 +589,13 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext

signals:

/**
* Is emitted when properties of the layout change. This signal is only
* emitted for settings directly managed by the layout, and is not emitted
* when child items change.
*/
void changed();

/**
* Emitted whenever the expression variables stored in the layout have been changed.
*/
Expand Down Expand Up @@ -608,11 +635,13 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext

std::unique_ptr< QgsLayoutPageCollection > mPageCollection;
std::unique_ptr< QgsLayoutUndoStack > mUndoStack;
QgsLayoutExporter mExporter;

//! List of multiframe objects
QList<QgsLayoutMultiFrame *> mMultiFrames;

//! Item ID for composer map to use for the world file generation
QString mWorldFileMapId;

//! Writes only the layout settings (not member settings like grid settings, etc) to XML
void writeXmlLayoutSettings( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
//! Reads only the layout settings (not member settings like grid settings, etc) from XML
Expand Down Expand Up @@ -643,6 +672,3 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
};

#endif //QGSLAYOUT_H



1 change: 1 addition & 0 deletions src/core/layout/qgslayoutaligner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "qgslayoutaligner.h"
#include "qgslayoutitem.h"
#include "qgslayout.h"
#include "qgslayoutundostack.h"

void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Alignment alignment )
{
Expand Down
3 changes: 3 additions & 0 deletions src/core/layout/qgslayoutcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class CORE_EXPORT QgsLayoutContext : public QObject
FlagOutlineOnly = 1 << 2, //!< Render items as outlines only.
FlagAntialiasing = 1 << 3, //!< Use antialiasing when drawing items.
FlagUseAdvancedEffects = 1 << 4, //!< Enable advanced effects such as blend modes.
FlagForceVectorOutput = 1 << 5, //!< Force output in vector format where possible, even if items require rasterization to keep their correct appearance.
};
Q_DECLARE_FLAGS( Flags, Flag )

Expand Down Expand Up @@ -235,6 +236,8 @@ class CORE_EXPORT QgsLayoutContext : public QObject
bool mPagesVisible = true;

friend class QgsLayoutExporter;
friend class TestQgsLayout;
friend class LayoutContextPreviewSettingRestorer;


};
Expand Down
735 changes: 731 additions & 4 deletions src/core/layout/qgslayoutexporter.cpp

Large diffs are not rendered by default.

297 changes: 295 additions & 2 deletions src/core/layout/qgslayoutexporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@
#define QGSLAYOUTEXPORTER_H

#include "qgis_core.h"
#include "qgsmargins.h"
#include "qgslayoutcontext.h"
#include <QPointer>
#include <QSize>
#include <QRectF>
#include <QPrinter>

class QgsLayout;
class QPainter;
class QgsLayoutItemMap;

/**
* \ingroup core
Expand All @@ -33,11 +39,34 @@ class CORE_EXPORT QgsLayoutExporter

public:

//! Contains details of a page being exported by the class
struct PageExportDetails
{
//! Target folder
QString directory;

//! Base part of filename (i.e. file name without extension or '.')
QString baseName;

//! File suffix/extension (without the leading '.')
QString extension;

//! Page number, where 0 = first page.
int page = 0;
};

/**
* Constructor for QgsLayoutExporter, for the specified \a layout.
*/
QgsLayoutExporter( QgsLayout *layout );

virtual ~QgsLayoutExporter() = default;

/**
* Returns the layout linked to this exporter.
*/
QgsLayout *layout() const;

/**
* Renders a full page to a destination \a painter.
*
Expand All @@ -46,19 +75,283 @@ class CORE_EXPORT QgsLayoutExporter
*
* \see renderRect()
*/
void renderPage( QPainter *painter, int page );
void renderPage( QPainter *painter, int page ) const;

/**
* Renders a full page to an image.
*
* The \a page argument specifies the page number to render. Page numbers
* are 0 based, such that the first page in a layout is page 0.
*
* The optional \a imageSize parameter can specify the target image size, in pixels.
* It is the caller's responsibility to ensure that the ratio of the target image size
* matches the ratio of the corresponding layout page size.
*
* The \a dpi parameter is an optional dpi override. Set to 0 to use the default layout print
* resolution. This parameter has no effect if \a imageSize is specified.
*
* Returns the rendered image, or a null QImage if the image does not fit into available memory.
*
* \see renderPage()
* \see renderRegionToImage()
*/
QImage renderPageToImage( int page, QSize imageSize = QSize(), double dpi = 0 ) const;

/**
* Renders a \a region from the layout to a \a painter. This method can be used
* to render sections of pages rather than full pages.
*
* \see renderPage()
* \see renderRegionToImage()
*/
void renderRegion( QPainter *painter, const QRectF &region ) const;

/**
* Renders a \a region of the layout to an image. This method can be used to render
* sections of pages rather than full pages.
*
* The optional \a imageSize parameter can specify the target image size, in pixels.
* It is the caller's responsibility to ensure that the ratio of the target image size
* matches the ratio of the specified region of the layout.
*
* The \a dpi parameter is an optional dpi override. Set to 0 to use the default layout print
* resolution. This parameter has no effect if \a imageSize is specified.
*
* Returns the rendered image, or a null QImage if the image does not fit into available memory.
*
* \see renderRegion()
* \see renderPageToImage()
*/
void renderRegion( QPainter *painter, const QRectF &region );
QImage renderRegionToImage( const QRectF &region, QSize imageSize = QSize(), double dpi = 0 ) const;


//! Result codes for exporting layouts
enum ExportResult
{
Success, //!< Export was successful
MemoryError, //!< Unable to allocate memory required to export
FileError, //!< Could not write to destination file, likely due to a lock held by another application
PrintError, //!< Could not start printing to destination device
};

//! Contains settings relating to exporting layouts to raster images
struct ImageExportSettings
{
//! Constructor for ImageExportSettings
ImageExportSettings()
: flags( QgsLayoutContext::FlagAntialiasing | QgsLayoutContext::FlagUseAdvancedEffects )
{}

//! Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
double dpi = -1;

/**
* Manual size in pixels for output image. If imageSize is not
* set then it will be automatically calculated based on the
* output dpi and layout size.
*
* If cropToContents is true then imageSize has no effect.
*
* Be careful when specifying manual sizes if pages in the layout
* have differing sizes! It's likely not going to give a reasonable
* output in this case, and the automatic dpi-based image size should be
* used instead.
*/
QSize imageSize;

/**
* 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 pixels. These margins will be added
* to the bounds of the exported layout if cropToContents is true.
*/
QgsMargins cropMargins;

/**
* List of specific pages to export, or an empty list to
* export all pages.
*
* Page numbers are 0 index based, so the first page in the
* layout corresponds to page 0.
*/
QList< int > pages;

/**
* Set to true to generate an external world file alongside
* exported images.
*/
bool generateWorldFile = false;

/**
* Layout context flags, which control how the export will be created.
*/
QgsLayoutContext::Flags flags = 0;

};

/**
* Exports the layout to the a \a filePath, using the specified export \a settings.
*
* If the layout is a multi-page layout, then filenames for each page will automatically
* be generated by appending "_1", "_2", etc to the image file's base name.
*
* Returns a result code indicating whether the export was successful or an
* error was encountered. If an error code is returned, errorFile() can be called
* to determine the filename for the export which encountered the error.
*/
ExportResult exportToImage( const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings );

//! Contains settings relating to exporting layouts to PDF
struct PdfExportSettings
{
//! Constructor for PdfExportSettings
PdfExportSettings()
: 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 whole layout to be rasterized while exporting.
*
* This option is mutually exclusive with forceVectorOutput.
*/
bool rasterizeWholeImage = false;

/**
* 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;

/**
* Layout context flags, which control how the export will be created.
*/
QgsLayoutContext::Flags flags = 0;

};

/**
* Exports the layout as a PDF 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 exportToPdf( const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings );

/**
* Returns the file name corresponding to the last error encountered during
* an export.
*/
QString errorFile() const { return mErrorFileName; }

/**
* Georeferences a \a file (image of PDF) exported from the layout.
*
* The \a referenceMap argument specifies a map item to use for georeferencing. If left as nullptr, the
* default layout QgsLayout::referenceMap() will be used.
*
* The \a exportRegion argument can be set to a valid rectangle to indicate that only part of the layout was
* exported.
*
* Similarly, the \a dpi can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
*
* The function will return true if the output was successfully georeferenced.
*
* \see computeGeoTransform()
*/
bool georeferenceOutput( const QString &file, QgsLayoutItemMap *referenceMap = nullptr,
const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;

/**
* Compute world file parameters. Assumes the whole page containing the reference map item
* will be exported.
*
* The \a dpi argument can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
*/
void computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f, double dpi = -1 ) const;

/**
* Computes the world file parameters for a specified \a region of the layout.
*
* The \a dpi argument can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
*/
void computeWorldFileParameters( const QRectF &region, double &a, double &b, double &c, double &d, double &e, double &f, double dpi = -1 ) const;

protected:

/**
* Generates the file name for a page during export.
*
* Subclasses can override this method to customise page file naming.
*/
virtual QString generateFileName( const PageExportDetails &details ) const;

private:

QPointer< QgsLayout > mLayout;

QString mErrorFileName;

QImage createImage( const ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const;

/**
* Saves an image to a file, possibly using format specific options (e.g. LZW compression for tiff)
*/
static bool saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat );

/**
* Computes a GDAL style geotransform for georeferencing a layout.
*
* The \a referenceMap argument specifies a map item to use for georeferencing. If left as nullptr, the
* default layout QgsLayout::referenceMap() will be used.
*
* The \a exportRegion argument can be set to a valid rectangle to indicate that only part of the layout was
* exported.
*
* Similarly, the \a dpi can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
*
* \see georeferenceOutput()
*/
std::unique_ptr<double[]> computeGeoTransform( const QgsLayoutItemMap *referenceMap = nullptr, const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;

//! Write a world file
void writeWorldFile( const QString &fileName, double a, double b, double c, double d, double e, double f ) const;

/**
* Prepare a \a printer for printing a layout as a PDF, to the destination \a filePath.
*/
void preparePrintAsPdf( QPrinter &printer, const QString &filePath );

void preparePrint( QPrinter &printer, bool setFirstPageSize = false );

/**
* Convenience function that prepares the printer and prints.
*/
ExportResult print( QPrinter &printer );

/**
* Print on a preconfigured printer
* \param printer QPrinter destination
* \param painter QPainter source
* \param startNewPage set to true to begin the print on a new page
* \param dpi set to a value > 0 to manually override the layout's default dpi
* \param rasterize set to true to force print as a raster image
*/
ExportResult printPrivate( QPrinter &printer, QPainter &painter, bool startNewPage = false, double dpi = -1, bool rasterize = false );

void updatePrinterPageSize( QPrinter &printer, int page );

friend class TestQgsLayout;

};

#endif //QGSLAYOUTEXPORTER_H
Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutgridsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "qgsreadwritecontext.h"
#include "qgslayout.h"
#include "qgsproject.h"
#include "qgslayoutundostack.h"
#include "qgslayoutpagecollection.h"

QgsLayoutGridSettings::QgsLayoutGridSettings( QgsLayout *layout )
: mGridResolution( QgsLayoutMeasurement( 10 ) )
Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutguidecollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "qgslayout.h"
#include "qgsproject.h"
#include "qgsreadwritecontext.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"
#include <QGraphicsLineItem>


Expand Down
99 changes: 83 additions & 16 deletions src/core/layout/qgslayoutitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include "qgslayoutitemgroup.h"
#include "qgspainting.h"
#include "qgslayouteffect.h"
#include "qgslayoutundostack.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutitempage.h"
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QUuid>
Expand Down Expand Up @@ -240,18 +243,32 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it
return;
}

double destinationDpi = itemStyle->matrix.m11() * 25.4;
bool previewRender = !mLayout || mLayout->context().isPreviewRender();
double destinationDpi = previewRender ? itemStyle->matrix.m11() * 25.4 : mLayout->context().dpi();
bool useImageCache = false;
bool forceRasterOutput = containsAdvancedEffects() && ( !mLayout || !( mLayout->context().flags() & QgsLayoutContext::FlagForceVectorOutput ) );

if ( useImageCache )
if ( useImageCache || forceRasterOutput )
{
double widthInPixels = boundingRect().width() * itemStyle->matrix.m11();
double heightInPixels = boundingRect().height() * itemStyle->matrix.m11();
double widthInPixels = 0;
double heightInPixels = 0;

if ( previewRender )
{
widthInPixels = boundingRect().width() * itemStyle->matrix.m11();
heightInPixels = boundingRect().height() * itemStyle->matrix.m11();
}
else
{
double layoutUnitsToPixels = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutPixels ).length() : destinationDpi / 25.4;
widthInPixels = boundingRect().width() * layoutUnitsToPixels;
heightInPixels = boundingRect().height() * layoutUnitsToPixels;
}

// limit size of image for better performance
double scale = 1.0;
if ( widthInPixels > CACHE_SIZE_LIMIT || heightInPixels > CACHE_SIZE_LIMIT )
if ( previewRender && ( widthInPixels > CACHE_SIZE_LIMIT || heightInPixels > CACHE_SIZE_LIMIT ) )
{
double scale = 1.0;
if ( widthInPixels > heightInPixels )
{
scale = widthInPixels / CACHE_SIZE_LIMIT;
Expand All @@ -267,7 +284,7 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it
destinationDpi = destinationDpi / scale;
}

if ( !mItemCachedImage.isNull() && qgsDoubleNear( mItemCacheDpi, destinationDpi ) )
if ( previewRender && !mItemCachedImage.isNull() && qgsDoubleNear( mItemCacheDpi, destinationDpi ) )
{
// can reuse last cached image
QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi );
Expand All @@ -282,13 +299,11 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it
}
else
{
mItemCacheDpi = destinationDpi;

mItemCachedImage = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
mItemCachedImage.fill( Qt::transparent );
mItemCachedImage.setDotsPerMeterX( 1000 * destinationDpi * 25.4 );
mItemCachedImage.setDotsPerMeterY( 1000 * destinationDpi * 25.4 );
QPainter p( &mItemCachedImage );
QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
image.fill( Qt::transparent );
image.setDotsPerMeterX( 1000 * destinationDpi * 25.4 );
image.setDotsPerMeterY( 1000 * destinationDpi * 25.4 );
QPainter p( &image );

preparePainter( &p );
QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( nullptr, &p, destinationDpi );
Expand All @@ -304,8 +319,14 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it
// scale painter from mm to dots
painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() );
painter->drawImage( boundingRect().x() * context.scaleFactor(),
boundingRect().y() * context.scaleFactor(), mItemCachedImage );
boundingRect().y() * context.scaleFactor(), image );
painter->restore();

if ( previewRender )
{
mItemCacheDpi = destinationDpi;
mItemCachedImage = image;
}
}
}
else
Expand Down Expand Up @@ -838,6 +859,16 @@ void QgsLayoutItem::setExcludeFromExports( bool exclude )
refreshDataDefinedProperty( QgsLayoutObject::ExcludeFromExports );
}

bool QgsLayoutItem::containsAdvancedEffects() const
{
return false;
}

bool QgsLayoutItem::requiresRasterization() const
{
return itemOpacity() < 1.0 || blendMode() != QPainter::CompositionMode_SourceOver;
}

double QgsLayoutItem::estimatedFrameBleed() const
{
if ( !hasFrame() )
Expand Down Expand Up @@ -902,6 +933,37 @@ QgsLayoutPoint QgsLayoutItem::applyDataDefinedPosition( const QgsLayoutPoint &po
return QgsLayoutPoint( evaluatedX, evaluatedY, position.units() );
}

void QgsLayoutItem::applyDataDefinedOrientation( double &width, double &height, const QgsExpressionContext &context )
{
bool ok = false;
QString orientationString = mDataDefinedProperties.valueAsString( QgsLayoutObject::PaperOrientation, context, QString(), &ok );
if ( ok && !orientationString.isEmpty() )
{
QgsLayoutItemPage::Orientation orientation = QgsLayoutUtils::decodePaperOrientation( orientationString, ok );
if ( ok )
{
double heightD, widthD;
switch ( orientation )
{
case QgsLayoutItemPage::Portrait:
{
heightD = std::max( height, width );
widthD = std::min( height, width );
break;
}
case QgsLayoutItemPage::Landscape:
{
heightD = std::min( height, width );
widthD = std::max( height, width );
break;
}
}
width = widthD;
height = heightD;
}
}
}

QgsLayoutSize QgsLayoutItem::applyDataDefinedSize( const QgsLayoutSize &size )
{
if ( !mLayout )
Expand All @@ -911,7 +973,8 @@ QgsLayoutSize QgsLayoutItem::applyDataDefinedSize( const QgsLayoutSize &size )

if ( !mDataDefinedProperties.isActive( QgsLayoutObject::PresetPaperSize ) &&
!mDataDefinedProperties.isActive( QgsLayoutObject::ItemWidth ) &&
!mDataDefinedProperties.isActive( QgsLayoutObject::ItemHeight ) )
!mDataDefinedProperties.isActive( QgsLayoutObject::ItemHeight ) &&
!mDataDefinedProperties.isActive( QgsLayoutObject::PaperOrientation ) )
return size;


Expand All @@ -932,6 +995,10 @@ QgsLayoutSize QgsLayoutItem::applyDataDefinedSize( const QgsLayoutSize &size )
// highest priority is dd width/height
evaluatedWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemWidth, context, evaluatedWidth );
evaluatedHeight = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemHeight, context, evaluatedHeight );

//which is finally overwritten by data defined orientation
applyDataDefinedOrientation( evaluatedWidth, evaluatedHeight, context );

return QgsLayoutSize( evaluatedWidth, evaluatedHeight, size.units() );
}

Expand Down
30 changes: 25 additions & 5 deletions src/core/layout/qgslayoutitem.h
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,24 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
void setExcludeFromExports( bool exclude );

/**
* Returns true if the item contains contents with blend modes or transparency
* effects which can only be reproduced by rastering the item.
*
* Subclasses should ensure that implemented overrides of this method
* also check the base class result.
*
* \see requiresRasterization()
*/
virtual bool containsAdvancedEffects() const;

/**
* Returns true if the item is drawn in such a way that forces the whole layout
* to be rasterized when exporting to vector formats.
* \see containsAdvancedEffects()
*/
virtual bool requiresRasterization() const;

/**
* Returns the estimated amount the item's frame bleeds outside the item's
* actual rectangle. For instance, if the item has a 2mm frame stroke, then
Expand Down Expand Up @@ -813,6 +831,11 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
void cancelCommand();

/**
* Returns whether the item should be drawn in the current context.
*/
bool shouldDrawItem() const;

public slots:

/**
Expand Down Expand Up @@ -1031,11 +1054,6 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );

/**
* Returns whether the item should be drawn in the current context.
*/
bool shouldDrawItem() const;

/**
* Applies any present data defined size overrides to the specified layout \a size.
*/
Expand Down Expand Up @@ -1120,6 +1138,8 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
void setScenePos( const QPointF &destinationPos );
bool shouldBlockUndoCommands() const;

void applyDataDefinedOrientation( double &width, double &height, const QgsExpressionContext &context );

friend class TestQgsLayoutItem;
friend class TestQgsLayoutView;
friend class QgsLayoutItemGroup;
Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutitemgroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "qgslayoutitemregistry.h"
#include "qgslayout.h"
#include "qgslayoututils.h"
#include "qgslayoutundostack.h"
#include "qgslayoutpagecollection.h"

QgsLayoutItemGroup::QgsLayoutItemGroup( QgsLayout *layout )
: QgsLayoutItem( layout )
Expand Down
168 changes: 132 additions & 36 deletions src/core/layout/qgslayoutitemmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,31 @@ bool QgsLayoutItemMap::containsWmsLayer() const
return false;
}

bool QgsLayoutItemMap::requiresRasterization() const
{
if ( QgsLayoutItem::requiresRasterization() )
return true;

// we MUST force the whole layout to render as a raster if any map item
// uses blend modes, and we are not drawing on a solid opaque background
// because in this case the map item needs to be rendered as a raster, but
// it also needs to interact with items below it
if ( !containsAdvancedEffects() )
return false;

// TODO layer transparency is probably ok to allow without forcing rasterization

if ( hasBackground() && qgsDoubleNear( backgroundColor().alphaF(), 1.0 ) )
return false;

return true;
}

bool QgsLayoutItemMap::containsAdvancedEffects() const
{
if ( QgsLayoutItem::containsAdvancedEffects() )
return true;

//check easy things first

//overviews
Expand Down Expand Up @@ -733,11 +756,12 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
if ( thisPaintRect.width() == 0 || thisPaintRect.height() == 0 )
return;

painter->save();
painter->setClipRect( thisPaintRect );
//TODO - try to reduce the amount of duplicate code here!

if ( mLayout->context().isPreviewRender() )
{
painter->save();
painter->setClipRect( thisPaintRect );
if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
{
// No initial render available - so draw some preview text alerting user
Expand Down Expand Up @@ -777,6 +801,23 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
//restore rotation
painter->restore();
}

painter->setClipRect( thisPaintRect, Qt::NoClip );

if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( painter );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( painter );
}
drawAnnotations( painter );
if ( shouldDrawPart( Frame ) )
{
drawMapFrame( painter );
}
painter->restore();
}
else
{
Expand All @@ -788,49 +829,104 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
if ( !paintDevice )
return;

// Fill with background color
if ( shouldDrawPart( Background ) )
QgsRectangle cExtent = extent();
QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );

if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->context().flags() & QgsLayoutContext::FlagForceVectorOutput ) ) )
{
drawMapBackground( painter );
}
// rasterize
double destinationDpi = style->matrix.m11() * 25.4;
double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutInches ).length() : 1;
int widthInPixels = std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi );
int heightInPixels = std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi );
QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );

image.fill( Qt::transparent );
image.setDotsPerMeterX( 1000 * destinationDpi / 25.4 );
image.setDotsPerMeterY( 1000 * destinationDpi / 25.4 );
double dotsPerMM = destinationDpi / 25.4;
QPainter p( &image );

QPointF tl = -boundingRect().topLeft();
QRect imagePaintRect( std::round( tl.x() * dotsPerMM ),
std::round( tl.y() * dotsPerMM ),
std::round( thisPaintRect.width() * dotsPerMM ),
std::round( thisPaintRect.height() * dotsPerMM ) );
p.setClipRect( imagePaintRect );

p.translate( imagePaintRect.topLeft() );

// Fill with background color - must be drawn onto the flattened image
// so that layers with opacity or blend modes can correctly interact with it
if ( shouldDrawPart( Background ) )
{
p.scale( dotsPerMM, dotsPerMM );
drawMapBackground( &p );
p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
}

QgsRectangle cExtent = extent();
drawMap( &p, cExtent, imagePaintRect.size(), image.logicalDpiX() );

QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );
// important - all other items, overviews, grids etc must be rendered to the
// flattened image, in case these have blend modes must need to interact
// with the map
p.scale( dotsPerMM, dotsPerMM );

painter->save();
painter->translate( mXOffset, mYOffset );
if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( &p );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( &p );
}
drawAnnotations( &p );

double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
size *= dotsPerMM; // output size will be in dots (pixels)
painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );
painter->save();
painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
painter->drawImage( std::round( -tl.x()* dotsPerMM ), std::round( -tl.y() * dotsPerMM ), image );
painter->scale( dotsPerMM, dotsPerMM );
}
else
{
// Fill with background color
if ( shouldDrawPart( Background ) )
{
drawMapBackground( painter );
}

//restore rotation
painter->restore();
mDrawing = false;
}
painter->setClipRect( thisPaintRect );
painter->save();
painter->translate( mXOffset, mYOffset );

painter->setClipRect( thisPaintRect, Qt::NoClip );
double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
size *= dotsPerMM; // output size will be in dots (pixels)
painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );

if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( painter );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( painter );
}
painter->restore();

//draw canvas items
drawAnnotations( painter );
painter->setClipRect( thisPaintRect, Qt::NoClip );

if ( shouldDrawPart( Frame ) )
{
drawMapFrame( painter );
}
if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( painter );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( painter );
}
drawAnnotations( painter );

painter->restore();
}

if ( shouldDrawPart( Frame ) )
{
drawMapFrame( painter );
}
painter->restore();
mDrawing = false;
}
}

int QgsLayoutItemMap::numberExportLayers() const
Expand Down Expand Up @@ -944,7 +1040,7 @@ void QgsLayoutItemMap::recreateCachedImageInBackground( double viewScaleFactor )
mPainterJob->start();
}

QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const
QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const
{
QgsExpressionContext expressionContext = createExpressionContext();
QgsCoordinateReferenceSystem renderCrs = crs();
Expand Down Expand Up @@ -990,7 +1086,7 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF

// layout-specific overrides of flags
jobMapSettings.setFlag( QgsMapSettings::ForceVectorOutput, true ); // force vector output (no caching of marker images etc.)
jobMapSettings.setFlag( QgsMapSettings::Antialiasing, true );
jobMapSettings.setFlag( QgsMapSettings::Antialiasing, mLayout->context().flags() & QgsLayoutContext::FlagAntialiasing );
jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
jobMapSettings.setFlag( QgsMapSettings::DrawSelection, false );
jobMapSettings.setFlag( QgsMapSettings::UseAdvancedEffects, mLayout->context().flags() & QgsLayoutContext::FlagUseAdvancedEffects );
Expand Down
7 changes: 3 additions & 4 deletions src/core/layout/qgslayoutitemmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
int numberExportLayers() const override;
void setFrameStrokeWidth( const QgsLayoutMeasurement &width ) override;


/**
* Returns the map scale.
* The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
Expand Down Expand Up @@ -276,8 +275,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
//! Returns true if the map contains a WMS layer.
bool containsWmsLayer() const;

//! Returns true if the map contains layers with blend modes or flattened layers for vectors
bool containsAdvancedEffects() const;
bool requiresRasterization() const override;
bool containsAdvancedEffects() const override;

/**
* Sets the \a rotation for the map - this does not affect the composer item shape, only the
Expand Down Expand Up @@ -409,7 +408,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
/**
* Return map settings that will be used for drawing of the map.
*/
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const;

void finalizeRestoreFromXml() override;

Expand Down
10 changes: 7 additions & 3 deletions src/core/layout/qgslayoutitemmapgrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ void QgsLayoutItemMapGrid::setCrs( const QgsCoordinateReferenceSystem &crs )

bool QgsLayoutItemMapGrid::usesAdvancedEffects() const
{
return mBlendMode == QPainter::CompositionMode_SourceOver;
return mBlendMode != QPainter::CompositionMode_SourceOver;
}

QPolygonF QgsLayoutItemMapGrid::scalePolygon( const QPolygonF &polygon, const double scale ) const
Expand Down Expand Up @@ -1219,7 +1219,11 @@ void QgsLayoutItemMapGrid::drawCoordinateAnnotation( QPainter *p, QPointF pos, c
ypos += ( mAnnotationFrameDistance + textHeight + gridFrameDistance );
xpos -= textWidth / 2.0;
if ( extension )
{
extension->bottom = std::max( extension->bottom, mAnnotationFrameDistance + gridFrameDistance + textHeight );
extension->left = std::max( extension->left, textWidth / 2.0 ); // annotation at bottom left/right may extend outside the bounds
extension->right = std::max( extension->right, textWidth / 2.0 );
}
}
else if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
{
Expand Down Expand Up @@ -2028,11 +2032,11 @@ void QgsLayoutItemMapGrid::calculateMaxExtension( double &top, double &right, do
QList< QPair< double, QLineF > > horizontalLines;
if ( mGridUnit == MapUnit && mCRS.isValid() && mCRS != mMap->crs() )
{
drawGridCrsTransform( context, 0, horizontalLines, verticalLines, false );
drawGridCrsTransform( context, 0, horizontalLines, verticalLines, true );
}
else
{
drawGridNoTransform( context, 0, horizontalLines, verticalLines, false );
drawGridNoTransform( context, 0, horizontalLines, verticalLines, true );
}

if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
Expand Down
7 changes: 5 additions & 2 deletions src/core/layout/qgslayoutitempage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include "qgspagesizeregistry.h"
#include "qgssymbollayerutils.h"
#include "qgslayoutitemundocommand.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"
#include <QPainter>
#include <QStyleOptionGraphicsItem>

Expand Down Expand Up @@ -214,10 +216,11 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph
//Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by
//anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols,
//but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width
maxBleedPixels--;
maxBleedPixels = std::floor( maxBleedPixels - 2 );

// round up
QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
( rect().width() * scale - 2 * maxBleedPixels ), ( rect().height() * scale - 2 * maxBleedPixels ) ) );
std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
QList<QPolygonF> rings; //empty list

symbol->renderPolygon( pagePolygon, &rings, nullptr, context );
Expand Down
2 changes: 1 addition & 1 deletion src/core/layout/qgslayoutitempage.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
/**
* Constructor for QgsLayoutItemPage, with the specified parent \a layout.
*/
explicit QgsLayoutItemPage( QgsLayout *layout SIP_TRANSFERTHIS );
explicit QgsLayoutItemPage( QgsLayout *layout );

/**
* Returns a new page item for the specified \a layout.
Expand Down
8 changes: 8 additions & 0 deletions src/core/layout/qgslayoutitempicture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,14 @@ void QgsLayoutItemPicture::refreshDataDefinedProperty( const QgsLayoutObject::Da
QgsLayoutItem::refreshDataDefinedProperty( property );
}

bool QgsLayoutItemPicture::containsAdvancedEffects() const
{
if ( QgsLayoutItem::containsAdvancedEffects() )
return true;

return mMode == FormatSVG && itemOpacity() < 1.0;
}

void QgsLayoutItemPicture::setPicturePath( const QString &path )
{
mSourcePath = path;
Expand Down
1 change: 1 addition & 0 deletions src/core/layout/qgslayoutitempicture.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
void recalculateSize();

void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;
bool containsAdvancedEffects() const override;

signals:
//! Is emitted on picture rotation change
Expand Down
1 change: 1 addition & 0 deletions src/core/layout/qgslayoutitemundocommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "qgsreadwritecontext.h"
#include "qgslayout.h"
#include "qgsproject.h"
#include "qgslayoutundostack.h"

///@cond PRIVATE
QgsLayoutItemUndoCommand::QgsLayoutItemUndoCommand( QgsLayoutItem *item, const QString &text, int id, QUndoCommand *parent )
Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutmultiframe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include "qgslayoutmultiframeundocommand.h"
#include "qgslayoutframe.h"
#include "qgslayout.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"
#include <QtCore>

QgsLayoutMultiFrame::QgsLayoutMultiFrame( QgsLayout *layout )
Expand Down
2 changes: 1 addition & 1 deletion src/core/layout/qgslayoutobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe
PresetPaperSize, //!< Preset paper size for composition
PaperWidth, //!< Paper width (deprecated)
PaperHeight, //!< Paper height (deprecated)
NumPages, //!< Number of pages in composition
NumPages, //!< Number of pages in composition (deprecated)
PaperOrientation, //!< Paper orientation
//general composer item properties
PageNumber, //!< Page number for item placement
Expand Down
Loading