From 84b10041b3039aa50284cf88d8188246b216a3fa Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 17 Sep 2024 09:48:03 +1000 Subject: [PATCH 1/4] Allow HTML data urls for QgsImageCache Adds support for base64 encoded image decoding when the path is a HTML data URL (in addition to the existing "base64:..." format support) Allows use of eg "data:image/jpeg;base64,XXXXXXXX" formats for image paths, so that the image cache can correctly handle embedded image paths from HTML/CSS content --- .../auto_additions/qgsabstractcontentcache.py | 2 ++ .../qgsabstractcontentcache.sip.in | 23 +++++++++++++ .../auto_additions/qgsabstractcontentcache.py | 2 ++ .../qgsabstractcontentcache.sip.in | 23 +++++++++++++ src/core/qgsabstractcontentcache.cpp | 21 ++++++++++++ src/core/qgsabstractcontentcache.h | 23 +++++++++++++ src/core/qgsabstractcontentcache_p.h | 10 ++++++ src/core/qgsimagecache.cpp | 13 +++++-- tests/src/core/testqgsimagecache.cpp | 34 +++++++++++++++++-- 9 files changed, 146 insertions(+), 5 deletions(-) diff --git a/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py b/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py index b98a6e9805bd..771326a4b8b2 100644 --- a/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py +++ b/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py @@ -5,6 +5,8 @@ pass try: QgsAbstractContentCacheBase.__attribute_docs__ = {'remoteContentFetched': 'Emitted when the cache has finished retrieving content from a remote ``url``.\n'} + QgsAbstractContentCacheBase.parseBase64DataUrl = staticmethod(QgsAbstractContentCacheBase.parseBase64DataUrl) + QgsAbstractContentCacheBase.isBase64Data = staticmethod(QgsAbstractContentCacheBase.isBase64Data) QgsAbstractContentCacheBase.__signal_arguments__ = {'remoteContentFetched': ['url: str']} except NameError: pass diff --git a/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in b/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in index e9faf0b50c2f..d65dc29a2c09 100644 --- a/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in +++ b/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in @@ -90,6 +90,29 @@ Required because template based class (such as :py:class:`QgsAbstractContentCach QgsAbstractContentCacheBase( QObject *parent ); %Docstring Constructor for QgsAbstractContentCacheBase, with the specified ``parent`` object. +%End + + static bool parseBase64DataUrl( const QString &path, QString *mimeType /Out/ = 0, QString *data /Out/ = 0 ); +%Docstring +Parses a ``path`` to determine if it represents a base 64 encoded HTML data URL, and if so, extracts the components +of the URL. + +Data URLs are of the form ``data:[;]base64,``. + +:param path: path to test + +:return: - ``True`` if ``path`` is a base 64 encoded data URL + - mimeType: the extracted mime type if the ``path`` is a data URL + - data: the extracted base64 data if the ``path`` is a data URL + +.. versionadded:: 3.40 +%End + + static bool isBase64Data( const QString &path ); +%Docstring +Returns ``True`` if ``path`` represents base64 encoded data. + +.. versionadded:: 3.40 %End signals: diff --git a/python/core/auto_additions/qgsabstractcontentcache.py b/python/core/auto_additions/qgsabstractcontentcache.py index b98a6e9805bd..771326a4b8b2 100644 --- a/python/core/auto_additions/qgsabstractcontentcache.py +++ b/python/core/auto_additions/qgsabstractcontentcache.py @@ -5,6 +5,8 @@ pass try: QgsAbstractContentCacheBase.__attribute_docs__ = {'remoteContentFetched': 'Emitted when the cache has finished retrieving content from a remote ``url``.\n'} + QgsAbstractContentCacheBase.parseBase64DataUrl = staticmethod(QgsAbstractContentCacheBase.parseBase64DataUrl) + QgsAbstractContentCacheBase.isBase64Data = staticmethod(QgsAbstractContentCacheBase.isBase64Data) QgsAbstractContentCacheBase.__signal_arguments__ = {'remoteContentFetched': ['url: str']} except NameError: pass diff --git a/python/core/auto_generated/qgsabstractcontentcache.sip.in b/python/core/auto_generated/qgsabstractcontentcache.sip.in index e9faf0b50c2f..d65dc29a2c09 100644 --- a/python/core/auto_generated/qgsabstractcontentcache.sip.in +++ b/python/core/auto_generated/qgsabstractcontentcache.sip.in @@ -90,6 +90,29 @@ Required because template based class (such as :py:class:`QgsAbstractContentCach QgsAbstractContentCacheBase( QObject *parent ); %Docstring Constructor for QgsAbstractContentCacheBase, with the specified ``parent`` object. +%End + + static bool parseBase64DataUrl( const QString &path, QString *mimeType /Out/ = 0, QString *data /Out/ = 0 ); +%Docstring +Parses a ``path`` to determine if it represents a base 64 encoded HTML data URL, and if so, extracts the components +of the URL. + +Data URLs are of the form ``data:[;]base64,``. + +:param path: path to test + +:return: - ``True`` if ``path`` is a base 64 encoded data URL + - mimeType: the extracted mime type if the ``path`` is a data URL + - data: the extracted base64 data if the ``path`` is a data URL + +.. versionadded:: 3.40 +%End + + static bool isBase64Data( const QString &path ); +%Docstring +Returns ``True`` if ``path`` represents base64 encoded data. + +.. versionadded:: 3.40 %End signals: diff --git a/src/core/qgsabstractcontentcache.cpp b/src/core/qgsabstractcontentcache.cpp index 56ab94d7c292..f126d8d06342 100644 --- a/src/core/qgsabstractcontentcache.cpp +++ b/src/core/qgsabstractcontentcache.cpp @@ -17,6 +17,7 @@ #include "qgsabstractcontentcache.h" #include "qgssetrequestinitiator_p.h" +#include // // QgsAbstractContentCacheEntry @@ -40,3 +41,23 @@ void QgsAbstractContentCacheBase::onRemoteContentFetched( const QString &, bool { } + +bool QgsAbstractContentCacheBase::parseBase64DataUrl( const QString &path, QString *mimeType, QString *data ) +{ + const thread_local QRegularExpression sRx( QStringLiteral( "^data:(.*/.*?);?(?:base64)?,(.*)$" ) ); + const QRegularExpressionMatch base64Match = sRx.match( path ); + if ( !base64Match.hasMatch() ) + return false; + + if ( mimeType ) + *mimeType = base64Match.captured( 1 ); + if ( data ) + *data = base64Match.captured( 2 ); + return true; +} + +bool QgsAbstractContentCacheBase::isBase64Data( const QString &path ) +{ + return path.startsWith( QLatin1String( "base64:" ) ) + || parseBase64DataUrl( path ); +} diff --git a/src/core/qgsabstractcontentcache.h b/src/core/qgsabstractcontentcache.h index ff3059d8ed53..76a0f8ee8559 100644 --- a/src/core/qgsabstractcontentcache.h +++ b/src/core/qgsabstractcontentcache.h @@ -140,6 +140,29 @@ class CORE_EXPORT QgsAbstractContentCacheBase: public QObject */ QgsAbstractContentCacheBase( QObject *parent ); + /** + * Parses a \a path to determine if it represents a base 64 encoded HTML data URL, and if so, extracts the components + * of the URL. + * + * Data URLs are of the form ``data:[;]base64,``. + * + * \param path path to test + * \param mimeType will be set to the extracted mime type if the \a path is a data URL + * \param data will be set to the extracted base64 data if the \a path is a data URL + * + * \returns TRUE if \a path is a base 64 encoded data URL + * + * \since QGIS 3.40 + */ + static bool parseBase64DataUrl( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr ); + + /** + * Returns TRUE if \a path represents base64 encoded data. + * + * \since QGIS 3.40 + */ + static bool isBase64Data( const QString &path ); + signals: /** diff --git a/src/core/qgsabstractcontentcache_p.h b/src/core/qgsabstractcontentcache_p.h index e6c424591b84..b0d38f7b040f 100644 --- a/src/core/qgsabstractcontentcache_p.h +++ b/src/core/qgsabstractcontentcache_p.h @@ -20,6 +20,7 @@ #include "qgsabstractcontentcache.h" #include "qgssetrequestinitiator_p.h" +#include template QByteArray QgsAbstractContentCache::getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking ) const @@ -44,6 +45,15 @@ QByteArray QgsAbstractContentCache::getContent( const QString &path, const QB const QByteArray base64 = path.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals ); } + else + { + // maybe a HTML data URL + QString base64String; + if ( parseBase64DataUrl( path, nullptr, &base64String ) ) + { + return QByteArray::fromBase64( base64String.toLocal8Bit(), QByteArray::OmitTrailingEquals ); + } + } // maybe it's a url... if ( !path.contains( QLatin1String( "://" ) ) ) // otherwise short, relative SVG paths might be considered URLs diff --git a/src/core/qgsimagecache.cpp b/src/core/qgsimagecache.cpp index 1477d2d081a8..48926f03e2ce 100644 --- a/src/core/qgsimagecache.cpp +++ b/src/core/qgsimagecache.cpp @@ -162,6 +162,13 @@ QImage QgsImageCache::pathAsImagePrivate( const QString &f, const QSize size, co fitsInCache = true; + QString base64String; + QString mimeType; + if ( parseBase64DataUrl( file, &mimeType, &base64String ) && mimeType.startsWith( QLatin1String( "image/" ) ) ) + { + file = QStringLiteral( "base64:%1" ).arg( base64String ); + } + QgsImageCacheEntry *currentEntry = findExistingEntry( new QgsImageCacheEntry( file, size, keepAspectRatio, opacity, targetDpi, frameNumber ) ); QImage result; @@ -212,7 +219,7 @@ QSize QgsImageCache::originalSize( const QString &path, bool blocking ) const return QSize(); // direct read if path is a file -- maybe more efficient than going the bytearray route? (untested!) - if ( !path.startsWith( QLatin1String( "base64:" ) ) && QFile::exists( path ) ) + if ( !isBase64Data( path ) && QFile::exists( path ) ) { const QImageReader reader( path ); if ( reader.size().isValid() ) @@ -298,7 +305,7 @@ void QgsImageCache::prepareAnimation( const QString &path ) std::unique_ptr< QImageReader > reader; std::unique_ptr< QBuffer > buffer; - if ( !path.startsWith( QLatin1String( "base64:" ) ) && QFile::exists( path ) ) + if ( !isBase64Data( path ) && QFile::exists( path ) ) { const QString basePart = QFileInfo( path ).baseName(); int id = 1; @@ -355,7 +362,7 @@ QImage QgsImageCache::renderImage( const QString &path, QSize size, const bool k isBroken = false; // direct read if path is a file -- maybe more efficient than going the bytearray route? (untested!) - if ( !path.startsWith( QLatin1String( "base64:" ) ) && QFile::exists( path ) ) + if ( !isBase64Data( path ) && QFile::exists( path ) ) { QImageReader reader( path ); reader.setAutoTransform( true ); diff --git a/tests/src/core/testqgsimagecache.cpp b/tests/src/core/testqgsimagecache.cpp index f5c9b6a6924c..640ff427f27c 100644 --- a/tests/src/core/testqgsimagecache.cpp +++ b/tests/src/core/testqgsimagecache.cpp @@ -46,7 +46,7 @@ class TestQgsImageCache : public QgsTest private: - bool imageCheck( const QString &testName, QImage &image, int mismatchCount ); + bool imageCheck( const QString &testName, const QImage &image, int mismatchCount ); private slots: void initTestCase();// will be called before the first testfunction is executed. @@ -66,6 +66,7 @@ class TestQgsImageCache : public QgsTest void imageFrames(); void preseedAnimation(); void cmykImage(); + void htmlDataUrl(); }; @@ -460,7 +461,7 @@ void TestQgsImageCache::preseedAnimation() QVERIFY( img.isNull() ); } -bool TestQgsImageCache::imageCheck( const QString &testName, QImage &image, int mismatchCount ) +bool TestQgsImageCache::imageCheck( const QString &testName, const QImage &image, int mismatchCount ) { //draw background QImage imageWithBackground( image.width(), image.height(), QImage::Format_RGB32 ); @@ -498,6 +499,35 @@ void TestQgsImageCache::cmykImage() #endif } +void TestQgsImageCache::htmlDataUrl() +{ + QVERIFY( !QgsImageCache::parseBase64DataUrl( QString() ) ); + QVERIFY( !QgsImageCache::parseBase64DataUrl( testDataPath( QStringLiteral( "/cmyk.jpg" ) ) ) ); + QVERIFY( !QgsImageCache::isBase64Data( testDataPath( QStringLiteral( "/cmyk.jpg" ) ) ) ); + QVERIFY( QgsImageCache::isBase64Data( QStringLiteral( "base64:zzzzzzzzzzzzzzz" ) ) ); + QVERIFY( QgsImageCache::isBase64Data( QStringLiteral( "data:image/jpeg;base64,SGVsbG8sIFdvcmxkIQ==" ) ) ); + + QString mimeType; + QString base64Data; + QVERIFY( QgsImageCache::parseBase64DataUrl( QStringLiteral( "data:image/jpeg;base64,SGVsbG8sIFdvcmxkIQ==" ), &mimeType, &base64Data ) ); + QCOMPARE( mimeType, QStringLiteral( "image/jpeg" ) ); + QCOMPARE( base64Data, QStringLiteral( "SGVsbG8sIFdvcmxkIQ==" ) ); + + // "base64" string is optional + QVERIFY( QgsImageCache::parseBase64DataUrl( QStringLiteral( "data:image/jpeg,SGVsbG8sIFdvcmxkIQ==" ), &mimeType, &base64Data ) ); + QCOMPARE( mimeType, QStringLiteral( "image/jpeg" ) ); + QCOMPARE( base64Data, QStringLiteral( "SGVsbG8sIFdvcmxkIQ==" ) ); + + QgsImageCache cache; + bool inCache = false; + const QImage img = cache.pathAsImage( QStringLiteral( "data:image/jpeg;base64,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" ), + QSize( 200, 200 ), true, 1.0, inCache ); + QVERIFY( imageCheck( QStringLiteral( "imagecache_base64" ), img, 30 ) ); + const QSize size = cache.originalSize( QStringLiteral( "base64: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" ) ); + QCOMPARE( size.width(), 60 ); + QCOMPARE( size.height(), 60 ); +} + QGSTEST_MAIN( TestQgsImageCache ) #include "testqgsimagecache.moc" From edcd18f8281554eaa67d04d1c7f70f92e9d6dde6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 17 Sep 2024 09:53:00 +1000 Subject: [PATCH 2/4] Allow QgsTextFragment to represent an inline image object Modifies the QgsTextDocument API to extract images from HTML content, and store in the associated QgsTextFragment/QgsTextCharacterFormat objects --- .../qgstextcharacterformat.sip.in | 56 +++++++++++++++++++ .../textrenderer/qgstextfragment.sip.in | 15 ++++- .../qgstextcharacterformat.sip.in | 56 +++++++++++++++++++ .../textrenderer/qgstextfragment.sip.in | 15 ++++- .../textrenderer/qgstextcharacterformat.cpp | 26 +++++++++ .../textrenderer/qgstextcharacterformat.h | 52 +++++++++++++++++ src/core/textrenderer/qgstextfragment.cpp | 14 +++-- src/core/textrenderer/qgstextfragment.h | 16 +++++- .../src/python/test_qgstextcharacterformat.py | 8 +++ tests/src/python/test_qgstextdocument.py | 18 +++++- 10 files changed, 268 insertions(+), 8 deletions(-) diff --git a/python/PyQt6/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in b/python/PyQt6/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in index 09c8b16b3d21..824e9dd09fdd 100644 --- a/python/PyQt6/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in +++ b/python/PyQt6/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in @@ -262,6 +262,62 @@ Returns whether the format has overline enabled. Sets whether the format has overline ``enabled``. .. seealso:: :py:func:`overline` +%End + + QString imagePath() const; +%Docstring +Returns the path to the image to render, if the format applies to a document image fragment. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`imageSize` + +.. seealso:: :py:func:`setImagePath` + +.. versionadded:: 3.40 +%End + + void setImagePath( const QString &path ); +%Docstring +Sets the ``path`` to the image to render, if the format applies to a document image fragment. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`setImageSize` + +.. seealso:: :py:func:`imagePath` + +.. versionadded:: 3.40 +%End + + QSizeF imageSize() const; +%Docstring +Returns the image size, if the format applies to a document image fragment. + +The image size is always considered to be in :py:class:`Qgis`.RenderUnit.Points. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`imagePath` + +.. seealso:: :py:func:`setImageSize` + +.. versionadded:: 3.40 +%End + + void setImageSize( const QSizeF &size ); +%Docstring +Sets the image ``size``, if the format applies to a document image fragment. + +The image size is always considered to be in :py:class:`Qgis`.RenderUnit.Points. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`setImagePath` + +.. seealso:: :py:func:`imageSize` + +.. versionadded:: 3.40 %End bool hasVerticalAlignmentSet() const; diff --git a/python/PyQt6/core/auto_generated/textrenderer/qgstextfragment.sip.in b/python/PyQt6/core/auto_generated/textrenderer/qgstextfragment.sip.in index 680257c1732e..4ba19e9f6db3 100644 --- a/python/PyQt6/core/auto_generated/textrenderer/qgstextfragment.sip.in +++ b/python/PyQt6/core/auto_generated/textrenderer/qgstextfragment.sip.in @@ -12,7 +12,13 @@ class QgsTextFragment { %Docstring(signature="appended") -Stores a fragment of text along with formatting overrides to be used when rendering the fragment. +Stores a fragment of document along with formatting overrides to be used when rendering the fragment. + +Text fragments consist of either a block of text or another atomic component of a document (such as an image). + +Each fragment has an associated :py:func:`~characterFormat`, which specifies the text formatting overrides +to use when rendering the fragment. Additionally, the :py:func:`~characterFormat` may contain properties +for other fragment types, such as image paths and sizes for image fragments. .. warning:: @@ -77,6 +83,13 @@ Returns the character formatting for the fragment. Sets the character ``format`` for the fragment. .. seealso:: :py:func:`characterFormat` +%End + + bool isImage() const; +%Docstring +Returns ``True`` if the fragment represents an image. + +.. versionadded:: 3.40 %End double horizontalAdvance( const QFont &font, const QgsRenderContext &context, bool fontHasBeenUpdatedForFragment = false, double scaleFactor = 1.0 ) const; diff --git a/python/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in b/python/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in index 09c8b16b3d21..824e9dd09fdd 100644 --- a/python/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in +++ b/python/core/auto_generated/textrenderer/qgstextcharacterformat.sip.in @@ -262,6 +262,62 @@ Returns whether the format has overline enabled. Sets whether the format has overline ``enabled``. .. seealso:: :py:func:`overline` +%End + + QString imagePath() const; +%Docstring +Returns the path to the image to render, if the format applies to a document image fragment. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`imageSize` + +.. seealso:: :py:func:`setImagePath` + +.. versionadded:: 3.40 +%End + + void setImagePath( const QString &path ); +%Docstring +Sets the ``path`` to the image to render, if the format applies to a document image fragment. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`setImageSize` + +.. seealso:: :py:func:`imagePath` + +.. versionadded:: 3.40 +%End + + QSizeF imageSize() const; +%Docstring +Returns the image size, if the format applies to a document image fragment. + +The image size is always considered to be in :py:class:`Qgis`.RenderUnit.Points. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`imagePath` + +.. seealso:: :py:func:`setImageSize` + +.. versionadded:: 3.40 +%End + + void setImageSize( const QSizeF &size ); +%Docstring +Sets the image ``size``, if the format applies to a document image fragment. + +The image size is always considered to be in :py:class:`Qgis`.RenderUnit.Points. + +.. seealso:: :py:func:`QgsTextFragment.isImage` + +.. seealso:: :py:func:`setImagePath` + +.. seealso:: :py:func:`imageSize` + +.. versionadded:: 3.40 %End bool hasVerticalAlignmentSet() const; diff --git a/python/core/auto_generated/textrenderer/qgstextfragment.sip.in b/python/core/auto_generated/textrenderer/qgstextfragment.sip.in index 680257c1732e..4ba19e9f6db3 100644 --- a/python/core/auto_generated/textrenderer/qgstextfragment.sip.in +++ b/python/core/auto_generated/textrenderer/qgstextfragment.sip.in @@ -12,7 +12,13 @@ class QgsTextFragment { %Docstring(signature="appended") -Stores a fragment of text along with formatting overrides to be used when rendering the fragment. +Stores a fragment of document along with formatting overrides to be used when rendering the fragment. + +Text fragments consist of either a block of text or another atomic component of a document (such as an image). + +Each fragment has an associated :py:func:`~characterFormat`, which specifies the text formatting overrides +to use when rendering the fragment. Additionally, the :py:func:`~characterFormat` may contain properties +for other fragment types, such as image paths and sizes for image fragments. .. warning:: @@ -77,6 +83,13 @@ Returns the character formatting for the fragment. Sets the character ``format`` for the fragment. .. seealso:: :py:func:`characterFormat` +%End + + bool isImage() const; +%Docstring +Returns ``True`` if the fragment represents an image. + +.. versionadded:: 3.40 %End double horizontalAdvance( const QFont &font, const QgsRenderContext &context, bool fontHasBeenUpdatedForFragment = false, double scaleFactor = 1.0 ) const; diff --git a/src/core/textrenderer/qgstextcharacterformat.cpp b/src/core/textrenderer/qgstextcharacterformat.cpp index 21cf800f39cf..1fe0ce1a3002 100644 --- a/src/core/textrenderer/qgstextcharacterformat.cpp +++ b/src/core/textrenderer/qgstextcharacterformat.cpp @@ -65,6 +65,12 @@ QgsTextCharacterFormat::QgsTextCharacterFormat( const QTextCharFormat &format ) if ( !families.isEmpty() ) mFontFamily = families.at( 0 ); } + if ( format.isImageFormat() ) + { + const QTextImageFormat imageFormat = format.toImageFormat(); + mImagePath = imageFormat.name(); + mImageSize = QSizeF( imageFormat.width(), imageFormat.height() ); + } } void QgsTextCharacterFormat::overrideWith( const QgsTextCharacterFormat &other ) @@ -168,6 +174,26 @@ void QgsTextCharacterFormat::setOverline( QgsTextCharacterFormat::BooleanValue e mOverline = enabled; } +QString QgsTextCharacterFormat::imagePath() const +{ + return mImagePath; +} + +void QgsTextCharacterFormat::setImagePath( const QString &path ) +{ + mImagePath = path; +} + +QSizeF QgsTextCharacterFormat::imageSize() const +{ + return mImageSize; +} + +void QgsTextCharacterFormat::setImageSize( const QSizeF &size ) +{ + mImageSize = size; +} + void QgsTextCharacterFormat::updateFontForFormat( QFont &font, const QgsRenderContext &context, const double scaleFactor ) const { // important -- MUST set family first diff --git a/src/core/textrenderer/qgstextcharacterformat.h b/src/core/textrenderer/qgstextcharacterformat.h index e3368504cfeb..8b6b79b5cce3 100644 --- a/src/core/textrenderer/qgstextcharacterformat.h +++ b/src/core/textrenderer/qgstextcharacterformat.h @@ -22,6 +22,7 @@ #include #include +#include class QTextCharFormat; class QgsRenderContext; @@ -253,6 +254,54 @@ class CORE_EXPORT QgsTextCharacterFormat */ void setOverline( BooleanValue enabled ); + /** + * Returns the path to the image to render, if the format applies to a document image fragment. + * + * \see QgsTextFragment::isImage() + * \see imageSize() + * \see setImagePath() + * + * \since QGIS 3.40 + */ + QString imagePath() const; + + /** + * Sets the \a path to the image to render, if the format applies to a document image fragment. + * + * \see QgsTextFragment::isImage() + * \see setImageSize() + * \see imagePath() + * + * \since QGIS 3.40 + */ + void setImagePath( const QString &path ); + + /** + * Returns the image size, if the format applies to a document image fragment. + * + * The image size is always considered to be in Qgis::RenderUnit::Points. + * + * \see QgsTextFragment::isImage() + * \see imagePath() + * \see setImageSize() + * + * \since QGIS 3.40 + */ + QSizeF imageSize() const; + + /** + * Sets the image \a size, if the format applies to a document image fragment. + * + * The image size is always considered to be in Qgis::RenderUnit::Points. + * + * \see QgsTextFragment::isImage() + * \see setImagePath() + * \see imageSize() + * + * \since QGIS 3.40 + */ + void setImageSize( const QSizeF &size ); + /** * Returns TRUE if the format has an explicit vertical alignment set. * @@ -326,6 +375,9 @@ class CORE_EXPORT QgsTextCharacterFormat bool mHasVerticalAlignSet = false; Qgis::TextCharacterVerticalAlignment mVerticalAlign = Qgis::TextCharacterVerticalAlignment::Normal; + QString mImagePath; + QSizeF mImageSize; + BooleanValue mStrikethrough = BooleanValue::NotSet; BooleanValue mUnderline = BooleanValue::NotSet; BooleanValue mOverline = BooleanValue::NotSet; diff --git a/src/core/textrenderer/qgstextfragment.cpp b/src/core/textrenderer/qgstextfragment.cpp index 4bfd0bca9979..f6105bbbe001 100644 --- a/src/core/textrenderer/qgstextfragment.cpp +++ b/src/core/textrenderer/qgstextfragment.cpp @@ -19,15 +19,16 @@ #include "qgsstringutils.h" QgsTextFragment::QgsTextFragment( const QString &text, const QgsTextCharacterFormat &format ) - : mText( text ) + : mText( text != QStringLiteral( "\ufffc" ) ? text : QString() ) + , mIsImage( text == QStringLiteral( "\ufffc" ) ) , mCharFormat( format ) {} QgsTextFragment::QgsTextFragment( const QTextFragment &fragment ) - : mText( fragment.text() ) - , mCharFormat( QgsTextCharacterFormat( fragment.charFormat() ) ) + : mText( fragment.text() != QStringLiteral( "\ufffc" ) ? fragment.text() : QString() ) + , mIsImage( fragment.text() == QStringLiteral( "\ufffc" ) ) + , mCharFormat( fragment.charFormat() ) { - } QString QgsTextFragment::text() const @@ -45,6 +46,11 @@ void QgsTextFragment::setCharacterFormat( const QgsTextCharacterFormat &charForm mCharFormat = charFormat; } +bool QgsTextFragment::isImage() const +{ + return mIsImage; +} + double QgsTextFragment::horizontalAdvance( const QFont &font, const QgsRenderContext &context, bool fontHasBeenUpdatedForFragment, double scaleFactor ) const { if ( fontHasBeenUpdatedForFragment ) diff --git a/src/core/textrenderer/qgstextfragment.h b/src/core/textrenderer/qgstextfragment.h index 4e16db741a07..a17f88f0e367 100644 --- a/src/core/textrenderer/qgstextfragment.h +++ b/src/core/textrenderer/qgstextfragment.h @@ -26,7 +26,13 @@ class QTextFragment; /** * \class QgsTextFragment * \ingroup core - * \brief Stores a fragment of text along with formatting overrides to be used when rendering the fragment. + * \brief Stores a fragment of document along with formatting overrides to be used when rendering the fragment. + * + * Text fragments consist of either a block of text or another atomic component of a document (such as an image). + * + * Each fragment has an associated characterFormat(), which specifies the text formatting overrides + * to use when rendering the fragment. Additionally, the characterFormat() may contain properties + * for other fragment types, such as image paths and sizes for image fragments. * * \warning This API is not considered stable and may change in future QGIS versions. * @@ -89,6 +95,13 @@ class CORE_EXPORT QgsTextFragment */ void setCharacterFormat( const QgsTextCharacterFormat &format ); + /** + * Returns TRUE if the fragment represents an image. + * + * \since QGIS 3.40 + */ + bool isImage() const; + /** * Returns the horizontal advance associated with this fragment, when rendered using * the specified base \a font within the specified render \a context. @@ -113,6 +126,7 @@ class CORE_EXPORT QgsTextFragment private: QString mText; + bool mIsImage = false; QgsTextCharacterFormat mCharFormat; }; diff --git a/tests/src/python/test_qgstextcharacterformat.py b/tests/src/python/test_qgstextcharacterformat.py index fb5756a23bc7..c5c34e5d1f3d 100644 --- a/tests/src/python/test_qgstextcharacterformat.py +++ b/tests/src/python/test_qgstextcharacterformat.py @@ -11,6 +11,7 @@ __date__ = '12/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' +from qgis.PyQt.QtCore import QSizeF from qgis.PyQt.QtGui import QColor from qgis.core import ( Qgis, @@ -64,6 +65,13 @@ def testGettersSetters(self): format.setVerticalAlignment(Qgis.TextCharacterVerticalAlignment.SuperScript) self.assertEqual(format.verticalAlignment(), Qgis.TextCharacterVerticalAlignment.SuperScript) + self.assertFalse(format.imagePath()) + self.assertEqual(format.imageSize(), QSizeF()) + format.setImagePath('my.jpg') + format.setImageSize(QSizeF(40, 60)) + self.assertEqual(format.imagePath(), 'my.jpg') + self.assertEqual(format.imageSize(), QSizeF(40, 60)) + def testUpdateFont(self): context = QgsRenderContext() font = QgsFontUtils.getStandardTestFont() diff --git a/tests/src/python/test_qgstextdocument.py b/tests/src/python/test_qgstextdocument.py index dd22b62e34e3..89ee20f898e4 100644 --- a/tests/src/python/test_qgstextdocument.py +++ b/tests/src/python/test_qgstextdocument.py @@ -11,7 +11,7 @@ __date__ = '12/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -from qgis.PyQt.QtCore import QT_VERSION_STR +from qgis.PyQt.QtCore import QT_VERSION_STR, QSizeF from qgis.core import ( Qgis, QgsFontUtils, @@ -222,6 +222,22 @@ def testFromHtmlVerticalAlignment(self): self.assertTrue(doc[2][1].characterFormat().hasVerticalAlignmentSet()) self.assertEqual(doc[2][1].characterFormat().verticalAlignment(), Qgis.TextCharacterVerticalAlignment.SubScript) + def testImage(self): + doc = QgsTextDocument.fromHtml([ + 'abcextra']) + self.assertEqual(len(doc), 1) + self.assertEqual(len(doc[0]), 3) + self.assertEqual(doc[0][0].text(), 'abc') + self.assertFalse(doc[0][0].isImage()) + self.assertFalse(doc[0][0].characterFormat().imagePath()) + self.assertTrue(doc[0][1].isImage()) + self.assertFalse(doc[0][1].text()) + self.assertEqual(doc[0][1].characterFormat().imagePath(), 'qgis.jpg') + self.assertEqual(doc[0][1].characterFormat().imageSize(), QSizeF(40, 60)) + self.assertEqual(doc[0][2].text(), 'extra') + self.assertFalse(doc[0][2].isImage()) + self.assertTrue(doc[0][2].characterFormat().italic()) + def testAppend(self): doc = QgsTextDocument() self.assertEqual(len(doc), 0) From 3d66bb8fc126b30fae6ddd4fee74842593897e1e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 17 Sep 2024 09:54:24 +1000 Subject: [PATCH 3/4] [feature] Support img tags in HTML label text Allows use of img tags in HTML label content. The following logic is applied: - Image path is set via the src="xxx" attribute. Local, HTTP, and base64 encoded paths are permitted - Any image format readable by QGIS can be used - Image sizes can be specified via the width="##" and height="##" attributes. If width or height is not specified it will automatically be calculated from the original image size - If width or height are specified, they are considered to be in POINTS - The css width/height settings are NOT respected (this is a Qt limitation) - Images are not supported for curved text labels - Images are placed inline only, floating images are not supported Sponsored by City of Freiburg im Breisgau --- .../qgstextdocumentmetrics.sip.in | 7 ++ .../qgstextdocumentmetrics.sip.in | 7 ++ .../textrenderer/qgstextdocumentmetrics.cpp | 117 ++++++++++++++---- .../textrenderer/qgstextdocumentmetrics.h | 8 ++ src/core/textrenderer/qgstextrenderer.cpp | 26 +++- tests/src/core/testqgslabelingengine.cpp | 54 ++++++++ tests/src/python/test_qgstextrenderer.py | 74 ++++++++++- .../expected_html_images.png | Bin 0 -> 30781 bytes .../image_autoheight/image_autoheight.png | Bin 0 -> 19660 bytes .../image_autosize/image_autosize.png | Bin 0 -> 9610 bytes .../image_autowidth/image_autowidth.png | Bin 0 -> 16452 bytes .../image_fixed_size/image_fixed_size.png | Bin 0 -> 29898 bytes 12 files changed, 265 insertions(+), 28 deletions(-) create mode 100644 tests/testdata/control_images/labelingengine/expected_html_images/expected_html_images.png create mode 100644 tests/testdata/control_images/text_renderer/image_autoheight/image_autoheight.png create mode 100644 tests/testdata/control_images/text_renderer/image_autosize/image_autosize.png create mode 100644 tests/testdata/control_images/text_renderer/image_autowidth/image_autowidth.png create mode 100644 tests/testdata/control_images/text_renderer/image_fixed_size/image_fixed_size.png diff --git a/python/PyQt6/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in b/python/PyQt6/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in index d7eea5efd7f6..17d851755b35 100644 --- a/python/PyQt6/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in +++ b/python/PyQt6/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in @@ -99,6 +99,13 @@ Returns the vertical offset from a text block's baseline which should be applied to the fragment at the specified index within that block. .. versionadded:: 3.30 +%End + + double fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const; +%Docstring +Returns the fixed height of the fragment at the specified block and fragment index, or -1 if the fragment does not have a fixed height. + +.. versionadded:: 3.40 %End double verticalOrientationXOffset( int blockIndex ) const; diff --git a/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in b/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in index d7eea5efd7f6..17d851755b35 100644 --- a/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in +++ b/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in @@ -99,6 +99,13 @@ Returns the vertical offset from a text block's baseline which should be applied to the fragment at the specified index within that block. .. versionadded:: 3.30 +%End + + double fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const; +%Docstring +Returns the fixed height of the fragment at the specified block and fragment index, or -1 if the fragment does not have a fixed height. + +.. versionadded:: 3.40 %End double verticalOrientationXOffset( int blockIndex ) const; diff --git a/src/core/textrenderer/qgstextdocumentmetrics.cpp b/src/core/textrenderer/qgstextdocumentmetrics.cpp index 466feeb72a08..819d1e1a698c 100644 --- a/src/core/textrenderer/qgstextdocumentmetrics.cpp +++ b/src/core/textrenderer/qgstextdocumentmetrics.cpp @@ -21,6 +21,8 @@ #include "qgstextdocument.h" #include "qgsrendercontext.h" #include "qgstextrenderer.h" +#include "qgsapplication.h" +#include "qgsimagecache.h" #include @@ -83,19 +85,23 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo const int fragmentSize = block.size(); double maxBlockAscent = 0; + double maxBlockAscentForTextFragments = 0; double maxBlockDescent = 0; double maxLineSpacing = 0; double maxBlockLeading = 0; double maxBlockMaxWidth = 0; double maxBlockCapHeight = 0; + double maxBlockFixedItemHeight = 0; QList< double > fragmentVerticalOffsets; fragmentVerticalOffsets.reserve( fragmentSize ); QList< QFont > fragmentFonts; fragmentFonts.reserve( fragmentSize ); - QList< double >fragmentHorizontalAdvance; + QList< double > fragmentHorizontalAdvance; fragmentHorizontalAdvance.reserve( fragmentSize ); + QList< double > fragmentFixedHeights; + fragmentFixedHeights.reserve( fragmentSize ); QFont previousNonSuperSubScriptFont; @@ -115,6 +121,7 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo fragmentVerticalOffsets << 0; fragmentHorizontalAdvance << fragmentWidth; + fragmentFixedHeights << -1; fragmentFonts << QFont(); } else @@ -190,34 +197,89 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo } fragmentVerticalOffsets << fragmentVerticalOffset; - const double fragmentWidth = fm.horizontalAdvance( fragment.text() ) / scaleFactor; + // calculate width of fragment + double fragmentWidth = 0; + if ( fragment.isImage() ) + { + double imageHeight = 0; + double imageWidth = 0; + if ( ( qgsDoubleNear( fragmentFormat.imageSize().width(), 0 ) || fragmentFormat.imageSize().width() < 0 ) + && ( qgsDoubleNear( fragmentFormat.imageSize().height(), 0 ) || fragmentFormat.imageSize().height() < 0 ) ) + { + // use original image size + const QSize imageSize = QgsApplication::imageCache()->originalSize( fragmentFormat.imagePath(), context.flags() & Qgis::RenderContextFlag::RenderBlocking ); + // TODO: maybe there's more optimal logic we could use here, but for now we assume 96dpi image resolution... + const QSizeF originalSizeMmAt96Dpi = imageSize / 3.7795275590551185; + const double pixelsPerMm = context.scaleFactor(); + imageWidth = originalSizeMmAt96Dpi.width() * pixelsPerMm; + imageHeight = originalSizeMmAt96Dpi.height() * pixelsPerMm; + } + else if ( ( qgsDoubleNear( fragmentFormat.imageSize().width(), 0 ) || fragmentFormat.imageSize().width() < 0 ) ) + { + // height specified, calculate width + const QSize originalImageSize = QgsApplication::imageCache()->originalSize( fragmentFormat.imagePath(), context.flags() & Qgis::RenderContextFlag::RenderBlocking ); + imageHeight = context.convertToPainterUnits( fragmentFormat.imageSize().height(), Qgis::RenderUnit::Points ); + imageWidth = originalImageSize.width() * imageHeight / originalImageSize.height(); + } + else if ( ( qgsDoubleNear( fragmentFormat.imageSize().height(), 0 ) || fragmentFormat.imageSize().height() < 0 ) ) + { + // width specified, calculate height + const QSize originalImageSize = QgsApplication::imageCache()->originalSize( fragmentFormat.imagePath(), context.flags() & Qgis::RenderContextFlag::RenderBlocking ); + imageWidth = context.convertToPainterUnits( fragmentFormat.imageSize().width(), Qgis::RenderUnit::Points ); + imageHeight = originalImageSize.height() * imageWidth / originalImageSize.width(); + } + else + { + imageWidth = context.convertToPainterUnits( fragmentFormat.imageSize().width(), Qgis::RenderUnit::Points ); + imageHeight = context.convertToPainterUnits( fragmentFormat.imageSize().height(), Qgis::RenderUnit::Points ); + } - fragmentHorizontalAdvance << fragmentWidth; + fragmentWidth = imageWidth; - const double fragmentHeightUsingAscentDescent = ( fm.ascent() + fm.descent() ) / scaleFactor; - const double fragmentHeightUsingLineSpacing = fm.lineSpacing() / scaleFactor; + // we consider the whole image as ascent, and descent as 0 + blockHeightUsingAscentDescent = std::max( blockHeightUsingAscentDescent, imageHeight + fm.descent() / scaleFactor ); + blockHeightUsingLineSpacing = std::max( blockHeightUsingLineSpacing, imageHeight + fm.leading() ); - blockWidth += fragmentWidth; - blockXMax += fragmentWidth; - blockHeightUsingAscentDescent = std::max( blockHeightUsingAscentDescent, fragmentHeightUsingAscentDescent ); + maxBlockAscent = std::max( maxBlockAscent, imageHeight ); + maxBlockCapHeight = std::max( maxBlockCapHeight, imageHeight ); + maxLineSpacing = std::max( maxLineSpacing, imageHeight + fm.leading() / scaleFactor ); + maxBlockLeading = std::max( maxBlockLeading, fm.leading() / scaleFactor ); + maxBlockMaxWidth = std::max( maxBlockMaxWidth, imageWidth ); + maxBlockFixedItemHeight = std::max( maxBlockFixedItemHeight, imageHeight ); + fragmentFixedHeights << imageHeight; + } + else + { + fragmentWidth = fm.horizontalAdvance( fragment.text() ) / scaleFactor; - blockHeightUsingLineSpacing = std::max( blockHeightUsingLineSpacing, fragmentHeightUsingLineSpacing ); - maxBlockAscent = std::max( maxBlockAscent, fm.ascent() / scaleFactor ); + const double fragmentHeightUsingAscentDescent = ( fm.ascent() + fm.descent() ) / scaleFactor; + const double fragmentHeightUsingLineSpacing = fm.lineSpacing() / scaleFactor; + blockHeightUsingAscentDescent = std::max( blockHeightUsingAscentDescent, fragmentHeightUsingAscentDescent ); - maxBlockCapHeight = std::max( maxBlockCapHeight, fm.capHeight() / scaleFactor ); + blockHeightUsingLineSpacing = std::max( blockHeightUsingLineSpacing, fragmentHeightUsingLineSpacing ); + maxBlockAscent = std::max( maxBlockAscent, fm.ascent() / scaleFactor ); + maxBlockAscentForTextFragments = std::max( maxBlockAscentForTextFragments, fm.ascent() / scaleFactor ); - blockHeightUsingAscentAccountingForVerticalOffset = std::max( std::max( maxBlockAscent, fragmentHeightForVerticallyOffsetText ), blockHeightUsingAscentAccountingForVerticalOffset ); + maxBlockCapHeight = std::max( maxBlockCapHeight, fm.capHeight() / scaleFactor ); - maxBlockDescent = std::max( maxBlockDescent, fm.descent() / scaleFactor ); - maxBlockMaxWidth = std::max( maxBlockMaxWidth, fm.maxWidth() / scaleFactor ); + maxBlockDescent = std::max( maxBlockDescent, fm.descent() / scaleFactor ); + maxBlockMaxWidth = std::max( maxBlockMaxWidth, fm.maxWidth() / scaleFactor ); + + if ( ( fm.lineSpacing() / scaleFactor ) > maxLineSpacing ) + { + maxLineSpacing = fm.lineSpacing() / scaleFactor; + maxBlockLeading = fm.leading() / scaleFactor; + } + fragmentFixedHeights << -1; + } blockYMaxAdjustLabel = std::max( blockYMaxAdjustLabel, fragmentYMaxAdjust ); + blockHeightUsingAscentAccountingForVerticalOffset = std::max( std::max( maxBlockAscent, fragmentHeightForVerticallyOffsetText ), blockHeightUsingAscentAccountingForVerticalOffset ); - if ( ( fm.lineSpacing() / scaleFactor ) > maxLineSpacing ) - { - maxLineSpacing = fm.lineSpacing() / scaleFactor; - maxBlockLeading = fm.leading() / scaleFactor; - } + fragmentHorizontalAdvance << fragmentWidth; + + blockWidth += fragmentWidth; + blockXMax += fragmentWidth; fragmentFonts << updatedFont; @@ -233,7 +295,7 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo { // same logic as used in QgsTextRenderer. (?!!) // needed to move bottom of text's descender to within bottom edge of label - res.mFirstLineAscentOffset = 0.25 * maxBlockAscent; // descent() is not enough + res.mFirstLineAscentOffset = 0.25 * maxBlockAscentForTextFragments; // descent() is not enough res.mLastLineAscentOffset = res.mFirstLineAscentOffset; res.mFirstLineCapHeight = maxBlockCapHeight; const double lineHeight = ( maxBlockAscent + maxBlockDescent ); // ignore +1 for baseline @@ -262,8 +324,11 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo } else { - const double thisLineHeightUsingAscentDescent = format.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( format.lineHeight() * ( maxBlockAscent + maxBlockDescent ) ) : lineHeightPainterUnits; - const double thisLineHeightUsingLineSpacing = format.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( format.lineHeight() * maxLineSpacing ) : lineHeightPainterUnits; + double thisLineHeightUsingAscentDescent = format.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( format.lineHeight() * ( maxBlockAscent + maxBlockDescent ) ) : lineHeightPainterUnits; + double thisLineHeightUsingLineSpacing = format.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( format.lineHeight() * maxLineSpacing ) : lineHeightPainterUnits; + + thisLineHeightUsingAscentDescent = std::max( thisLineHeightUsingAscentDescent, maxBlockFixedItemHeight ); + thisLineHeightUsingLineSpacing = std::max( thisLineHeightUsingLineSpacing, maxBlockFixedItemHeight ); currentLabelBaseline += thisLineHeightUsingAscentDescent; currentRectBaseline += thisLineHeightUsingLineSpacing; @@ -278,7 +343,7 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo heightCapHeightMode += thisLineHeightUsingLineSpacing; heightAscentMode += thisLineHeightUsingLineSpacing; if ( blockIndex == blockSize - 1 ) - res.mLastLineAscentOffset = 0.25 * maxBlockAscent; + res.mLastLineAscentOffset = 0.25 * maxBlockAscentForTextFragments; } if ( blockIndex == blockSize - 1 ) @@ -305,6 +370,7 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo res.mBlockMaxDescent << maxBlockDescent; res.mBlockMaxCharacterWidth << maxBlockMaxWidth; res.mFragmentVerticalOffsetsLabelMode << fragmentVerticalOffsets; + res.mFragmentFixedHeights << fragmentFixedHeights; res.mFragmentVerticalOffsetsRectMode << fragmentVerticalOffsets; res.mFragmentVerticalOffsetsPointMode << fragmentVerticalOffsets; res.mFragmentHorizontalAdvance << fragmentHorizontalAdvance; @@ -477,6 +543,11 @@ double QgsTextDocumentMetrics::fragmentVerticalOffset( int blockIndex, int fragm BUILTIN_UNREACHABLE } +double QgsTextDocumentMetrics::fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const +{ + return mFragmentFixedHeights.value( blockIndex ).value( fragmentIndex ); +} + double QgsTextDocumentMetrics::verticalOrientationXOffset( int blockIndex ) const { return mVerticalOrientationXOffsets.value( blockIndex ); diff --git a/src/core/textrenderer/qgstextdocumentmetrics.h b/src/core/textrenderer/qgstextdocumentmetrics.h index 1a182e84551c..f5c10f1bf69b 100644 --- a/src/core/textrenderer/qgstextdocumentmetrics.h +++ b/src/core/textrenderer/qgstextdocumentmetrics.h @@ -113,6 +113,13 @@ class CORE_EXPORT QgsTextDocumentMetrics */ double fragmentVerticalOffset( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const; + /** + * Returns the fixed height of the fragment at the specified block and fragment index, or -1 if the fragment does not have a fixed height. + * + * \since QGIS 3.40 + */ + double fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const; + /** * Returns the vertical orientation x offset for the specified block. */ @@ -160,6 +167,7 @@ class CORE_EXPORT QgsTextDocumentMetrics QList< double > mBaselineOffsetsAscentBased; QList< QList< double > > mFragmentHorizontalAdvance; + QList< QList< double > > mFragmentFixedHeights; QList< QList< double > > mFragmentVerticalOffsetsLabelMode; QList< QList< double > > mFragmentVerticalOffsetsPointMode; diff --git a/src/core/textrenderer/qgstextrenderer.cpp b/src/core/textrenderer/qgstextrenderer.cpp index 6d83ae3df5b9..e9f17510441a 100644 --- a/src/core/textrenderer/qgstextrenderer.cpp +++ b/src/core/textrenderer/qgstextrenderer.cpp @@ -30,6 +30,8 @@ #include "qgstextrendererutils.h" #include "qgsgeos.h" #include "qgspainting.h" +#include "qgsapplication.h" +#include "qgsimagecache.h" #include #include @@ -603,7 +605,7 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend { QFont fragmentFont = metrics.fragmentFont( component.blockIndex, fragmentIndex ); - if ( !fragment.isWhitespace() ) + if ( !fragment.isWhitespace() && !fragment.isImage() ) { if ( component.extraWordSpacing || component.extraLetterSpacing ) applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing ); @@ -771,7 +773,7 @@ void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer int fragmentIndex = 0; for ( const QgsTextFragment &fragment : component.block ) { - if ( !fragment.isWhitespace() ) + if ( !fragment.isWhitespace() && !fragment.isImage() ) { const QFont fragmentFont = metrics.fragmentFont( component.blockIndex, fragmentIndex ); @@ -1880,7 +1882,7 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con for ( const QgsTextFragment &fragment : block ) { // draw text, QPainterPath method - if ( !fragment.isWhitespace() ) + if ( !fragment.isWhitespace() && !fragment.isImage() ) { QPainterPath path; path.setFillRule( Qt::WindingFill ); @@ -1899,6 +1901,22 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con textp.setBrush( textColor ); textp.drawPath( path ); } + else if ( fragment.isImage() ) + { + bool fitsInCache = false; + const double imageWidth = metrics.fragmentHorizontalAdvance( blockIndex, fragmentIndex, mode ) * fontScale; + const double imageHeight = metrics.fragmentFixedHeight( blockIndex, fragmentIndex, mode ) * fontScale; + + const QImage image = QgsApplication::imageCache()->pathAsImage( fragment.characterFormat().imagePath(), + QSize( static_cast< int >( std::round( imageWidth ) ), + static_cast< int >( std::round( imageHeight ) ) ), + false, + 1, fitsInCache, context.flags() & Qgis::RenderContextFlag::RenderBlocking ); + const double imageBaseline = metrics.fragmentVerticalOffset( blockIndex, fragmentIndex, mode ); + const double yOffset = imageBaseline - image.height(); + if ( !image.isNull() ) + textp.drawImage( QPointF( xOffset, yOffset ), image ); + } xOffset += metrics.fragmentHorizontalAdvance( blockIndex, fragmentIndex, mode ) * fontScale; fragmentIndex ++; @@ -1940,7 +1958,7 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con int fragmentIndex = 0; for ( const QgsTextFragment &fragment : block ) { - if ( !fragment.isWhitespace() ) + if ( !fragment.isWhitespace() && !fragment.isImage() ) { QFont fragmentFont = metrics.fragmentFont( blockIndex, fragmentIndex ); diff --git a/tests/src/core/testqgslabelingengine.cpp b/tests/src/core/testqgslabelingengine.cpp index c179e8899e21..7d0312806499 100644 --- a/tests/src/core/testqgslabelingengine.cpp +++ b/tests/src/core/testqgslabelingengine.cpp @@ -79,6 +79,7 @@ class TestQgsLabelingEngine : public QgsTest void testPointLabelTabsHtml(); void testPointLabelHtmlFormatting(); void testPointLabelHtmlFormattingDataDefinedSize(); + void testPointLabelHtmlImages(); void testCurvedLabelsWithTinySegments(); void testCurvedLabelCorrectLinePlacement(); void testCurvedLabelNegativeDistance(); @@ -1900,6 +1901,59 @@ void TestQgsLabelingEngine::testPointLabelHtmlFormattingDataDefinedSize() QVERIFY( imageCheck( QStringLiteral( "label_point_html_rendering" ), img, 20 ) ); } +void TestQgsLabelingEngine::testPointLabelHtmlImages() +{ + // test point label rendering with HTML images + QgsPalLayerSettings settings; + setDefaultLabelParams( settings ); + + QgsTextFormat format = settings.format(); + format.setSize( 20 ); + format.setColor( QColor( 0, 0, 0 ) ); + format.setAllowHtmlFormatting( true ); + settings.setFormat( format ); + + settings.fieldName = QStringLiteral( "'test HTML'" ).arg( TEST_DATA_DIR ); + settings.isExpression = true; + settings.placement = Qgis::LabelPlacement::OverPoint; + settings.labelPerPart = false; + + std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + vl2->setRenderer( new QgsNullSymbolRenderer() ); + + QgsFeature f; + f.setAttributes( QgsAttributes() << 1 ); + const QgsGeometry refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190100 5000000, 190200 5000000)" ) ); + f.setGeometry( refGeom.centroid() ); + QVERIFY( vl2->dataProvider()->addFeature( f ) ); + + vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary! + vl2->setLabelsEnabled( true ); + + // make a fake render context + const QSize size( 640, 480 ); + QgsMapSettings mapSettings; + mapSettings.setLabelingEngineSettings( createLabelEngineSettings() ); + mapSettings.setDestinationCrs( vl2->crs() ); + + mapSettings.setOutputSize( size ); + mapSettings.setExtent( refGeom.boundingBox() ); + mapSettings.setLayers( QList() << vl2.get() ); + mapSettings.setOutputDpi( 96 ); + + QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings(); + engineSettings.setFlag( Qgis::LabelingFlag::UsePartialCandidates, false ); + engineSettings.setFlag( Qgis::LabelingFlag::DrawCandidates, true ); + mapSettings.setLabelingEngineSettings( engineSettings ); + + QgsMapRendererSequentialJob job( mapSettings ); + job.start(); + job.waitForFinished(); + + QImage img = job.renderedImage(); + QVERIFY( imageCheck( QStringLiteral( "html_images" ), img, 20 ) ); +} + void TestQgsLabelingEngine::testCurvedLabelsHtmlSuperSubscript() { // test line label rendering with HTML formatting diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index b1706f56a5dd..d15daf3c28ec 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -62,7 +62,7 @@ import unittest from qgis.testing import start_app, QgisTestCase -from utilities import getTestFont, svgSymbolsPath +from utilities import getTestFont, svgSymbolsPath, unitTestDataPath start_app() @@ -4001,6 +4001,78 @@ def testHtmlAlignmentCenterBase(self): '

Test some text

Short

test

test

center
'], rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + def testHtmlImageAutoSize(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(30) + format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) + format.setColor(QColor(255, 0, 0)) + format.setAllowHtmlFormatting(True) + format.background().setEnabled(True) + format.background().setType(QgsTextBackgroundSettings.ShapeType.ShapeRectangle) + format.background().setSize(QSizeF(0, 0)) + format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) + format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) + format.background().setFillColor(QColor(255, 255, 255)) + + assert self.checkRender(format, 'image_autosize', None, text=[ + f'

Test test

'], + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + + def testHtmlImageAutoWidth(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(30) + format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) + format.setColor(QColor(255, 0, 0)) + format.setAllowHtmlFormatting(True) + format.background().setEnabled(True) + format.background().setType(QgsTextBackgroundSettings.ShapeType.ShapeRectangle) + format.background().setSize(QSizeF(0, 0)) + format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) + format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) + format.background().setFillColor(QColor(255, 255, 255)) + + assert self.checkRender(format, 'image_autowidth', None, text=[ + f'

Test test

'], + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + + def testHtmlImageAutoHeight(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(30) + format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) + format.setColor(QColor(255, 0, 0)) + format.setAllowHtmlFormatting(True) + format.background().setEnabled(True) + format.background().setType(QgsTextBackgroundSettings.ShapeType.ShapeRectangle) + format.background().setSize(QSizeF(0, 0)) + format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) + format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) + format.background().setFillColor(QColor(255, 255, 255)) + + assert self.checkRender(format, 'image_autoheight', None, text=[ + f'

Test test

'], + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + + def testHtmlImageFixedSize(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(30) + format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) + format.setColor(QColor(255, 0, 0)) + format.setAllowHtmlFormatting(True) + format.background().setEnabled(True) + format.background().setType(QgsTextBackgroundSettings.ShapeType.ShapeRectangle) + format.background().setSize(QSizeF(0, 0)) + format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) + format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) + format.background().setFillColor(QColor(255, 255, 255)) + + assert self.checkRender(format, 'image_fixed_size', None, text=[ + f'

Test test

'], + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + def testHtmlSuperSubscript(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) diff --git a/tests/testdata/control_images/labelingengine/expected_html_images/expected_html_images.png b/tests/testdata/control_images/labelingengine/expected_html_images/expected_html_images.png new file mode 100644 index 0000000000000000000000000000000000000000..21cd843be5c27b966d142e8f156e8c2b5ce372d6 GIT binary patch literal 30781 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 T{1hm|K)|J>pdw!_YaIGNv4Uma literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/text_renderer/image_autoheight/image_autoheight.png b/tests/testdata/control_images/text_renderer/image_autoheight/image_autoheight.png new file mode 100644 index 0000000000000000000000000000000000000000..ec4a12e942eaf4f786e93f32c3fe7bec57950027 GIT binary patch literal 19660 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 S2K=5(MvKiRY;R literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/text_renderer/image_autosize/image_autosize.png b/tests/testdata/control_images/text_renderer/image_autosize/image_autosize.png new file mode 100644 index 0000000000000000000000000000000000000000..56b398057a085384950080d751cce3e946b84dd3 GIT binary patch literal 9610 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

KUV#p<$Mo|b<)A~vClZ8=NpbZ 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�a16*`6SpMX(3JW_ntB)W zUHhV;a=n1QayDb~7yllQ3lYF%7H_}HN&ZZ+Zz8OjX8@Ms<@#9Te9QOJx4U`0I&Q#{ z=$O-8$LfSm5^Hx>*`>}NS^D9RgeuaFKXbj}xi=867BSe_PCcTH*XXRZ>70fkL~+V> zXO&Afjvi?~{(YpwjzCMir&e69N10>Ocxy=TMawR4pwW4ufL7+>M%K)y|@ zZa=4c5dix|(sllil9reAjmP6KZ>#Q&Q!dmtG$DAY61h(qC(YzBq8@X*2c&l>Aw%sJ zn|zQIcuWg)XNoOH6fA~w}HXDwpl;?UfRGl^(>m|0L-n&i?dWxz7kO@C$(wmHPpb2xgLuICRtv5>)?eX{Wfss z-iksSGJ5>4E|!}+;1bF%oEjBul$_8Ev_v` z7iZ;bL-&He{TN-ferSRFJ|N6Oi{w!Km4c-K;xGQ1-JoIDs&w=ao-)$U^LauD%+F*L zvbr%94MaJ@1L9)IezOGVV(ifP4x`V>^MutiUi7fwwi-kWThi`TyPLWxzMNd-AZI7d z17_1cUUyZFid!pn!i|F=?Gz8rchX^)r$gu9$+W@6{)!uxyBu z^IuEu?tuB)?Wp8t6U?oXK9p4w5+pestt2ougR@V3HHQ0{c2|00T_gq>D+aP2KK`y7 z>#355}-cAoXVH7?@F$&S7x4G7D*rfj&{pTjB4X?H2hO0R7Owh{!%?(x{!wJ*5vxG zRF=aQ<76*COH>chn!)}j2N9J;6U{y`I;=o%jdQow43|s|(-d+K<5{lh40BZf?A39t zpTMp(tz^EI68faldb`RG%y>z3KTtT|}1@Wi8d6WA8bc{*y#}Q2uZG(&v_OQ*_ zvvQL&pyc`{TmUiCvtQygA?Da&A9nc{;bB~STSEvoidY#F-mfbz5L?ld_1eulN!DDW(U6#_lD<+e zdUv|Ijqua&v9aBSG;h201t}O15YG14xWo#CPb_#SOnfd@2sZ6sUbM0AvX$7h#+K_x zU8W}_TNJ4Islm@_i1g(^Y%0{!rpS`4QMh$pG*u7U0pkO|5~i&YQUJ@gf$l1nE0a{$d1DZ5+M^2 z5n*w%UFFcxa6kP*Ms~O6fB?!&#dBMwOE^pn^HgfjckK z_RA0yYXn4E7)-in<}rnx=Z>Q~L%aUj7F$S35@VWVqR07Q2Z-sKNCZ*jQeXGbbabEEHCZO08);Qs3n_HG?(5 z>ck$!`%dmQE+40%5?zDILUsqGf%aSoKaEbfD}5Q(FsV5vJLt23R!ASMD7hXD0+l<@ zij~7zs^yOWSprQ;(H>~=j2+f{)sg%Kq-C>t5&Ycbb5`X=7|2C1lm1Z&CsEN+M}jaE z4UJ#O&?(3XIOBbVOTwZHlYaf$Zd;f*ES{L3F?m!F5a{jA z%5VsAMizEzsUgP9^#j3-Zq)XiWHp2GYnj@SIIbx5N~T6gVkrkF$zrMyYDy{PuxSD< z^z?+xxKZd{0V_u^NDW)Cy&KPD50i!l0$J@c{PO0Et@0vLv4fVDd}HJ(E5J>J->4K` z?ofQ(DtXZa5$4Pn2g6Rfzs-qGc;|PQ=PN3FR;2Y3r!Aoh$YJ6T5ro>>UJyo@eNJG- zS#H&==;6M$WI~q=Op6>dyRLFrkNIz*uT(Y&t(VnTQP9Q|B9>E27XF;WoBxzzbY1zf zG;$kGg2i(ALl6IXGBBAc>fa!{wM~OtG|`|-Y*b3)sk1G0Qwq=}Bns9fGcMPVVhhF< zFTS2-dwchs93ytbT_wCYlW&N-PSq#Nv7#H25~IYh*w7Gh8K&V-AW(zTuzGDK0~{3B zt>sCe9@1(sXErZveG7*+m|ha324B-07l_G*jxi&w$0lN0 z{KcG53sgv;B8?t;Vmdq$i?xzTAB8C#+8CC`L#tIEpW&a}#1)@}eBG;xqK)P1l$)wWslgq=WZ}(&4DdR+g)14aDw+HiH|6XuyP^0tI5Tz*t&a4I^Oqic?e;ywI!Xvp3we z7xVDjezu5|HsGO67E4xCT#Bqp!I!utcf%Ixr(#UOH_^a$uwdl_B2{rhV&*2oU5~|( z#p8R+>8*I-ecbcAnhE`u*uRVHyX?7&!|qd!^}UVL~PfrASI=Cu3IV z8Z*d=XwZq<4ss~Q)>j7U%wQWMvN-XM51Ud0uHI^|7znX95EmkAx&CTcqt;ert+X~u zjyOk&iy-XR{avLNT9M+KQ*pCsJ$zDRb;D$P|5HDY1%EQ8Ne&sU`1X8Kq?BG2aT#@@ zjA>YPEkb1sW%#eIbgnGT6@(Gq z*vGu_aQt|7u^JoC)JuMl&*^2Mk+S2axlWtj4&80>cv&mot&6O})&>c5FrMneH8t&wDJ^P@$gEwTvK04f z@eK7ue$cdt^os`BE_(rWB?5cJU`5zM#`5j%7gdfDY-5mhubneXp*6CcB;lKPFfD~w z46_S7psgr1!V~eI^>~k7@XN~^OH(mr`Whs`@L*2?MvCM%jIk%>_tMXd6^RAzDXf@12=xMt)Ah7H$#`MEsy0LYzA7Dwfvz`$W3gEauQY;7**Pw#$|eD z=wF%@TTziE z)mMbJGF7n*mF)uwv$u+k;%Zf7>7pU1qNG*DQJ7#(8=$7y!R}3j|1gbli6(L>!W~iMZ;3#uRcMEt~qM4P&cqo>MsT zoLEz-+3?&4S>?SP0eT`l7C8%MQ)cSCWwOAL00y;OImXL7vri9pT9lG0Q+C4?fUnL_k5#9r}GXIFU*EOE*@8FWyKyI>ZBMOz?tQ|2KD>{z}zSD>O5Inpg z<0GcjLbA{)V{#vR);AU>1gsS?GYNr2ELJ?MLD0{ITJ@+uz2VEJ`k|R4xkd=sCO7!AopS&^AD4tckjcu&$0LnT7ayxck zO6C4dyOXjWMgt;642uN<3NzHMPXq?nbcdo*!4z6Zj2$+LI1JY$=KJ$f;sX7Dx?m#` zy^y2T(O(l8cl2rhihxX>H60y=a3Fm|_8yK&u-@Zq5*`RzTv9yDEqTDP!q6_POI#Sc zx*^4J`5dwF4oEnOk*sux1z%vDFU{da08v1}uoB9tBzV{YC56X#L?G9YpFAUoYPe{| z3i3fn*V77q*mO_L;B`avzl2M;4mA)Y(3+%kvq29AOqJDk z!n@^RVasLkC*Ay)4rmdg_G{M`%?{_PDjF<&le2kB{>PPP8Uyd%#ij1J5MayN^n(XG zW0L|@K-)VBzU5x1CIN`YV$N(l5Bp%0-Al)zV$>v$eU#y45nzg=Hu#am z22;kY5a0PSmd8y*6`{eXFqjk^5a_7rF%Vk1GL-MQ#v=dMM+YXwC34&x_&Y(F;3{Ty zQc`@JW=M*_WwXQIqeDT#U60%5vcBJf;D@oEpHKht!xemu5C7)B;q#wpEgkyH!=w51 z{ZbMZqo~#O@^-9rH6xum@5IzG=};i!zN|3p#J)!%(E}i4VC|$qn$v;Zc2&II+;n-J^G*Ea(#{EzTF2#0$V$1#hn|Ol^Jyz9r?1U zOXtX^DjFMasZ< z;54?%m&0*kTCJPXB2rJVr8)mHo~#pT++L*KBrghHilQXD!X!x_sg54utr1HhI^b7< zUe&G~UY$vK)I9GlGNBE%4xCmkE1aoDpEdX&_2bru&(iMZi~eK&hs*GD-3MxmCtVD=`KR%{Rd?9(gB1KR1wOo%mjkcxDiP)L!`Uo=w>+;?b`Em- zbgx){n;h|iG8~v{LpiOC3&9ElMtc3&zkzMHai#zfez2qp64z*ED1d8Ro<7v?JxF^w`E3F2*W01*}an9fa?BTf1cFfh2u9`s!i2y{NPE$sYo| z%XiPSa6sL4S92Nf(W?P&T$&rj{Bh6TzK`pW!%D%&wE95sX?UKcV>% z(-T-G=$GHgOY!_C-spZ9HO9Ijm$apbmosv{?L0?QQ$p$6I*pUa9(U_J^PeOsqW}-P zB0>fym!VkoaiJR0=UFd{nx7-G11ptx1A@o_mvEgwo|!(J?=LLPU~oTKRBt)Kb^VjO ze18yKn-tiY%iqPF#s`70HUFy-fckN4&?opT@$xFC9Jcjt_qcBmF8FcN{o^|{*A{Zy zI^>iO$!kggtJZ5T+%}xQVO|;n@;#+QeYw+EOLVt__a{k+?{!|o( zeJ2X==-<(MuB{c&nhTyG83Y~*l7e6N-r5) z)oZmKI49VhuwQONM2{BI3YmG@w|qkll~0OW=n9i+0-w!PPSIqx=9_EGn!oP4*>bhJ zE@l-vZmvmjY8iC-#nKfqFkBW1BKEv=zVM#`tDVsWy?Kr6`Ovvjnqw|#!5{wL4=Hn550bV!wJJCkDC|Y61DS8y%i>y zHNcwH2e9DgVNH|jhVTiDU!mRFiL6m6!!1>{dR%k``gSiFU>#pXX8oo8CaX8d*mO-o zjTLLf)DpK%-qnFtUJ*b_z;A008dYD;!Gu4qklSC*R|MZi^7#|B&En&1K5GWtTU<6~ z2+t9nm-AhOX7mu%=BixYw+&=~T|+%Q21F#o<&0vUjQzJ^|2CY7NCxnSlV`dgw`Ds$Z*7!ao#Seba=QJcL7%!Uzn>P+Zd(N4{&?VT|3}?^A>cXX zv#sH8R>t3b`0di*9qDyBL9g}krKjgcb#nj!^}06$|MSfDx9Yk9D70RK_s_nM1D6p2 z@Hshr`4{U{)bx;U-8W?*Hi}X|!@sK{V&QW8qrgFgZE2ZdGQQSu)Fkd-Oz^#8vE#;~ zCOJ4ro9!Kz42sV}Qopd9$tV94!@9wl!pv7(-)usMJ0WOobhyXnOcjw1+hoCr=$3GI=fPAKBF!}VqJq+0BH_d(Znes8u zJ^suT1kCi0N3qPDkQw-|x`7G-1i<n2AFrnWDV~jb(3}7l2vyUm+PwyZCkSBJfzV^~-iYN+HzZ8xkPXj|&E23I5psb^Eka z$6@1r?di5`igw~@PB2d|+eP5~PMTC+7b|5)BfxF=Z@{y{nsv(%l0JyZ|7vpR`(>Nh{rZAB75#sUUgZO>Fhksmd3;p)&xywn^0L72vQq2hIcY0JUEtdd4uNOKd z21|o*#E6wjaRXXTEwR&*B4T+keZlOf72T`E5mC%RkX_MTY5bzA3(WT_)w(Qp_grZO zwf(BmPn$e8$ZQ5HB!{$5Ee1J8{at@?GAh)jOh}*{yys5YbVq?PZYZPrn7lf?%X!Y_ zYzMs64XnGGlg1m=gCB?RuS$2&-**Ia-(D}?Ht}OV4nK77&(f}M*#w~+0{DFQ84N7s z`pZU8hIIrlL&BVmjv~@&nrPzhwL?vjwVbKK=FPhIz8}gpg`eSxR3!`TDcjASNeHep z3nQ;C@M@QAkyIs4lHDfo2FLcry4 z4uEsO`_#{=GvC>i?Fxl33w|j34wxVJ&2N;%T+Hy2lIe0rZ_{YFV^1B%ig;7)7Pp6& z=!Hhk!|pRK<$v3WnK<+i#Sb$J;5l(-LLn{4AXS|{^De9p5P{9n!hWzV*#7UX5Ydf5HYzXXO&@H^f6uXkk= zC5SzbF_$qt@QeLeKR^mzi0jT{dvOnu5=1*yBeqVj!EX1bu|rG&`1&8uqPeCLzB>~B z(3^L)D^u&sfeC)d0{1MBq#sbv%!gmvdJ|sZyROqZOIA@hV;G^=qBU#E2} zQ0HoP$TT28=BdjkloMe2Zho zh)P=6n4wn@4x&e3GnXShzE7kpHtT{H^tkl#?0Co%;IC8vzPS}LG_=w;fxhjMA2Q{K z>VNS-&5#hl-*sDZnZ0C7`hy2Ce(Q6W;|{!hW3y|P(|gxpUzNUF0hCH`=Xvl_7cBT} zZCfRuH<+*EK=yj0hVaqZh?$EKZt_=AY`en0ISG7Xna^%oOfPXarTRtg$PG|ZiPWfu zAD|Uy?GJ23d?zj3UxCR{F4`$Y-V7IdYf}!lYr0~(zOm%tY5aIA{?zu~7#4j+7 z|2+OU_5t33m@;4|PwCgqD2vKYI%*EGVQeM-YM2levS9*O0Pt9Thzc_vh9?g%e-+gmxcQ zr}M>(S`L&z`TMP0TEJuPFED@g?eK!{yDzFCb`2c}b)GQ3yIlP)KU~-!Zt4#oVNR41 zvKzRG`nh}uM0smiE}xeGl@4{`{mJ)8)Nx6#$pkm!dOPIOrpKLI<94{fVO68Uxj{FR z0aoH1(_VP)c!=Uj9=2wXT^hxbOGF#R6pH|q{H0U3n3SWO&JY<-8yTg=rkvx`p56Xo z%+u3mt8-YJzm4i$15NuBGC4-yE@?pfjV?au4SWH>Jm{et?fo%fr?zt&D?d}!@**7m zH75Y-m`~!qAuy zipiI}>oRPbMm*qkHwCmvBZv@iO~ZDb=+Hx~u5O-TZGk#+g|yg||IOqop3l$(>fj*$ zTEt?q_vQ!Ol+WhgT0#KP`~B8t7T--6(<#4o>BTg=M%G_He&z`A_pc*+`x zGM});Oo|Tls?1^_E-Fc^Op_$DFEUN0eJT-F5=m!mSzkroj1Kj4IXEUbx*rvG$Pr95 zo-q)_RJHke57^ok1;6Y0OZZ$yO7yDy_>Zue#ZUlK$i|vNiYN{-<-O?MdI-3h8$F#( z*h)Cy+t2O$>0`Kcf5pve#i!pI62i*(%?^!YFNe8EjxL#eA>LO6*E-r88$W^+0-h(^ zBuQ14`^H;??R<_Pn@tRDTuD{7yqj|wBA^Vs++{;A6Me>X1xQJfGBa1WZSwBI7>LrT zQ3@(%&1Y(GHna`QkFq(|s#nZ6I8R!&nkKTX7ImABS2>pKH0=;6Lz3Zc_8oz9+Ev0@ zvXGkQHm?k~5Y-|3GgGg`e)|k;tUOF+=GF^@TkSSJSu$EWAps8;+qIqUUngyi-RNRs znj}PtFXJAvrcMJK&>U)kQ<(}Mx3NQ9J@}jVt$J@CwIOTvA)mV8?ZNn)SMm5X|2g0u z-8OZQ*>!$)-~SCzl6JTZy{u?m1A^1_@pic~1&|&E5;&!i-5}~+nq_>O6(XQ~LpGIi zqKS^D!L#x9ScfyW!6eFQeKAj+C{v2W&jHAAoGFxzbrFsLYvurN?jCNuNsWd2YQ)2C z7kh0c1B+K0xw$aHE`CLm}bK9aO>tr9?VG3t;}VUAm7t7fff;43Sjy&)=h@QmnTWNrKRc*$Ak2WA7F-YH6z(a+#`KhUIrpBYxaA zO~e@ZPUH2LbUjU6&Ls5bd7SGS=!6)&?^kuD{Fw5;N9Vmx__&HK-Y0z~2S#SzEI)cW z(sG<}om$pw*EYc%TlZP;G34^$<308+qZ=7~r&BlZMwB_yd-x?Iv360F2ZWW8_=iBn zn~OI3$(dOL^;6|jP!@xw=5}rbPo0bYA^$iIyT-~BkD#EzWFE=aT!Kj^)PsYnoq8hV z=*D{glOLqYl}9!PohQRvB&s-F58W>hs{-oae>vMI4w((_?Z5jHJ|5lL^RXs5Y%gz5 zEe2==Alf#)+l!(he|)@Bwm(-Hp!D47G;zI0LvB8RPhML$v2t;NOY~BM+CLx0;{QS} zXB=)f&yC}X4T^PZ{T_sl?JdvvhL<^l#~HrNOn4ymEV6~`m~$riB0O-W!+)KWf<9~H zHD9zXN*cV>ZU3F$#=*X0f7*nuc8?DXC2*ajhehz8<9b&`diGrh^j=9E~| z@3v?M?E_ulS?Lk5E&`W9-N!WGr<@%i`T#r^uX*?*J`SExzEcmqfCK`M!RwIe`-H4~ zUht^ zZyAJbyIy5#LVQqSXx8ZJ1!5@g$93Be0i^Fo34nbJ+&2T-TFEV9w8$Of@w~~|F?dA+ z0tMS1{%`^xPkMFU&+PmkPmU)wm#>FYlEB3^q;0f!5YXN8F0AukHgzbZHai_tVSsm~ ze_PlsfU!9MFx*QOLBOe@S$F#csE+#9Ru@9+1EIJGmd6u=%@c?sP}<#iY^O4r8QGl8em@yD5*I3 zzw(K>c#Vc;tXWw9E2-#W3=}CFF_lLK%6(;xM;R^6PuIoY`JwGWtYnSfS$y`@i-Ys0 zJ*C&&E#J-j$O7I$5pzdi16~E{`%$Oxu>61f{{Q`_|97JQkDiG45BK9hXrMZ%(DcXB z8N6#~JvpPHsAg|?eISYv1YtiUXDEy Date: Tue, 17 Sep 2024 19:22:03 +1000 Subject: [PATCH 4/4] Test masks --- .../image_autoheight/image_autoheight_mask.png | Bin 0 -> 1821 bytes .../image_autowidth/image_autowidth_mask.png | Bin 0 -> 1823 bytes .../image_fixed_size/image_fixed_size_mask.png | Bin 0 -> 1836 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/testdata/control_images/text_renderer/image_autoheight/image_autoheight_mask.png create mode 100644 tests/testdata/control_images/text_renderer/image_autowidth/image_autowidth_mask.png create mode 100644 tests/testdata/control_images/text_renderer/image_fixed_size/image_fixed_size_mask.png diff --git a/tests/testdata/control_images/text_renderer/image_autoheight/image_autoheight_mask.png b/tests/testdata/control_images/text_renderer/image_autoheight/image_autoheight_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..40e8dae5d62b991e2c2a329f106cd95213742949 GIT binary patch literal 1821 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~teM`SSr1Aih2Gp?{-p2@(# zcHYy)F{EP7+q)NwW;rl81Sga{`d6j*Sq3M&(!|Ad;9k7Pai*iT(I`ZML$Dk&Ah!1&y(b0-tT_=^w!j- z^Cvov@7()_CuL{BG?qKhZ_Q*||8^-eQ;(|2dS3?y51l{E&VOEemYhrd!E;1J{K1^I z=l3N!Y_7dIA8>YGZB72Gi?{n&zgr(}-r=WmKJ&-&Z~LFFzFA%~?=+_ zTydCr)BE2?*Ef}=F@0ZsU2=PF^(n&$rSu$${4W0om(IQQ46WFgdwW`G?DX3E^Nj+g z-)z~KxJQ%eP=1|Hd$dhaJkGjHvfJcRKmsy|iY?nFY zZ+N>ytFbEFtEjzjL*jI~HiR?^=L@ie_da$rGxHvFCsHQ@hemSNC z4_Tgc7Rt4?qw`)!YwpV!a1 z$@1>NR6eLNdQX;b-97&w8yDlxM>lUBon_ivDD!`FSrX&ay<#=j*WKM~^!(~amM7i1 z?~XjHl(Vz9H<#pplQ{4B!?$nWuD8#h%)#w`Nc_CspZ?>?-!~qZcTMK;Id(CoO_P2m zt$U$&IWEWBA*^T-;~!@SGqb+MmPrr9_HZX8m)~M#zB4;Im0{9!z7w<7w!2&W{;`8^ zwzb6}Z}ul|ZgcvkZk+y!yX43II4gy(Ot!w4_59gY3+6>>$o023Y}gIdBz0Q$mWl6j zyPJQy{?7VVA@%jh>3ey?6H?2s++4nQ$1>Z#-PWu}>?d2b-)3RyD_P!c{asOkL1jD7 ziSqR^_E|Ckm9H7ycb~nuU1yR0M~lNrUv19sc6C{EE_t=?_S3WO-Q0KE_E>S7v_o5> zOxr?)2Wqtqv literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/text_renderer/image_fixed_size/image_fixed_size_mask.png b/tests/testdata/control_images/text_renderer/image_fixed_size/image_fixed_size_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..8d83c9aecf298bd28e251f57296a39e5c4b71837 GIT binary patch literal 1836 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~teM`SSr1Aih2Gp?{-p2@(# zcF)trF{EP7+q)NwW;rl81Sh+l49`w{d;%*Mq$Z^6_L}PW@&88 z{rxfDUi#t30&7M_xlQX{_b=S>?0iFk-aK0dAx1_8OIL?ymHVoV&pnepu{nF(?{o4%(;E~oWnBfb#3qd#xlg&C>+=rGh?qA(4vhW_+_5Gef#!>;s*v} z0ftG3(-rRhW%>PSrpC9TAFIEYGZsEL{P1nMT(B$|q^< z=M6sH_;K&y`g8YLk2JFWce9+XXWX2gQ!@SI%?A5>4;g=++_g@B=ajEk|L%?ZSokU5 z=JSmodzO4z^LhQB{`q10H|~F6oEcZ5_UUTvu9(2Wv*FJV#KrI1l6yON`rfPsaUV+` z=G6QZFVHZzwO#)(O<`Ig!@0yA4LCD7M2DV2Zm8