Start work on exporter for layouts

This commit is contained in:
Nyall Dawson 2017-10-16 14:03:57 +10:00
parent 9630a39190
commit edecc37c9f
20 changed files with 412 additions and 54 deletions

View File

@ -158,6 +158,7 @@
%Include composer/qgscomposertexttable.sip
%Include composer/qgspaperitem.sip
%Include layout/qgslayoutaligner.sip
%Include layout/qgslayoutexporter.sip
%Include layout/qgslayoutgridsettings.sip
%Include layout/qgslayoutmeasurement.sip
%Include layout/qgslayoutmeasurementconverter.sip

View File

@ -62,6 +62,13 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
:rtype: QgsLayoutModel
%End
QgsLayoutExporter &exporter();
%Docstring
Returns the layout's exporter, which is used for rendering the layout and exporting
to various formats.
:rtype: QgsLayoutExporter
%End
QString name() const;
%Docstring
Returns the layout's name.

View File

@ -0,0 +1,57 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/layout/qgslayoutexporter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLayoutExporter
{
%Docstring
Handles rendering and exports of layouts to various formats.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutexporter.h"
%End
public:
QgsLayoutExporter( QgsLayout *layout );
%Docstring
Constructor for QgsLayoutExporter, for the specified ``layout``.
%End
void renderPage( QPainter *painter, int page );
%Docstring
Renders a full page to a destination ``painter``.
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.
.. seealso:: renderRect()
%End
void renderRegion( QPainter *painter, const QRectF &region );
%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:: renderPage()
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/layout/qgslayoutexporter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -589,14 +589,6 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
Draws the background for the item.
%End
bool isPreviewRender( QPainter *painter ) const;
%Docstring
Returns true if the render to the specified ``painter`` is a preview render,
i.e. is being rendered inside a QGraphicsView widget as opposed to a destination
device (such as an image).
:rtype: bool
%End
virtual void setFixedSize( const QgsLayoutSize &size );
%Docstring
Sets a fixed ``size`` for the layout item, which prevents it from being freely

View File

@ -72,6 +72,15 @@ class QgsLayoutUtils
:rtype: float
%End
static bool isPreviewRender( QPainter *painter );
%Docstring
Returns true if the render to the specified ``painter`` is a preview render,
i.e. is being rendered inside a QGraphicsView widget as opposed to a destination
device (such as an image).
:rtype: bool
%End
};
/************************************************************************

View File

@ -152,6 +152,44 @@ class QgsCompositionChecker : QgsMultiRenderChecker
};
class QgsLayoutChecker : QgsMultiRenderChecker
{
%Docstring
Renders a layout to an image and compares with an expected output
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsmultirenderchecker.h"
%End
public:
QgsLayoutChecker( const QString &testName, QgsLayout *layout );
%Docstring
Constructor for QgsLayoutChecker.
%End
void setSize( QSize size );
%Docstring
Sets the output (reference) image ``size``.
%End
bool runTest( QString &report, int page = 0, int pixelDiff = 0 );
%Docstring
Runs a render check on the layout, adding results to the specified ``report``.
The maximum number of allowable pixels differing from the reference image is
specified via the ``pixelDiff`` argument.
The page number is specified via ``page``, where 0 corresponds to the first
page in the layout.
Returns false if the rendered layout differs from the expected reference image.
:rtype: bool
%End
};
%End

View File

@ -363,6 +363,7 @@ SET(QGIS_CORE_SRCS
layout/qgslayoutaligner.cpp
layout/qgslayoutcontext.cpp
layout/qgslayouteffect.cpp
layout/qgslayoutexporter.cpp
layout/qgslayoutgridsettings.cpp
layout/qgslayoutguidecollection.cpp
layout/qgslayoutitem.cpp
@ -975,6 +976,7 @@ SET(QGIS_CORE_HDRS
composer/qgspaperitem.h
layout/qgslayoutaligner.h
layout/qgslayoutexporter.h
layout/qgslayoutgridsettings.h
layout/qgslayoutitemundocommand.h
layout/qgslayoutmeasurement.h

View File

@ -31,6 +31,7 @@ QgsLayout::QgsLayout( QgsProject *project )
, 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 );
@ -87,6 +88,11 @@ QgsLayoutModel *QgsLayout::itemsModel()
return mItemsModel.get();
}
QgsLayoutExporter &QgsLayout::exporter()
{
return mExporter;
}
QList<QgsLayoutItem *> QgsLayout::selectedLayoutItems( const bool includeLockedItems )
{
QList<QgsLayoutItem *> layoutItemList;

View File

@ -25,6 +25,7 @@
#include "qgslayoutgridsettings.h"
#include "qgslayoutguidecollection.h"
#include "qgslayoutundostack.h"
#include "qgslayoutexporter.h"
class QgsLayoutItemMap;
class QgsLayoutModel;
@ -83,6 +84,12 @@ 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()
@ -517,6 +524,7 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
std::unique_ptr< QgsLayoutPageCollection > mPageCollection;
std::unique_ptr< QgsLayoutUndoStack > mUndoStack;
QgsLayoutExporter mExporter;
bool mBlockUndoCommands = false;

View File

@ -0,0 +1,64 @@
/***************************************************************************
qgslayoutexporter.cpp
-------------------
begin : October 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 "qgslayoutexporter.h"
#include "qgslayout.h"
QgsLayoutExporter::QgsLayoutExporter( QgsLayout *layout )
: mLayout( layout )
{
}
void QgsLayoutExporter::renderPage( QPainter *painter, int page )
{
if ( !mLayout )
return;
if ( mLayout->pageCollection()->pageCount() <= page || page < 0 )
{
return;
}
QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page );
if ( !pageItem )
{
return;
}
QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() );
renderRegion( painter, paperRect );
}
void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF &region )
{
QPaintDevice *paintDevice = painter->device();
if ( !paintDevice || !mLayout )
{
return;
}
#if 0 //TODO
setSnapLinesVisible( false );
#endif
mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
#if 0 // TODO
setSnapLinesVisible( true );
#endif
}

View File

@ -0,0 +1,67 @@
/***************************************************************************
qgslayoutexporter.h
-------------------
begin : October 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 QGSLAYOUTEXPORTER_H
#define QGSLAYOUTEXPORTER_H
#include "qgis_core.h"
#include <QPointer>
class QgsLayout;
class QPainter;
/**
* \ingroup core
* \class QgsLayoutExporter
* \brief Handles rendering and exports of layouts to various formats.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutExporter
{
public:
/**
* Constructor for QgsLayoutExporter, for the specified \a layout.
*/
QgsLayoutExporter( QgsLayout *layout );
/**
* Renders a full page to a destination \a painter.
*
* 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.
*
* \see renderRect()
*/
void renderPage( QPainter *painter, int page );
/**
* 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()
*/
void renderRegion( QPainter *painter, const QRectF &region );
private:
QPointer< QgsLayout > mLayout;
};
#endif //QGSLAYOUTEXPORTER_H

