From 3ace4676fa803a7428a3b34ecd9ef124ef265cb8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 31 May 2024 14:59:18 +1000 Subject: [PATCH] Improve results when using path stroking Use GEOS to do the path stroking --- .../painting/qgsgeometrypaintdevice.sip.in | 1 + .../painting/qgsgeometrypaintdevice.sip.in | 1 + src/core/painting/qgsgeometrypaintdevice.cpp | 146 ++++++++++++------ src/core/painting/qgsgeometrypaintdevice.h | 9 ++ .../src/python/test_qgsgeometrypaintdevice.py | 26 ++-- 5 files changed, 129 insertions(+), 54 deletions(-) diff --git a/python/PyQt6/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in b/python/PyQt6/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in index 507b2d1270e..b1254eda088 100644 --- a/python/PyQt6/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in +++ b/python/PyQt6/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in @@ -11,6 +11,7 @@ + class QgsGeometryPaintDevice: QPaintDevice { %Docstring(signature="appended") diff --git a/python/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in b/python/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in index 507b2d1270e..b1254eda088 100644 --- a/python/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in +++ b/python/core/auto_generated/painting/qgsgeometrypaintdevice.sip.in @@ -11,6 +11,7 @@ + class QgsGeometryPaintDevice: QPaintDevice { %Docstring(signature="appended") diff --git a/src/core/painting/qgsgeometrypaintdevice.cpp b/src/core/painting/qgsgeometrypaintdevice.cpp index e44d6d4651a..9d343b0a3a4 100644 --- a/src/core/painting/qgsgeometrypaintdevice.cpp +++ b/src/core/painting/qgsgeometrypaintdevice.cpp @@ -48,8 +48,12 @@ QPaintEngine::Type QgsGeometryPaintEngine::type() const return QPaintEngine::User; } -void QgsGeometryPaintEngine::updateState( const QPaintEngineState & ) +void QgsGeometryPaintEngine::updateState( const QPaintEngineState &state ) { + if ( mUsePathStroker && state.state().testFlag( QPaintEngine::DirtyFlag::DirtyPen ) ) + { + mPen = state.pen(); + } } void QgsGeometryPaintEngine::drawImage( const QRectF &, const QImage &, const QRectF &, Qt::ImageConversionFlags ) @@ -320,12 +324,76 @@ void QgsGeometryPaintEngine::drawPolygon( const QPointF *points, int pointCount, } } +void QgsGeometryPaintEngine::addStrokedLine( const QgsLineString *line, double penWidth, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, const QTransform *matrix ) +{ + QgsGeos geos( line ); + + std::unique_ptr< QgsAbstractGeometry > buffered( geos.buffer( penWidth / 2, mStrokedPathsSegments, endCapStyle, joinStyle, miterLimit ) ); + if ( !buffered ) + return; + + if ( matrix ) + buffered->transform( *matrix ); + + if ( QgsGeometryCollection *bufferedCollection = qgsgeometry_cast< QgsGeometryCollection * >( buffered.get() ) ) + { + mGeometry.addGeometries( bufferedCollection->takeGeometries() ); + } + else if ( buffered ) + { + mGeometry.addGeometry( buffered.release() ); + } +} + +Qgis::EndCapStyle QgsGeometryPaintEngine::penStyleToCapStyle( Qt::PenCapStyle style ) +{ + switch ( style ) + { + case Qt::FlatCap: + return Qgis::EndCapStyle::Flat; + case Qt::SquareCap: + return Qgis::EndCapStyle::Square; + case Qt::RoundCap: + return Qgis::EndCapStyle::Round; + case Qt::MPenCapStyle: + // undocumented? + break; + } + + return Qgis::EndCapStyle::Round; +} + +Qgis::JoinStyle QgsGeometryPaintEngine::penStyleToJoinStyle( Qt::PenJoinStyle style ) +{ + switch ( style ) + { + case Qt::MiterJoin: + case Qt::SvgMiterJoin: + return Qgis::JoinStyle::Miter; + case Qt::BevelJoin: + return Qgis::JoinStyle::Bevel; + case Qt::RoundJoin: + return Qgis::JoinStyle::Round; + case Qt::MPenJoinStyle: + // undocumented? + break; + } + return Qgis::JoinStyle::Round; +} + // based on QPainterPath::toSubpathPolygons() -void addSubpathGeometries( QgsGeometryCollection &collection, const QPainterPath &path, const QTransform &matrix ) +void QgsGeometryPaintEngine::addSubpathGeometries( const QPainterPath &path, const QTransform &matrix ) { if ( path.isEmpty() ) return; + const bool transformIsIdentity = matrix.isIdentity(); + + const Qgis::EndCapStyle endCapStyle = penStyleToCapStyle( mPen.capStyle() ); + const Qgis::JoinStyle joinStyle = penStyleToJoinStyle( mPen.joinStyle() ); + const double penWidth = mPen.widthF() <= 0 ? 1 : mPen.widthF(); + const double miterLimit = mPen.miterLimit(); + QVector< double > currentX; QVector< double > currentY; const int count = path.elementCount(); @@ -343,13 +411,21 @@ void addSubpathGeometries( QgsGeometryCollection &collection, const QPainterPath if ( currentX.size() > 1 ) { std::unique_ptr< QgsLineString > line = std::make_unique< QgsLineString >( currentX, currentY ); - if ( line->isClosed() ) + if ( mUsePathStroker ) { + addStrokedLine( line.get(), penWidth, endCapStyle, joinStyle, miterLimit, transformIsIdentity ? nullptr : &matrix ); + } + else if ( line->isClosed() ) + { + if ( !transformIsIdentity ) + line->transform( matrix ); queuedPolygons.emplace_back( std::make_unique< QgsPolygon >( line.release() ) ); } else { - collection.addGeometry( line.release() ); + if ( !transformIsIdentity ) + line->transform( matrix ); + mGeometry.addGeometry( line.release() ); } } currentX.resize( 0 ); @@ -357,19 +433,15 @@ void addSubpathGeometries( QgsGeometryCollection &collection, const QPainterPath currentX.reserve( 16 ); currentY.reserve( 16 ); - double tx, ty; - matrix.map( e.x, e.y, &tx, &ty ); - currentX << tx; - currentY << ty; + currentX << e.x; + currentY << e.y; break; } case QPainterPath::LineToElement: { - double tx, ty; - matrix.map( e.x, e.y, &tx, &ty ); - currentX << tx; - currentY << ty; + currentX << e.x; + currentY << e.y; break; } @@ -381,30 +453,18 @@ void addSubpathGeometries( QgsGeometryCollection &collection, const QPainterPath const double x1 = path.elementAt( i - 1 ).x; const double y1 = path.elementAt( i - 1 ).y; - double tx1, ty1; - matrix.map( x1, y1, &tx1, &ty1 ); - - double tx2, ty2; - matrix.map( e.x, e.y, &tx2, &ty2 ); - const double x3 = path.elementAt( i + 1 ).x; const double y3 = path.elementAt( i + 1 ).y; - double tx3, ty3; - matrix.map( x3, y3, &tx3, &ty3 ); - const double x4 = path.elementAt( i + 2 ).x; const double y4 = path.elementAt( i + 2 ).y; - double tx4, ty4; - matrix.map( x4, y4, &tx4, &ty4 ); - // TODO -- we could likely reduce the number of segmented points here! std::unique_ptr< QgsLineString> bezier( QgsLineString::fromBezierCurve( - QgsPoint( tx1, ty1 ), - QgsPoint( tx2, ty2 ), - QgsPoint( tx3, ty3 ), - QgsPoint( tx4, ty4 ) ) ); + QgsPoint( x1, y1 ), + QgsPoint( e.x, e.y ), + QgsPoint( x3, y3 ), + QgsPoint( x4, y4 ) ) ); currentX << bezier->xVector(); currentY << bezier->yVector(); @@ -421,20 +481,28 @@ void addSubpathGeometries( QgsGeometryCollection &collection, const QPainterPath if ( currentX.size() > 1 ) { std::unique_ptr< QgsLineString > line = std::make_unique< QgsLineString >( currentX, currentY ); - if ( line->isClosed() ) + if ( mUsePathStroker ) { + addStrokedLine( line.get(), penWidth, endCapStyle, joinStyle, miterLimit, transformIsIdentity ? nullptr : &matrix ); + } + else if ( line->isClosed() ) + { + if ( !transformIsIdentity ) + line->transform( matrix ); queuedPolygons.emplace_back( std::make_unique< QgsPolygon >( line.release() ) ); } else { - collection.addGeometry( line.release() ); + if ( !transformIsIdentity ) + line->transform( matrix ); + mGeometry.addGeometry( line.release() ); } } if ( queuedPolygons.empty() ) return; - collection.reserve( collection.numGeometries() + queuedPolygons.size() ); + mGeometry.reserve( mGeometry.numGeometries() + queuedPolygons.size() ); QgsMultiPolygon tempMultiPolygon; tempMultiPolygon.reserve( queuedPolygons.size() ); @@ -451,24 +519,14 @@ void addSubpathGeometries( QgsGeometryCollection &collection, const QPainterPath for ( auto it = g->const_parts_begin(); it != g->const_parts_end(); ++it ) { - collection.addGeometry( ( *it )->clone() ); + mGeometry.addGeometry( ( *it )->clone() ); } } void QgsGeometryPaintEngine::drawPath( const QPainterPath &path ) { - QPainterPath realPath = path; - if ( mUsePathStroker ) - { - QPen pen = painter()->pen(); - QPainterPathStroker stroker( pen ); - QPainterPath strokedPath = stroker.createStroke( path ); - realPath = strokedPath; - } - - QTransform transform = painter()->combinedTransform(); - - addSubpathGeometries( mGeometry, realPath, transform ); + const QTransform transform = painter()->combinedTransform(); + addSubpathGeometries( path, transform ); } // diff --git a/src/core/painting/qgsgeometrypaintdevice.h b/src/core/painting/qgsgeometrypaintdevice.h index 90c937980e0..8da977b11fa 100644 --- a/src/core/painting/qgsgeometrypaintdevice.h +++ b/src/core/painting/qgsgeometrypaintdevice.h @@ -25,6 +25,8 @@ #include #include +class QgsLineString; + #ifndef SIP_RUN /** @@ -82,8 +84,15 @@ class QgsGeometryPaintEngine: public QPaintEngine private: + void addSubpathGeometries( const QPainterPath &path, const QTransform &matrix ); + void addStrokedLine( const QgsLineString *line, double penWidth, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, const QTransform *matrix ); + static Qgis::EndCapStyle penStyleToCapStyle( Qt::PenCapStyle style ); + static Qgis::JoinStyle penStyleToJoinStyle( Qt::PenJoinStyle style ); + bool mUsePathStroker = false; + QPen mPen; QgsGeometryCollection mGeometry; + int mStrokedPathsSegments = 8; }; #endif diff --git a/tests/src/python/test_qgsgeometrypaintdevice.py b/tests/src/python/test_qgsgeometrypaintdevice.py index 676852105e1..e32c017aaca 100644 --- a/tests/src/python/test_qgsgeometrypaintdevice.py +++ b/tests/src/python/test_qgsgeometrypaintdevice.py @@ -75,8 +75,10 @@ class TestQgsGeometryPaintDevice(QgisTestCase): ) painter.end() - self.assertEqual(device.geometry().asWkt(2), - 'GeometryCollection (LineString (5.5 10.7, 6.8 12.9),LineString (15.5 12.7, 3.8 42.9),LineString (-4 -1, 2 3))') + result = device.geometry() + result.normalize() + self.assertEqual(result.asWkt(2), + 'GeometryCollection (LineString (15.5 12.7, 3.8 42.9),LineString (5.5 10.7, 6.8 12.9),LineString (-4 -1, 2 3))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), @@ -104,8 +106,10 @@ class TestQgsGeometryPaintDevice(QgisTestCase): painter.end() - self.assertEqual(device.geometry().asWkt(2), - 'GeometryCollection (LineString (11 32.1, 13.6 38.7),LineString (31 38.1, 7.6 128.7),LineString (-8 -3, 4 9))') + result = device.geometry() + result.normalize() + self.assertEqual(result.asWkt(2), + 'GeometryCollection (LineString (31 38.1, 7.6 128.7),LineString (11 32.1, 13.6 38.7),LineString (-8 -3, 4 9))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), 39) @@ -134,8 +138,10 @@ class TestQgsGeometryPaintDevice(QgisTestCase): painter.end() - self.assertEqual(device.geometry().asWkt(2), - 'GeometryCollection (Polygon ((6.15 10.32, 7.45 12.52, 6.15 13.28, 4.85 11.08, 6.15 10.32)),Polygon ((16.2 12.97, 4.5 43.17, 3.1 42.63, 14.8 12.43, 16.2 12.97)),Polygon ((-3.58 -1.62, 2.42 2.38, 1.58 3.62, -4.42 -0.38, -3.58 -1.62)))') + result = device.geometry() + result.normalize() + self.assertEqual(result.asWkt(2), + 'GeometryCollection (Polygon ((4.85 11.08, 6.15 13.28, 7.45 12.52, 6.15 10.32, 4.85 11.08)),Polygon ((3.1 42.63, 4.5 43.17, 16.2 12.97, 14.8 12.43, 3.1 42.63)),Polygon ((-4.42 -0.38, 1.58 3.62, 2.42 2.38, -3.58 -1.62, -4.42 -0.38)))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), @@ -378,7 +384,7 @@ class TestQgsGeometryPaintDevice(QgisTestCase): result = device.geometry().clone() result.normalize() self.assertEqual(result.asWkt(2), - 'GeometryCollection (Polygon ((4.85 11.08, 6.15 13.28, 6.82 13.65, 15.52 13.45, 15.65 11.96, 5.65 9.96, 4.85 11.08),(8.71 12.11, 15.48 11.95, 15.5 12.7, 15.35 13.44, 8.71 12.11),(6.78 12.15, 7.22 12.14, 7.45 12.52, 6.8 12.9, 6.78 12.15),(5.35 11.44, 5.5 10.7, 6.15 10.32, 7 11.76, 5.35 11.44),(7 11.76, 8.71 12.11, 7.22 12.14, 7 11.76)),Polygon ((13.29 11.24, 21.29 35.24, 22.71 34.76, 14.71 10.76, 13.29 11.24)),Polygon ((-4.42 -0.38, 1.58 3.62, 2.42 2.38, -3.58 -1.62, -4.42 -0.38)))') + 'GeometryCollection (Polygon ((4.85 11.08, 6.15 13.28, 6.82 13.65, 15.52 13.45, 15.65 11.96, 5.65 9.96, 4.85 11.08),(7 11.76, 8.71 12.11, 7.22 12.14, 7 11.76)),Polygon ((13.29 11.24, 21.29 35.24, 22.71 34.76, 14.71 10.76, 13.29 11.24)),Polygon ((-4.42 -0.38, 1.58 3.62, 2.42 2.38, -3.58 -1.62, -4.42 -0.38)))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), @@ -409,7 +415,7 @@ class TestQgsGeometryPaintDevice(QgisTestCase): result = device.geometry().clone() result.normalize() self.assertEqual(result.asWkt(2), - 'GeometryCollection (Polygon ((9.71 33.24, 12.31 39.84, 13.63 40.95, 31.03 40.35, 31.29 35.89, 11.29 29.89, 9.71 33.24),(17.41 36.32, 30.97 35.85, 31 38.1, 30.71 40.31, 17.41 36.32),(13.57 36.45, 14.44 36.42, 14.89 37.56, 13.6 38.7, 13.57 36.45),(10.71 34.31, 11 32.1, 12.29 30.96, 14 35.29, 10.71 34.31),(14 35.29, 17.41 36.32, 14.44 36.42, 14 35.29)),Polygon ((26.58 33.71, 42.58 105.71, 45.42 104.29, 29.42 32.29, 26.58 33.71)),Polygon ((-8.83 -1.13, 3.17 10.87, 4.83 7.13, -7.17 -4.87, -8.83 -1.13)))') + 'GeometryCollection (Polygon ((9.71 33.24, 12.31 39.84, 13.63 40.95, 31.03 40.35, 31.29 35.89, 11.29 29.89, 9.71 33.24),(14 35.29, 17.41 36.32, 14.44 36.42, 14 35.29)),Polygon ((26.58 33.71, 42.58 105.71, 45.42 104.29, 29.42 32.29, 26.58 33.71)),Polygon ((-8.83 -1.13, 3.17 10.87, 4.83 7.13, -7.17 -4.87, -8.83 -1.13)))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), 54) @@ -489,7 +495,7 @@ class TestQgsGeometryPaintDevice(QgisTestCase): result = device.geometry().clone() result.normalize() self.assertEqual(result.asWkt(2), - 'GeometryCollection (Polygon ((4.82 10.39, 5.35 11.44, 15.35 13.44, 16.23 12.51, 13.8 3.2, 13.7 2.98, 13.61 2.85, 13.52 2.72, 13.43 2.6, 13.34 2.47, 13.25 2.35, 13.16 2.23, 13.07 2.11, 12.97 1.99, 12.87 1.87, 12.78 1.76, 12.68 1.64, 12.58 1.53, 12.48 1.42, 12.38 1.31, 12.28 1.2, 12.18 1.09, 12.07 0.99, 11.97 0.88, 11.86 0.78, 11.76 0.68, 11.65 0.58, 11.54 0.49, 11.43 0.39, 11.32 0.3, 11.21 0.21, 11.1 0.12, 10.99 0.03, 10.88 -0.05, 10.77 -0.13, 10.66 -0.21, 9.55 0.09, 4.82 10.39),(6.58 10.15, 10.51 1.58, 10.57 1.62, 10.66 1.71, 10.75 1.79, 10.85 1.88, 10.94 1.96, 11.03 2.05, 11.12 2.14, 11.2 2.23, 11.29 2.33, 11.38 2.42, 11.46 2.52, 11.55 2.62, 11.63 2.72, 11.72 2.82, 11.8 2.92, 11.88 3.03, 11.97 3.13, 12.05 3.24, 12.13 3.35, 12.21 3.46, 12.29 3.58, 12.37 3.69, 12.38 3.71, 14.47 11.73, 6.58 10.15),(9.8 1.02, 10.23 0.4, 10.91 0.72, 10.51 1.58, 10.48 1.54, 10.38 1.46, 10.29 1.39, 10.19 1.31, 10.09 1.23, 10 1.16, 9.9 1.09, 9.8 1.02),(14.47 11.73, 15.65 11.96, 15.5 12.7, 14.77 12.89, 14.47 11.73),(12.35 3.58, 13.07 3.39, 12.45 3.81, 12.38 3.71, 12.35 3.58),(5.5 10.7, 5.65 9.96, 6.58 10.15, 6.18 11.01, 5.5 10.7)))') + 'GeometryCollection (Polygon ((4.82 10.39, 5.35 11.44, 15.35 13.44, 16.23 12.51, 13.8 3.2, 13.69 2.97, 13.61 2.85, 13.61 2.85, 13.52 2.72, 13.52 2.72, 13.43 2.6, 13.43 2.59, 13.34 2.47, 13.34 2.47, 13.25 2.35, 13.25 2.34, 13.16 2.23, 13.16 2.22, 13.07 2.11, 13.06 2.1, 12.97 1.99, 12.97 1.98, 12.88 1.87, 12.87 1.87, 12.78 1.76, 12.78 1.75, 12.69 1.64, 12.68 1.64, 12.59 1.53, 12.58 1.52, 12.49 1.42, 12.48 1.41, 12.39 1.31, 12.38 1.3, 12.29 1.2, 12.28 1.2, 12.19 1.1, 12.18 1.09, 12.08 0.99, 12.08 0.99, 11.98 0.89, 11.97 0.88, 11.87 0.79, 11.87 0.78, 11.77 0.69, 11.76 0.68, 11.66 0.59, 11.66 0.59, 11.56 0.5, 11.55 0.49, 11.45 0.4, 11.44 0.4, 11.34 0.31, 11.33 0.3, 11.23 0.22, 11.22 0.21, 11.12 0.13, 11.11 0.12, 11 0.04, 11 0.04, 10.89 -0.04, 10.88 -0.05, 10.78 -0.13, 10.77 -0.13, 10.66 -0.21, 9.55 0.09, 4.82 10.39),(6.58 10.15, 10.51 1.58, 10.56 1.62, 10.65 1.7, 10.75 1.79, 10.84 1.87, 10.93 1.96, 11.02 2.05, 11.11 2.14, 11.2 2.23, 11.29 2.33, 11.37 2.42, 11.46 2.52, 11.55 2.62, 11.63 2.72, 11.72 2.82, 11.8 2.93, 11.88 3.03, 11.97 3.14, 12.05 3.25, 12.13 3.36, 12.21 3.47, 12.29 3.58, 12.37 3.69, 12.38 3.71, 14.47 11.73, 6.58 10.15)))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), @@ -512,7 +518,7 @@ class TestQgsGeometryPaintDevice(QgisTestCase): result = device.geometry().clone() result.normalize() self.assertEqual(result.asWkt(2), - 'GeometryCollection (Polygon ((9.64 31.16, 10.71 34.31, 30.71 40.31, 32.45 37.53, 27.59 9.61, 27.39 8.93, 27.22 8.55, 27.04 8.17, 26.87 7.79, 26.69 7.42, 26.5 7.05, 26.32 6.69, 26.13 6.33, 25.94 5.97, 25.75 5.62, 25.56 5.27, 25.36 4.92, 25.16 4.58, 24.96 4.25, 24.76 3.92, 24.56 3.59, 24.35 3.27, 24.14 2.96, 23.93 2.65, 23.72 2.34, 23.51 2.04, 23.3 1.75, 23.08 1.46, 22.87 1.18, 22.65 0.9, 22.43 0.63, 22.21 0.36, 21.99 0.1, 21.77 -0.15, 21.54 -0.39, 21.32 -0.63, 19.09 0.27, 9.64 31.16),(13.16 30.45, 21.03 4.73, 21.14 4.87, 21.33 5.12, 21.51 5.37, 21.69 5.63, 21.87 5.89, 22.05 6.16, 22.23 6.43, 22.41 6.7, 22.58 6.99, 22.76 7.27, 22.93 7.56, 23.1 7.86, 23.27 8.16, 23.44 8.46, 23.6 8.77, 23.77 9.08, 23.93 9.4, 24.1 9.73, 24.26 10.06, 24.42 10.39, 24.58 10.73, 24.74 11.08, 24.76 11.12, 28.94 35.19, 13.16 30.45),(19.6 3.05, 20.46 1.21, 21.82 2.15, 21.03 4.73, 20.95 4.63, 20.76 4.39, 20.57 4.16, 20.38 3.93, 20.19 3.7, 19.99 3.48, 19.79 3.26, 19.6 3.05),(28.94 35.19, 31.29 35.89, 31 38.1, 29.55 38.67, 28.94 35.19),(24.69 10.75, 26.14 10.18, 24.89 11.43, 24.76 11.12, 24.69 10.75),(11 32.1, 11.29 29.89, 13.16 30.45, 12.36 33.04, 11 32.1)))') + 'GeometryCollection (Polygon ((9.64 31.16, 10.71 34.31, 30.71 40.31, 32.45 37.53, 27.59 9.61, 27.39 8.92, 27.22 8.56, 27.21 8.54, 27.05 8.17, 27.04 8.15, 26.87 7.8, 26.86 7.78, 26.69 7.42, 26.68 7.4, 26.51 7.05, 26.5 7.03, 26.32 6.69, 26.31 6.67, 26.14 6.33, 26.12 6.31, 25.95 5.97, 25.94 5.95, 25.76 5.62, 25.75 5.6, 25.57 5.27, 25.55 5.25, 25.37 4.93, 25.36 4.91, 25.18 4.59, 25.16 4.57, 24.98 4.26, 24.97 4.24, 24.78 3.93, 24.77 3.91, 24.58 3.61, 24.56 3.59, 24.37 3.29, 24.36 3.27, 24.17 2.98, 24.15 2.96, 23.96 2.67, 23.95 2.65, 23.75 2.37, 23.74 2.35, 23.54 2.07, 23.52 2.05, 23.33 1.78, 23.31 1.76, 23.11 1.49, 23.1 1.47, 22.89 1.2, 22.88 1.19, 22.67 0.93, 22.66 0.91, 22.45 0.66, 22.44 0.64, 22.23 0.39, 22.22 0.37, 22.01 0.13, 21.99 0.11, 21.78 -0.13, 21.77 -0.15, 21.56 -0.38, 21.54 -0.4, 21.33 -0.62, 19.09 0.27, 9.64 31.16),(13.16 30.45, 21.03 4.73, 21.12 4.85, 21.31 5.1, 21.49 5.36, 21.67 5.62, 21.86 5.88, 22.04 6.15, 22.22 6.42, 22.39 6.7, 22.57 6.98, 22.75 7.27, 22.92 7.56, 23.09 7.86, 23.26 8.16, 23.43 8.47, 23.6 8.78, 23.77 9.09, 23.93 9.41, 24.1 9.74, 24.26 10.07, 24.42 10.4, 24.58 10.74, 24.74 11.08, 24.76 11.12, 28.94 35.19, 13.16 30.45)))') self.assertEqual( device.metric(QPaintDevice.PaintDeviceMetric.PdmWidth), 22)