Skip to content
Permalink
Browse files

More cleanups, avoid multiple parsing of the same HTML content for a …

…label
  • Loading branch information
nyalldawson committed May 11, 2020
1 parent a3b9164 commit 7fc9c555deff6700c14ad24af0344c36273168ec
@@ -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, bool allowHtmlFormatting = false );
static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true );
%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.
@@ -1940,15 +1940,6 @@ and background shapes.
AlignRight,
};

static QStringList extractTextBlocksFromHtml( const QString &html );
%Docstring
Extracts text blocks from a ``html`` string.

This removes formatting tags, and outputs lines of text which represent the text content of HTML blocks.

.. versionadded:: 3.14
%End

static int sizeToPixel( double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale = QgsMapUnitScale() );
%Docstring
Calculates pixel size (considering output size should be in pixel or map units, scale factors and optionally oversampling)
@@ -1374,7 +1374,7 @@ bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const
return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
}

void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY )
void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY, QgsTextDocument *document )
{
if ( !fm || !f )
{
@@ -1393,8 +1393,6 @@ 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();
@@ -1533,7 +1531,19 @@ 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, htmlFormatting );

QStringList multiLineSplit;

if ( document )
{
document->splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
multiLineSplit = document->toPlainText();
}
else
{
multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
}

int lines = multiLineSplit.size();

switch ( orientation )
@@ -1889,8 +1899,13 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
// NOTE: this should come AFTER any option that affects font metrics
std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
double labelX, labelY, rotatedLabelX, rotatedLabelY; // will receive label size
calculateLabelSize( labelFontMetrics.get(), labelText, labelX, labelY, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY );

QgsTextDocument doc;
if ( format().allowHtmlFormatting() )
doc = QgsTextDocument::fromHtml( QStringList() << labelText );

// also applies the line split to doc!
calculateLabelSize( labelFontMetrics.get(), labelText, labelX, labelY, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, format().allowHtmlFormatting() ? &doc : nullptr );

// maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
//
@@ -2428,6 +2443,7 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
lf->setAnchorPosition( anchorPosition );
lf->setFeature( feature );
lf->setSymbol( symbol );
lf->setDocument( doc );
if ( !qgsDoubleNear( rotatedLabelX, 0.0 ) && !qgsDoubleNear( rotatedLabelY, 0.0 ) )
lf->setRotatedSize( QSizeF( rotatedLabelX, rotatedLabelY ) );
mFeatsRegPal++;
@@ -2470,7 +2486,7 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
// TODO: only for placement which needs character info
// account for any data defined font metrics adjustments
lf->calculateInfo( placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved,
labelFontMetrics.get(), xform, maxcharanglein, maxcharangleout, format().allowHtmlFormatting() );
labelFontMetrics.get(), xform, maxcharanglein, maxcharangleout, format().allowHtmlFormatting() ? &doc : nullptr );
// for labelFeature the LabelInfo is passed to feat when it is registered

// TODO: allow layer-wide feature dist in PAL...?
@@ -3550,49 +3566,33 @@ bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, Q
return false;
}

QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping, const bool allowHtmlFormatting )
QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
{
QStringList multiLineSplit;
auto splitLine = [ & ]( const QString & input )
if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
{
QStringList thisParts;
if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
//wrap on both the wrapchr and new line characters
const QStringList lines = text.split( wrapCharacter );
for ( const QString &line : lines )
{
//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' ) );
}
multiLineSplit.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
{
splitLine( text );
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;
}
return multiLineSplit;
}
@@ -943,7 +943,12 @@ class CORE_EXPORT QgsPalLayerSettings
* If the text orientation is set to rotation-based, the spaced taken to render
* vertically oriented text will be written to \a rotatedLabelX and \a rotatedLabelY .
*/
#ifndef SIP_RUN
void calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f = nullptr, QgsRenderContext *context = nullptr, double *rotatedLabelX SIP_OUT = nullptr, double *rotatedLabelY SIP_OUT = nullptr,
QgsTextDocument *document = nullptr );
#else
void calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f = nullptr, QgsRenderContext *context = nullptr, double *rotatedLabelX SIP_OUT = nullptr, double *rotatedLabelY SIP_OUT = nullptr );
#endif

/**
* Register a feature for labeling.
@@ -1301,7 +1306,7 @@ class CORE_EXPORT QgsPalLabeling
*
* \since QGIS 2.9
*/
static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true, bool allowHtmlFormatting = false );
static QStringList splitToLines( const QString &text, const QString &wrapCharacter, int autoWrapLength = 0, bool useMaxLineLengthWhenAutoWrapping = true );

