37#include <QAbstractTextDocumentLayout>
38#include <QCoreApplication>
40#include <QNetworkReply>
45#include "moc_qgslayoutitemhtml.cpp"
52 mHtmlUnitsToLayoutUnits = htmlUnitsToLayoutUnits();
55 if ( QThread::currentThread() == QApplication::instance()->thread() )
57 mWebPage = std::make_unique< QgsWebPage >();
66 mWebPage->setIdentifier( tr(
"Layout HTML item" ) );
67 mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
68 mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
71 QPalette palette = mWebPage->palette();
72 palette.setBrush( QPalette::Base, Qt::transparent );
73 mWebPage->setPalette( palette );
80 setExpressionContext(
mLayout->reportContext().feature(),
mLayout->reportContext().layer() );
89 mFetcher->deleteLater();
111 frame->setVisible( label->isVisible() );
114 frame->setRotation( label->rotation() );
118 frame->setZValue( label->zValue() );
128 html->setUserStylesheetEnabled(
true );
129 html->setUserStylesheet( label->createStylesheet() );
173 switch ( mContentMode )
178 QString currentUrl = mUrl.toString();
185 currentUrl = currentUrl.trimmed();
186 QgsDebugMsgLevel( QStringLiteral(
"exprVal Source Url:%1" ).arg( currentUrl ), 2 );
188 if ( currentUrl.isEmpty() )
192 if ( !( useCache && currentUrl == mLastFetchedUrl ) )
194 loadedHtml = fetchHtml( QUrl( currentUrl ) );
195 mLastFetchedUrl = currentUrl;
199 loadedHtml = mFetchedHtml;
210 if ( mEvaluateExpressions )
218 connect( mWebPage.get(), &QWebPage::loadFinished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
222 mWebPage->setViewportSize( QSize( maxFrameWidth() * mHtmlUnitsToLayoutUnits, 0 ) );
226 QUrl( mActualFetchedUrl ) :
227 QUrl::fromLocalFile(
mLayout->project()->absoluteFilePath() );
229 mWebPage->mainFrame()->setHtml( loadedHtml, baseUrl );
233 if ( mEnableUserStylesheet && ! mUserStylesheet.isEmpty() )
236 ba.append( mUserStylesheet.toUtf8() );
237 const QUrl cssFileURL = QUrl( QString(
"data:text/css;charset=utf-8;base64," + ba.toBase64() ) );
238 settings->setUserStyleSheetUrl( cssFileURL );
242 settings->setUserStyleSheetUrl( QUrl() );
246 loop.exec( QEventLoop::ExcludeUserInputEvents );
250 if ( !mAtlasFeatureJSON.isEmpty() )
252 JavascriptExecutorLoop jsLoop;
254 mWebPage->mainFrame()->addToJavaScriptWindowObject( QStringLiteral(
"loop" ), &jsLoop );
255 mWebPage->mainFrame()->evaluateJavaScript( QStringLiteral(
"if ( typeof setFeature === \"function\" ) { try{ setFeature(%1); } catch (err) { loop.reportError(err.message); } }; loop.done();" ).arg( mAtlasFeatureJSON ) );
257 jsLoop.execIfNotDone();
266double QgsLayoutItemHtml::maxFrameWidth()
const
271 maxWidth = std::max( maxWidth,
static_cast< double >(
frame->boundingRect().width() ) );
285 QSize contentsSize = mWebPage->mainFrame()->contentsSize();
288 const double maxWidth = maxFrameWidth();
290 contentsSize.setWidth( maxWidth * mHtmlUnitsToLayoutUnits );
292 mWebPage->setViewportSize( contentsSize );
293 mSize.setWidth( contentsSize.width() / mHtmlUnitsToLayoutUnits );
294 mSize.setHeight( contentsSize.height() / mHtmlUnitsToLayoutUnits );
295 if ( contentsSize.isValid() )
303void QgsLayoutItemHtml::renderCachedImage()
309 mRenderedPage = QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
310 if ( mRenderedPage.isNull() )
314 mRenderedPage.fill( Qt::transparent );
316 painter.begin( &mRenderedPage );
317 mWebPage->mainFrame()->render( &painter );
321QString QgsLayoutItemHtml::fetchHtml(
const QUrl &url )
327 mFetcher->fetchContent(
url );
330 loop.exec( QEventLoop::ExcludeUserInputEvents );
332 mFetchedHtml = mFetcher->contentAsString();
333 mActualFetchedUrl = mFetcher->reply()->url().toString();
353 painter->translate( 0.0, -renderExtent.top() * mHtmlUnitsToLayoutUnits );
354 mWebPage->mainFrame()->render( painter, QRegion( renderExtent.left(), renderExtent.top() * mHtmlUnitsToLayoutUnits, renderExtent.width() * mHtmlUnitsToLayoutUnits, renderExtent.height() * mHtmlUnitsToLayoutUnits ) );
356 Q_UNUSED( renderExtent )
357 if (
mLayout->renderContext().isPreviewRender() )
370 painter->setBrush( QBrush( QColor( 255, 125, 125, 125 ) ) );
371 painter->setPen( Qt::NoPen );
372 painter->drawRect( painterRect );
373 painter->setBrush( Qt::NoBrush );
375 painter->setPen( QColor( 200, 0, 0, 255 ) );
377 td.setTextWidth( painterRect.width() );
378 td.setHtml( QStringLiteral(
"<span style=\"color: rgb(200,0,0);\"><b>%1</b><br>%2</span>" ).arg(
379 tr(
"WebKit not available!" ),
380 tr(
"The item cannot be rendered because this QGIS install was built without WebKit support." ) ) );
381 painter->setClipRect( painterRect );
382 QAbstractTextDocumentLayout::PaintContext ctx;
383 td.documentLayout()->draw( painter, ctx );
389double QgsLayoutItemHtml::htmlUnitsToLayoutUnits()
401 if ( c1.second < c2.second )
403 else if ( c1.second > c2.second )
405 else if ( c1.first > c2.first )
413 if ( !mWebPage || mRenderedPage.isNull() || !mUseSmartBreaks )
419 const int idealPos = yPos * htmlUnitsToLayoutUnits();
422 if ( idealPos >= mRenderedPage.height() )
427 const int maxSearchDistance = mMaxBreakDistance * htmlUnitsToLayoutUnits();
433 bool currentPixelTransparent =
false;
434 bool previousPixelTransparent =
false;
436 QList< QPair<int, int> > candidates;
437 const int minRow = std::max( idealPos - maxSearchDistance, 0 );
438 for (
int candidateRow = idealPos; candidateRow >= minRow; --candidateRow )
441 currentColor = qRgba( 0, 0, 0, 0 );
443 for (
int col = 0; col < mRenderedPage.width(); ++col )
449 pixelColor = mRenderedPage.pixel( col, candidateRow );
450 currentPixelTransparent = qAlpha( pixelColor ) == 0;
451 if ( pixelColor != currentColor && !( currentPixelTransparent && previousPixelTransparent ) )
454 currentColor = pixelColor;
457 previousPixelTransparent = currentPixelTransparent;
459 candidates.append( qMakePair( candidateRow, changes ) );
463 std::sort( candidates.begin(), candidates.end(),
candidateSort );
470 const int maxCandidateRow = candidates[0].first;
471 int minCandidateRow = maxCandidateRow + 1;
472 const int minCandidateChanges = candidates[0].second;
474 QList< QPair<int, int> >::iterator it;
475 for ( it = candidates.begin(); it != candidates.end(); ++it )
477 if ( ( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
482 return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToLayoutUnits();
484 minCandidateRow = ( *it ).first;
489 return candidates[0].first / htmlUnitsToLayoutUnits();
508 mUserStylesheet = stylesheet;
517 if ( mEnableUserStylesheet != stylesheetEnabled )
519 mEnableUserStylesheet = stylesheetEnabled;
527 return tr(
"<HTML frame>" );
532 htmlElem.setAttribute( QStringLiteral(
"contentMode" ), QString::number(
static_cast< int >( mContentMode ) ) );
533 htmlElem.setAttribute( QStringLiteral(
"url" ), mUrl.toString() );
534 htmlElem.setAttribute( QStringLiteral(
"html" ), mHtml );
535 htmlElem.setAttribute( QStringLiteral(
"evaluateExpressions" ), mEvaluateExpressions ?
"true" :
"false" );
536 htmlElem.setAttribute( QStringLiteral(
"useSmartBreaks" ), mUseSmartBreaks ?
"true" :
"false" );
537 htmlElem.setAttribute( QStringLiteral(
"maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
538 htmlElem.setAttribute( QStringLiteral(
"stylesheet" ), mUserStylesheet );
539 htmlElem.setAttribute( QStringLiteral(
"stylesheetEnabled" ), mEnableUserStylesheet ?
"true" :
"false" );
547 if ( !contentModeOK )
551 mEvaluateExpressions = itemElem.attribute( QStringLiteral(
"evaluateExpressions" ), QStringLiteral(
"true" ) ) == QLatin1String(
"true" );
552 mUseSmartBreaks = itemElem.attribute( QStringLiteral(
"useSmartBreaks" ), QStringLiteral(
"true" ) ) == QLatin1String(
"true" );
553 mMaxBreakDistance = itemElem.attribute( QStringLiteral(
"maxBreakDistance" ), QStringLiteral(
"10" ) ).toDouble();
554 mHtml = itemElem.attribute( QStringLiteral(
"html" ) );
555 mUserStylesheet = itemElem.attribute( QStringLiteral(
"stylesheet" ) );
556 mEnableUserStylesheet = itemElem.attribute( QStringLiteral(
"stylesheetEnabled" ), QStringLiteral(
"false" ) ) == QLatin1String(
"true" );
559 const QString urlString = itemElem.attribute( QStringLiteral(
"url" ) );
560 if ( !urlString.isEmpty() )
573 mExpressionFeature = feature;
574 mExpressionLayer = layer;
584 QgsLayoutItemMap *referenceMap =
mLayout->referenceMap();
586 mDistanceArea.setSourceCrs( referenceMap->
crs(),
mLayout->project()->transformContext() );
590 mDistanceArea.setEllipsoid(
mLayout->project()->ellipsoid() );
596 QgsJsonExporter exporter( layer );
597 exporter.setIncludeRelated(
true );
598 mAtlasFeatureJSON = exporter.exportFeature( feature );
602 mAtlasFeatureJSON.clear();
606void QgsLayoutItemHtml::refreshExpressionContext()
608 QgsVectorLayer *vl =
nullptr;
613 vl =
mLayout->reportContext().layer();
614 feature =
mLayout->reportContext().feature();
617 setExpressionContext( feature, vl );
635void JavascriptExecutorLoop::done()
641void JavascriptExecutorLoop::execIfNotDone()
644 exec( QEventLoop::ExcludeUserInputEvents );
648 for (
int i = 0; i < 100; i++ )
649 qApp->processEvents();
652void JavascriptExecutorLoop::reportError(
const QString &error )
A collection of stubs to mimic the API of a QWebSettings on systems where QtWebkit is not available.
@ Millimeters
Millimeters.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
bool isValid() const
Returns the validity of this feature.
Base class for frame items, which form a layout multiframe item.
int type() const override
Returns unique multiframe type id.
QSizeF totalSize() const override
Returns the total size of the multiframe's content, in layout units.
void setUrl(const QUrl &url)
Sets the url for content to display in the item when the item is using the QgsLayoutItemHtml::Url mod...
ContentMode
Source modes for the HTML content to render in the item.
@ ManualHtml
HTML content is manually set for the item.
@ Url
Using this mode item fetches its content via a url.
void setEvaluateExpressions(bool evaluateExpressions)
Sets whether the html item will evaluate QGIS expressions prior to rendering the HTML content.
bool readPropertiesFromElement(const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets multiframe state from a DOM element.
QString html() const
Returns the HTML source displayed in the item if the item is using the QgsLayoutItemHtml::ManualHtml ...
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties) override
~QgsLayoutItemHtml() override
double maxBreakDistance() const
Returns the maximum distance allowed when calculating where to place page breaks in the html.
static QgsLayoutItemHtml * createFromLabel(QgsLayoutItemLabel *label)
Returns a new QgsLayoutItemHtml matching the content and rendering of a given label.
static QgsLayoutItemHtml * create(QgsLayout *layout)
Returns a new QgsLayoutItemHtml for the specified parent layout.
bool evaluateExpressions() const
Returns whether html item will evaluate QGIS expressions prior to rendering the HTML content.
double findNearbyPageBreak(double yPos) override
Finds the optimal position to break a frame at.
QUrl url() const
Returns the URL of the content displayed in the item if the item is using the QgsLayoutItemHtml::Url ...
void setMaxBreakDistance(double distance)
Sets the maximum distance allowed when calculating where to place page breaks in the html.
void setUserStylesheetEnabled(bool enabled)
Sets whether user stylesheets are enabled for the HTML content.
void setHtml(const QString &html)
Sets the html to display in the item when the item is using the QgsLayoutItemHtml::ManualHtml mode.
QString displayName() const override
Returns the multiframe display name.
void setUseSmartBreaks(bool useSmartBreaks)
Sets whether the html item should use smart breaks.
void recalculateFrameSizes() override
Recalculates the frame sizes for the current viewport dimensions.
void setUserStylesheet(const QString &stylesheet)
Sets the user stylesheet CSS rules to use while rendering the HTML content.
QIcon icon() const override
Returns the item's icon.
bool writePropertiesToElement(QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context) const override
Stores multiframe state within an XML DOM element.
QgsLayoutItemHtml(QgsLayout *layout)
Constructor for QgsLayoutItemHtml, with the specified parent layout.
void loadHtml(bool useCache=false, const QgsExpressionContext *context=nullptr)
Reloads the html source from the url and redraws the item.
void render(QgsLayoutItemRenderContext &context, const QRectF &renderExtent, int frameIndex) override
Renders a portion of the multiframe's content into a render context.
bool useSmartBreaks() const
Returns whether html item is using smart breaks.
A layout item subclass for text labels.
QString currentText() const
Returns the text as it appears on the label (with evaluated expressions and other dynamic content).
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
@ LayoutHtml
Html multiframe item.
Contains settings and helpers relating to a render of a QgsLayoutItem.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
QColor backgroundColor(bool useDataDefined=true) const
Returns the background color for this item.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
QgsLayoutItemGroup * parentGroup() const
Returns the item's parent group, if the item is part of a QgsLayoutItemGroup group.
QgsLayoutMeasurement frameStrokeWidth() const
Returns the frame's stroke width.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
double itemOpacity() const
Returns the item's opacity.
ReferencePoint referencePoint() const
Returns the reference point for positioning of the layout item.
QgsLayoutPoint positionWithUnits() const
Returns the item's current position, including units.
bool frameEnabled() const
Returns true if the item includes a frame.
QColor frameStrokeColor() const
Returns the frame's stroke color.
Qt::PenJoinStyle frameJoinStyle() const
Returns the join style used for drawing the item's frame.
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutMultiFrame(QgsLayout *layout)
Construct a new multiframe item, attached to the specified layout.
void contentsChanged()
Emitted when the contents of the multi frame have changed and the frames must be redrawn.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QList< QgsLayoutFrame * > mFrameItems
friend class QgsLayoutFrame
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
int frameIndex(QgsLayoutFrame *frame) const
Returns the index of a frame within the multiframe.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
DataDefinedProperty
Data defined properties for different item types.
@ SourceUrl
Html source url.
@ AllProperties
All properties for item.
void changed()
Emitted certain settings in the context is changed, e.g.
QgsCoordinateReferenceSystem crs
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
HTTP network content fetcher.
void finished()
Emitted when content has loaded.
A container for the context for various read/write operations on objects.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
Represents a vector layer which manages a vector based dataset.
bool candidateSort(QPair< int, int > c1, QPair< int, int > c2)
#define QgsDebugMsgLevel(str, level)