QGIS/src/core/annotations/qgshtmlannotation.cpp
Nyall Dawson 38ef62e1cc Fix incorrect annotation scaling when exporting layouts
Previously, annotation size and position always used pixel units. This
did not work well when exporting layouts, resulting in tiny annotations
(it also caused issues when moving projects between hidpi/non hidpi
displays).

Instead, use millimeters for annotation size and position so that the
appearance is consistent across displays and works correctly in layout
exports.

Add lots of unit tests covering this.

Fixes #18373
2019-04-30 15:21:39 +10:00

169 lines
5.2 KiB
C++

/***************************************************************************
qgshtmlannotation.h
------------------------
begin : February 26, 2010
copyright : (C) 2010 by Marco Hugentobler
email : marco dot hugentobler at hugis dot net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgshtmlannotation.h"
#include "qgsfeature.h"
#include "qgsfeatureiterator.h"
#include "qgslogger.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgsexpression.h"
#include "qgsnetworkaccessmanager.h"
#include "qgswebpage.h"
#include "qgswebframe.h"
#include "qgsexpressioncontextutils.h"
#include <QDomElement>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QGraphicsProxyWidget>
#include <QPainter>
#include <QSettings>
#include <QWidget>
QgsHtmlAnnotation::QgsHtmlAnnotation( QObject *parent )
: QgsAnnotation( parent )
{
mWebPage = new QgsWebPage();
mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
mWebPage->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
connect( mWebPage->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared, this, &QgsHtmlAnnotation::javascript );
}
QgsHtmlAnnotation *QgsHtmlAnnotation::clone() const
{
std::unique_ptr< QgsHtmlAnnotation > c( new QgsHtmlAnnotation() );
copyCommonProperties( c.get() );
c->setSourceFile( mHtmlFile );
return c.release();
}
void QgsHtmlAnnotation::setSourceFile( const QString &htmlFile )
{
QFile file( htmlFile );
mHtmlFile = htmlFile;
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
mHtmlSource.clear();
}
else
{
QTextStream in( &file );
in.setCodec( "UTF-8" );
mHtmlSource = in.readAll();
}
file.close();
setAssociatedFeature( associatedFeature() );
emit appearanceChanged();
}
void QgsHtmlAnnotation::renderAnnotation( QgsRenderContext &context, QSizeF size ) const
{
if ( !context.painter() )
{
return;
}
// scale painter back to 96 dpi, so layout prints match screen rendering
context.painter()->save();
const double scaleFactor = context.painter()->device()->logicalDpiX() / 96.0;
context.painter()->scale( scaleFactor, scaleFactor );
size /= scaleFactor;
mWebPage->setViewportSize( size.toSize() );
mWebPage->mainFrame()->render( context.painter() );
context.painter()->restore();
}
QSizeF QgsHtmlAnnotation::minimumFrameSize() const
{
if ( mWebPage )
{
QSizeF widgetMinSize = QSizeF( 0, 0 ); // mWebPage->minimumSize();
return QSizeF( contentsMargin().left() + contentsMargin().right() + widgetMinSize.width(),
contentsMargin().top() + contentsMargin().bottom() + widgetMinSize.height() );
}
else
{
return QSizeF( 0, 0 );
}
}
void QgsHtmlAnnotation::writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
{
QDomElement formAnnotationElem = doc.createElement( QStringLiteral( "HtmlAnnotationItem" ) );
formAnnotationElem.setAttribute( QStringLiteral( "htmlfile" ), sourceFile() );
_writeXml( formAnnotationElem, doc, context );
elem.appendChild( formAnnotationElem );
}
void QgsHtmlAnnotation::readXml( const QDomElement &itemElem, const QgsReadWriteContext &context )
{
mHtmlFile = itemElem.attribute( QStringLiteral( "htmlfile" ), QString() );
QDomElement annotationElem = itemElem.firstChildElement( QStringLiteral( "AnnotationItem" ) );
if ( !annotationElem.isNull() )
{
_readXml( annotationElem, context );
}
// upgrade old layer
if ( !mapLayer() && itemElem.hasAttribute( QStringLiteral( "vectorLayer" ) ) )
{
setMapLayer( QgsProject::instance()->mapLayer( itemElem.attribute( QStringLiteral( "vectorLayer" ) ) ) );
}
if ( mWebPage )
{
setSourceFile( mHtmlFile );
}
}
void QgsHtmlAnnotation::setAssociatedFeature( const QgsFeature &feature )
{
QgsAnnotation::setAssociatedFeature( feature );
QString newText;
QgsVectorLayer *vectorLayer = qobject_cast< QgsVectorLayer * >( mapLayer() );
if ( feature.isValid() && vectorLayer )
{
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vectorLayer ) );
context.setFeature( feature );
newText = QgsExpression::replaceExpressionText( mHtmlSource, &context );
}
else
{
newText = mHtmlSource;
}
mWebPage->mainFrame()->setHtml( newText );
emit appearanceChanged();
}
void QgsHtmlAnnotation::javascript()
{
QWebFrame *frame = mWebPage->mainFrame();
frame->addToJavaScriptWindowObject( QStringLiteral( "layer" ), mapLayer() );
}