/**
* Splits a text string to a list of graphemes, which are the smallest allowable character
@@ -52,7 +52,7 @@ bool QgsTextLabelFeature::hasCharacterFormat( int partId ) const
return partId < mCharacterFormats.size();
}

void QgsTextLabelFeature::calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, double maxinangle, double maxoutangle, bool allowHtmlTags )
void QgsTextLabelFeature::calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, double maxinangle, double maxoutangle, QgsTextDocument *document )
{
if ( mInfo )
return;
@@ -81,34 +81,19 @@ void QgsTextLabelFeature::calculateInfo( bool curvedLabeling, QFontMetricsF *fm,
qreal charWidth;
qreal wordSpaceFix;


if ( allowHtmlTags && curvedLabeling )
if ( document && curvedLabeling )
{
QTextDocument doc;
doc.setHtml( mLabelText );

QTextBlock block = doc.firstBlock();
while ( true )
for ( const QgsTextBlock &block : qgis::as_const( *document ) )
{
auto it = block.begin();
QString blockText;
while ( !it.atEnd() )
for ( const QgsTextFragment &fragment : block )
{
const QTextFragment fragment = it.fragment();
if ( fragment.isValid() )
const QStringList graphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
for ( const QString &grapheme : graphemes )
{
const QStringList graphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
for ( const QString &grapheme : graphemes )
{
mClusters.append( grapheme );
mCharacterFormats.append( fragment.charFormat() );
}
mClusters.append( grapheme );
mCharacterFormats.append( fragment.characterFormat() );
}
it++;
}
block = block.next();
if ( !block.isValid() )
break;
}
}
else
@@ -148,3 +133,13 @@ void QgsTextLabelFeature::calculateInfo( bool curvedLabeling, QFontMetricsF *fm,
mInfo->char_info[i].width = labelWidth;
}
}

QgsTextDocument QgsTextLabelFeature::document() const
{
return mDocument;
}

void QgsTextLabelFeature::setDocument( const QgsTextDocument &document )
{
mDocument = document;
}
@@ -60,7 +60,7 @@ class QgsTextLabelFeature : public QgsLabelFeature
bool hasCharacterFormat( int partId ) const;

//! calculate data for info(). setDefinedFont() must have been called already.
void calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, double maxinangle, double maxoutangle, bool allowHtmlTags );
void calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, double maxinangle, double maxoutangle, QgsTextDocument *document = nullptr );

//! Gets data-defined values
const QMap< QgsPalLayerSettings::Property, QVariant > &dataDefinedValues() const { return mDataDefinedValues; }
@@ -75,6 +75,20 @@ class QgsTextLabelFeature : public QgsLabelFeature
//! Metrics of the font for rendering
QFontMetricsF *labelFontMetrics() { return mFontMetrics; }

/**
* Returns the document for the label.
* \see setDocument()
* \since QGIS 3.14
*/
QgsTextDocument document() const;

/**
* Sets the \a document for the label.
* \see document()
* \since QGIS 3.14
*/
void setDocument( const QgsTextDocument &document );

protected:
//! List of graphemes (used for curved labels)
QStringList mClusters;
@@ -88,6 +102,8 @@ class QgsTextLabelFeature : public QgsLabelFeature
//! Stores attribute values for data defined properties
QMap< QgsPalLayerSettings::Property, QVariant > mDataDefinedValues;

QgsTextDocument mDocument;

};

#endif //QGSTEXTLABELFEATURE_H
@@ -633,8 +633,7 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q
}
else
{
document = QgsTextDocument::fromHtml( QStringList() << txt );
document.splitLines( tmpLyr.wrapChar, tmpLyr.autoWrapLength, tmpLyr.useMaxLineLengthForAutoWrap );
document = lf->document();
}

QgsTextRenderer::drawTextInternal( drawType, context, tmpLyr.format(), component, document, labelfm,
@@ -2605,38 +2605,6 @@ QPixmap QgsTextFormat::textFormatPreviewPixmap( const QgsTextFormat &format, QSi
return pixmap;
}

QStringList QgsTextRenderer::extractTextBlocksFromHtml( const QString &html )
{
QStringList res;

QTextDocument doc;
doc.setHtml( html );

QTextBlock block = doc.firstBlock();
while ( true )
{
auto it = block.begin();
QString blockText;
while ( !it.atEnd() )
{
const QTextFragment fragment = it.fragment();
if ( fragment.isValid() )
{
blockText.append( fragment.text() );
}
it++;
}
if ( !blockText.isEmpty() )
res << blockText;

block = block.next();
if ( !block.isValid() )
break;
}

return res;
}

int QgsTextRenderer::sizeToPixel( double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale )
{
return static_cast< int >( c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 ); //NOLINT
@@ -4475,6 +4443,9 @@ QgsTextDocument QgsTextDocument::fromHtml( const QStringList &lines )
document.reserve( lines.size() );
for ( const QString &line : lines )
{
// QTextDocument is a very heavy way of parsing HTML + css (it's heavily geared toward an editable text document,
// and includes a LOT of calculations we don't need, when all we're after is a HTML + CSS style parser).
// TODO - try to find an alternative library we can use here
QTextDocument sourceDoc;
sourceDoc.setHtml( line );

@@ -1955,15 +1955,6 @@ class CORE_EXPORT QgsTextRenderer
AlignRight, //!< Right align
};

/**
* Extracts text blocks from a \a html string.
*
* This removes formatting tags, and outputs lines of text which represent the text content of HTML blocks.
*
* \since QGIS 3.14
*/
static QStringList extractTextBlocksFromHtml( const QString &html );

/**
* Calculates pixel size (considering output size should be in pixel or map units, scale factors and optionally oversampling)
* \param size size to convert

0 comments on commit 7fc9c55

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