Add support for super and subscript HTML formatting in text renderer

This allows for either:

- <sup>superscript</sup> / <sub>subscript</sub> components in text,
where the text will be vertically super or subscript aligned
and automatically sized to 2/3rd of the parent font size. Users
can also set a fixed font size for the super/sub script by
including css rules, e.g. <sup style="font-size:33pt">super</sup>

- "vertical-align: super" or "vertical-align: sub" CSS formatting
rules in any other HTML element

Sponsored by OSGEO UK
This commit is contained in:
Nyall Dawson 2022-11-05 11:14:25 +10:00
parent 79b809a585
commit a7c39ffdc7
13 changed files with 340 additions and 15 deletions

View File

@ -49,6 +49,19 @@ Returns ``True`` if the metrics could not be calculated because the text format
QSizeF documentSize( Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation ) const;
%Docstring
Returns the overall size of the document.
%End
QRectF outerBounds( Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation ) const;
%Docstring
Returns the outer bounds of the document, which is the :py:func:`~QgsTextDocumentMetrics.documentSize` adjusted to account
for any text elements which fall outside of the usual document margins (such as super or
sub script elements)
.. warning::
Currently this is only supported for the Qgis.TextLayoutMode.Labeling mode.
.. versionadded:: 3.30
%End
double blockWidth( int blockIndex ) const;
@ -70,6 +83,14 @@ Returns the offset from the top of the document to the text baseline for the giv
%Docstring
Returns the horizontal advance of the fragment at the specified block and fragment index.
.. versionadded:: 3.30
%End
double fragmentVerticalOffset( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
%Docstring
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

View File

@ -1460,7 +1460,7 @@ bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const
return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
}
void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY, QgsTextDocument *document, QgsTextDocumentMetrics *documentMetrics )
void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY, QgsTextDocument *document, QgsTextDocumentMetrics *documentMetrics, QRectF *outerBounds )
{
if ( !fm || !f )
{
@ -1714,6 +1714,16 @@ void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QSt
*rotatedLabelY = rh * uPP;
}
#endif
if ( outerBounds && documentMetrics )
{
const QRectF outerBoundsPixels = documentMetrics->outerBounds( Qgis::TextLayoutMode::Labeling, orientation );
*outerBounds = QRectF( outerBoundsPixels.left() * uPP,
outerBoundsPixels.top() * uPP,
outerBoundsPixels.width() * uPP,
outerBoundsPixels.height() * uPP );
}
}
void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext &context )
@ -2018,15 +2028,16 @@ std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails
QgsTextDocument doc;
QgsTextDocumentMetrics documentMetrics;
QRectF outerBounds;
if ( format().allowHtmlFormatting() && !labelText.isEmpty() )
{
doc = QgsTextDocument::fromHtml( QStringList() << labelText );
// also applies the line split to doc and calculates document metrics!
calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &doc, &documentMetrics );
calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &doc, &documentMetrics, &outerBounds );
}
else
{
calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, nullptr, nullptr );
calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, nullptr, nullptr, &outerBounds );
}
// maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
@ -2668,6 +2679,11 @@ std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails
obstacleGeometry.boundingBox().height() ) );
}
if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelWidth ) || !qgsDoubleNear( outerBounds.height(), labelHeight ) )
{
labelFeature->setOuterBounds( outerBounds );
}
//set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
//this makes labels align to the font's baseline or highest character
double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );

View File

@ -758,7 +758,7 @@ class CORE_EXPORT QgsPalLayerSettings
*/
#ifndef SIP_RUN
void calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f = nullptr, QgsRenderContext *context = nullptr, double *rotatedLabelX SIP_OUT = nullptr, double *rotatedLabelY SIP_OUT = nullptr,
QgsTextDocument *document = nullptr, QgsTextDocumentMetrics *documentMetrics = nullptr );
QgsTextDocument *document = nullptr, QgsTextDocumentMetrics *documentMetrics = nullptr, QRectF *outerBounds = nullptr );
#else
void calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f = nullptr, QgsRenderContext *context = nullptr, double *rotatedLabelX SIP_OUT = nullptr, double *rotatedLabelY SIP_OUT = nullptr );
#endif

View File

