QGIS API Documentation 3.41.0-Master (af5edcb665c)
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 thisBlockMetrics.isFirstBlock = false;
587 }
588 }
589 else
590 {
591 // simple case, no wrapping
592 finalizeTextFragment( thisBlockMetrics, fragment, fragmentWidth );
593 }
594 }
595 }
596}
597
598QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor, const QgsTextDocumentRenderContext &documentContext )
599{
601
602 const QFont font = format.scaledFont( context, scaleFactor, &res.mIsNullSize );
603 if ( res.isNullFontSize() )
604 return res;
605
606 DocumentMetrics documentMetrics;
607
608 // for absolute line heights
609 const double documentLineHeightPainterUnits = context.convertToPainterUnits( format.lineHeight(), format.lineHeightUnit() );
610
612 ? format.tabStopDistance() * font.pixelSize() / scaleFactor
614
615 const QList< QgsTextFormat::Tab > tabPositions = format.tabPositions();
616 documentMetrics.tabStopDistancesPainterUnits.reserve( tabPositions.size() );
617 for ( const QgsTextFormat::Tab &tab : tabPositions )
618 {
619 documentMetrics.tabStopDistancesPainterUnits.append(
621 ? tab.position() * font.pixelSize() / scaleFactor
622 : context.convertToPainterUnits( tab.position(), format.tabStopDistanceUnit(), format.tabStopDistanceMapUnitScale() )
623 );
624 }
625
626 documentMetrics.blockSize = document.size();
627 res.mDocument.reserve( documentMetrics.blockSize );
628 res.mFragmentFonts.reserve( documentMetrics.blockSize );
629
630 for ( int blockIndex = 0; blockIndex < documentMetrics.blockSize; blockIndex++ )
631 {
632 const QgsTextBlock &block = document.at( blockIndex );
633 QgsTextBlock outputBlock;
634 outputBlock.setBlockFormat( block.blockFormat() );
635 outputBlock.reserve( block.size() );
636
637 const int fragmentSize = block.size();
638
639 BlockMetrics thisBlockMetrics;
640 thisBlockMetrics.lineHeightPainterUnits = documentLineHeightPainterUnits;
641 // apply block line height if set
642 if ( !std::isnan( block.blockFormat().lineHeightPercentage() ) )
643 {
644 thisBlockMetrics.lineHeightPercentage = block.blockFormat().lineHeightPercentage();
645 }
646 else if ( !std::isnan( block.blockFormat().lineHeight() ) )
647 {
649 }
650 else if ( format.lineHeightUnit() == Qgis::RenderUnit::Percentage )
651 {
652 thisBlockMetrics.lineHeightPercentage = format.lineHeight();
653 }
654
655 thisBlockMetrics.fragmentVerticalOffsets.reserve( fragmentSize );
656 thisBlockMetrics.fragmentFonts.reserve( fragmentSize );
657 thisBlockMetrics.fragmentHorizontalAdvance.reserve( fragmentSize );
658 thisBlockMetrics.fragmentFixedHeights.reserve( fragmentSize );
659
660 thisBlockMetrics.isFirstBlock = blockIndex == 0;
661 thisBlockMetrics.isLastBlock = blockIndex == documentMetrics.blockSize - 1;
662
663 thisBlockMetrics.marginTop = context.convertToPainterUnits(
664 !std::isnan( block.blockFormat().margins().top() ) ? block.blockFormat().margins().top() : 0, Qgis::RenderUnit::Points );
665 thisBlockMetrics.marginBottom = context.convertToPainterUnits(
666 !std::isnan( block.blockFormat().margins().bottom() ) ? block.blockFormat().margins().bottom() : 0, Qgis::RenderUnit::Points );
667 thisBlockMetrics.marginLeft = context.convertToPainterUnits(
668 !std::isnan( block.blockFormat().margins().left() ) ? block.blockFormat().margins().left() : 0, Qgis::RenderUnit::Points );
669 thisBlockMetrics.marginRight = context.convertToPainterUnits(
670 !std::isnan( block.blockFormat().margins().right() ) ? block.blockFormat().margins().right() : 0, Qgis::RenderUnit::Points );
671
672 for ( int fragmentIndex = 0; fragmentIndex < fragmentSize; ++fragmentIndex )
673 {
674 const QgsTextFragment &fragment = block.at( fragmentIndex );
675 processFragment( res, format, context, documentContext, scaleFactor, documentMetrics, thisBlockMetrics, font, fragment, outputBlock );
676 }
677
678 finalizeBlock( res, format, documentMetrics, outputBlock, thisBlockMetrics );
679 }
680
681 documentMetrics.heightLabelMode -= documentMetrics.lastLineLeading;
682 documentMetrics.heightPointRectMode -= documentMetrics.lastLineLeading;
683
684 res.mDocumentSizeLabelMode = QSizeF( documentMetrics.width, documentMetrics.heightLabelMode );
685 res.mDocumentSizePointRectMode = QSizeF( documentMetrics.width, documentMetrics.heightPointRectMode );
686 res.mDocumentSizeCapHeightMode = QSizeF( documentMetrics.width, documentMetrics.heightCapHeightMode );
687 res.mDocumentSizeAscentMode = QSizeF( documentMetrics.width, documentMetrics.heightAscentMode );
688
689 // adjust baselines
690 if ( !res.mBaselineOffsetsLabelMode.isEmpty() )
691 {
692 const double labelModeBaselineAdjust = res.mBaselineOffsetsLabelMode.constLast() + res.mLastLineAscentOffset;
693 const double pointModeBaselineAdjust = res.mBaselineOffsetsPointMode.constLast();
694 for ( int i = 0; i < documentMetrics.blockSize; ++i )
695 {
696 res.mBaselineOffsetsLabelMode[i] -= labelModeBaselineAdjust;
697 res.mBaselineOffsetsPointMode[i] -= pointModeBaselineAdjust;
698 }
699 }
700
701 if ( !res.mBlockMaxCharacterWidth.isEmpty() )
702 {
703 QList< double > adjustedRightToLeftXOffsets;
704 double currentOffset = 0;
705 const int size = res.mBlockMaxCharacterWidth.size();
706
707 double widthVerticalOrientation = 0;
708 for ( int i = 0; i < size; ++i )
709 {
710 const double rightToLeftBlockMaxCharacterWidth = res.mBlockMaxCharacterWidth[size - 1 - i ];
711 const double rightToLeftLineSpacing = documentMetrics.blockVerticalLineSpacing[ size - 1 - i ];
712
713 adjustedRightToLeftXOffsets << currentOffset;
714 currentOffset += rightToLeftLineSpacing;
715
716 if ( i == size - 1 )
717 widthVerticalOrientation += rightToLeftBlockMaxCharacterWidth;
718 else
719 widthVerticalOrientation += rightToLeftLineSpacing;
720 }
721 std::reverse( adjustedRightToLeftXOffsets.begin(), adjustedRightToLeftXOffsets.end() );
722 res.mVerticalOrientationXOffsets = adjustedRightToLeftXOffsets;
723
724 res.mDocumentSizeVerticalOrientation = QSizeF( widthVerticalOrientation, documentMetrics.heightVerticalOrientation );
725 }
726
727 res.mVerticalMarginsBetweenBlocks = documentMetrics.verticalMarginsBetweenBlocks;
728 res.mLeftBlockMargins = documentMetrics.blockLeftMargin;
729 res.mRightBlockMargins = documentMetrics.blockRightMargin;
730
731 res.mOuterBoundsLabelMode = QRectF( documentMetrics.outerXMin, -documentMetrics.outerYMaxLabel,
732 documentMetrics.outerXMax - documentMetrics.outerXMin,
733 documentMetrics.heightLabelMode - documentMetrics.outerYMinLabel + documentMetrics.outerYMaxLabel );
734
735 return res;
736}
737
739{
740 switch ( orientation )
741 {
743 switch ( mode )
744 {
747 return mDocumentSizePointRectMode;
748
750 return mDocumentSizeCapHeightMode;
751
753 return mDocumentSizeAscentMode;
754
756 return mDocumentSizeLabelMode;
757 };
759
761 return mDocumentSizeVerticalOrientation;
763 return QSizeF(); // label mode only
764 }
765
767}
768
770{
771 switch ( orientation )
772 {
774 switch ( mode )
775 {
780 return QRectF();
781
783 return mOuterBoundsLabelMode;
784 };
786
789 return QRectF(); // label mode only
790 }
791
793}
794
795double QgsTextDocumentMetrics::blockWidth( int blockIndex ) const
796{
797 return mBlockWidths.value( blockIndex );
798}
799
800double QgsTextDocumentMetrics::blockHeight( int blockIndex ) const
801{
802 return mBlockHeights.value( blockIndex );
803}
804
806{
807 return mFirstLineCapHeight;
808}
809
811{
812 double verticalAdjustmentForBlockMargins = 0;
813 for ( int i = 0; i < blockIndex; ++i )
814 {
815 double marginBeforeBlock = 0;
816 verticalAdjustmentForBlockMargins += marginBeforeBlock;
817 }
818
819 switch ( mode )
820 {
822 return mBaselineOffsetsRectMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
824 return mBaselineOffsetsCapHeightMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
826 return mBaselineOffsetsAscentBased.value( blockIndex ) + verticalAdjustmentForBlockMargins;
828 return mBaselineOffsetsPointMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
830 return mBaselineOffsetsLabelMode.value( blockIndex ) + verticalAdjustmentForBlockMargins;
831 }
833}
834
835double QgsTextDocumentMetrics::fragmentHorizontalAdvance( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
836{
837 return mFragmentHorizontalAdvance.value( blockIndex ).value( fragmentIndex );
838}
839
840double QgsTextDocumentMetrics::fragmentVerticalOffset( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode mode ) const
841{
842 switch ( mode )
843 {
847 return mFragmentVerticalOffsetsRectMode.value( blockIndex ).value( fragmentIndex );
849 return mFragmentVerticalOffsetsPointMode.value( blockIndex ).value( fragmentIndex );
851 return mFragmentVerticalOffsetsLabelMode.value( blockIndex ).value( fragmentIndex );
852 }
854}
855
856double QgsTextDocumentMetrics::fragmentFixedHeight( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
857{
858 return mFragmentFixedHeights.value( blockIndex ).value( fragmentIndex );
859}
860
861double QgsTextDocumentMetrics::fragmentAscent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
862{
863 return mFragmentAscent.value( blockIndex ).value( fragmentIndex );
864}
865
866double QgsTextDocumentMetrics::fragmentDescent( int blockIndex, int fragmentIndex, Qgis::TextLayoutMode ) const
867{
868 return mFragmentDescent.value( blockIndex ).value( fragmentIndex );
869}
870
872{
873 return mVerticalOrientationXOffsets.value( blockIndex );
874}
875
877{
878 return mBlockMaxCharacterWidth.value( blockIndex );
879}
880
882{
883 return mBlockMaxDescent.value( blockIndex );
884}
885
886double QgsTextDocumentMetrics::blockMaximumAscent( int blockIndex ) const
887{
888 return mBlockMaxAscent.value( blockIndex );
889}
890
891QFont QgsTextDocumentMetrics::fragmentFont( int blockIndex, int fragmentIndex ) const
892{
893 return mFragmentFonts.value( blockIndex ).value( fragmentIndex );
894}
895
897{
898 if ( blockIndex < 0 )
899 return mVerticalMarginsBetweenBlocks.value( 0 );
900
901 return mVerticalMarginsBetweenBlocks.value( blockIndex + 1 );
902}
903
904double QgsTextDocumentMetrics::blockLeftMargin( int blockIndex ) const
905{
906 return mLeftBlockMargins.value( blockIndex );
907}
908
909double QgsTextDocumentMetrics::blockRightMargin( int blockIndex ) const
910{
911 return mRightBlockMargins.value( blockIndex );
912}
913
TextLayoutMode
Text layout modes.
Definition qgis.h:2762
@ 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:2747
@ 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:6720
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
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.