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 );
159 std::vector<double> pathDistances( numPoints );
161 const double *x = line->
xData();
162 const double *y = line->
yData();
165 pathDistances[0] = 0;
169 for (
int i = 1; i < numPoints; ++i )
173 pathDistances[i] = std::sqrt( dx * dx + dy * dy );
179 return generateCurvedTextPlacementPrivate( metrics, line->
xData(), line->
yData(), numPoints, pathDistances, offsetAlongLine, direction, maxConcaveAngle, maxConvexAngle, uprightOnly );
185 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, maxConcaveAngle, maxConvexAngle, uprightOnly );
188 QgsTextRendererUtils::CurvePlacementProperties *QgsTextRendererUtils::generateCurvedTextPlacementPrivate(
const QgsPrecalculatedTextMetrics &metrics,
const double *x,
const double *y,
int numPoints,
const std::vector<double> &pathDistances,
double offsetAlongLine, LabelLineDirection direction,
double maxConcaveAngle,
double maxConvexAngle,
bool uprightOnly,
bool isSecondAttempt )
190 std::unique_ptr< CurvePlacementProperties > output = std::make_unique< CurvePlacementProperties >();
191 output->graphemePlacement.reserve( metrics.
count() );
193 double offsetAlongSegment = offsetAlongLine;
196 while ( index < numPoints && offsetAlongSegment > pathDistances[index] )
198 offsetAlongSegment -= pathDistances[index];
201 if ( index >= numPoints )
203 return output.release();
208 const double segmentLength = pathDistances[index];
212 return output.release();
215 const int characterCount = metrics.
count();
221 double distance = offsetAlongSegment;
222 int endindex = index;
224 double startLabelX = 0;
225 double startLabelY = 0;
226 double endLabelX = 0;
227 double endLabelY = 0;
228 for (
int i = 0; i < characterCount; i++ )
231 double characterStartX, characterStartY;
232 if ( !nextCharPosition( characterWidth, pathDistances[endindex], x, y, numPoints, endindex, distance, characterStartX, characterStartY, endLabelX, endLabelY ) )
234 return output.release();
238 startLabelX = characterStartX;
239 startLabelY = characterStartY;
244 const double dx = endLabelX - startLabelX;
245 const double dy = endLabelY - startLabelY;
246 const double lineAngle = std::atan2( -dy, dx ) * 180 / M_PI;
248 if ( lineAngle > 90 || lineAngle < -90 )
250 output->labeledLineSegmentIsRightToLeft =
true;
254 if ( isSecondAttempt )
258 output->labeledLineSegmentIsRightToLeft =
false;
259 output->flippedCharacterPlacementToGetUprightLabels =
true;
262 const double dx = x[index] - x[index - 1];
263 const double dy = y[index] - y[index - 1];
265 double angle = std::atan2( -dy, dx );
267 for (
int i = 0; i < characterCount; i++ )
269 const double lastCharacterAngle =
angle;
272 const double characterWidth = !output->flippedCharacterPlacementToGetUprightLabels ? metrics.
characterWidth( i ) : metrics.
characterWidth( characterCount - i - 1 );
277 double characterStartX = 0;
278 double characterStartY = 0;
279 double characterEndX = 0;
280 double characterEndY = 0;
281 if ( !nextCharPosition( characterWidth, pathDistances[index], x, y, numPoints, index, offsetAlongSegment, characterStartX, characterStartY, characterEndX, characterEndY ) )
283 output->graphemePlacement.clear();
284 return output.release();
288 angle = std::atan2( characterStartY - characterEndY, characterEndX - characterStartX );
290 if ( maxConcaveAngle >= 0 || maxConvexAngle >= 0 )
295 double angleDelta = lastCharacterAngle -
angle;
297 while ( angleDelta > M_PI )
298 angleDelta -= 2 * M_PI;
299 while ( angleDelta < -M_PI )
300 angleDelta += 2 * M_PI;
301 if ( ( maxConcaveAngle >= 0 && angleDelta > 0 && angleDelta > maxConcaveAngle ) || ( maxConvexAngle >= 0 && angleDelta < 0 && angleDelta < -maxConvexAngle ) )
303 output->graphemePlacement.clear();
304 return output.release();
311 if ( output->flippedCharacterPlacementToGetUprightLabels )
315 characterStartX += dist * std::cos(
angle + M_PI_2 );
316 characterStartY -= dist * std::sin(
angle + M_PI_2 );
318 double renderAngle =
angle;
319 CurvedGraphemePlacement placement;
320 placement.graphemeIndex = !output->flippedCharacterPlacementToGetUprightLabels ? i : characterCount - i - 1;
321 placement.x = characterStartX;
322 placement.y = characterStartY;
323 placement.width = characterWidth;
324 placement.height = characterHeight;
325 if ( output->flippedCharacterPlacementToGetUprightLabels )
328 placement.x += characterWidth * std::cos( renderAngle );
329 placement.y -= characterWidth * std::sin( renderAngle );
332 placement.angle = -renderAngle;
333 output->graphemePlacement.push_back( placement );
336 while ( renderAngle >= 2 * M_PI )
337 renderAngle -= 2 * M_PI;
338 while ( renderAngle < 0 )
339 renderAngle += 2 * M_PI;
341 if ( renderAngle > M_PI_2 && renderAngle < 1.5 * M_PI )
342 output->upsideDownCharCount++;
345 if ( !isSecondAttempt && uprightOnly && output->upsideDownCharCount >= characterCount / 2.0 )
349 return generateCurvedTextPlacementPrivate( metrics, x, y, numPoints, pathDistances, offsetAlongLine, direction, maxConcaveAngle, maxConvexAngle, uprightOnly,
true );
352 return output.release();
355 bool 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 )
364 double segmentStartX = x[index - 1];
365 double segmentStartY = y[index - 1];
367 double segmentEndX = x[index];
368 double segmentEndY = y[index];
370 const double segmentDx = segmentEndX - segmentStartX;
371 const double segmentDy = segmentEndY - segmentStartY;
373 characterStartX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
374 characterStartY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
380 if ( segmentLength - currentDistanceAlongSegment >= charWidth )
383 currentDistanceAlongSegment += charWidth;
384 characterEndX = segmentStartX + segmentDx * currentDistanceAlongSegment / segmentLength;
385 characterEndY = segmentStartY + segmentDy * currentDistanceAlongSegment / segmentLength;
393 segmentStartX = segmentEndX;
394 segmentStartY = segmentEndY;
396 if ( index >= numPoints )
400 segmentEndX = x[index];
401 segmentEndY = y[index];
403 while ( std::sqrt( std::pow( characterStartX - segmentEndX, 2 ) + std::pow( characterStartY - segmentEndY, 2 ) ) < charWidth );
406 findLineCircleIntersection( characterStartX, characterStartY, charWidth, segmentStartX, segmentStartY, segmentEndX, segmentEndY, characterEndX, characterEndY );
409 currentDistanceAlongSegment = std::sqrt( std::pow( segmentStartX - characterEndX, 2 ) + std::pow( segmentStartY - characterEndY, 2 ) );
414 void QgsTextRendererUtils::findLineCircleIntersection(
double cx,
double cy,
double radius,
double x1,
double y1,
double x2,
double y2,
double &xRes,
double &yRes )
416 double multiplier = 1;
428 radius *= multiplier;
431 const double dx = x2 - x1;
432 const double dy = y2 - y1;
434 const double A = dx * dx + dy * dy;
435 const double B = 2 * ( dx * ( x1 - cx ) + dy * ( y1 - cy ) );
436 const double C = ( x1 - cx ) * ( x1 - cx ) + ( y1 - cy ) * ( y1 - cy ) - radius * radius;
438 const double det = B * B - 4 * A * C;
439 if ( A <= 0.000000000001 || det < 0 )
446 const double t = -B / ( 2 * A );
455 const double t = ( -B + std::sqrt( det ) ) / ( 2 * A );
460 if ( multiplier != 1 )