@ -556,7 +556,7 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q
painter->drawRect( rect );
painter->setPen( QColor( 0, 0, 0, 120 ) );
painter->setPen( QColor( 0, 0, 0, 60 ) );
const QgsMargins &margins = label->getFeaturePart()->feature()->visualMargin();
if ( margins.top() > 0 )
{
@ -584,7 +584,10 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q
outerBoundsPt2.x() - outerBoundsPt1.x(),
outerBoundsPt2.y() - outerBoundsPt1.y() );
painter->setPen( QColor( 255, 0, 255, 140 ) );
QPen pen( QColor( 255, 0, 255, 140 ) );
pen.setCosmetic( true );
pen.setWidth( 1 );
painter->setPen( pen );
painter->drawRect( outerBoundsPixel );
}
@ -594,13 +597,39 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q
const QgsTextDocument &document = textFeature->document();
const int blockCount = document.size();
double prevBlockBaseline = rect.bottom() - rect.top();
// draw block baselines
painter->setPen( QColor( 0, 0, 255, 220 ) );
for ( int blockIndex = 0; blockIndex < blockCount; ++blockIndex )
{
const double blockBaseLine = metrics.baselineOffset( blockIndex, Qgis::TextLayoutMode::Labeling );
painter->drawLine( QPointF( rect.left(), rect.top() + blockBaseLine ),
QPointF( rect.right(), rect.top() + blockBaseLine ) );
const QgsTextBlock &block = document.at( blockIndex );
const int fragmentCount = block.size();
double left = 0;
for ( int fragmentIndex = 0; fragmentIndex < fragmentCount; ++fragmentIndex )
{
const double fragmentVerticalOffset = metrics.fragmentVerticalOffset( blockIndex, fragmentIndex, Qgis::TextLayoutMode::Labeling );
const double right = left + metrics.fragmentHorizontalAdvance( blockIndex, fragmentIndex, Qgis::TextLayoutMode::Labeling );
if ( fragmentIndex > 0 )
{
QPen pen( QColor( 0, 0, 255, 220 ) );
pen.setStyle( Qt::PenStyle::DashLine );
painter->setPen( pen );
painter->drawLine( QPointF( rect.left() + left, rect.top() + blockBaseLine + fragmentVerticalOffset ),
QPointF( rect.left() + left, rect.top() + prevBlockBaseline ) );
}
painter->setPen( QColor( 0, 0, 255, 220 ) );
painter->drawLine( QPointF( rect.left() + left, rect.top() + blockBaseLine + fragmentVerticalOffset ),
QPointF( rect.left() + right, rect.top() + blockBaseLine + fragmentVerticalOffset ) );
left = right;
}
prevBlockBaseline = blockBaseLine;
}
}

View File

