22 const QString skind =
string.trimmed();
24 if ( skind.compare( QLatin1String(
"Square" ), Qt::CaseInsensitive ) == 0 )
28 else if ( skind.compare( QLatin1String(
"Ellipse" ), Qt::CaseInsensitive ) == 0 )
32 else if ( skind.compare( QLatin1String(
"Circle" ), Qt::CaseInsensitive ) == 0 )
36 else if ( skind.compare( QLatin1String(
"SVG" ), Qt::CaseInsensitive ) == 0 )
40 else if ( skind.compare( QLatin1String(
"marker" ), Qt::CaseInsensitive ) == 0 )
49 const QString stype =
string.trimmed();
53 if ( stype.compare( QLatin1String(
"Fixed" ), Qt::CaseInsensitive ) == 0 )
62 const QString rotstr =
string.trimmed();
66 if ( rotstr.compare( QLatin1String(
"Offset" ), Qt::CaseInsensitive ) == 0 )
70 else if ( rotstr.compare( QLatin1String(
"Fixed" ), Qt::CaseInsensitive ) == 0 )
79 const QString
str =
string.trimmed();
83 if (
str.compare( QLatin1String(
"Text" ), Qt::CaseInsensitive ) == 0 )
87 else if (
str.compare( QLatin1String(
"Buffer" ), Qt::CaseInsensitive ) == 0 )
91 else if (
str.compare( QLatin1String(
"Background" ), Qt::CaseInsensitive ) == 0 )
100 switch ( orientation )
103 return QStringLiteral(
"horizontal" );
105 return QStringLiteral(
"vertical" );
107 return QStringLiteral(
"rotation-based" );
117 const QString cleaned = name.toLower().trimmed();
119 if ( cleaned == QLatin1String(
"horizontal" ) )
121 else if ( cleaned == QLatin1String(
"vertical" ) )
123 else if ( cleaned == QLatin1String(
"rotation-based" ) )
147 const int r = layer->
customProperty( property +
'R', QVariant( defaultColor.red() ) ).toInt();
148 const int g = layer->
customProperty( property +
'G', QVariant( defaultColor.green() ) ).toInt();
149 const int b = layer->
customProperty( property +
'B', QVariant( defaultColor.blue() ) ).toInt();
150 const int a = withAlpha ? layer->
customProperty( property +
'A', QVariant( defaultColor.alpha() ) ).toInt() : 255;
151 return QColor( r, g, b, a );
156 const int numPoints = line.size();
157 std::vector<double> pathDistances( numPoints );
159 const QPointF *p = line.data();
162 pathDistances[0] = 0;
163 double prevX = p->x();
164 double prevY = p->y();
167 std::vector< double > x( numPoints );
168 std::vector< double > y( numPoints );
172 for (
int i = 1; i < numPoints; ++i )
176 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
185 return generateCurvedTextPlacementPrivate( metrics, x.data(), y.data(), numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
false );
190 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle );
193std::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 )
195 std::unique_ptr< CurvePlacementProperties > output = std::make_unique< CurvePlacementProperties >();
196 output->graphemePlacement.reserve( metrics.
count() );
198 double offsetAlongSegment = offsetAlongLine;
201 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
203 offsetAlongSegment -= pathDistances[index];
206 if ( index >= numPoints )
211 const double segmentLength = pathDistances[index];
218 int characterCount = metrics.
count();
224 double distance = offsetAlongSegment;
225 int endindex = index;
227 double startLabelX = 0;
228 double startLabelY = 0;
229 double endLabelX = 0;
230 double endLabelY = 0;
231 for (
int i = 0; i < characterCount; i++ )
234 double characterStartX, characterStartY;
235 if ( !nextCharPosition( characterWidth, pathDistances[endindex], x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY ) )
239 characterCount = i + 1;
249 startLabelX = characterStartX;
250 startLabelY = characterStartY;
255 const double dx = endLabelX - startLabelX;
256 const double dy = endLabelY - startLabelY;
257 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
259 if ( lineAngle > 90 || lineAngle < -90 )
261 output->labeledLineSegmentIsRightToLeft =
true;
265 if ( isSecondAttempt )
269 output->labeledLineSegmentIsRightToLeft =
false;
270 output->flippedCharacterPlacementToGetUprightLabels =
true;
273 const double dx = x[index] - x[index - 1];
274 const double dy = y[index] - y[index - 1];
276 double angle = std::atan2( -dy, dx );
281 for (
int i = 0; i < characterCount; i++ )
283 const double lastCharacterAngle =
angle;
286 const double characterWidth = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterWidth( i ) : metrics.
characterWidth( characterCount - i - 1 );
291 const double characterHeight = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterHeight( i ) : metrics.
characterHeight( characterCount - i - 1 );
292 const double characterDescent = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterDescent( i ) : metrics.
characterDescent( characterCount - i - 1 );
294 double characterStartX = 0;
295 double characterStartY = 0;
296 double characterEndX = 0;
297 double characterEndY = 0;
298 if ( !nextCharPosition( characterWidth, pathDistances[index], x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY ) )
302 characterCount = i + 1;
307 output->graphemePlacement.clear();
313 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
315 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
320 double angleDelta = lastCharacterAngle -
angle;
322 while ( angleDelta > M_PI )
323 angleDelta -= 2 * M_PI;
324 while ( angleDelta < -M_PI )
325 angleDelta += 2 * M_PI;
326 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
328 output->graphemePlacement.clear();
337 double dist = 0.9 * maxCharacterHeight / 2 - ( maxCharacterDescent - characterDescent );
338 if ( output->flippedCharacterPlacementToGetUprightLabels )
342 characterStartX += dist * std::cos( angle + M_PI_2 );
343 characterStartY -= dist * std::sin( angle + M_PI_2 );
346 double renderAngle =
angle;
347 CurvedGraphemePlacement placement;
348 placement.graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
349 placement.x = characterStartX;
350 placement.y = characterStartY;
351 placement.width = characterWidth;
352 placement.height = characterHeight;
353 if ( output->flippedCharacterPlacementToGetUprightLabels )
356 placement.x += characterWidth * std::cos( renderAngle );
357 placement.y -= characterWidth * std::sin( renderAngle );
360 placement.angle = -renderAngle;
361 output->graphemePlacement.push_back( placement );
364 while ( renderAngle >= 2 * M_PI )
365 renderAngle -= 2 * M_PI;
366 while ( renderAngle < 0 )
367 renderAngle += 2 * M_PI;
369 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
370 output->upsideDownCharCount++;
377 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, flags, maxConcaveAngle, maxConvexAngle,
true );
383bool 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 )
392 double segmentStartX = x[index - 1];
393 double segmentStartY = y[index - 1];
395 double segmentEndX = x[index];
396 double segmentEndY = y[index];
398 const double segmentDx = segmentEndX - segmentStartX;
399 const double segmentDy = segmentEndY - segmentStartY;
401 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
402 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
408 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
411 currentDistanceAlongSegment += charWidth;
412 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
413 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
421 segmentStartX = segmentEndX;
422 segmentStartY = segmentEndY;
424 if ( index >= numPoints )
428 segmentEndX = x[index];
429 segmentEndY = y[index];
431 while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
434 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
437 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
442void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
444 double multiplier = 1;
456 radius *= multiplier;
459 const double dx = x2 - x1;
460 const double dy = y2 - y1;
462 const double A = dx * dx + dy * dy;
463 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
464 const double C = ( x1 - cx ) * ( x1 - cx ) + ( y1 - cy ) * ( y1 - cy ) - radius * radius;
466 const double det = B * B - 4 * A * C;
467 if ( A <= 0.000000000001 || det < 0 )
474 const double t = -B / ( 2 * A );
483 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
488 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)
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.
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())
Flags controlling behavior of curved text generation.
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)