Skip to content

Commit

Permalink
Cleanup and refactor calculationg of label text metrics for curved
Browse files Browse the repository at this point in the history
labels, and move methods for calculating curved text placement
out of PAL so that they can be reused elsewhere
  • Loading branch information
nyalldawson committed Apr 8, 2021
1 parent 19a3d8e commit 832d5e9
Show file tree
Hide file tree
Showing 18 changed files with 687 additions and 388 deletions.
Expand Up @@ -224,7 +224,6 @@ Returns the height of a character when rendered with the specified text ``format

};


/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
Expand Up @@ -63,6 +63,8 @@ Attempts to decode a string representation of a text orientation.
%End




};


Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -1551,6 +1551,7 @@ set(QGIS_CORE_HDRS
textrenderer/qgstextformat.h
textrenderer/qgstextfragment.h
textrenderer/qgstextmasksettings.h
textrenderer/qgstextmetrics.h
textrenderer/qgstextrenderer.h
textrenderer/qgstextrendererutils.h
textrenderer/qgstextshadowsettings.h
Expand Down
2 changes: 0 additions & 2 deletions src/core/labeling/qgslabelfeature.cpp
Expand Up @@ -31,8 +31,6 @@ QgsLabelFeature::~QgsLabelFeature()
mPermissibleZoneGeosPrepared.reset();
mPermissibleZoneGeos.reset();
}

delete mInfo;
}

void QgsLabelFeature::setPermissibleZone( const QgsGeometry &geometry )
Expand Down
8 changes: 0 additions & 8 deletions src/core/labeling/qgslabelfeature.h
Expand Up @@ -27,7 +27,6 @@

namespace pal
{
class LabelInfo;
class Layer;
}

Expand Down Expand Up @@ -344,11 +343,6 @@ class CORE_EXPORT QgsLabelFeature
//! Sets text of the label
void setLabelText( const QString &text ) { mLabelText = text; }

//! Gets additional info required for curved label placement. Returns NULLPTR if not set
pal::LabelInfo *curvedLabelInfo() const { return mInfo; }
//! takes ownership of the instance
void setCurvedLabelInfo( pal::LabelInfo *info ) { mInfo = info; }

//! Gets PAL layer of the label feature. Should be only used internally in PAL
pal::Layer *layer() const { return mLayer; }
//! Assign PAL layer to the label feature. Should be only used internally in PAL
Expand Down Expand Up @@ -596,8 +590,6 @@ class CORE_EXPORT QgsLabelFeature
bool mAlwaysShow = false;
//! text of the label
QString mLabelText;
//! extra information for curved labels (may be NULLPTR)
pal::LabelInfo *mInfo = nullptr;

//! Distance to allow label to overrun linear features
double mOverrunDistance = 0;
Expand Down
23 changes: 19 additions & 4 deletions src/core/labeling/qgspallabeling.cpp
Expand Up @@ -2524,13 +2524,28 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
// store the label's calculated font for later use during painting
QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
lf->setDefinedFont( labelFont );
lf->setFontMetrics( *labelFontMetrics );

// TODO: only for placement which needs character info
// account for any data defined font metrics adjustments
lf->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
lf->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
lf->calculateInfo( placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved,
labelFontMetrics.get(), xform, format().allowHtmlFormatting() ? &doc : nullptr );
switch ( placement )
{
case QgsPalLayerSettings::AroundPoint:
case QgsPalLayerSettings::OverPoint:
case QgsPalLayerSettings::Line:
case QgsPalLayerSettings::Horizontal:
case QgsPalLayerSettings::Free:
case QgsPalLayerSettings::OrderedPositionsAroundPoint:
case QgsPalLayerSettings::OutsidePolygons:
// these placements don't require text metrics
break;

case QgsPalLayerSettings::Curved:
case QgsPalLayerSettings::PerimeterCurved:
lf->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, *labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, format().allowHtmlFormatting() ? &doc : nullptr ) );
break;
}

// for labelFeature the LabelInfo is passed to feat when it is registered

// TODO: allow layer-wide feature dist in PAL...?
Expand Down
90 changes: 39 additions & 51 deletions src/core/labeling/qgstextlabelfeature.cpp
Expand Up @@ -29,101 +29,89 @@ QgsTextLabelFeature::QgsTextLabelFeature( QgsFeatureId id, geos::unique_ptr geom
mDefinedFont = QFont();
}


QgsTextLabelFeature::~QgsTextLabelFeature()
{
delete mFontMetrics;
}

QgsTextLabelFeature::~QgsTextLabelFeature() = default;

