24using namespace Qt::StringLiterals;
37 auto evaluatedType = SHAPE_TYPE_MAP.lookup(
string );
40 return *evaluatedType;
52 auto evaluatedType = SIZE_TYPE_MAP.lookup(
string );
55 return *evaluatedType;
68 auto evaluatedType = ROTATION_TYPE_MAP.lookup(
string );
71 return *evaluatedType;
85 auto evaluatedType = SHADOW_PLACEMENT_MAP.lookup(
string );
88 return *evaluatedType;
95 switch ( orientation )
98 return u
"horizontal"_s;
100 return u
"vertical"_s;
102 return u
"rotation-based"_s;
118 auto evaluatedOrientation = ORIENTATION_MAP.lookup( name );
119 if ( evaluatedOrientation )
121 return *evaluatedOrientation;
145 const int r = layer->
customProperty( property +
'R', QVariant( defaultColor.red() ) ).toInt();
146 const int g = layer->
customProperty( property +
'G', QVariant( defaultColor.green() ) ).toInt();
147 const int b = layer->
customProperty( property +
'B', QVariant( defaultColor.blue() ) ).toInt();
148 const int a = withAlpha ? layer->
customProperty( property +
'A', QVariant( defaultColor.alpha() ) ).toInt() : 255;
149 return QColor( r, g, b, a );
154 const QPolygonF &line,
155 double offsetAlongLine,
157 double maxConcaveAngle,
158 double maxConvexAngle,
163 const std::size_t numPoints = line.size();
164 std::vector<double> pathDistances( numPoints );
166 const QPointF *p = line.data();
169 pathDistances[0] = 0;
170 double prevX = p->x();
171 double prevY = p->y();
174 std::vector< double > x( numPoints );
175 std::vector< double > y( numPoints );
179 for ( std::size_t i = 1; i < numPoints; ++i )
183 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
192 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, 0, 0, textAnchor );
200 const std::vector<double> &pathDistances,
201 double offsetAlongLine,
203 double maxConcaveAngle,
204 double maxConvexAngle,
206 double additionalCharacterSpacing,
207 double additionalWordSpacing,
211 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, additionalCharacterSpacing, additionalWordSpacing, textAnchor );
214std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacementPrivate(
219 const std::vector<double> &pathDistances,
220 double offsetAlongLine,
221 LabelLineDirection direction,
223 double maxConcaveAngle,
224 double maxConvexAngle,
225 bool isSecondAttempt,
226 double additionalCharacterSpacing,
227 double additionalWordSpacing,
231 auto output = std::make_unique< CurvePlacementProperties >();
232 output->graphemePlacement.reserve( metrics.
count() );
237 double totalLineLength = 0.0;
238 for (
double pathDistance : pathDistances )
240 totalLineLength += pathDistance;
244 switch ( textAnchor )
262 double totalTextWidth = 0.0;
263 int characterCount = metrics.
count();
264 for (
int i = 0; i < characterCount; ++i )
267 double spacing = 0.0;
270 spacing = additionalCharacterSpacing;
273 const QString g = metrics.
grapheme( i - 1 );
274 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
276 spacing += additionalWordSpacing;
280 totalTextWidth += currentCharacterWidth + spacing;
282 double adjustedOffsetAlongLine = 0.0;
286 constexpr double TEXT_WIDTH_ADJUSTMENT_FACTOR = 1.5;
287 switch ( textAnchor )
290 adjustedOffsetAlongLine = offsetAlongLine;
294 adjustedOffsetAlongLine = ( totalLineLength - ( totalTextWidth + TEXT_WIDTH_ADJUSTMENT_FACTOR ) ) / 2.0;
298 adjustedOffsetAlongLine = totalLineLength - ( totalTextWidth + TEXT_WIDTH_ADJUSTMENT_FACTOR );
305 std::vector< double > modifiedX( x, x + numPoints );
306 std::vector< double > modifiedY( y, y + numPoints );
307 std::vector< double > modifiedPathDistances = pathDistances;
312 const double extension = std::abs( adjustedOffsetAlongLine );
313 const double firstSegmentLength = modifiedPathDistances[1];
314 if ( firstSegmentLength > 0.0 )
316 const double dx = modifiedX[1] - modifiedX[0];
317 const double dy = modifiedY[1] - modifiedY[0];
318 modifiedX[0] -= ( dx / firstSegmentLength ) * extension;
319 modifiedY[0] -= ( dy / firstSegmentLength ) * extension;
320 modifiedPathDistances[1] += extension;
321 adjustedOffsetAlongLine = 0.0;
325 double offsetAlongSegment = adjustedOffsetAlongLine;
328 while ( index < numPoints && offsetAlongSegment > modifiedPathDistances[index] )
330 offsetAlongSegment -= modifiedPathDistances[index];
333 if ( index >= numPoints )
338 const double segmentLength = modifiedPathDistances[index];
349 double distance = offsetAlongSegment;
350 int endindex = index;
352 double startLabelX = 0;
353 double startLabelY = 0;
354 double endLabelX = 0;
355 double endLabelY = 0;
356 for (
int i = 0; i < characterCount; i++ )
359 double characterStartX, characterStartY;
362 double currentSpacing = 0.0;
365 currentSpacing = additionalCharacterSpacing;
368 const QString g = metrics.
grapheme( i - 1 );
369 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
371 currentSpacing += additionalWordSpacing;
376 if ( !nextCharPosition( characterWidth, modifiedPathDistances, modifiedX.data(), modifiedY.data(), numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY, flags, currentSpacing ) )
380 characterCount = i + 1;
390 startLabelX = characterStartX;
391 startLabelY = characterStartY;
396 const double dx = endLabelX - startLabelX;
397 const double dy = endLabelY - startLabelY;
398 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
400 if ( lineAngle > 90 || lineAngle < -90 )
402 output->labeledLineSegmentIsRightToLeft =
true;
406 if ( isSecondAttempt )
410 output->labeledLineSegmentIsRightToLeft =
false;
411 output->flippedCharacterPlacementToGetUprightLabels =
true;
414 const double dx = modifiedX[index] - modifiedX[index - 1];
415 const double dy = modifiedY[index] - modifiedY[index - 1];
417 double angle = std::atan2( -dy, dx );
422 for (
int i = 0; i < characterCount; i++ )
424 const double lastCharacterAngle =
angle;
427 const int k = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
438 double characterStartX = 0;
439 double characterStartY = 0;
440 double characterEndX = 0;
441 double characterEndY = 0;
444 double currentSpacing = 0.0;
447 currentSpacing = additionalCharacterSpacing;
450 int prevCharIndex = !output->flippedCharacterPlacementToGetUprightLabels ? k - 1 : k + 1;
451 if ( prevCharIndex >= 0 && prevCharIndex < metrics.
count() )
453 const QString g = metrics.
grapheme( prevCharIndex );
454 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
455 currentSpacing += additionalWordSpacing;
460 if ( !nextCharPosition( characterWidth, modifiedPathDistances, modifiedX.data(), modifiedY.data(), numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY, flags, currentSpacing ) )
464 characterCount = i + 1;
469 output->graphemePlacement.clear();
475 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
477 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
482 double angleDelta = lastCharacterAngle -
angle;
484 while ( angleDelta > M_PI )
485 angleDelta -= 2 * M_PI;
486 while ( angleDelta < -M_PI )
487 angleDelta += 2 * M_PI;
488 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
490 output->graphemePlacement.clear();
499 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
500 if ( output->flippedCharacterPlacementToGetUprightLabels )
504 characterStartX += dist * std::cos( angle + M_PI_2 );
505 characterStartY -= dist * std::sin( angle + M_PI_2 );
508 double renderAngle =
angle;
510 placement.
graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
511 placement.x = characterStartX;
512 placement.y = characterStartY;
513 placement.width = characterWidth;
514 placement.height = characterHeight;
515 const QString grapheme = metrics.
grapheme( placement.graphemeIndex );
516 placement.isWhitespace = grapheme.isEmpty() || grapheme.at( 0 ).isSpace() || grapheme.at( 0 ) ==
'\t';
517 if ( output->flippedCharacterPlacementToGetUprightLabels )
520 placement.x += characterWidth * std::cos( renderAngle );
521 placement.y -= characterWidth * std::sin( renderAngle );
524 placement.angle = -renderAngle;
525 output->graphemePlacement.push_back( placement );
528 while ( renderAngle >= 2 * M_PI )
529 renderAngle -= 2 * M_PI;
530 while ( renderAngle < 0 )
531 renderAngle += 2 * M_PI;
533 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
534 output->upsideDownCharCount++;
541 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true, additionalCharacterSpacing, additionalWordSpacing, textAnchor );
547bool QgsTextRendererUtils::nextCharPosition(
549 const std::vector<double> &pathDistances,
554 double ¤tDistanceAlongSegment,
555 double &characterStartX,
556 double &characterStartY,
557 double &characterEndX,
558 double &characterEndY,
560 double additionalSpacing
565 currentDistanceAlongSegment += additionalSpacing;
568 while ( index < numPoints && currentDistanceAlongSegment > pathDistances[index] )
570 currentDistanceAlongSegment -= pathDistances[index];
574 while ( currentDistanceAlongSegment < 0 )
579 currentDistanceAlongSegment += pathDistances[index];
585 if ( index >= numPoints )
597 double segmentStartX = x[index - 1];
598 double segmentStartY = y[index - 1];
600 double segmentEndX = x[index];
601 double segmentEndY = y[index];
603 double segmentLength = pathDistances[index];
605 const double segmentDx = segmentEndX - segmentStartX;
606 const double segmentDy = segmentEndY - segmentStartY;
608 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
609 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
615 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
618 currentDistanceAlongSegment += charWidth;
619 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
620 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
629 if ( index >= numPoints )
634 const double lastSegmentDx = segmentEndX - segmentStartX;
635 const double lastSegmentDy = segmentEndY - segmentStartY;
636 const double lastSegmentLength = std::sqrt( lastSegmentDx * lastSegmentDx + lastSegmentDy * lastSegmentDy );
643 segmentEndX = segmentStartX + ( lastSegmentDx / lastSegmentLength ) * charWidth;
644 segmentEndY = segmentStartY + ( lastSegmentDy / lastSegmentLength ) * charWidth;
654 segmentStartX = segmentEndX;
655 segmentStartY = segmentEndY;
656 segmentEndX = x[index];
657 segmentEndY = y[index];
658 }
while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
661 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
664 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
669void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
671 double multiplier = 1;
683 radius *= multiplier;
686 const double dx = x2 - x1;
687 const double dy = y2 - y1;
689 const double A = dx * dx + dy * dy;
690 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
693 const double det = B * B - 4 * A * C;
694 if ( A <= 0.000000000001 || det < 0 )
701 const double t = -B / ( 2 * A );
710 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
715 if ( multiplier != 1 )
TextAnchorPoint
Anchor point of label text.
@ EndOfText
Anchor using end of text.
@ StartOfText
Anchor using start of text.
@ CenterOfText
Anchor using center of text.
@ FollowPlacement
Automatically set the anchor point based on the line anchor point value. Values <25% of line length w...
TextOrientation
Text orientations.
@ Vertical
Vertically oriented text.
@ RotationBased
Horizontally or vertically oriented text based on rotation (only available for map labeling).
@ Horizontal
Horizontally oriented text.
RenderUnit
Rendering size units.
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes).
@ TruncateStringWhenLineIsTooShort
When a string is too long for the line, truncate characters instead of aborting the placement.
@ UprightCharactersOnly
Permit upright characters only. If not present then upside down text placement is permitted.
@ ExtendLineToFitText
When a string is too long for the line, extend the line's final segment to fit the entire string.
@ UseBaselinePlacement
Generate placement based on the character baselines instead of centers.
QFlags< CurvedTextFlag > CurvedTextFlags
Flags controlling behavior of curved text generation.
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
Contains precalculated properties regarding text metrics for text to be rendered at a later stage.
double maximumCharacterHeight() const
Returns the maximum height of any character found in the text.
double characterDescent(int position) const
Returns the descent of the character at the specified position.
int count() const
Returns the total number of characters.
double maximumCharacterDescent() const
Returns the maximum descent of any character found in the text.
double characterWidth(int position) const
Returns the width of the character at the specified position.
QString grapheme(int index) const
Returns the grapheme at the specified index.
double characterHeight(int position) const
Returns the character height of the character at the specified position (actually font metrics height...
SizeType
Methods for determining the background shape size.
@ SizeBuffer
Shape size is determined by adding a buffer margin around text.
ShapeType
Background shape types.
@ ShapeMarkerSymbol
Marker symbol.
@ ShapeSquare
Square - buffered sizes only.
@ ShapeRectangle
Rectangle.
RotationType
Methods for determining the rotation of the background shape.
@ RotationOffset
Shape rotation is offset from text rotation.
@ RotationSync
Shape rotation is synced with text rotation.
@ RotationFixed
Shape rotation is a fixed angle.
Contains placement information for a single grapheme in a curved text layout.
int graphemeIndex
Index of corresponding grapheme.
static QgsTextBackgroundSettings::ShapeType decodeShapeType(const QString &string)
Decodes a string representation of a background shape type to a type.
static Qgis::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
LabelLineDirection
Controls behavior of curved text with respect to line directions.
@ RespectPainterOrientation
Curved text will be placed respecting the painter orientation, and the actual line direction will be ...
static QColor readColor(QgsVectorLayer *layer, const QString &property, const QColor &defaultColor=Qt::black, bool withAlpha=true)
Converts an encoded color value from a layer property.
static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType(const QString &string)
Decodes a string representation of a shadow placement type to a type.
static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType(const QString &string)
Decodes a string representation of a background rotation type to a type.
static QString encodeTextOrientation(Qgis::TextOrientation orientation)
Encodes a text orientation.
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacement(const QgsPrecalculatedTextMetrics &metrics, const QPolygonF &line, double offsetAlongLine, LabelLineDirection direction=RespectPainterOrientation, double maxConcaveAngle=-1, double maxConvexAngle=-1, Qgis::CurvedTextFlags flags=Qgis::CurvedTextFlags(), Qgis::TextAnchorPoint textAnchor=Qgis::TextAnchorPoint::StartOfText)
Calculates curved text placement properties.
static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
static Qgis::RenderUnit convertFromOldLabelUnit(int val)
Converts a unit from an old (pre 3.0) label unit.
ShadowPlacement
Placement positions for text shadow.
@ ShadowBuffer
Draw shadow under buffer.
@ ShadowShape
Draw shadow under background shape.
@ ShadowLowest
Draw shadow below all text components.
@ ShadowText
Draw shadow under text.
Represents a vector layer which manages a vector based dataset.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
constexpr QgsDataDefinedStringDecoder< V, N > makeDataDefinedStringDecoder(const DataDefinedDecoderEntry< V >(&values)[N])
Creates a QgsDataDefinedStringDecoder from a hardcoded lookup table.