[feature] Support CSS background property in labels/text renderer
This commit adds support for the CSS ``background-color`` and ``background-image`` properties when used with HTML labels. - Backgrounds are supported for block items (eg ``<div>``) or inline items (eg ``<span>``) - For images, the CSS should be formatted as ``background-image: url(xx)``. It supports local file paths, http links, or base64 embedded content - Backgrounds are not supported for curved text - HTML backgrounds are always rendered above any background shape for the label, and below drop shadows/buffers Sponsored by City of Freiburg im Breisgau
@ -198,6 +198,8 @@ Returns ``True`` if the block has a background set.
|
||||
%Docstring
|
||||
Returns the brush used for rendering the background of the block.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundBrush`
|
||||
@ -209,8 +211,34 @@ Returns the brush used for rendering the background of the block.
|
||||
%Docstring
|
||||
Sets the ``brush`` used for rendering the background of the block.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundBrush`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QString backgroundImagePath() const;
|
||||
%Docstring
|
||||
Returns the path for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
void setBackgroundImagePath( const QString &path );
|
||||
%Docstring
|
||||
Sets the ``path`` for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
|
@ -385,6 +385,8 @@ Returns ``True`` if the fragment has a background set.
|
||||
%Docstring
|
||||
Returns the brush used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundImagePath` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundBrush`
|
||||
@ -396,8 +398,34 @@ Returns the brush used for rendering the background of the fragment.
|
||||
%Docstring
|
||||
Sets the ``brush`` used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundImagePath` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundBrush`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QString backgroundImagePath() const;
|
||||
%Docstring
|
||||
Returns the path for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
void setBackgroundImagePath( const QString &path );
|
||||
%Docstring
|
||||
Sets the ``path`` for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
|
@ -172,6 +172,24 @@ to the fragment at the specified index within that block.
|
||||
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 fragmentAscent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
%Docstring
|
||||
Returns the ascent of the fragment at the specified block and fragment index.
|
||||
|
||||
.. seealso:: :py:func:`fragmentDescent`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
double fragmentDescent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
%Docstring
|
||||
Returns the descent of the fragment at the specified block and fragment index.
|
||||
|
||||
.. seealso:: :py:func:`fragmentAscent`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
double verticalOrientationXOffset( int blockIndex ) const;
|
||||
@ -187,6 +205,17 @@ Returns the maximum character width for the specified block.
|
||||
double blockMaximumDescent( int blockIndex ) const;
|
||||
%Docstring
|
||||
Returns the maximum descent encountered in the specified block.
|
||||
|
||||
.. seealso:: :py:func:`blockMaximumAscent`
|
||||
%End
|
||||
|
||||
double blockMaximumAscent( int blockIndex ) const;
|
||||
%Docstring
|
||||
Returns the maximum ascent encountered in the specified block.
|
||||
|
||||
.. seealso:: :py:func:`blockMaximumDescent`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QFont fragmentFont( int blockIndex, int fragmentIndex ) const;
|
||||
|
@ -198,6 +198,8 @@ Returns ``True`` if the block has a background set.
|
||||
%Docstring
|
||||
Returns the brush used for rendering the background of the block.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundBrush`
|
||||
@ -209,8 +211,34 @@ Returns the brush used for rendering the background of the block.
|
||||
%Docstring
|
||||
Sets the ``brush`` used for rendering the background of the block.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundBrush`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QString backgroundImagePath() const;
|
||||
%Docstring
|
||||
Returns the path for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
void setBackgroundImagePath( const QString &path );
|
||||
%Docstring
|
||||
Sets the ``path`` for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextBlockFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
|
@ -385,6 +385,8 @@ Returns ``True`` if the fragment has a background set.
|
||||
%Docstring
|
||||
Returns the brush used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundImagePath` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundBrush`
|
||||
@ -396,8 +398,34 @@ Returns the brush used for rendering the background of the fragment.
|
||||
%Docstring
|
||||
Sets the ``brush`` used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundImagePath` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundBrush`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QString backgroundImagePath() const;
|
||||
%Docstring
|
||||
Returns the path for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`hasBackground`
|
||||
|
||||
.. seealso:: :py:func:`setBackgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
void setBackgroundImagePath( const QString &path );
|
||||
%Docstring
|
||||
Sets the ``path`` for the image to be used for rendering the background of the fragment.
|
||||
|
||||
Alternatively, the format may have a :py:func:`~QgsTextCharacterFormat.backgroundBrush` set.
|
||||
|
||||
.. seealso:: :py:func:`backgroundImagePath`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
|
@ -172,6 +172,24 @@ to the fragment at the specified index within that block.
|
||||
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 fragmentAscent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
%Docstring
|
||||
Returns the ascent of the fragment at the specified block and fragment index.
|
||||
|
||||
.. seealso:: :py:func:`fragmentDescent`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
double fragmentDescent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
%Docstring
|
||||
Returns the descent of the fragment at the specified block and fragment index.
|
||||
|
||||
.. seealso:: :py:func:`fragmentAscent`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
double verticalOrientationXOffset( int blockIndex ) const;
|
||||
@ -187,6 +205,17 @@ Returns the maximum character width for the specified block.
|
||||
double blockMaximumDescent( int blockIndex ) const;
|
||||
%Docstring
|
||||
Returns the maximum descent encountered in the specified block.
|
||||
|
||||
.. seealso:: :py:func:`blockMaximumAscent`
|
||||
%End
|
||||
|
||||
double blockMaximumAscent( int blockIndex ) const;
|
||||
%Docstring
|
||||
Returns the maximum ascent encountered in the specified block.
|
||||
|
||||
.. seealso:: :py:func:`blockMaximumDescent`
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QFont fragmentFont( int blockIndex, int fragmentIndex ) const;
|
||||
|
@ -45,6 +45,7 @@ Qgis::TextHorizontalAlignment convertTextBlockFormatAlign( const QTextBlockForma
|
||||
|
||||
QgsTextBlockFormat::QgsTextBlockFormat( const QTextBlockFormat &format )
|
||||
: mBackgroundBrush( format.background() )
|
||||
, mBackgroundPath( format.stringProperty( QTextFormat::BackgroundImageUrl ) )
|
||||
, mLineHeight( format.hasProperty( QTextFormat::LineHeight ) && format.lineHeightType() != QTextBlockFormat::ProportionalHeight ? format.lineHeight() : std::numeric_limits< double >::quiet_NaN() )
|
||||
, mLineHeightPercentage( format.hasProperty( QTextFormat::LineHeight ) && format.lineHeightType() == QTextBlockFormat::ProportionalHeight ? ( format.lineHeight() / 100.0 ) : std::numeric_limits< double >::quiet_NaN() )
|
||||
{
|
||||
@ -107,7 +108,7 @@ void QgsTextBlockFormat::updateFontForFormat( QFont &, const QgsRenderContext &,
|
||||
|
||||
bool QgsTextBlockFormat::hasBackground() const
|
||||
{
|
||||
return mBackgroundBrush.style() != Qt::NoBrush;
|
||||
return mBackgroundBrush.style() != Qt::NoBrush || !mBackgroundPath.isEmpty();
|
||||
}
|
||||
|
||||
QBrush QgsTextBlockFormat::backgroundBrush() const
|
||||
@ -119,3 +120,13 @@ void QgsTextBlockFormat::setBackgroundBrush( const QBrush &brush )
|
||||
{
|
||||
mBackgroundBrush = brush;
|
||||
}
|
||||
|
||||
QString QgsTextBlockFormat::backgroundImagePath() const
|
||||
{
|
||||
return mBackgroundPath;
|
||||
}
|
||||
|
||||
void QgsTextBlockFormat::setBackgroundImagePath( const QString &path )
|
||||
{
|
||||
mBackgroundPath = path;
|
||||
}
|
||||
|
@ -193,6 +193,8 @@ class CORE_EXPORT QgsTextBlockFormat
|
||||
/**
|
||||
* Returns the brush used for rendering the background of the block.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundBrush() set.
|
||||
*
|
||||
* \see hasBackground()
|
||||
* \see setBackgroundBrush()
|
||||
* \since QGIS 3.42
|
||||
@ -202,11 +204,34 @@ class CORE_EXPORT QgsTextBlockFormat
|
||||
/**
|
||||
* Sets the \a brush used for rendering the background of the block.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundBrush() set.
|
||||
*
|
||||
* \see backgroundBrush()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
void setBackgroundBrush( const QBrush &brush );
|
||||
|
||||
/**
|
||||
* Returns the path for the image to be used for rendering the background of the fragment.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundBrush() set.
|
||||
*
|
||||
* \see hasBackground()
|
||||
* \see setBackgroundImagePath()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
QString backgroundImagePath() const;
|
||||
|
||||
/**
|
||||
* Sets the \a path for the image to be used for rendering the background of the fragment.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundBrush() set.
|
||||
*
|
||||
* \see backgroundImagePath()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
void setBackgroundImagePath( const QString &path );
|
||||
|
||||
/**
|
||||
* Updates the specified \a font in place, applying block formatting options which
|
||||
* are applicable on a font level when rendered in the given \a context.
|
||||
@ -221,6 +246,7 @@ class CORE_EXPORT QgsTextBlockFormat
|
||||
private:
|
||||
|
||||
QBrush mBackgroundBrush;
|
||||
QString mBackgroundPath;
|
||||
|
||||
double mLineHeight = std::numeric_limits< double >::quiet_NaN();
|
||||
double mLineHeightPercentage = std::numeric_limits< double >::quiet_NaN();
|
||||
|
@ -53,6 +53,7 @@ QgsTextCharacterFormat::QgsTextCharacterFormat( const QTextCharFormat &format )
|
||||
, mUnderline( format.hasProperty( QTextFormat::FontUnderline ) ? ( format.fontUnderline() ? BooleanValue::SetTrue : BooleanValue::SetFalse ) : BooleanValue::NotSet )
|
||||
, mOverline( format.hasProperty( QTextFormat::FontOverline ) ? ( format.fontOverline() ? BooleanValue::SetTrue : BooleanValue::SetFalse ) : BooleanValue::NotSet )
|
||||
, mBackgroundBrush( format.background() )
|
||||
, mBackgroundPath( format.background().style() == Qt::NoBrush ? format.stringProperty( QTextFormat::BackgroundImageUrl ) : QString() )
|
||||
{
|
||||
mVerticalAlign = convertTextCharFormatVAlign( format, mHasVerticalAlignSet );
|
||||
|
||||
@ -103,8 +104,10 @@ void QgsTextCharacterFormat::overrideWith( const QgsTextCharacterFormat &other )
|
||||
mVerticalAlign = other.mVerticalAlign;
|
||||
mHasVerticalAlignSet = true;
|
||||
}
|
||||
if ( mBackgroundBrush.style() == Qt::NoBrush && other.mBackgroundBrush.style() != Qt::NoBrush )
|
||||
if ( mBackgroundBrush.style() == Qt::NoBrush && mBackgroundPath.isEmpty() && other.mBackgroundBrush.style() != Qt::NoBrush )
|
||||
mBackgroundBrush = other.mBackgroundBrush;
|
||||
if ( mBackgroundBrush.style() == Qt::NoBrush && mBackgroundPath.isEmpty() && !other.mBackgroundPath.isEmpty() )
|
||||
mBackgroundPath = other.mBackgroundPath;
|
||||
}
|
||||
|
||||
QColor QgsTextCharacterFormat::textColor() const
|
||||
@ -255,6 +258,16 @@ void QgsTextCharacterFormat::updateFontForFormat( QFont &font, const QgsRenderCo
|
||||
}
|
||||
}
|
||||
|
||||
QString QgsTextCharacterFormat::backgroundImagePath() const
|
||||
{
|
||||
return mBackgroundPath;
|
||||
}
|
||||
|
||||
void QgsTextCharacterFormat::setBackgroundImagePath( const QString &path )
|
||||
{
|
||||
mBackgroundPath = path;
|
||||
}
|
||||
|
||||
QgsTextCharacterFormat::BooleanValue QgsTextCharacterFormat::italic() const
|
||||
{
|
||||
return mItalic;
|
||||
@ -287,7 +300,7 @@ void QgsTextCharacterFormat::setWordSpacing( double spacing )
|
||||
|
||||
bool QgsTextCharacterFormat::hasBackground() const
|
||||
{
|
||||
return mBackgroundBrush.style() != Qt::NoBrush;
|
||||
return mBackgroundBrush.style() != Qt::NoBrush || !mBackgroundPath.isEmpty();
|
||||
}
|
||||
|
||||
QBrush QgsTextCharacterFormat::backgroundBrush() const
|
||||
|
@ -362,6 +362,8 @@ class CORE_EXPORT QgsTextCharacterFormat
|
||||
/**
|
||||
* Returns the brush used for rendering the background of the fragment.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundImagePath() set.
|
||||
*
|
||||
* \see hasBackground()
|
||||
* \see setBackgroundBrush()
|
||||
* \since QGIS 3.42
|
||||
@ -371,11 +373,34 @@ class CORE_EXPORT QgsTextCharacterFormat
|
||||
/**
|
||||
* Sets the \a brush used for rendering the background of the fragment.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundImagePath() set.
|
||||
*
|
||||
* \see backgroundBrush()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
void setBackgroundBrush( const QBrush &brush );
|
||||
|
||||
/**
|
||||
* Returns the path for the image to be used for rendering the background of the fragment.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundBrush() set.
|
||||
*
|
||||
* \see hasBackground()
|
||||
* \see setBackgroundImagePath()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
QString backgroundImagePath() const;
|
||||
|
||||
/**
|
||||
* Sets the \a path for the image to be used for rendering the background of the fragment.
|
||||
*
|
||||
* Alternatively, the format may have a backgroundBrush() set.
|
||||
*
|
||||
* \see backgroundImagePath()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
void setBackgroundImagePath( const QString &path );
|
||||
|
||||
/**
|
||||
* Updates the specified \a font in place, applying character formatting options which
|
||||
* are applicable on a font level when rendered in the given \a context.
|
||||
@ -409,6 +434,7 @@ class CORE_EXPORT QgsTextCharacterFormat
|
||||
BooleanValue mOverline = BooleanValue::NotSet;
|
||||
|
||||
QBrush mBackgroundBrush;
|
||||
QString mBackgroundPath;
|
||||
|
||||
};
|
||||
|
||||
|
@ -91,6 +91,8 @@ struct BlockMetrics
|
||||
QList< double > fragmentVerticalOffsets;
|
||||
QList< double > fragmentFixedHeights;
|
||||
QList< double > fragmentHorizontalAdvance;
|
||||
QList< double > fragmentAscent;
|
||||
QList< double > fragmentDescent;
|
||||
|
||||
QFont previousNonSuperSubScriptFont;
|
||||
bool isFirstNonTabFragment = true;
|
||||
@ -130,6 +132,8 @@ struct BlockMetrics
|
||||
fragmentVerticalOffsets.clear();
|
||||
fragmentFixedHeights.clear();
|
||||
fragmentHorizontalAdvance.clear();
|
||||
fragmentAscent.clear();
|
||||
fragmentDescent.clear();
|
||||
previousNonSuperSubScriptFont = QFont();
|
||||
isFirstNonTabFragment = true;
|
||||
}
|
||||
@ -238,12 +242,15 @@ void QgsTextDocumentMetrics::finalizeBlock( QgsTextDocumentMetrics &res, const Q
|
||||
res.mBaselineOffsetsCapHeightMode << documentMetrics.currentCapHeightBasedBaseline;
|
||||
res.mBaselineOffsetsAscentBased << documentMetrics.currentAscentBasedBaseline;
|
||||
res.mBlockMaxDescent << metrics.maxBlockDescent;
|
||||
res.mBlockMaxAscent << metrics.maxBlockAscent;
|
||||
res.mBlockMaxCharacterWidth << metrics.maxBlockMaxWidth;
|
||||
res.mFragmentVerticalOffsetsLabelMode << metrics.fragmentVerticalOffsets;
|
||||
res.mFragmentFixedHeights << metrics.fragmentFixedHeights;
|
||||
res.mFragmentVerticalOffsetsRectMode << metrics.fragmentVerticalOffsets;
|
||||
res.mFragmentVerticalOffsetsPointMode << metrics.fragmentVerticalOffsets;
|
||||
res.mFragmentHorizontalAdvance << metrics.fragmentHorizontalAdvance;
|
||||
res.mFragmentAscent << metrics.fragmentAscent;
|
||||
res.mFragmentDescent << metrics.fragmentDescent;
|
||||
|
||||
res.mDocument.append( outputBlock );
|
||||
outputBlock.clear();
|
||||
@ -288,6 +295,8 @@ void QgsTextDocumentMetrics::processFragment( QgsTextDocumentMetrics &res, const
|
||||
thisBlockMetrics.fragmentHorizontalAdvance << fragmentWidth;
|
||||
thisBlockMetrics.fragmentFixedHeights << -1;
|
||||
thisBlockMetrics.fragmentFonts << QFont();
|
||||
thisBlockMetrics.fragmentAscent << 0;
|
||||
thisBlockMetrics.fragmentDescent << 0;
|
||||
currentOutputBlock.append( fragment );
|
||||
}
|
||||
else
|
||||
@ -441,6 +450,8 @@ void QgsTextDocumentMetrics::processFragment( QgsTextDocumentMetrics &res, const
|
||||
|
||||
thisBlockMetrics.maxBlockAscent = std::max( thisBlockMetrics.maxBlockAscent, imageHeight );
|
||||
thisBlockMetrics.maxBlockCapHeight = std::max( thisBlockMetrics.maxBlockCapHeight, imageHeight );
|
||||
thisBlockMetrics.fragmentAscent << imageHeight;
|
||||
thisBlockMetrics.fragmentDescent << 0;
|
||||
thisBlockMetrics.maxLineSpacing = std::max( thisBlockMetrics.maxLineSpacing, imageHeight + fm.leading() / scaleFactor );
|
||||
thisBlockMetrics.maxBlockLeading = std::max( thisBlockMetrics.maxBlockLeading, fm.leading() / scaleFactor );
|
||||
thisBlockMetrics.maxBlockMaxWidth = std::max( thisBlockMetrics.maxBlockMaxWidth, imageWidth );
|
||||
@ -465,12 +476,17 @@ void QgsTextDocumentMetrics::processFragment( QgsTextDocumentMetrics &res, const
|
||||
thisBlockMetrics.blockHeightUsingAscentDescent = std::max( thisBlockMetrics.blockHeightUsingAscentDescent, fragmentHeightUsingAscentDescent );
|
||||
|
||||
thisBlockMetrics.blockHeightUsingLineSpacing = std::max( thisBlockMetrics.blockHeightUsingLineSpacing, fragmentHeightUsingLineSpacing );
|
||||
thisBlockMetrics.maxBlockAscent = std::max( thisBlockMetrics.maxBlockAscent, fm.ascent() / scaleFactor );
|
||||
thisBlockMetrics.maxBlockAscentForTextFragments = std::max( thisBlockMetrics.maxBlockAscentForTextFragments, fm.ascent() / scaleFactor );
|
||||
const double ascent = fm.ascent() / scaleFactor;
|
||||
thisBlockMetrics.fragmentAscent << ascent;
|
||||
thisBlockMetrics.maxBlockAscent = std::max( thisBlockMetrics.maxBlockAscent, ascent );
|
||||
thisBlockMetrics.maxBlockAscentForTextFragments = std::max( thisBlockMetrics.maxBlockAscentForTextFragments, ascent );
|
||||
|
||||
thisBlockMetrics.maxBlockCapHeight = std::max( thisBlockMetrics.maxBlockCapHeight, fm.capHeight() / scaleFactor );
|
||||
|
||||
thisBlockMetrics.maxBlockDescent = std::max( thisBlockMetrics.maxBlockDescent, fm.descent() / scaleFactor );
|
||||
const double descent = fm.descent() / scaleFactor;
|
||||
thisBlockMetrics.fragmentDescent << descent;
|
||||
|
||||
thisBlockMetrics.maxBlockDescent = std::max( thisBlockMetrics.maxBlockDescent, descent );
|
||||
thisBlockMetrics.maxBlockMaxWidth = std::max( thisBlockMetrics.maxBlockMaxWidth, fm.maxWidth() / scaleFactor );
|
||||
|
||||
if ( ( fm.lineSpacing() / scaleFactor ) > thisBlockMetrics.maxLineSpacing )
|
||||
@ -842,6 +858,16 @@ double QgsTextDocumentMetrics::fragmentFixedHeight( int blockIndex, int fragment
|
||||
return mFragmentFixedHeights.value( blockIndex ).value( fragmentIndex );
|
||||
}
|
||||
|
||||
double QgsTextDocumentMetrics::fragmentAscent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
|
||||
{
|
||||
return mFragmentAscent.value( blockIndex ).value( fragmentIndex );
|
||||
}
|
||||
|
||||
double QgsTextDocumentMetrics::fragmentDescent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
|
||||
{
|
||||
return mFragmentDescent.value( blockIndex ).value( fragmentIndex );
|
||||
}
|
||||
|
||||
double QgsTextDocumentMetrics::verticalOrientationXOffset( int blockIndex ) const
|
||||
{
|
||||
return mVerticalOrientationXOffsets.value( blockIndex );
|
||||
@ -857,6 +883,11 @@ double QgsTextDocumentMetrics::blockMaximumDescent( int blockIndex ) const
|
||||
return mBlockMaxDescent.value( blockIndex );
|
||||
}
|
||||
|
||||
double QgsTextDocumentMetrics::blockMaximumAscent( int blockIndex ) const
|
||||
{
|
||||
return mBlockMaxAscent.value( blockIndex );
|
||||
}
|
||||
|
||||
QFont QgsTextDocumentMetrics::fragmentFont( int blockIndex, int fragmentIndex ) const
|
||||
{
|
||||
return mFragmentFonts.value( blockIndex ).value( fragmentIndex );
|
||||
|
@ -189,6 +189,24 @@ class CORE_EXPORT QgsTextDocumentMetrics
|
||||
*/
|
||||
double fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
|
||||
/**
|
||||
* Returns the ascent of the fragment at the specified block and fragment index.
|
||||
*
|
||||
* \see fragmentDescent()
|
||||
*
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
double fragmentAscent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
|
||||
/**
|
||||
* Returns the descent of the fragment at the specified block and fragment index.
|
||||
*
|
||||
* \see fragmentAscent()
|
||||
*
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
double fragmentDescent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
|
||||
|
||||
/**
|
||||
* Returns the vertical orientation x offset for the specified block.
|
||||
*/
|
||||
@ -201,9 +219,19 @@ class CORE_EXPORT QgsTextDocumentMetrics
|
||||
|
||||
/**
|
||||
* Returns the maximum descent encountered in the specified block.
|
||||
*
|
||||
* \see blockMaximumAscent()
|
||||
*/
|
||||
double blockMaximumDescent( int blockIndex ) const;
|
||||
|
||||
/**
|
||||
* Returns the maximum ascent encountered in the specified block.
|
||||
*
|
||||
* \see blockMaximumDescent()
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
double blockMaximumAscent( int blockIndex ) const;
|
||||
|
||||
/**
|
||||
* Returns the calculated font for the fragment at the specified block and fragment indices.
|
||||
*/
|
||||
@ -277,8 +305,12 @@ class CORE_EXPORT QgsTextDocumentMetrics
|
||||
QList< QList< double > > mFragmentVerticalOffsetsPointMode;
|
||||
QList< QList< double > > mFragmentVerticalOffsetsRectMode;
|
||||
|
||||
QList< QList< double > > mFragmentAscent;
|
||||
QList< QList< double > > mFragmentDescent;
|
||||
|
||||
QList< double > mVerticalOrientationXOffsets;
|
||||
QList< double > mBlockMaxDescent;
|
||||
QList< double > mBlockMaxAscent;
|
||||
QList< double > mBlockMaxCharacterWidth;
|
||||
double mFirstLineAscentOffset = 0;
|
||||
double mLastLineAscentOffset = 0;
|
||||
|
@ -1857,12 +1857,124 @@ QVector< QgsTextRenderer::BlockMetrics > QgsTextRenderer::calculateBlockMetrics(
|
||||
{
|
||||
thisBlockMetrics.xOffset = metrics.blockLeftMargin( blockIndex );
|
||||
}
|
||||
|
||||
switch ( mode )
|
||||
{
|
||||
case Qgis::TextLayoutMode::Rectangle:
|
||||
case Qgis::TextLayoutMode::RectangleCapHeightBased:
|
||||
case Qgis::TextLayoutMode::RectangleAscentBased:
|
||||
thisBlockMetrics.backgroundWidth = targetWidth;
|
||||
thisBlockMetrics.backgroundXOffset = 0;
|
||||
break;
|
||||
case Qgis::TextLayoutMode::Point:
|
||||
case Qgis::TextLayoutMode::Labeling:
|
||||
thisBlockMetrics.backgroundWidth = thisBlockMetrics.width;
|
||||
thisBlockMetrics.backgroundXOffset = thisBlockMetrics.xOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
blockMetrics << thisBlockMetrics;
|
||||
blockIndex++;
|
||||
}
|
||||
return blockMetrics;
|
||||
}
|
||||
|
||||
QBrush QgsTextRenderer::createBrushForPath( QgsRenderContext &context, const QString &path )
|
||||
{
|
||||
bool fitsInCache = false;
|
||||
// use original image size
|
||||
const QSize imageSize = QgsApplication::imageCache()->originalSize( path, 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();
|
||||
const double imageWidth = originalSizeMmAt96Dpi.width() * pixelsPerMm;
|
||||
const double imageHeight = originalSizeMmAt96Dpi.height() * pixelsPerMm;
|
||||
QBrush res;
|
||||
if ( imageWidth == 0 || imageHeight == 0 )
|
||||
return res;
|
||||
const QImage image = QgsApplication::imageCache()->pathAsImage( path,
|
||||
QSize( static_cast< int >( std::round( imageWidth ) ),
|
||||
static_cast< int >( std::round( imageHeight ) ) ),
|
||||
false,
|
||||
1, fitsInCache, context.flags() & Qgis::RenderContextFlag::RenderBlocking );
|
||||
|
||||
if ( !image.isNull() )
|
||||
{
|
||||
|
||||
res.setTextureImage( image );
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void QgsTextRenderer::renderDocumentBackgrounds( QgsRenderContext &context, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, const Component &component, const QVector< QgsTextRenderer::BlockMetrics > &blockMetrics, Qgis::TextLayoutMode mode, double verticalAlignOffset, double rotation )
|
||||
{
|
||||
int blockIndex = 0;
|
||||
context.painter()->translate( component.origin );
|
||||
if ( !qgsDoubleNear( rotation, 0.0 ) )
|
||||
context.painter()->rotate( rotation );
|
||||
|
||||
context.painter()->setPen( Qt::NoPen );
|
||||
context.painter()->setBrush( Qt::NoBrush );
|
||||
for ( const QgsTextBlock &block : document )
|
||||
{
|
||||
const double baseLineOffset = metrics.baselineOffset( blockIndex, mode );
|
||||
const double blockMaximumDescent = metrics.blockMaximumDescent( blockIndex );
|
||||
const double blockMaximumAscent = metrics.blockMaximumAscent( blockIndex );
|
||||
|
||||
if ( block.blockFormat().hasBackground() )
|
||||
{
|
||||
QBrush backgroundBrush = block.blockFormat().backgroundBrush();
|
||||
if ( !block.blockFormat().backgroundImagePath().isEmpty() )
|
||||
{
|
||||
const QBrush backgroundImageBrush = createBrushForPath( context, block.blockFormat().backgroundImagePath() );
|
||||
if ( backgroundImageBrush.style() == Qt::BrushStyle::TexturePattern )
|
||||
backgroundBrush = backgroundImageBrush;
|
||||
}
|
||||
|
||||
context.painter()->setBrush( backgroundBrush );
|
||||
context.painter()->drawRect( QRectF( blockMetrics[ blockIndex ].backgroundXOffset, baseLineOffset - blockMaximumAscent, blockMetrics[ blockIndex ].backgroundWidth, blockMaximumDescent + blockMaximumAscent ) );
|
||||
}
|
||||
|
||||
double xOffset = 0;
|
||||
int fragmentIndex = 0;
|
||||
|
||||
for ( const QgsTextFragment &fragment : block )
|
||||
{
|
||||
const double horizontalAdvance = metrics.fragmentHorizontalAdvance( blockIndex, fragmentIndex, mode );
|
||||
const double ascent = metrics.fragmentAscent( blockIndex, fragmentIndex, mode );
|
||||
const double descent = metrics.fragmentDescent( blockIndex, fragmentIndex, mode );
|
||||
|
||||
if ( fragment.characterFormat().hasBackground() )
|
||||
{
|
||||
const double yOffset = metrics.fragmentVerticalOffset( blockIndex, fragmentIndex, mode );
|
||||
|
||||
QBrush backgroundBrush = fragment.characterFormat().backgroundBrush();
|
||||
if ( !fragment.characterFormat().backgroundImagePath().isEmpty() )
|
||||
{
|
||||
const QBrush backgroundImageBrush = createBrushForPath( context, fragment.characterFormat().backgroundImagePath() );
|
||||
if ( backgroundImageBrush.style() == Qt::BrushStyle::TexturePattern )
|
||||
backgroundBrush = backgroundImageBrush;
|
||||
}
|
||||
|
||||
context.painter()->setBrush( backgroundBrush );
|
||||
context.painter()->drawRect( QRectF( blockMetrics[ blockIndex ].xOffset + xOffset,
|
||||
baseLineOffset + verticalAlignOffset + yOffset - ascent, horizontalAdvance, ascent + descent ) );
|
||||
}
|
||||
|
||||
xOffset += horizontalAdvance;
|
||||
fragmentIndex ++;
|
||||
}
|
||||
|
||||
blockIndex++;
|
||||
}
|
||||
|
||||
context.painter()->setBrush( Qt::NoBrush );
|
||||
|
||||
if ( !qgsDoubleNear( rotation, 0.0 ) )
|
||||
context.painter()->rotate( -rotation );
|
||||
context.painter()->translate( -component.origin );
|
||||
}
|
||||
|
||||
void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, Qgis::TextLayoutMode mode, const Component &component, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, double fontScale, const Qgis::TextHorizontalAlignment hAlignment,
|
||||
Qgis::TextVerticalAlignment vAlignment, double rotation )
|
||||
{
|
||||
@ -1933,6 +2045,12 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
|
||||
|| ( components & Qgis::TextComponent::Shadow ) )
|
||||
{
|
||||
const QVector< BlockMetrics > blockMetrics = calculateBlockMetrics( document, metrics, mode, targetWidth, hAlignment );
|
||||
|
||||
if ( document.hasBackgrounds() )
|
||||
{
|
||||
renderDocumentBackgrounds( context, document, metrics, component, blockMetrics, mode, verticalAlignOffset, rotation );
|
||||
}
|
||||
|
||||
int blockIndex = 0;
|
||||
for ( const QgsTextBlock &block : document )
|
||||
{
|
||||
|
@ -519,7 +519,9 @@ class CORE_EXPORT QgsTextRenderer
|
||||
struct BlockMetrics
|
||||
{
|
||||
double xOffset = 0;
|
||||
double backgroundXOffset = 0;
|
||||
double width = 0;
|
||||
double backgroundWidth = 0;
|
||||
double extraWordSpace = 0;
|
||||
double extraLetterSpace = 0;
|
||||
};
|
||||
@ -533,6 +535,8 @@ class CORE_EXPORT QgsTextRenderer
|
||||
QVector< DeferredRenderFragment > fragments;
|
||||
};
|
||||
|
||||
static QBrush createBrushForPath( QgsRenderContext &context, const QString &path );
|
||||
|
||||
static void renderBlockHorizontal( const QgsTextBlock &block, int blockIndex,
|
||||
const QgsTextDocumentMetrics &metrics, QgsRenderContext &context,
|
||||
const QgsTextFormat &format,
|
||||
@ -540,6 +544,7 @@ class CORE_EXPORT QgsTextRenderer
|
||||
double fontScale, double extraWordSpace, double extraLetterSpace,
|
||||
Qgis::TextLayoutMode mode,
|
||||
DeferredRenderBlock *deferredRenderBlock );
|
||||
static void renderDocumentBackgrounds( QgsRenderContext &context, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, const Component &component, const QVector< QgsTextRenderer::BlockMetrics > &blockMetrics, Qgis::TextLayoutMode mode, double verticalAlignOffset, double rotation );
|
||||
static void renderDeferredBlocks( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, const std::vector<DeferredRenderBlock> &deferredBlocks, bool usePathsForText, double fontScale, const Component &component, double rotation );
|
||||
static void renderDeferredBuffer( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, const std::vector<DeferredRenderBlock> &deferredBlocks, double fontScale, const Component &component, double rotation );
|
||||
static void renderDeferredShadowForText( QgsRenderContext &context, const QgsTextFormat &format, const std::vector<DeferredRenderBlock> &deferredBlocks, double fontScale, const Component &component, double rotation );
|
||||
|
@ -61,6 +61,11 @@ class TestQgsTextBlockFormat(QgisTestCase):
|
||||
self.assertTrue(format.hasBackground())
|
||||
self.assertEqual(format.backgroundBrush().color().name(), '#ffff00')
|
||||
|
||||
format = QgsTextBlockFormat()
|
||||
format.setBackgroundImagePath('test')
|
||||
self.assertTrue(format.hasBackground())
|
||||
self.assertEqual(format.backgroundImagePath(), 'test')
|
||||
|
||||
def testUpdateFont(self):
|
||||
context = QgsRenderContext()
|
||||
font = QgsFontUtils.getStandardTestFont()
|
||||
|
@ -80,6 +80,11 @@ class TestQgsTextCharacterFormat(QgisTestCase):
|
||||
self.assertTrue(format.hasBackground())
|
||||
self.assertEqual(format.backgroundBrush().color().name(), '#ffff00')
|
||||
|
||||
format = QgsTextCharacterFormat()
|
||||
format.setBackgroundImagePath('test')
|
||||
self.assertTrue(format.hasBackground())
|
||||
self.assertEqual(format.backgroundImagePath(), 'test')
|
||||
|
||||
def testUpdateFont(self):
|
||||
context = QgsRenderContext()
|
||||
font = QgsFontUtils.getStandardTestFont()
|
||||
|
@ -356,6 +356,23 @@ class TestQgsTextDocument(QgisTestCase):
|
||||
self.assertFalse(doc.hasBackgrounds())
|
||||
self.assertFalse(doc[0].hasBackgrounds())
|
||||
|
||||
# background paths
|
||||
doc = QgsTextDocument.fromHtml(['<div style="background-image: url(something)"><span style="background-color: red;">red</span><span style="background-image: url(something_else);">yellow</span>outside span</div>'])
|
||||
self.assertTrue(doc.hasBackgrounds())
|
||||
self.assertEqual(len(doc), 1)
|
||||
# there's a bug in Qt's css parsing here -- the background-image incorrectly gets attached to the spans, not the div!
|
||||
# self.assertTrue(doc[0].blockFormat().hasBackground())
|
||||
self.assertTrue(doc[0].hasBackgrounds())
|
||||
|
||||
self.assertTrue(doc[0][0].characterFormat().hasBackground())
|
||||
self.assertEqual(doc[0][0].characterFormat().backgroundBrush().color().name(), '#ff0000')
|
||||
self.assertFalse(doc[0][0].characterFormat().backgroundImagePath())
|
||||
self.assertTrue(doc[0][1].characterFormat().hasBackground())
|
||||
self.assertEqual(doc[0][1].characterFormat().backgroundImagePath(), 'something_else')
|
||||
self.assertTrue(doc[0][2].characterFormat().hasBackground())
|
||||
self.assertEqual(doc[0][2].characterFormat().backgroundImagePath(),
|
||||
'something')
|
||||
|
||||
def testFromTextAndFormat(self):
|
||||
format = QgsTextFormat()
|
||||
format.setAllowHtmlFormatting(False)
|
||||
|
@ -20,7 +20,7 @@ from qgis.PyQt.QtCore import (
|
||||
QRectF,
|
||||
QSize,
|
||||
QSizeF,
|
||||
Qt,
|
||||
Qt
|
||||
)
|
||||
from qgis.PyQt.QtGui import (
|
||||
QBrush,
|
||||
@ -307,6 +307,48 @@ class PyQgsTextRenderer(QgisTestCase):
|
||||
|
||||
self.assertTrue(self.image_check('draw_document_rect', 'draw_document_rect', image, 'draw_document_rect'))
|
||||
|
||||
def testDrawRectHtmlBackground(self):
|
||||
"""
|
||||
Test drawing html with backgrounds in rect mode
|
||||
"""
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.setAllowHtmlFormatting(True)
|
||||
format.setSize(16)
|
||||
self.assertTrue(self.checkRender(format, 'html_background_rect', rect=QRectF(10, 100, 300, 100), text=['<div style="background-color: blue"><span style="background-color: rgba(255,0,0,0.5);">red</span> <span style="font-size: 10pt; background-color: yellow;">yellow</span> outside span</div><div style="background-color: pink; text-align: right;">no span <span style="background-color: yellow;">yel</span> no bg</div>']))
|
||||
|
||||
def testDrawPointHtmlBackground(self):
|
||||
"""
|
||||
Test drawing html with backgrounds in point mode
|
||||
"""
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.setAllowHtmlFormatting(True)
|
||||
format.setSize(16)
|
||||
self.assertTrue(self.checkRenderPoint(format, 'html_background_point', point=QPointF(10, 100), text=['<div style="background-color: blue"><span style="background-color: rgba(255,0,0,0.5);">red</span> <span style="font-size: 10pt; background-color: yellow;">yellow</span> outside span</div><div style="background-color: pink; ext-align: right;">no span <span style="background-color: yellow;">yel</span> no bg</div>']))
|
||||
|
||||
def testDrawRectHtmlBackgroundImage(self):
|
||||
"""
|
||||
Test drawing html with background image in rect mode
|
||||
"""
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.setAllowHtmlFormatting(True)
|
||||
format.setSize(36)
|
||||
image_url = unitTestDataPath() + "/raster_brush.png"
|
||||
self.assertTrue(self.checkRender(format, 'html_background_image_rect', rect=QRectF(10, 100, 300, 100), text=[f'<div style="background-image: url({image_url})">test text</div>']))
|
||||
|
||||
def testDrawPointHtmlBackgroundImage(self):
|
||||
"""
|
||||
Test drawing html with backgrounds in point mode
|
||||
"""
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.setAllowHtmlFormatting(True)
|
||||
format.setSize(36)
|
||||
image_url = unitTestDataPath() + "/raster_brush.png"
|
||||
self.assertTrue(self.checkRenderPoint(format, 'html_background_image_point', point=QPointF(10, 100), text=[f'<div style="background-image: url({image_url})">test text</div>']))
|
||||
|
||||
def testDrawRectCapHeightMode(self):
|
||||
"""
|
||||
Test drawing text in rect mode with cap height based line heights
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 5.0 KiB |
BIN
tests/testdata/control_images/text_renderer/html_background_point/html_background_point.png
vendored
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
tests/testdata/control_images/text_renderer/html_background_point/html_background_point_mask.png
vendored
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
tests/testdata/control_images/text_renderer/html_background_rect/html_background_rect.png
vendored
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
tests/testdata/control_images/text_renderer/html_background_rect/html_background_rect_mask.png
vendored
Normal file
After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.5 KiB |