Skip to content

Commit

Permalink
Refactor curved text placement generation to use a flag approach
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Mar 31, 2023
1 parent cd3a1bf commit c8db000
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 21 deletions.
12 changes: 7 additions & 5 deletions src/core/pal/feature.cpp
Expand Up @@ -1309,7 +1309,7 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
return lPos.size();
}

std::unique_ptr< LabelPosition > FeaturePart::curvedPlacementAtOffset( PointSet *mapShape, const std::vector< double> &pathDistances, QgsTextRendererUtils::LabelLineDirection direction, const double offsetAlongLine, bool &labeledLineSegmentIsRightToLeft, bool applyAngleConstraints, bool uprightOnly )
std::unique_ptr< LabelPosition > FeaturePart::curvedPlacementAtOffset( PointSet *mapShape, const std::vector< double> &pathDistances, QgsTextRendererUtils::LabelLineDirection direction, const double offsetAlongLine, bool &labeledLineSegmentIsRightToLeft, bool applyAngleConstraints, QgsTextRendererUtils::CurvedTextFlags flags )
{
const QgsPrecalculatedTextMetrics *metrics = qgis::down_cast< QgsTextLabelFeature * >( mLF )->textMetrics();
Q_ASSERT( metrics );
Expand All @@ -1318,10 +1318,10 @@ std::unique_ptr< LabelPosition > FeaturePart::curvedPlacementAtOffset( PointSet
const double maximumCharacterAngleOutside = applyAngleConstraints ? std::fabs( qgis::down_cast< QgsTextLabelFeature *>( mLF )->maximumCharacterAngleOutside() ) : -1;

std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > placement(
QgsTextRendererUtils::generateCurvedTextPlacement( *metrics, mapShape->x.data(), mapShape->y.data(), mapShape->nbPoints, pathDistances, offsetAlongLine, direction, maximumCharacterAngleInside, maximumCharacterAngleOutside, uprightOnly )
QgsTextRendererUtils::generateCurvedTextPlacement( *metrics, mapShape->x.data(), mapShape->y.data(), mapShape->nbPoints, pathDistances, offsetAlongLine, direction, maximumCharacterAngleInside, maximumCharacterAngleOutside, flags )
);

labeledLineSegmentIsRightToLeft = !uprightOnly ? placement->labeledLineSegmentIsRightToLeft : placement->flippedCharacterPlacementToGetUprightLabels;
labeledLineSegmentIsRightToLeft = !( flags & QgsTextRendererUtils::CurvedTextFlag::UprightCharactersOnly ) ? placement->labeledLineSegmentIsRightToLeft : placement->flippedCharacterPlacementToGetUprightLabels;

if ( placement->graphemePlacement.empty() )
return nullptr;
Expand Down Expand Up @@ -1548,9 +1548,11 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
// placements may need to be reversed if using map orientation and the line has right-to-left direction
bool labeledLineSegmentIsRightToLeft = false;
const QgsTextRendererUtils::LabelLineDirection direction = ( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) ? QgsTextRendererUtils::RespectPainterOrientation : QgsTextRendererUtils::FollowLineDirection;
std::unique_ptr< LabelPosition > labelPosition = curvedPlacementAtOffset( currentMapShape, pathDistances, direction, distanceAlongLineToStartCandidate, labeledLineSegmentIsRightToLeft, !singleCandidateOnly,
onlyShowUprightLabels() && ( !singleCandidateOnly || !( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) ) );
QgsTextRendererUtils::CurvedTextFlags curvedTextFlags;
if ( onlyShowUprightLabels() && ( !singleCandidateOnly || !( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) ) )
curvedTextFlags |= QgsTextRendererUtils::CurvedTextFlag::UprightCharactersOnly;

std::unique_ptr< LabelPosition > labelPosition = curvedPlacementAtOffset( currentMapShape, pathDistances, direction, distanceAlongLineToStartCandidate, labeledLineSegmentIsRightToLeft, !singleCandidateOnly, curvedTextFlags );
if ( !labelPosition )
{
continue;
Expand Down
3 changes: 2 additions & 1 deletion src/core/pal/feature.h
Expand Up @@ -224,7 +224,8 @@ namespace pal
* \returns calculated label position
*/
std::unique_ptr< LabelPosition > curvedPlacementAtOffset( PointSet *mapShape, const std::vector<double> &pathDistances,
QgsTextRendererUtils::LabelLineDirection direction, double distance, bool &labeledLineSegmentIsRightToLeft, bool applyAngleConstraints, bool uprightOnly );
QgsTextRendererUtils::LabelLineDirection direction, double distance, bool &labeledLineSegmentIsRightToLeft, bool applyAngleConstraints,
QgsTextRendererUtils::CurvedTextFlags flags );

/**
* Generate curved candidates for line features.
Expand Down
8 changes: 7 additions & 1 deletion src/core/textrenderer/qgstextrenderer.cpp
Expand Up @@ -290,7 +290,13 @@ void QgsTextRenderer::drawDocumentOnLine( const QPolygonF &line, const QgsTextFo
QgsPrecalculatedTextMetrics metrics( graphemes, std::move( characterWidths ), std::move( characterHeights ), std::move( characterDescents ) );
metrics.setGraphemeFormats( graphemeFormats );

std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > placement = QgsTextRendererUtils::generateCurvedTextPlacement( metrics, labelBaselineCurve, offsetAlongLine );
std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > placement = QgsTextRendererUtils::generateCurvedTextPlacement(
metrics, labelBaselineCurve, offsetAlongLine,
QgsTextRendererUtils::RespectPainterOrientation,
-1, -1,
QgsTextRendererUtils::CurvedTextFlag::UseBaselinePlacement
| QgsTextRendererUtils::CurvedTextFlag::UprightCharactersOnly );

if ( placement->graphemePlacement.empty() )
return;

Expand Down
16 changes: 8 additions & 8 deletions src/core/textrenderer/qgstextrendererutils.cpp
Expand Up @@ -151,7 +151,7 @@ QColor QgsTextRendererUtils::readColor( QgsVectorLayer *layer, const QString &pr
return QColor( r, g, b, a );
}

std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const QPolygonF &line, double offsetAlongLine, LabelLineDirection direction, double maxConcaveAngle, double maxConvexAngle, bool uprightOnly )
std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const QPolygonF &line, double offsetAlongLine, LabelLineDirection direction, double maxConcaveAngle, double maxConvexAngle, CurvedTextFlags flags )
{
const int numPoints = line.size();
std::vector<double> pathDistances( numPoints );
Expand Down Expand Up @@ -182,15 +182,15 @@ std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendere
y[i] = prevY;
}

return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, maxConcaveAngle, maxConvexAngle, uprightOnly, false, true );
return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle, false );
}

std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector<double> &pathDistances, double offsetAlongLine, LabelLineDirection direction, double maxConcaveAngle, double maxConvexAngle, bool uprightOnly )
std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector<double> &pathDistances, double offsetAlongLine, LabelLineDirection direction, double maxConcaveAngle, double maxConvexAngle, bool uprightOnly, CurvedTextFlags flags )
{
return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, maxConcaveAngle, maxConvexAngle, uprightOnly );
return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle, uprightOnly );
}

std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacementPrivate( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector<double> &pathDistances, double offsetAlongLine, LabelLineDirection direction, double maxConcaveAngle, double maxConvexAngle, bool uprightOnly, bool isSecondAttempt, bool calculateBaselinePlacement )
std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacementPrivate( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector<double> &pathDistances, double offsetAlongLine, LabelLineDirection direction, CurvedTextFlags flags, double maxConcaveAngle, double maxConvexAngle, bool isSecondAttempt )
{
std::unique_ptr< CurvePlacementProperties > output = std::make_unique< CurvePlacementProperties >();
output->graphemePlacement.reserve( metrics.count() );
Expand Down Expand Up @@ -314,7 +314,7 @@ std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendere
}
}

if ( !calculateBaselinePlacement )
if ( !( flags & CurvedTextFlag::UseBaselinePlacement ) )
{
// Shift the character downwards since the draw position is specified at the baseline
// and we're calculating the mean line here
Expand Down Expand Up @@ -354,11 +354,11 @@ std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendere
output->upsideDownCharCount++;
}

if ( !isSecondAttempt && uprightOnly && output->upsideDownCharCount >= characterCount / 2.0 )
if ( !isSecondAttempt && ( flags & QgsTextRendererUtils::CurvedTextFlag::UprightCharactersOnly ) && output->upsideDownCharCount >= characterCount / 2.0 )
{
// more of text is upside down then right side up...
// if text should be shown upright then retry with the opposite orientation
return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, maxConcaveAngle, maxConvexAngle, uprightOnly, true );
return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle, true );
}

return output;
Expand Down
31 changes: 26 additions & 5 deletions src/core/textrenderer/qgstextrendererutils.h
Expand Up @@ -141,6 +141,24 @@ class CORE_EXPORT QgsTextRendererUtils
FollowLineDirection //!< Curved text placement will respect the line direction and ignore painter orientation
};

/**
* Flags controlling behavior of curved text generation.
*
* \since QGIS 3.2
*/
enum class CurvedTextFlag : int
{
UseBaselinePlacement = 1 << 1, //!< Generate placement based on the character baselines instead of centers
UprightCharactersOnly = 1 << 2, //!< Permit upright characters only. If not present then upside down text placement is permitted.
};

/**
* Flags controlling behavior of curved text generation.
*
* \since QGIS 3.2
*/
Q_DECLARE_FLAGS( CurvedTextFlags, CurvedTextFlag )

/**
* Calculates curved text placement properties.
*
Expand All @@ -150,12 +168,12 @@ class CORE_EXPORT QgsTextRendererUtils
* \param direction controls placement of text with respect to painter orientation or line direction
* \param maxConcaveAngle maximum angle between characters for concave text, or -1 if not set
* \param maxConvexAngle maximum angle between characters for convex text, or -1 if not set
* \param uprightOnly set to TRUE if text should be placed in an upright orientation only, or FALSE to allow upside down text placement
* \param flags flags controlling behavior of curved text generation
*
* \returns calculated placement properties, or NULLPTR if placement could not be calculated. Caller takes ownership of the returned placement.
* \since QGIS 3.20
*/
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const QPolygonF &line, double offsetAlongLine, LabelLineDirection direction = RespectPainterOrientation, double maxConcaveAngle = -1, double maxConvexAngle = -1, bool uprightOnly = true ) SIP_SKIP;
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const QPolygonF &line, double offsetAlongLine, LabelLineDirection direction = RespectPainterOrientation, double maxConcaveAngle = -1, double maxConvexAngle = -1, CurvedTextFlags flags = CurvedTextFlags() ) SIP_SKIP;

