Skip to content

Commit

Permalink
Add support for super and subscript HTML formatting in text renderer
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson committed Nov 14, 2022
1 parent 79b809a commit a7c39ff
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 15 deletions.
Expand Up @@ -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;
Expand All @@ -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

Expand Down
22 changes: 19 additions & 3 deletions src/core/labeling/qgspallabeling.cpp
Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 );
Expand Down
2 changes: 1 addition & 1 deletion src/core/labeling/qgspallabeling.h
Expand Up @@ -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
Expand Down
39 changes: 34 additions & 5 deletions src/core/labeling/qgsvectorlayerlabelprovider.cpp
Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 );
}

Expand All @@ -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;
}
}

Expand Down

0 comments on commit a7c39ff

Please sign in to comment.