mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-15 00:02:52 -04:00
[api] Move text wrapping handling logic from layout table code to QgsTextRenderer
Allows other users of QgsTextRenderer to take advantage of the automatic line wrapping behaviour
This commit is contained in:
parent
dcf0cfe5a3
commit
fadfb477d9
@ -1169,6 +1169,13 @@ QgsStringUtils.AllSmallCaps.__doc__ = "Force all characters to small caps (since
|
||||
Qgis.Capitalization.__doc__ = 'String capitalization options.\n\n.. note::\n\n Prior to QGIS 3.24 this was available as :py:class:`QgsStringUtils`.Capitalization\n\n.. versionadded:: 3.24\n\n' + '* ``MixedCase``: ' + Qgis.Capitalization.MixedCase.__doc__ + '\n' + '* ``AllUppercase``: ' + Qgis.Capitalization.AllUppercase.__doc__ + '\n' + '* ``AllLowercase``: ' + Qgis.Capitalization.AllLowercase.__doc__ + '\n' + '* ``ForceFirstLetterToCapital``: ' + Qgis.Capitalization.ForceFirstLetterToCapital.__doc__ + '\n' + '* ``SmallCaps``: ' + Qgis.Capitalization.SmallCaps.__doc__ + '\n' + '* ``TitleCase``: ' + Qgis.Capitalization.TitleCase.__doc__ + '\n' + '* ``UpperCamelCase``: ' + Qgis.Capitalization.UpperCamelCase.__doc__ + '\n' + '* ``AllSmallCaps``: ' + Qgis.Capitalization.AllSmallCaps.__doc__
|
||||
# --
|
||||
Qgis.Capitalization.baseClass = Qgis
|
||||
# monkey patching scoped based enum
|
||||
Qgis.TextRendererFlag.WrapLines.__doc__ = "Automatically wrap long lines of text"
|
||||
Qgis.TextRendererFlag.__doc__ = 'Flags which control the behavior of rendering text.\n\n.. versionadded:: 3.24\n\n' + '* ``WrapLines``: ' + Qgis.TextRendererFlag.WrapLines.__doc__
|
||||
# --
|
||||
Qgis.TextRendererFlag.baseClass = Qgis
|
||||
Qgis.TextRendererFlags.baseClass = Qgis
|
||||
TextRendererFlags = Qgis # dirty hack since SIP seems to introduce the flags in module
|
||||
QgsCurve.Orientation = Qgis.AngularDirection
|
||||
# monkey patching scoped based enum
|
||||
QgsCurve.Clockwise = Qgis.AngularDirection.Clockwise
|
||||
|
@ -750,6 +750,12 @@ The development version
|
||||
AllSmallCaps,
|
||||
};
|
||||
|
||||
enum class TextRendererFlag
|
||||
{
|
||||
WrapLines,
|
||||
};
|
||||
typedef QFlags<Qgis::TextRendererFlag> TextRendererFlags;
|
||||
|
||||
|
||||
enum class AngularDirection
|
||||
{
|
||||
@ -858,6 +864,8 @@ QFlags<Qgis::VectorLayerTypeFlag> operator|(Qgis::VectorLayerTypeFlag f1, QFlags
|
||||
|
||||
QFlags<Qgis::MarkerLinePlacement> operator|(Qgis::MarkerLinePlacement f1, QFlags<Qgis::MarkerLinePlacement> f2);
|
||||
|
||||
QFlags<Qgis::TextRendererFlag> operator|(Qgis::TextRendererFlag f1, QFlags<Qgis::TextRendererFlag> f2);
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -87,7 +87,8 @@ Calculates pixel size (considering output size should be in pixel or map units,
|
||||
|
||||
static void drawText( const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines,
|
||||
QgsRenderContext &context, const QgsTextFormat &format,
|
||||
bool drawAsOutlines = true, VAlignment vAlignment = AlignTop );
|
||||
bool drawAsOutlines = true, VAlignment vAlignment = AlignTop,
|
||||
Qgis::TextRendererFlags flags = Qgis::TextRendererFlags() );
|
||||
%Docstring
|
||||
Draws text within a rectangle using the specified settings.
|
||||
|
||||
@ -102,6 +103,7 @@ Draws text within a rectangle using the specified settings.
|
||||
rendering and may result in side effects like misaligned text buffers. This setting is deprecated and has no effect
|
||||
as of QGIS 3.4.3 and the text format should be set using :py:func:`QgsRenderContext.setTextRenderFormat()` instead.
|
||||
:param vAlignment: vertical alignment (since QGIS 3.16)
|
||||
:param flags: text rendering flags (since QGIS 3.24)
|
||||
%End
|
||||
|
||||
static void drawText( QPointF point, double rotation, HAlignment alignment, const QStringList &textLines,
|
||||
@ -196,7 +198,7 @@ Returns the width of a text based on a given format.
|
||||
%End
|
||||
|
||||
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode = Point,
|
||||
QFontMetricsF *fontMetrics = 0 );
|
||||
QFontMetricsF *fontMetrics = 0, Qgis::TextRendererFlags flags = Qgis::TextRendererFlags(), double maxLineWidth = 0 );
|
||||
%Docstring
|
||||
Returns the height of a text based on a given format.
|
||||
|
||||
@ -205,6 +207,8 @@ Returns the height of a text based on a given format.
|
||||
:param textLines: list of lines of text to calculate width from
|
||||
:param mode: draw mode
|
||||
:param fontMetrics: font metrics
|
||||
:param flags: text renderer flags (since QGIS 3.24)
|
||||
:param maxLineWidth: maximum line width, in painter units. Used when the Qgis.TextRendererFlag.WrapLines flag is used (since QGIS 3.24)
|
||||
%End
|
||||
|
||||
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects = false );
|
||||
@ -218,6 +222,24 @@ Returns the height of a character when rendered with the specified text ``format
|
||||
returned height. If ``False``, then the returned size considers the character only.
|
||||
|
||||
.. versionadded:: 3.16
|
||||
%End
|
||||
|
||||
static bool textRequiresWrapping( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format );
|
||||
%Docstring
|
||||
Returns ``True`` if the specified ``text`` requires line wrapping in order to fit within the specified ``width`` (in painter units).
|
||||
|
||||
.. seealso:: :py:func:`wrappedText`
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
|
||||
static QStringList wrappedText( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format );
|
||||
%Docstring
|
||||
Wraps a ``text`` string to multiple lines, such that each individual line will fit within the specified ``width`` (in painter units).
|
||||
|
||||
.. seealso:: :py:func:`textRequiresWrapping`
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
|
||||
static const double FONT_WORKAROUND_SCALE;
|
||||
|
@ -470,13 +470,7 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
||||
|
||||
const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], cellHeaderHeight - 2 * mCellMargin );
|
||||
|
||||
// disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
|
||||
// and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
|
||||
QStringList str = column.heading().split( '\n' );
|
||||
if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), column.heading(), column.width(), headerFormat ) )
|
||||
{
|
||||
str = wrappedText( context.renderContext(), column.heading(), column.width(), headerFormat );
|
||||
}
|
||||
const QStringList str = column.heading().split( '\n' );
|
||||
|
||||
// scale to dots
|
||||
{
|
||||
@ -485,7 +479,9 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
||||
textCell.top() * context.renderContext().scaleFactor(),
|
||||
textCell.width() * context.renderContext().scaleFactor(),
|
||||
textCell.height() * context.renderContext().scaleFactor() ), 0,
|
||||
headerAlign, str, context.renderContext(), headerFormat, true, QgsTextRenderer::AlignVCenter );
|
||||
headerAlign, str, context.renderContext(), headerFormat, true, QgsTextRenderer::AlignVCenter,
|
||||
mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags()
|
||||
);
|
||||
}
|
||||
|
||||
currentX += mMaxColumnWidthMap[ col ];
|
||||
@ -514,6 +510,7 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
||||
|
||||
for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
|
||||
{
|
||||
( void )column;
|
||||
const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
|
||||
//draw background
|
||||
p->save();
|
||||
@ -527,19 +524,12 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
||||
|
||||
QVariant cellContents = mTableContents.at( row ).at( col );
|
||||
const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
|
||||
QStringList str = localizedString.split( '\n' );
|
||||
const QStringList str = localizedString.split( '\n' );
|
||||
|
||||
QgsTextFormat cellFormat = textFormatForCell( row, col );
|
||||
QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), scopeForCell( row, col ) );
|
||||
cellFormat.updateDataDefinedProperties( context.renderContext() );
|
||||
|
||||
// disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
|
||||
// and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
|
||||
if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), localizedString, column.width(), cellFormat ) )
|
||||
{
|
||||
str = wrappedText( context.renderContext(), localizedString, column.width(), cellFormat );
|
||||
}
|
||||
|
||||
p->save();
|
||||
p->setClipRect( fullCell );
|
||||
const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
|
||||
@ -559,7 +549,8 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
||||
textCell.width() * context.renderContext().scaleFactor(),
|
||||
textCell.height() * context.renderContext().scaleFactor() ), 0,
|
||||
QgsTextRenderer::convertQtHAlignment( horizontalAlignmentForCell( row, col ) ), str, context.renderContext(), cellFormat, true,
|
||||
QgsTextRenderer::convertQtVAlignment( verticalAlignmentForCell( row, col ) ) );
|
||||
QgsTextRenderer::convertQtVAlignment( verticalAlignmentForCell( row, col ) ),
|
||||
mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags() );
|
||||
}
|
||||
p->restore();
|
||||
|
||||
@ -1189,16 +1180,16 @@ bool QgsLayoutTable::calculateMaxRowHeights()
|
||||
{
|
||||
heights[i] = 0;
|
||||
}
|
||||
else if ( textRequiresWrapping( context, col.heading(), mColumns.at( i ).width(), cellFormat ) )
|
||||
{
|
||||
//contents too wide for cell, need to wrap
|
||||
heights[i] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, col.heading(), mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect )
|
||||
/ context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters )
|
||||
- headerDescentMm;
|
||||
}
|
||||
else
|
||||
{
|
||||
heights[i] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << col.heading(), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters )
|
||||
heights[i] = QgsTextRenderer::textHeight( context,
|
||||
cellFormat,
|
||||
QStringList() << col.heading(), QgsTextRenderer::Rect,
|
||||
nullptr,
|
||||
mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags(),
|
||||
context.convertToPainterUnits( mColumns.at( i ).width(), QgsUnitTypes::RenderMillimeters )
|
||||
)
|
||||
/ context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters )
|
||||
- headerDescentMm;
|
||||
}
|
||||
i++;
|
||||
@ -1219,15 +1210,14 @@ bool QgsLayoutTable::calculateMaxRowHeights()
|
||||
const double contentDescentMm = QgsTextRenderer::fontMetrics( context, cellFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).descent() / QgsTextRenderer::FONT_WORKAROUND_SCALE / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
|
||||
const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };
|
||||
|
||||
if ( textRequiresWrapping( context, localizedString, mColumns.at( i ).width(), cellFormat ) )
|
||||
{
|
||||
//contents too wide for cell, need to wrap
|
||||
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, localizedString, mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
|
||||
}
|
||||
else
|
||||
{
|
||||
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << localizedString.split( '\n' ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
|
||||
}
|
||||
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context,
|
||||
cellFormat,
|
||||
QStringList() << localizedString.split( '\n' ),
|
||||
QgsTextRenderer::Rect,
|
||||
nullptr,
|
||||
mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags(),
|
||||
context.convertToPainterUnits( mColumns.at( i ).width(), QgsUnitTypes::RenderMillimeters )
|
||||
) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
|
||||
|
||||
i++;
|
||||
}
|
||||
@ -1375,83 +1365,6 @@ void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &contex
|
||||
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
|
||||
}
|
||||
|
||||
bool QgsLayoutTable::textRequiresWrapping( QgsRenderContext &context, const QString &text, double columnWidth, const QgsTextFormat &format ) const
|
||||
{
|
||||
if ( qgsDoubleNear( columnWidth, 0.0 ) || mWrapBehavior != WrapText )
|
||||
return false;
|
||||
|
||||
const QStringList multiLineSplit = text.split( '\n' );
|
||||
const double currentTextWidth = QgsTextRenderer::textWidth( context, format, multiLineSplit ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
|
||||
return currentTextWidth > columnWidth;
|
||||
}
|
||||
|
||||
QStringList QgsLayoutTable::wrappedText( QgsRenderContext &context, const QString &value, double columnWidth, const QgsTextFormat &format ) const
|
||||
{
|
||||
QStringList lines = value.split( '\n' );
|
||||
QStringList outLines;
|
||||
const auto constLines = lines;
|
||||
for ( const QString &line : constLines )
|
||||
{
|
||||
if ( textRequiresWrapping( context, line, columnWidth, format ) )
|
||||
{
|
||||
//first step is to identify words which must be on their own line (too long to fit)
|
||||
QStringList words = line.split( ' ' );
|
||||
QStringList linesToProcess;
|
||||
QString wordsInCurrentLine;
|
||||
const auto constWords = words;
|
||||
for ( const QString &word : constWords )
|
||||
{
|
||||
if ( textRequiresWrapping( context, word, columnWidth, format ) )
|
||||
{
|
||||
//too long to fit
|
||||
if ( !wordsInCurrentLine.isEmpty() )
|
||||
linesToProcess << wordsInCurrentLine;
|
||||
wordsInCurrentLine.clear();
|
||||
linesToProcess << word;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !wordsInCurrentLine.isEmpty() )
|
||||
wordsInCurrentLine.append( ' ' );
|
||||
wordsInCurrentLine.append( word );
|
||||
}
|
||||
}
|
||||
if ( !wordsInCurrentLine.isEmpty() )
|
||||
linesToProcess << wordsInCurrentLine;
|
||||
|
||||
const auto constLinesToProcess = linesToProcess;
|
||||
for ( const QString &line : constLinesToProcess )
|
||||
{
|
||||
QString remainingText = line;
|
||||
int lastPos = remainingText.lastIndexOf( ' ' );
|
||||
while ( lastPos > -1 )
|
||||
{
|
||||
//check if remaining text is short enough to go in one line
|
||||
if ( !textRequiresWrapping( context, remainingText, columnWidth, format ) )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !textRequiresWrapping( context, remainingText.left( lastPos ), columnWidth, format ) )
|
||||
{
|
||||
outLines << remainingText.left( lastPos );
|
||||
remainingText = remainingText.mid( lastPos + 1 );
|
||||
lastPos = 0;
|
||||
}
|
||||
lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
|
||||
}
|
||||
outLines << remainingText;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outLines << line;
|
||||
}
|
||||
}
|
||||
|
||||
return outLines;
|
||||
}
|
||||
|
||||
QColor QgsLayoutTable::backgroundColor( int row, int column ) const
|
||||
{
|
||||
QColor color = mBackgroundColor;
|
||||
|
@ -771,10 +771,6 @@ class CORE_EXPORT QgsLayoutTable: public QgsLayoutMultiFrame
|
||||
//! Initializes cell style map
|
||||
void initStyles();
|
||||
|
||||
bool textRequiresWrapping( QgsRenderContext &context, const QString &text, double columnWidth, const QgsTextFormat &format ) const;
|
||||
|
||||
QStringList wrappedText( QgsRenderContext &context, const QString &value, double columnWidth, const QgsTextFormat &format ) const;
|
||||
|
||||
/**
|
||||
* Returns the calculated background color for a row and column combination.
|
||||
* \param row row number, where -1 is the header row, and 0 is the first body row
|
||||
|
@ -1223,6 +1223,18 @@ class CORE_EXPORT Qgis
|
||||
};
|
||||
Q_ENUM( Capitalization )
|
||||
|
||||
/**
|
||||
* Flags which control the behavior of rendering text.
|
||||
*
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
enum class TextRendererFlag : int
|
||||
{
|
||||
WrapLines = 1 << 0, //!< Automatically wrap long lines of text
|
||||
};
|
||||
Q_ENUM( TextRendererFlag )
|
||||
Q_DECLARE_FLAGS( TextRendererFlags, TextRendererFlag )
|
||||
Q_FLAG( TextRendererFlags )
|
||||
|
||||
/**
|
||||
* Angular directions.
|
||||
@ -1367,6 +1379,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::MapSettingsFlags )
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::RenderContextFlags )
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::VectorLayerTypeFlags )
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::MarkerLinePlacements )
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TextRendererFlags )
|
||||
|
||||
|
||||
// hack to workaround warnings when casting void pointers
|
||||
|
@ -77,13 +77,26 @@ int QgsTextRenderer::sizeToPixel( double size, const QgsRenderContext &c, QgsUni
|
||||
return static_cast< int >( c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 ); //NOLINT
|
||||
}
|
||||
|
||||
void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool, VAlignment vAlignment )
|
||||
void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &text, QgsRenderContext &context, const QgsTextFormat &format, bool, VAlignment vAlignment, Qgis::TextRendererFlags flags )
|
||||
{
|
||||
QgsTextFormat tmpFormat = format;
|
||||
if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
|
||||
tmpFormat.updateDataDefinedProperties( context );
|
||||
tmpFormat = updateShadowPosition( tmpFormat );
|
||||
|
||||
QStringList textLines;
|
||||
for ( const QString &line : text )
|
||||
{
|
||||
if ( flags & Qgis::TextRendererFlag::WrapLines && textRequiresWrapping( context, line, rect.width(), format ) )
|
||||
{
|
||||
textLines.append( wrappedText( context, line, rect.width(), format ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
textLines.append( line );
|
||||
}
|
||||
}
|
||||
|
||||
QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
|
||||
document.applyCapitalization( format.capitalization() );
|
||||
|
||||
@ -599,15 +612,28 @@ double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTex
|
||||
return width / scaleFactor;
|
||||
}
|
||||
|
||||
double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF * )
|
||||
double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF *, Qgis::TextRendererFlags flags, double maxLineWidth )
|
||||
{
|
||||
QStringList lines;
|
||||
for ( const QString &line : textLines )
|
||||
{
|
||||
if ( flags & Qgis::TextRendererFlag::WrapLines && maxLineWidth > 0 && textRequiresWrapping( context, line, maxLineWidth, format ) )
|
||||
{
|
||||
lines.append( wrappedText( context, line, maxLineWidth, format ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.append( line );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !format.allowHtmlFormatting() )
|
||||
{
|
||||
return textHeight( context, format, QgsTextDocument::fromPlainText( textLines ), mode );
|
||||
return textHeight( context, format, QgsTextDocument::fromPlainText( lines ), mode );
|
||||
}
|
||||
else
|
||||
{
|
||||
return textHeight( context, format, QgsTextDocument::fromHtml( textLines ), mode );
|
||||
return textHeight( context, format, QgsTextDocument::fromHtml( lines ), mode );
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,6 +674,80 @@ double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTe
|
||||
return height + maxExtension;
|
||||
}
|
||||
|
||||
bool QgsTextRenderer::textRequiresWrapping( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format )
|
||||
{
|
||||
if ( qgsDoubleNear( width, 0.0 ) )
|
||||
return false;
|
||||
|
||||
const QStringList multiLineSplit = text.split( '\n' );
|
||||
const double currentTextWidth = QgsTextRenderer::textWidth( context, format, multiLineSplit );
|
||||
return currentTextWidth > width;
|
||||
}
|
||||
|
||||
QStringList QgsTextRenderer::wrappedText( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format )
|
||||
{
|
||||
const QStringList lines = text.split( '\n' );
|
||||
QStringList outLines;
|
||||
for ( const QString &line : lines )
|
||||
{
|
||||
if ( textRequiresWrapping( context, line, width, format ) )
|
||||
{
|
||||
//first step is to identify words which must be on their own line (too long to fit)
|
||||
const QStringList words = line.split( ' ' );
|
||||
QStringList linesToProcess;
|
||||
QString wordsInCurrentLine;
|
||||
for ( const QString &word : words )
|
||||
{
|
||||
if ( textRequiresWrapping( context, word, width, format ) )
|
||||
{
|
||||
//too long to fit
|
||||
if ( !wordsInCurrentLine.isEmpty() )
|
||||
linesToProcess << wordsInCurrentLine;
|
||||
wordsInCurrentLine.clear();
|
||||
linesToProcess << word;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !wordsInCurrentLine.isEmpty() )
|
||||
wordsInCurrentLine.append( ' ' );
|
||||
wordsInCurrentLine.append( word );
|
||||
}
|
||||
}
|
||||
if ( !wordsInCurrentLine.isEmpty() )
|
||||
linesToProcess << wordsInCurrentLine;
|
||||
|
||||
for ( const QString &line : std::as_const( linesToProcess ) )
|
||||
{
|
||||
QString remainingText = line;
|
||||
int lastPos = remainingText.lastIndexOf( ' ' );
|
||||
while ( lastPos > -1 )
|
||||
{
|
||||
//check if remaining text is short enough to go in one line
|
||||
if ( !textRequiresWrapping( context, remainingText, width, format ) )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !textRequiresWrapping( context, remainingText.left( lastPos ), width, format ) )
|
||||
{
|
||||
outLines << remainingText.left( lastPos );
|
||||
remainingText = remainingText.mid( lastPos + 1 );
|
||||
lastPos = 0;
|
||||
}
|
||||
lastPos = remainingText.lastIndexOf( ' ', lastPos - 1 );
|
||||
}
|
||||
outLines << remainingText;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outLines << line;
|
||||
}
|
||||
}
|
||||
|
||||
return outLines;
|
||||
}
|
||||
|
||||
double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &doc, DrawMode mode )
|
||||
{
|
||||
QgsTextDocument document = doc;
|
||||
|
@ -115,10 +115,12 @@ class CORE_EXPORT QgsTextRenderer
|
||||
* rendering and may result in side effects like misaligned text buffers. This setting is deprecated and has no effect
|
||||
* as of QGIS 3.4.3 and the text format should be set using QgsRenderContext::setTextRenderFormat() instead.
|
||||
* \param vAlignment vertical alignment (since QGIS 3.16)
|
||||
* \param flags text rendering flags (since QGIS 3.24)
|
||||
*/
|
||||
static void drawText( const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines,
|
||||
QgsRenderContext &context, const QgsTextFormat &format,
|
||||
bool drawAsOutlines = true, VAlignment vAlignment = AlignTop );
|
||||
bool drawAsOutlines = true, VAlignment vAlignment = AlignTop,
|
||||
Qgis::TextRendererFlags flags = Qgis::TextRendererFlags() );
|
||||
|
||||
/**
|
||||
* Draws text at a point origin using the specified settings.
|
||||
@ -212,9 +214,11 @@ class CORE_EXPORT QgsTextRenderer
|
||||
* \param textLines list of lines of text to calculate width from
|
||||
* \param mode draw mode
|
||||
* \param fontMetrics font metrics
|
||||
* \param flags text renderer flags (since QGIS 3.24)
|
||||
* \param maxLineWidth maximum line width, in painter units. Used when the Qgis::TextRendererFlag::WrapLines flag is used (since QGIS 3.24)
|
||||
*/
|
||||
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode = Point,
|
||||
QFontMetricsF *fontMetrics = nullptr );
|
||||
QFontMetricsF *fontMetrics = nullptr, Qgis::TextRendererFlags flags = Qgis::TextRendererFlags(), double maxLineWidth = 0 );
|
||||
|
||||
/**
|
||||
* Returns the height of a character when rendered with the specified text \a format.
|
||||
@ -229,6 +233,22 @@ class CORE_EXPORT QgsTextRenderer
|
||||
*/
|
||||
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects = false );
|
||||
|
||||
/**
|
||||
* Returns TRUE if the specified \a text requires line wrapping in order to fit within the specified \a width (in painter units).
|
||||
*
|
||||
* \see wrappedText()
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
static bool textRequiresWrapping( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format );
|
||||
|
||||
/**
|
||||
* Wraps a \a text string to multiple lines, such that each individual line will fit within the specified \a width (in painter units).
|
||||
*
|
||||
* \see textRequiresWrapping()
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
static QStringList wrappedText( const QgsRenderContext &context, const QString &text, double width, const QgsTextFormat &format );
|
||||
|
||||
/**
|
||||
* Scale factor for upscaling font sizes and downscaling destination painter devices.
|
||||
*
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "qgslayoutatlas.h"
|
||||
#include "qgslayoututils.h"
|
||||
#include "qgspallabeling.h"
|
||||
#include "qgstextrenderer.h"
|
||||
|
||||
#include <QObject>
|
||||
#include "qgstest.h"
|
||||
@ -1644,18 +1645,15 @@ void TestQgsLayoutTable::wrappedText()
|
||||
{
|
||||
QgsProject p;
|
||||
QgsLayout l( &p );
|
||||
QgsLayoutItemAttributeTable *t = new QgsLayoutItemAttributeTable( &l );
|
||||
t->setWrapBehavior( QgsLayoutTable::WrapText );
|
||||
|
||||
const QFont f;
|
||||
const QString sourceText( "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua" );
|
||||
QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( &l, nullptr );
|
||||
const QString wrapText = t->wrappedText( context, sourceText, 101 /*columnWidth*/, QgsTextFormat::fromQFont( f ) ).join( '\n' );
|
||||
const QString wrapText = QgsTextRenderer::wrappedText( context, sourceText, context.convertToPainterUnits( 101, QgsUnitTypes::RenderMillimeters ) /*columnWidth*/, QgsTextFormat::fromQFont( f ) ).join( '\n' );
|
||||
//there should be no line break before the last word (bug #20546)
|
||||
QVERIFY( !wrapText.endsWith( "\naliqua" ) );
|
||||
}
|
||||
|
||||
|
||||
void TestQgsLayoutTable::testBaseSort()
|
||||
{
|
||||
QgsLayout l( QgsProject::instance() );
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Loading…
x
Reference in New Issue
Block a user