QString QgsTextLabelFeature::text( int partId ) const
{
if ( partId == -1 )
return mLabelText;
else
return mClusters.at( partId );
return mTextMetrics->grapheme( partId );
}

QgsTextCharacterFormat QgsTextLabelFeature::characterFormat( int partId ) const
{
return mCharacterFormats.value( partId );
return mTextMetrics.has_value() ? mTextMetrics->graphemeFormat( partId ) : QgsTextCharacterFormat();
}

bool QgsTextLabelFeature::hasCharacterFormat( int partId ) const
{
return partId < mCharacterFormats.size();
return mTextMetrics.has_value() && partId < mTextMetrics->graphemeFormatCount();
}

void QgsTextLabelFeature::calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, QgsTextDocument *document )
void QgsTextLabelFeature::setFontMetrics( const QFontMetricsF &metrics )
{
if ( mInfo )
return;

mFontMetrics = new QFontMetricsF( *fm ); // duplicate metrics for when drawing label

qreal letterSpacing = mDefinedFont.letterSpacing();
qreal wordSpacing = mDefinedFont.wordSpacing();
mFontMetrics = metrics; // duplicate metrics for when drawing label
}

QgsPrecalculatedTextMetrics QgsTextLabelFeature::calculateTextMetrics( const QgsMapToPixel *xform, const QFontMetricsF &fontMetrics, double letterSpacing, double wordSpacing, const QString &text, QgsTextDocument *document )
{
// create label info!
const double mapScale = xform->mapUnitsPerPixel();
const double characterHeight = mapScale * fm->height();

// mLetterSpacing/mWordSpacing = 0.0 is default for non-curved labels
// (non-curved spacings handled by Qt in QgsPalLayerSettings/QgsPalLabeling)
qreal charWidth;
qreal wordSpaceFix;
const double characterHeight = mapScale * fontMetrics.height();
QStringList graphemes;
QVector< QgsTextCharacterFormat > graphemeFormats;

if ( document && curvedLabeling )
if ( document )
{
for ( const QgsTextBlock &block : std::as_const( *document ) )
{
for ( const QgsTextFragment &fragment : block )
{
const QStringList graphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
for ( const QString &grapheme : graphemes )
const QStringList fragmentGraphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
for ( const QString &grapheme : fragmentGraphemes )
{
mClusters.append( grapheme );
mCharacterFormats.append( fragment.characterFormat() );
graphemes.append( grapheme );
graphemeFormats.append( fragment.characterFormat() );
}
}
}
}
else
{
//split string by valid grapheme boundaries - required for certain scripts (see #6883)
mClusters = QgsPalLabeling::splitToGraphemes( mLabelText );
graphemes = QgsPalLabeling::splitToGraphemes( text );
}

std::vector< double > characterWidths( mClusters.count() );
for ( int i = 0; i < mClusters.count(); i++ )
QVector< double > characterWidths( graphemes.count() );
for ( int i = 0; i < graphemes.count(); i++ )
{
// reconstruct how Qt creates word spacing, then adjust per individual stored character
// this will allow PAL to create each candidate width = character width + correct spacing
charWidth = fm->horizontalAdvance( mClusters[i] );
if ( curvedLabeling )
{
wordSpaceFix = qreal( 0.0 );
if ( mClusters[i] == QLatin1String( " " ) )
{
// word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
int nxt = i + 1;
wordSpaceFix = ( nxt < mClusters.count() && mClusters[nxt] != QLatin1String( " " ) ) ? wordSpacing : qreal( 0.0 );
}
// this workaround only works for clusters with a single character. Not sure how it should be handled
// with multi-character clusters.
if ( mClusters[i].length() == 1 &&
!qgsDoubleNear( fm->horizontalAdvance( QString( mClusters[i].at( 0 ) ) ), fm->horizontalAdvance( mClusters[i].at( 0 ) ) + letterSpacing ) )
{
// word spacing applied when it shouldn't be
wordSpaceFix -= wordSpacing;
}

charWidth = fm->horizontalAdvance( QString( mClusters[i] ) ) + wordSpaceFix;
qreal wordSpaceFix = qreal( 0.0 );
if ( graphemes[i] == QLatin1String( " " ) )
{
// word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
int nxt = i + 1;
wordSpaceFix = ( nxt < graphemes.count() && graphemes[nxt] != QLatin1String( " " ) ) ? wordSpacing : qreal( 0.0 );
}
// this workaround only works for clusters with a single character. Not sure how it should be handled
// with multi-character clusters.
if ( graphemes[i].length() == 1 &&
!qgsDoubleNear( fontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) ), fontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing ) )
{
// word spacing applied when it shouldn't be
wordSpaceFix -= wordSpacing;
}

const double charWidth = fontMetrics.horizontalAdvance( QString( graphemes[i] ) ) + wordSpaceFix;
characterWidths[i] = mapScale * charWidth;
}
mInfo = new pal::LabelInfo( characterHeight, std::move( characterWidths ) );

