mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-22 00:07:53 -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.__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
|
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
|
QgsCurve.Orientation = Qgis.AngularDirection
|
||||||
# monkey patching scoped based enum
|
# monkey patching scoped based enum
|
||||||
QgsCurve.Clockwise = Qgis.AngularDirection.Clockwise
|
QgsCurve.Clockwise = Qgis.AngularDirection.Clockwise
|
||||||
|
@ -750,6 +750,12 @@ The development version
|
|||||||
AllSmallCaps,
|
AllSmallCaps,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class TextRendererFlag
|
||||||
|
{
|
||||||
|
WrapLines,
|
||||||
|
};
|
||||||
|
typedef QFlags<Qgis::TextRendererFlag> TextRendererFlags;
|
||||||
|
|
||||||
|
|
||||||
enum class AngularDirection
|
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::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,
|
static void drawText( const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines,
|
||||||
QgsRenderContext &context, const QgsTextFormat &format,
|
QgsRenderContext &context, const QgsTextFormat &format,
|
||||||
bool drawAsOutlines = true, VAlignment vAlignment = AlignTop );
|
bool drawAsOutlines = true, VAlignment vAlignment = AlignTop,
|
||||||
|
Qgis::TextRendererFlags flags = Qgis::TextRendererFlags() );
|
||||||
%Docstring
|
%Docstring
|
||||||
Draws text within a rectangle using the specified settings.
|
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
|
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.
|
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 vAlignment: vertical alignment (since QGIS 3.16)
|
||||||
|
:param flags: text rendering flags (since QGIS 3.24)
|
||||||
%End
|
%End
|
||||||
|
|
||||||
static void drawText( QPointF point, double rotation, HAlignment alignment, const QStringList &textLines,
|
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
|
%End
|
||||||
|
|
||||||
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode = Point,
|
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
|
%Docstring
|
||||||
Returns the height of a text based on a given format.
|
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 textLines: list of lines of text to calculate width from
|
||||||
:param mode: draw mode
|
:param mode: draw mode
|
||||||
:param fontMetrics: font metrics
|
: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
|
%End
|
||||||
|
|
||||||
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects = false );
|
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.
|
returned height. If ``False``, then the returned size considers the character only.
|
||||||
|
|
||||||
.. versionadded:: 3.16
|
.. 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
|
%End
|
||||||
|
|
||||||
static const double FONT_WORKAROUND_SCALE;
|
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 );
|
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
|
const QStringList str = column.heading().split( '\n' );
|
||||||
// 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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale to dots
|
// scale to dots
|
||||||
{
|
{
|
||||||
@ -485,7 +479,9 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
|||||||
textCell.top() * context.renderContext().scaleFactor(),
|
textCell.top() * context.renderContext().scaleFactor(),
|
||||||
textCell.width() * context.renderContext().scaleFactor(),
|
textCell.width() * context.renderContext().scaleFactor(),
|
||||||
textCell.height() * context.renderContext().scaleFactor() ), 0,
|
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 ];
|
currentX += mMaxColumnWidthMap[ col ];
|
||||||
@ -514,6 +510,7 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
|||||||
|
|
||||||
for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
|
for ( const QgsLayoutTableColumn &column : std::as_const( mColumns ) )
|
||||||
{
|
{
|
||||||
|
( void )column;
|
||||||
const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
|
const QRectF fullCell( currentX, currentY, mMaxColumnWidthMap[col] + 2 * mCellMargin, rowHeight );
|
||||||
//draw background
|
//draw background
|
||||||
p->save();
|
p->save();
|
||||||
@ -527,19 +524,12 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
|
|||||||
|
|
||||||
QVariant cellContents = mTableContents.at( row ).at( col );
|
QVariant cellContents = mTableContents.at( row ).at( col );
|
||||||
const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
|
const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
|
||||||
QStringList str = localizedString.split( '\n' );
|
const QStringList str = localizedString.split( '\n' );
|
||||||
|
|
||||||
QgsTextFormat cellFormat = textFormatForCell( row, col );
|
QgsTextFormat cellFormat = textFormatForCell( row, col );
|
||||||
QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), scopeForCell( row, col ) );
|
QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), scopeForCell( row, col ) );
|
||||||
cellFormat.updateDataDefinedProperties( context.renderContext() );
|
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->save();
|
||||||
p->setClipRect( fullCell );
|
p->setClipRect( fullCell );
|
||||||
const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
|
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.width() * context.renderContext().scaleFactor(),
|
||||||
textCell.height() * context.renderContext().scaleFactor() ), 0,
|
textCell.height() * context.renderContext().scaleFactor() ), 0,
|
||||||
QgsTextRenderer::convertQtHAlignment( horizontalAlignmentForCell( row, col ) ), str, context.renderContext(), cellFormat, true,
|
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();
|
p->restore();
|
||||||
|
|
||||||
@ -1189,16 +1180,16 @@ bool QgsLayoutTable::calculateMaxRowHeights()
|
|||||||
{
|
{
|
||||||
heights[i] = 0;
|
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
|
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;
|
- headerDescentMm;
|
||||||
}
|
}
|
||||||
i++;
|
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 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 ) };
|
const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };
|
||||||
|
|
||||||
if ( textRequiresWrapping( context, localizedString, mColumns.at( i ).width(), cellFormat ) )
|
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context,
|
||||||
{
|
cellFormat,
|
||||||
//contents too wide for cell, need to wrap
|
QStringList() << localizedString.split( '\n' ),
|
||||||
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, localizedString, mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
|
QgsTextRenderer::Rect,
|
||||||
}
|
nullptr,
|
||||||
else
|
mWrapBehavior == WrapText ? Qgis::TextRendererFlag::WrapLines : Qgis::TextRendererFlags(),
|
||||||
{
|
context.convertToPainterUnits( mColumns.at( i ).width(), QgsUnitTypes::RenderMillimeters )
|
||||||
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << localizedString.split( '\n' ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
|
) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@ -1375,83 +1365,6 @@ void QgsLayoutTable::drawHorizontalGridLines( QgsLayoutItemRenderContext &contex
|
|||||||
painter->drawLine( QPointF( halfGridStrokeWidth, currentY ), QPointF( mTableSize.width() - halfGridStrokeWidth, currentY ) );
|
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 QgsLayoutTable::backgroundColor( int row, int column ) const
|
||||||
{
|
{
|
||||||
QColor color = mBackgroundColor;
|
QColor color = mBackgroundColor;
|
||||||
|
@ -771,10 +771,6 @@ class CORE_EXPORT QgsLayoutTable: public QgsLayoutMultiFrame
|
|||||||
//! Initializes cell style map
|
//! Initializes cell style map
|
||||||
void initStyles();
|
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.
|
* 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
|
* \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 )
|
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.
|
* 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::RenderContextFlags )
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::VectorLayerTypeFlags )
|
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::VectorLayerTypeFlags )
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::MarkerLinePlacements )
|
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::MarkerLinePlacements )
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TextRendererFlags )
|
||||||
|
|
||||||
|
|
||||||
// hack to workaround warnings when casting void pointers
|
// 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
|
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;
|
QgsTextFormat tmpFormat = format;
|
||||||
if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
|
if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
|
||||||
tmpFormat.updateDataDefinedProperties( context );
|
tmpFormat.updateDataDefinedProperties( context );
|
||||||
tmpFormat = updateShadowPosition( tmpFormat );
|
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 );
|
QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
|
||||||
document.applyCapitalization( format.capitalization() );
|
document.applyCapitalization( format.capitalization() );
|
||||||
|
|
||||||
@ -599,15 +612,28 @@ double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTex
|
|||||||
return width / scaleFactor;
|
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() )
|
if ( !format.allowHtmlFormatting() )
|
||||||
{
|
{
|
||||||
return textHeight( context, format, QgsTextDocument::fromPlainText( textLines ), mode );
|
return textHeight( context, format, QgsTextDocument::fromPlainText( lines ), mode );
|
||||||
}
|
}
|
||||||
else
|
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;
|
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 )
|
double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &doc, DrawMode mode )
|
||||||
{
|
{
|
||||||
QgsTextDocument document = doc;
|
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
|
* 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.
|
* 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 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,
|
static void drawText( const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines,
|
||||||
QgsRenderContext &context, const QgsTextFormat &format,
|
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.
|
* 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 textLines list of lines of text to calculate width from
|
||||||
* \param mode draw mode
|
* \param mode draw mode
|
||||||
* \param fontMetrics font metrics
|
* \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,
|
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.
|
* 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 );
|
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.
|
* Scale factor for upscaling font sizes and downscaling destination painter devices.
|
||||||
*
|
*
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include "qgslayoutatlas.h"
|
#include "qgslayoutatlas.h"
|
||||||
#include "qgslayoututils.h"
|
#include "qgslayoututils.h"
|
||||||
#include "qgspallabeling.h"
|
#include "qgspallabeling.h"
|
||||||
|
#include "qgstextrenderer.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "qgstest.h"
|
#include "qgstest.h"
|
||||||
@ -1644,18 +1645,15 @@ void TestQgsLayoutTable::wrappedText()
|
|||||||
{
|
{
|
||||||
QgsProject p;
|
QgsProject p;
|
||||||
QgsLayout l( &p );
|
QgsLayout l( &p );
|
||||||
QgsLayoutItemAttributeTable *t = new QgsLayoutItemAttributeTable( &l );
|
|
||||||
t->setWrapBehavior( QgsLayoutTable::WrapText );
|
|
||||||
|
|
||||||
const QFont f;
|
const QFont f;
|
||||||
const QString sourceText( "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua" );
|
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 );
|
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)
|
//there should be no line break before the last word (bug #20546)
|
||||||
QVERIFY( !wrapText.endsWith( "\naliqua" ) );
|
QVERIFY( !wrapText.endsWith( "\naliqua" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void TestQgsLayoutTable::testBaseSort()
|
void TestQgsLayoutTable::testBaseSort()
|
||||||
{
|
{
|
||||||
QgsLayout l( QgsProject::instance() );
|
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