33 #include <QCoreApplication>
36 #include <QNetworkReply>
43 mHtmlUnitsToLayoutUnits = htmlUnitsToLayoutUnits();
46 if ( QThread::currentThread() == QApplication::instance()->thread() )
48 mWebPage = qgis::make_unique< QgsWebPage >();
57 mWebPage->setIdentifier( tr(
"Layout HTML item" ) );
58 mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
59 mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
62 QPalette palette = mWebPage->palette();
63 palette.setBrush( QPalette::Base, Qt::transparent );
64 mWebPage->setPalette( palette );
71 setExpressionContext(
mLayout->reportContext().feature(),
mLayout->reportContext().layer() );
80 mFetcher->deleteLater();
137 switch ( mContentMode )
142 QString currentUrl = mUrl.toString();
149 currentUrl = currentUrl.trimmed();
150 QgsDebugMsg( QStringLiteral(
"exprVal Source Url:%1" ).arg( currentUrl ) );
152 if ( currentUrl.isEmpty() )
156 if ( !( useCache && currentUrl == mLastFetchedUrl ) )
158 loadedHtml = fetchHtml( QUrl( currentUrl ) );
159 mLastFetchedUrl = currentUrl;
163 loadedHtml = mFetchedHtml;
174 if ( mEvaluateExpressions )
182 connect( mWebPage.get(), &QWebPage::loadFinished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
186 mWebPage->setViewportSize( QSize( maxFrameWidth() * mHtmlUnitsToLayoutUnits, 0 ) );
190 QUrl( mActualFetchedUrl ) :
191 QUrl::fromLocalFile(
mLayout->project()->absoluteFilePath() );
193 mWebPage->mainFrame()->setHtml( loadedHtml, baseUrl );
197 if ( mEnableUserStylesheet && ! mUserStylesheet.isEmpty() )
200 ba.append( mUserStylesheet.toUtf8() );
201 QUrl cssFileURL = QUrl( QString(
"data:text/css;charset=utf-8;base64," + ba.toBase64() ) );
202 settings->setUserStyleSheetUrl( cssFileURL );
206 settings->setUserStyleSheetUrl( QUrl() );
210 loop.exec( QEventLoop::ExcludeUserInputEvents );
213 if ( !mAtlasFeatureJSON.isEmpty() )
215 JavascriptExecutorLoop jsLoop;
217 mWebPage->mainFrame()->addToJavaScriptWindowObject( QStringLiteral(
"loop" ), &jsLoop );
218 mWebPage->mainFrame()->evaluateJavaScript( QStringLiteral(
"if ( typeof setFeature === \"function\" ) { try{ setFeature(%1); } catch (err) { loop.reportError(err.message); } }; loop.done();" ).arg( mAtlasFeatureJSON ) );
220 jsLoop.execIfNotDone();
228 double QgsLayoutItemHtml::maxFrameWidth()
const
233 maxWidth = std::max( maxWidth,
static_cast< double >(
frame->boundingRect().width() ) );
247 QSize contentsSize = mWebPage->mainFrame()->contentsSize();
250 double maxWidth = maxFrameWidth();
252 contentsSize.setWidth( maxWidth * mHtmlUnitsToLayoutUnits );
254 mWebPage->setViewportSize( contentsSize );
255 mSize.setWidth( contentsSize.width() / mHtmlUnitsToLayoutUnits );
256 mSize.setHeight( contentsSize.height() / mHtmlUnitsToLayoutUnits );
257 if ( contentsSize.isValid() )
265 void QgsLayoutItemHtml::renderCachedImage()
271 mRenderedPage = QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
272 if ( mRenderedPage.isNull() )
276 mRenderedPage.fill( Qt::transparent );
278 painter.begin( &mRenderedPage );
279 mWebPage->mainFrame()->render( &painter );
283 QString QgsLayoutItemHtml::fetchHtml(
const QUrl &url )
292 loop.exec( QEventLoop::ExcludeUserInputEvents );
295 mActualFetchedUrl = mFetcher->
reply()->url().toString();
313 painter->translate( 0.0, -renderExtent.top() * mHtmlUnitsToLayoutUnits );
314 mWebPage->mainFrame()->render( painter, QRegion( renderExtent.left(), renderExtent.top() * mHtmlUnitsToLayoutUnits, renderExtent.width() * mHtmlUnitsToLayoutUnits, renderExtent.height() * mHtmlUnitsToLayoutUnits ) );
317 double QgsLayoutItemHtml::htmlUnitsToLayoutUnits()
329 if ( c1.second < c2.second )
331 else if ( c1.second > c2.second )
333 else if ( c1.first > c2.first )
341 if ( !mWebPage || mRenderedPage.isNull() || !mUseSmartBreaks )
347 int idealPos = yPos * htmlUnitsToLayoutUnits();
350 if ( idealPos >= mRenderedPage.height() )
355 int maxSearchDistance = mMaxBreakDistance * htmlUnitsToLayoutUnits();
361 bool currentPixelTransparent =
false;
362 bool previousPixelTransparent =
false;
364 QList< QPair<int, int> > candidates;
365 int minRow = std::max( idealPos - maxSearchDistance, 0 );
366 for (
int candidateRow = idealPos; candidateRow >= minRow; --candidateRow )
369 currentColor = qRgba( 0, 0, 0, 0 );
371 for (
int col = 0; col < mRenderedPage.width(); ++col )
377 pixelColor = mRenderedPage.pixel( col, candidateRow );
378 currentPixelTransparent = qAlpha( pixelColor ) == 0;
379 if ( pixelColor != currentColor && !( currentPixelTransparent && previousPixelTransparent ) )
382 currentColor = pixelColor;
385 previousPixelTransparent = currentPixelTransparent;
387 candidates.append( qMakePair( candidateRow, changes ) );
391 std::sort( candidates.begin(), candidates.end(),
candidateSort );
398 int maxCandidateRow = candidates[0].first;
399 int minCandidateRow = maxCandidateRow + 1;
400 int minCandidateChanges = candidates[0].second;
402 QList< QPair<int, int> >::iterator it;
403 for ( it = candidates.begin(); it != candidates.end(); ++it )
405 if ( ( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
410 return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToLayoutUnits();
412 minCandidateRow = ( *it ).first;
417 return candidates[0].first / htmlUnitsToLayoutUnits();
436 mUserStylesheet = stylesheet;
445 if ( mEnableUserStylesheet != stylesheetEnabled )
447 mEnableUserStylesheet = stylesheetEnabled;
455 return tr(
"<HTML frame>" );
460 htmlElem.setAttribute( QStringLiteral(
"contentMode" ), QString::number(
static_cast< int >( mContentMode ) ) );
461 htmlElem.setAttribute( QStringLiteral(
"url" ), mUrl.toString() );
462 htmlElem.setAttribute( QStringLiteral(
"html" ), mHtml );
463 htmlElem.setAttribute( QStringLiteral(
"evaluateExpressions" ), mEvaluateExpressions ?
"true" :
"false" );
464 htmlElem.setAttribute( QStringLiteral(
"useSmartBreaks" ), mUseSmartBreaks ?
"true" :
"false" );
465 htmlElem.setAttribute( QStringLiteral(
"maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
466 htmlElem.setAttribute( QStringLiteral(
"stylesheet" ), mUserStylesheet );
467 htmlElem.setAttribute( QStringLiteral(
"stylesheetEnabled" ), mEnableUserStylesheet ?
"true" :
"false" );
475 if ( !contentModeOK )
479 mEvaluateExpressions = itemElem.attribute( QStringLiteral(
"evaluateExpressions" ), QStringLiteral(
"true" ) ) == QLatin1String(
"true" );
480 mUseSmartBreaks = itemElem.attribute( QStringLiteral(
"useSmartBreaks" ), QStringLiteral(
"true" ) ) == QLatin1String(
"true" );
481 mMaxBreakDistance = itemElem.attribute( QStringLiteral(
"maxBreakDistance" ), QStringLiteral(
"10" ) ).toDouble();
482 mHtml = itemElem.attribute( QStringLiteral(
"html" ) );
483 mUserStylesheet = itemElem.attribute( QStringLiteral(
"stylesheet" ) );
484 mEnableUserStylesheet = itemElem.attribute( QStringLiteral(
"stylesheetEnabled" ), QStringLiteral(
"false" ) ) == QLatin1String(
"true" );
487 QString urlString = itemElem.attribute( QStringLiteral(
"url" ) );
488 if ( !urlString.isEmpty() )
501 mExpressionFeature = feature;
502 mExpressionLayer = layer;
525 exporter.setIncludeRelated(
true );
526 mAtlasFeatureJSON = exporter.exportFeature( feature );
530 mAtlasFeatureJSON.clear();
534 void QgsLayoutItemHtml::refreshExpressionContext()
541 vl =
mLayout->reportContext().layer();
542 feature =
mLayout->reportContext().feature();
545 setExpressionContext( feature, vl );
563 void JavascriptExecutorLoop::done()
569 void JavascriptExecutorLoop::execIfNotDone()
572 exec( QEventLoop::ExcludeUserInputEvents );
576 for (
int i = 0; i < 100; i++ )
577 qApp->processEvents();
580 void JavascriptExecutorLoop::reportError(
const QString &error )