QgsPrecalculatedTextMetrics res( graphemes, characterHeight, std::move( characterWidths ) );
res.setGraphemeFormats( graphemeFormats );
return res;
}

QgsTextDocument QgsTextLabelFeature::document() const
Expand Down
54 changes: 44 additions & 10 deletions src/core/labeling/qgstextlabelfeature.h
Expand Up @@ -19,6 +19,8 @@

#include "qgslabelfeature.h"
#include "qgstextdocument.h"
#include "qgstextmetrics.h"
#include <optional>

class QgsTextCharacterFormat;

Expand Down Expand Up @@ -62,9 +64,6 @@ class QgsTextLabelFeature : public QgsLabelFeature
*/
bool hasCharacterFormat( int partId ) const;

//! calculate data for info(). setDefinedFont() must have been called already.
void calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, QgsTextDocument *document = nullptr );

//! Gets data-defined values
const QMap< QgsPalLayerSettings::Property, QVariant > &dataDefinedValues() const { return mDataDefinedValues; }
//! Sets data-defined values
Expand All @@ -75,8 +74,43 @@ class QgsTextLabelFeature : public QgsLabelFeature
//! Font to be used for rendering
QFont definedFont() { return mDefinedFont; }

//! Metrics of the font for rendering
QFontMetricsF *labelFontMetrics() { return mFontMetrics; }
/**
* Metrics of the font for rendering.
*
* May be NULLPTR.
*/
QFontMetricsF *labelFontMetrics() { return mFontMetrics.has_value() ? &mFontMetrics.value() : nullptr; }

/**
* Sets the font \a metrics.
*/
void setFontMetrics( const QFontMetricsF &metrics );

/**
* Returns additional info required for curved label placement.
*
* Returns NULLPTR if not set.
*
* \see setTextMetrics()
* \since QGIS 3.20
*/
const QgsPrecalculatedTextMetrics *textMetrics() const { return mTextMetrics.has_value() ? &mTextMetrics.value() : nullptr; }

/**
* Sets additional text \a metrics required for curved label placement.
*
* \see textMetrics()
* \since QGIS 3.20
*/
void setTextMetrics( const QgsPrecalculatedTextMetrics &metrics ) { mTextMetrics = metrics; }

/**
* Calculate text metrics for later retrieval via textMetrics().
*
* \since QGIS 3.20
*/
static QgsPrecalculatedTextMetrics calculateTextMetrics( const QgsMapToPixel *xform, const QFontMetricsF &fontMetrics, double letterSpacing,
double wordSpacing, const QString &text = QString(), QgsTextDocument *document = nullptr );

/**
* Returns the document for the label.
Expand Down Expand Up @@ -125,15 +159,13 @@ class QgsTextLabelFeature : public QgsLabelFeature
double maximumCharacterAngleOutside() const { return mMaximumCharacterAngleOutside; }

protected:
//! List of graphemes (used for curved labels)
QStringList mClusters;

QList< QgsTextCharacterFormat > mCharacterFormats;

//! Font for rendering
QFont mDefinedFont;

//! Metrics of the font for rendering
QFontMetricsF *mFontMetrics = nullptr;
std::optional< QFontMetricsF > mFontMetrics;

//! Stores attribute values for data defined properties
QMap< QgsPalLayerSettings::Property, QVariant > mDataDefinedValues;

Expand All @@ -142,6 +174,8 @@ class QgsTextLabelFeature : public QgsLabelFeature
double mMaximumCharacterAngleInside = 0;
double mMaximumCharacterAngleOutside = 0;

std::optional< QgsPrecalculatedTextMetrics > mTextMetrics;

};

#endif //QGSTEXTLABELFEATURE_H
1 change: 0 additions & 1 deletion src/core/labeling/qgsvectorlayerlabelprovider.cpp
Expand Up @@ -629,7 +629,6 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q
}
}


QgsTextRenderer::HAlignment hAlign = QgsTextRenderer::AlignLeft;
if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiCenter )
hAlign = QgsTextRenderer::AlignCenter;
Expand Down

0 comments on commit 832d5e9

Please sign in to comment.