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