Skip to content

Commit a9af35b

Browse files
committedApr 28, 2014
[FEATURE][composer] HTML frames no longer cut through lines of text when splitting over multiple pages (sponsored by City of Uster, Switzerland)
1 parent 4885289 commit a9af35b

File tree

3 files changed

+117
-3
lines changed

3 files changed

+117
-3
lines changed
 

‎src/core/composer/qgscomposerframe.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ class CORE_EXPORT QgsComposerFrame: public QgsComposerItem
4343

4444
QgsComposerMultiFrame* multiFrame() const { return mMultiFrame; }
4545

46-
4746
private:
4847
QgsComposerFrame(); //forbidden
4948
QgsComposerMultiFrame* mMultiFrame;

‎src/core/composer/qgscomposerhtml.cpp

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
#include <QPainter>
2222
#include <QWebFrame>
2323
#include <QWebPage>
24+
#include <QImage>
2425

2526
QgsComposerHtml::QgsComposerHtml( QgsComposition* c, bool createUndoCommands ): QgsComposerMultiFrame( c, createUndoCommands ),
26-
mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 )
27+
mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 ), mRenderedPage( 0 )
2728
{
2829
mHtmlUnitsToMM = htmlUnitsToMM();
2930
mWebPage = new QWebPage();
@@ -34,13 +35,14 @@ QgsComposerHtml::QgsComposerHtml( QgsComposition* c, bool createUndoCommands ):
3435
}
3536
}
3637

37-
QgsComposerHtml::QgsComposerHtml(): QgsComposerMultiFrame( 0, false ), mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 )
38+
QgsComposerHtml::QgsComposerHtml(): QgsComposerMultiFrame( 0, false ), mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 ), mRenderedPage( 0 )
3839
{
3940
}
4041

4142
QgsComposerHtml::~QgsComposerHtml()
4243
{
4344
delete mWebPage;
45+
delete mRenderedPage;
4446
}
4547

4648
void QgsComposerHtml::setUrl( const QUrl& url )
@@ -67,6 +69,9 @@ void QgsComposerHtml::setUrl( const QUrl& url )
6769
mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
6870
mSize.setWidth( contentsSize.width() / mHtmlUnitsToMM );
6971
mSize.setHeight( contentsSize.height() / mHtmlUnitsToMM );
72+
73+
renderCachedImage();
74+
7075
recalculateFrameSizes();
7176
emit changed();
7277
}
@@ -77,6 +82,20 @@ void QgsComposerHtml::frameLoaded( bool ok )
7782
mLoaded = true;
7883
}
7984

85+
void QgsComposerHtml::renderCachedImage()
86+
{
87+
//render page to cache image
88+
if ( mRenderedPage )
89+
{
90+
delete mRenderedPage;
91+
}
92+
mRenderedPage = new QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
93+
QPainter painter;
94+
painter.begin( mRenderedPage );
95+
mWebPage->mainFrame()->render( &painter );
96+
painter.end();
97+
}
98+
8099
QSizeF QgsComposerHtml::totalSize() const
81100
{
82101
return mSize;
@@ -121,6 +140,94 @@ void QgsComposerHtml::addFrame( QgsComposerFrame* frame, bool recalcFrameSizes )
121140
}
122141
}
123142

143+
bool candidateSort( const QPair<int, int> &c1, const QPair<int, int> &c2 )
144+
{
145+
if ( c1.second < c2.second )
146+
return true;
147+
else if ( c1.second > c2.second )
148+
return false;
149+
else if ( c1.first > c2.first )
150+
return true;
151+
else
152+
return false;
153+
}
154+
155+
double QgsComposerHtml::findNearbyPageBreak( double yPos )
156+
{
157+
if ( !mWebPage || !mRenderedPage )
158+
{
159+
return yPos;
160+
}
161+
162+
//convert yPos to pixels
163+
int idealPos = yPos * htmlUnitsToMM();
164+
165+
//if ideal break pos is past end of page, there's nothing we need to do
166+
if ( idealPos >= mRenderedPage->height() )
167+
{
168+
return yPos;
169+
}
170+
171+
int maxSearchDistance = 200;
172+
173+
//loop through all lines just before ideal break location, up to max distance
174+
//of maxSearchDistance
175+
int changes = 0;
176+
QRgb currentColor;
177+
QRgb pixelColor;
178+
QList< QPair<int, int> > candidates;
179+
for ( int candidateRow = idealPos; candidateRow >= idealPos - maxSearchDistance; --candidateRow )
180+
{
181+
changes = 0;
182+
currentColor = qRgba( 0, 0, 0, 0 );
183+
//check all pixels in this line
184+
for ( int col = 0; col < mRenderedPage->width(); ++col )
185+
{
186+
//count how many times the pixels change color in this row
187+
//eventually, we select a row to break at with the minimum number of color changes
188+
//since this is likely a line break, or gap between table cells, etc
189+
//but very unlikely to be midway through a text line or picture
190+
pixelColor = mRenderedPage->pixel( col, candidateRow );
191+
if ( pixelColor != currentColor )
192+
{
193+
//color has changed
194+
currentColor = pixelColor;
195+
changes++;
196+
}
197+
}
198+
candidates.append( qMakePair( candidateRow, changes ) );
199+
}
200+
201+
//sort candidate rows by number of changes ascending, row number descending
202+
qSort( candidates.begin(), candidates.end(), candidateSort );
203+
//first candidate is now the largest row with smallest number of changes
204+
205+
//ok, now take the mid point of the best candidate position
206+
//we do this so that the spacing between text lines is likely to be split in half
207+
//otherwise the html will be broken immediately above a line of text, which
208+
//looks a little messy
209+
int maxCandidateRow = candidates[0].first;
210+
int minCandidateRow = maxCandidateRow + 1;
211+
int minCandidateChanges = candidates[0].second;
212+
213+
QList< QPair<int, int> >::iterator it;
214+
for ( it = candidates.begin(); it != candidates.end(); ++it )
215+
{
216+
if (( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
217+
{
218+
//no longer in a consecutive block of rows of minimum pixel colour changes
219+
//so return the row mid-way through the block
220+
//first converting back to mm
221+
return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToMM();
222+
}
223+
minCandidateRow = ( *it ).first;
224+
}
225+
226+
//above loop didn't work for some reason
227+
//return first candidate converted to mm
228+
return candidates[0].first / htmlUnitsToMM();
229+
}
230+
124231
bool QgsComposerHtml::writeXML( QDomElement& elem, QDomDocument & doc, bool ignoreFrames ) const
125232
{
126233
QDomElement htmlElem = doc.createElement( "ComposerHtml" );

‎src/core/composer/qgscomposerhtml.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <QUrl>
2121

2222
class QWebPage;
23+
class QImage;
2324

2425
class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame
2526
{
@@ -40,6 +41,9 @@ class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame
4041

4142
void addFrame( QgsComposerFrame* frame, bool recalcFrameSizes = true );
4243

44+
//overriden to break frames without dividing lines of text
45+
double findNearbyPageBreak( double yPos );
46+
4347
private slots:
4448
void frameLoaded( bool ok );
4549

@@ -49,8 +53,12 @@ class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame
4953
bool mLoaded;
5054
QSizeF mSize; //total size in mm
5155
double mHtmlUnitsToMM;
56+
QImage* mRenderedPage;
5257

5358
double htmlUnitsToMM(); //calculate scale factor
59+
60+
//renders a snapshot of the page to a cached image
61+
void renderCachedImage();
5462
};
5563

5664
#endif // QGSCOMPOSERHTML_H

0 commit comments

Comments
 (0)
Please sign in to comment.