Skip to content

Commit

Permalink
Update QgsTextRenderer::textWidth/textHeight to handle HTML formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed May 12, 2020
1 parent 8b26316 commit 5092939
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 55 deletions.
5 changes: 2 additions & 3 deletions src/app/decorations/qgsdecorationcopyright.cpp
Expand Up @@ -121,11 +121,10 @@ void QgsDecorationCopyright::render( const QgsMapSettings &mapSettings, QgsRende
QString displayString = QgsExpression::replaceExpressionText( mLabelText, &context.expressionContext() );
QStringList displayStringList = displayString.split( '\n' );

QFontMetricsF fm( mTextFormat.scaledFont( context ) );
QFontMetricsF textMetrics = QgsTextRenderer::fontMetrics( context, mTextFormat );
double textDescent = textMetrics.descent();
double textWidth = QgsTextRenderer::textWidth( context, mTextFormat, displayStringList, &fm );
double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, QgsTextRenderer::Point, &fm );
double textWidth = QgsTextRenderer::textWidth( context, mTextFormat, displayStringList );
double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, QgsTextRenderer::Point );

QPaintDevice *device = context.painter()->device();
int deviceHeight = device->height() / device->devicePixelRatioF();
Expand Down
5 changes: 2 additions & 3 deletions src/app/decorations/qgsdecorationtitle.cpp
Expand Up @@ -110,11 +110,10 @@ void QgsDecorationTitle::render( const QgsMapSettings &mapSettings, QgsRenderCon
QString displayString = QgsExpression::replaceExpressionText( mLabelText, &context.expressionContext() );
QStringList displayStringList = displayString.split( '\n' );

QFontMetricsF fm( mTextFormat.scaledFont( context ) );
QFontMetricsF textMetrics = QgsTextRenderer::fontMetrics( context, mTextFormat );
double textDescent = textMetrics.descent();
double textWidth = QgsTextRenderer::textWidth( context, mTextFormat, displayStringList, &fm );
double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, QgsTextRenderer::Point, &fm );
double textWidth = QgsTextRenderer::textWidth( context, mTextFormat, displayStringList );
double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, QgsTextRenderer::Point );

QPaintDevice *device = context.painter()->device();
int deviceHeight = device->height() / device->devicePixelRatioF();
Expand Down
4 changes: 1 addition & 3 deletions src/core/labeling/qgsvectorlayerlabelprovider.cpp
Expand Up @@ -520,8 +520,6 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q
component.origin = outPt;
component.rotation = label->getAlpha();



if ( drawType == QgsTextRenderer::Background )
{
// get rotated label's center point
Expand All @@ -547,7 +545,7 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q

component.size = QSizeF( labelWidthPx, labelHeightPx );

QgsTextRenderer::drawBackground( context, component, tmpLyr.format(), QStringList(), QgsTextRenderer::Label );
QgsTextRenderer::drawBackground( context, component, tmpLyr.format(), QgsTextDocument(), QgsTextRenderer::Label );
}

else if ( drawType == QgsTextRenderer::Buffer
Expand Down
155 changes: 110 additions & 45 deletions src/core/textrenderer/qgstextrenderer.cpp
Expand Up @@ -154,8 +154,7 @@ void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, QgsTextRend
component.center = rect.center();
}

const QStringList lines = document.toPlainText();
QgsTextRenderer::drawBackground( context, component, format, lines, Rect );
QgsTextRenderer::drawBackground( context, component, format, document, Rect );

break;
}
Expand Down Expand Up @@ -205,8 +204,7 @@ void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer
if ( !format.background().enabled() )
return;

const QStringList lines = document.toPlainText();
QgsTextRenderer::drawBackground( context, component, format, lines, Point );
QgsTextRenderer::drawBackground( context, component, format, document, Point );
break;
}

Expand Down Expand Up @@ -447,35 +445,60 @@ void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer
p->restore();
}

double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics )
double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF * )
{
//calculate max width of text lines
std::unique_ptr< QFontMetricsF > newFm;
if ( !fontMetrics )
if ( !format.allowHtmlFormatting() )
{
newFm.reset( new QFontMetricsF( format.scaledFont( context ) ) );
fontMetrics = newFm.get();
return textWidth( context, format, QgsTextDocument::fromPlainText( textLines ) );
}
else
{
return textWidth( context, format, QgsTextDocument::fromHtml( textLines ) );
}
}

double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document )
{
//calculate max width of text lines
const QFont baseFont = format.scaledFont( context );

double width = 0;
switch ( format.orientation() )
{
case QgsTextFormat::HorizontalOrientation:
{
double maxLineWidth = 0;
const auto constTextLines = textLines;
for ( const QString &line : constTextLines )
for ( const QgsTextBlock &block : document )
{
maxLineWidth = std::max( maxLineWidth, fontMetrics->width( line ) );
double blockWidth = 0;
for ( const QgsTextFragment &fragment : block )
{
blockWidth += fragment.horizontalAdvance( baseFont );
}
maxLineWidth = std::max( maxLineWidth, blockWidth );
}
width = maxLineWidth;
break;
}

case QgsTextFormat::VerticalOrientation:
{
double labelWidth = fontMetrics->maxWidth();
width = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.lineHeight();
double totalLineWidth = 0;
int blockIndex = 0;
for ( const QgsTextBlock &block : document )
{
double blockWidth = 0;
for ( const QgsTextFragment &fragment : block )
{
QFont fragmentFont = baseFont;
fragment.characterFormat().updateFontForFormat( fragmentFont );
blockWidth = std::max( QFontMetricsF( fragmentFont ).maxWidth(), blockWidth );
}

totalLineWidth += blockIndex == 0 ? blockWidth : blockWidth * format.lineHeight();
blockIndex++;
}
width = totalLineWidth;
break;
}

Expand All @@ -489,49 +512,92 @@ double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTex
return width;
}

double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF *fontMetrics )
double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF * )
{
//calculate max width of text lines
std::unique_ptr< QFontMetricsF > newFm;
if ( !fontMetrics )
if ( !format.allowHtmlFormatting() )
{
newFm.reset( new QFontMetricsF( format.scaledFont( context ) ) );
fontMetrics = newFm.get();
return textHeight( context, format, QgsTextDocument::fromPlainText( textLines ), mode );
}
else
{
return textHeight( context, format, QgsTextDocument::fromHtml( textLines ), mode );
}
}

