QGIS API Documentation 3.41.0-Master (64d82d4c163)
Loading...
Searching...
No Matches
qgstextdocumentmetrics.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstextdocumentmetrics.cpp
3 -----------------
4 begin : September 2022
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 ***************************************************************************/
16#include "qgis.h"
17#include "qgsstringutils.h"
18#include "qgstextblock.h"
19#include "qgstextfragment.h"
20#include "qgstextformat.h"
21#include "qgstextdocument.h"
22#include "qgsrendercontext.h"
23#include "qgstextrenderer.h"
24#include "qgsapplication.h"
25#include "qgsimagecache.h"
26
27#include <QFontMetricsF>
28
29// to match Qt behavior in QTextLine::draw
32
34{
37 double width = 0;
38 double heightLabelMode = 0;
41 double heightAscentMode = 0;
42 int blockSize = 0;
48 double lastLineLeading = 0;
49
51
52 QVector < double > blockVerticalLineSpacing;
53
60
61 QVector< double > blockLeftMargin;
62 QVector< double > blockRightMargin;
63
64 double outerXMin = 0;
65 double outerXMax = 0;
66 double outerYMinLabel = 0;
67 double outerYMaxLabel = 0;
68};
69
71{
72 bool isFirstBlock = false;
73 bool isLastBlock = false;
74 double maxLineSpacing = 0;
75 double blockWidth = 0;
76 double blockXMax = 0;
85 double maxBlockAscent = 0;
86 double maxBlockDescent = 0;
87 double maxBlockMaxWidth = 0;
88 double maxBlockLeading = 0;
89
90 QList< QFont > fragmentFonts;
91 QList< double > fragmentVerticalOffsets;
92 QList< double > fragmentFixedHeights;
94 QList< double > fragmentAscent;
95 QList< double > fragmentDescent;
96
99
100 // non calculated properties
103
104 double marginTop = 0;
105 double marginBottom = 0;
106 double marginLeft = 0;
107 double marginRight = 0;
108
140};
141
142
143void QgsTextDocumentMetrics::finalizeBlock( QgsTextDocumentMetrics &res, const QgsTextFormat &, DocumentMetrics &documentMetrics, QgsTextBlock &outputBlock, BlockMetrics &metrics )
144{
145 if ( metrics.isFirstBlock )
146 {
147 documentMetrics.verticalMarginsBetweenBlocks.append( metrics.marginTop );
148 documentMetrics.verticalMarginsBetweenBlocks.append( metrics.marginBottom );
149
150 documentMetrics.currentLabelBaseline += metrics.marginTop;
151 documentMetrics.currentRectBaseline += metrics.marginTop;
152 documentMetrics.currentPointBaseline += metrics.marginTop;
153 documentMetrics.currentCapHeightBasedBaseline += metrics.marginTop;
154 documentMetrics.currentAscentBasedBaseline += metrics.marginTop;
155
156 // same logic as used in QgsTextRenderer. (?!!)
157 // needed to move bottom of text's descender to within bottom edge of label
158 res.mFirstLineAscentOffset = 0.25 * metrics.maxBlockAscentForTextFragments; // descent() is not enough
159 res.mLastLineAscentOffset = res.mFirstLineAscentOffset;
160 res.mFirstLineCapHeight = metrics.maxBlockCapHeight;
161 const double lineHeight = ( metrics.maxBlockAscent + metrics.maxBlockDescent ); // ignore +1 for baseline
162
163 // rendering labels needs special handling - in this case text should be
164 // drawn with the bottom left corner coinciding with origin, vs top left
165 // for standard text rendering. Line height is also slightly different.
166 documentMetrics.currentLabelBaseline = -res.mFirstLineAscentOffset;
167
170
171 // standard rendering - designed to exactly replicate QPainter's drawText method
172 documentMetrics.currentRectBaseline = -res.mFirstLineAscentOffset + lineHeight - 1 /*baseline*/;
173
174 documentMetrics.currentCapHeightBasedBaseline += res.mFirstLineCapHeight;
175 documentMetrics.currentAscentBasedBaseline += metrics.maxBlockAscent;
176
177 // standard rendering - designed to exactly replicate QPainter's drawText rect method
178 documentMetrics.currentPointBaseline = 0;
179
180 documentMetrics.heightLabelMode += metrics.blockHeightUsingAscentDescent + metrics.marginTop;
181 documentMetrics.heightPointRectMode += metrics.blockHeightUsingAscentDescent + metrics.marginTop;
182 documentMetrics.heightCapHeightMode += metrics.maxBlockCapHeight + metrics.marginTop;
183 documentMetrics.heightAscentMode += metrics.maxBlockAscent + metrics.marginTop;
184 }
185 else
186 {
187 // html vertical margins between blocks collapse and take the size of the highest margin:
188 const double verticalMarginBeforeBlock = std::max( documentMetrics.verticalMarginsBetweenBlocks.last(), metrics.marginTop );
189 documentMetrics.verticalMarginsBetweenBlocks.last() = verticalMarginBeforeBlock;
190 documentMetrics.verticalMarginsBetweenBlocks.append( metrics.marginBottom );
191
192 double thisLineHeightUsingAscentDescent = metrics.lineHeightPercentage != 0 ? ( metrics.lineHeightPercentage * ( metrics.maxBlockAscent + metrics.maxBlockDescent ) ) : metrics.lineHeightPainterUnits;
193 double thisLineHeightUsingLineSpacing = metrics.lineHeightPercentage != 0 ? ( metrics.lineHeightPercentage * metrics.maxLineSpacing ) : metrics.lineHeightPainterUnits;
194
195 thisLineHeightUsingAscentDescent = std::max( thisLineHeightUsingAscentDescent, metrics.maxBlockFixedItemHeight );
196 thisLineHeightUsingLineSpacing = std::max( thisLineHeightUsingLineSpacing, metrics.maxBlockFixedItemHeight );
197
198 documentMetrics.currentLabelBaseline += verticalMarginBeforeBlock + thisLineHeightUsingAscentDescent;
199 documentMetrics.currentRectBaseline += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
200 documentMetrics.currentPointBaseline += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
201 // using cap height??
202 documentMetrics.currentCapHeightBasedBaseline += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
203 // using ascent?
204 documentMetrics.currentAscentBasedBaseline += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
205
206 documentMetrics.heightLabelMode += verticalMarginBeforeBlock + thisLineHeightUsingAscentDescent;
207 documentMetrics.heightPointRectMode += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
208 documentMetrics.heightCapHeightMode += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
209 documentMetrics.heightAscentMode += verticalMarginBeforeBlock + thisLineHeightUsingLineSpacing;
210 if ( metrics.isLastBlock )
211 {
212 res.mLastLineAscentOffset = 0.25 * metrics.maxBlockAscentForTextFragments;
213 documentMetrics.heightLabelMode += metrics.marginBottom;
214 documentMetrics.heightPointRectMode += metrics.marginBottom;
215 documentMetrics.heightCapHeightMode += metrics.marginBottom;
216 documentMetrics.heightAscentMode += metrics.marginBottom;
217 }
218 }
219
220 documentMetrics.blockLeftMargin << metrics.marginLeft;
221 documentMetrics.blockRightMargin << metrics.marginRight;
222
223 if ( metrics.isLastBlock )
224 {
225 if ( metrics.blockYMaxAdjustLabel > metrics.maxBlockDescent )
226 documentMetrics.outerYMaxLabel = metrics.blockYMaxAdjustLabel - metrics.maxBlockDescent;
227 }
228
229 documentMetrics.blockVerticalLineSpacing << ( metrics.lineHeightPercentage != 0 ? ( metrics.maxBlockMaxWidth * metrics.lineHeightPercentage ) : metrics.lineHeightPainterUnits );
230
231 res.mBlockHeights << metrics.blockHeightUsingLineSpacing;
232
233 documentMetrics.width = std::max( documentMetrics.width, metrics.blockWidth + metrics.marginLeft + metrics.marginRight );
234 documentMetrics.outerXMax = std::max( documentMetrics.outerXMax, metrics.blockXMax );
235
236 documentMetrics.heightVerticalOrientation = std::max( documentMetrics.heightVerticalOrientation, metrics.blockHeightVerticalOrientation );
237 res.mBlockWidths << metrics.blockWidth;
238 res.mFragmentFonts << metrics.fragmentFonts;
239 res.mBaselineOffsetsLabelMode << documentMetrics.currentLabelBaseline;
240 res.mBaselineOffsetsPointMode << documentMetrics.currentPointBaseline;
241 res.mBaselineOffsetsRectMode << documentMetrics.currentRectBaseline;
242 res.mBaselineOffsetsCapHeightMode << documentMetrics.currentCapHeightBasedBaseline;
243 res.mBaselineOffsetsAscentBased << documentMetrics.currentAscentBasedBaseline;
244 res.mBlockMaxDescent << metrics.maxBlockDescent;
245 res.mBlockMaxAscent << metrics.maxBlockAscent;
246 res.mBlockMaxCharacterWidth << metrics.maxBlockMaxWidth;
247 res.mFragmentVerticalOffsetsLabelMode << metrics.fragmentVerticalOffsets;
248 res.mFragmentFixedHeights << metrics.fragmentFixedHeights;
249 res.mFragmentVerticalOffsetsRectMode << metrics.fragmentVerticalOffsets;
250 res.mFragmentVerticalOffsetsPointMode << metrics.fragmentVerticalOffsets;
251 res.mFragmentHorizontalAdvance << metrics.fragmentHorizontalAdvance;
252 res.mFragmentAscent << metrics.fragmentAscent;
253 res.mFragmentDescent << metrics.fragmentDescent;
254
255 res.mDocument.append( outputBlock );
256 outputBlock.clear();
257
258 if ( !metrics.isFirstBlock )
259 documentMetrics.lastLineLeading = metrics.maxBlockLeading;
260
261 // reset metrics for next block
262 metrics.resetCalculatedStats();
263};
264
265
266void QgsTextDocumentMetrics::processFragment( QgsTextDocumentMetrics &res, const QgsTextFormat &format, const QgsRenderContext &context, const QgsTextDocumentRenderContext &documentContext, double scaleFactor, DocumentMetrics &documentMetrics, BlockMetrics &thisBlockMetrics, const QFont &font, const QgsTextFragment &fragment, QgsTextBlock &currentOutputBlock )
267{
268 if ( fragment.isTab() )
269 {
270 // special handling for tab characters
271 double nextTabStop = 0;
272 if ( !documentMetrics.tabStopDistancesPainterUnits.isEmpty() )
273 {
274 // if we don't find a tab stop before the current length of line, we just ignore the tab character entirely
275 nextTabStop = thisBlockMetrics.blockXMax;
276 for ( const double tabStop : std::as_const( documentMetrics.tabStopDistancesPainterUnits ) )
277 {
278 if ( tabStop >= thisBlockMetrics.blockXMax )
279 {
280 nextTabStop = tabStop;
281 break;
282 }
283 }
284 }
285 else
286 {
287 nextTabStop = ( std::floor( thisBlockMetrics.blockXMax / documentMetrics.tabStopDistancePainterUnits ) + 1 ) * documentMetrics.tabStopDistancePainterUnits;
288 }
289 const double fragmentWidth = nextTabStop - thisBlockMetrics.blockXMax;
290
291 thisBlockMetrics.blockWidth += fragmentWidth;
292 thisBlockMetrics.blockXMax += fragmentWidth;
293
294 thisBlockMetrics.fragmentVerticalOffsets << 0;
295 thisBlockMetrics.fragmentHorizontalAdvance << fragmentWidth;
296 thisBlockMetrics.fragmentFixedHeights << -1;
297 thisBlockMetrics.fragmentFonts << QFont();
298 thisBlockMetrics.fragmentAscent << 0;
299 thisBlockMetrics.fragmentDescent << 0;
300 currentOutputBlock.append( fragment );
301 }
302 else
303 {
304 const QgsTextCharacterFormat &fragmentFormat = fragment.characterFormat();
305
306 double fragmentHeightForVerticallyOffsetText = 0;
307 double fragmentYMaxAdjust = 0;
308
309 QFont updatedFont = font;
310 fragmentFormat.updateFontForFormat( updatedFont, context, scaleFactor );
311
312 QFontMetricsF fm( updatedFont );
313
314 // first, just do what we need to calculate the fragment width. We need this upfront to determine if we need to split this fragment up into a new block
315 // in order to respect text wrapping
316 if ( thisBlockMetrics.isFirstNonTabFragment )
317 thisBlockMetrics.previousNonSuperSubScriptFont = updatedFont;
318
319 double fragmentVerticalOffset = 0;
320 if ( fragmentFormat.hasVerticalAlignmentSet() )
321 {
322 switch ( fragmentFormat.verticalAlignment() )
323 {
325 thisBlockMetrics.previousNonSuperSubScriptFont = updatedFont;
326 break;
327
329 {
330 const QFontMetricsF previousFM( thisBlockMetrics.previousNonSuperSubScriptFont );
331
332 if ( fragmentFormat.fontPointSize() < 0 )
333 {
334 // if fragment has no explicit font size set, then we scale the inherited font size to 60% of base font size
335 // this allows for easier use of super/subscript in labels as "my text<sup>2</sup>" will automatically render
336 // the superscript in a smaller font size. BUT if the fragment format HAS a non -1 font size then it indicates
337 // that the document has an explicit font size for the super/subscript element, eg "my text<sup style="font-size: 6pt">2</sup>"
338 // which we should respect
339 updatedFont.setPixelSize( static_cast< int >( std::round( updatedFont.pixelSize() * QgsTextRenderer::SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR ) ) );
340 fm = QFontMetricsF( updatedFont );
341 }
342
343 // to match Qt behavior in QTextLine::draw
344 fragmentVerticalOffset = -( previousFM.ascent() + previousFM.descent() ) * SUPERSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR / scaleFactor;
345
346 // note -- this should really be fm.ascent(), not fm.capHeight() -- but in practice the ascent of most fonts is too large
347 // and causes unnecessarily large bounding boxes of vertically offset text!
348 fragmentHeightForVerticallyOffsetText = -fragmentVerticalOffset + fm.capHeight() / scaleFactor;
349 break;
350 }
351
353 {
354 const QFontMetricsF previousFM( thisBlockMetrics.previousNonSuperSubScriptFont );
355
356 if ( fragmentFormat.fontPointSize() < 0 )
357 {
358 // see above!!
359 updatedFont.setPixelSize( static_cast< int>( std::round( updatedFont.pixelSize() * QgsTextRenderer::SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR ) ) );
360 fm = QFontMetricsF( updatedFont );
361 }
362
363 // to match Qt behavior in QTextLine::draw
364 fragmentVerticalOffset = ( previousFM.ascent() + previousFM.descent() ) * SUBSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR / scaleFactor;
365
366 fragmentYMaxAdjust = fragmentVerticalOffset + fm.descent() / scaleFactor;
367 break;
368 }
369 }
370 }
371 else
372 {
373 thisBlockMetrics.previousNonSuperSubScriptFont = updatedFont;
374 }
375
376 auto updateCommonBlockMetrics = [ &fragmentVerticalOffset,
377 &fragmentYMaxAdjust,
378 &fragmentHeightForVerticallyOffsetText,
379 &updatedFont,
380 &fm,
381 scaleFactor]( BlockMetrics & thisBlockMetrics, double fragmentWidth, const QgsTextFragment & fragment )
382 {
383 thisBlockMetrics.fragmentVerticalOffsets << fragmentVerticalOffset;
384 thisBlockMetrics.blockYMaxAdjustLabel = std::max( thisBlockMetrics.blockYMaxAdjustLabel, fragmentYMaxAdjust );
385 thisBlockMetrics.blockHeightUsingAscentAccountingForVerticalOffset = std::max( std::max( thisBlockMetrics.maxBlockAscent, fragmentHeightForVerticallyOffsetText ), thisBlockMetrics.blockHeightUsingAscentAccountingForVerticalOffset );
386
387 thisBlockMetrics.fragmentHorizontalAdvance << fragmentWidth;
388
389 thisBlockMetrics.blockWidth += fragmentWidth;
390 thisBlockMetrics.blockXMax += fragmentWidth;
391
392 thisBlockMetrics.fragmentFonts << updatedFont;
393
394 const double verticalOrientationFragmentHeight = thisBlockMetrics.isFirstNonTabFragment ? ( fm.ascent() / scaleFactor * fragment.text().size() + ( fragment.text().size() - 1 ) * updatedFont.letterSpacing() / scaleFactor )
395 : ( fragment.text().size() * ( fm.ascent() / scaleFactor + updatedFont.letterSpacing() / scaleFactor ) );
396 thisBlockMetrics.blockHeightVerticalOrientation += verticalOrientationFragmentHeight;
397
398 thisBlockMetrics.isFirstNonTabFragment = false;
399 };
400
401 // calculate width of fragment
402 if ( fragment.isImage() )
403 {
404 double imageHeight = 0;
405 double imageWidth = 0;
406 if ( ( qgsDoubleNear( fragmentFormat.imageSize().width(), 0 ) || fragmentFormat.imageSize().width() < 0 )
407 && ( qgsDoubleNear( fragmentFormat.imageSize().height(), 0 ) || fragmentFormat.imageSize().height() < 0 ) )
408 {
409 // use original image size
410 const QSize imageSize = QgsApplication::imageCache()->originalSize( fragmentFormat.imagePath(), context.flags() & Qgis::RenderContextFlag::RenderBlocking );
411 // TODO: maybe there's more optimal logic we could use here, but for now we assume 96dpi image resolution...
412 const QSizeF originalSizeMmAt96Dpi = imageSize / 3.7795275590551185;
413 const double pixelsPerMm = context.scaleFactor();
414 imageWidth = originalSizeMmAt96Dpi.width() * pixelsPerMm;
415 imageHeight = originalSizeMmAt96Dpi.height() * pixelsPerMm;
416 }
417 else if ( ( qgsDoubleNear( fragmentFormat.imageSize().width(), 0 ) || fragmentFormat.imageSize().width() < 0 ) )
418 {
419 // height specified, calculate width
420 const QSize originalImageSize = QgsApplication::imageCache()->originalSize( fragmentFormat.imagePath(), context.flags() & Qgis::RenderContextFlag::RenderBlocking );
421 imageHeight = context.convertToPainterUnits( fragmentFormat.imageSize().height(), Qgis::RenderUnit::Points );
422 imageWidth = originalImageSize.width() * imageHeight / originalImageSize.height();
423 }
424 else if ( ( qgsDoubleNear( fragmentFormat.imageSize().height(), 0 ) || fragmentFormat.imageSize().height() < 0 ) )
425 {
426 // width specified, calculate height
427 const QSize originalImageSize = QgsApplication::imageCache()->originalSize( fragmentFormat.imagePath(), context.flags() & Qgis::RenderContextFlag::RenderBlocking );
428 imageWidth = context.convertToPainterUnits( fragmentFormat.imageSize().width(), Qgis::RenderUnit::Points );
429 imageHeight = originalImageSize.height() * imageWidth / originalImageSize.width();
430 }
431 else
432 {
433 imageWidth = context.convertToPainterUnits( fragmentFormat.imageSize().width(), Qgis::RenderUnit::Points );
434 imageHeight = context.convertToPainterUnits( fragmentFormat.imageSize().height(), Qgis::RenderUnit::Points );
435 }
436
437 // do we need to move this image fragment to a new block to respect wrapping?
438 if ( documentContext.flags() & Qgis::TextRendererFlag::WrapLines && documentContext.maximumWidth() > 0
439 && ( thisBlockMetrics.blockXMax + imageWidth > documentContext.maximumWidth() )
440 && !currentOutputBlock.empty() )
441 {
442 // yep, need to wrap before the image
443 finalizeBlock( res, format, documentMetrics, currentOutputBlock, thisBlockMetrics );
444 thisBlockMetrics.isFirstBlock = false;
445 }
446
447 // we consider the whole image as ascent, and descent as 0
448 thisBlockMetrics.blockHeightUsingAscentDescent = std::max( thisBlockMetrics.blockHeightUsingAscentDescent, imageHeight + fm.descent() / scaleFactor );
449 thisBlockMetrics.blockHeightUsingLineSpacing = std::max( thisBlockMetrics.blockHeightUsingLineSpacing, imageHeight + fm.leading() );
450
451 thisBlockMetrics.maxBlockAscent = std::max( thisBlockMetrics.maxBlockAscent, imageHeight );
452 thisBlockMetrics.maxBlockCapHeight = std::max( thisBlockMetrics.maxBlockCapHeight, imageHeight );
453 thisBlockMetrics.fragmentAscent << imageHeight;
454 thisBlockMetrics.fragmentDescent << 0;
455 thisBlockMetrics.maxLineSpacing = std::max( thisBlockMetrics.maxLineSpacing, imageHeight + fm.leading() / scaleFactor );
456 thisBlockMetrics.maxBlockLeading = std::max( thisBlockMetrics.maxBlockLeading, fm.leading() / scaleFactor );
457 thisBlockMetrics.maxBlockMaxWidth = std::max( thisBlockMetrics.maxBlockMaxWidth, imageWidth );
458 thisBlockMetrics.maxBlockFixedItemHeight = std::max( thisBlockMetrics.maxBlockFixedItemHeight, imageHeight );
459 thisBlockMetrics.fragmentFixedHeights << imageHeight;
460 updateCommonBlockMetrics( thisBlockMetrics, imageWidth, fragment );
461 currentOutputBlock.append( fragment );
462 }
463 else
464 {
465 const double fragmentHeightUsingAscentDescent = ( fm.ascent() + fm.descent() ) / scaleFactor;
466 const double fragmentHeightUsingLineSpacing = fm.lineSpacing() / scaleFactor;
467
468 auto finalizeTextFragment = [fragmentHeightUsingAscentDescent,
469 fragmentHeightUsingLineSpacing,
470 &fm,
471 scaleFactor,
472 &currentOutputBlock,
473 &updateCommonBlockMetrics
474 ]( BlockMetrics & thisBlockMetrics, const QgsTextFragment & fragment, double fragmentWidth )
475 {
476 thisBlockMetrics.blockHeightUsingAscentDescent = std::max( thisBlockMetrics.blockHeightUsingAscentDescent, fragmentHeightUsingAscentDescent );
477
478 thisBlockMetrics.blockHeightUsingLineSpacing = std::max( thisBlockMetrics.blockHeightUsingLineSpacing, fragmentHeightUsingLineSpacing );
479 const double ascent = fm.ascent() / scaleFactor;
480 thisBlockMetrics.fragmentAscent << ascent;
481 thisBlockMetrics.maxBlockAscent = std::max( thisBlockMetrics.maxBlockAscent, ascent );
482 thisBlockMetrics.maxBlockAscentForTextFragments = std::max( thisBlockMetrics.maxBlockAscentForTextFragments, ascent );
483
484 thisBlockMetrics.maxBlockCapHeight = std::max( thisBlockMetrics.maxBlockCapHeight, fm.capHeight() / scaleFactor );
485
486 const double descent = fm.descent() / scaleFactor;
487 thisBlockMetrics.fragmentDescent << descent;
488
489 thisBlockMetrics.maxBlockDescent = std::max( thisBlockMetrics.maxBlockDescent, descent );
490 thisBlockMetrics.maxBlockMaxWidth = std::max( thisBlockMetrics.maxBlockMaxWidth, fm.maxWidth() / scaleFactor );
491
492 if ( ( fm.lineSpacing() / scaleFactor ) > thisBlockMetrics.maxLineSpacing )
493 {
494 thisBlockMetrics.maxLineSpacing = fm.lineSpacing() / scaleFactor;
495 thisBlockMetrics.maxBlockLeading = fm.leading() / scaleFactor;
496 }
497 thisBlockMetrics.fragmentFixedHeights << -1;
498 updateCommonBlockMetrics( thisBlockMetrics, fragmentWidth, fragment );
499 currentOutputBlock.append( fragment );
500 };
501
502 double fragmentWidth = fm.horizontalAdvance( fragment.text() ) / scaleFactor;
503
504 // do we need to split this fragment to respect wrapping?
505 if ( documentContext.flags() & Qgis::TextRendererFlag::WrapLines && documentContext.maximumWidth() > 0
506 && ( thisBlockMetrics.blockXMax + fragmentWidth > documentContext.maximumWidth() ) )
507 {
508 // yep, need to split the fragment!
509
510 //first step is to identify words which must be on their own line (too long to fit)
511 const QStringList words = fragment.text().split( ' ' );
512 QStringList linesToProcess;
513 QStringList wordsInCurrentLine;
514 double remainingWidthInCurrentLine = documentContext.maximumWidth() - thisBlockMetrics.blockXMax;
515 for ( const QString &word : words )
516 {
517 const double wordWidth = fm.horizontalAdvance( word ) / scaleFactor;
518 if ( wordWidth > remainingWidthInCurrentLine )
519 {
520 //too long to fit
521 if ( !wordsInCurrentLine.isEmpty() )
522 linesToProcess << wordsInCurrentLine.join( ' ' );
523 wordsInCurrentLine.clear();
524 linesToProcess << word;
525 remainingWidthInCurrentLine = documentContext.maximumWidth();
526 }
527 else
528 {
529 wordsInCurrentLine.append( word );
530 }
531 }
532 if ( !wordsInCurrentLine.isEmpty() )
533 linesToProcess << wordsInCurrentLine.join( ' ' );
534
535 remainingWidthInCurrentLine = documentContext.maximumWidth() - thisBlockMetrics.blockXMax;
536 for ( int lineIndex = 0; lineIndex < linesToProcess.size(); ++lineIndex )
537 {
538 QString remainingText = linesToProcess.at( lineIndex );
539 int lastPos = remainingText.lastIndexOf( ' ' );
540 while ( lastPos > -1 )
541 {
542 //check if remaining text is short enough to go in one line
543 if ( ( fm.horizontalAdvance( remainingText ) / scaleFactor ) <= remainingWidthInCurrentLine )
544 {
545 break;
546 }
547
548 const double widthTextToLastPos = fm.horizontalAdvance( remainingText.left( lastPos ) ) / scaleFactor;
549 if ( widthTextToLastPos <= remainingWidthInCurrentLine )
550 {
551 QgsTextFragment thisLineFragment;
552 thisLineFragment.setCharacterFormat( fragment.characterFormat() );
553 thisLineFragment.setText( remainingText.left( lastPos ) );
554 finalizeTextFragment( thisBlockMetrics, thisLineFragment, widthTextToLastPos );
555 // move to new block
556 finalizeBlock( res, format, documentMetrics, currentOutputBlock, thisBlockMetrics );
557 thisBlockMetrics.isFirstBlock = false;
558 remainingWidthInCurrentLine = documentContext.maximumWidth();
559 remainingText = remainingText.mid( lastPos + 1 );
560 lastPos = 0;
561 }
562 lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
563 }
564
565 // if too big, and block is not empty, then flush current block first
566 if ( ( fm.horizontalAdvance( remainingText ) / scaleFactor ) > remainingWidthInCurrentLine && !currentOutputBlock.empty() )
567 {
568 finalizeBlock( res, format, documentMetrics, currentOutputBlock, thisBlockMetrics );
569 thisBlockMetrics.isFirstBlock = false;
570 remainingWidthInCurrentLine = documentContext.maximumWidth();
571 }
572
573 QgsTextFragment thisLineFragment;
574 thisLineFragment.setCharacterFormat( fragment.characterFormat() );
575 thisLineFragment.setText( remainingText );
576 finalizeTextFragment( thisBlockMetrics, thisLineFragment, fm.horizontalAdvance( remainingText ) / scaleFactor );
577
578 if ( lineIndex < linesToProcess.size() - 1 )
579 {
580 // start new block if we aren't at the last line
581 finalizeBlock( res, format, documentMetrics, currentOutputBlock, thisBlockMetrics );
582 thisBlockMetrics.isFirstBlock = false;
583 remainingWidthInCurrentLine = documentContext.maximumWidth();
584 }
585 }
586 }
587 else
588 {
589 // simple case, no wrapping
590 finalizeTextFragment( thisBlockMetrics, fragment, fragmentWidth );
591 }
592 }
593 }
594}
595
596QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor, const QgsTextDocumentRenderContext &documentContext )
597{
599
600 const QFont font = format.scaledFont( context, scaleFactor, &res.mIsNullSize );
601 if ( res.isNullFontSize() )
602 return res;
603
604 DocumentMetrics documentMetrics;
605
606 // for absolute line heights
607 const double documentLineHeightPainterUnits = context.convertToPainterUnits( format.lineHeight(), format.lineHeightUnit() );
608
610 ? format.tabStopDistance() * font.pixelSize() / scaleFactor
612
613 const QList< QgsTextFormat::Tab > tabPositions = format.tabPositions();
614 documentMetrics.tabStopDistancesPainterUnits.reserve( tabPositions.size() );
615 for ( const QgsTextFormat::Tab &tab : tabPositions )
616 {
617 documentMetrics.tabStopDistancesPainterUnits.append(
619 ? tab.position() * font.pixelSize() / scaleFactor
620 : context.convertToPainterUnits( tab.position(), format.tabStopDistanceUnit(), format.tabStopDistanceMapUnitScale() )
621 );
622 }
623
624 documentMetrics.blockSize = document.size();
625 res.mDocument.reserve( documentMetrics.blockSize );
626 res.mFragmentFonts.reserve( documentMetrics.blockSize );
627
628 for ( int blockIndex = 0; blockIndex < documentMetrics.blockSize; blockIndex++ )
629 {
630 const QgsTextBlock &block = document.at( blockIndex );
631 QgsTextBlock outputBlock;
632 outputBlock.setBlockFormat( block.blockFormat() );
633 outputBlock.reserve( block.size() );
634
635 const int fragmentSize = block.size();
636
637 BlockMetrics thisBlockMetrics;
638 thisBlockMetrics.lineHeightPainterUnits = documentLineHeightPainterUnits;
639 // apply block line height if set
640 if ( !std::isnan( block.blockFormat().lineHeightPercentage() ) )
641 {
642 thisBlockMetrics.lineHeightPercentage = block.blockFormat().lineHeightPercentage();
643 }
644 else if ( !std::isnan( block.blockFormat().lineHeight() ) )
645 {
647 }
648 else if ( format.lineHeightUnit() == Qgis::RenderUnit::Percentage )
649 {
650 thisBlockMetrics.lineHeightPercentage = format.lineHeight();
651 }
652
653 thisBlockMetrics.fragmentVerticalOffsets.reserve( fragmentSize );
654 thisBlockMetrics.fragmentFonts.reserve( fragmentSize );
655 thisBlockMetrics.fragmentHorizontalAdvance.reserve( fragmentSize );
656 thisBlockMetrics.fragmentFixedHeights.reserve( fragmentSize );
657
658 thisBlockMetrics.isFirstBlock = blockIndex == 0;
659 thisBlockMetrics.isLastBlock = blockIndex == documentMetrics.blockSize - 1;
660
661 thisBlockMetrics.marginTop = context.convertToPainterUnits(
662 !std::isnan( block.blockFormat().margins().top() ) ? block.blockFormat().margins().top() : 0, Qgis::RenderUnit::Points );
663 thisBlockMetrics.marginBottom = context.convertToPainterUnits(
664 !std::isnan( block.blockFormat().margins().bottom() ) ? block.blockFormat().margins().bottom() : 0, Qgis::RenderUnit::Points );
665 thisBlockMetrics.marginLeft = context.convertToPainterUnits(
666 !std::isnan( block.blockFormat().margins().left() ) ? block.blockFormat().margins().left() : 0, Qgis::RenderUnit::Points );
667 thisBlockMetrics.marginRight = context.convertToPainterUnits(
668 !std::isnan( block.blockFormat().margins().right() ) ? block.blockFormat().margins().right() : 0, Qgis::RenderUnit::Points );
669
670 for ( int fragmentIndex = 0; fragmentIndex < fragmentSize; ++fragmentIndex )
671 {
672 const QgsTextFragment &fragment = block.at( fragmentIndex );
673 processFragment( res, format, context, documentContext, scaleFactor, documentMetrics, thisBlockMetrics, font, fragment, outputBlock );
674 }
675
676 finalizeBlock( res, format, documentMetrics, outputBlock, thisBlockMetrics );
677 }
678
679 documentMetrics.heightLabelMode -= documentMetrics.lastLineLeading;
680 documentMetrics.heightPointRectMode -= documentMetrics.lastLineLeading;
681
682 res.mDocumentSizeLabelMode = QSizeF( documentMetrics.width, documentMetrics.heightLabelMode );
683 res.mDocumentSizePointRectMode = QSizeF( documentMetrics.width, documentMetrics.heightPointRectMode );
684 res.mDocumentSizeCapHeightMode = QSizeF( documentMetrics.width, documentMetrics.heightCapHeightMode );
685 res.mDocumentSizeAscentMode = QSizeF( documentMetrics.width, documentMetrics.heightAscentMode );
686
687 // adjust baselines
688 if ( !res.mBaselineOffsetsLabelMode.isEmpty() )
689 {
690 const double labelModeBaselineAdjust = res.mBaselineOffsetsLabelMode.constLast() + res.mLastLineAscentOffset;
691 const double pointModeBaselineAdjust = res.mBaselineOffsetsPointMode.constLast();
692 for ( int i = 0; i < documentMetrics.blockSize; ++i )
693 {
694 res.mBaselineOffsetsLabelMode[i] -= labelModeBaselineAdjust;
695 res.mBaselineOffsetsPointMode[i] -= pointModeBaselineAdjust;
696 }
697 }
698
699 if ( !res.mBlockMaxCharacterWidth.isEmpty() )
700 {
701 QList< double > adjustedRightToLeftXOffsets;
702 double currentOffset = 0;
703 const int size = res.mBlockMaxCharacterWidth.size();
704
705 double widthVerticalOrientation = 0;
706 for ( int i = 0; i < size; ++i )
707 {
708 const double rightToLeftBlockMaxCharacterWidth = res.mBlockMaxCharacterWidth[size - 1 - i ];
709 const double rightToLeftLineSpacing = documentMetrics.blockVerticalLineSpacing[ size - 1 - i ];
710
711 adjustedRightToLeftXOffsets << currentOffset;
712 currentOffset += rightToLeftLineSpacing;
713
714 if ( i == size - 1 )
715 widthVerticalOrientation += rightToLeftBlockMaxCharacterWidth;
716 else
717 widthVerticalOrientation += rightToLeftLineSpacing;
718 }
719 std::reverse( adjustedRightToLeftXOffsets.begin(), adjustedRightToLeftXOffsets.end() );
720 res.mVerticalOrientationXOffsets = adjustedRightToLeftXOffsets;
721
722 res.mDocumentSizeVerticalOrientation = QSizeF( widthVerticalOrientation, documentMetrics.heightVerticalOrientation );
723 }
724
725 res.mVerticalMarginsBetweenBlocks = documentMetrics.verticalMarginsBetweenBlocks;
726 res.mLeftBlockMargins = documentMetrics.blockLeftMargin;
727 res.mRightBlockMargins = documentMetrics.blockRightMargin;
728
729 res.mOuterBoundsLabelMode = QRectF( documentMetrics.outerXMin, -documentMetrics.outerYMaxLabel,
730 documentMetrics.outerXMax - documentMetrics.outerXMin,
731 documentMetrics.heightLabelMode - documentMetrics.outerYMinLabel + documentMetrics.outerYMaxLabel );
732
733 return res;
734}
735
737{
738 switch ( orientation )
739 {
741 switch ( mode )
742 {
745 return mDocumentSizePointRectMode;
746
748 return mDocumentSizeCapHeightMode;
749
751 return mDocumentSizeAscentMode;
752
754 return mDocumentSizeLabelMode;
755 };
757
759 return mDocumentSizeVerticalOrientation;
761 return QSizeF(); // label mode only
762 }
763
765}
766
768{
769 switch ( orientation )
770 {
772 switch ( mode )
773 {
778 return QRectF();
779
781 return mOuterBoundsLabelMode;
782 };
784
787 return QRectF(); // label mode only
788 }
789
791}
792
793double QgsTextDocumentMetrics::blockWidth( int blockIndex ) const
794{
795 return mBlockWidths.value( blockIndex );
796}
797
798double QgsTextDocumentMetrics::blockHeight( int blockIndex ) const
799{
800 return mBlockHeights.value( blockIndex );
801}
802
804{
805 return mFirstLineCapHeight;
806}
807
809{
810 double verticalAdjustmentForBlockMargins = 0;
811 for ( int i = 0; i < blockIndex; ++i )
812 {
813 double marginBeforeBlock = 0;
814 verticalAdjustmentForBlockMargins += marginBeforeBlock;
815 }
816
817 switch ( mode )
818 {
820 return mBaselineOffsetsRectMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
822 return mBaselineOffsetsCapHeightMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
824 return mBaselineOffsetsAscentBased.value( blockIndex ) + verticalAdjustmentForBlockMargins;
826 return mBaselineOffsetsPointMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
828 return mBaselineOffsetsLabelMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
829 }
831}
832
833double QgsTextDocumentMetrics::fragmentHorizontalAdvance( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
834{
835 return mFragmentHorizontalAdvance.value( blockIndex ).value( fragmentIndex );
836}
837
838double QgsTextDocumentMetrics::fragmentVerticalOffset( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const
839{
840 switch ( mode )
841 {
845 return mFragmentVerticalOffsetsRectMode.value( blockIndex ).value( fragmentIndex );
847 return mFragmentVerticalOffsetsPointMode.value( blockIndex ).value( fragmentIndex );
849 return mFragmentVerticalOffsetsLabelMode.value( blockIndex ).value( fragmentIndex );
850 }
852}
853
854double QgsTextDocumentMetrics::fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
855{
856 return mFragmentFixedHeights.value( blockIndex ).value( fragmentIndex );
857}
858
859double QgsTextDocumentMetrics::fragmentAscent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
860{
861 return mFragmentAscent.value( blockIndex ).value( fragmentIndex );
862}
863
864double QgsTextDocumentMetrics::fragmentDescent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
865{
866 return mFragmentDescent.value( blockIndex ).value( fragmentIndex );
867}
868
870{
871 return mVerticalOrientationXOffsets.value( blockIndex );
872}
873
875{
876 return mBlockMaxCharacterWidth.value( blockIndex );
877}
878
880{
881 return mBlockMaxDescent.value( blockIndex );
882}
883
884double QgsTextDocumentMetrics::blockMaximumAscent( int blockIndex ) const
885{
886 return mBlockMaxAscent.value( blockIndex );
887}
888
889QFont QgsTextDocumentMetrics::fragmentFont( int blockIndex, int fragmentIndex ) const
890{
891 return mFragmentFonts.value( blockIndex ).value( fragmentIndex );
892}
893
895{
896 if ( blockIndex < 0 )
897 return mVerticalMarginsBetweenBlocks.value( 0 );
898
899 return mVerticalMarginsBetweenBlocks.value( blockIndex + 1 );
900}
901
902double QgsTextDocumentMetrics::blockLeftMargin( int blockIndex ) const
903{
904 return mLeftBlockMargins.value( blockIndex );
905}
906
907double QgsTextDocumentMetrics::blockRightMargin( int blockIndex ) const
908{
909 return mRightBlockMargins.value( blockIndex );
910}
911
TextLayoutMode
Text layout modes.
Definition qgis.h:2782
@ Labeling
Labeling-specific layout mode.
@ Point
Text at point of origin layout mode.
@ RectangleAscentBased
Similar to Rectangle mode, but uses ascents only when calculating font and line heights.
@ RectangleCapHeightBased
Similar to Rectangle mode, but uses cap height only when calculating font heights for the first line ...
@ Rectangle
Text within rectangle layout mode.
TextOrientation
Text orientations.
Definition qgis.h:2767
@ Vertical
Vertically oriented text.
@ RotationBased
Horizontally or vertically oriented text based on rotation (only available for map labeling)
@ Horizontal
Horizontally oriented text.
@ Normal
Adjacent characters are positioned in the standard way for text in the writing system in use.
@ SubScript
Characters are placed below the base line for normal text.
@ SuperScript
Characters are placed above the base line for normal text.
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Points
Points (e.g., for font sizes)
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ WrapLines
Automatically wrap long lines of text.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
double top() const
Returns the top margin.
Definition qgsmargins.h:77
double right() const
Returns the right margin.
Definition qgsmargins.h:83
double bottom() const
Returns the bottom margin.
Definition qgsmargins.h:89
double left() const
Returns the left margin.
Definition qgsmargins.h:71
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
double lineHeight() const
Returns the line height in points, or NaN if the line height is not set and should be auto calculated...
double lineHeightPercentage() const
Returns the line height percentage size (as fraction of font size from 0.0 to 1.0),...
QgsMargins margins() const
Returns the block margins, in points.
Represents a block of text consisting of one or more QgsTextFragment objects.
int size() const
Returns the number of fragments in the block.
const QgsTextBlockFormat & blockFormat() const
Returns the block formatting for the fragment.
void clear()
Clears the block, removing all its contents.
void reserve(int count)
Reserves the specified count of fragments for optimised fragment appending.
void setBlockFormat(const QgsTextBlockFormat &format)
Sets the block format for the fragment.
void append(const QgsTextFragment &fragment)
Appends a fragment to the block.
const QgsTextFragment & at(int index) const
Returns the fragment at the specified index.
bool empty() const
Returns true if the block is empty.
Stores information relating to individual character formatting.
void updateFontForFormat(QFont &font, const QgsRenderContext &context, double scaleFactor=1.0) const
Updates the specified font in place, applying character formatting options which are applicable on a ...
QSizeF imageSize() const
Returns the image size, if the format applies to a document image fragment.
QString imagePath() const
Returns the path to the image to render, if the format applies to a document image fragment.
Qgis::TextCharacterVerticalAlignment verticalAlignment() const
Returns the format vertical alignment.
bool hasVerticalAlignmentSet() const
Returns true if the format has an explicit vertical alignment set.
double fontPointSize() const
Returns the font point size, or -1 if the font size is not set and should be inherited.
Contains pre-calculated metrics of a QgsTextDocument.
double verticalOrientationXOffset(int blockIndex) const
Returns the vertical orientation x offset for the specified block.
double fragmentVerticalOffset(int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode) const
Returns the vertical offset from a text block's baseline which should be applied to the fragment at t...
double blockMaximumDescent(int blockIndex) const
Returns the maximum descent encountered in the specified block.
double fragmentDescent(int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode) const
Returns the descent of the fragment at the specified block and fragment index.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
double blockRightMargin(int blockIndex) const
Returns the margin for the right side of the specified block index.
double firstLineCapHeight() const
Returns the cap height for the first line of text.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0, const QgsTextDocumentRenderContext &documentContext=QgsTextDocumentRenderContext())
Returns precalculated text metrics for a text document, when rendered using the given base format and...
QFont fragmentFont(int blockIndex, int fragmentIndex) const
Returns the calculated font for the fragment at the specified block and fragment indices.
double blockMaximumCharacterWidth(int blockIndex) const
Returns the maximum character width for the specified block.
double baselineOffset(int blockIndex, Qgis::TextLayoutMode mode) const
Returns the offset from the top of the document to the text baseline for the given block index.
double fragmentFixedHeight(int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode) const
Returns the fixed height of the fragment at the specified block and fragment index,...
QRectF outerBounds(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the outer bounds of the document, which is the documentSize() adjusted to account for any tex...
double blockLeftMargin(int blockIndex) const
Returns the margin for the left side of the specified block index.
double blockMaximumAscent(int blockIndex) const
Returns the maximum ascent encountered in the specified block.
double fragmentAscent(int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode) const
Returns the ascent of the fragment at the specified block and fragment index.
double blockHeight(int blockIndex) const
Returns the height of the block at the specified index.
double fragmentHorizontalAdvance(int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode) const
Returns the horizontal advance of the fragment at the specified block and fragment index.
bool isNullFontSize() const
Returns true if the metrics could not be calculated because the text format has a null font size.
const QgsTextDocument & document() const
Returns the document associated with the calculated metrics.
double blockWidth(int blockIndex) const
Returns the width of the block at the specified index.
double blockVerticalMargin(int blockIndex) const
Returns the vertical margin for the specified block index.
Encapsulates the context in which a text document is to be rendered.
Qgis::TextRendererFlags flags() const
Returns associated text renderer flags.
double maximumWidth() const
Returns the maximum width (in painter units) for rendered text.
Represents a document consisting of one or more QgsTextBlock objects.
const QgsTextBlock & at(int index) const
Returns the block at the specified index.
void reserve(int count)
Reserves the specified count of blocks for optimised block appending.
int size() const
Returns the number of blocks in the document.
void append(const QgsTextBlock &block)
Appends a block to the document.
Defines a tab position for a text format.
Container for all settings relating to text rendering.
QList< QgsTextFormat::Tab > tabPositions() const
Returns the list of tab positions for tab stops.
double lineHeight() const
Returns the line height for text.
double tabStopDistance() const
Returns the distance for tab stops.
QFont scaledFont(const QgsRenderContext &context, double scaleFactor=1.0, bool *isZeroSize=nullptr) const
Returns a font with the size scaled to match the format's size settings (including units and map unit...
Qgis::RenderUnit lineHeightUnit() const
Returns the units for the line height for text.
Qgis::RenderUnit tabStopDistanceUnit() const
Returns the units for the tab stop distance.
QgsMapUnitScale tabStopDistanceMapUnitScale() const
Returns the map unit scale object for the tab stop distance.
Stores a fragment of document along with formatting overrides to be used when rendering the fragment.
void setText(const QString &text)
Sets the text content of the fragment.
QString text() const
Returns the text content of the fragment.
void setCharacterFormat(const QgsTextCharacterFormat &format)
Sets the character format for the fragment.
const QgsTextCharacterFormat & characterFormat() const
Returns the character formatting for the fragment.
bool isImage() const
Returns true if the fragment represents an image.
bool isTab() const
Returns true if the fragment consists of just a tab character.
static constexpr double SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR
Scale factor to use for super or subscript text which doesn't have an explicit font size set.
#define BUILTIN_UNREACHABLE
Definition qgis.h:6779
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6125
constexpr double SUPERSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR
constexpr double SUBSCRIPT_VERTICAL_BASELINE_ADJUSTMENT_FACTOR
QList< QFont > fragmentFonts
QList< double > fragmentDescent
QList< double > fragmentFixedHeights
QList< double > fragmentHorizontalAdvance
double blockHeightUsingAscentAccountingForVerticalOffset
QList< double > fragmentAscent
QList< double > fragmentVerticalOffsets
QList< double > tabStopDistancesPainterUnits
QVector< double > blockRightMargin
QVector< double > blockLeftMargin
QVector< double > blockVerticalLineSpacing
QVector< double > verticalMarginsBetweenBlocks
Calculated vertical margins between blocks.