23 const QString skind =
string.trimmed();
25 if ( skind.compare( QLatin1String(
"Square" ), Qt::CaseInsensitive ) == 0 )
29 else if ( skind.compare( QLatin1String(
"Ellipse" ), Qt::CaseInsensitive ) == 0 )
33 else if ( skind.compare( QLatin1String(
"Circle" ), Qt::CaseInsensitive ) == 0 )
37 else if ( skind.compare( QLatin1String(
"SVG" ), Qt::CaseInsensitive ) == 0 )
41 else if ( skind.compare( QLatin1String(
"marker" ), Qt::CaseInsensitive ) == 0 )
50 const QString stype =
string.trimmed();
54 if ( stype.compare( QLatin1String(
"Fixed" ), Qt::CaseInsensitive ) == 0 )
63 const QString rotstr =
string.trimmed();
67 if ( rotstr.compare( QLatin1String(
"Offset" ), Qt::CaseInsensitive ) == 0 )
71 else if ( rotstr.compare( QLatin1String(
"Fixed" ), Qt::CaseInsensitive ) == 0 )
80 const QString str =
string.trimmed();
84 if ( str.compare( QLatin1String(
"Text" ), Qt::CaseInsensitive ) == 0 )
88 else if ( str.compare( QLatin1String(
"Buffer" ), Qt::CaseInsensitive ) == 0 )
92 else if ( str.compare( QLatin1String(
"Background" ), Qt::CaseInsensitive ) == 0 )
101 switch ( orientation )
104 return QStringLiteral(
"horizontal" );
106 return QStringLiteral(
"vertical" );
108 return QStringLiteral(
"rotation-based" );
118 const QString cleaned = name.toLower().trimmed();
120 if ( cleaned == QLatin1String(
"horizontal" ) )
122 else if ( cleaned == QLatin1String(
"vertical" ) )
124 else if ( cleaned == QLatin1String(
"rotation-based" ) )
148 const int r = layer->
customProperty( property +
'R', QVariant( defaultColor.red() ) ).toInt();
149 const int g = layer->
customProperty( property +
'G', QVariant( defaultColor.green() ) ).toInt();
150 const int b = layer->
customProperty( property +
'B', QVariant( defaultColor.blue() ) ).toInt();
151 const int a = withAlpha ? layer->
customProperty( property +
'A', QVariant( defaultColor.alpha() ) ).toInt() : 255;
152 return QColor( r, g, b, a );
157 const int numPoints = line.size();
158 std::vector<double> pathDistances( numPoints );
160 const QPointF *p = line.data();
163 pathDistances[0] = 0;
164 double prevX = p->x();
165 double prevY = p->y();
168 std::vector< double > x( numPoints );
169 std::vector< double > y( numPoints );
173 for (
int i = 1; i < numPoints; ++i )
177 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
186 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false );
191 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle );
194std::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, CurvedTextFlags flags,
double maxConcaveAngle,
double maxConvexAngle,
bool isSecondAttempt )
196 std::unique_ptr< CurvePlacementProperties > output = std::make_unique< CurvePlacementProperties >();
197 output->graphemePlacement.reserve( metrics.
count() );
199 double offsetAlongSegment = offsetAlongLine;
202 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
204 offsetAlongSegment -= pathDistances[index];
207 if ( index >= numPoints )
212 const double segmentLength = pathDistances[index];
219 int characterCount = metrics.
count();
225 double distance = offsetAlongSegment;
226 int endindex = index;
228 double startLabelX = 0;
229 double startLabelY = 0;
230 double endLabelX = 0;
231 double endLabelY = 0;
232 for (
int i = 0; i < characterCount; i++ )
235 double characterStartX, characterStartY;
236 if ( !nextCharPosition( characterWidth, pathDistances[endindex], x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY ) )
240 characterCount = i + 1;
250 startLabelX = characterStartX;
251 startLabelY = characterStartY;
256 const double dx = endLabelX - startLabelX;
257 const double dy = endLabelY - startLabelY;
258 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
260 if ( lineAngle > 90 || lineAngle < -90 )
262 output->labeledLineSegmentIsRightToLeft =
true;
266 if ( isSecondAttempt )
270 output->labeledLineSegmentIsRightToLeft =
false;
271 output->flippedCharacterPlacementToGetUprightLabels =
true;
274 const double dx = x[index] - x[index - 1];
275 const double dy = y[index] - y[index - 1];
277 double angle = std::atan2( -dy, dx );
282 for (
int i = 0; i < characterCount; i++ )
284 const double lastCharacterAngle =
angle;
287 const double characterWidth = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterWidth( i ) : metrics.
characterWidth( characterCount - i - 1 );
292 const double characterHeight = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterHeight( i ) : metrics.
characterHeight( characterCount - i - 1 );
293 const double characterDescent = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterDescent( i ) : metrics.
characterDescent( characterCount - i - 1 );
295 double characterStartX = 0;
296 double characterStartY = 0;
297 double characterEndX = 0;
298 double characterEndY = 0;
299 if ( !nextCharPosition( characterWidth, pathDistances[index], x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY ) )
303 characterCount = i + 1;
308 output->graphemePlacement.clear();
314 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
316 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
321 double angleDelta = lastCharacterAngle -
angle;
323 while ( angleDelta > M_PI )
324 angleDelta -= 2 * M_PI;
325 while ( angleDelta < -M_PI )
326 angleDelta += 2 * M_PI;
327 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
329 output->graphemePlacement.clear();
338 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
339 if ( output->flippedCharacterPlacementToGetUprightLabels )
343 characterStartX += dist * std::cos( angle + M_PI_2 );
344 characterStartY -= dist * std::sin( angle + M_PI_2 );
347 double renderAngle =
angle;
348 CurvedGraphemePlacement placement;
349 placement.graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
350 placement.x = characterStartX;
351 placement.y = characterStartY;
352 placement.width = characterWidth;
353 placement.height = characterHeight;
354 if ( output->flippedCharacterPlacementToGetUprightLabels )
357 placement.x += characterWidth * std::cos( renderAngle );
358 placement.y -= characterWidth * std::sin( renderAngle );
361 placement.angle = -renderAngle;
362 output->graphemePlacement.push_back( placement );
365 while ( renderAngle >= 2 * M_PI )
366 renderAngle -= 2 * M_PI;
367 while ( renderAngle < 0 )
368 renderAngle += 2 * M_PI;
370 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
371 output->upsideDownCharCount++;
378 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true );
384bool QgsTextRendererUtils::nextCharPosition(
double charWidth,
double segmentLength,
const double *x,
const double *y,
int numPoints,
int &index,
double ¤tDistanceAlongSegment,
double &characterStartX,
double &characterStartY,
double &characterEndX,
double &characterEndY )
393 double segmentStartX = x[index - 1];
394 double segmentStartY = y[index - 1];
396 double segmentEndX = x[index];
397 double segmentEndY = y[index];
399 const double segmentDx = segmentEndX - segmentStartX;
400 const double segmentDy = segmentEndY - segmentStartY;
402 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
403 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
409 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
412 currentDistanceAlongSegment += charWidth;
413 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
414 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
422 segmentStartX = segmentEndX;
423 segmentStartY = segmentEndY;
425 if ( index >= numPoints )
429 segmentEndX = x[index];
430 segmentEndY = y[index];
432 while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
435 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
438 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
443void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
445 double multiplier = 1;
457 radius *= multiplier;
460 const double dx = x2 - x1;
461 const double dy = y2 - y1;
463 const double A = dx * dx + dy * dy;
464 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
467 const double det = B * B - 4 * A * C;
468 if ( A <= 0.000000000001 || det < 0 )
475 const double t = -B / ( 2 * A );
484 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
489 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)
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 renderered 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.
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.
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 QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
@ 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.
@ UseBaselinePlacement
Generate placement based on the character baselines instead of centers.
static Qgis::RenderUnit convertFromOldLabelUnit(int val)
Converts a unit from an old (pre 3.0) label unit.
QFlags< CurvedTextFlag > CurvedTextFlags
Flags controlling behavior of curved text generation.
static std::unique_ptr< CurvePlacementProperties > generateCurvedTextPlacement(const QgsPrecalculatedTextMetrics &metrics, const QPolygonF &line, double offsetAlongLine, LabelLineDirection direction=RespectPainterOrientation, double maxConcaveAngle=-1, double maxConvexAngle=-1, CurvedTextFlags flags=CurvedTextFlags())
Calculates curved text placement properties.
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 data sets.
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)