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 )