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 std::size_t numPoints = line.size();
163 std::vector<double> pathDistances( numPoints );
165 const QPointF *p = line.data();
168 pathDistances[0] = 0;
169 double prevX = p->x();
170 double prevY = p->y();
173 std::vector< double > x( numPoints );
174 std::vector< double > y( numPoints );
178 for ( std::size_t i = 1; i < numPoints; ++i )
182 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
191 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false );
194std::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,
Qgis::CurvedTextFlags flags,
double additionalCharacterSpacing,
double additionalWordSpacing )
196 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, additionalCharacterSpacing, additionalWordSpacing );
199std::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,
Qgis::CurvedTextFlags flags,
double maxConcaveAngle,
double maxConvexAngle,
bool isSecondAttempt,
double additionalCharacterSpacing,
double additionalWordSpacing )
201 auto output = std::make_unique< CurvePlacementProperties >();
202 output->graphemePlacement.reserve( metrics.
count() );
207 double offsetAlongSegment = offsetAlongLine;
210 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
212 offsetAlongSegment -= pathDistances[index];
215 if ( index >= numPoints )
220 const double segmentLength = pathDistances[index];
227 int characterCount = metrics.
count();
233 double distance = offsetAlongSegment;
234 int endindex = index;
236 double startLabelX = 0;
237 double startLabelY = 0;
238 double endLabelX = 0;
239 double endLabelY = 0;
240 for (
int i = 0; i < characterCount; i++ )
243 double characterStartX, characterStartY;
246 double currentSpacing = 0.0;
249 currentSpacing = additionalCharacterSpacing;
252 const QString g = metrics.
grapheme( i - 1 );
253 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
255 currentSpacing += additionalWordSpacing;
260 if ( !nextCharPosition( characterWidth, pathDistances, x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY, flags, currentSpacing ) )
264 characterCount = i + 1;
274 startLabelX = characterStartX;
275 startLabelY = characterStartY;
280 const double dx = endLabelX - startLabelX;
281 const double dy = endLabelY - startLabelY;
282 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
284 if ( lineAngle > 90 || lineAngle < -90 )
286 output->labeledLineSegmentIsRightToLeft =
true;
290 if ( isSecondAttempt )
294 output->labeledLineSegmentIsRightToLeft =
false;
295 output->flippedCharacterPlacementToGetUprightLabels =
true;
298 const double dx = x[index] - x[index - 1];
299 const double dy = y[index] - y[index - 1];
301 double angle = std::atan2( -dy, dx );
306 for (
int i = 0; i < characterCount; i++ )
308 const double lastCharacterAngle =
angle;
311 const int k = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
322 double characterStartX = 0;
323 double characterStartY = 0;
324 double characterEndX = 0;
325 double characterEndY = 0;
328 double currentSpacing = 0.0;
331 currentSpacing = additionalCharacterSpacing;
334 int prevCharIndex = !output->flippedCharacterPlacementToGetUprightLabels ? k - 1 : k + 1;
335 if ( prevCharIndex >= 0 && prevCharIndex < metrics.
count() )
337 const QString g = metrics.
grapheme( prevCharIndex );
338 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
339 currentSpacing += additionalWordSpacing;
344 if ( !nextCharPosition( characterWidth, pathDistances, x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY, flags, currentSpacing ) )
348 characterCount = i + 1;
353 output->graphemePlacement.clear();
359 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
361 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
366 double angleDelta = lastCharacterAngle -
angle;
368 while ( angleDelta > M_PI )
369 angleDelta -= 2 * M_PI;
370 while ( angleDelta < -M_PI )
371 angleDelta += 2 * M_PI;
372 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
374 output->graphemePlacement.clear();
383 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
384 if ( output->flippedCharacterPlacementToGetUprightLabels )
388 characterStartX += dist * std::cos( angle + M_PI_2 );
389 characterStartY -= dist * std::sin( angle + M_PI_2 );
392 double renderAngle =
angle;
394 placement.
graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
395 placement.x = characterStartX;
396 placement.y = characterStartY;
397 placement.width = characterWidth;
398 placement.height = characterHeight;
399 const QString grapheme = metrics.
grapheme( placement.graphemeIndex );
400 placement.isWhitespace = grapheme.isEmpty() || grapheme.at( 0 ).isSpace() || grapheme.at( 0 ) ==
'\t';
401 if ( output->flippedCharacterPlacementToGetUprightLabels )
404 placement.x += characterWidth * std::cos( renderAngle );
405 placement.y -= characterWidth * std::sin( renderAngle );
408 placement.angle = -renderAngle;
409 output->graphemePlacement.push_back( placement );
412 while ( renderAngle >= 2 * M_PI )
413 renderAngle -= 2 * M_PI;
414 while ( renderAngle < 0 )
415 renderAngle += 2 * M_PI;
417 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
418 output->upsideDownCharCount++;
425 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true, additionalCharacterSpacing, additionalWordSpacing );
431bool QgsTextRendererUtils::nextCharPosition(
double charWidth,
const std::vector<double> &pathDistances,
const double *x,
const double *y,
int numPoints,
int &index,
double ¤tDistanceAlongSegment,
double &characterStartX,
double &characterStartY,
double &characterEndX,
double &characterEndY,
Qgis::CurvedTextFlags flags,
double additionalSpacing )
435 currentDistanceAlongSegment += additionalSpacing;
438 while ( index < numPoints && currentDistanceAlongSegment > pathDistances[index] )
440 currentDistanceAlongSegment -= pathDistances[index];
444 while ( currentDistanceAlongSegment < 0 )
449 currentDistanceAlongSegment += pathDistances[index];
455 if ( index >= numPoints )
467 double segmentStartX = x[index - 1];
468 double segmentStartY = y[index - 1];
470 double segmentEndX = x[index];
471 double segmentEndY = y[index];
473 double segmentLength = pathDistances[index];
475 const double segmentDx = segmentEndX - segmentStartX;
476 const double segmentDy = segmentEndY - segmentStartY;
478 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
479 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
485 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
488 currentDistanceAlongSegment += charWidth;
489 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
490 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
499 if ( index >= numPoints )
504 const double lastSegmentDx = segmentEndX - segmentStartX;
505 const double lastSegmentDy = segmentEndY - segmentStartY;
506 const double lastSegmentLength = std::sqrt( lastSegmentDx * lastSegmentDx + lastSegmentDy * lastSegmentDy );
513 segmentEndX = segmentStartX + ( lastSegmentDx / lastSegmentLength ) * charWidth;
514 segmentEndY = segmentStartY + ( lastSegmentDy / lastSegmentLength ) * charWidth;
525 segmentStartX = segmentEndX;
526 segmentStartY = segmentEndY;
527 segmentEndX = x[index];
528 segmentEndY = y[index];
530 while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
533 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
536 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
541void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
543 double multiplier = 1;
555 radius *= multiplier;
558 const double dx = x2 - x1;
559 const double dy = y2 - y1;
561 const double A = dx * dx + dy * dy;
562 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
565 const double det = B * B - 4 * A * C;
566 if ( A <= 0.000000000001 || det < 0 )
573 const double t = -B / ( 2 * A );
582 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
587 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).