QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposerutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerutils.cpp
3  -------------------
4  begin : July 2014
5  copyright : (C) 2014 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscomposerutils.h"
19 #include "qgscomposition.h"
20 #include "qgsdatadefined.h"
21 #include <QPainter>
22 
23 #define FONT_WORKAROUND_SCALE 10 //scale factor for upscaling fontsize and downscaling painter
24 
25 #ifndef M_DEG2RAD
26 #define M_DEG2RAD 0.0174532925
27 #endif
28 
29 void QgsComposerUtils::drawArrowHead( QPainter *p, const double x, const double y, const double angle, const double arrowHeadWidth )
30 {
31  if ( !p )
32  {
33  return;
34  }
35 
36  double angleRad = angle / 180.0 * M_PI;
37  QPointF middlePoint( x, y );
38  //rotate both arrow points
39  QPointF p1 = QPointF( -arrowHeadWidth / 2.0, arrowHeadWidth );
40  QPointF p2 = QPointF( arrowHeadWidth / 2.0, arrowHeadWidth );
41 
42  QPointF p1Rotated, p2Rotated;
43  p1Rotated.setX( p1.x() * cos( angleRad ) + p1.y() * -sin( angleRad ) );
44  p1Rotated.setY( p1.x() * sin( angleRad ) + p1.y() * cos( angleRad ) );
45  p2Rotated.setX( p2.x() * cos( angleRad ) + p2.y() * -sin( angleRad ) );
46  p2Rotated.setY( p2.x() * sin( angleRad ) + p2.y() * cos( angleRad ) );
47 
48  QPolygonF arrowHeadPoly;
49  arrowHeadPoly << middlePoint;
50  arrowHeadPoly << QPointF( middlePoint.x() + p1Rotated.x(), middlePoint.y() + p1Rotated.y() );
51  arrowHeadPoly << QPointF( middlePoint.x() + p2Rotated.x(), middlePoint.y() + p2Rotated.y() );
52 
53  p->save();
54 
55  QPen arrowPen = p->pen();
56  arrowPen.setJoinStyle( Qt::RoundJoin );
57  QBrush arrowBrush = p->brush();
58  arrowBrush.setStyle( Qt::SolidPattern );
59  p->setPen( arrowPen );
60  p->setBrush( arrowBrush );
61  arrowBrush.setStyle( Qt::SolidPattern );
62  p->drawPolygon( arrowHeadPoly );
63 
64  p->restore();
65 }
66 
67 double QgsComposerUtils::angle( const QPointF &p1, const QPointF &p2 )
68 {
69  double xDiff = p2.x() - p1.x();
70  double yDiff = p2.y() - p1.y();
71  double length = sqrt( xDiff * xDiff + yDiff * yDiff );
72  if ( length <= 0 )
73  {
74  return 0;
75  }
76 
77  double angle = acos(( -yDiff * length ) / ( length * length ) ) * 180 / M_PI;
78  if ( xDiff < 0 )
79  {
80  return ( 360 - angle );
81  }
82  return angle;
83 }
84 
85 void QgsComposerUtils::rotate( const double angle, double &x, double &y )
86 {
87  double rotToRad = angle * M_PI / 180.0;
88  double xRot, yRot;
89  xRot = x * cos( rotToRad ) - y * sin( rotToRad );
90  yRot = x * sin( rotToRad ) + y * cos( rotToRad );
91  x = xRot;
92  y = yRot;
93 }
94 
96 {
97  double clippedAngle = angle;
98  if ( clippedAngle >= 360.0 || clippedAngle <= -360.0 )
99  {
100  clippedAngle = fmod( clippedAngle, 360.0 );
101  }
102  if ( clippedAngle < 0.0 )
103  {
104  clippedAngle += 360.0;
105  }
106  return clippedAngle;
107 }
108 
109 double QgsComposerUtils::snappedAngle( const double angle )
110 {
111  //normalize angle to 0-360 degrees
112  double clippedAngle = normalizedAngle( angle );
113 
114  //snap angle to 45 degree
115  if ( clippedAngle >= 22.5 && clippedAngle < 67.5 )
116  {
117  return 45.0;
118  }
119  else if ( clippedAngle >= 67.5 && clippedAngle < 112.5 )
120  {
121  return 90.0;
122  }
123  else if ( clippedAngle >= 112.5 && clippedAngle < 157.5 )
124  {
125  return 135.0;
126  }
127  else if ( clippedAngle >= 157.5 && clippedAngle < 202.5 )
128  {
129  return 180.0;
130  }
131  else if ( clippedAngle >= 202.5 && clippedAngle < 247.5 )
132  {
133  return 225.0;
134  }
135  else if ( clippedAngle >= 247.5 && clippedAngle < 292.5 )
136  {
137  return 270.0;
138  }
139  else if ( clippedAngle >= 292.5 && clippedAngle < 337.5 )
140  {
141  return 315.0;
142  }
143  else
144  {
145  return 0.0;
146  }
147 }
148 
149 QRectF QgsComposerUtils::largestRotatedRectWithinBounds( const QRectF originalRect, const QRectF boundsRect, const double rotation )
150 {
151  double originalWidth = originalRect.width();
152  double originalHeight = originalRect.height();
153  double boundsWidth = boundsRect.width();
154  double boundsHeight = boundsRect.height();
155  double ratioBoundsRect = boundsWidth / boundsHeight;
156 
157  double clippedRotation = normalizedAngle( rotation );
158 
159  //shortcut for some rotation values
160  if ( clippedRotation == 0 || clippedRotation == 90 || clippedRotation == 180 || clippedRotation == 270 )
161  {
162  double rectScale;
163  if ( clippedRotation == 0 || clippedRotation == 180 )
164  {
165  rectScale = (( originalWidth / originalHeight ) > ratioBoundsRect ) ? boundsWidth / originalWidth : boundsHeight / originalHeight;
166  }
167  else
168  {
169  rectScale = (( originalHeight / originalWidth ) > ratioBoundsRect ) ? boundsWidth / originalHeight : boundsHeight / originalWidth;
170  }
171  double rectScaledWidth = rectScale * originalWidth;
172  double rectScaledHeight = rectScale * originalHeight;
173 
174  if ( clippedRotation == 0 || clippedRotation == 180 )
175  {
176  return QRectF(( boundsWidth - rectScaledWidth ) / 2.0, ( boundsHeight - rectScaledHeight ) / 2.0, rectScaledWidth, rectScaledHeight );
177  }
178  else
179  {
180  return QRectF(( boundsWidth - rectScaledHeight ) / 2.0, ( boundsHeight - rectScaledWidth ) / 2.0, rectScaledWidth, rectScaledHeight );
181  }
182  }
183 
184  //convert angle to radians and flip
185  double angleRad = -clippedRotation * M_DEG2RAD;
186  double cosAngle = cos( angleRad );
187  double sinAngle = sin( angleRad );
188 
189  //calculate size of bounds of rotated rectangle
190  double widthBoundsRotatedRect = originalWidth * fabs( cosAngle ) + originalHeight * fabs( sinAngle );
191  double heightBoundsRotatedRect = originalHeight * fabs( cosAngle ) + originalWidth * fabs( sinAngle );
192 
193  //compare ratio of rotated rect with bounds rect and calculate scaling of rotated
194  //rect to fit within bounds
195  double ratioBoundsRotatedRect = widthBoundsRotatedRect / heightBoundsRotatedRect;
196  double rectScale = ratioBoundsRotatedRect > ratioBoundsRect ? boundsWidth / widthBoundsRotatedRect : boundsHeight / heightBoundsRotatedRect;
197  double rectScaledWidth = rectScale * originalWidth;
198  double rectScaledHeight = rectScale * originalHeight;
199 
200  //now calculate offset so that rotated rectangle is centered within bounds
201  //first calculate min x and y coordinates
202  double currentCornerX = 0;
203  double minX = 0;
204  currentCornerX += rectScaledWidth * cosAngle;
205  minX = minX < currentCornerX ? minX : currentCornerX;
206  currentCornerX += rectScaledHeight * sinAngle;
207  minX = minX < currentCornerX ? minX : currentCornerX;
208  currentCornerX -= rectScaledWidth * cosAngle;
209  minX = minX < currentCornerX ? minX : currentCornerX;
210 
211  double currentCornerY = 0;
212  double minY = 0;
213  currentCornerY -= rectScaledWidth * sinAngle;
214  minY = minY < currentCornerY ? minY : currentCornerY;
215  currentCornerY += rectScaledHeight * cosAngle;
216  minY = minY < currentCornerY ? minY : currentCornerY;
217  currentCornerY += rectScaledWidth * sinAngle;
218  minY = minY < currentCornerY ? minY : currentCornerY;
219 
220  //now calculate offset position of rotated rectangle
221  double offsetX = ratioBoundsRotatedRect > ratioBoundsRect ? 0 : ( boundsWidth - rectScale * widthBoundsRotatedRect ) / 2.0;
222  offsetX += fabs( minX );
223  double offsetY = ratioBoundsRotatedRect > ratioBoundsRect ? ( boundsHeight - rectScale * heightBoundsRotatedRect ) / 2.0 : 0;
224  offsetY += fabs( minY );
225 
226  return QRectF( offsetX, offsetY, rectScaledWidth, rectScaledHeight );
227 }
228 
229 double QgsComposerUtils::pointsToMM( const double pointSize )
230 {
231  //conversion to mm based on 1 point = 1/72 inch
232  return ( pointSize * 0.3527 );
233 }
234 
235 double QgsComposerUtils::mmToPoints( const double mmSize )
236 {
237  //conversion to points based on 1 point = 1/72 inch
238  return ( mmSize / 0.3527 );
239 }
240 
241 void QgsComposerUtils::relativeResizeRect( QRectF& rectToResize, const QRectF& boundsBefore, const QRectF& boundsAfter )
242 {
243  //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
244  double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
245  double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
246  double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
247  double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
248 
249  rectToResize.setRect( left, top, right - left, bottom - top );
250 }
251 
252 double QgsComposerUtils::relativePosition( const double position, const double beforeMin, const double beforeMax, const double afterMin, const double afterMax )
253 {
254  //calculate parameters for linear scale between before and after ranges
255  double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
256  double c = afterMin - ( beforeMin * m );
257 
258  //return linearly scaled position
259  return m * position + c;
260 }
261 
263 {
264  if ( orientationString.compare( "Portrait", Qt::CaseInsensitive ) == 0 )
265  {
266  ok = true;
268  }
269  if ( orientationString.compare( "Landscape", Qt::CaseInsensitive ) == 0 )
270  {
271  ok = true;
273  }
274  ok = false;
275  return QgsComposition::Landscape; // default to landscape
276 }
277 
278 bool QgsComposerUtils::decodePresetPaperSize( const QString presetString, double &width, double &height )
279 {
280  QList< QPair< QString, QSizeF > > presets;
281  presets << qMakePair( QString( "A5" ), QSizeF( 148, 210 ) );
282  presets << qMakePair( QString( "A4" ), QSizeF( 210, 297 ) );
283  presets << qMakePair( QString( "A3" ), QSizeF( 297, 420 ) );
284  presets << qMakePair( QString( "A2" ), QSizeF( 420, 594 ) );
285  presets << qMakePair( QString( "A1" ), QSizeF( 594, 841 ) );
286  presets << qMakePair( QString( "A0" ), QSizeF( 841, 1189 ) );
287  presets << qMakePair( QString( "B5" ), QSizeF( 176, 250 ) );
288  presets << qMakePair( QString( "B4" ), QSizeF( 250, 353 ) );
289  presets << qMakePair( QString( "B3" ), QSizeF( 353, 500 ) );
290  presets << qMakePair( QString( "B2" ), QSizeF( 500, 707 ) );
291  presets << qMakePair( QString( "B1" ), QSizeF( 707, 1000 ) );
292  presets << qMakePair( QString( "B0" ), QSizeF( 1000, 1414 ) );
293  // North american formats
294  presets << qMakePair( QString( "Legal" ), QSizeF( 215.9, 355.6 ) );
295  presets << qMakePair( QString( "Letter" ), QSizeF( 215.9, 279.4 ) );
296  presets << qMakePair( QString( "ANSI A" ), QSizeF( 215.9, 279.4 ) );
297  presets << qMakePair( QString( "ANSI B" ), QSizeF( 279.4, 431.8 ) );
298  presets << qMakePair( QString( "ANSI C" ), QSizeF( 431.8, 558.8 ) );
299  presets << qMakePair( QString( "ANSI D" ), QSizeF( 558.8, 863.6 ) );
300  presets << qMakePair( QString( "ANSI E" ), QSizeF( 863.6, 1117.6 ) );
301  presets << qMakePair( QString( "Arch A" ), QSizeF( 228.6, 304.8 ) );
302  presets << qMakePair( QString( "Arch B" ), QSizeF( 304.8, 457.2 ) );
303  presets << qMakePair( QString( "Arch C" ), QSizeF( 457.2, 609.6 ) );
304  presets << qMakePair( QString( "Arch D" ), QSizeF( 609.6, 914.4 ) );
305  presets << qMakePair( QString( "Arch E" ), QSizeF( 914.4, 1219.2 ) );
306  presets << qMakePair( QString( "Arch E1" ), QSizeF( 762, 1066.8 ) );
307 
308  QList< QPair< QString, QSizeF > >::const_iterator presetIt = presets.constBegin();
309  for ( ;presetIt != presets.constEnd(); ++presetIt )
310  {
311  if ( presetString.compare(( *presetIt ).first, Qt::CaseInsensitive ) == 0 )
312  {
313  width = ( *presetIt ).second.width();
314  height = ( *presetIt ).second.height();
315  return true;
316  }
317  }
318  return false;
319 }
320 
321 void QgsComposerUtils::readDataDefinedPropertyMap( const QDomElement &itemElem, QMap<QgsComposerObject::DataDefinedProperty, QString> *dataDefinedNames, QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties )
322 {
323  QMap<QgsComposerObject::DataDefinedProperty, QString>::const_iterator i = dataDefinedNames->constBegin();
324  for ( ; i != dataDefinedNames->constEnd(); ++i )
325  {
326  QString elemName = i.value();
327  QDomNodeList ddNodeList = itemElem.elementsByTagName( elemName );
328  if ( ddNodeList.size() > 0 )
329  {
330  QDomElement ddElem = ddNodeList.at( 0 ).toElement();
331  readDataDefinedProperty( i.key(), ddElem, dataDefinedProperties );
332  }
333  }
334 }
335 
336 void QgsComposerUtils::readDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property, const QDomElement &ddElem, QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties )
337 {
338  if ( property == QgsComposerObject::AllProperties || property == QgsComposerObject::NoProperty )
339  {
340  //invalid property
341  return;
342  }
343 
344  QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->constFind( property );
345 
346  QgsDataDefined* dd = 0;
347  if ( it != dataDefinedProperties->constEnd() )
348  {
349  dd = it.value();
350  }
351  else
352  {
353  //QgsDataDefined for property doesn't currently exist, need to add new
354  dd = new QgsDataDefined();
355  dataDefinedProperties->insert( property, dd );
356  }
357 
358  //set values for QgsDataDefined
359  QString active = ddElem.attribute( "active" );
360  if ( active.compare( "true", Qt::CaseInsensitive ) == 0 )
361  {
362  dd->setActive( true );
363  }
364  else
365  {
366  dd->setActive( false );
367  }
368  QString useExpr = ddElem.attribute( "useExpr" );
369  if ( useExpr.compare( "true", Qt::CaseInsensitive ) == 0 )
370  {
371  dd->setUseExpression( true );
372  }
373  else
374  {
375  dd->setUseExpression( false );
376  }
377  dd->setField( ddElem.attribute( "field" ) );
378  dd->setExpressionString( ddElem.attribute( "expr" ) );
379 }
380 
381 void QgsComposerUtils::writeDataDefinedPropertyMap( QDomElement &itemElem, QDomDocument &doc, const QMap<QgsComposerObject::DataDefinedProperty, QString> *dataDefinedNames, const QMap<QgsComposerObject::DataDefinedProperty, QgsDataDefined *> *dataDefinedProperties )
382 {
383  QMap<QgsComposerObject::DataDefinedProperty, QString >::const_iterator i = dataDefinedNames->constBegin();
384  for ( ; i != dataDefinedNames->constEnd(); ++i )
385  {
386  QString newElemName = i.value();
387 
388  QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->find( i.key() );
389  if ( it != dataDefinedProperties->constEnd() )
390  {
391  QgsDataDefined* dd = it.value();
392  if ( dd )
393  {
394  bool active = dd->isActive();
395  bool useExpr = dd->useExpression();
396  QString expr = dd->expressionString();
397  QString field = dd->field();
398 
399  bool defaultVals = ( !active && !useExpr && expr.isEmpty() && field.isEmpty() );
400 
401  if ( !defaultVals )
402  {
403  QDomElement ddElem = doc.createElement( newElemName );
404  if ( active )
405  {
406  ddElem.setAttribute( "active", "true" );
407  }
408  else
409  {
410  ddElem.setAttribute( "active", "false" );
411  }
412  if ( useExpr )
413  {
414  ddElem.setAttribute( "useExpr", "true" );
415  }
416  else
417  {
418  ddElem.setAttribute( "useExpr", "false" );
419  }
420  ddElem.setAttribute( "expr", expr );
421  ddElem.setAttribute( "field", field );
422  itemElem.appendChild( ddElem );
423  }
424  }
425  }
426  }
427 }
428 
429 QFont QgsComposerUtils::scaledFontPixelSize( const QFont &font )
430 {
431  //upscale using FONT_WORKAROUND_SCALE
432  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
433  QFont scaledFont = font;
434  double pixelSize = pointsToMM( scaledFont.pointSizeF() ) * FONT_WORKAROUND_SCALE + 0.5;
435  scaledFont.setPixelSize( pixelSize );
436  return scaledFont;
437 }
438 
439 double QgsComposerUtils::fontAscentMM( const QFont &font )
440 {
441  //upscale using FONT_WORKAROUND_SCALE
442  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
443  QFont metricsFont = scaledFontPixelSize( font );
444  QFontMetricsF fontMetrics( metricsFont );
445  return ( fontMetrics.ascent() / FONT_WORKAROUND_SCALE );
446 }
447 
448 double QgsComposerUtils::fontDescentMM( const QFont &font )
449 {
450  //upscale using FONT_WORKAROUND_SCALE
451  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
452  QFont metricsFont = scaledFontPixelSize( font );
453  QFontMetricsF fontMetrics( metricsFont );
454  return ( fontMetrics.descent() / FONT_WORKAROUND_SCALE );
455 }
456 
457 double QgsComposerUtils::fontHeightMM( const QFont &font )
458 {
459  //upscale using FONT_WORKAROUND_SCALE
460  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
461  QFont metricsFont = scaledFontPixelSize( font );
462  QFontMetricsF fontMetrics( metricsFont );
463  return ( fontMetrics.height() / FONT_WORKAROUND_SCALE );
464 }
465 
466 double QgsComposerUtils::fontHeightCharacterMM( const QFont &font, const QChar &character )
467 {
468  //upscale using FONT_WORKAROUND_SCALE
469  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
470  QFont metricsFont = scaledFontPixelSize( font );
471  QFontMetricsF fontMetrics( metricsFont );
472  return ( fontMetrics.boundingRect( character ).height() / FONT_WORKAROUND_SCALE );
473 }
474 
475 double QgsComposerUtils::textWidthMM( const QFont &font, const QString &text )
476 {
477  //upscale using FONT_WORKAROUND_SCALE
478  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
479  QFont metricsFont = scaledFontPixelSize( font );
480  QFontMetricsF fontMetrics( metricsFont );
481  return ( fontMetrics.width( text ) / FONT_WORKAROUND_SCALE );
482 }
483 
484 void QgsComposerUtils::drawText( QPainter *painter, const QPointF &pos, const QString &text, const QFont &font, const QColor &color )
485 {
486  if ( !painter )
487  {
488  return;
489  }
490 
491  //upscale using FONT_WORKAROUND_SCALE
492  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
493  QFont textFont = scaledFontPixelSize( font );
494 
495  painter->save();
496  painter->setFont( textFont );
497  if ( color.isValid() )
498  {
499  painter->setPen( color );
500  }
501  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
502  painter->scale( scaleFactor, scaleFactor );
503  painter->drawText( pos * FONT_WORKAROUND_SCALE, text );
504  painter->restore();
505 }
506 
507 void QgsComposerUtils::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 )
508 {
509  if ( !painter )
510  {
511  return;
512  }
513 
514  //upscale using FONT_WORKAROUND_SCALE
515  //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
516  QFont textFont = scaledFontPixelSize( font );
517 
518  QRectF scaledRect( rect.x() * FONT_WORKAROUND_SCALE, rect.y() * FONT_WORKAROUND_SCALE,
519  rect.width() * FONT_WORKAROUND_SCALE, rect.height() * FONT_WORKAROUND_SCALE );
520 
521  painter->save();
522  painter->setFont( textFont );
523  if ( color.isValid() )
524  {
525  painter->setPen( color );
526  }
527  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
528  painter->scale( scaleFactor, scaleFactor );
529  painter->drawText( scaledRect, halignment | valignment | flags, text );
530  painter->restore();
531 }