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 );
164 const std::size_t numPoints = line.size();
165 std::vector<double> pathDistances( numPoints );
167 const QPointF *p = line.data();
170 pathDistances[0] = 0;
171 double prevX = p->x();
172 double prevY = p->y();
175 std::vector< double > x( numPoints );
176 std::vector< double > y( numPoints );
180 for ( std::size_t i = 1; i < numPoints; ++i )
184 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
193 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false );
201 const std::vector<double> &pathDistances,
202 double offsetAlongLine,
204 double maxConcaveAngle,
205 double maxConvexAngle,
207 double additionalCharacterSpacing,
208 double additionalWordSpacing
211 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, additionalCharacterSpacing, additionalWordSpacing );
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
230 auto output = std::make_unique< CurvePlacementProperties >();
231 output->graphemePlacement.reserve( metrics.
count() );
236 double offsetAlongSegment = offsetAlongLine;
239 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
241 offsetAlongSegment -= pathDistances[index];
244 if ( index >= numPoints )
249 const double segmentLength = pathDistances[index];
256 int characterCount = metrics.
count();
262 double distance = offsetAlongSegment;
263 int endindex = index;
265 double startLabelX = 0;
266 double startLabelY = 0;
267 double endLabelX = 0;
268 double endLabelY = 0;
269 for (
int i = 0; i < characterCount; i++ )
272 double characterStartX, characterStartY;
275 double currentSpacing = 0.0;
278 currentSpacing = additionalCharacterSpacing;
281 const QString g = metrics.
grapheme( i - 1 );
282 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
284 currentSpacing += additionalWordSpacing;
289 if ( !nextCharPosition( characterWidth, pathDistances, x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY, flags, currentSpacing ) )
293 characterCount = i + 1;
303 startLabelX = characterStartX;
304 startLabelY = characterStartY;
309 const double dx = endLabelX - startLabelX;
310 const double dy = endLabelY - startLabelY;
311 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
313 if ( lineAngle > 90 || lineAngle < -90 )
315 output->labeledLineSegmentIsRightToLeft =
true;
319 if ( isSecondAttempt )
323 output->labeledLineSegmentIsRightToLeft =
false;
324 output->flippedCharacterPlacementToGetUprightLabels =
true;
327 const double dx = x[index] - x[index - 1];
328 const double dy = y[index] - y[index - 1];
330 double angle = std::atan2( -dy, dx );
335 for (
int i = 0; i < characterCount; i++ )
337 const double lastCharacterAngle =
angle;
340 const int k = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
351 double characterStartX = 0;
352 double characterStartY = 0;
353 double characterEndX = 0;
354 double characterEndY = 0;
357 double currentSpacing = 0.0;
360 currentSpacing = additionalCharacterSpacing;
363 int prevCharIndex = !output->flippedCharacterPlacementToGetUprightLabels ? k - 1 : k + 1;
364 if ( prevCharIndex >= 0 && prevCharIndex < metrics.
count() )
366 const QString g = metrics.
grapheme( prevCharIndex );
367 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
368 currentSpacing += additionalWordSpacing;
373 if ( !nextCharPosition( characterWidth, pathDistances, x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY, flags, currentSpacing ) )
377 characterCount = i + 1;
382 output->graphemePlacement.clear();
388 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
390 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
395 double angleDelta = lastCharacterAngle -
angle;
397 while ( angleDelta > M_PI )
398 angleDelta -= 2 * M_PI;
399 while ( angleDelta < -M_PI )
400 angleDelta += 2 * M_PI;
401 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
403 output->graphemePlacement.clear();
412 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
413 if ( output->flippedCharacterPlacementToGetUprightLabels )
417 characterStartX += dist * std::cos( angle + M_PI_2 );
418 characterStartY -= dist * std::sin( angle + M_PI_2 );
421 double renderAngle =
angle;
423 placement.
graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
424 placement.x = characterStartX;
425 placement.y = characterStartY;
426 placement.width = characterWidth;
427 placement.height = characterHeight;
428 const QString grapheme = metrics.
grapheme( placement.graphemeIndex );
429 placement.isWhitespace = grapheme.isEmpty() || grapheme.at( 0 ).isSpace() || grapheme.at( 0 ) ==
'\t';
430 if ( output->flippedCharacterPlacementToGetUprightLabels )
433 placement.x += characterWidth * std::cos( renderAngle );
434 placement.y -= characterWidth * std::sin( renderAngle );
437 placement.angle = -renderAngle;
438 output->graphemePlacement.push_back( placement );
441 while ( renderAngle >= 2 * M_PI )
442 renderAngle -= 2 * M_PI;
443 while ( renderAngle < 0 )
444 renderAngle += 2 * M_PI;
446 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
447 output->upsideDownCharCount++;
454 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true, additionalCharacterSpacing, additionalWordSpacing );
460bool QgsTextRendererUtils::nextCharPosition(
462 const std::vector<double> &pathDistances,
467 double ¤tDistanceAlongSegment,
468 double &characterStartX,
469 double &characterStartY,
470 double &characterEndX,
471 double &characterEndY,
473 double additionalSpacing
478 currentDistanceAlongSegment += additionalSpacing;
481 while ( index < numPoints && currentDistanceAlongSegment > pathDistances[index] )
483 currentDistanceAlongSegment -= pathDistances[index];
487 while ( currentDistanceAlongSegment < 0 )
492 currentDistanceAlongSegment += pathDistances[index];
498 if ( index >= numPoints )
510 double segmentStartX = x[index - 1];
511 double segmentStartY = y[index - 1];
513 double segmentEndX = x[index];
514 double segmentEndY = y[index];
516 double segmentLength = pathDistances[index];
518 const double segmentDx = segmentEndX - segmentStartX;
519 const double segmentDy = segmentEndY - segmentStartY;
521 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
522 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
528 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
531 currentDistanceAlongSegment += charWidth;
532 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
533 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
542 if ( index >= numPoints )
547 const double lastSegmentDx = segmentEndX - segmentStartX;
548 const double lastSegmentDy = segmentEndY - segmentStartY;
549 const double lastSegmentLength = std::sqrt( lastSegmentDx * lastSegmentDx + lastSegmentDy * lastSegmentDy );
556 segmentEndX = segmentStartX + ( lastSegmentDx / lastSegmentLength ) * charWidth;
557 segmentEndY = segmentStartY + ( lastSegmentDy / lastSegmentLength ) * charWidth;
567 segmentStartX = segmentEndX;
568 segmentStartY = segmentEndY;
569 segmentEndX = x[index];
570 segmentEndY = y[index];
571 }
while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
574 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
577 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
582void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
584 double multiplier = 1;
596 radius *= multiplier;
599 const double dx = x2 - x1;
600 const double dy = y2 - y1;
602 const double A = dx * dx + dy * dy;
603 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
606 const double det = B * B - 4 * A * C;
607 if ( A <= 0.000000000001 || det < 0 )
614 const double t = -B / ( 2 * A );
623 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
628 if ( multiplier != 1 )
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 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())
Calculates curved text placement properties.
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 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).