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(
"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 ) );
318 double QgsLayoutItemHtml::htmlUnitsToLayoutUnits()
330 if ( c1.second < c2.second )
332 else if ( c1.second > c2.second )
334 else if ( c1.first > c2.first )
342 if ( !mWebPage || mRenderedPage.isNull() || !mUseSmartBreaks )
348 int idealPos = yPos * htmlUnitsToLayoutUnits();
351 if ( idealPos >= mRenderedPage.height() )
356 int maxSearchDistance = mMaxBreakDistance * htmlUnitsToLayoutUnits();
362 bool currentPixelTransparent =
false;
363 bool previousPixelTransparent =
false;
365 QList< QPair<int, int> > candidates;
366 int minRow = std::max( idealPos - maxSearchDistance, 0 );
367 for (
int candidateRow = idealPos; candidateRow >= minRow; --candidateRow )
370 currentColor = qRgba( 0, 0, 0, 0 );
372 for (
int col = 0; col < mRenderedPage.width(); ++col )
378 pixelColor = mRenderedPage.pixel( col, candidateRow );
379 currentPixelTransparent = qAlpha( pixelColor ) == 0;
380 if ( pixelColor != currentColor && !( currentPixelTransparent && previousPixelTransparent ) )
383 currentColor = pixelColor;
386 previousPixelTransparent = currentPixelTransparent;
388 candidates.append( qMakePair( candidateRow, changes ) );
392 std::sort( candidates.begin(), candidates.end(),
candidateSort );
399 int maxCandidateRow = candidates[0].first;
400 int minCandidateRow = maxCandidateRow + 1;
401 int minCandidateChanges = candidates[0].second;
403 QList< QPair<int, int> >::iterator it;
404 for ( it = candidates.begin(); it != candidates.end(); ++it )
406 if ( ( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
411 return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToLayoutUnits();
413 minCandidateRow = ( *it ).first;
418 return candidates[0].first / htmlUnitsToLayoutUnits();
437 mUserStylesheet = stylesheet;
446 if ( mEnableUserStylesheet != stylesheetEnabled )
448 mEnableUserStylesheet = stylesheetEnabled;
456 return tr(
"<HTML frame>" );
461 htmlElem.setAttribute( QStringLiteral(
"contentMode" ), QString::number(
static_cast< int >( mContentMode ) ) );
462 htmlElem.setAttribute( QStringLiteral(
"url" ), mUrl.toString() );
463 htmlElem.setAttribute( QStringLiteral(
"html" ), mHtml );
464 htmlElem.setAttribute( QStringLiteral(
"evaluateExpressions" ), mEvaluateExpressions ?
"true" :
"false" );
465 htmlElem.setAttribute( QStringLiteral(
"useSmartBreaks" ), mUseSmartBreaks ?
"true" :
"false" );
466 htmlElem.setAttribute( QStringLiteral(
"maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
467 htmlElem.setAttribute( QStringLiteral(
"stylesheet" ), mUserStylesheet );
468 htmlElem.setAttribute( QStringLiteral(
"stylesheetEnabled" ), mEnableUserStylesheet ?
"true" :
"false" );
476 if ( !contentModeOK )
480 mEvaluateExpressions = itemElem.attribute( QStringLiteral(
"evaluateExpressions" ), QStringLiteral(
"true" ) ) == QLatin1String(
"true" );
481 mUseSmartBreaks = itemElem.attribute( QStringLiteral(
"useSmartBreaks" ), QStringLiteral(
"true" ) ) == QLatin1String(
"true" );
482 mMaxBreakDistance = itemElem.attribute( QStringLiteral(
"maxBreakDistance" ), QStringLiteral(
"10" ) ).toDouble();
483 mHtml = itemElem.attribute( QStringLiteral(
"html" ) );
484 mUserStylesheet = itemElem.attribute( QStringLiteral(
"stylesheet" ) );
485 mEnableUserStylesheet = itemElem.attribute( QStringLiteral(
"stylesheetEnabled" ), QStringLiteral(
"false" ) ) == QLatin1String(
"true" );
488 QString urlString = itemElem.attribute( QStringLiteral(
"url" ) );
489 if ( !urlString.isEmpty() )
502 mExpressionFeature = feature;
503 mExpressionLayer = layer;
526 exporter.setIncludeRelated(
true );
527 mAtlasFeatureJSON = exporter.exportFeature( feature );
531 mAtlasFeatureJSON.clear();
535 void QgsLayoutItemHtml::refreshExpressionContext()
542 vl =
mLayout->reportContext().layer();
543 feature =
mLayout->reportContext().feature();
546 setExpressionContext( feature, vl );
564 void JavascriptExecutorLoop::done()
570 void JavascriptExecutorLoop::execIfNotDone()
573 exec( QEventLoop::ExcludeUserInputEvents );
577 for (
int i = 0; i < 100; i++ )
578 qApp->processEvents();
581 void JavascriptExecutorLoop::reportError(
const QString &error )