24 const QString skind =
string.trimmed();
26 if ( skind.compare( QLatin1String(
"Square" ), Qt::CaseInsensitive ) == 0 )
30 else if ( skind.compare( QLatin1String(
"Ellipse" ), Qt::CaseInsensitive ) == 0 )
34 else if ( skind.compare( QLatin1String(
"Circle" ), Qt::CaseInsensitive ) == 0 )
38 else if ( skind.compare( QLatin1String(
"SVG" ), Qt::CaseInsensitive ) == 0 )
42 else if ( skind.compare( QLatin1String(
"marker" ), Qt::CaseInsensitive ) == 0 )
51 const QString stype =
string.trimmed();
55 if ( stype.compare( QLatin1String(
"Fixed" ), Qt::CaseInsensitive ) == 0 )
64 const QString rotstr =
string.trimmed();
68 if ( rotstr.compare( QLatin1String(
"Offset" ), Qt::CaseInsensitive ) == 0 )
72 else if ( rotstr.compare( QLatin1String(
"Fixed" ), Qt::CaseInsensitive ) == 0 )
81 const QString str =
string.trimmed();
85 if ( str.compare( QLatin1String(
"Text" ), Qt::CaseInsensitive ) == 0 )
89 else if ( str.compare( QLatin1String(
"Buffer" ), Qt::CaseInsensitive ) == 0 )
93 else if ( str.compare( QLatin1String(
"Background" ), Qt::CaseInsensitive ) == 0 )
102 switch ( orientation )
105 return QStringLiteral(
"horizontal" );
107 return QStringLiteral(
"vertical" );
109 return QStringLiteral(
"rotation-based" );
119 const QString cleaned = name.toLower().trimmed();
121 if ( cleaned == QLatin1String(
"horizontal" ) )
123 else if ( cleaned == QLatin1String(
"vertical" ) )
125 else if ( cleaned == QLatin1String(
"rotation-based" ) )
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 int 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 (
int 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 );
192 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle );
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 )
197 auto output = std::make_unique< CurvePlacementProperties >();
198 output->graphemePlacement.reserve( metrics.
count() );
200 double offsetAlongSegment = offsetAlongLine;
203 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
205 offsetAlongSegment -= pathDistances[index];
208 if ( index >= numPoints )
213 const double segmentLength = pathDistances[index];
220 int characterCount = metrics.
count();
226 double distance = offsetAlongSegment;
227 int endindex = index;
229 double startLabelX = 0;
230 double startLabelY = 0;
231 double endLabelX = 0;
232 double endLabelY = 0;
233 for (
int i = 0; i < characterCount; i++ )
236 double characterStartX, characterStartY;
237 if ( !nextCharPosition( characterWidth, pathDistances[endindex], x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY, flags ) )
241 characterCount = i + 1;
251 startLabelX = characterStartX;
252 startLabelY = characterStartY;
257 const double dx = endLabelX - startLabelX;
258 const double dy = endLabelY - startLabelY;
259 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
261 if ( lineAngle > 90 || lineAngle < -90 )
263 output->labeledLineSegmentIsRightToLeft =
true;
267 if ( isSecondAttempt )
271 output->labeledLineSegmentIsRightToLeft =
false;
272 output->flippedCharacterPlacementToGetUprightLabels =
true;
275 const double dx = x[index] - x[index - 1];
276 const double dy = y[index] - y[index - 1];
278 double angle = std::atan2( -dy, dx );
283 for (
int i = 0; i < characterCount; i++ )
285 const double lastCharacterAngle =
angle;
288 const double characterWidth = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterWidth( i ) : metrics.
characterWidth( characterCount - i - 1 );
293 const double characterHeight = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterHeight( i ) : metrics.
characterHeight( characterCount - i - 1 );
294 const double characterDescent = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterDescent( i ) : metrics.
characterDescent( characterCount - i - 1 );
296 double characterStartX = 0;
297 double characterStartY = 0;
298 double characterEndX = 0;
299 double characterEndY = 0;
300 if ( !nextCharPosition( characterWidth, pathDistances[index], x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY, flags ) )
304 characterCount = i + 1;
309 output->graphemePlacement.clear();
315 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
317 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
322 double angleDelta = lastCharacterAngle -
angle;
324 while ( angleDelta > M_PI )
325 angleDelta -= 2 * M_PI;
326 while ( angleDelta < -M_PI )
327 angleDelta += 2 * M_PI;
328 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
330 output->graphemePlacement.clear();
339 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
340 if ( output->flippedCharacterPlacementToGetUprightLabels )
344 characterStartX += dist * std::cos( angle + M_PI_2 );
345 characterStartY -= dist * std::sin( angle + M_PI_2 );
348 double renderAngle =
angle;
350 placement.
graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
351 placement.x = characterStartX;
352 placement.y = characterStartY;
353 placement.width = characterWidth;
354 placement.height = characterHeight;
355 if ( output->flippedCharacterPlacementToGetUprightLabels )
358 placement.x += characterWidth * std::cos( renderAngle );
359 placement.y -= characterWidth * std::sin( renderAngle );
362 placement.angle = -renderAngle;
363 output->graphemePlacement.push_back( placement );
366 while ( renderAngle >= 2 * M_PI )
367 renderAngle -= 2 * M_PI;
368 while ( renderAngle < 0 )
369 renderAngle += 2 * M_PI;
371 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
372 output->upsideDownCharCount++;
379 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true );
385bool 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,
Qgis::CurvedTextFlags flags )
394 double segmentStartX = x[index - 1];
395 double segmentStartY = y[index - 1];
397 double segmentEndX = x[index];
398 double segmentEndY = y[index];
400 const double segmentDx = segmentEndX - segmentStartX;
401 const double segmentDy = segmentEndY - segmentStartY;
403 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
404 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
410 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
413 currentDistanceAlongSegment += charWidth;
414 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
415 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
424 if ( index >= numPoints )
429 const double lastSegmentDx = segmentEndX - segmentStartX;
430 const double lastSegmentDy = segmentEndY - segmentStartY;
431 const double lastSegmentLength = std::sqrt( lastSegmentDx * lastSegmentDx + lastSegmentDy * lastSegmentDy );
438 segmentEndX = segmentStartX + ( lastSegmentDx / lastSegmentLength ) * charWidth;
439 segmentEndY = segmentStartY + ( lastSegmentDy / lastSegmentLength ) * charWidth;
450 segmentStartX = segmentEndX;
451 segmentStartY = segmentEndY;
452 segmentEndX = x[index];
453 segmentEndY = y[index];
455 while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
458 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
461 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
466void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
468 double multiplier = 1;
480 radius *= multiplier;
483 const double dx = x2 - x1;
484 const double dy = y2 - y1;
486 const double A = dx * dx + dy * dy;
487 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
490 const double det = B * B - 4 * A * C;
491 if ( A <= 0.000000000001 || det < 0 )
498 const double t = -B / ( 2 * A );
507 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
512 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.
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).