/**
* Calculates curved text placement properties.
Expand All @@ -169,17 +187,17 @@ class CORE_EXPORT QgsTextRendererUtils
* \param direction controls placement of text with respect to painter orientation or line direction
* \param maxConcaveAngle maximum angle between characters for concave text, or -1 if not set
* \param maxConvexAngle maximum angle between characters for convex text, or -1 if not set
* \param uprightOnly set to TRUE if text should be placed in an upright orientation only, or FALSE to allow upside down text placement
* \param flags flags controlling behavior of curved text generation
*
* \returns calculated placement properties, or NULLPTR if placement could not be calculated. Caller takes ownership of the returned placement.
* \since QGIS 3.20
*/
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector< double> &pathDistances, double offsetAlongLine, LabelLineDirection direction = RespectPainterOrientation, double maxConcaveAngle = -1, double maxConvexAngle = -1, bool uprightOnly = true ) SIP_SKIP;
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacement( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector< double> &pathDistances, double offsetAlongLine, LabelLineDirection direction = RespectPainterOrientation, double maxConcaveAngle = -1, double maxConvexAngle = -1, bool uprightOnly = true, CurvedTextFlags flags = CurvedTextFlags() ) SIP_SKIP;
#endif

private:

static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacementPrivate( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector< double> &pathDistances, double offsetAlongLine, LabelLineDirection direction, double maxConcaveAngle = -1, double maxConvexAngle = -1, bool uprightOnly = true, bool isSecondAttempt = false, bool calculateBaselinePlacement = false ) SIP_SKIP;
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacementPrivate( const QgsPrecalculatedTextMetrics &metrics, const double *x, const double *y, int numPoints, const std::vector< double> &pathDistances, double offsetAlongLine, LabelLineDirection direction, CurvedTextFlags flags, double maxConcaveAngle = -1, double maxConvexAngle = -1, bool isSecondAttempt = false ) SIP_SKIP;

//! Returns TRUE if the next char position is found. The referenced parameters are updated.
static bool nextCharPosition( double charWidth, double segmentLength, const double *x, const double *y, int numPoints, int &index, double &currentDistanceAlongSegment,
Expand All @@ -189,6 +207,9 @@ class CORE_EXPORT QgsTextRendererUtils
double x1, double y1, double x2, double y2,
double &xRes, double &yRes );
};
#ifndef SIP_RUN
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsTextRendererUtils::CurvedTextFlags );
#endif


#endif // QGSTEXTRENDERERUTILS_H
2 changes: 1 addition & 1 deletion tests/src/python/test_qgsannotationlinetextitem.py
Expand Up @@ -272,7 +272,7 @@ def testRenderWithTransform(self):
def imageCheck(self, name, reference_image, image):
TestQgsAnnotationLineTextItem.report += f"<h2>Render {name}</h2>\n"
temp_dir = QDir.tempPath() + '/'
file_name = temp_dir + 'patch_' + name + ".png"
file_name = temp_dir + 'annotation_' + name + ".png"
image.save(file_name, "PNG")
checker = QgsRenderChecker()
checker.setControlPathPrefix("annotation_layer")
Expand Down

0 comments on commit c8db000

Please sign in to comment.