Skip to content
Permalink
Browse files

Add "justify" alignment mode for QgsTextRenderer

  • Loading branch information
nyalldawson committed Jul 9, 2020
1 parent 52f7741 commit 445beba9b6ac1c5b58a3cf14b238e2f0056575fc
@@ -302,6 +302,9 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend
QFont fragmentFont = font;
fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );

if ( component.extraWordSpacing || component.extraLetterSpacing )
applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing );

path.addText( xOffset, 0, fragmentFont, fragment.text() );

xOffset += fragment.horizontalAdvance( fragmentFont, true, scaleFactor );
@@ -1278,6 +1281,51 @@ QgsTextFormat::TextOrientation QgsTextRenderer::calculateRotationAndOrientationF
return QgsTextFormat::HorizontalOrientation;
}

void QgsTextRenderer::calculateExtraSpacingForLineJustification( const double spaceToDistribute, const QgsTextBlock &block, double &extraWordSpace, double &extraLetterSpace )
{
const QString blockText = block.toPlainText();
QTextBoundaryFinder finder( QTextBoundaryFinder::Word, blockText );
finder.toStart();
int wordBoundaries = 0;
while ( finder.toNextBoundary() != -1 )
{
if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
wordBoundaries++;
}

if ( wordBoundaries > 0 )
{
// word boundaries found => justify by padding word spacing
extraWordSpace = spaceToDistribute / wordBoundaries;
}
else
{
// no word boundaries found => justify by letter spacing
QTextBoundaryFinder finder( QTextBoundaryFinder::Grapheme, blockText );
finder.toStart();

int graphemeBoundaries = 0;
while ( finder.toNextBoundary() != -1 )
{
if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
graphemeBoundaries++;
}

if ( graphemeBoundaries > 0 )
{
extraLetterSpace = spaceToDistribute / graphemeBoundaries;
}
}
}

void QgsTextRenderer::applyExtraSpacingForLineJustification( QFont &font, double extraWordSpace, double extraLetterSpace )
{
const double prevWordSpace = font.wordSpacing();
font.setWordSpacing( prevWordSpace + extraWordSpace );
const double prevLetterSpace = font.letterSpacing();
font.setLetterSpacing( QFont::AbsoluteSpacing, prevLetterSpace + extraLetterSpace );
}

void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, TextPart drawType, DrawMode mode, const Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, HAlignment hAlignment,
VAlignment vAlignment, double rotation )
{
@@ -1337,6 +1385,8 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
{
const QgsTextBlock block = document.at( i );

const bool isFinalLine = i == document.size() - 1;

QgsScopedQPainterState painterState( context.painter() );
context.setPainterFlagsUsingContext();
context.painter()->translate( component.origin );
@@ -1355,6 +1405,8 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
// figure x offset for horizontal alignment of multiple lines
double xMultiLineOffset = 0.0;
double labelWidth = fontMetrics->width( line ) / fontScale;
double extraWordSpace = 0;
double extraLetterSpace = 0;
if ( adjustForAlignment )
{
double labelWidthDiff = 0;
@@ -1368,8 +1420,14 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
labelWidthDiff = labelWidest - labelWidth;
break;

case AlignLeft:
case AlignJustify:
if ( !isFinalLine && labelWidest > labelWidth )
{
calculateExtraSpacingForLineJustification( labelWidest - labelWidth, block, extraWordSpace, extraLetterSpace );
}
break;

case AlignLeft:
break;
}

@@ -1399,7 +1457,6 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
}
break;
}
//QgsDebugMsgLevel( QStringLiteral( "xMultiLineOffset: %1" ).arg( xMultiLineOffset ), 4 );
}

double yMultiLineOffset = ascentOffset;
@@ -1434,6 +1491,8 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
subComponent.offset = QPointF( 0.0, -ascentOffset );
subComponent.rotation = -component.rotation * 180 / M_PI;
subComponent.rotationOffset = 0.0;
subComponent.extraWordSpacing = extraWordSpace * fontScale;
subComponent.extraLetterSpacing = extraLetterSpace * fontScale;

