33 #include <QCoreApplication> 
   36 #include <QNetworkReply> 
   45   mHtmlUnitsToLayoutUnits = htmlUnitsToLayoutUnits();
 
   48   if ( QThread::currentThread() == QApplication::instance()->thread() )
 
   50     mWebPage = std::make_unique< QgsWebPage >();
 
   59     mWebPage->setIdentifier( tr( 
"Layout HTML item" ) );
 
   60     mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
 
   61     mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
 
   64     QPalette palette = mWebPage->palette();
 
   65     palette.setBrush( QPalette::Base, Qt::transparent );
 
   66     mWebPage->setPalette( palette );
 
   73   setExpressionContext( 
mLayout->reportContext().feature(), 
mLayout->reportContext().layer() );
 
   82   mFetcher->deleteLater();
 
  139   switch ( mContentMode )
 
  144       QString currentUrl = mUrl.toString();
 
  151         currentUrl = currentUrl.trimmed();
 
  152         QgsDebugMsg( QStringLiteral( 
"exprVal Source Url:%1" ).arg( currentUrl ) );
 
  154       if ( currentUrl.isEmpty() )
 
  158       if ( !( useCache && currentUrl == mLastFetchedUrl ) )
 
  160         loadedHtml = fetchHtml( QUrl( currentUrl ) );
 
  161         mLastFetchedUrl = currentUrl;
 
  165         loadedHtml = mFetchedHtml;
 
  176   if ( mEvaluateExpressions )
 
  184   connect( mWebPage.get(), &QWebPage::loadFinished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
 
  188   mWebPage->setViewportSize( QSize( maxFrameWidth() * mHtmlUnitsToLayoutUnits, 0 ) );
 
  192                        QUrl( mActualFetchedUrl ) :
 
  193                        QUrl::fromLocalFile( 
mLayout->project()->absoluteFilePath() );
 
  195   mWebPage->mainFrame()->setHtml( loadedHtml, baseUrl );
 
  199   if ( mEnableUserStylesheet && ! mUserStylesheet.isEmpty() )
 
  202     ba.append( mUserStylesheet.toUtf8() );
 
  203     const QUrl cssFileURL = QUrl( QString( 
"data:text/css;charset=utf-8;base64," + ba.toBase64() ) );
 
  204     settings->setUserStyleSheetUrl( cssFileURL );
 
  208     settings->setUserStyleSheetUrl( QUrl() );
 
  212     loop.exec( QEventLoop::ExcludeUserInputEvents );
 
  215   if ( !mAtlasFeatureJSON.isEmpty() )
 
  217     JavascriptExecutorLoop jsLoop;
 
  219     mWebPage->mainFrame()->addToJavaScriptWindowObject( QStringLiteral( 
"loop" ), &jsLoop );
 
  220     mWebPage->mainFrame()->evaluateJavaScript( QStringLiteral( 
"if ( typeof setFeature === \"function\" ) { try{ setFeature(%1); } catch (err) { loop.reportError(err.message); } }; loop.done();" ).arg( mAtlasFeatureJSON ) );
 
  222     jsLoop.execIfNotDone();
 
  230 double QgsLayoutItemHtml::maxFrameWidth()
 const 
  235     maxWidth = std::max( maxWidth, 
static_cast< double >( 
frame->boundingRect().width() ) );
 
  249   QSize contentsSize = mWebPage->mainFrame()->contentsSize();
 
  252   const double maxWidth = maxFrameWidth();
 
  254   contentsSize.setWidth( maxWidth * mHtmlUnitsToLayoutUnits );
 
  256   mWebPage->setViewportSize( contentsSize );
 
  257   mSize.setWidth( contentsSize.width() / mHtmlUnitsToLayoutUnits );
 
  258   mSize.setHeight( contentsSize.height() / mHtmlUnitsToLayoutUnits );
 
  259   if ( contentsSize.isValid() )
 
  267 void QgsLayoutItemHtml::renderCachedImage()
 
  273   mRenderedPage = QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
 
  274   if ( mRenderedPage.isNull() )
 
  278   mRenderedPage.fill( Qt::transparent );
 
  280   painter.begin( &mRenderedPage );
 
  281   mWebPage->mainFrame()->render( &painter );
 
  285 QString QgsLayoutItemHtml::fetchHtml( 
const QUrl &url )
 
  294     loop.exec( QEventLoop::ExcludeUserInputEvents );
 
  297   mActualFetchedUrl = mFetcher->
reply()->url().toString();
 
  315   painter->translate( 0.0, -renderExtent.top() * mHtmlUnitsToLayoutUnits );
 
  316   mWebPage->mainFrame()->render( painter, QRegion( renderExtent.left(), renderExtent.top() * mHtmlUnitsToLayoutUnits, renderExtent.width() * mHtmlUnitsToLayoutUnits, renderExtent.height() * mHtmlUnitsToLayoutUnits ) );
 
  319 double QgsLayoutItemHtml::htmlUnitsToLayoutUnits()
 
  331   if ( c1.second < c2.second )
 
  333   else if ( c1.second > c2.second )
 
  335   else if ( c1.first > c2.first )
 
  343   if ( !mWebPage || mRenderedPage.isNull() || !mUseSmartBreaks )
 
  349   const int idealPos = yPos * htmlUnitsToLayoutUnits();
 
  352   if ( idealPos >= mRenderedPage.height() )
 
  357   const int maxSearchDistance = mMaxBreakDistance * htmlUnitsToLayoutUnits();
 
  363   bool currentPixelTransparent = 
false;
 
  364   bool previousPixelTransparent = 
false;
 
  366   QList< QPair<int, int> > candidates;
 
  367   const int minRow = std::max( idealPos - maxSearchDistance, 0 );
 
  368   for ( 
int candidateRow = idealPos; candidateRow >= minRow; --candidateRow )
 
  371     currentColor = qRgba( 0, 0, 0, 0 );
 
  373     for ( 
int col = 0; col < mRenderedPage.width(); ++col )
 
  379       pixelColor = mRenderedPage.pixel( col, candidateRow );
 
  380       currentPixelTransparent = qAlpha( pixelColor ) == 0;
 
  381       if ( pixelColor != currentColor && !( currentPixelTransparent && previousPixelTransparent ) )
 
  384         currentColor = pixelColor;
 
  387       previousPixelTransparent = currentPixelTransparent;
 
  389     candidates.append( qMakePair( candidateRow, changes ) );
 
  393   std::sort( candidates.begin(), candidates.end(), 
candidateSort );
 
  400   const int maxCandidateRow = candidates[0].first;
 
  401   int minCandidateRow = maxCandidateRow + 1;
 
  402   const int minCandidateChanges = candidates[0].second;
 
  404   QList< QPair<int, int> >::iterator it;
 
  405   for ( it = candidates.begin(); it != candidates.end(); ++it )
 
  407     if ( ( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
 
  412       return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToLayoutUnits();
 
  414     minCandidateRow = ( *it ).first;
 
  419   return candidates[0].first / htmlUnitsToLayoutUnits();
 
  438   mUserStylesheet = stylesheet;
 
  447   if ( mEnableUserStylesheet != stylesheetEnabled )
 
  449     mEnableUserStylesheet = stylesheetEnabled;
 
  457   return tr( 
"<HTML frame>" );
 
  462   htmlElem.setAttribute( QStringLiteral( 
"contentMode" ), QString::number( 
static_cast< int >( mContentMode ) ) );
 
  463   htmlElem.setAttribute( QStringLiteral( 
"url" ), mUrl.toString() );
 
  464   htmlElem.setAttribute( QStringLiteral( 
"html" ), mHtml );
 
  465   htmlElem.setAttribute( QStringLiteral( 
"evaluateExpressions" ), mEvaluateExpressions ? 
"true" : 
"false" );
 
  466   htmlElem.setAttribute( QStringLiteral( 
"useSmartBreaks" ), mUseSmartBreaks ? 
"true" : 
"false" );
 
  467   htmlElem.setAttribute( QStringLiteral( 
"maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
 
  468   htmlElem.setAttribute( QStringLiteral( 
"stylesheet" ), mUserStylesheet );
 
  469   htmlElem.setAttribute( QStringLiteral( 
"stylesheetEnabled" ), mEnableUserStylesheet ? 
"true" : 
"false" );
 
  477   if ( !contentModeOK )
 
  481   mEvaluateExpressions = itemElem.attribute( QStringLiteral( 
"evaluateExpressions" ), QStringLiteral( 
"true" ) ) == QLatin1String( 
"true" );
 
  482   mUseSmartBreaks = itemElem.attribute( QStringLiteral( 
"useSmartBreaks" ), QStringLiteral( 
"true" ) ) == QLatin1String( 
"true" );
 
  483   mMaxBreakDistance = itemElem.attribute( QStringLiteral( 
"maxBreakDistance" ), QStringLiteral( 
"10" ) ).toDouble();
 
  484   mHtml = itemElem.attribute( QStringLiteral( 
"html" ) );
 
  485   mUserStylesheet = itemElem.attribute( QStringLiteral( 
"stylesheet" ) );
 
  486   mEnableUserStylesheet = itemElem.attribute( QStringLiteral( 
"stylesheetEnabled" ), QStringLiteral( 
"false" ) ) == QLatin1String( 
"true" );
 
  489   const QString urlString = itemElem.attribute( QStringLiteral( 
"url" ) );
 
  490   if ( !urlString.isEmpty() )
 
  503   mExpressionFeature = feature;
 
  504   mExpressionLayer = layer;
 
  527     exporter.setIncludeRelated( 
true );
 
  528     mAtlasFeatureJSON = exporter.exportFeature( feature );
 
  532     mAtlasFeatureJSON.clear();
 
  536 void QgsLayoutItemHtml::refreshExpressionContext()
 
  543     vl = 
mLayout->reportContext().layer();
 
  544     feature = 
mLayout->reportContext().feature();
 
  547   setExpressionContext( feature, vl );
 
  565 void JavascriptExecutorLoop::done()
 
  571 void JavascriptExecutorLoop::execIfNotDone()
 
  574     exec( QEventLoop::ExcludeUserInputEvents );
 
  578   for ( 
int i = 0; i < 100; i++ )
 
  579     qApp->processEvents();
 
  582 void JavascriptExecutorLoop::reportError( 
const QString &error )
 
The QWebSettings class is a collection of stubs to mimic the API of a QWebSettings on systems where Q...
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
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.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
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.
Handles exporting QgsFeature features to GeoJSON features.
Base class for frame items, which form a layout multiframe item.
A layout multiframe subclass for HTML content.
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 ...
~QgsLayoutItemHtml() override
double maxBreakDistance() const
Returns the maximum distance allowed when calculating where to place page breaks in the html.
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.
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
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.
Layout graphical items for displaying a map.
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.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
int frameCount() const
Returns the number of frames associated with this multiframe.
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
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
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.
@ AllProperties
All properties for item.
@ SourceUrl
Html source url.
void changed()
Emitted certain settings in the context is changed, e.g.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
QgsCoordinateReferenceSystem crs
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
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.
QNetworkReply * reply()
Returns a reference to the network reply.
QString contentAsString() const
Returns the fetched content as a string.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
The class is used as a container of context for various read/write operations on other 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.
@ LayoutMillimeters
Millimeters.
Represents a vector layer which manages a vector based data sets.
bool candidateSort(QPair< int, int > c1, QPair< int, int > c2)