QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
24#include <QFontMetricsF>
25
26QgsTextDocumentMetrics QgsTextDocumentMetrics::calculateMetrics( const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor )
27{
29
30 bool isNullSize = false;
31 const QFont font = format.scaledFont( context, scaleFactor, &isNullSize );
32 if ( isNullSize )
33 return res;
34
35 // for absolute line heights
36 const double lineHeightPainterUnits = context.convertToPainterUnits( format.lineHeight(), format.lineHeightUnit() );
37
38 double width = 0;
39 double heightLabelMode = 0;
40 double heightPointRectMode = 0;
41 const int blockSize = document.size();
42 res.mFragmentFonts.reserve( blockSize );
43 double currentLabelBaseline = 0;
44 double currentPointBaseline = 0;
45 double currentRectBaseline = 0;
46 double lastLineLeading = 0;
47
48 double heightVerticalOrientation = 0;
49
50 QVector < double > blockVerticalLineSpacing;
51
52 for ( int blockIndex = 0; blockIndex < blockSize; blockIndex++ )
53 {
54 const QgsTextBlock &block = document.at( blockIndex );
55
56 double blockWidth = 0;
57 double blockHeightUsingAscentDescent = 0;
58 double blockHeightUsingLineSpacing = 0;
59 double blockHeightVerticalOrientation = 0;
60 const int fragmentSize = block.size();
61
62 double maxBlockAscent = 0;
63 double maxBlockDescent = 0;
64 double maxLineSpacing = 0;
65 double maxBlockLeading = 0;
66 double maxBlockMaxWidth = 0;
67
68 QList< QFont > fragmentFonts;
69 fragmentFonts.reserve( fragmentSize );
70 for ( int fragmentIndex = 0; fragmentIndex < fragmentSize; ++fragmentIndex )
71 {
72 const QgsTextFragment &fragment = block.at( fragmentIndex );
73
74 QFont updatedFont = font;
75 fragment.characterFormat().updateFontForFormat( updatedFont, context, scaleFactor );
76 const QFontMetricsF fm( updatedFont );
77
78 const double fragmentWidth = fm.horizontalAdvance( fragment.text() ) / scaleFactor;
79 const double fragmentHeightUsingAscentDescent = ( fm.ascent() + fm.descent() ) / scaleFactor;
80 const double fragmentHeightUsingLineSpacing = fm.lineSpacing() / scaleFactor;
81
82 blockWidth += fragmentWidth;
83 blockHeightUsingAscentDescent = std::max( blockHeightUsingAscentDescent, fragmentHeightUsingAscentDescent );
84 blockHeightUsingLineSpacing = std::max( blockHeightUsingLineSpacing, fragmentHeightUsingLineSpacing );
85 maxBlockAscent = std::max( maxBlockAscent, fm.ascent() / scaleFactor );
86 maxBlockDescent = std::max( maxBlockDescent, fm.descent() / scaleFactor );
87 maxBlockMaxWidth = std::max( maxBlockMaxWidth, fm.maxWidth() / scaleFactor );
88
89 if ( ( fm.lineSpacing() / scaleFactor ) > maxLineSpacing )
90 {
91 maxLineSpacing = fm.lineSpacing() / scaleFactor;
92 maxBlockLeading = fm.leading() / scaleFactor;
93 }
94
95 fragmentFonts << updatedFont;
96
97 const double verticalOrientationFragmentHeight = fragmentIndex == 0 ? ( fm.ascent() / scaleFactor * fragment.text().size() + ( fragment.text().size() - 1 ) * updatedFont.letterSpacing() / scaleFactor )
98 : ( fragment.text().size() * ( fm.ascent() / scaleFactor + updatedFont.letterSpacing() / scaleFactor ) );
99 blockHeightVerticalOrientation += verticalOrientationFragmentHeight;
100 }
101
102 if ( blockIndex == 0 )
103 {
104 // same logic as used in QgsTextRenderer. (?!!)
105 // needed to move bottom of text's descender to within bottom edge of label
106 res.mFirstLineAscentOffset = 0.25 * maxBlockAscent; // descent() is not enough
107 res.mLastLineAscentOffset = res.mFirstLineAscentOffset;
108 const double lineHeight = ( maxBlockAscent + maxBlockDescent ); // ignore +1 for baseline
109
110 // rendering labels needs special handling - in this case text should be
111 // drawn with the bottom left corner coinciding with origin, vs top left
112 // for standard text rendering. Line height is also slightly different.
113 currentLabelBaseline = -res.mFirstLineAscentOffset;
114
115 // standard rendering - designed to exactly replicate QPainter's drawText method
116 currentRectBaseline = -res.mFirstLineAscentOffset + lineHeight - 1 /*baseline*/;
117
118 // standard rendering - designed to exactly replicate QPainter's drawText rect method
119 currentPointBaseline = 0;
120
121 heightLabelMode += blockHeightUsingAscentDescent;
122 heightPointRectMode += blockHeightUsingAscentDescent;
123 }
124 else
125 {
126 const double thisLineHeightUsingAscentDescent = format.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( format.lineHeight() * ( maxBlockAscent + maxBlockDescent ) ) : lineHeightPainterUnits;
127 const double thisLineHeightUsingLineSpacing = format.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( format.lineHeight() * maxLineSpacing ) : lineHeightPainterUnits;
128
129 currentLabelBaseline += thisLineHeightUsingAscentDescent;
130 currentRectBaseline += thisLineHeightUsingLineSpacing;
131 currentPointBaseline += thisLineHeightUsingLineSpacing;
132
133 heightLabelMode += thisLineHeightUsingAscentDescent;
134 heightPointRectMode += thisLineHeightUsingLineSpacing;
135 if ( blockIndex == blockSize - 1 )
136 res.mLastLineAscentOffset = 0.25 * maxBlockAscent;
137 }
138
139 blockVerticalLineSpacing << ( format.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( maxBlockMaxWidth * format.lineHeight() ) : lineHeightPainterUnits );
140
141 res.mBlockHeights << blockHeightUsingLineSpacing;
142
143 width = std::max( width, blockWidth );
144 heightVerticalOrientation = std::max( heightVerticalOrientation, blockHeightVerticalOrientation );
145 res.mBlockWidths << blockWidth;
146 res.mFragmentFonts << fragmentFonts;
147 res.mBaselineOffsetsLabelMode << currentLabelBaseline;
148 res.mBaselineOffsetsPointMode << currentPointBaseline;
149 res.mBaselineOffsetsRectMode << currentRectBaseline;
150 res.mBlockMaxDescent << maxBlockDescent;
151 res.mBlockMaxCharacterWidth << maxBlockMaxWidth;
152
153 if ( blockIndex > 0 )
154 lastLineLeading = maxBlockLeading;
155 }
156
157 heightLabelMode -= lastLineLeading;
158 heightPointRectMode -= lastLineLeading;
159
160 res.mDocumentSizeLabelMode = QSizeF( width, heightLabelMode );
161 res.mDocumentSizePointRectMode = QSizeF( width, heightPointRectMode );
162
163 // adjust baselines
164 if ( !res.mBaselineOffsetsLabelMode.isEmpty() )
165 {
166 const double labelModeBaselineAdjust = res.mBaselineOffsetsLabelMode.constLast() + res.mLastLineAscentOffset;
167 const double pointModeBaselineAdjust = res.mBaselineOffsetsPointMode.constLast();
168 for ( int i = 0; i < blockSize; ++i )
169 {
170 res.mBaselineOffsetsLabelMode[i] -= labelModeBaselineAdjust;
171 res.mBaselineOffsetsPointMode[i] -= pointModeBaselineAdjust;
172 }
173 }
174
175 if ( !res.mBlockMaxCharacterWidth.isEmpty() )
176 {
177 QList< double > adjustedRightToLeftXOffsets;
178 double currentOffset = 0;
179 const int size = res.mBlockMaxCharacterWidth.size();
180
181 double widthVerticalOrientation = 0;
182 for ( int i = 0; i < size; ++i )
183 {
184 const double rightToLeftBlockMaxCharacterWidth = res.mBlockMaxCharacterWidth[size - 1 - i ];
185 const double rightToLeftLineSpacing = blockVerticalLineSpacing[ size - 1 - i ];
186
187 adjustedRightToLeftXOffsets << currentOffset;
188 currentOffset += rightToLeftLineSpacing;
189
190 if ( i == size - 1 )
191 widthVerticalOrientation += rightToLeftBlockMaxCharacterWidth;
192 else
193 widthVerticalOrientation += rightToLeftLineSpacing;
194 }
195 std::reverse( adjustedRightToLeftXOffsets.begin(), adjustedRightToLeftXOffsets.end() );
196 res.mVerticalOrientationXOffsets = adjustedRightToLeftXOffsets;
197
198 res.mDocumentSizeVerticalOrientation = QSizeF( widthVerticalOrientation, heightVerticalOrientation );
199 }
200
201 return res;
202}
203
205{
206 switch ( orientation )
207 {
208 case Qgis::TextOrientation::Horizontal:
209 switch ( mode )
210 {
211 case Qgis::TextLayoutMode::Rectangle:
213 return mDocumentSizePointRectMode;
214
215 case Qgis::TextLayoutMode::Labeling:
216 return mDocumentSizeLabelMode;
217 };
219
220 case Qgis::TextOrientation::Vertical:
221 return mDocumentSizeVerticalOrientation;
222 case Qgis::TextOrientation::RotationBased:
223 return QSizeF(); // label mode only
224 }
225
227}
228
229double QgsTextDocumentMetrics::blockWidth( int blockIndex ) const
230{
231 return mBlockWidths.value( blockIndex );
232}
233
234double QgsTextDocumentMetrics::blockHeight( int blockIndex ) const
235{
236 return mBlockHeights.value( blockIndex );
237}
238
240{
241 switch ( mode )
242 {
243 case Qgis::TextLayoutMode::Rectangle:
244 return mBaselineOffsetsRectMode.value( blockIndex );
246 return mBaselineOffsetsPointMode.value( blockIndex );
247 case Qgis::TextLayoutMode::Labeling:
248 return mBaselineOffsetsLabelMode.value( blockIndex );
249 }
251}
252
254{
255 return mVerticalOrientationXOffsets.value( blockIndex );
256}
257
259{
260 return mBlockMaxCharacterWidth.value( blockIndex );
261}
262
264{
265 return mBlockMaxDescent.value( blockIndex );
266}
267
268QFont QgsTextDocumentMetrics::fragmentFont( int blockIndex, int fragmentIndex ) const
269{
270 return mFragmentFonts.value( blockIndex ).value( fragmentIndex );
271}
272
TextLayoutMode
Text layout modes.
Definition: qgis.h:1445
@ Point
Text at point of origin layout mode.
TextOrientation
Text orientations.
Definition: qgis.h:1430
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Represents a block of text consisting of one or more QgsTextFragment objects.
Definition: qgstextblock.h:36
int size() const
Returns the number of fragments in the block.
const QgsTextFragment & at(int index) const
Returns the fragment at the specified index.
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 ...
Contains pre-calculated metrics of a QgsTextDocument.
double verticalOrientationXOffset(int blockIndex) const
Returns the vertical orientation x offset for the specified block.
double blockMaximumDescent(int blockIndex) const
Returns the maximum descent encountered in the specified block.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
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.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0)
Returns precalculated text metrics for a text document, when rendered using the given base format and...
double blockHeight(int blockIndex) const
Returns the height of the block at the specified index.
double blockWidth(int blockIndex) const
Returns the width of the block at the specified index.
Represents a document consisting of one or more QgsTextBlock objects.
const QgsTextBlock & at(int index) const
Returns the block at the specified index.
int size() const
Returns the number of blocks in the document.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
double lineHeight() const
Returns the line height for text.
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...
QgsUnitTypes::RenderUnit lineHeightUnit() const
Returns the units for the line height for text.
Stores a fragment of text along with formatting overrides to be used when rendering the fragment.
QString text() const
Returns the text content of the fragment.
const QgsTextCharacterFormat & characterFormat() const
Returns the character formatting for the fragment.
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
#define BUILTIN_UNREACHABLE
Definition: qgis.h:3148