// draw the mask below the text (for preview)
if ( format.mask().enabled() )
@@ -1464,7 +1523,9 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con

QFont fragmentFont = font;
fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
QFontMetricsF fragmentMetrics = QFontMetricsF( fragmentFont );

if ( extraWordSpace || extraLetterSpace )
applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );

path.addText( xOffset, 0, fragmentFont, fragment.text() );

@@ -1474,12 +1535,6 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
textp.drawPath( path );

xOffset += fragment.horizontalAdvance( fragmentFont, true );

// TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
// e.g. some capitalization options, but not others
//textp.setFont( tmpLyr.textFont );
//textp.setPen( tmpLyr.textColor );
//textp.drawText( 0, 0, component.text() );
}
textp.end();

@@ -1519,6 +1574,9 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con
QFont fragmentFont = font;
fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );

if ( extraWordSpace || extraLetterSpace )
applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );

QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
textColor.setAlphaF( format.opacity() );

@@ -271,6 +271,11 @@ class CORE_EXPORT QgsTextRenderer
double dpiRatio = 1.0;
//! Horizontal alignment
HAlignment hAlign = AlignLeft;

//! Any additional word spacing to apply while rendering component
double extraWordSpacing = 0;
//! Any additional letter spacing to apply while rendering component
double extraLetterSpacing = 0;
};

static double textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document );
@@ -347,6 +352,9 @@ class CORE_EXPORT QgsTextRenderer

static QgsTextFormat::TextOrientation calculateRotationAndOrientationForComponent( const QgsTextFormat &format, const Component &component, double &rotation );

static void calculateExtraSpacingForLineJustification( double spaceToDistribute, const QgsTextBlock &block, double &extraWordSpace, double &extraLetterSpace );
static void applyExtraSpacingForLineJustification( QFont &font, double extraWordSpace, double extraLetterSpace );

static void drawTextInternalHorizontal( QgsRenderContext &context,
const QgsTextFormat &format,
TextPart drawType,
@@ -2146,7 +2146,10 @@ def testDrawTextRectMultilineJustifyAlign(self):
format.setFont(getTestFont('bold'))
format.setSize(30)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
assert self.checkRender(format, 'text_rect_multiline_justify_aligned', text=['a test', 'of', 'justification', 'align'],
format.buffer().setEnabled(True)
format.buffer().setSize(4)
format.buffer().setSizeUnit(QgsUnitTypes.RenderMillimeters)
assert self.checkRender(format, 'text_rect_multiline_justify_aligned', text=['a t est', 'off', 'justification', 'align'],
alignment=QgsTextRenderer.AlignJustify, rect=QRectF(100, 100, 200, 100))

def testDrawTextRectJustifyAlign(self):
@@ -2234,6 +2237,22 @@ def testDrawTextPointRightAlign(self):
assert self.checkRenderPoint(format, 'text_point_right_aligned', text=['test'],
alignment=QgsTextRenderer.AlignRight, point=QPointF(300, 200))

def testDrawTextPointJustifyAlign(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(30)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
assert self.checkRenderPoint(format, 'text_point_justify_aligned', text=['test'],
alignment=QgsTextRenderer.AlignJustify, point=QPointF(100, 200))

def testDrawTextPointMultilineJustifyAlign(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
format.setSize(30)
format.setSizeUnit(QgsUnitTypes.RenderPoints)
assert self.checkRenderPoint(format, 'text_point_justify_multiline_aligned', text=['a t est', 'off', 'justification', 'align'],
alignment=QgsTextRenderer.AlignJustify, point=QPointF(100, 200))

def testDrawTextPointCenterAlign(self):
format = QgsTextFormat()
format.setFont(getTestFont('bold'))
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 445beba

Please sign in to comment.
You can’t perform that action at this time.