24 const QString skind =
string.trimmed();
26 if ( skind.compare(
"Square"_L1, Qt::CaseInsensitive ) == 0 )
30 else if ( skind.compare(
"Ellipse"_L1, Qt::CaseInsensitive ) == 0 )
34 else if ( skind.compare(
"Circle"_L1, Qt::CaseInsensitive ) == 0 )
38 else if ( skind.compare(
"SVG"_L1, Qt::CaseInsensitive ) == 0 )
42 else if ( skind.compare(
"marker"_L1, Qt::CaseInsensitive ) == 0 )
51 const QString stype =
string.trimmed();
55 if ( stype.compare(
"Fixed"_L1, Qt::CaseInsensitive ) == 0 )
64 const QString rotstr =
string.trimmed();
68 if ( rotstr.compare(
"Offset"_L1, Qt::CaseInsensitive ) == 0 )
72 else if ( rotstr.compare(
"Fixed"_L1, Qt::CaseInsensitive ) == 0 )
81 const QString str =
string.trimmed();
85 if ( str.compare(
"Text"_L1, Qt::CaseInsensitive ) == 0 )
89 else if ( str.compare(
"Buffer"_L1, Qt::CaseInsensitive ) == 0 )
93 else if ( str.compare(
"Background"_L1, Qt::CaseInsensitive ) == 0 )
102 switch ( orientation )
105 return u
"horizontal"_s;
107 return u
"vertical"_s;
109 return u
"rotation-based"_s;
119 const QString cleaned = name.toLower().trimmed();
121 if ( cleaned ==
"horizontal"_L1 )
123 else if ( cleaned ==
"vertical"_L1 )
125 else if ( cleaned ==
"rotation-based"_L1 )
149 const int r = layer->
customProperty( property +
'R', QVariant( defaultColor.red() ) ).toInt();
150 const int g = layer->
customProperty( property +
'G', QVariant( defaultColor.green() ) ).toInt();
151 const int b = layer->
customProperty( property +
'B', QVariant( defaultColor.blue() ) ).toInt();
152 const int a = withAlpha ? layer->
customProperty( property +
'A', QVariant( defaultColor.alpha() ) ).toInt() : 255;
153 return QColor( r, g, b, a );
158 const std::size_t numPoints = line.size();
159 std::vector<double> pathDistances( numPoints );
161 const QPointF *p = line.data();
164 pathDistances[0] = 0;
165 double prevX = p->x();
166 double prevY = p->y();
169 std::vector< double > x( numPoints );
170 std::vector< double > y( numPoints );
174 for ( std::size_t i = 1; i < numPoints; ++i )
178 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
187 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false );
190std::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 )
192 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false, additionalCharacterSpacing, additionalWordSpacing );
195std::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 )
197 auto output = std::make_unique< CurvePlacementProperties >();
198 output->graphemePlacement.reserve( metrics.
count() );
203 double offsetAlongSegment = offsetAlongLine;
206 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
208 offsetAlongSegment -= pathDistances[index];
211 if ( index >= numPoints )
216 const double segmentLength = pathDistances[index];
223 int characterCount = metrics.
count();
229 double distance = offsetAlongSegment;
230 int endindex = index;
232 double startLabelX = 0;
233 double startLabelY = 0;
234 double endLabelX = 0;
235 double endLabelY = 0;
236 for (
int i = 0; i < characterCount; i++ )
239 double characterStartX, characterStartY;
242 double currentSpacing = 0.0;
245 currentSpacing = additionalCharacterSpacing;
248 const QString g = metrics.
grapheme( i - 1 );
249 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
251 currentSpacing += additionalWordSpacing;
256 if ( !nextCharPosition( characterWidth, pathDistances, x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY, flags, currentSpacing ) )
260 characterCount = i + 1;
270 startLabelX = characterStartX;
271 startLabelY = characterStartY;
276 const double dx = endLabelX - startLabelX;
277 const double dy = endLabelY - startLabelY;
278 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
280 if ( lineAngle > 90 || lineAngle < -90 )
282 output->labeledLineSegmentIsRightToLeft =
true;
286 if ( isSecondAttempt )
290 output->labeledLineSegmentIsRightToLeft =
false;
291 output->flippedCharacterPlacementToGetUprightLabels =
true;
294 const double dx = x[index] - x[index - 1];
295 const double dy = y[index] - y[index - 1];
297 double angle = std::atan2( -dy, dx );
302 for (
int i = 0; i < characterCount; i++ )
304 const double lastCharacterAngle =
angle;
307 const int k = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
318 double characterStartX = 0;
319 double characterStartY = 0;
320 double characterEndX = 0;
321 double characterEndY = 0;
324 double currentSpacing = 0.0;
327 currentSpacing = additionalCharacterSpacing;
330 int prevCharIndex = !output->flippedCharacterPlacementToGetUprightLabels ? k - 1 : k + 1;
331 if ( prevCharIndex >= 0 && prevCharIndex < metrics.
count() )
333 const QString g = metrics.
grapheme( prevCharIndex );
334 if ( !g.isEmpty() && g.at( 0 ).isSpace() )
335 currentSpacing += additionalWordSpacing;
340 if ( !nextCharPosition( characterWidth, pathDistances, x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY, flags, currentSpacing ) )
344 characterCount = i + 1;
349 output->graphemePlacement.clear();
355 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
357 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
362 double angleDelta = lastCharacterAngle -
angle;
364 while ( angleDelta > M_PI )
365 angleDelta -= 2 * M_PI;
366 while ( angleDelta < -M_PI )
367 angleDelta += 2 * M_PI;
368 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
370 output->graphemePlacement.clear();
379 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
380 if ( output->flippedCharacterPlacementToGetUprightLabels )
384 characterStartX += dist * std::cos( angle + M_PI_2 );
385 characterStartY -= dist * std::sin( angle + M_PI_2 );
388 double renderAngle =
angle;
390 placement.
graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
391 placement.x = characterStartX;
392 placement.y = characterStartY;
393 placement.width = characterWidth;
394 placement.height = characterHeight;
395 const QString grapheme = metrics.
grapheme( placement.graphemeIndex );
396 placement.isWhitespace = grapheme.isEmpty() || grapheme.at( 0 ).isSpace() || grapheme.at( 0 ) ==
'\t';
397 if ( output->flippedCharacterPlacementToGetUprightLabels )
400 placement.x += characterWidth * std::cos( renderAngle );
401 placement.y -= characterWidth * std::sin( renderAngle );
404 placement.angle = -renderAngle;
405 output->graphemePlacement.push_back( placement );
408 while ( renderAngle >= 2 * M_PI )
409 renderAngle -= 2 * M_PI;
410 while ( renderAngle < 0 )
411 renderAngle += 2 * M_PI;
413 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
414 output->upsideDownCharCount++;
421 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true, additionalCharacterSpacing, additionalWordSpacing );
427bool 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 )
431 currentDistanceAlongSegment += additionalSpacing;
434 while ( index < numPoints && currentDistanceAlongSegment > pathDistances[index] )
436 currentDistanceAlongSegment -= pathDistances[index];
440 while ( currentDistanceAlongSegment < 0 )
445 currentDistanceAlongSegment += pathDistances[index];
451 if ( index >= numPoints )
463 double segmentStartX = x[index - 1];
464 double segmentStartY = y[index - 1];
466 double segmentEndX = x[index];
467 double segmentEndY = y[index];
469 double segmentLength = pathDistances[index];
471 const double segmentDx = segmentEndX - segmentStartX;
472 const double segmentDy = segmentEndY - segmentStartY;
474 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
475 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
481 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
484 currentDistanceAlongSegment += charWidth;
485 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
486 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
495 if ( index >= numPoints )
500 const double lastSegmentDx = segmentEndX - segmentStartX;
501 const double lastSegmentDy = segmentEndY - segmentStartY;
502 const double lastSegmentLength = std::sqrt( lastSegmentDx * lastSegmentDx + lastSegmentDy * lastSegmentDy );
509 segmentEndX = segmentStartX + ( lastSegmentDx / lastSegmentLength ) * charWidth;
510 segmentEndY = segmentStartY + ( lastSegmentDy / lastSegmentLength ) * charWidth;
521 segmentStartX = segmentEndX;
522 segmentStartY = segmentEndY;
523 segmentEndX = x[index];
524 segmentEndY = y[index];
526 while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
529 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
532 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
537void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
539 double multiplier = 1;
551 radius *= multiplier;
554 const double dx = x2 - x1;
555 const double dy = y2 - y1;
557 const double A = dx * dx + dy * dy;
558 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
561 const double det = B * B - 4 * A * C;
562 if ( A <= 0.000000000001 || det < 0 )
569 const double t = -B / ( 2 * A );
578 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
583 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).