mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-04 00:06:15 -04:00
395 lines
14 KiB
C++
395 lines
14 KiB
C++
/***************************************************************************
|
|
qgslayoututils.cpp
|
|
------------------
|
|
begin : July 2017
|
|
copyright : (C) 2017 by Nyall Dawson
|
|
email : nyall dot dawson at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgslayoututils.h"
|
|
#include "qgslayout.h"
|
|
#include "qgsrendercontext.h"
|
|
#include "qgslayoutitemmap.h"
|
|
#include <QPainter>
|
|
#include <cmath>
|
|
|
|
#ifndef M_DEG2RAD
|
|
#define M_DEG2RAD 0.0174532925
|
|
#endif
|
|
|
|
void QgsLayoutUtils::rotate( double angle, double &x, double &y )
|
|
{
|
|
double rotToRad = angle * M_PI / 180.0;
|
|
double xRot, yRot;
|
|
xRot = x * std::cos( rotToRad ) - y * std::sin( rotToRad );
|
|
yRot = x * std::sin( rotToRad ) + y * std::cos( rotToRad );
|
|
x = xRot;
|
|
y = yRot;
|
|
}
|
|
|
|
double QgsLayoutUtils::normalizedAngle( const double angle, const bool allowNegative )
|
|
{
|
|
double clippedAngle = angle;
|
|
if ( clippedAngle >= 360.0 || clippedAngle <= -360.0 )
|
|
{
|
|
clippedAngle = std::fmod( clippedAngle, 360.0 );
|
|
}
|
|
if ( !allowNegative && clippedAngle < 0.0 )
|
|
{
|
|
clippedAngle += 360.0;
|
|
}
|
|
return clippedAngle;
|
|
}
|
|
|
|
double QgsLayoutUtils::snappedAngle( double angle )
|
|
{
|
|
//normalize angle to 0-360 degrees
|
|
double clippedAngle = normalizedAngle( angle );
|
|
|
|
//snap angle to 45 degree
|
|
if ( clippedAngle >= 22.5 && clippedAngle < 67.5 )
|
|
{
|
|
return 45.0;
|
|
}
|
|
else if ( clippedAngle >= 67.5 && clippedAngle < 112.5 )
|
|
{
|
|
return 90.0;
|
|
}
|
|
else if ( clippedAngle >= 112.5 && clippedAngle < 157.5 )
|
|
{
|
|
return 135.0;
|
|
}
|
|
else if ( clippedAngle >= 157.5 && clippedAngle < 202.5 )
|
|
{
|
|
return 180.0;
|
|
}
|
|
else if ( clippedAngle >= 202.5 && clippedAngle < 247.5 )
|
|
{
|
|
return 225.0;
|
|
}
|
|
else if ( clippedAngle >= 247.5 && clippedAngle < 292.5 )
|
|
{
|
|
return 270.0;
|
|
}
|
|
else if ( clippedAngle >= 292.5 && clippedAngle < 337.5 )
|
|
{
|
|
return 315.0;
|
|
}
|
|
else
|
|
{
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
QgsRenderContext QgsLayoutUtils::createRenderContextForMap( QgsLayoutItemMap *map, QPainter *painter, double dpi )
|
|
{
|
|
if ( !map )
|
|
{
|
|
QgsRenderContext context;
|
|
context.setPainter( painter );
|
|
if ( dpi < 0 && painter && painter->device() )
|
|
{
|
|
context.setScaleFactor( painter->device()->logicalDpiX() / 25.4 );
|
|
}
|
|
else if ( dpi > 0 )
|
|
{
|
|
context.setScaleFactor( dpi / 25.4 );
|
|
}
|
|
else
|
|
{
|
|
context.setScaleFactor( 3.465 ); //assume 88 dpi as standard value
|
|
}
|
|
return context;
|
|
}
|
|
else
|
|
{
|
|
// default to 88 dpi if no painter specified
|
|
if ( dpi < 0 )
|
|
{
|
|
dpi = ( painter && painter->device() ) ? painter->device()->logicalDpiX() : 88;
|
|
}
|
|
double dotsPerMM = dpi / 25.4;
|
|
|
|
// get map settings from reference map
|
|
QgsRectangle extent = map->extent();
|
|
QSizeF mapSizeLayoutUnits = map->rect().size();
|
|
QSizeF mapSizeMM = map->layout()->convertFromLayoutUnits( mapSizeLayoutUnits, QgsUnitTypes::LayoutMillimeters ).toQSizeF();
|
|
QgsMapSettings ms = map->mapSettings( extent, mapSizeMM * dotsPerMM, dpi );
|
|
QgsRenderContext context = QgsRenderContext::fromMapSettings( ms );
|
|
if ( painter )
|
|
context.setPainter( painter );
|
|
|
|
context.setFlags( map->layout()->renderContext().renderContextFlags() );
|
|
return context;
|
|
}
|
|
}
|
|
|
|
QgsRenderContext QgsLayoutUtils::createRenderContextForLayout( QgsLayout *layout, QPainter *painter, double dpi )
|
|
{
|
|
QgsLayoutItemMap *referenceMap = layout ? layout->referenceMap() : nullptr;
|
|
QgsRenderContext context = createRenderContextForMap( referenceMap, painter, dpi );
|
|
if ( layout )
|
|
context.setFlags( layout->renderContext().renderContextFlags() );
|
|
return context;
|
|
}
|
|
|
|
void QgsLayoutUtils::relativeResizeRect( QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter )
|
|
{
|
|
//linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
|
|
double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
|
|
double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
|
|
double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
|
|
double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
|
|
|
|
rectToResize.setRect( left, top, right - left, bottom - top );
|
|
}
|
|
|
|
double QgsLayoutUtils::relativePosition( const double position, const double beforeMin, const double beforeMax, const double afterMin, const double afterMax )
|
|
{
|
|
//calculate parameters for linear scale between before and after ranges
|
|
double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
|
|
double c = afterMin - ( beforeMin * m );
|
|
|
|
//return linearly scaled position
|
|
return m * position + c;
|
|
}
|
|
QFont QgsLayoutUtils::scaledFontPixelSize( const QFont &font )
|
|
{
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont scaledFont = font;
|
|
double pixelSize = pointsToMM( scaledFont.pointSizeF() ) * FONT_WORKAROUND_SCALE + 0.5;
|
|
scaledFont.setPixelSize( pixelSize );
|
|
return scaledFont;
|
|
}
|
|
|
|
double QgsLayoutUtils::fontAscentMM( const QFont &font )
|
|
{
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont metricsFont = scaledFontPixelSize( font );
|
|
QFontMetricsF fontMetrics( metricsFont );
|
|
return ( fontMetrics.ascent() / FONT_WORKAROUND_SCALE );
|
|
}
|
|
|
|
double QgsLayoutUtils::fontDescentMM( const QFont &font )
|
|
{
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont metricsFont = scaledFontPixelSize( font );
|
|
QFontMetricsF fontMetrics( metricsFont );
|
|
return ( fontMetrics.descent() / FONT_WORKAROUND_SCALE );
|
|
|
|
}
|
|
|
|
double QgsLayoutUtils::fontHeightMM( const QFont &font )
|
|
{
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont metricsFont = scaledFontPixelSize( font );
|
|
QFontMetricsF fontMetrics( metricsFont );
|
|
return ( fontMetrics.height() / FONT_WORKAROUND_SCALE );
|
|
|
|
}
|
|
|
|
double QgsLayoutUtils::fontHeightCharacterMM( const QFont &font, QChar character )
|
|
{
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont metricsFont = scaledFontPixelSize( font );
|
|
QFontMetricsF fontMetrics( metricsFont );
|
|
return ( fontMetrics.boundingRect( character ).height() / FONT_WORKAROUND_SCALE );
|
|
}
|
|
|
|
double QgsLayoutUtils::textWidthMM( const QFont &font, const QString &text )
|
|
{
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont metricsFont = scaledFontPixelSize( font );
|
|
QFontMetricsF fontMetrics( metricsFont );
|
|
return ( fontMetrics.width( text ) / FONT_WORKAROUND_SCALE );
|
|
}
|
|
|
|
double QgsLayoutUtils::textHeightMM( const QFont &font, const QString &text, double multiLineHeight )
|
|
{
|
|
QStringList multiLineSplit = text.split( '\n' );
|
|
int lines = multiLineSplit.size();
|
|
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont metricsFont = scaledFontPixelSize( font );
|
|
QFontMetricsF fontMetrics( metricsFont );
|
|
|
|
double fontHeight = fontMetrics.ascent() + fontMetrics.descent(); // ignore +1 for baseline
|
|
double textHeight = fontMetrics.ascent() + static_cast< double >( ( lines - 1 ) * fontHeight * multiLineHeight );
|
|
|
|
return textHeight / FONT_WORKAROUND_SCALE;
|
|
}
|
|
|
|
void QgsLayoutUtils::drawText( QPainter *painter, QPointF position, const QString &text, const QFont &font, const QColor &color )
|
|
{
|
|
if ( !painter )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont textFont = scaledFontPixelSize( font );
|
|
|
|
painter->save();
|
|
painter->setFont( textFont );
|
|
if ( color.isValid() )
|
|
{
|
|
painter->setPen( color );
|
|
}
|
|
double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
|
|
painter->scale( scaleFactor, scaleFactor );
|
|
painter->drawText( position * FONT_WORKAROUND_SCALE, text );
|
|
painter->restore();
|
|
}
|
|
|
|
void QgsLayoutUtils::drawText( QPainter *painter, const QRectF &rect, const QString &text, const QFont &font, const QColor &color, const Qt::AlignmentFlag halignment, const Qt::AlignmentFlag valignment, const int flags )
|
|
{
|
|
if ( !painter )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//upscale using FONT_WORKAROUND_SCALE
|
|
//ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
|
|
QFont textFont = scaledFontPixelSize( font );
|
|
|
|
QRectF scaledRect( rect.x() * FONT_WORKAROUND_SCALE, rect.y() * FONT_WORKAROUND_SCALE,
|
|
rect.width() * FONT_WORKAROUND_SCALE, rect.height() * FONT_WORKAROUND_SCALE );
|
|
|
|
painter->save();
|
|
painter->setFont( textFont );
|
|
if ( color.isValid() )
|
|
{
|
|
painter->setPen( color );
|
|
}
|
|
double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
|
|
painter->scale( scaleFactor, scaleFactor );
|
|
painter->drawText( scaledRect, halignment | valignment | flags, text );
|
|
painter->restore();
|
|
}
|
|
|
|
QRectF QgsLayoutUtils::largestRotatedRectWithinBounds( const QRectF &originalRect, const QRectF &boundsRect, const double rotation )
|
|
{
|
|
double originalWidth = originalRect.width();
|
|
double originalHeight = originalRect.height();
|
|
double boundsWidth = boundsRect.width();
|
|
double boundsHeight = boundsRect.height();
|
|
double ratioBoundsRect = boundsWidth / boundsHeight;
|
|
|
|
double clippedRotation = normalizedAngle( rotation );
|
|
|
|
//shortcut for some rotation values
|
|
if ( qgsDoubleNear( clippedRotation, 0.0 ) || qgsDoubleNear( clippedRotation, 90.0 ) || qgsDoubleNear( clippedRotation, 180.0 ) || qgsDoubleNear( clippedRotation, 270.0 ) )
|
|
{
|
|
double rectScale;
|
|
if ( qgsDoubleNear( clippedRotation, 0.0 ) || qgsDoubleNear( clippedRotation, 180.0 ) )
|
|
{
|
|
rectScale = ( ( originalWidth / originalHeight ) > ratioBoundsRect ) ? boundsWidth / originalWidth : boundsHeight / originalHeight;
|
|
}
|
|
else
|
|
{
|
|
rectScale = ( ( originalHeight / originalWidth ) > ratioBoundsRect ) ? boundsWidth / originalHeight : boundsHeight / originalWidth;
|
|
}
|
|
double rectScaledWidth = rectScale * originalWidth;
|
|
double rectScaledHeight = rectScale * originalHeight;
|
|
|
|
if ( qgsDoubleNear( clippedRotation, 0.0 ) || qgsDoubleNear( clippedRotation, 180.0 ) )
|
|
{
|
|
return QRectF( ( boundsWidth - rectScaledWidth ) / 2.0, ( boundsHeight - rectScaledHeight ) / 2.0, rectScaledWidth, rectScaledHeight );
|
|
}
|
|
else
|
|
{
|
|
return QRectF( ( boundsWidth - rectScaledHeight ) / 2.0, ( boundsHeight - rectScaledWidth ) / 2.0, rectScaledWidth, rectScaledHeight );
|
|
}
|
|
}
|
|
|
|
//convert angle to radians and flip
|
|
double angleRad = -clippedRotation * M_DEG2RAD;
|
|
double cosAngle = std::cos( angleRad );
|
|
double sinAngle = std::sin( angleRad );
|
|
|
|
//calculate size of bounds of rotated rectangle
|
|
double widthBoundsRotatedRect = originalWidth * std::fabs( cosAngle ) + originalHeight * std::fabs( sinAngle );
|
|
double heightBoundsRotatedRect = originalHeight * std::fabs( cosAngle ) + originalWidth * std::fabs( sinAngle );
|
|
|
|
//compare ratio of rotated rect with bounds rect and calculate scaling of rotated
|
|
//rect to fit within bounds
|
|
double ratioBoundsRotatedRect = widthBoundsRotatedRect / heightBoundsRotatedRect;
|
|
double rectScale = ratioBoundsRotatedRect > ratioBoundsRect ? boundsWidth / widthBoundsRotatedRect : boundsHeight / heightBoundsRotatedRect;
|
|
double rectScaledWidth = rectScale * originalWidth;
|
|
double rectScaledHeight = rectScale * originalHeight;
|
|
|
|
//now calculate offset so that rotated rectangle is centered within bounds
|
|
//first calculate min x and y coordinates
|
|
double currentCornerX = 0;
|
|
double minX = 0;
|
|
currentCornerX += rectScaledWidth * cosAngle;
|
|
minX = minX < currentCornerX ? minX : currentCornerX;
|
|
currentCornerX += rectScaledHeight * sinAngle;
|
|
minX = minX < currentCornerX ? minX : currentCornerX;
|
|
currentCornerX -= rectScaledWidth * cosAngle;
|
|
minX = minX < currentCornerX ? minX : currentCornerX;
|
|
|
|
double currentCornerY = 0;
|
|
double minY = 0;
|
|
currentCornerY -= rectScaledWidth * sinAngle;
|
|
minY = minY < currentCornerY ? minY : currentCornerY;
|
|
currentCornerY += rectScaledHeight * cosAngle;
|
|
minY = minY < currentCornerY ? minY : currentCornerY;
|
|
currentCornerY += rectScaledWidth * sinAngle;
|
|
minY = minY < currentCornerY ? minY : currentCornerY;
|
|
|
|
//now calculate offset position of rotated rectangle
|
|
double offsetX = ratioBoundsRotatedRect > ratioBoundsRect ? 0 : ( boundsWidth - rectScale * widthBoundsRotatedRect ) / 2.0;
|
|
offsetX += std::fabs( minX );
|
|
double offsetY = ratioBoundsRotatedRect > ratioBoundsRect ? ( boundsHeight - rectScale * heightBoundsRotatedRect ) / 2.0 : 0;
|
|
offsetY += std::fabs( minY );
|
|
|
|
return QRectF( offsetX, offsetY, rectScaledWidth, rectScaledHeight );
|
|
}
|
|
|
|
QgsLayoutItemPage::Orientation QgsLayoutUtils::decodePaperOrientation( const QString &string, bool &ok )
|
|
{
|
|
QString s = string.trimmed();
|
|
if ( s.compare( QLatin1String( "Portrait" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
ok = true;
|
|
return QgsLayoutItemPage::Portrait;
|
|
}
|
|
else if ( s.compare( QLatin1String( "Landscape" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
ok = true;
|
|
return QgsLayoutItemPage::Landscape;
|
|
}
|
|
ok = false;
|
|
return QgsLayoutItemPage::Landscape; // default to landscape
|
|
}
|
|
|
|
double QgsLayoutUtils::pointsToMM( const double pointSize )
|
|
{
|
|
//conversion to mm based on 1 point = 1/72 inch
|
|
return ( pointSize * 0.3527 );
|
|
}
|
|
|
|
double QgsLayoutUtils::mmToPoints( const double mmSize )
|
|
{
|
|
//conversion to points based on 1 point = 1/72 inch
|
|
return ( mmSize / 0.3527 );
|
|
}
|