double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document, DrawMode mode )
{
//calculate max height of text lines

const QFont baseFont = format.scaledFont( context );

switch ( format.orientation() )
{
case QgsTextFormat::HorizontalOrientation:
{
double labelHeight = fontMetrics->ascent() + fontMetrics->descent(); // ignore +1 for baseline
switch ( mode )
int blockIndex = 0;
double totalHeight = 0;
for ( const QgsTextBlock &block : document )
{
case Label:
// rendering labels needs special handling - in this case text should be
// drawn with the bottom left corner coinciding with origin, vs top left
// for standard text rendering. Line height is also slightly different.
return labelHeight + ( textLines.size() - 1 ) * labelHeight * format.lineHeight();
double maxBlockHeight = 0;
double maxBlockLineSpacing = 0;
for ( const QgsTextFragment &fragment : block )
{
QFont fragmentFont = baseFont;
fragment.characterFormat().updateFontForFormat( fragmentFont );
const QFontMetricsF fm( fragmentFont );

case Rect:
case Point:
// standard rendering - designed to exactly replicate QPainter's drawText method
return labelHeight + ( textLines.size() - 1 ) * fontMetrics->lineSpacing() * format.lineHeight();
const double fragmentHeight = fm.ascent() + fm.descent(); // ignore +1 for baseline

maxBlockHeight = std::max( maxBlockHeight, fragmentHeight );
maxBlockLineSpacing = std::max( maxBlockLineSpacing, fm.lineSpacing() );
}

switch ( mode )
{
case Label:
// rendering labels needs special handling - in this case text should be
// drawn with the bottom left corner coinciding with origin, vs top left
// for standard text rendering. Line height is also slightly different.
totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockHeight * format.lineHeight();
break;

case Rect:
case Point:
// standard rendering - designed to exactly replicate QPainter's drawText method
totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockLineSpacing * format.lineHeight();
break;
}

blockIndex++;
}
break;

return totalHeight;
}

case QgsTextFormat::VerticalOrientation:
{
double labelHeight = fontMetrics->ascent();
double letterSpacing = format.scaledFont( context ).letterSpacing();
int maxLineLength = 0;
for ( const auto &line : textLines )
double maxBlockHeight = 0;
for ( const QgsTextBlock &block : document )
{
if ( line.length() > maxLineLength )
maxLineLength = line.length();
double blockHeight = 0;
int fragmentIndex = 0;
for ( const QgsTextFragment &fragment : block )
{
QFont fragmentFont = baseFont;
fragment.characterFormat().updateFontForFormat( fragmentFont );
const QFontMetricsF fm( fragmentFont );

const double labelHeight = fm.ascent();
const double letterSpacing = fragmentFont.letterSpacing();

blockHeight += fragmentIndex = 0 ? labelHeight * fragment.text().size() + ( fragment.text().size() - 1 ) * letterSpacing
: fragment.text().size() * ( labelHeight + letterSpacing );
fragmentIndex++;
}
maxBlockHeight = std::max( maxBlockHeight, blockHeight );
}
return labelHeight * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
break;

return maxBlockHeight;
}

case QgsTextFormat::RotationBasedOrientation:
Expand All @@ -544,8 +610,7 @@ double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTe
return 0;
}

void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer::Component component, const QgsTextFormat &format,
const QStringList &textLines, DrawMode mode )
void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer::Component component, const QgsTextFormat &format, const QgsTextDocument &document, QgsTextRenderer::DrawMode mode )
{
QgsTextBackgroundSettings background = format.background();

Expand Down Expand Up @@ -578,8 +643,8 @@ void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer
{
// need to calculate size of text
QFontMetricsF fm( format.scaledFont( context ) );
double width = textWidth( context, format, textLines, &fm );
double height = textHeight( context, format, textLines, mode, &fm );
double width = textWidth( context, format, document );
double height = textHeight( context, format, document, mode );

switch ( mode )
{
Expand Down
5 changes: 4 additions & 1 deletion src/core/textrenderer/qgstextrenderer.h
Expand Up @@ -215,6 +215,9 @@ class CORE_EXPORT QgsTextRenderer
HAlignment hAlign = AlignLeft;
};

static double textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document );
static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document, DrawMode mode );

/**
* Draws a single component of rendered text using the specified settings.
* \param rect destination rectangle for text
Expand Down Expand Up @@ -258,7 +261,7 @@ class CORE_EXPORT QgsTextRenderer
static void drawBackground( QgsRenderContext &context,
Component component,
const QgsTextFormat &format,
const QStringList &textLines,
const QgsTextDocument &document,
DrawMode mode = Rect );

static void drawShadow( QgsRenderContext &context,
Expand Down

0 comments on commit 5092939

Please sign in to comment.