23using namespace Qt::StringLiterals;
28 const QString skind =
string.trimmed();
30 if ( skind.compare(
"Square"_L1, Qt::CaseInsensitive ) == 0 )
34 else if ( skind.compare(
"Ellipse"_L1, Qt::CaseInsensitive ) == 0 )
38 else if ( skind.compare(
"Circle"_L1, Qt::CaseInsensitive ) == 0 )
42 else if ( skind.compare(
"SVG"_L1, Qt::CaseInsensitive ) == 0 )
46 else if ( skind.compare(
"marker"_L1, Qt::CaseInsensitive ) == 0 )
55 const QString stype =
string.trimmed();
59 if ( stype.compare(
"Fixed"_L1, Qt::CaseInsensitive ) == 0 )
68 const QString rotstr =
string.trimmed();
72 if ( rotstr.compare(
"Offset"_L1, Qt::CaseInsensitive ) == 0 )
76 else if ( rotstr.compare(
"Fixed"_L1, Qt::CaseInsensitive ) == 0 )
85 const QString str =
string.trimmed();
89 if ( str.compare(
"Text"_L1, Qt::CaseInsensitive ) == 0 )
93 else if ( str.compare(
"Buffer"_L1, Qt::CaseInsensitive ) == 0 )
97 else if ( str.compare(
"Background"_L1, Qt::CaseInsensitive ) == 0 )
106 switch ( orientation )
109 return u
"horizontal"_s;
111 return u
"vertical"_s;
113 return u
"rotation-based"_s;
123 const QString cleaned = name.toLower().trimmed();
125 if ( cleaned ==
"horizontal"_L1 )
127 else if ( cleaned ==
"vertical"_L1 )
129 else if ( cleaned ==
"rotation-based"_L1 )
153 const int r = layer->
customProperty( property +
'R', QVariant( defaultColor.red() ) ).toInt();
154 const int g = layer->
customProperty( property +
'G', QVariant( defaultColor.green() ) ).toInt();
155 const int b = layer->
customProperty( property +
'B', QVariant( defaultColor.blue() ) ).toInt();
156 const int a = withAlpha ? layer->
customProperty( property +
'A', QVariant( defaultColor.alpha() ) ).toInt() : 255;
157 return QColor( r, g, b, a );
162 const QPolygonF &line,
163 double offsetAlongLine,
165 double maxConcaveAngle,
166 double maxConvexAngle,
171 const std::size_t numPoints = line.size();
172 std::vector<double> pathDistances( numPoints );
174 const QPointF *p = line.data();
177 pathDistances[0] = 0;
178 double prevX = p->x();
179 double prevY = p->y();
182 std::vector< double > x( numPoints );
183 std::vector< double > y( numPoints );
187 for ( std::size_t i = 1; i < numPoints; ++i )
191 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
200 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, 0, 0, textAnchor );
208 const std::vector<double> &pathDistances,
209 double offsetAlongLine,
211 double maxConcaveAngle,
212 double maxConvexAngle,
214 double additionalCharacterSpacing,
215 double additionalWordSpacing,
219 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, additionalCharacterSpacing, additionalWordSpacing, textAnchor );
222std::unique_ptr< QgsTextRendererUtils::CurvePlacementProperties > QgsTextRendererUtils::generateCurvedTextPlacementPrivate(
227 const std::vector<double> &pathDistances,
228 double offsetAlongLine,
229 LabelLineDirection direction,
231 double maxConcaveAngle,
232 double maxConvexAngle,
233 bool isSecondAttempt,
234 double additionalCharacterSpacing,
235 double additionalWordSpacing,
239 auto output = std::make_unique< CurvePlacementProperties >();
240 output->graphemePlacement.reserve( metrics.
count() );
245 double totalLineLength = 0.0;
246 for (
double pathDistance : pathDistances )
248 totalLineLength += pathDistance;
252 switch ( textAnchor )
270 double totalTextWidth = 0.0;
271 int characterCount = metrics.
count();
272 for (
int i = 0; i < characterCount; ++i )
275 double spacing = 0.0;
278 spacing = additionalCharacterSpacing;
281 const QString g = metrics.
grapheme( i - 1 );
282 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
284 spacing += additionalWordSpacing;
288 totalTextWidth += currentCharacterWidth + spacing;
290 double adjustedOffsetAlongLine = 0.0;
294 constexpr double TEXT_WIDTH_ADJUSTMENT_FACTOR = 1.5;
295 switch ( textAnchor )
298 adjustedOffsetAlongLine = offsetAlongLine;
302 adjustedOffsetAlongLine = ( totalLineLength - ( totalTextWidth + TEXT_WIDTH_ADJUSTMENT_FACTOR ) ) / 2.0;
306 adjustedOffsetAlongLine = totalLineLength - ( totalTextWidth + TEXT_WIDTH_ADJUSTMENT_FACTOR );
313 std::vector< double > modifiedX( x, x + numPoints );
314 std::vector< double > modifiedY( y, y + numPoints );
315 std::vector< double > modifiedPathDistances = pathDistances;
320 const double extension = std::abs( adjustedOffsetAlongLine );
321 const double firstSegmentLength = modifiedPathDistances[1];
322 if ( firstSegmentLength > 0.0 )
324 const double dx = modifiedX[1] - modifiedX[0];
325 const double dy = modifiedY[1] - modifiedY[0];
326 modifiedX[0] -= ( dx / firstSegmentLength ) * extension;
327 modifiedY[0] -= ( dy / firstSegmentLength ) * extension;
328 modifiedPathDistances[1] += extension;
329 adjustedOffsetAlongLine = 0.0;
333 double offsetAlongSegment = adjustedOffsetAlongLine;
336 while ( index < numPoints && offsetAlongSegment > modifiedPathDistances[index] )
338 offsetAlongSegment -= modifiedPathDistances[index];
341 if ( index >= numPoints )
346 const double segmentLength = modifiedPathDistances[index];
357 double distance = offsetAlongSegment;
358 int endindex = index;
360 double startLabelX = 0;
361 double startLabelY = 0;
362 double endLabelX = 0;
363 double endLabelY = 0;
364 for (
int i = 0; i < characterCount; i++ )
367 double characterStartX, characterStartY;
370 double currentSpacing = 0.0;
373 currentSpacing = additionalCharacterSpacing;
376 const QString g = metrics.
grapheme( i - 1 );
377 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
379 currentSpacing += additionalWordSpacing;
384 if ( !nextCharPosition( characterWidth, modifiedPathDistances, modifiedX.data(), modifiedY.data(), numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY, flags, currentSpacing ) )
388 characterCount = i + 1;
398 startLabelX = characterStartX;
399 startLabelY = characterStartY;
404 const double dx = endLabelX - startLabelX;
405 const double dy = endLabelY - startLabelY;
406 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
408 if ( lineAngle > 90 || lineAngle < -90 )
410 output->labeledLineSegmentIsRightToLeft =
true;
414 if ( isSecondAttempt )
418 output->labeledLineSegmentIsRightToLeft =
false;
419 output->flippedCharacterPlacementToGetUprightLabels =
true;
422 const double dx = modifiedX[index] - modifiedX[index - 1];
423 const double dy = modifiedY[index] - modifiedY[index - 1];
425 double angle = std::atan2( -dy, dx );
430 for (
int i = 0; i < characterCount; i++ )
432 const double lastCharacterAngle =
angle;
435 const int k = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
446 double characterStartX = 0;
447 double characterStartY = 0;
448 double characterEndX = 0;
449 double characterEndY = 0;
452 double currentSpacing = 0.0;
455 currentSpacing = additionalCharacterSpacing;
458 int prevCharIndex = !output->flippedCharacterPlacementToGetUprightLabels ? k - 1 : k + 1;
459 if ( prevCharIndex >= 0 && prevCharIndex < metrics.
count() )
461 const QString g = metrics.
grapheme( prevCharIndex );
462 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
463 currentSpacing += additionalWordSpacing;
468 if ( !nextCharPosition( characterWidth, modifiedPathDistances, modifiedX.data(), modifiedY.data(), numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY, flags, currentSpacing ) )
472 characterCount = i + 1;
477 output->graphemePlacement.clear();
483 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
485 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
490 double angleDelta = lastCharacterAngle -
angle;
492 while ( angleDelta > M_PI )
493 angleDelta -= 2 * M_PI;
494 while ( angleDelta < -M_PI )
495 angleDelta += 2 * M_PI;
496 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
498 output->graphemePlacement.clear();
507 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
508 if ( output->flippedCharacterPlacementToGetUprightLabels )
512 characterStartX += dist * std::cos( angle + M_PI_2 );
513 characterStartY -= dist * std::sin( angle + M_PI_2 );
516 double renderAngle =
angle;
518 placement.
graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
519 placement.x = characterStartX;
520 placement.y = characterStartY;
521 placement.width = characterWidth;
522 placement.height = characterHeight;
523 const QString grapheme = metrics.
grapheme( placement.graphemeIndex );
524 placement.isWhitespace = grapheme.isEmpty() || grapheme.at( 0 ).isSpace() || grapheme.at( 0 ) ==
'\t';
525 if ( output->flippedCharacterPlacementToGetUprightLabels )
528 placement.x += characterWidth * std::cos( renderAngle );
529 placement.y -= characterWidth * std::sin( renderAngle );
532 placement.angle = -renderAngle;
533 output->graphemePlacement.push_back( placement );
536 while ( renderAngle >= 2 * M_PI )
537 renderAngle -= 2 * M_PI;
538 while ( renderAngle < 0 )
539 renderAngle += 2 * M_PI;
541 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
542 output->upsideDownCharCount++;
549 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true, additionalCharacterSpacing, additionalWordSpacing, textAnchor );
555bool QgsTextRendererUtils::nextCharPosition(
557 const std::vector<double> &pathDistances,
562 double ¤tDistanceAlongSegment,
563 double &characterStartX,
564 double &characterStartY,
565 double &characterEndX,
566 double &characterEndY,
568 double additionalSpacing
573 currentDistanceAlongSegment += additionalSpacing;
576 while ( index < numPoints && currentDistanceAlongSegment > pathDistances[index] )
578 currentDistanceAlongSegment -= pathDistances[index];
582 while ( currentDistanceAlongSegment < 0 )
587 currentDistanceAlongSegment += pathDistances[index];
593 if ( index >= numPoints )
605 double segmentStartX = x[index - 1];
606 double segmentStartY = y[index - 1];
608 double segmentEndX = x[index];
609 double segmentEndY = y[index];
611 double segmentLength = pathDistances[index];
613 const double segmentDx = segmentEndX - segmentStartX;
614 const double segmentDy = segmentEndY - segmentStartY;
616 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
617 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
623 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
626 currentDistanceAlongSegment += charWidth;
627 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
628 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
637 if ( index >= numPoints )
642 const double lastSegmentDx = segmentEndX - segmentStartX;
643 const double lastSegmentDy = segmentEndY - segmentStartY;
644 const double lastSegmentLength = std::sqrt( lastSegmentDx * lastSegmentDx + lastSegmentDy * lastSegmentDy );
651 segmentEndX = segmentStartX + ( lastSegmentDx / lastSegmentLength ) * charWidth;
652 segmentEndY = segmentStartY + ( lastSegmentDy / lastSegmentLength ) * charWidth;
662 segmentStartX = segmentEndX;
663 segmentStartY = segmentEndY;
664 segmentEndX = x[index];
665 segmentEndY = y[index];
666 }
while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
669 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
672 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
677void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
679 double multiplier = 1;
691 radius *= multiplier;
694 const double dx = x2 - x1;
695 const double dy = y2 - y1;
697 const double A = dx * dx + dy * dy;
698 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
701 const double det = B * B - 4 * A * C;
702 if ( A <= 0.000000000001 || det < 0 )
709 const double t = -B / ( 2 * A );
718 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
723 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).