From f5d68ff4233b0793b7bc6fd21f1d2f45ff85652c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 8 May 2020 13:34:11 +1000 Subject: [PATCH] Adapt QgsPalLabeling.splitToLines to account for HTML formatting --- .../labeling/qgspallabeling.sip.in | 2 +- src/core/labeling/qgspallabeling.cpp | 58 ++++++++++++------- src/core/labeling/qgspallabeling.h | 2 +- .../labeling/qgsvectorlayerlabelprovider.cpp | 2 +- tests/src/python/test_qgspallabeling_base.py | 10 ++++ 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/python/core/auto_generated/labeling/qgspallabeling.sip.in b/python/core/auto_generated/labeling/qgspallabeling.sip.in index f6d8aed6805..6d8e32080a1 100644 --- a/python/core/auto_generated/labeling/qgspallabeling.sip.in +++ b/python/core/auto_generated/labeling/qgspallabeling.sip.in @@ -783,7 +783,7 @@ Checks whether a geometry requires preparation before registration with PAL .. versionadded:: 2.9 %End - static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true ); + static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true, bool allowHtmlFormatting = false ); %Docstring Splits a ``text`` string to a list of separate lines, using a specified wrap character (``wrapCharacter``). The text string will be split on either newline characters or the wrap character. diff --git a/src/core/labeling/qgspallabeling.cpp b/src/core/labeling/qgspallabeling.cpp index f8ec48f1c99..409e2696fcf 100644 --- a/src/core/labeling/qgspallabeling.cpp +++ b/src/core/labeling/qgspallabeling.cpp @@ -1393,6 +1393,8 @@ void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QSt } QgsRenderContext *rc = context ? context : scopedRc.get(); + const bool htmlFormatting = mFormat.allowHtmlFormatting(); + QString wrapchr = wrapChar; int evalAutoWrapLength = autoWrapLength; double multilineH = mFormat.lineHeight(); @@ -1531,7 +1533,7 @@ void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QSt double w = 0.0, h = 0.0, rw = 0.0, rh = 0.0; double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline - const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap ); + const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap, htmlFormatting ); int lines = multiLineSplit.size(); switch ( orientation ) @@ -3548,33 +3550,49 @@ bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, Q return false; } -QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping ) +QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping, const bool allowHtmlFormatting ) { QStringList multiLineSplit; - if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) ) + auto splitLine = [ & ]( const QString & input ) { - //wrap on both the wrapchr and new line characters - const QStringList lines = text.split( wrapCharacter ); - for ( const QString &line : lines ) + QStringList thisParts; + if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) ) { - multiLineSplit.append( line.split( '\n' ) ); + //wrap on both the wrapchr and new line characters + const QStringList lines = input.split( wrapCharacter ); + for ( const QString &line : lines ) + { + thisParts.append( line.split( '\n' ) ); + } } + else + { + thisParts = input.split( '\n' ); + } + + // apply auto wrapping to each manually created line + if ( autoWrapLength != 0 ) + { + QStringList autoWrappedLines; + autoWrappedLines.reserve( thisParts.count() ); + for ( const QString &line : qgis::as_const( thisParts ) ) + { + autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) ); + } + thisParts = autoWrappedLines; + } + multiLineSplit.append( thisParts ); + }; + + if ( allowHtmlFormatting ) + { + const QStringList htmlBlocks = QgsTextRenderer::extractTextBlocksFromHtml( text ); + for ( const QString &block : htmlBlocks ) + splitLine( block ); } else { - multiLineSplit = text.split( '\n' ); - } - - // apply auto wrapping to each manually created line - if ( autoWrapLength != 0 ) - { - QStringList autoWrappedLines; - autoWrappedLines.reserve( multiLineSplit.count() ); - for ( const QString &line : qgis::as_const( multiLineSplit ) ) - { - autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) ); - } - multiLineSplit = autoWrappedLines; + splitLine( text ); } return multiLineSplit; } diff --git a/src/core/labeling/qgspallabeling.h b/src/core/labeling/qgspallabeling.h index 0ebed2d35ca..4b791f4c5cf 100644 --- a/src/core/labeling/qgspallabeling.h +++ b/src/core/labeling/qgspallabeling.h @@ -1301,7 +1301,7 @@ class CORE_EXPORT QgsPalLabeling * * \since QGIS 2.9 */ - static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true ); + static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true, bool allowHtmlFormatting = false ); /** * Splits a text string to a list of graphemes, which are the smallest allowable character diff --git a/src/core/labeling/qgsvectorlayerlabelprovider.cpp b/src/core/labeling/qgsvectorlayerlabelprovider.cpp index 78316d900f3..ade0aeb307d 100644 --- a/src/core/labeling/qgsvectorlayerlabelprovider.cpp +++ b/src/core/labeling/qgsvectorlayerlabelprovider.cpp @@ -611,7 +611,7 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q } //QgsDebugMsgLevel( "drawLabel " + txt, 4 ); - QStringList multiLineList = QgsPalLabeling::splitToLines( txt, tmpLyr.wrapChar, tmpLyr.autoWrapLength, tmpLyr.useMaxLineLengthForAutoWrap ); + QStringList multiLineList = QgsPalLabeling::splitToLines( txt, tmpLyr.wrapChar, tmpLyr.autoWrapLength, tmpLyr.useMaxLineLengthForAutoWrap, tmpLyr.format().allowHtmlFormatting() ); QgsTextRenderer::HAlignment hAlign = QgsTextRenderer::AlignLeft; if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiCenter ) diff --git a/tests/src/python/test_qgspallabeling_base.py b/tests/src/python/test_qgspallabeling_base.py index 833c11a0261..47bdf648406 100644 --- a/tests/src/python/test_qgspallabeling_base.py +++ b/tests/src/python/test_qgspallabeling_base.py @@ -356,6 +356,16 @@ class TestQgsPalLabeling(unittest.TestCase): """Intended to be overridden in subclasses""" pass + def testSplitToLines(self): + self.assertEqual(QgsPalLabeling.splitToLines('', ''), ['']) + self.assertEqual(QgsPalLabeling.splitToLines('abc def', ''), ['abc def']) + self.assertEqual(QgsPalLabeling.splitToLines('abc def', ' '), ['abc', 'def']) + self.assertEqual(QgsPalLabeling.splitToLines('abc\ndef', ' '), ['abc', 'def']) + self.assertEqual(QgsPalLabeling.splitToLines('abc def', ' ', allowHtmlFormatting=True), ['abc', 'def']) + self.assertEqual(QgsPalLabeling.splitToLines('R_ED not
red
', ' ', allowHtmlFormatting=True), ['R_ED', 'not', '', 'red']) + self.assertEqual(QgsPalLabeling.splitToLines('R_ED not
red
', '_', + allowHtmlFormatting=True), ['R', 'ED not ', 'red']) + class TestPALConfig(TestQgsPalLabeling):