Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgstextrenderer.cpp
3 : : -------------------
4 : : begin : September 2015
5 : : copyright : (C) Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #include "qgstextrenderer.h"
17 : : #include "qgsvectorlayer.h"
18 : : #include "qgstextformat.h"
19 : : #include "qgstextdocument.h"
20 : : #include "qgstextfragment.h"
21 : : #include "qgspallabeling.h"
22 : : #include "qgspainteffect.h"
23 : : #include "qgspainterswapper.h"
24 : : #include "qgsmarkersymbollayer.h"
25 : : #include "qgssymbollayerutils.h"
26 : :
27 : : #include <QTextBoundaryFinder>
28 : :
29 : : Q_GUI_EXPORT extern int qt_defaultDpiX();
30 : : Q_GUI_EXPORT extern int qt_defaultDpiY();
31 : :
32 : 0 : static void _fixQPictureDPI( QPainter *p )
33 : : {
34 : : // QPicture makes an assumption that we drawing to it with system DPI.
35 : : // Then when being drawn, it scales the painter. The following call
36 : : // negates the effect. There is no way of setting QPicture's DPI.
37 : : // See QTBUG-20361
38 : 0 : p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
39 : 0 : static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
40 : 0 : }
41 : :
42 : 0 : QgsTextRenderer::HAlignment QgsTextRenderer::convertQtHAlignment( Qt::Alignment alignment )
43 : : {
44 : 0 : if ( alignment & Qt::AlignLeft )
45 : 0 : return AlignLeft;
46 : 0 : else if ( alignment & Qt::AlignRight )
47 : 0 : return AlignRight;
48 : 0 : else if ( alignment & Qt::AlignHCenter )
49 : 0 : return AlignCenter;
50 : 0 : else if ( alignment & Qt::AlignJustify )
51 : 0 : return AlignJustify;
52 : :
53 : : // not supported?
54 : 0 : return AlignLeft;
55 : 0 : }
56 : :
57 : 0 : QgsTextRenderer::VAlignment QgsTextRenderer::convertQtVAlignment( Qt::Alignment alignment )
58 : : {
59 : 0 : if ( alignment & Qt::AlignTop )
60 : 0 : return AlignTop;
61 : 0 : else if ( alignment & Qt::AlignBottom )
62 : 0 : return AlignBottom;
63 : 0 : else if ( alignment & Qt::AlignVCenter )
64 : 0 : return AlignVCenter;
65 : : //not supported
66 : 0 : else if ( alignment & Qt::AlignBaseline )
67 : 0 : return AlignBottom;
68 : :
69 : 0 : return AlignTop;
70 : 0 : }
71 : :
72 : 0 : int QgsTextRenderer::sizeToPixel( double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale )
73 : : {
74 : 0 : return static_cast< int >( c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 ); //NOLINT
75 : : }
76 : :
77 : 0 : void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool, VAlignment vAlignment )
78 : : {
79 : 0 : QgsTextFormat tmpFormat = format;
80 : 0 : if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
81 : 0 : tmpFormat.updateDataDefinedProperties( context );
82 : 0 : tmpFormat = updateShadowPosition( tmpFormat );
83 : :
84 : 0 : QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
85 : 0 : document.applyCapitalization( format.capitalization() );
86 : :
87 : 0 : if ( tmpFormat.background().enabled() )
88 : : {
89 : 0 : drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Background );
90 : 0 : }
91 : :
92 : 0 : if ( tmpFormat.buffer().enabled() )
93 : : {
94 : 0 : drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Buffer );
95 : 0 : }
96 : :
97 : 0 : drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Text );
98 : 0 : }
99 : :
100 : 0 : void QgsTextRenderer::drawText( QPointF point, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool )
101 : : {
102 : 0 : QgsTextFormat tmpFormat = format;
103 : 0 : if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
104 : 0 : tmpFormat.updateDataDefinedProperties( context );
105 : 0 : tmpFormat = updateShadowPosition( tmpFormat );
106 : :
107 : 0 : QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
108 : 0 : document.applyCapitalization( format.capitalization() );
109 : :
110 : 0 : if ( tmpFormat.background().enabled() )
111 : : {
112 : 0 : drawPart( point, rotation, alignment, document, context, tmpFormat, Background );
113 : 0 : }
114 : :
115 : 0 : if ( tmpFormat.buffer().enabled() )
116 : : {
117 : 0 : drawPart( point, rotation, alignment, document, context, tmpFormat, Buffer );
118 : 0 : }
119 : :
120 : 0 : drawPart( point, rotation, alignment, document, context, tmpFormat, Text );
121 : 0 : }
122 : :
123 : 0 : QgsTextFormat QgsTextRenderer::updateShadowPosition( const QgsTextFormat &format )
124 : : {
125 : 0 : if ( !format.shadow().enabled() || format.shadow().shadowPlacement() != QgsTextShadowSettings::ShadowLowest )
126 : 0 : return format;
127 : :
128 : 0 : QgsTextFormat tmpFormat = format;
129 : 0 : if ( tmpFormat.background().enabled() && tmpFormat.background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadow not compatible with marker symbol backgrounds
130 : : {
131 : 0 : tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
132 : 0 : }
133 : 0 : else if ( tmpFormat.buffer().enabled() )
134 : : {
135 : 0 : tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowBuffer );
136 : 0 : }
137 : : else
138 : : {
139 : 0 : tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowText );
140 : : }
141 : 0 : return tmpFormat;
142 : 0 : }
143 : :
144 : 0 : void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, HAlignment alignment,
145 : : const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
146 : : {
147 : 0 : const QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
148 : :
149 : 0 : drawPart( rect, rotation, alignment, AlignTop, document, context, format, part );
150 : 0 : }
151 : :
152 : 0 : void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, VAlignment vAlignment, const QgsTextDocument &document, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part )
153 : : {
154 : 0 : if ( !context.painter() )
155 : : {
156 : 0 : return;
157 : : }
158 : :
159 : 0 : Component component;
160 : 0 : component.dpiRatio = 1.0;
161 : 0 : component.origin = rect.topLeft();
162 : 0 : component.rotation = rotation;
163 : 0 : component.size = rect.size();
164 : 0 : component.hAlign = alignment;
165 : :
166 : 0 : switch ( part )
167 : : {
168 : : case Background:
169 : : {
170 : 0 : if ( !format.background().enabled() )
171 : 0 : return;
172 : :
173 : 0 : if ( !qgsDoubleNear( rotation, 0.0 ) )
174 : : {
175 : : // get rotated label's center point
176 : :
177 : 0 : double xc = rect.width() / 2.0;
178 : 0 : double yc = rect.height() / 2.0;
179 : :
180 : 0 : double angle = -rotation;
181 : 0 : double xd = xc * std::cos( angle ) - yc * std::sin( angle );
182 : 0 : double yd = xc * std::sin( angle ) + yc * std::cos( angle );
183 : :
184 : 0 : component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd );
185 : 0 : }
186 : : else
187 : : {
188 : 0 : component.center = rect.center();
189 : : }
190 : :
191 : 0 : QgsTextRenderer::drawBackground( context, component, format, document, Rect );
192 : :
193 : 0 : break;
194 : : }
195 : :
196 : : case Buffer:
197 : : {
198 : 0 : if ( !format.buffer().enabled() )
199 : 0 : break;
200 : 0 : }
201 : : FALLTHROUGH
202 : : case Text:
203 : : case Shadow:
204 : : {
205 : 0 : drawTextInternal( part, context, format, component,
206 : 0 : document,
207 : : nullptr,
208 : 0 : alignment, vAlignment );
209 : 0 : break;
210 : : }
211 : : }
212 : 0 : }
213 : :
214 : 0 : void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
215 : : {
216 : 0 : const QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
217 : 0 : drawPart( origin, rotation, alignment, document, context, format, part );
218 : 0 : }
219 : :
220 : 0 : void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QgsTextDocument &document, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part )
221 : : {
222 : 0 : if ( !context.painter() )
223 : : {
224 : 0 : return;
225 : : }
226 : :
227 : 0 : Component component;
228 : 0 : component.dpiRatio = 1.0;
229 : 0 : component.origin = origin;
230 : 0 : component.rotation = rotation;
231 : 0 : component.hAlign = alignment;
232 : :
233 : 0 : switch ( part )
234 : : {
235 : : case Background:
236 : : {
237 : 0 : if ( !format.background().enabled() )
238 : 0 : return;
239 : :
240 : 0 : QgsTextRenderer::drawBackground( context, component, format, document, Point );
241 : 0 : break;
242 : : }
243 : :
244 : : case Buffer:
245 : : {
246 : 0 : if ( !format.buffer().enabled() )
247 : 0 : break;
248 : 0 : }
249 : : FALLTHROUGH
250 : : case Text:
251 : : case Shadow:
252 : : {
253 : 0 : drawTextInternal( part, context, format, component,
254 : 0 : document,
255 : : nullptr,
256 : 0 : alignment, AlignTop,
257 : : Point );
258 : 0 : break;
259 : : }
260 : : }
261 : 0 : }
262 : :
263 : 0 : QFontMetricsF QgsTextRenderer::fontMetrics( QgsRenderContext &context, const QgsTextFormat &format, const double scaleFactor )
264 : : {
265 : 0 : return QFontMetricsF( format.scaledFont( context, scaleFactor ), context.painter() ? context.painter()->device() : nullptr );
266 : 0 : }
267 : :
268 : 0 : double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
269 : : {
270 : 0 : QPainter *p = context.painter();
271 : :
272 : 0 : QgsTextFormat::TextOrientation orientation = format.orientation();
273 : 0 : if ( format.orientation() == QgsTextFormat::RotationBasedOrientation )
274 : : {
275 : 0 : if ( component.rotation >= -315 && component.rotation < -90 )
276 : : {
277 : 0 : orientation = QgsTextFormat::VerticalOrientation;
278 : 0 : }
279 : 0 : else if ( component.rotation >= -90 && component.rotation < -45 )
280 : : {
281 : 0 : orientation = QgsTextFormat::VerticalOrientation;
282 : 0 : }
283 : : else
284 : : {
285 : 0 : orientation = QgsTextFormat::HorizontalOrientation;
286 : : }
287 : 0 : }
288 : :
289 : 0 : QgsTextBufferSettings buffer = format.buffer();
290 : :
291 : 0 : const double penSize = context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() );
292 : :
293 : 0 : const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
294 : 0 : const QFont font = format.scaledFont( context, scaleFactor );
295 : :
296 : 0 : QPainterPath path;
297 : 0 : path.setFillRule( Qt::WindingFill );
298 : 0 : double advance = 0;
299 : 0 : switch ( orientation )
300 : : {
301 : : case QgsTextFormat::HorizontalOrientation:
302 : : {
303 : 0 : double xOffset = 0;
304 : 0 : for ( const QgsTextFragment &fragment : component.block )
305 : : {
306 : 0 : QFont fragmentFont = font;
307 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
308 : :
309 : 0 : if ( component.extraWordSpacing || component.extraLetterSpacing )
310 : 0 : applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing );
311 : :
312 : 0 : path.addText( xOffset, 0, fragmentFont, fragment.text() );
313 : :
314 : 0 : xOffset += fragment.horizontalAdvance( fragmentFont, true, scaleFactor );
315 : 0 : }
316 : 0 : advance = xOffset;
317 : 0 : break;
318 : : }
319 : :
320 : : case QgsTextFormat::VerticalOrientation:
321 : : case QgsTextFormat::RotationBasedOrientation:
322 : : {
323 : 0 : double letterSpacing = font.letterSpacing();
324 : 0 : double partYOffset = component.offset.y() * scaleFactor;
325 : 0 : for ( const QgsTextFragment &fragment : component.block )
326 : : {
327 : 0 : QFont fragmentFont = font;
328 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
329 : :
330 : 0 : QFontMetricsF fragmentMetrics( fragmentFont );
331 : 0 : const double labelWidth = fragmentMetrics.maxWidth();
332 : :
333 : 0 : const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
334 : 0 : for ( const QString &part : parts )
335 : : {
336 : 0 : double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) - letterSpacing ) ) / 2;
337 : 0 : path.addText( partXOffset, partYOffset, fragmentFont, part );
338 : 0 : partYOffset += fragmentMetrics.ascent() + letterSpacing;
339 : : }
340 : 0 : }
341 : 0 : advance = partYOffset - component.offset.y() * scaleFactor;
342 : 0 : break;
343 : : }
344 : : }
345 : :
346 : 0 : QColor bufferColor = buffer.color();
347 : 0 : bufferColor.setAlphaF( buffer.opacity() );
348 : 0 : QPen pen( bufferColor );
349 : 0 : pen.setWidthF( penSize * scaleFactor );
350 : 0 : pen.setJoinStyle( buffer.joinStyle() );
351 : 0 : QColor tmpColor( bufferColor );
352 : : // honor pref for whether to fill buffer interior
353 : 0 : if ( !buffer.fillBufferInterior() )
354 : : {
355 : 0 : tmpColor.setAlpha( 0 );
356 : 0 : }
357 : :
358 : : // store buffer's drawing in QPicture for drop shadow call
359 : 0 : QPicture buffPict;
360 : 0 : QPainter buffp;
361 : 0 : buffp.begin( &buffPict );
362 : 0 : if ( buffer.paintEffect() && buffer.paintEffect()->enabled() )
363 : : {
364 : 0 : context.setPainter( &buffp );
365 : 0 : std::unique_ptr< QgsPaintEffect > tmpEffect( buffer.paintEffect()->clone() );
366 : :
367 : 0 : tmpEffect->begin( context );
368 : 0 : context.painter()->setPen( pen );
369 : 0 : context.painter()->setBrush( tmpColor );
370 : 0 : if ( scaleFactor != 1.0 )
371 : 0 : context.painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
372 : 0 : context.painter()->drawPath( path );
373 : 0 : if ( scaleFactor != 1.0 )
374 : 0 : context.painter()->scale( scaleFactor, scaleFactor );
375 : 0 : tmpEffect->end( context );
376 : :
377 : 0 : context.setPainter( p );
378 : 0 : }
379 : : else
380 : : {
381 : 0 : if ( scaleFactor != 1.0 )
382 : 0 : buffp.scale( 1 / scaleFactor, 1 / scaleFactor );
383 : 0 : buffp.setPen( pen );
384 : 0 : buffp.setBrush( tmpColor );
385 : 0 : buffp.drawPath( path );
386 : : }
387 : 0 : buffp.end();
388 : :
389 : 0 : if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer )
390 : : {
391 : 0 : QgsTextRenderer::Component bufferComponent = component;
392 : 0 : bufferComponent.origin = QPointF( 0.0, 0.0 );
393 : 0 : bufferComponent.picture = buffPict;
394 : 0 : bufferComponent.pictureBuffer = penSize / 2.0;
395 : :
396 : 0 : if ( format.orientation() == QgsTextFormat::VerticalOrientation || format.orientation() == QgsTextFormat::RotationBasedOrientation )
397 : : {
398 : 0 : bufferComponent.offset.setY( bufferComponent.offset.y() - bufferComponent.size.height() );
399 : 0 : }
400 : 0 : drawShadow( context, bufferComponent, format );
401 : 0 : }
402 : :
403 : 0 : QgsScopedQPainterState painterState( p );
404 : 0 : context.setPainterFlagsUsingContext( p );
405 : :
406 : 0 : if ( context.useAdvancedEffects() )
407 : : {
408 : 0 : p->setCompositionMode( buffer.blendMode() );
409 : 0 : }
410 : :
411 : : // scale for any print output or image saving @ specific dpi
412 : 0 : p->scale( component.dpiRatio, component.dpiRatio );
413 : 0 : _fixQPictureDPI( p );
414 : 0 : p->drawPicture( 0, 0, buffPict );
415 : :
416 : 0 : return advance / scaleFactor;
417 : 0 : }
418 : :
419 : 0 : void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
420 : : {
421 : 0 : QgsTextMaskSettings mask = format.mask();
422 : :
423 : : // the mask is drawn to a side painter
424 : : // or to the main painter for preview
425 : 0 : QPainter *p = context.isGuiPreview() ? context.painter() : context.maskPainter( context.currentMaskId() );
426 : 0 : if ( ! p )
427 : 0 : return;
428 : :
429 : 0 : double penSize = context.convertToPainterUnits( mask.size(), mask.sizeUnit(), mask.sizeMapUnitScale() );
430 : :
431 : : // buffer: draw the text with a big pen
432 : 0 : QPainterPath path;
433 : 0 : path.setFillRule( Qt::WindingFill );
434 : :
435 : 0 : const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
436 : :
437 : : // TODO: vertical text mode was ignored when masking feature was added.
438 : : // Hopefully Oslandia come back and fix this? Hint hint...
439 : :
440 : 0 : const QFont font = format.scaledFont( context, scaleFactor );
441 : 0 : double xOffset = 0;
442 : 0 : for ( const QgsTextFragment &fragment : component.block )
443 : : {
444 : 0 : QFont fragmentFont = font;
445 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
446 : :
447 : 0 : path.addText( xOffset, 0, fragmentFont, fragment.text() );
448 : :
449 : 0 : xOffset += fragment.horizontalAdvance( fragmentFont, true );
450 : 0 : }
451 : :
452 : 0 : QColor bufferColor( Qt::gray );
453 : 0 : bufferColor.setAlphaF( mask.opacity() );
454 : :
455 : 0 : QPen pen;
456 : 0 : QBrush brush;
457 : 0 : brush.setColor( bufferColor );
458 : 0 : pen.setColor( bufferColor );
459 : 0 : pen.setWidthF( penSize * scaleFactor );
460 : 0 : pen.setJoinStyle( mask.joinStyle() );
461 : :
462 : 0 : QgsScopedQPainterState painterState( p );
463 : 0 : context.setPainterFlagsUsingContext( p );
464 : :
465 : : // scale for any print output or image saving @ specific dpi
466 : 0 : p->scale( component.dpiRatio, component.dpiRatio );
467 : 0 : if ( mask.paintEffect() && mask.paintEffect()->enabled() )
468 : : {
469 : 0 : QgsPainterSwapper swapper( context, p );
470 : : {
471 : 0 : QgsEffectPainter effectPainter( context, mask.paintEffect() );
472 : 0 : if ( scaleFactor != 1.0 )
473 : 0 : context.painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
474 : 0 : context.painter()->setPen( pen );
475 : 0 : context.painter()->setBrush( brush );
476 : 0 : context.painter()->drawPath( path );
477 : 0 : if ( scaleFactor != 1.0 )
478 : 0 : context.painter()->scale( scaleFactor, scaleFactor );
479 : 0 : }
480 : 0 : }
481 : : else
482 : : {
483 : 0 : if ( scaleFactor != 1.0 )
484 : 0 : p->scale( 1 / scaleFactor, 1 / scaleFactor );
485 : 0 : p->setPen( pen );
486 : 0 : p->setBrush( brush );
487 : 0 : p->drawPath( path );
488 : 0 : if ( scaleFactor != 1.0 )
489 : 0 : p->scale( scaleFactor, scaleFactor );
490 : :
491 : : }
492 : 0 : }
493 : :
494 : 0 : double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF * )
495 : : {
496 : 0 : QgsTextDocument doc;
497 : 0 : if ( !format.allowHtmlFormatting() )
498 : : {
499 : 0 : doc = QgsTextDocument::fromPlainText( textLines );
500 : 0 : }
501 : : else
502 : : {
503 : 0 : doc = QgsTextDocument::fromHtml( textLines );
504 : : }
505 : 0 : doc.applyCapitalization( format.capitalization() );
506 : 0 : return textWidth( context, format, doc );
507 : 0 : }
508 : :
509 : 0 : double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document )
510 : : {
511 : : //calculate max width of text lines
512 : 0 : const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
513 : 0 : const QFont baseFont = format.scaledFont( context, scaleFactor );
514 : :
515 : 0 : double width = 0;
516 : 0 : switch ( format.orientation() )
517 : : {
518 : : case QgsTextFormat::HorizontalOrientation:
519 : : {
520 : 0 : double maxLineWidth = 0;
521 : 0 : for ( const QgsTextBlock &block : document )
522 : : {
523 : 0 : double blockWidth = 0;
524 : 0 : for ( const QgsTextFragment &fragment : block )
525 : : {
526 : 0 : blockWidth += fragment.horizontalAdvance( baseFont, scaleFactor );
527 : : }
528 : 0 : maxLineWidth = std::max( maxLineWidth, blockWidth );
529 : : }
530 : 0 : width = maxLineWidth;
531 : 0 : break;
532 : : }
533 : :
534 : : case QgsTextFormat::VerticalOrientation:
535 : : {
536 : 0 : double totalLineWidth = 0;
537 : 0 : int blockIndex = 0;
538 : 0 : for ( const QgsTextBlock &block : document )
539 : : {
540 : 0 : double blockWidth = 0;
541 : 0 : for ( const QgsTextFragment &fragment : block )
542 : : {
543 : 0 : QFont fragmentFont = baseFont;
544 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
545 : 0 : blockWidth = std::max( QFontMetricsF( fragmentFont ).maxWidth(), blockWidth );
546 : 0 : }
547 : :
548 : 0 : totalLineWidth += blockIndex == 0 ? blockWidth : blockWidth * format.lineHeight();
549 : 0 : blockIndex++;
550 : : }
551 : 0 : width = totalLineWidth;
552 : 0 : break;
553 : : }
554 : :
555 : : case QgsTextFormat::RotationBasedOrientation:
556 : : {
557 : : // label mode only
558 : 0 : break;
559 : : }
560 : : }
561 : :
562 : 0 : return width / scaleFactor;
563 : 0 : }
564 : :
565 : 0 : double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF * )
566 : : {
567 : 0 : if ( !format.allowHtmlFormatting() )
568 : : {
569 : 0 : return textHeight( context, format, QgsTextDocument::fromPlainText( textLines ), mode );
570 : : }
571 : : else
572 : : {
573 : 0 : return textHeight( context, format, QgsTextDocument::fromHtml( textLines ), mode );
574 : : }
575 : 0 : }
576 : :
577 : 0 : double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects )
578 : : {
579 : 0 : const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
580 : 0 : const QFont baseFont = format.scaledFont( context, scaleFactor );
581 : 0 : const QFontMetrics fm( baseFont );
582 : 0 : const double height = ( character.isNull() ? fm.height() : fm.boundingRect( character ).height() ) / scaleFactor;
583 : :
584 : 0 : if ( !includeEffects )
585 : 0 : return height;
586 : :
587 : 0 : double maxExtension = 0;
588 : 0 : if ( format.buffer().enabled() )
589 : : {
590 : 0 : maxExtension += context.convertToPainterUnits( format.buffer().size(), format.buffer().sizeUnit(), format.buffer().sizeMapUnitScale() );
591 : 0 : }
592 : 0 : if ( format.shadow().enabled() )
593 : : {
594 : 0 : maxExtension += context.convertToPainterUnits( format.shadow().offsetDistance(), format.shadow().offsetUnit(), format.shadow().offsetMapUnitScale() )
595 : 0 : + context.convertToPainterUnits( format.shadow().blurRadius(), format.shadow().blurRadiusUnit(), format.shadow().blurRadiusMapUnitScale() );
596 : 0 : }
597 : 0 : if ( format.background().enabled() )
598 : : {
599 : 0 : maxExtension += context.convertToPainterUnits( std::fabs( format.background().offset().y() ), format.background().offsetUnit(), format.background().offsetMapUnitScale() )
600 : 0 : + context.convertToPainterUnits( format.background().strokeWidth(), format.background().strokeWidthUnit(), format.background().strokeWidthMapUnitScale() ) / 2.0;
601 : 0 : if ( format.background().sizeType() == QgsTextBackgroundSettings::SizeBuffer && format.background().size().height() > 0 )
602 : : {
603 : 0 : maxExtension += context.convertToPainterUnits( format.background().size().height(), format.background().sizeUnit(), format.background().sizeMapUnitScale() );
604 : 0 : }
605 : 0 : }
606 : :
607 : 0 : return height + maxExtension;
608 : 0 : }
609 : :
610 : 0 : double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &doc, DrawMode mode )
611 : : {
612 : 0 : QgsTextDocument document = doc;
613 : 0 : document.applyCapitalization( format.capitalization() );
614 : :
615 : : //calculate max height of text lines
616 : 0 : const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
617 : :
618 : 0 : const QFont baseFont = format.scaledFont( context, scaleFactor );
619 : :
620 : 0 : switch ( format.orientation() )
621 : : {
622 : : case QgsTextFormat::HorizontalOrientation:
623 : : {
624 : 0 : int blockIndex = 0;
625 : 0 : double totalHeight = 0;
626 : 0 : double lastLineLeading = 0;
627 : 0 : for ( const QgsTextBlock &block : document )
628 : : {
629 : 0 : double maxBlockHeight = 0;
630 : 0 : double maxBlockLineSpacing = 0;
631 : 0 : double maxBlockLeading = 0;
632 : 0 : for ( const QgsTextFragment &fragment : block )
633 : : {
634 : 0 : QFont fragmentFont = baseFont;
635 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
636 : 0 : const QFontMetricsF fm( fragmentFont );
637 : :
638 : 0 : const double fragmentHeight = fm.ascent() + fm.descent(); // ignore +1 for baseline
639 : :
640 : 0 : maxBlockHeight = std::max( maxBlockHeight, fragmentHeight );
641 : 0 : if ( fm.lineSpacing() > maxBlockLineSpacing )
642 : : {
643 : 0 : maxBlockLineSpacing = fm.lineSpacing();
644 : 0 : maxBlockLeading = fm.leading();
645 : 0 : }
646 : 0 : }
647 : :
648 : 0 : switch ( mode )
649 : : {
650 : : case Label:
651 : : // rendering labels needs special handling - in this case text should be
652 : : // drawn with the bottom left corner coinciding with origin, vs top left
653 : : // for standard text rendering. Line height is also slightly different.
654 : 0 : totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockHeight * format.lineHeight();
655 : 0 : break;
656 : :
657 : : case Rect:
658 : : case Point:
659 : : // standard rendering - designed to exactly replicate QPainter's drawText method
660 : 0 : totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockLineSpacing * format.lineHeight();
661 : 0 : if ( blockIndex > 0 )
662 : 0 : lastLineLeading = maxBlockLeading;
663 : 0 : break;
664 : : }
665 : :
666 : 0 : blockIndex++;
667 : : }
668 : :
669 : 0 : return ( totalHeight - lastLineLeading ) / scaleFactor;
670 : : }
671 : :
672 : : case QgsTextFormat::VerticalOrientation:
673 : : {
674 : 0 : double maxBlockHeight = 0;
675 : 0 : for ( const QgsTextBlock &block : document )
676 : : {
677 : 0 : double blockHeight = 0;
678 : 0 : int fragmentIndex = 0;
679 : 0 : for ( const QgsTextFragment &fragment : block )
680 : : {
681 : 0 : QFont fragmentFont = baseFont;
682 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
683 : 0 : const QFontMetricsF fm( fragmentFont );
684 : :
685 : 0 : const double labelHeight = fm.ascent();
686 : 0 : const double letterSpacing = fragmentFont.letterSpacing();
687 : :
688 : 0 : blockHeight += fragmentIndex = 0 ? labelHeight * fragment.text().size() + ( fragment.text().size() - 1 ) * letterSpacing
689 : 0 : : fragment.text().size() * ( labelHeight + letterSpacing );
690 : 0 : fragmentIndex++;
691 : 0 : }
692 : 0 : maxBlockHeight = std::max( maxBlockHeight, blockHeight );
693 : : }
694 : :
695 : 0 : return maxBlockHeight / scaleFactor;
696 : : }
697 : :
698 : : case QgsTextFormat::RotationBasedOrientation:
699 : : {
700 : : // label mode only
701 : 0 : break;
702 : : }
703 : : }
704 : :
705 : 0 : return 0;
706 : 0 : }
707 : :
708 : 0 : void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer::Component component, const QgsTextFormat &format, const QgsTextDocument &document, QgsTextRenderer::DrawMode mode )
709 : : {
710 : 0 : QgsTextBackgroundSettings background = format.background();
711 : :
712 : 0 : QPainter *prevP = context.painter();
713 : 0 : QPainter *p = context.painter();
714 : 0 : std::unique_ptr< QgsPaintEffect > tmpEffect;
715 : 0 : if ( background.paintEffect() && background.paintEffect()->enabled() )
716 : : {
717 : 0 : tmpEffect.reset( background.paintEffect()->clone() );
718 : 0 : tmpEffect->begin( context );
719 : 0 : p = context.painter();
720 : 0 : }
721 : :
722 : : //QgsDebugMsgLevel( QStringLiteral( "Background label rotation: %1" ).arg( component.rotation() ), 4 );
723 : :
724 : : // shared calculations between shapes and SVG
725 : :
726 : : // configure angles, set component rotation and rotationOffset
727 : 0 : if ( background.rotationType() != QgsTextBackgroundSettings::RotationFixed )
728 : : {
729 : 0 : component.rotation = -( component.rotation * 180 / M_PI ); // RotationSync
730 : 0 : component.rotationOffset =
731 : 0 : background.rotationType() == QgsTextBackgroundSettings::RotationOffset ? background.rotation() : 0.0;
732 : 0 : }
733 : : else // RotationFixed
734 : : {
735 : 0 : component.rotation = 0.0; // don't use label's rotation
736 : 0 : component.rotationOffset = background.rotation();
737 : : }
738 : :
739 : 0 : const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1;
740 : :
741 : 0 : if ( mode != Label )
742 : : {
743 : : // need to calculate size of text
744 : 0 : QFontMetricsF fm( format.scaledFont( context, scaleFactor ) );
745 : 0 : double width = textWidth( context, format, document );
746 : 0 : double height = textHeight( context, format, document, mode );
747 : :
748 : 0 : switch ( mode )
749 : : {
750 : : case Rect:
751 : 0 : switch ( component.hAlign )
752 : : {
753 : : case AlignLeft:
754 : : case AlignJustify:
755 : 0 : component.center = QPointF( component.origin.x() + width / 2.0,
756 : 0 : component.origin.y() + height / 2.0 );
757 : 0 : break;
758 : :
759 : : case AlignCenter:
760 : 0 : component.center = QPointF( component.origin.x() + component.size.width() / 2.0,
761 : 0 : component.origin.y() + height / 2.0 );
762 : 0 : break;
763 : :
764 : : case AlignRight:
765 : 0 : component.center = QPointF( component.origin.x() + component.size.width() - width / 2.0,
766 : 0 : component.origin.y() + height / 2.0 );
767 : 0 : break;
768 : : }
769 : 0 : break;
770 : :
771 : : case Point:
772 : : {
773 : 0 : double originAdjust = fm.ascent() / scaleFactor / 2.0 - fm.leading() / scaleFactor / 2.0;
774 : 0 : switch ( component.hAlign )
775 : : {
776 : : case AlignLeft:
777 : : case AlignJustify:
778 : 0 : component.center = QPointF( component.origin.x() + width / 2.0,
779 : 0 : component.origin.y() - height / 2.0 + originAdjust );
780 : 0 : break;
781 : :
782 : : case AlignCenter:
783 : 0 : component.center = QPointF( component.origin.x(),
784 : 0 : component.origin.y() - height / 2.0 + originAdjust );
785 : 0 : break;
786 : :
787 : : case AlignRight:
788 : 0 : component.center = QPointF( component.origin.x() - width / 2.0,
789 : 0 : component.origin.y() - height / 2.0 + originAdjust );
790 : 0 : break;
791 : : }
792 : 0 : break;
793 : : }
794 : :
795 : : case Label:
796 : 0 : break;
797 : : }
798 : :
799 : 0 : if ( format.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
800 : 0 : component.size = QSizeF( width, height );
801 : 0 : }
802 : :
803 : : // TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
804 : :
805 : 0 : switch ( background.type() )
806 : : {
807 : : case QgsTextBackgroundSettings::ShapeSVG:
808 : : case QgsTextBackgroundSettings::ShapeMarkerSymbol:
809 : : {
810 : : // all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
811 : :
812 : 0 : if ( background.type() == QgsTextBackgroundSettings::ShapeSVG && background.svgFile().isEmpty() )
813 : 0 : return;
814 : :
815 : 0 : if ( background.type() == QgsTextBackgroundSettings::ShapeMarkerSymbol && !background.markerSymbol() )
816 : 0 : return;
817 : :
818 : 0 : double sizeOut = 0.0;
819 : : // only one size used for SVG/marker symbol sizing/scaling (no use of shapeSize.y() or Y field in gui)
820 : 0 : if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
821 : : {
822 : 0 : sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
823 : 0 : }
824 : 0 : else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
825 : : {
826 : 0 : sizeOut = std::max( component.size.width(), component.size.height() );
827 : 0 : double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
828 : :
829 : : // add buffer
830 : 0 : sizeOut += bufferSize * 2;
831 : 0 : }
832 : :
833 : : // don't bother rendering symbols smaller than 1x1 pixels in size
834 : : // TODO: add option to not show any svgs under/over a certain size
835 : 0 : if ( sizeOut < 1.0 )
836 : 0 : return;
837 : :
838 : 0 : std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
839 : 0 : if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
840 : : {
841 : 0 : QVariantMap map; // for SVG symbology marker
842 : 0 : map[QStringLiteral( "name" )] = background.svgFile().trimmed();
843 : 0 : map[QStringLiteral( "size" )] = QString::number( sizeOut );
844 : 0 : map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
845 : 0 : map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
846 : :
847 : : // offset is handled by this local painter
848 : : // TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
849 : : //map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
850 : : //map["offset_unit"] = QgsUnitTypes::encodeUnit(
851 : : // tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
852 : :
853 : 0 : map[QStringLiteral( "fill" )] = background.fillColor().name();
854 : 0 : map[QStringLiteral( "outline" )] = background.strokeColor().name();
855 : 0 : map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
856 : 0 : map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
857 : :
858 : 0 : if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
859 : : {
860 : 0 : QgsTextShadowSettings shadow = format.shadow();
861 : : // configure SVG shadow specs
862 : 0 : QVariantMap shdwmap( map );
863 : 0 : shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
864 : 0 : shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
865 : 0 : shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
866 : :
867 : : // store SVG's drawing in QPicture for drop shadow call
868 : 0 : QPicture svgPict;
869 : 0 : QPainter svgp;
870 : 0 : svgp.begin( &svgPict );
871 : :
872 : : // draw shadow symbol
873 : :
874 : : // clone current render context map unit/mm conversion factors, but not
875 : : // other map canvas parameters, then substitute this painter for use in symbology painting
876 : : // NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
877 : : // but will be created relative to the SVG's computed size, not the current map canvas
878 : 0 : QgsRenderContext shdwContext;
879 : 0 : shdwContext.setMapToPixel( context.mapToPixel() );
880 : 0 : shdwContext.setScaleFactor( context.scaleFactor() );
881 : 0 : shdwContext.setPainter( &svgp );
882 : :
883 : 0 : std::unique_ptr< QgsSymbolLayer > symShdwL( QgsSvgMarkerSymbolLayer::create( shdwmap ) );
884 : 0 : QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL.get() );
885 : 0 : QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
886 : :
887 : 0 : svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
888 : 0 : svgp.end();
889 : :
890 : 0 : component.picture = svgPict;
891 : : // TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
892 : 0 : component.pictureBuffer = 0.0;
893 : :
894 : 0 : component.size = QSizeF( sizeOut, sizeOut );
895 : 0 : component.offset = QPointF( 0.0, 0.0 );
896 : :
897 : : // rotate about origin center of SVG
898 : 0 : QgsScopedQPainterState painterState( p );
899 : 0 : context.setPainterFlagsUsingContext( p );
900 : :
901 : 0 : p->translate( component.center.x(), component.center.y() );
902 : 0 : p->rotate( component.rotation );
903 : 0 : double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
904 : 0 : double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
905 : 0 : p->translate( QPointF( xoff, yoff ) );
906 : 0 : p->rotate( component.rotationOffset );
907 : 0 : p->translate( -sizeOut / 2, sizeOut / 2 );
908 : :
909 : 0 : drawShadow( context, component, format );
910 : 0 : }
911 : 0 : renderedSymbol.reset( );
912 : :
913 : 0 : QgsSymbolLayer *symL = QgsSvgMarkerSymbolLayer::create( map );
914 : 0 : renderedSymbol.reset( new QgsMarkerSymbol( QgsSymbolLayerList() << symL ) );
915 : 0 : }
916 : : else
917 : : {
918 : 0 : renderedSymbol.reset( background.markerSymbol()->clone() );
919 : 0 : renderedSymbol->setSize( sizeOut );
920 : 0 : renderedSymbol->setSizeUnit( QgsUnitTypes::RenderPixels );
921 : : }
922 : :
923 : 0 : renderedSymbol->setOpacity( background.opacity() );
924 : :
925 : : // draw the actual symbol
926 : 0 : QgsScopedQPainterState painterState( p );
927 : 0 : context.setPainterFlagsUsingContext( p );
928 : :
929 : 0 : if ( context.useAdvancedEffects() )
930 : : {
931 : 0 : p->setCompositionMode( background.blendMode() );
932 : 0 : }
933 : 0 : p->translate( component.center.x(), component.center.y() );
934 : 0 : p->rotate( component.rotation );
935 : 0 : double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
936 : 0 : double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
937 : 0 : p->translate( QPointF( xoff, yoff ) );
938 : 0 : p->rotate( component.rotationOffset );
939 : :
940 : 0 : const QgsFeature f = context.expressionContext().feature();
941 : 0 : renderedSymbol->startRender( context, context.expressionContext().fields() );
942 : 0 : renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
943 : 0 : renderedSymbol->stopRender( context );
944 : 0 : p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
945 : :
946 : : break;
947 : 0 : }
948 : :
949 : : case QgsTextBackgroundSettings::ShapeRectangle:
950 : : case QgsTextBackgroundSettings::ShapeCircle:
951 : : case QgsTextBackgroundSettings::ShapeSquare:
952 : : case QgsTextBackgroundSettings::ShapeEllipse:
953 : : {
954 : 0 : double w = component.size.width();
955 : 0 : double h = component.size.height();
956 : :
957 : 0 : if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
958 : : {
959 : 0 : w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
960 : 0 : background.sizeMapUnitScale() );
961 : 0 : h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
962 : 0 : background.sizeMapUnitScale() );
963 : 0 : }
964 : 0 : else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
965 : : {
966 : 0 : if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
967 : : {
968 : 0 : if ( w > h )
969 : 0 : h = w;
970 : 0 : else if ( h > w )
971 : 0 : w = h;
972 : 0 : }
973 : 0 : else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
974 : : {
975 : : // start with label bound by circle
976 : 0 : h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
977 : 0 : w = h;
978 : 0 : }
979 : 0 : else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
980 : : {
981 : : // start with label bound by ellipse
982 : 0 : h = h * M_SQRT1_2 * 2;
983 : 0 : w = w * M_SQRT1_2 * 2;
984 : 0 : }
985 : :
986 : 0 : double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
987 : 0 : background.sizeMapUnitScale() );
988 : 0 : double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
989 : 0 : background.sizeMapUnitScale() );
990 : :
991 : 0 : w += bufferWidth * 2;
992 : 0 : h += bufferHeight * 2;
993 : 0 : }
994 : :
995 : : // offsets match those of symbology: -x = left, -y = up
996 : 0 : QRectF rect( -w / 2.0, - h / 2.0, w, h );
997 : :
998 : 0 : if ( rect.isNull() )
999 : 0 : return;
1000 : :
1001 : 0 : QgsScopedQPainterState painterState( p );
1002 : 0 : context.setPainterFlagsUsingContext( p );
1003 : :
1004 : 0 : p->translate( QPointF( component.center.x(), component.center.y() ) );
1005 : 0 : p->rotate( component.rotation );
1006 : 0 : double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
1007 : 0 : double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
1008 : 0 : p->translate( QPointF( xoff, yoff ) );
1009 : 0 : p->rotate( component.rotationOffset );
1010 : :
1011 : 0 : double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
1012 : :
1013 : 0 : QPen pen;
1014 : 0 : if ( background.strokeWidth() > 0 )
1015 : : {
1016 : 0 : pen.setColor( background.strokeColor() );
1017 : 0 : pen.setWidthF( penSize );
1018 : 0 : if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
1019 : 0 : pen.setJoinStyle( background.joinStyle() );
1020 : 0 : }
1021 : : else
1022 : : {
1023 : 0 : pen = Qt::NoPen;
1024 : : }
1025 : :
1026 : : // store painting in QPicture for shadow drawing
1027 : 0 : QPicture shapePict;
1028 : 0 : QPainter shapep;
1029 : 0 : shapep.begin( &shapePict );
1030 : 0 : shapep.setPen( pen );
1031 : 0 : shapep.setBrush( background.fillColor() );
1032 : :
1033 : 0 : if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
1034 : 0 : || background.type() == QgsTextBackgroundSettings::ShapeSquare )
1035 : : {
1036 : 0 : if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
1037 : : {
1038 : 0 : shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
1039 : 0 : }
1040 : : else
1041 : : {
1042 : 0 : double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
1043 : 0 : double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
1044 : 0 : shapep.drawRoundedRect( rect, xRadius, yRadius );
1045 : : }
1046 : 0 : }
1047 : 0 : else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
1048 : 0 : || background.type() == QgsTextBackgroundSettings::ShapeCircle )
1049 : : {
1050 : 0 : shapep.drawEllipse( rect );
1051 : 0 : }
1052 : 0 : shapep.end();
1053 : :
1054 : 0 : if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
1055 : : {
1056 : 0 : component.picture = shapePict;
1057 : 0 : component.pictureBuffer = penSize / 2.0;
1058 : :
1059 : 0 : component.size = rect.size();
1060 : 0 : component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
1061 : 0 : drawShadow( context, component, format );
1062 : 0 : }
1063 : :
1064 : 0 : p->setOpacity( background.opacity() );
1065 : 0 : if ( context.useAdvancedEffects() )
1066 : : {
1067 : 0 : p->setCompositionMode( background.blendMode() );
1068 : 0 : }
1069 : :
1070 : : // scale for any print output or image saving @ specific dpi
1071 : 0 : p->scale( component.dpiRatio, component.dpiRatio );
1072 : 0 : _fixQPictureDPI( p );
1073 : 0 : p->drawPicture( 0, 0, shapePict );
1074 : : break;
1075 : 0 : }
1076 : : }
1077 : :
1078 : 0 : if ( tmpEffect )
1079 : : {
1080 : 0 : tmpEffect->end( context );
1081 : 0 : context.setPainter( prevP );
1082 : 0 : }
1083 : 0 : }
1084 : :
1085 : 0 : void QgsTextRenderer::drawShadow( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
1086 : : {
1087 : 0 : QgsTextShadowSettings shadow = format.shadow();
1088 : :
1089 : : // incoming component sizes should be multiplied by rasterCompressFactor, as
1090 : : // this allows shadows to be created at paint device dpi (e.g. high resolution),
1091 : : // then scale device painter by 1.0 / rasterCompressFactor for output
1092 : :
1093 : 0 : QPainter *p = context.painter();
1094 : 0 : double componentWidth = component.size.width(), componentHeight = component.size.height();
1095 : 0 : double xOffset = component.offset.x(), yOffset = component.offset.y();
1096 : 0 : double pictbuffer = component.pictureBuffer;
1097 : :
1098 : : // generate pixmap representation of label component drawing
1099 : 0 : bool mapUnits = shadow.blurRadiusUnit() == QgsUnitTypes::RenderMapUnits;
1100 : 0 : double radius = context.convertToPainterUnits( shadow.blurRadius(), shadow.blurRadiusUnit(), shadow.blurRadiusMapUnitScale() );
1101 : 0 : radius /= ( mapUnits ? context.scaleFactor() / component.dpiRatio : 1 );
1102 : 0 : radius = static_cast< int >( radius + 0.5 ); //NOLINT
1103 : :
1104 : : // TODO: add labeling gui option to adjust blurBufferClippingScale to minimize pixels, or
1105 : : // to ensure shadow isn't clipped too tight. (Or, find a better method of buffering)
1106 : 0 : double blurBufferClippingScale = 3.75;
1107 : 0 : int blurbuffer = ( radius > 17 ? 16 : radius ) * blurBufferClippingScale;
1108 : :
1109 : 0 : QImage blurImg( componentWidth + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
1110 : 0 : componentHeight + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
1111 : : QImage::Format_ARGB32_Premultiplied );
1112 : :
1113 : : // TODO: add labeling gui option to not show any shadows under/over a certain size
1114 : : // keep very small QImages from causing paint device issues, i.e. must be at least > 1
1115 : 0 : int minBlurImgSize = 1;
1116 : : // max limitation on QgsSvgCache is 10,000 for screen, which will probably be reasonable for future caching here, too
1117 : : // 4 x QgsSvgCache limit for output to print/image at higher dpi
1118 : : // TODO: should it be higher, scale with dpi, or have no limit? Needs testing with very large labels rendered at high dpi output
1119 : 0 : int maxBlurImgSize = 40000;
1120 : 0 : if ( blurImg.isNull()
1121 : 0 : || ( blurImg.width() < minBlurImgSize || blurImg.height() < minBlurImgSize )
1122 : 0 : || ( blurImg.width() > maxBlurImgSize || blurImg.height() > maxBlurImgSize ) )
1123 : 0 : return;
1124 : :
1125 : 0 : blurImg.fill( QColor( Qt::transparent ).rgba() );
1126 : 0 : QPainter pictp;
1127 : 0 : if ( !pictp.begin( &blurImg ) )
1128 : 0 : return;
1129 : 0 : pictp.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
1130 : 0 : QPointF imgOffset( blurbuffer + pictbuffer + xOffset,
1131 : 0 : blurbuffer + pictbuffer + componentHeight + yOffset );
1132 : :
1133 : 0 : pictp.drawPicture( imgOffset,
1134 : 0 : component.picture );
1135 : :
1136 : : // overlay shadow color
1137 : 0 : pictp.setCompositionMode( QPainter::CompositionMode_SourceIn );
1138 : 0 : pictp.fillRect( blurImg.rect(), shadow.color() );
1139 : 0 : pictp.end();
1140 : :
1141 : : // blur the QImage in-place
1142 : 0 : if ( shadow.blurRadius() > 0.0 && radius > 0 )
1143 : : {
1144 : 0 : QgsSymbolLayerUtils::blurImageInPlace( blurImg, blurImg.rect(), radius, shadow.blurAlphaOnly() );
1145 : 0 : }
1146 : :
1147 : : #if 0
1148 : : // debug rect for QImage shadow registration and clipping visualization
1149 : : QPainter picti;
1150 : : picti.begin( &blurImg );
1151 : : picti.setBrush( Qt::Dense7Pattern );
1152 : : QPen imgPen( QColor( 0, 0, 255, 255 ) );
1153 : : imgPen.setWidth( 1 );
1154 : : picti.setPen( imgPen );
1155 : : picti.setOpacity( 0.1 );
1156 : : picti.drawRect( 0, 0, blurImg.width(), blurImg.height() );
1157 : : picti.end();
1158 : : #endif
1159 : :
1160 : 0 : double offsetDist = context.convertToPainterUnits( shadow.offsetDistance(), shadow.offsetUnit(), shadow.offsetMapUnitScale() );
1161 : 0 : double angleRad = shadow.offsetAngle() * M_PI / 180; // to radians
1162 : 0 : if ( shadow.offsetGlobal() )
1163 : : {
1164 : : // TODO: check for differences in rotation origin and cw/ccw direction,
1165 : : // when this shadow function is used for something other than labels
1166 : :
1167 : : // it's 0-->cw-->360 for labels
1168 : : //QgsDebugMsgLevel( QStringLiteral( "Shadow aggregated label rotation (degrees): %1" ).arg( component.rotation() + component.rotationOffset() ), 4 );
1169 : 0 : angleRad -= ( component.rotation * M_PI / 180 + component.rotationOffset * M_PI / 180 );
1170 : 0 : }
1171 : :
1172 : 0 : QPointF transPt( -offsetDist * std::cos( angleRad + M_PI_2 ),
1173 : 0 : -offsetDist * std::sin( angleRad + M_PI_2 ) );
1174 : :
1175 : 0 : p->save();
1176 : 0 : p->setRenderHint( QPainter::SmoothPixmapTransform );
1177 : 0 : context.setPainterFlagsUsingContext( p );
1178 : 0 : if ( context.useAdvancedEffects() )
1179 : : {
1180 : 0 : p->setCompositionMode( shadow.blendMode() );
1181 : 0 : }
1182 : 0 : p->setOpacity( shadow.opacity() );
1183 : :
1184 : 0 : double scale = shadow.scale() / 100.0;
1185 : : // TODO: scale from center/center, left/center or left/top, instead of default left/bottom?
1186 : 0 : p->scale( scale, scale );
1187 : 0 : if ( component.useOrigin )
1188 : : {
1189 : 0 : p->translate( component.origin.x(), component.origin.y() );
1190 : 0 : }
1191 : 0 : p->translate( transPt );
1192 : 0 : p->translate( -imgOffset.x(),
1193 : 0 : -imgOffset.y() );
1194 : 0 : p->drawImage( 0, 0, blurImg );
1195 : 0 : p->restore();
1196 : :
1197 : : // debug rects
1198 : : #if 0
1199 : : // draw debug rect for QImage painting registration
1200 : : p->save();
1201 : : p->setBrush( Qt::NoBrush );
1202 : : QPen imgPen( QColor( 255, 0, 0, 10 ) );
1203 : : imgPen.setWidth( 2 );
1204 : : imgPen.setStyle( Qt::DashLine );
1205 : : p->setPen( imgPen );
1206 : : p->scale( scale, scale );
1207 : : if ( component.useOrigin() )
1208 : : {
1209 : : p->translate( component.origin().x(), component.origin().y() );
1210 : : }
1211 : : p->translate( transPt );
1212 : : p->translate( -imgOffset.x(),
1213 : : -imgOffset.y() );
1214 : : p->drawRect( 0, 0, blurImg.width(), blurImg.height() );
1215 : : p->restore();
1216 : :
1217 : : // draw debug rect for passed in component dimensions
1218 : : p->save();
1219 : : p->setBrush( Qt::NoBrush );
1220 : : QPen componentRectPen( QColor( 0, 255, 0, 70 ) );
1221 : : componentRectPen.setWidth( 1 );
1222 : : if ( component.useOrigin() )
1223 : : {
1224 : : p->translate( component.origin().x(), component.origin().y() );
1225 : : }
1226 : : p->setPen( componentRectPen );
1227 : : p->drawRect( QRect( -xOffset, -componentHeight - yOffset, componentWidth, componentHeight ) );
1228 : : p->restore();
1229 : : #endif
1230 : 0 : }
1231 : :
1232 : :
1233 : 0 : void QgsTextRenderer::drawTextInternal( TextPart drawType,
1234 : : QgsRenderContext &context,
1235 : : const QgsTextFormat &format,
1236 : : const Component &component,
1237 : : const QgsTextDocument &document,
1238 : : const QFontMetricsF *fontMetrics,
1239 : : HAlignment alignment, VAlignment vAlignment, DrawMode mode )
1240 : : {
1241 : 0 : if ( !context.painter() )
1242 : : {
1243 : 0 : return;
1244 : : }
1245 : :
1246 : 0 : double fontScale = 1.0;
1247 : 0 : std::unique_ptr< QFontMetricsF > tmpMetrics;
1248 : 0 : if ( !fontMetrics )
1249 : : {
1250 : 0 : fontScale = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
1251 : 0 : const QFont f = format.scaledFont( context, fontScale );
1252 : 0 : tmpMetrics = std::make_unique< QFontMetricsF >( f );
1253 : 0 : fontMetrics = tmpMetrics.get();
1254 : 0 : }
1255 : :
1256 : 0 : double rotation = 0;
1257 : 0 : const QgsTextFormat::TextOrientation orientation = calculateRotationAndOrientationForComponent( format, component, rotation );
1258 : 0 : switch ( orientation )
1259 : : {
1260 : : case QgsTextFormat::HorizontalOrientation:
1261 : : {
1262 : 0 : drawTextInternalHorizontal( context, format, drawType, mode, component, document, fontScale, fontMetrics, alignment, vAlignment, rotation );
1263 : 0 : break;
1264 : : }
1265 : :
1266 : : case QgsTextFormat::VerticalOrientation:
1267 : : case QgsTextFormat::RotationBasedOrientation:
1268 : : {
1269 : 0 : drawTextInternalVertical( context, format, drawType, mode, component, document, fontScale, fontMetrics, alignment, vAlignment, rotation );
1270 : 0 : break;
1271 : : }
1272 : : }
1273 : 0 : }
1274 : :
1275 : 0 : QgsTextFormat::TextOrientation QgsTextRenderer::calculateRotationAndOrientationForComponent( const QgsTextFormat &format, const QgsTextRenderer::Component &component, double &rotation )
1276 : : {
1277 : 0 : rotation = -component.rotation * 180 / M_PI;
1278 : :
1279 : 0 : switch ( format.orientation() )
1280 : : {
1281 : : case QgsTextFormat::RotationBasedOrientation:
1282 : : {
1283 : : // Between 45 to 135 and 235 to 315 degrees, rely on vertical orientation
1284 : 0 : if ( rotation >= -315 && rotation < -90 )
1285 : : {
1286 : 0 : rotation -= 90;
1287 : 0 : return QgsTextFormat::VerticalOrientation;
1288 : : }
1289 : 0 : else if ( rotation >= -90 && rotation < -45 )
1290 : : {
1291 : 0 : rotation += 90;
1292 : 0 : return QgsTextFormat::VerticalOrientation;
1293 : : }
1294 : :
1295 : 0 : return QgsTextFormat::HorizontalOrientation;
1296 : : }
1297 : :
1298 : : case QgsTextFormat::HorizontalOrientation:
1299 : : case QgsTextFormat::VerticalOrientation:
1300 : 0 : return format.orientation();
1301 : : }
1302 : 0 : return QgsTextFormat::HorizontalOrientation;
1303 : 0 : }
1304 : :
1305 : 0 : void QgsTextRenderer::calculateExtraSpacingForLineJustification( const double spaceToDistribute, const QgsTextBlock &block, double &extraWordSpace, double &extraLetterSpace )
1306 : : {
1307 : 0 : const QString blockText = block.toPlainText();
1308 : 0 : QTextBoundaryFinder finder( QTextBoundaryFinder::Word, blockText );
1309 : 0 : finder.toStart();
1310 : 0 : int wordBoundaries = 0;
1311 : 0 : while ( finder.toNextBoundary() != -1 )
1312 : : {
1313 : 0 : if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
1314 : 0 : wordBoundaries++;
1315 : : }
1316 : :
1317 : 0 : if ( wordBoundaries > 0 )
1318 : : {
1319 : : // word boundaries found => justify by padding word spacing
1320 : 0 : extraWordSpace = spaceToDistribute / wordBoundaries;
1321 : 0 : }
1322 : : else
1323 : : {
1324 : : // no word boundaries found => justify by letter spacing
1325 : 0 : QTextBoundaryFinder finder( QTextBoundaryFinder::Grapheme, blockText );
1326 : 0 : finder.toStart();
1327 : :
1328 : 0 : int graphemeBoundaries = 0;
1329 : 0 : while ( finder.toNextBoundary() != -1 )
1330 : : {
1331 : 0 : if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
1332 : 0 : graphemeBoundaries++;
1333 : : }
1334 : :
1335 : 0 : if ( graphemeBoundaries > 0 )
1336 : : {
1337 : 0 : extraLetterSpace = spaceToDistribute / graphemeBoundaries;
1338 : 0 : }
1339 : 0 : }
1340 : 0 : }
1341 : :
1342 : 0 : void QgsTextRenderer::applyExtraSpacingForLineJustification( QFont &font, double extraWordSpace, double extraLetterSpace )
1343 : : {
1344 : 0 : const double prevWordSpace = font.wordSpacing();
1345 : 0 : font.setWordSpacing( prevWordSpace + extraWordSpace );
1346 : 0 : const double prevLetterSpace = font.letterSpacing();
1347 : 0 : font.setLetterSpacing( QFont::AbsoluteSpacing, prevLetterSpace + extraLetterSpace );
1348 : 0 : }
1349 : :
1350 : 0 : void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, TextPart drawType, DrawMode mode, const Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, HAlignment hAlignment,
1351 : : VAlignment vAlignment, double rotation )
1352 : : {
1353 : 0 : QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
1354 : 0 : const QStringList textLines = document.toPlainText();
1355 : :
1356 : 0 : double labelWidest = 0.0;
1357 : 0 : switch ( mode )
1358 : : {
1359 : : case Label:
1360 : : case Point:
1361 : 0 : for ( const QString &line : textLines )
1362 : : {
1363 : 0 : double labelWidth = fontMetrics->horizontalAdvance( line ) / fontScale;
1364 : 0 : if ( labelWidth > labelWidest )
1365 : : {
1366 : 0 : labelWidest = labelWidth;
1367 : 0 : }
1368 : : }
1369 : 0 : break;
1370 : :
1371 : : case Rect:
1372 : 0 : labelWidest = component.size.width();
1373 : 0 : break;
1374 : : }
1375 : :
1376 : 0 : double labelHeight = ( fontMetrics->ascent() + fontMetrics->descent() ) / fontScale; // ignore +1 for baseline
1377 : : // double labelHighest = labelfm->height() + ( double )(( lines - 1 ) * labelHeight * tmpLyr.multilineHeight );
1378 : :
1379 : : // needed to move bottom of text's descender to within bottom edge of label
1380 : 0 : double ascentOffset = 0.25 * fontMetrics->ascent() / fontScale; // labelfm->descent() is not enough
1381 : :
1382 : 0 : int i = 0;
1383 : :
1384 : 0 : bool adjustForAlignment = hAlignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
1385 : :
1386 : 0 : if ( mode == Rect && vAlignment != AlignTop )
1387 : : {
1388 : : // need to calculate overall text height in advance so that we can adjust for vertical alignment
1389 : 0 : const double overallHeight = textHeight( context, format, textLines, Rect );
1390 : 0 : switch ( vAlignment )
1391 : : {
1392 : : case AlignTop:
1393 : 0 : break;
1394 : :
1395 : : case AlignVCenter:
1396 : 0 : ascentOffset = -( component.size.height() - overallHeight ) * 0.5 + ascentOffset;
1397 : 0 : break;
1398 : :
1399 : : case AlignBottom:
1400 : 0 : ascentOffset = -( component.size.height() - overallHeight ) + ascentOffset;
1401 : 0 : break;
1402 : : }
1403 : 0 : }
1404 : :
1405 : 0 : for ( const QString &line : std::as_const( textLines ) )
1406 : : {
1407 : 0 : const QgsTextBlock block = document.at( i );
1408 : :
1409 : 0 : const bool isFinalLine = i == document.size() - 1;
1410 : :
1411 : 0 : QgsScopedQPainterState painterState( context.painter() );
1412 : 0 : context.setPainterFlagsUsingContext();
1413 : 0 : context.painter()->translate( component.origin );
1414 : 0 : if ( !qgsDoubleNear( rotation, 0.0 ) )
1415 : 0 : context.painter()->rotate( rotation );
1416 : :
1417 : : // apply to the mask painter the same transformations
1418 : 0 : if ( maskPainter )
1419 : : {
1420 : 0 : maskPainter->save();
1421 : 0 : maskPainter->translate( component.origin );
1422 : 0 : if ( !qgsDoubleNear( rotation, 0.0 ) )
1423 : 0 : maskPainter->rotate( rotation );
1424 : 0 : }
1425 : :
1426 : : // figure x offset for horizontal alignment of multiple lines
1427 : 0 : double xMultiLineOffset = 0.0;
1428 : 0 : double labelWidth = fontMetrics->horizontalAdvance( line ) / fontScale;
1429 : 0 : double extraWordSpace = 0;
1430 : 0 : double extraLetterSpace = 0;
1431 : 0 : if ( adjustForAlignment )
1432 : : {
1433 : 0 : double labelWidthDiff = 0;
1434 : 0 : switch ( hAlignment )
1435 : : {
1436 : : case AlignCenter:
1437 : 0 : labelWidthDiff = ( labelWidest - labelWidth ) * 0.5;
1438 : 0 : break;
1439 : :
1440 : : case AlignRight:
1441 : 0 : labelWidthDiff = labelWidest - labelWidth;
1442 : 0 : break;
1443 : :
1444 : : case AlignJustify:
1445 : 0 : if ( !isFinalLine && labelWidest > labelWidth )
1446 : : {
1447 : 0 : calculateExtraSpacingForLineJustification( labelWidest - labelWidth, block, extraWordSpace, extraLetterSpace );
1448 : 0 : }
1449 : 0 : break;
1450 : :
1451 : : case AlignLeft:
1452 : 0 : break;
1453 : : }
1454 : :
1455 : 0 : switch ( mode )
1456 : : {
1457 : : case Label:
1458 : : case Rect:
1459 : 0 : xMultiLineOffset = labelWidthDiff;
1460 : 0 : break;
1461 : :
1462 : : case Point:
1463 : : {
1464 : 0 : switch ( hAlignment )
1465 : : {
1466 : : case AlignRight:
1467 : 0 : xMultiLineOffset = labelWidthDiff - labelWidest;
1468 : 0 : break;
1469 : :
1470 : : case AlignCenter:
1471 : 0 : xMultiLineOffset = labelWidthDiff - labelWidest / 2.0;
1472 : 0 : break;
1473 : :
1474 : : case AlignLeft:
1475 : : case AlignJustify:
1476 : 0 : break;
1477 : : }
1478 : : }
1479 : 0 : break;
1480 : : }
1481 : 0 : }
1482 : :
1483 : 0 : double yMultiLineOffset = ascentOffset;
1484 : 0 : switch ( mode )
1485 : : {
1486 : : case Label:
1487 : : // rendering labels needs special handling - in this case text should be
1488 : : // drawn with the bottom left corner coinciding with origin, vs top left
1489 : : // for standard text rendering. Line height is also slightly different.
1490 : 0 : yMultiLineOffset = - ascentOffset - ( textLines.size() - 1 - i ) * labelHeight * format.lineHeight();
1491 : 0 : break;
1492 : :
1493 : : case Rect:
1494 : : // standard rendering - designed to exactly replicate QPainter's drawText method
1495 : 0 : yMultiLineOffset = - ascentOffset + labelHeight - 1 /*baseline*/ + format.lineHeight() * fontMetrics->lineSpacing() * i / fontScale;
1496 : 0 : break;
1497 : :
1498 : : case Point:
1499 : : // standard rendering - designed to exactly replicate QPainter's drawText rect method
1500 : 0 : yMultiLineOffset = 0 - ( textLines.size() - 1 - i ) * fontMetrics->lineSpacing() * format.lineHeight() / fontScale;
1501 : 0 : break;
1502 : :
1503 : : }
1504 : :
1505 : 0 : context.painter()->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
1506 : 0 : if ( maskPainter )
1507 : 0 : maskPainter->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
1508 : :
1509 : 0 : Component subComponent;
1510 : 0 : subComponent.block = block;
1511 : 0 : subComponent.size = QSizeF( labelWidth, labelHeight );
1512 : 0 : subComponent.offset = QPointF( 0.0, -ascentOffset );
1513 : 0 : subComponent.rotation = -component.rotation * 180 / M_PI;
1514 : 0 : subComponent.rotationOffset = 0.0;
1515 : 0 : subComponent.extraWordSpacing = extraWordSpace * fontScale;
1516 : 0 : subComponent.extraLetterSpacing = extraLetterSpace * fontScale;
1517 : :
1518 : : // draw the mask below the text (for preview)
1519 : 0 : if ( format.mask().enabled() )
1520 : : {
1521 : 0 : QgsTextRenderer::drawMask( context, subComponent, format );
1522 : 0 : }
1523 : :
1524 : 0 : if ( drawType == QgsTextRenderer::Buffer )
1525 : : {
1526 : 0 : QgsTextRenderer::drawBuffer( context, subComponent, format );
1527 : 0 : }
1528 : : else
1529 : : {
1530 : : // store text's drawing in QPicture for drop shadow call
1531 : 0 : QPicture textPict;
1532 : 0 : QPainter textp;
1533 : 0 : textp.begin( &textPict );
1534 : 0 : textp.setPen( Qt::NoPen );
1535 : 0 : const QFont font = format.scaledFont( context, fontScale );
1536 : 0 : textp.scale( 1 / fontScale, 1 / fontScale );
1537 : :
1538 : 0 : double xOffset = 0;
1539 : 0 : for ( const QgsTextFragment &fragment : block )
1540 : : {
1541 : : // draw text, QPainterPath method
1542 : 0 : QPainterPath path;
1543 : 0 : path.setFillRule( Qt::WindingFill );
1544 : :
1545 : 0 : QFont fragmentFont = font;
1546 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
1547 : :
1548 : 0 : if ( extraWordSpace || extraLetterSpace )
1549 : 0 : applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
1550 : :
1551 : 0 : path.addText( xOffset, 0, fragmentFont, fragment.text() );
1552 : :
1553 : 0 : QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
1554 : 0 : textColor.setAlphaF( format.opacity() );
1555 : 0 : textp.setBrush( textColor );
1556 : 0 : textp.drawPath( path );
1557 : :
1558 : 0 : xOffset += fragment.horizontalAdvance( fragmentFont, true );
1559 : 0 : }
1560 : 0 : textp.end();
1561 : :
1562 : 0 : if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
1563 : : {
1564 : 0 : subComponent.picture = textPict;
1565 : 0 : subComponent.pictureBuffer = 0.0; // no pen width to deal with
1566 : 0 : subComponent.origin = QPointF( 0.0, 0.0 );
1567 : :
1568 : 0 : QgsTextRenderer::drawShadow( context, subComponent, format );
1569 : 0 : }
1570 : :
1571 : : // paint the text
1572 : 0 : if ( context.useAdvancedEffects() )
1573 : : {
1574 : 0 : context.painter()->setCompositionMode( format.blendMode() );
1575 : 0 : }
1576 : :
1577 : : // scale for any print output or image saving @ specific dpi
1578 : 0 : context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
1579 : :
1580 : 0 : switch ( context.textRenderFormat() )
1581 : : {
1582 : : case QgsRenderContext::TextFormatAlwaysOutlines:
1583 : : {
1584 : : // draw outlined text
1585 : 0 : _fixQPictureDPI( context.painter() );
1586 : 0 : context.painter()->drawPicture( 0, 0, textPict );
1587 : 0 : break;
1588 : : }
1589 : :
1590 : : case QgsRenderContext::TextFormatAlwaysText:
1591 : : {
1592 : 0 : double xOffset = 0;
1593 : 0 : for ( const QgsTextFragment &fragment : block )
1594 : : {
1595 : 0 : QFont fragmentFont = font;
1596 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
1597 : :
1598 : 0 : if ( extraWordSpace || extraLetterSpace )
1599 : 0 : applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
1600 : :
1601 : 0 : QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
1602 : 0 : textColor.setAlphaF( format.opacity() );
1603 : :
1604 : 0 : context.painter()->setPen( textColor );
1605 : 0 : context.painter()->setFont( fragmentFont );
1606 : 0 : context.painter()->setRenderHint( QPainter::TextAntialiasing );
1607 : :
1608 : 0 : context.painter()->scale( 1 / fontScale, 1 / fontScale );
1609 : 0 : context.painter()->drawText( xOffset, 0, fragment.text() );
1610 : 0 : context.painter()->scale( fontScale, fontScale );
1611 : :
1612 : 0 : xOffset += fragment.horizontalAdvance( fragmentFont, true, fontScale );
1613 : 0 : }
1614 : : }
1615 : 0 : }
1616 : 0 : }
1617 : 0 : if ( maskPainter )
1618 : 0 : maskPainter->restore();
1619 : 0 : i++;
1620 : 0 : }
1621 : 0 : }
1622 : :
1623 : 0 : void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart drawType, QgsTextRenderer::DrawMode mode, const QgsTextRenderer::Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, QgsTextRenderer::HAlignment hAlignment, QgsTextRenderer::VAlignment, double rotation )
1624 : : {
1625 : 0 : QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
1626 : 0 : const QStringList textLines = document.toPlainText();
1627 : :
1628 : 0 : const QFont font = format.scaledFont( context, fontScale );
1629 : 0 : double letterSpacing = font.letterSpacing() / fontScale;
1630 : :
1631 : 0 : double labelWidth = fontMetrics->maxWidth() / fontScale; // label width represents the width of one line of a multi-line label
1632 : 0 : double actualLabelWidest = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.lineHeight();
1633 : 0 : double labelWidest = 0.0;
1634 : 0 : switch ( mode )
1635 : : {
1636 : : case Label:
1637 : : case Point:
1638 : 0 : labelWidest = actualLabelWidest;
1639 : 0 : break;
1640 : :
1641 : : case Rect:
1642 : 0 : labelWidest = component.size.width();
1643 : 0 : break;
1644 : : }
1645 : :
1646 : 0 : int maxLineLength = 0;
1647 : 0 : for ( const QString &line : std::as_const( textLines ) )
1648 : : {
1649 : 0 : maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1650 : : }
1651 : 0 : double actualLabelHeight = fontMetrics->ascent() / fontScale + ( fontMetrics->ascent() / fontScale + letterSpacing ) * ( maxLineLength - 1 );
1652 : 0 : double ascentOffset = fontMetrics->ascent() / fontScale;
1653 : :
1654 : 0 : int i = 0;
1655 : :
1656 : 0 : bool adjustForAlignment = hAlignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
1657 : :
1658 : 0 : for ( const QgsTextBlock &block : document )
1659 : : {
1660 : 0 : QgsScopedQPainterState painterState( context.painter() );
1661 : 0 : context.setPainterFlagsUsingContext();
1662 : :
1663 : 0 : context.painter()->translate( component.origin );
1664 : 0 : if ( !qgsDoubleNear( rotation, 0.0 ) )
1665 : 0 : context.painter()->rotate( rotation );
1666 : :
1667 : : // apply to the mask painter the same transformations
1668 : 0 : if ( maskPainter )
1669 : : {
1670 : 0 : maskPainter->save();
1671 : 0 : maskPainter->translate( component.origin );
1672 : 0 : if ( !qgsDoubleNear( rotation, 0.0 ) )
1673 : 0 : maskPainter->rotate( rotation );
1674 : 0 : }
1675 : :
1676 : : // figure x offset of multiple lines
1677 : 0 : double xOffset = actualLabelWidest - labelWidth - ( i * labelWidth * format.lineHeight() );
1678 : 0 : if ( adjustForAlignment )
1679 : : {
1680 : 0 : double labelWidthDiff = 0;
1681 : 0 : switch ( hAlignment )
1682 : : {
1683 : : case AlignCenter:
1684 : 0 : labelWidthDiff = ( labelWidest - actualLabelWidest ) * 0.5;
1685 : 0 : break;
1686 : :
1687 : : case AlignRight:
1688 : 0 : labelWidthDiff = labelWidest - actualLabelWidest;
1689 : 0 : break;
1690 : :
1691 : : case AlignLeft:
1692 : : case AlignJustify:
1693 : 0 : break;
1694 : : }
1695 : :
1696 : 0 : switch ( mode )
1697 : : {
1698 : : case Label:
1699 : : case Rect:
1700 : 0 : xOffset += labelWidthDiff;
1701 : 0 : break;
1702 : :
1703 : : case Point:
1704 : 0 : break;
1705 : : }
1706 : 0 : }
1707 : :
1708 : 0 : double yOffset = 0.0;
1709 : 0 : switch ( mode )
1710 : : {
1711 : : case Label:
1712 : 0 : if ( format.orientation() == QgsTextFormat::RotationBasedOrientation )
1713 : : {
1714 : 0 : if ( rotation >= -405 && rotation < -180 )
1715 : : {
1716 : 0 : yOffset = ascentOffset;
1717 : 0 : }
1718 : 0 : else if ( rotation >= 0 && rotation < 45 )
1719 : : {
1720 : 0 : xOffset -= actualLabelWidest;
1721 : 0 : yOffset = -actualLabelHeight + ascentOffset + fontMetrics->descent() / fontScale;
1722 : 0 : }
1723 : 0 : }
1724 : : else
1725 : : {
1726 : 0 : yOffset = -actualLabelHeight + ascentOffset;
1727 : : }
1728 : 0 : break;
1729 : :
1730 : : case Point:
1731 : 0 : yOffset = -actualLabelHeight + ascentOffset;
1732 : 0 : break;
1733 : :
1734 : : case Rect:
1735 : 0 : yOffset = ascentOffset;
1736 : 0 : break;
1737 : : }
1738 : :
1739 : 0 : context.painter()->translate( QPointF( xOffset, yOffset ) );
1740 : :
1741 : 0 : double fragmentYOffset = 0;
1742 : 0 : for ( const QgsTextFragment &fragment : block )
1743 : : {
1744 : : // apply some character replacement to draw symbols in vertical presentation
1745 : 0 : const QString line = QgsStringUtils::substituteVerticalCharacters( fragment.text() );
1746 : :
1747 : 0 : QFont fragmentFont( font );
1748 : 0 : fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
1749 : :
1750 : 0 : QFontMetricsF fragmentMetrics( fragmentFont );
1751 : :
1752 : 0 : double labelHeight = fragmentMetrics.ascent() / fontScale + ( fragmentMetrics.ascent() / fontScale + letterSpacing ) * ( line.length() - 1 );
1753 : :
1754 : 0 : Component subComponent;
1755 : 0 : subComponent.block = QgsTextBlock( fragment );
1756 : 0 : subComponent.size = QSizeF( labelWidth, labelHeight );
1757 : 0 : subComponent.offset = QPointF( 0.0, fragmentYOffset );
1758 : 0 : subComponent.rotation = -component.rotation * 180 / M_PI;
1759 : 0 : subComponent.rotationOffset = 0.0;
1760 : :
1761 : : // draw the mask below the text (for preview)
1762 : 0 : if ( format.mask().enabled() )
1763 : : {
1764 : : // WARNING: totally broken! (has been since mask was introduced)
1765 : : #if 0
1766 : : QgsTextRenderer::drawMask( context, subComponent, format );
1767 : : #endif
1768 : 0 : }
1769 : :
1770 : 0 : if ( drawType == QgsTextRenderer::Buffer )
1771 : : {
1772 : 0 : fragmentYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format );
1773 : 0 : }
1774 : : else
1775 : : {
1776 : : // draw text, QPainterPath method
1777 : 0 : QPainterPath path;
1778 : 0 : path.setFillRule( Qt::WindingFill );
1779 : 0 : const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
1780 : 0 : double partYOffset = 0.0;
1781 : 0 : for ( const auto &part : parts )
1782 : : {
1783 : 0 : double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
1784 : 0 : path.addText( partXOffset * fontScale, partYOffset * fontScale, fragmentFont, part );
1785 : 0 : partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
1786 : : }
1787 : :
1788 : : // store text's drawing in QPicture for drop shadow call
1789 : 0 : QPicture textPict;
1790 : 0 : QPainter textp;
1791 : 0 : textp.begin( &textPict );
1792 : 0 : textp.setPen( Qt::NoPen );
1793 : 0 : QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
1794 : 0 : textColor.setAlphaF( format.opacity() );
1795 : 0 : textp.setBrush( textColor );
1796 : 0 : textp.scale( 1 / fontScale, 1 / fontScale );
1797 : 0 : textp.drawPath( path );
1798 : : // TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
1799 : : // e.g. some capitalization options, but not others
1800 : : //textp.setFont( tmpLyr.textFont );
1801 : : //textp.setPen( tmpLyr.textColor );
1802 : : //textp.drawText( 0, 0, component.text() );
1803 : 0 : textp.end();
1804 : :
1805 : 0 : if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
1806 : : {
1807 : 0 : subComponent.picture = textPict;
1808 : 0 : subComponent.pictureBuffer = 0.0; // no pen width to deal with
1809 : 0 : subComponent.origin = QPointF( 0.0, fragmentYOffset );
1810 : 0 : const double prevY = subComponent.offset.y();
1811 : 0 : subComponent.offset = QPointF( 0, -labelHeight );
1812 : 0 : subComponent.useOrigin = true;
1813 : 0 : QgsTextRenderer::drawShadow( context, subComponent, format );
1814 : 0 : subComponent.useOrigin = false;
1815 : 0 : subComponent.offset = QPointF( 0, prevY );
1816 : 0 : }
1817 : :
1818 : : // paint the text
1819 : 0 : if ( context.useAdvancedEffects() )
1820 : : {
1821 : 0 : context.painter()->setCompositionMode( format.blendMode() );
1822 : 0 : }
1823 : :
1824 : : // scale for any print output or image saving @ specific dpi
1825 : 0 : context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
1826 : :
1827 : 0 : switch ( context.textRenderFormat() )
1828 : : {
1829 : : case QgsRenderContext::TextFormatAlwaysOutlines:
1830 : : {
1831 : : // draw outlined text
1832 : 0 : _fixQPictureDPI( context.painter() );
1833 : 0 : context.painter()->drawPicture( 0, fragmentYOffset, textPict );
1834 : 0 : fragmentYOffset += partYOffset;
1835 : 0 : break;
1836 : : }
1837 : :
1838 : : case QgsRenderContext::TextFormatAlwaysText:
1839 : : {
1840 : 0 : context.painter()->setFont( fragmentFont );
1841 : 0 : context.painter()->setPen( textColor );
1842 : 0 : context.painter()->setRenderHint( QPainter::TextAntialiasing );
1843 : :
1844 : 0 : double partYOffset = 0.0;
1845 : 0 : for ( const QString &part : parts )
1846 : : {
1847 : 0 : double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
1848 : 0 : context.painter()->scale( 1 / fontScale, 1 / fontScale );
1849 : 0 : context.painter()->drawText( partXOffset * fontScale, ( fragmentYOffset + partYOffset ) * fontScale, part );
1850 : 0 : context.painter()->scale( fontScale, fontScale );
1851 : 0 : partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
1852 : : }
1853 : 0 : fragmentYOffset += partYOffset;
1854 : : }
1855 : 0 : }
1856 : 0 : }
1857 : 0 : }
1858 : :
1859 : 0 : if ( maskPainter )
1860 : 0 : maskPainter->restore();
1861 : 0 : i++;
1862 : 0 : }
1863 : 0 : }
|