Skip to content

Commit

Permalink
[FEATURE][composer] HTML frames no longer cut through lines of text w…
Browse files Browse the repository at this point in the history
…hen splitting over multiple pages (sponsored by City of Uster, Switzerland)
  • Loading branch information
nyalldawson committed Apr 28, 2014
1 parent 4885289 commit a9af35b
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 3 deletions.
1 change: 0 additions & 1 deletion src/core/composer/qgscomposerframe.h
Expand Up @@ -43,7 +43,6 @@ class CORE_EXPORT QgsComposerFrame: public QgsComposerItem

QgsComposerMultiFrame* multiFrame() const { return mMultiFrame; }


private:
QgsComposerFrame(); //forbidden
QgsComposerMultiFrame* mMultiFrame;
Expand Down
111 changes: 109 additions & 2 deletions src/core/composer/qgscomposerhtml.cpp
Expand Up @@ -21,9 +21,10 @@
#include <QPainter>
#include <QWebFrame>
#include <QWebPage>
#include <QImage>

QgsComposerHtml::QgsComposerHtml( QgsComposition* c, bool createUndoCommands ): QgsComposerMultiFrame( c, createUndoCommands ),
mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 )
mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 ), mRenderedPage( 0 )
{
mHtmlUnitsToMM = htmlUnitsToMM();
mWebPage = new QWebPage();
Expand All @@ -34,13 +35,14 @@ QgsComposerHtml::QgsComposerHtml( QgsComposition* c, bool createUndoCommands ):
}
}

QgsComposerHtml::QgsComposerHtml(): QgsComposerMultiFrame( 0, false ), mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 )
QgsComposerHtml::QgsComposerHtml(): QgsComposerMultiFrame( 0, false ), mWebPage( 0 ), mLoaded( false ), mHtmlUnitsToMM( 1.0 ), mRenderedPage( 0 )
{
}

QgsComposerHtml::~QgsComposerHtml()
{
delete mWebPage;
delete mRenderedPage;
}

void QgsComposerHtml::setUrl( const QUrl& url )
Expand All @@ -67,6 +69,9 @@ void QgsComposerHtml::setUrl( const QUrl& url )
mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
mSize.setWidth( contentsSize.width() / mHtmlUnitsToMM );
mSize.setHeight( contentsSize.height() / mHtmlUnitsToMM );

renderCachedImage();

recalculateFrameSizes();
emit changed();
}
Expand All @@ -77,6 +82,20 @@ void QgsComposerHtml::frameLoaded( bool ok )
mLoaded = true;
}

void QgsComposerHtml::renderCachedImage()
{
//render page to cache image
if ( mRenderedPage )
{
delete mRenderedPage;
}
mRenderedPage = new QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
QPainter painter;
painter.begin( mRenderedPage );
mWebPage->mainFrame()->render( &painter );
painter.end();
}

QSizeF QgsComposerHtml::totalSize() const
{
return mSize;
Expand Down Expand Up @@ -121,6 +140,94 @@ void QgsComposerHtml::addFrame( QgsComposerFrame* frame, bool recalcFrameSizes )
}
}

bool candidateSort( const QPair<int, int> &c1, const QPair<int, int> &c2 )
{
if ( c1.second < c2.second )
return true;
else if ( c1.second > c2.second )
return false;
else if ( c1.first > c2.first )
return true;
else
return false;
}

double QgsComposerHtml::findNearbyPageBreak( double yPos )
{
if ( !mWebPage || !mRenderedPage )
{
return yPos;
}

//convert yPos to pixels
int idealPos = yPos * htmlUnitsToMM();

//if ideal break pos is past end of page, there's nothing we need to do
if ( idealPos >= mRenderedPage->height() )
{
return yPos;
}

int maxSearchDistance = 200;

//loop through all lines just before ideal break location, up to max distance
//of maxSearchDistance
int changes = 0;
QRgb currentColor;
QRgb pixelColor;
QList< QPair<int, int> > candidates;
for ( int candidateRow = idealPos; candidateRow >= idealPos - maxSearchDistance; --candidateRow )
{
changes = 0;
currentColor = qRgba( 0, 0, 0, 0 );
//check all pixels in this line
for ( int col = 0; col < mRenderedPage->width(); ++col )
{
//count how many times the pixels change color in this row
//eventually, we select a row to break at with the minimum number of color changes
//since this is likely a line break, or gap between table cells, etc
//but very unlikely to be midway through a text line or picture
pixelColor = mRenderedPage->pixel( col, candidateRow );
if ( pixelColor != currentColor )
{
//color has changed
currentColor = pixelColor;
changes++;
}
}
candidates.append( qMakePair( candidateRow, changes ) );
}

//sort candidate rows by number of changes ascending, row number descending
qSort( candidates.begin(), candidates.end(), candidateSort );
//first candidate is now the largest row with smallest number of changes

//ok, now take the mid point of the best candidate position
//we do this so that the spacing between text lines is likely to be split in half
//otherwise the html will be broken immediately above a line of text, which
//looks a little messy
int maxCandidateRow = candidates[0].first;
int minCandidateRow = maxCandidateRow + 1;
int minCandidateChanges = candidates[0].second;

QList< QPair<int, int> >::iterator it;
for ( it = candidates.begin(); it != candidates.end(); ++it )
{
if (( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
{
//no longer in a consecutive block of rows of minimum pixel colour changes
//so return the row mid-way through the block
//first converting back to mm
return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToMM();
}
minCandidateRow = ( *it ).first;
}

//above loop didn't work for some reason
//return first candidate converted to mm
return candidates[0].first / htmlUnitsToMM();
}

bool QgsComposerHtml::writeXML( QDomElement& elem, QDomDocument & doc, bool ignoreFrames ) const
{
QDomElement htmlElem = doc.createElement( "ComposerHtml" );
Expand Down
8 changes: 8 additions & 0 deletions src/core/composer/qgscomposerhtml.h
Expand Up @@ -20,6 +20,7 @@
#include <QUrl>

class QWebPage;
class QImage;

class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame
{
Expand All @@ -40,6 +41,9 @@ class CORE_EXPORT QgsComposerHtml: public QgsComposerMultiFrame

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

//overriden to break frames without dividing lines of text
double findNearbyPageBreak( double yPos );

private slots:
void frameLoaded( bool ok );

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

double htmlUnitsToMM(); //calculate scale factor

//renders a snapshot of the page to a cached image
void renderCachedImage();
};

#endif // QGSCOMPOSERHTML_H

0 comments on commit a9af35b

Please sign in to comment.