mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
Merge pull request #5897 from nyalldawson/layout_next
Misc layout fixes
This commit is contained in:
commit
a5f7f410a1
@ -206,6 +206,11 @@ Resets all other pages' guides to match the guides from the specified ``sourcePa
|
||||
void update();
|
||||
%Docstring
|
||||
Updates the position (and visibility) of all guide line items.
|
||||
%End
|
||||
|
||||
QList< QgsLayoutGuide * > guides();
|
||||
%Docstring
|
||||
Returns a list of all guides contained in the collection.
|
||||
%End
|
||||
|
||||
QList< QgsLayoutGuide * > guides( Qt::Orientation orientation, int page = -1 );
|
||||
|
@ -84,6 +84,16 @@ Default value is 0.
|
||||
|
||||
:param colorTolerance: The maximum difference for each color component
|
||||
including alpha to be considered correct.
|
||||
%End
|
||||
|
||||
void setSizeTolerance( int xTolerance, int yTolerance );
|
||||
%Docstring
|
||||
Sets the largest allowable difference in size between the rendered and the expected image.
|
||||
|
||||
:param xTolerance: x tolerance in pixels
|
||||
:param yTolerance: y tolerance in pixels
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
bool runTest( const QString &testName, unsigned int mismatchCount = 0 );
|
||||
|
@ -1529,11 +1529,12 @@ void QgsLayoutDesignerDialog::exportToRaster()
|
||||
if ( imageDlg.antialiasing() )
|
||||
settings.flags |= QgsLayoutContext::FlagAntialiasing;
|
||||
|
||||
QFileInfo fi( fileNExt.first );
|
||||
switch ( exporter.exportToImage( fileNExt.first, settings ) )
|
||||
{
|
||||
case QgsLayoutExporter::Success:
|
||||
mMessageBar->pushMessage( tr( "Export layout" ),
|
||||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fileNExt.first ).toString(), fileNExt.first ),
|
||||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fi.path() ).toString(), fileNExt.first ),
|
||||
QgsMessageBar::INFO, 0 );
|
||||
break;
|
||||
|
||||
@ -1630,13 +1631,14 @@ void QgsLayoutDesignerDialog::exportToPdf()
|
||||
// force a refresh, to e.g. update data defined properties, tables, etc
|
||||
mLayout->refresh();
|
||||
|
||||
QFileInfo fi( outputFileName );
|
||||
QgsLayoutExporter exporter( mLayout );
|
||||
switch ( exporter.exportToPdf( outputFileName, pdfSettings ) )
|
||||
{
|
||||
case QgsLayoutExporter::Success:
|
||||
{
|
||||
mMessageBar->pushMessage( tr( "Export layout" ),
|
||||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( outputFileName ).toString(), outputFileName ),
|
||||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fi.path() ).toString(), outputFileName ),
|
||||
QgsMessageBar::INFO, 0 );
|
||||
break;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "qgslayoutpagecollection.h"
|
||||
#include "qgsogrutils.h"
|
||||
#include "qgspaintenginehack.h"
|
||||
#include "qgslayoutguidecollection.h"
|
||||
#include <QImageWriter>
|
||||
#include <QSize>
|
||||
|
||||
@ -48,6 +49,34 @@ class LayoutContextPreviewSettingRestorer
|
||||
bool mPreviousSetting = false;
|
||||
};
|
||||
|
||||
class LayoutGuideHider
|
||||
{
|
||||
public:
|
||||
|
||||
LayoutGuideHider( QgsLayout *layout )
|
||||
: mLayout( layout )
|
||||
{
|
||||
const QList< QgsLayoutGuide * > guides = mLayout->guides().guides();
|
||||
for ( QgsLayoutGuide *guide : guides )
|
||||
{
|
||||
mPrevVisibility.insert( guide, guide->item()->isVisible() );
|
||||
guide->item()->setVisible( false );
|
||||
}
|
||||
}
|
||||
|
||||
~LayoutGuideHider()
|
||||
{
|
||||
for ( auto it = mPrevVisibility.constBegin(); it != mPrevVisibility.constEnd(); ++it )
|
||||
{
|
||||
it.key()->item()->setVisible( it.value() );
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QgsLayout *mLayout = nullptr;
|
||||
QHash< QgsLayoutGuide *, bool > mPrevVisibility;
|
||||
};
|
||||
|
||||
///@endcond PRIVATE
|
||||
|
||||
QgsLayoutExporter::QgsLayoutExporter( QgsLayout *layout )
|
||||
@ -150,18 +179,12 @@ void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF ®ion )
|
||||
( void )cacheRestorer;
|
||||
LayoutContextPreviewSettingRestorer restorer( mLayout );
|
||||
( void )restorer;
|
||||
|
||||
#if 0 //TODO
|
||||
setSnapLinesVisible( false );
|
||||
#endif
|
||||
LayoutGuideHider guideHider( mLayout );
|
||||
( void ) guideHider;
|
||||
|
||||
painter->setRenderHint( QPainter::Antialiasing, mLayout->context().flags() & QgsLayoutContext::FlagAntialiasing );
|
||||
|
||||
mLayout->render( painter, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), region );
|
||||
|
||||
#if 0 // TODO
|
||||
setSnapLinesVisible( true );
|
||||
#endif
|
||||
}
|
||||
|
||||
QImage QgsLayoutExporter::renderRegionToImage( const QRectF ®ion, QSize imageSize, double dpi ) const
|
||||
|
@ -89,7 +89,7 @@ void QgsLayoutGuide::update()
|
||||
}
|
||||
else
|
||||
{
|
||||
mLineItem->setLine( 0, layoutPos, mPage->rect().width(), layoutPos );
|
||||
mLineItem->setLine( 0, layoutPos + mPage->y(), mPage->rect().width(), layoutPos + mPage->y() );
|
||||
mLineItem->setVisible( showGuide );
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ void QgsLayoutGuide::update()
|
||||
}
|
||||
else
|
||||
{
|
||||
mLineItem->setLine( layoutPos, 0, layoutPos, mPage->rect().height() );
|
||||
mLineItem->setLine( layoutPos, mPage->y(), layoutPos, mPage->y() + mPage->rect().height() );
|
||||
mLineItem->setVisible( showGuide );
|
||||
}
|
||||
|
||||
@ -467,6 +467,11 @@ void QgsLayoutGuideCollection::update()
|
||||
}
|
||||
}
|
||||
|
||||
QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides()
|
||||
{
|
||||
return mGuides;
|
||||
}
|
||||
|
||||
QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides( Qt::Orientation orientation, int page )
|
||||
{
|
||||
QList<QgsLayoutGuide *> res;
|
||||
|
@ -235,6 +235,11 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel, public
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* Returns a list of all guides contained in the collection.
|
||||
*/
|
||||
QList< QgsLayoutGuide * > guides();
|
||||
|
||||
/**
|
||||
* Returns the list of guides contained in the collection with the specified
|
||||
* \a orientation and on a matching \a page.
|
||||
|
@ -95,6 +95,7 @@ void QgsLayoutPageCollection::reflow()
|
||||
currentY += mLayout->convertToLayoutUnits( page->pageSize() ).height() + spaceBetweenPages();
|
||||
p.setY( currentY );
|
||||
}
|
||||
mLayout->guides().update();
|
||||
mLayout->updateBounds();
|
||||
emit changed();
|
||||
}
|
||||
@ -193,7 +194,8 @@ int QgsLayoutPageCollection::predictPageNumberForPoint( QPointF point ) const
|
||||
|
||||
QgsLayoutItemPage *QgsLayoutPageCollection::pageAtPoint( QPointF point ) const
|
||||
{
|
||||
Q_FOREACH ( QGraphicsItem *item, mLayout->items( point ) )
|
||||
const QList< QGraphicsItem * > items = mLayout->items( point );
|
||||
for ( QGraphicsItem *item : items )
|
||||
{
|
||||
if ( item->type() == QgsLayoutItemRegistry::LayoutPage )
|
||||
{
|
||||
|
@ -55,6 +55,7 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma
|
||||
QgsRenderChecker checker;
|
||||
checker.enableDashBuffering( true );
|
||||
checker.setColorTolerance( mColorTolerance );
|
||||
checker.setSizeTolerance( mMaxSizeDifferenceX, mMaxSizeDifferenceY );
|
||||
checker.setControlPathPrefix( mControlPathPrefix );
|
||||
checker.setControlPathSuffix( suffix );
|
||||
checker.setControlName( mControlName );
|
||||
|
@ -94,6 +94,14 @@ class CORE_EXPORT QgsMultiRenderChecker
|
||||
*/
|
||||
void setColorTolerance( unsigned int colorTolerance ) { mColorTolerance = colorTolerance; }
|
||||
|
||||
/**
|
||||
* Sets the largest allowable difference in size between the rendered and the expected image.
|
||||
* \param xTolerance x tolerance in pixels
|
||||
* \param yTolerance y tolerance in pixels
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
void setSizeTolerance( int xTolerance, int yTolerance ) { mMaxSizeDifferenceX = xTolerance; mMaxSizeDifferenceY = yTolerance; }
|
||||
|
||||
/**
|
||||
* Test using renderer to generate the image to be compared.
|
||||
*
|
||||
@ -134,6 +142,8 @@ class CORE_EXPORT QgsMultiRenderChecker
|
||||
QString mControlName;
|
||||
QString mControlPathPrefix;
|
||||
unsigned int mColorTolerance = 0;
|
||||
int mMaxSizeDifferenceX = 0;
|
||||
int mMaxSizeDifferenceY = 0;
|
||||
QgsMapSettings mMapSettings;
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,7 @@ import sip
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from qgis.core import (QgsMultiRenderChecker,
|
||||
QgsLayoutExporter,
|
||||
@ -24,10 +25,13 @@ from qgis.core import (QgsMultiRenderChecker,
|
||||
QgsProject,
|
||||
QgsMargins,
|
||||
QgsLayoutItemShape,
|
||||
QgsLayoutGuide,
|
||||
QgsRectangle,
|
||||
QgsLayoutItemPage,
|
||||
QgsLayoutItemMap,
|
||||
QgsLayoutPoint,
|
||||
QgsLayoutMeasurement,
|
||||
QgsUnitTypes,
|
||||
QgsSimpleFillSymbolLayer,
|
||||
QgsFillSymbol)
|
||||
from qgis.PyQt.QtCore import QSize, QSizeF, QDir, QRectF, Qt
|
||||
@ -35,6 +39,58 @@ from qgis.PyQt.QtGui import QImage, QPainter
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
from utilities import getExecutablePath
|
||||
|
||||
# PDF-to-image utility
|
||||
# look for Poppler w/ Cairo, then muPDF
|
||||
# * Poppler w/ Cairo renders correctly
|
||||
# * Poppler w/o Cairo does not always correctly render vectors in PDF to image
|
||||
# * muPDF renders correctly, but sightly shifts colors
|
||||
for util in [
|
||||
'pdftocairo',
|
||||
# 'mudraw',
|
||||
]:
|
||||
PDFUTIL = getExecutablePath(util)
|
||||
if PDFUTIL:
|
||||
break
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if not PDFUTIL:
|
||||
raise Exception('PDF-to-image utility not found on PATH: '
|
||||
'install Poppler (with Cairo)')
|
||||
|
||||
|
||||
def pdfToPng(pdf_file_path, rendered_file_path, page, dpi=96):
|
||||
if PDFUTIL.strip().endswith('pdftocairo'):
|
||||
filebase = os.path.join(
|
||||
os.path.dirname(rendered_file_path),
|
||||
os.path.splitext(os.path.basename(rendered_file_path))[0]
|
||||
)
|
||||
call = [
|
||||
PDFUTIL, '-png', '-singlefile', '-r', str(dpi),
|
||||
'-x', '0', '-y', '0', '-f', str(page), '-l', str(page),
|
||||
pdf_file_path, filebase
|
||||
]
|
||||
elif PDFUTIL.strip().endswith('mudraw'):
|
||||
call = [
|
||||
PDFUTIL, '-c', 'rgba',
|
||||
'-r', str(dpi), '-f', str(page), '-l', str(page),
|
||||
# '-b', '8',
|
||||
'-o', rendered_file_path, pdf_file_path
|
||||
]
|
||||
else:
|
||||
return False, ''
|
||||
|
||||
print("exportToPdf call: {0}".format(' '.join(call)))
|
||||
try:
|
||||
subprocess.check_call(call)
|
||||
except subprocess.CalledProcessError as e:
|
||||
assert False, ("exportToPdf failed!\n"
|
||||
"cmd: {0}\n"
|
||||
"returncode: {1}\n"
|
||||
"message: {2}".format(e.cmd, e.returncode, e.message))
|
||||
|
||||
|
||||
start_app()
|
||||
|
||||
|
||||
@ -54,12 +110,13 @@ class TestQgsLayoutExporter(unittest.TestCase):
|
||||
with open(report_file_path, 'a') as report_file:
|
||||
report_file.write(self.report)
|
||||
|
||||
def checkImage(self, name, reference_image, rendered_image):
|
||||
def checkImage(self, name, reference_image, rendered_image, size_tolerance=0):
|
||||
checker = QgsMultiRenderChecker()
|
||||
checker.setControlPathPrefix("layout_exporter")
|
||||
checker.setControlName("expected_layoutexporter_" + reference_image)
|
||||
checker.setRenderedImage(rendered_image)
|
||||
checker.setColorTolerance(2)
|
||||
checker.setSizeTolerance(size_tolerance, size_tolerance)
|
||||
result = checker.runTest(name, 20)
|
||||
self.report += checker.report()
|
||||
print((self.report))
|
||||
@ -134,6 +191,10 @@ class TestQgsLayoutExporter(unittest.TestCase):
|
||||
l = QgsLayout(QgsProject.instance())
|
||||
l.initializeDefaults()
|
||||
|
||||
# add a guide, to ensure it is not included in export
|
||||
g1 = QgsLayoutGuide(Qt.Horizontal, QgsLayoutMeasurement(15, QgsUnitTypes.LayoutMillimeters), l.pageCollection().page(0))
|
||||
l.guides().addGuide(g1)
|
||||
|
||||
# add some items
|
||||
item1 = QgsLayoutItemShape(l)
|
||||
item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
|
||||
@ -278,6 +339,57 @@ class TestQgsLayoutExporter(unittest.TestCase):
|
||||
page2_path = os.path.join(self.basetestpath, 'test_exporttoimagesize_2.png')
|
||||
self.assertTrue(self.checkImage('exporttoimagesize_page2', 'exporttoimagesize_page2', page2_path))
|
||||
|
||||
def testExportToPdf(self):
|
||||
l = QgsLayout(QgsProject.instance())
|
||||
l.initializeDefaults()
|
||||
|
||||
# add a second page
|
||||
page2 = QgsLayoutItemPage(l)
|
||||
page2.setPageSize('A5')
|
||||
l.pageCollection().addPage(page2)
|
||||
|
||||
# add some items
|
||||
item1 = QgsLayoutItemShape(l)
|
||||
item1.attemptSetSceneRect(QRectF(10, 20, 100, 150))
|
||||
fill = QgsSimpleFillSymbolLayer()
|
||||
fill_symbol = QgsFillSymbol()
|
||||
fill_symbol.changeSymbolLayer(0, fill)
|
||||
fill.setColor(Qt.green)
|
||||
fill.setStrokeStyle(Qt.NoPen)
|
||||
item1.setSymbol(fill_symbol)
|
||||
l.addItem(item1)
|
||||
|
||||
item2 = QgsLayoutItemShape(l)
|
||||
item2.attemptSetSceneRect(QRectF(10, 20, 100, 150))
|
||||
item2.attemptMove(QgsLayoutPoint(10, 20), page=1)
|
||||
fill = QgsSimpleFillSymbolLayer()
|
||||
fill_symbol = QgsFillSymbol()
|
||||
fill_symbol.changeSymbolLayer(0, fill)
|
||||
fill.setColor(Qt.cyan)
|
||||
fill.setStrokeStyle(Qt.NoPen)
|
||||
item2.setSymbol(fill_symbol)
|
||||
l.addItem(item2)
|
||||
|
||||
exporter = QgsLayoutExporter(l)
|
||||
# setup settings
|
||||
settings = QgsLayoutExporter.PdfExportSettings()
|
||||
settings.dpi = 80
|
||||
settings.rasterizeWholeImage = False
|
||||
settings.forceVectorOutput = False
|
||||
|
||||
pdf_file_path = os.path.join(self.basetestpath, 'test_exporttopdfdpi.pdf')
|
||||
self.assertEqual(exporter.exportToPdf(pdf_file_path, settings), QgsLayoutExporter.Success)
|
||||
self.assertTrue(os.path.exists(pdf_file_path))
|
||||
|
||||
rendered_page_1 = os.path.join(self.basetestpath, 'test_exporttopdfdpi.png')
|
||||
dpi = 80
|
||||
pdfToPng(pdf_file_path, rendered_page_1, dpi=dpi, page=1)
|
||||
rendered_page_2 = os.path.join(self.basetestpath, 'test_exporttopdfdpi2.png')
|
||||
pdfToPng(pdf_file_path, rendered_page_2, dpi=dpi, page=2)
|
||||
|
||||
self.assertTrue(self.checkImage('exporttopdfdpi_page1', 'exporttopdfdpi_page1', rendered_page_1, size_tolerance=1))
|
||||
self.assertTrue(self.checkImage('exporttopdfdpi_page2', 'exporttopdfdpi_page2', rendered_page_2, size_tolerance=1))
|
||||
|
||||
def testExportWorldFile(self):
|
||||
l = QgsLayout(QgsProject.instance())
|
||||
l.initializeDefaults()
|
||||
|
@ -65,6 +65,11 @@ class TestQgsLayoutGuide(unittest.TestCase):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
l.initializeDefaults() # add a page
|
||||
# add a second page
|
||||
page2 = QgsLayoutItemPage(l)
|
||||
page2.setPageSize('A5')
|
||||
l.pageCollection().addPage(page2)
|
||||
|
||||
g = QgsLayoutGuide(Qt.Horizontal, QgsLayoutMeasurement(5, QgsUnitTypes.LayoutCentimeters), l.pageCollection().page(0))
|
||||
g.setLayout(l)
|
||||
g.update()
|
||||
@ -85,6 +90,19 @@ class TestQgsLayoutGuide(unittest.TestCase):
|
||||
self.assertEqual(g.item().line().y2(), 15)
|
||||
self.assertEqual(g.layoutPosition(), 15)
|
||||
|
||||
# guide on page2
|
||||
g1 = QgsLayoutGuide(Qt.Horizontal, QgsLayoutMeasurement(5, QgsUnitTypes.LayoutCentimeters), l.pageCollection().page(1))
|
||||
g1.setLayout(l)
|
||||
g1.update()
|
||||
g1.setPosition(QgsLayoutMeasurement(15, QgsUnitTypes.LayoutMillimeters))
|
||||
g1.update()
|
||||
self.assertTrue(g1.item().isVisible())
|
||||
self.assertEqual(g1.item().line().x1(), 0)
|
||||
self.assertEqual(g1.item().line().y1(), 235)
|
||||
self.assertEqual(g1.item().line().x2(), 148)
|
||||
self.assertEqual(g1.item().line().y2(), 235)
|
||||
self.assertEqual(g1.layoutPosition(), 235)
|
||||
|
||||
# vertical guide
|
||||
g2 = QgsLayoutGuide(Qt.Vertical, QgsLayoutMeasurement(5, QgsUnitTypes.LayoutCentimeters), l.pageCollection().page(0))
|
||||
g2.setLayout(l)
|
||||
@ -109,6 +127,17 @@ class TestQgsLayoutGuide(unittest.TestCase):
|
||||
g.update()
|
||||
self.assertFalse(g.item().isVisible())
|
||||
|
||||
# guide on page2
|
||||
g3 = QgsLayoutGuide(Qt.Vertical, QgsLayoutMeasurement(5, QgsUnitTypes.LayoutCentimeters), l.pageCollection().page(1))
|
||||
g3.setLayout(l)
|
||||
g3.update()
|
||||
self.assertTrue(g3.item().isVisible())
|
||||
self.assertEqual(g3.item().line().x1(), 50)
|
||||
self.assertEqual(g3.item().line().y1(), 220)
|
||||
self.assertEqual(g3.item().line().x2(), 50)
|
||||
self.assertEqual(g3.item().line().y2(), 430)
|
||||
self.assertEqual(g3.layoutPosition(), 50)
|
||||
|
||||
def testCollection(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Loading…
x
Reference in New Issue
Block a user