View File

@ -433,7 +433,7 @@ bool QgsLayoutItem::shouldBlockUndoCommands() const
bool QgsLayoutItem::shouldDrawItem( QPainter *painter ) const
{
if ( isPreviewRender( painter ) )
if ( QgsLayoutUtils::isPreviewRender( painter ) )
{
//preview mode so OK to draw item
return true;
@ -767,31 +767,6 @@ void QgsLayoutItem::drawBackground( QgsRenderContext &context )
p->restore();
}
bool QgsLayoutItem::isPreviewRender( QPainter *painter ) const
{
if ( !painter || !painter->device() )
return false;
// if rendering to a QGraphicsView, we are in preview mode
QPaintDevice *device = painter->device();
if ( dynamic_cast< QPixmap * >( device ) )
return true;
QObject *obj = dynamic_cast< QObject *>( device );
if ( !obj )
return false;
const QMetaObject *mo = obj->metaObject();
while ( mo )
{
if ( mo->className() == QStringLiteral( "QGraphicsView" ) )
return true;
mo = mo->superClass();
}
return false;
}
void QgsLayoutItem::setFixedSize( const QgsLayoutSize &size )
{
mFixedSize = size;

View File

@ -583,13 +583,6 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
virtual void drawBackground( QgsRenderContext &context );
/**
* Returns true if the render to the specified \a painter is a preview render,
* i.e. is being rendered inside a QGraphicsView widget as opposed to a destination
* device (such as an image).
*/
bool isPreviewRender( QPainter *painter ) const;
/**
* Sets a fixed \a size for the layout item, which prevents it from being freely
* resized. Set an empty size if item can be freely resized.

View File

@ -178,9 +178,7 @@ void QgsLayoutItemPage::draw( QgsRenderContext &context, const QStyleOptionGraph
QPainter *painter = context.painter();
painter->save();
#if 0 //TODO
if ( mComposition->plotStyle() == QgsComposition::Preview )
#endif
if ( QgsLayoutUtils::isPreviewRender( context.painter() ) )
{
//if in preview mode, draw page border and shadow so that it's
//still possible to tell where pages with a transparent style begin and end
@ -256,6 +254,9 @@ void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphics
if ( !mLayout )
return;
if ( !QgsLayoutUtils::isPreviewRender( painter ) )
return;
const QgsLayoutContext &context = mLayout->context();
const QgsLayoutGridSettings &grid = mLayout->gridSettings();

View File

@ -109,3 +109,28 @@ double QgsLayoutUtils::relativePosition( const double position, const double bef
//return linearly scaled position
return m * position + c;
}
bool QgsLayoutUtils::isPreviewRender( QPainter *painter )
{
if ( !painter || !painter->device() )
return false;
// if rendering to a QGraphicsView, we are in preview mode
QPaintDevice *device = painter->device();
if ( dynamic_cast< QPixmap * >( device ) )
return true;
QObject *obj = dynamic_cast< QObject *>( device );
if ( !obj )
return false;
const QMetaObject *mo = obj->metaObject();
while ( mo )
{
if ( mo->className() == QStringLiteral( "QGraphicsView" ) )
return true;
mo = mo->superClass();
}
return false;
}

View File

@ -81,6 +81,14 @@ class CORE_EXPORT QgsLayoutUtils
*/
static double relativePosition( const double position, const double beforeMin, const double beforeMax, const double afterMin, const double afterMax );
/**
* Returns true if the render to the specified \a painter is a preview render,
* i.e. is being rendered inside a QGraphicsView widget as opposed to a destination
* device (such as an image).
*/
static bool isPreviewRender( QPainter *painter );
};
#endif //QGSLAYOUTUTILS_H

View File

@ -15,6 +15,7 @@
#include "qgsmultirenderchecker.h"
#include "qgscomposition.h"
#include "qgslayout.h"
#include <QDebug>
void QgsMultiRenderChecker::setControlName( const QString &name )
@ -170,6 +171,70 @@ bool QgsCompositionChecker::testComposition( QString &checkedReport, int page, i
return testResult;
}
//
// QgsLayoutChecker
//
QgsLayoutChecker::QgsLayoutChecker( const QString &testName, QgsLayout *layout )
: QgsMultiRenderChecker()
, mTestName( testName )
, mLayout( layout )
, mSize( 1122, 794 )
, mDotsPerMeter( 96 / 25.4 * 1000 )
{
// Qt has some slight render inconsistencies on the whole image sometimes
setColorTolerance( 5 );
}
bool QgsLayoutChecker::runTest( QString &checkedReport, int page, int pixelDiff )
{
if ( !mLayout )
{
return false;
}
setControlName( "expected_" + mTestName );
#if 0
//fake mode to generate expected image
//assume 96 dpi and size of the control image 1122 * 794
QImage newImage( QSize( 1122, 794 ), QImage::Format_RGB32 );
mComposition->setPlotStyle( QgsComposition::Print );
newImage.setDotsPerMeterX( 96 / 25.4 * 1000 );
newImage.setDotsPerMeterY( 96 / 25.4 * 1000 );
drawBackground( &newImage );
QPainter expectedPainter( &newImage );
//QRectF sourceArea( 0, 0, mComposition->paperWidth(), mComposition->paperHeight() );
//QRectF targetArea( 0, 0, 3507, 2480 );
mComposition->renderPage( &expectedPainter, page );
expectedPainter.end();
newImage.save( controlImagePath() + QDir::separator() + "expected_" + mTestName + ".png", "PNG" );
return true;
#endif //0
QImage outputImage( mSize, QImage::Format_RGB32 );
outputImage.setDotsPerMeterX( mDotsPerMeter );
outputImage.setDotsPerMeterY( mDotsPerMeter );
drawBackground( &outputImage );
QPainter p( &outputImage );
mLayout->exporter().renderPage( &p, page );
p.end();
QString renderedFilePath = QDir::tempPath() + '/' + QFileInfo( mTestName ).baseName() + "_rendered.png";
outputImage.save( renderedFilePath, "PNG" );
setRenderedImage( renderedFilePath );
bool testResult = runTest( mTestName, pixelDiff );
checkedReport += report();
return testResult;
}
///@endcond
#endif

View File

@ -165,6 +165,48 @@ class CORE_EXPORT QgsCompositionChecker : public QgsMultiRenderChecker
QSize mSize;
int mDotsPerMeter;
};
/**
* \ingroup core
* \class QgsLayoutChecker
* Renders a layout to an image and compares with an expected output
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutChecker : public QgsMultiRenderChecker
{
public:
/**
* Constructor for QgsLayoutChecker.
*/
QgsLayoutChecker( const QString &testName, QgsLayout *layout );
/**
* Sets the output (reference) image \a size.
*/
void setSize( QSize size ) { mSize = size; }
/**
* Runs a render check on the layout, adding results to the specified \a report.
*
* The maximum number of allowable pixels differing from the reference image is
* specified via the \a pixelDiff argument.
*
* The page number is specified via \a page, where 0 corresponds to the first
* page in the layout.
*
* Returns false if the rendered layout differs from the expected reference image.
*/
bool runTest( QString &report, int page = 0, int pixelDiff = 0 );
private:
QgsLayoutChecker() = delete;
QString mTestName;
QgsLayout *mLayout = nullptr;
QSize mSize;
int mDotsPerMeter;
};
///@endcond
SIP_END

