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