diff --git a/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in b/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in index dbf9bf70a1b..9460cf3949a 100644 --- a/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in +++ b/python/core/auto_generated/textrenderer/qgstextdocumentmetrics.sip.in @@ -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 diff --git a/src/core/labeling/qgspallabeling.cpp b/src/core/labeling/qgspallabeling.cpp index 29706252f61..e88ee645fd3 100644 --- a/src/core/labeling/qgspallabeling.cpp +++ b/src/core/labeling/qgspallabeling.cpp @@ -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 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 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 ); diff --git a/src/core/labeling/qgspallabeling.h b/src/core/labeling/qgspallabeling.h index 5198ec8ba71..c4d24582032 100644 --- a/src/core/labeling/qgspallabeling.h +++ b/src/core/labeling/qgspallabeling.h @@ -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 diff --git a/src/core/labeling/qgsvectorlayerlabelprovider.cpp b/src/core/labeling/qgsvectorlayerlabelprovider.cpp index bedd3ac2c5f..644b476fc69 100644 --- a/src/core/labeling/qgsvectorlayerlabelprovider.cpp +++ b/src/core/labeling/qgsvectorlayerlabelprovider.cpp @@ -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; } } diff --git a/src/core/textrenderer/qgstextdocumentmetrics.cpp b/src/core/textrenderer/qgstextdocumentmetrics.cpp index 86ab682b416..3de13e32ee8 100644 --- a/src/core/textrenderer/qgstextdocumentmetrics.cpp +++ b/src/core/textrenderer/qgstextdocumentmetrics.cpp @@ -23,6 +23,13 @@ #include +// 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 text2" 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 text2" + // 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 ); diff --git a/src/core/textrenderer/qgstextdocumentmetrics.h b/src/core/textrenderer/qgstextdocumentmetrics.h index c87c36c5fe0..336df53a223 100644 --- a/src/core/textrenderer/qgstextdocumentmetrics.h +++ b/src/core/textrenderer/qgstextdocumentmetrics.h @@ -22,6 +22,7 @@ #include #include +#include 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; diff --git a/src/core/textrenderer/qgstextrenderer.cpp b/src/core/textrenderer/qgstextrenderer.cpp index 8f797fd78a0..380349cf846 100644 --- a/src/core/textrenderer/qgstextrenderer.cpp +++ b/src/core/textrenderer/qgstextrenderer.cpp @@ -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 ); diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 770c4001fd6..002e667e7b1 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -3310,6 +3310,75 @@ class PyQgsTextRenderer(unittest.TestCase): 'te

st'], 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=[ + 'subNsup'], + 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=[ + 'suNsup'], + 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=[ + 'subNsup'], + 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=[ + 'subNsup'], + 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=[ + 'subNsup'], + point=QPointF(50, 200)) + def testTextRenderFormat(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript/text_html_supersubscript.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript/text_html_supersubscript.png new file mode 100644 index 00000000000..00d9616a2bd Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_supersubscript/text_html_supersubscript.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer/text_html_supersubscript_buffer.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer/text_html_supersubscript_buffer.png new file mode 100644 index 00000000000..6a48eb0b182 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer/text_html_supersubscript_buffer.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow.png new file mode 100644 index 00000000000..6d3ee518812 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript_fixed_font_size/text_html_supersubscript_fixed_font_size.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript_fixed_font_size/text_html_supersubscript_fixed_font_size.png new file mode 100644 index 00000000000..a4ce406f517 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_supersubscript_fixed_font_size/text_html_supersubscript_fixed_font_size.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow.png new file mode 100644 index 00000000000..9b9d334d675 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow.png differ