View File

@ -57,14 +57,11 @@ void QgsLayoutMouseHandles::paint( QPainter *painter, const QStyleOptionGraphics
Q_UNUSED( itemStyle );
Q_UNUSED( pWidget );
//TODO
#if 0
if ( mLayout->plotStyle() != QgsComposition::Preview )
if ( !QgsLayoutUtils::isPreviewRender( painter ) )
{
//don't draw selection handles in composition outputs
//don't draw selection handles in layout outputs
return;
}
#endif
if ( mLayout->context().boundingBoxesVisible() )
{

View File

@ -26,6 +26,7 @@
#include "qgslayoutitemshape.h"
#include "qgsproject.h"
#include "qgsgui.h"
#include "qgslayoututils.h"
#include <QtTest/QSignalSpy>
#include <QSvgGenerator>
#include <QPrinter>
@ -344,7 +345,7 @@ class TestViewItem : public QgsLayoutItem
void paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * ) override
{
mDrawn = true;
mPreview = isPreviewRender( painter );
mPreview = QgsLayoutUtils::isPreviewRender( painter );
}
void draw( QgsRenderContext &, const QStyleOptionGraphicsItem * ) override
{
@ -369,18 +370,18 @@ void TestQgsLayoutView::isPreviewRender()
// render to image
QVERIFY( !item->isPreviewRender( nullptr ) );
QVERIFY( !QgsLayoutUtils::isPreviewRender( nullptr ) );
QImage im = QImage( 250, 250, QImage::Format_RGB32 );
QPainter painter;
QVERIFY( painter.begin( &im ) );
QVERIFY( !item->isPreviewRender( &painter ) );
QVERIFY( !QgsLayoutUtils::isPreviewRender( &painter ) );
painter.end();
// render to svg
QSvgGenerator generator;
generator.setFileName( QDir::tempPath() + "/layout_text.svg" );
QVERIFY( painter.begin( &generator ) );
QVERIFY( !item->isPreviewRender( &painter ) );
QVERIFY( !QgsLayoutUtils::isPreviewRender( &painter ) );
painter.end();
// render to pdf
@ -389,7 +390,7 @@ void TestQgsLayoutView::isPreviewRender()
printer.setOutputFormat( QPrinter::PdfFormat );
printer.setOutputFileName( QDir::tempPath() + "/layout_text.pdf" );
QVERIFY( painter.begin( &printer ) );
QVERIFY( !item->isPreviewRender( &painter ) );
QVERIFY( !QgsLayoutUtils::isPreviewRender( &painter ) );
painter.end();
// render in view - kinda gross!