@ -23,6 +23,13 @@
#include <QFontMetricsF>
// to match QTextEngine handling of superscript/subscript font sizes
constexpr double SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR = 2.0 / 3.0;
// to match Qt behavior in QTextLine::draw
constexpr double SUPERSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR = 0.5;
constexpr double SUBSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR = 1.0 / 6.0;
QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor )
{
QgsTextDocumentMetrics res;
@ -48,14 +55,25 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
QVector < double > blockVerticalLineSpacing;
double outerXMin = 0;
double outerXMax = 0;
double outerYMinLabel = 0;
double outerYMaxLabel = 0;
for ( int blockIndex = 0; blockIndex < blockSize; blockIndex++ )
{
const QgsTextBlock &block = document.at( blockIndex );
double blockWidth = 0;
double blockXMax = 0;
double blockYMaxAdjustLabel = 0;
double blockHeightUsingAscentDescent = 0;
double blockHeightUsingLineSpacing = 0;
double blockHeightVerticalOrientation = 0;
double blockHeightUsingAscentAccountingForVerticalOffset = 0;
const int fragmentSize = block.size();
double maxBlockAscent = 0;
@ -64,18 +82,89 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
double maxBlockLeading = 0;
double maxBlockMaxWidth = 0;
QList< double > fragmentVerticalOffsets;
fragmentVerticalOffsets.reserve( fragmentSize );
QList< QFont > fragmentFonts;
fragmentFonts.reserve( fragmentSize );
QList< double >fragmentHorizontalAdvance;
fragmentHorizontalAdvance.reserve( fragmentSize );
QFont previousNonSuperSubScriptFont;
for ( int fragmentIndex = 0; fragmentIndex < fragmentSize; ++fragmentIndex )
{
const QgsTextFragment &fragment = block.at( fragmentIndex );
const QgsTextCharacterFormat &fragmentFormat = fragment.characterFormat();
double fragmentHeightForVerticallyOffsetText = 0;
double fragmentYMaxAdjust = 0;
QFont updatedFont = font;
fragmentFormat.updateFontForFormat( updatedFont, context, scaleFactor );
const QFontMetricsF fm( updatedFont );
QFontMetricsF fm( updatedFont );
if ( fragmentIndex == 0 )
previousNonSuperSubScriptFont = updatedFont;
double fragmentVerticalOffset = 0;
if ( fragmentFormat.hasVerticalAlignmentSet() )
{
switch ( fragmentFormat.verticalAlignment() )
{
case Qgis::TextCharacterVerticalAlignment::Normal:
previousNonSuperSubScriptFont = updatedFont;
break;
case Qgis::TextCharacterVerticalAlignment::SuperScript:
{
const QFontMetricsF previousFM( previousNonSuperSubScriptFont );
if ( fragmentFormat.fontPointSize() < 0 )
{
// if fragment has no explicit font size set, then we scale the inherited font size to 60% of base font size
// this allows for easier use of super/subscript in labels as "my text<sup>2</sup>" will automatically render
// the superscript in a smaller font size. BUT if the fragment format HAS a non -1 font size then it indicates
// that the document has an explicit font size for the super/subscript element, eg "my text<sup style="font-size: 6pt">2</sup>"
// which we should respect
updatedFont.setPixelSize( updatedFont.pixelSize() * SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR );
fm = QFontMetricsF( updatedFont );
}
// to match Qt behavior in QTextLine::draw
fragmentVerticalOffset = -( previousFM.ascent() + previousFM.descent() ) * SUPERSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR / scaleFactor;
// note -- this should really be fm.ascent(), not fm.capHeight() -- but in practice the ascent of most fonts is too large
// and causes unnecessarily large bounding boxes of vertically offset text!
fragmentHeightForVerticallyOffsetText = -fragmentVerticalOffset + fm.capHeight() / scaleFactor;
break;
}
case Qgis::TextCharacterVerticalAlignment::SubScript:
{
const QFontMetricsF previousFM( previousNonSuperSubScriptFont );
if ( fragmentFormat.fontPointSize() < 0 )
{
// see above!!
updatedFont.setPixelSize( updatedFont.pixelSize() * SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR );
fm = QFontMetricsF( updatedFont );
}
// to match Qt behavior in QTextLine::draw
fragmentVerticalOffset = ( previousFM.ascent() + previousFM.descent() ) * SUBSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR / scaleFactor;
fragmentYMaxAdjust = fragmentVerticalOffset + fm.descent() / scaleFactor;
break;
}
}
}
else
{
previousNonSuperSubScriptFont = updatedFont;
}
fragmentVerticalOffsets << fragmentVerticalOffset;
const double fragmentWidth = fm.horizontalAdvance( fragment.text() ) / scaleFactor;
@ -85,12 +174,19 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
const double fragmentHeightUsingLineSpacing = fm.lineSpacing() / scaleFactor;
blockWidth += fragmentWidth;
blockXMax += fragmentWidth;
blockHeightUsingAscentDescent = std::max( blockHeightUsingAscentDescent, fragmentHeightUsingAscentDescent );
blockHeightUsingLineSpacing = std::max( blockHeightUsingLineSpacing, fragmentHeightUsingLineSpacing );
maxBlockAscent = std::max( maxBlockAscent, fm.ascent() / scaleFactor );
blockHeightUsingAscentAccountingForVerticalOffset = std::max( std::max( maxBlockAscent, fragmentHeightForVerticallyOffsetText ), blockHeightUsingAscentAccountingForVerticalOffset );
maxBlockDescent = std::max( maxBlockDescent, fm.descent() / scaleFactor );
maxBlockMaxWidth = std::max( maxBlockMaxWidth, fm.maxWidth() / scaleFactor );
blockYMaxAdjustLabel = std::max( blockYMaxAdjustLabel, fragmentYMaxAdjust );
if ( ( fm.lineSpacing() / scaleFactor ) > maxLineSpacing )
{
maxLineSpacing = fm.lineSpacing() / scaleFactor;
@ -117,6 +213,9 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
// for standard text rendering. Line height is also slightly different.
currentLabelBaseline = -res.mFirstLineAscentOffset;
if ( blockHeightUsingAscentAccountingForVerticalOffset > maxBlockAscent )
outerYMinLabel = maxBlockAscent - blockHeightUsingAscentAccountingForVerticalOffset;
// standard rendering - designed to exactly replicate QPainter's drawText method
currentRectBaseline = -res.mFirstLineAscentOffset + lineHeight - 1 /*baseline*/;
@ -141,11 +240,19 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
res.mLastLineAscentOffset = 0.25 * maxBlockAscent;
}
if ( blockIndex == blockSize - 1 )
{
if ( blockYMaxAdjustLabel > maxBlockDescent )
outerYMaxLabel = blockYMaxAdjustLabel - maxBlockDescent;
}
blockVerticalLineSpacing << ( format.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( maxBlockMaxWidth * format.lineHeight() ) : lineHeightPainterUnits );
res.mBlockHeights << blockHeightUsingLineSpacing;
width = std::max( width, blockWidth );
outerXMax = std::max( outerXMax, blockXMax );
heightVerticalOrientation = std::max( heightVerticalOrientation, blockHeightVerticalOrientation );
res.mBlockWidths << blockWidth;
res.mFragmentFonts << fragmentFonts;
@ -154,6 +261,9 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
res.mBaselineOffsetsRectMode << currentRectBaseline;
res.mBlockMaxDescent << maxBlockDescent;
res.mBlockMaxCharacterWidth << maxBlockMaxWidth;
res.mFragmentVerticalOffsetsLabelMode << fragmentVerticalOffsets;
res.mFragmentVerticalOffsetsRectMode << fragmentVerticalOffsets;
res.mFragmentVerticalOffsetsPointMode << fragmentVerticalOffsets;
res.mFragmentHorizontalAdvance << fragmentHorizontalAdvance;
if ( blockIndex > 0 )
@ -169,6 +279,8 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
// adjust baselines
if ( !res.mBaselineOffsetsLabelMode.isEmpty() )
{
// outerYMinLabel += res.mBaselineOffsetsLabelMode[0];
const double labelModeBaselineAdjust = res.mBaselineOffsetsLabelMode.constLast() + res.mLastLineAscentOffset;
const double pointModeBaselineAdjust = res.mBaselineOffsetsPointMode.constLast();
for ( int i = 0; i < blockSize; ++i )
@ -204,6 +316,10 @@ QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDo
res.mDocumentSizeVerticalOrientation = QSizeF( widthVerticalOrientation, heightVerticalOrientation );
}
res.mOuterBoundsLabelMode = QRectF( outerXMin, -outerYMaxLabel,
outerXMax - outerXMin,
heightLabelMode - outerYMinLabel + outerYMaxLabel );
return res;
}
@ -232,6 +348,31 @@ QSizeF QgsTextDocumentMetrics::documentSize( Qgis::TextLayoutMode mode, Qgis::Te
BUILTIN_UNREACHABLE
}
QRectF QgsTextDocumentMetrics::outerBounds( Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation ) const
{
switch ( orientation )
{
case Qgis::TextOrientation::Horizontal:
switch ( mode )
{
case Qgis::TextLayoutMode::Rectangle:
case Qgis::TextLayoutMode::Point:
return QRectF();
case Qgis::TextLayoutMode::Labeling:
return mOuterBoundsLabelMode;
};
BUILTIN_UNREACHABLE
case Qgis::TextOrientation::Vertical:
return QRectF();
case Qgis::TextOrientation::RotationBased:
return QRectF(); // label mode only
}
BUILTIN_UNREACHABLE
}
double QgsTextDocumentMetrics::blockWidth( int blockIndex ) const
{
return mBlockWidths.value( blockIndex );
@ -261,6 +402,20 @@ double QgsTextDocumentMetrics::fragmentHorizontalAdvance( int blockIndex, int fr
return mFragmentHorizontalAdvance.value( blockIndex ).value( fragmentIndex );
}
double QgsTextDocumentMetrics::fragmentVerticalOffset( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const
{
switch ( mode )
{
case Qgis::TextLayoutMode::Rectangle:
return mFragmentVerticalOffsetsRectMode.value( blockIndex ).value( fragmentIndex );
case Qgis::TextLayoutMode::Point:
return mFragmentVerticalOffsetsPointMode.value( blockIndex ).value( fragmentIndex );
case Qgis::TextLayoutMode::Labeling:
return mFragmentVerticalOffsetsLabelMode.value( blockIndex ).value( fragmentIndex );
}
BUILTIN_UNREACHABLE
}
double QgsTextDocumentMetrics::verticalOrientationXOffset( int blockIndex ) const
{
return mVerticalOrientationXOffsets.value( blockIndex );

View File

@ -22,6 +22,7 @@
#include <QVector>
#include <QSizeF>
#include <QRectF>
class QgsTextDocument;
class QgsRenderContext;
@ -64,6 +65,17 @@ class CORE_EXPORT QgsTextDocumentMetrics
*/
QSizeF documentSize( Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation ) const;
/**
* Returns the outer bounds of the document, which is the documentSize() adjusted to account
* for any text elements which fall outside of the usual document margins (such as super or
* sub script elements)
*
* \warning Currently this is only supported for the Qgis::TextLayoutMode::Labeling mode.
*
* \since QGIS 3.30
*/
QRectF outerBounds( Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation ) const;
/**
* Returns the width of the block at the specified index.
*/
@ -86,6 +98,14 @@ class CORE_EXPORT QgsTextDocumentMetrics
*/
double fragmentHorizontalAdvance( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
/**
* Returns the vertical offset from a text block's baseline which should be applied
* to the fragment at the specified index within that block.
*
* \since QGIS 3.30
*/
double fragmentVerticalOffset( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const;
/**
* Returns the vertical orientation x offset for the specified block.
*/
@ -119,6 +139,8 @@ class CORE_EXPORT QgsTextDocumentMetrics
QSizeF mDocumentSizePointRectMode;
QSizeF mDocumentSizeVerticalOrientation;
QRectF mOuterBoundsLabelMode;
QList < QList< QFont > > mFragmentFonts;
QList< double > mBlockWidths;
QList< double > mBlockHeights;
@ -128,6 +150,10 @@ class CORE_EXPORT QgsTextDocumentMetrics
QList< QList< double > > mFragmentHorizontalAdvance;
QList< QList< double > > mFragmentVerticalOffsetsLabelMode;
QList< QList< double > > mFragmentVerticalOffsetsPointMode;
QList< QList< double > > mFragmentVerticalOffsetsRectMode;
QList< double > mVerticalOrientationXOffsets;
QList< double > mBlockMaxDescent;
QList< double > mBlockMaxCharacterWidth;

View File

@ -364,7 +364,8 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend
if ( component.extraWordSpacing || component.extraLetterSpacing )
applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing );
path.addText( xOffset, 0, fragmentFont, fragment.text() );
const double yOffset = metrics.fragmentVerticalOffset( component.blockIndex, fragmentIndex, mode );
path.addText( xOffset, yOffset, fragmentFont, fragment.text() );
xOffset += metrics.fragmentHorizontalAdvance( component.blockIndex, fragmentIndex, mode );
@ -390,12 +391,15 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend
const QFontMetricsF fragmentMetrics( fragmentFont );
const double fragmentYOffset = metrics.fragmentVerticalOffset( component.blockIndex, fragmentIndex, mode )
/ 1;
const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
for ( const QString &part : parts )
{
double partXOffset = ( blockMaximumCharacterWidth - ( fragmentMetrics.horizontalAdvance( part ) / scaleFactor - letterSpacing ) ) / 2;
partYOffset += fragmentMetrics.ascent() / scaleFactor;
path.addText( partXOffset, partYOffset, fragmentFont, part );
path.addText( partXOffset, partYOffset + fragmentYOffset, fragmentFont, part );
partYOffset += letterSpacing;
}
partLastDescent = fragmentMetrics.descent() / scaleFactor;
@ -525,7 +529,8 @@ void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer
{
const QFont fragmentFont = metrics.fragmentFont( component.blockIndex, fragmentIndex );
path.addText( xOffset, 0, fragmentFont, fragment.text() );
const double fragmentYOffset = metrics.fragmentVerticalOffset( component.blockIndex, fragmentIndex, mode );
path.addText( xOffset, fragmentYOffset, fragmentFont, fragment.text() );
xOffset += metrics.fragmentHorizontalAdvance( component.blockIndex, fragmentIndex, mode );
fragmentIndex++;
@ -1621,7 +1626,9 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
if ( extraWordSpace || extraLetterSpace )
applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
path.addText( xOffset, 0, fragmentFont, fragment.text() );
const double yOffset = metrics.fragmentVerticalOffset( blockIndex, fragmentIndex, mode );
path.addText( xOffset, yOffset, fragmentFont, fragment.text() );
QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
textColor.setAlphaF( fragment.characterFormat().textColor().isValid() ? textColor.alphaF() * format.opacity() : format.opacity() );
@ -1673,6 +1680,8 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
if ( extraWordSpace || extraLetterSpace )
applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
const double yOffset = metrics.fragmentVerticalOffset( blockIndex, fragmentIndex, mode );
QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
textColor.setAlphaF( fragment.characterFormat().textColor().isValid() ? textColor.alphaF() * format.opacity() : format.opacity() );
@ -1681,7 +1690,7 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
context.painter()->setRenderHint( QPainter::TextAntialiasing );
context.painter()->scale( 1 / fontScale, 1 / fontScale );
context.painter()->drawText( xOffset, 0, fragment.text() );
context.painter()->drawText( xOffset, yOffset, fragment.text() );
context.painter()->scale( fontScale, fontScale );
xOffset += metrics.fragmentHorizontalAdvance( blockIndex, fragmentIndex, mode );

View File

@ -3310,6 +3310,75 @@ class PyQgsTextRenderer(unittest.TestCase):
'<i>t</i><b style="font-size: 30pt">e</b><p><span style="color: red">s<span style="color: rgba(255,0,0,0.5); text-decoration: underline; font-size:80pt">t</span></span>'],
point=QPointF(50, 200))
def testHtmlSuperSubscript(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(60)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
format.setColor(QColor(255, 0, 0))
format.setAllowHtmlFormatting(True)
assert self.checkRenderPoint(format, 'text_html_supersubscript', None, text=[
'<sub>sub</sub>N<sup>sup</sup>'],
point=QPointF(50, 200))
def testHtmlSuperSubscriptFixedFontSize(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(60)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
format.setColor(QColor(255, 0, 0))
format.setAllowHtmlFormatting(True)
assert self.checkRenderPoint(format, 'text_html_supersubscript_fixed_font_size', None, text=[
'<sub style="font-size:80pt">s<span style="font-size:30pt">u</span></sub>N<sup style="font-size:40pt">s<span style="font-size: 20pt">up</span></sup>'],
point=QPointF(50, 200))
def testHtmlSuperSubscriptBuffer(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(60)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
format.setColor(QColor(255, 0, 0))
format.setAllowHtmlFormatting(True)
format.buffer().setEnabled(True)
format.buffer().setSize(5)
format.buffer().setColor(QColor(50, 150, 200))
assert self.checkRenderPoint(format, 'text_html_supersubscript_buffer', None, text=[
'<sub>sub</sub>N<sup>sup</sup>'],
point=QPointF(50, 200))
def testHtmlSuperSubscriptShadow(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(60)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
format.setColor(QColor(0, 255, 0))
format.setAllowHtmlFormatting(True)
format.shadow().setEnabled(True)
format.shadow().setOffsetDistance(5)
format.shadow().setBlurRadius(0)
format.shadow().setColor(QColor(50, 150, 200))
assert self.checkRenderPoint(format, 'text_html_supersubscript_shadow', None, text=[
'<sub>sub</sub>N<sup>sup</sup>'],
point=QPointF(50, 200))
def testHtmlSuperSubscriptBufferShadow(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(60)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
format.setColor(QColor(0, 255, 0))
format.setAllowHtmlFormatting(True)
format.buffer().setEnabled(True)
format.buffer().setSize(5)
format.buffer().setColor(QColor(200, 50, 150))
format.shadow().setEnabled(True)
format.shadow().setOffsetDistance(5)
format.shadow().setBlurRadius(0)
format.shadow().setColor(QColor(50, 150, 200))
assert self.checkRenderPoint(format, 'text_html_supersubscript_buffer_shadow', None, text=[
'<sub>sub</sub>N<sup>sup</sup>'],
point=QPointF(50, 200))
def testTextRenderFormat(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB