Skip to content

Commit

Permalink
Add ability to auto calculate header/footer heights [http://code.goog…
Browse files Browse the repository at this point in the history
…le.com/p/wkhtmltopdf/issues/detail?id=175]; fix half visible headers/footers when they are huge; escape header/footer dispersing out of page for bigger spacing values
  • Loading branch information
ro0gr committed Mar 29, 2012
1 parent 7b96dcd commit 80cbcc5
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 26 deletions.
160 changes: 138 additions & 22 deletions src/lib/pdfconverter.cc
Expand Up @@ -78,9 +78,9 @@ PdfConverterPrivate::PdfConverterPrivate(PdfGlobal & s, PdfConverter & o) :
settings(s), pageLoader(s.load),
out(o), printer(0), painter(0)
#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
, hfLoader(s.load), tocLoader1(s.load), tocLoader2(s.load)
, measuringHFLoader(s.load), hfLoader(s.load), tocLoader1(s.load), tocLoader2(s.load)
, tocLoader(&tocLoader1), tocLoaderOld(&tocLoader2)
, outline(0)
, outline(0), currentHeader(0), currentFooter(0)
#endif
{

Expand All @@ -102,12 +102,17 @@ PdfConverterPrivate::PdfConverterPrivate(PdfGlobal & s, PdfConverter & o) :
connect(&pageLoader, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));

#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
connect(&hfLoader, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
connect(&measuringHFLoader, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
connect(&measuringHFLoader, SIGNAL(loadFinished(bool)), this, SLOT(measuringHeadersLoaded(bool)));
connect(&measuringHFLoader, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
connect(&measuringHFLoader, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));

connect(&hfLoader, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
connect(&hfLoader, SIGNAL(loadFinished(bool)), this, SLOT(headersLoaded(bool)));
connect(&hfLoader, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
connect(&hfLoader, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));

connect(&tocLoader1, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
connect(&tocLoader1, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
connect(&tocLoader1, SIGNAL(loadFinished(bool)), this, SLOT(tocLoaded(bool)));
connect(&tocLoader1, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
connect(&tocLoader1, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));
Expand Down Expand Up @@ -137,22 +142,47 @@ void PdfConverterPrivate::beginConvert() {
return;
}
#endif
bool headerHeightsCalcNeeded = false;

for (QList<PageObject>::iterator i=objects.begin(); i != objects.end(); ++i) {
PageObject & o=*i;
settings::PdfObject & s = o.settings;

if (!s.header.htmlUrl.isEmpty() && looksLikeHtmlAndNotAUrl(s.header.htmlUrl)) {
emit out.error("--header-html should be a URL and not a string containing HTML code.");
fail();
return;
}

if (!s.footer.htmlUrl.isEmpty() && looksLikeHtmlAndNotAUrl(s.footer.htmlUrl)) {
emit out.error("--footer-html should be a URL and not a string containing HTML code.");
fail();
return;
}
if (!s.header.htmlUrl.isEmpty() ) {
if (looksLikeHtmlAndNotAUrl(s.header.htmlUrl)) {
emit out.error("--header-html should be a URL and not a string containing HTML code.");
fail();
return;
}

// we should auto calculate header if top margin is not specified
if (settings.margin.top.first == -1) {
headerHeightsCalcNeeded = true;
o.measuringHeader = &measuringHFLoader.addResource(
MultiPageLoader::guessUrlFromString(s.header.htmlUrl), s.load)->page;
} else {
// or just set static values
o.headerReserveHeight = settings.margin.top.first + s.header.spacing;
}
}

if (!s.footer.htmlUrl.isEmpty()) {
if (looksLikeHtmlAndNotAUrl(s.footer.htmlUrl)) {
emit out.error("--footer-html should be a URL and not a string containing HTML code.");
fail();
return;
}

if (settings.margin.bottom.first == -1) {
// we should auto calculate footer if top margin is not specified
headerHeightsCalcNeeded = true;
o.measuringFooter = &measuringHFLoader.addResource(
MultiPageLoader::guessUrlFromString(s.footer.htmlUrl), s.load)->page;
} else {
// or just set static values
o.footerReserveHeight = settings.margin.bottom.first + s.footer.spacing;
}
}

if (!s.isTableOfContent) {
o.loaderObject = pageLoader.addResource(s.page, s.load, &o.data);
Expand All @@ -162,13 +192,64 @@ void PdfConverterPrivate::beginConvert() {
}
}


emit out.phaseChanged();
loadProgress(0);

pageLoader.load();
if (headerHeightsCalcNeeded) {
// preload header/footer to check their heights
measuringHFLoader.load();
} else {
// set defaults if top or bottom mergin is not specified
if (settings.margin.top.first == -1) {
settings.margin.top.first = 10;
}
if (settings.margin.bottom.first == -1) {
settings.margin.bottom.first = 10;
}

pageLoader.load();
}
}

// calculates header/footer height
// returns millimeters
qreal PdfConverterPrivate::calculateHeaderHeight(PageObject & object, QWebPage & header) {
typedef QPair<QWebElement, QString> p_t;
settings::PdfObject & s = object.settings;
QPainter * testPainter = new QPainter();
QPrinter * testPrinter = createPrinter();

QWebPrinter wp(header.mainFrame(), testPrinter, *testPainter);
qreal height = wp.elementLocation(header.mainFrame()->findFirstElement("body")).second.height();

delete testPainter;
delete testPrinter;

return (height / PdfConverter::millimeterToPointMultiplier);
}

QPrinter * PdfConverterPrivate::createPrinter() {
QPrinter * printer = new QPrinter(settings.resolution);
if (settings.dpi != -1) printer->setResolution(settings.dpi);
//Tell the printer object to print the file <out>

printer->setOutputFormat(
(settings.outputFormat == "ps" || (settings.outputFormat == "" && settings.out.endsWith(".ps", Qt::CaseInsensitive)))?
QPrinter::PostScriptFormat : QPrinter::PdfFormat
);
printer->setOutputFileName(lout);

if ((settings.size.height.first != -1) && (settings.size.width.first != -1)) {
printer->setPaperSize(QSizeF(settings.size.width.first,settings.size.height.first + 100), settings.size.height.second);
} else {
printer->setPaperSize(settings.size.pageSize);
}

printer->setOrientation(settings.orientation);
printer->setColorMode(settings.colorMode);

return printer;
}

void PdfConverterPrivate::preprocessPage(PageObject & obj) {
currentObject++;
Expand Down Expand Up @@ -239,10 +320,10 @@ void PdfConverterPrivate::pagesLoaded(bool ok) {
return;
}

//Setup margins and papersize
printer->setPageMargins(settings.margin.left.first, settings.margin.top.first,
settings.margin.right.first, settings.margin.bottom.first,
settings.margin.left.second);
//Setup margins and papersize
printer->setPageMargins(settings.margin.left.first, objects[0].headerReserveHeight,
settings.margin.right.first, objects[0].footerReserveHeight,
settings.margin.left.second);

if ((settings.size.height.first != -1) && (settings.size.width.first != -1)) {
printer->setPaperSize(QSizeF(settings.size.width.first,settings.size.height.first), settings.size.height.second);
Expand Down Expand Up @@ -440,6 +521,9 @@ void PdfConverterPrivate::fillParms(QHash<QString, QString> & parms, int page, c
void PdfConverterPrivate::endPage(PageObject & object, bool hasHeaderFooter, int objectPage, int pageNumber) {
typedef QPair<QWebElement, QString> p_t;
settings::PdfObject & s = object.settings;
// save margin values
qreal leftMargin, topMargin, rightMargin, bottomMargin;
printer->getPageMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin, settings.margin.left.second);
if (hasHeaderFooter) {
QHash<QString, QString> parms;
fillParms(parms, pageNumber, object);
Expand Down Expand Up @@ -487,6 +571,8 @@ void PdfConverterPrivate::endPage(PageObject & object, bool hasHeaderFooter, int
painter->save();
painter->resetTransform();
double spacing = s.header.spacing * printer->height() / printer->heightMM();
// clear vertical margins for proper header rendering
printer->setPageMargins(leftMargin, 0, rightMargin, 0, settings.margin.left.second);
painter->translate(0, -spacing);
QWebPrinter wp(header->mainFrame(), printer, *painter);
painter->translate(0,-wp.elementLocation(header->mainFrame()->findFirstElement("body")).second.height());
Expand All @@ -503,8 +589,9 @@ void PdfConverterPrivate::endPage(PageObject & object, bool hasHeaderFooter, int
painter->addHyperlink(r, QUrl(p.second));
}
wp.spoolPage(1);
// restore margins
printer->setPageMargins(leftMargin, topMargin, rightMargin, bottomMargin, settings.margin.left.second);
painter->restore();

}

if (currentFooter) {
Expand All @@ -513,6 +600,9 @@ void PdfConverterPrivate::endPage(PageObject & object, bool hasHeaderFooter, int
painter->resetTransform();
double spacing = s.footer.spacing * printer->height() / printer->heightMM();
painter->translate(0, printer->height()+ spacing);
// clear vertical margins for proper header rendering
printer->setPageMargins(leftMargin, 0, rightMargin, 0, settings.margin.left.second);

QWebPrinter wp(footer->mainFrame(), printer, *painter);

QVector<p_t> local;
Expand All @@ -528,6 +618,8 @@ void PdfConverterPrivate::endPage(PageObject & object, bool hasHeaderFooter, int
painter->addHyperlink(r, QUrl(p.second));
}
wp.spoolPage(1);
// restore margins
printer->setPageMargins(leftMargin, topMargin, rightMargin, bottomMargin, settings.margin.left.second);
painter->restore();
}

Expand Down Expand Up @@ -597,6 +689,30 @@ void PdfConverterPrivate::tocLoaded(bool ok) {
#endif
}


void PdfConverterPrivate::measuringHeadersLoaded(bool ok) {
#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
if (errorCode == 0) errorCode = measuringHFLoader.httpErrorCode();
#endif
if (!ok) {
fail();
return;
}

for (int d=0; d < objects.size(); ++d) {
PageObject & obj = objects[d];
if (obj.measuringHeader) {
obj.headerReserveHeight = calculateHeaderHeight(obj, *obj.measuringHeader) + obj.settings.header.spacing;
}

if (obj.measuringFooter) {
obj.footerReserveHeight = calculateHeaderHeight(obj, *obj.measuringFooter) + obj.settings.header.spacing;
}
}

pageLoader.load();
}

void PdfConverterPrivate::headersLoaded(bool ok) {
#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
if (errorCode == 0) errorCode = hfLoader.httpErrorCode();
Expand Down
1 change: 1 addition & 0 deletions src/lib/pdfconverter.hh
Expand Up @@ -45,6 +45,7 @@ public:
void addResource(const settings::PdfObject & pageSettings, const QString * data=0);
const settings::PdfGlobal & globalSettings() const;
const QByteArray & output();
static const qreal millimeterToPointMultiplier = 2.83464567;
private:
PdfConverterPrivate * d;
virtual ConverterPrivate & priv();
Expand Down
23 changes: 21 additions & 2 deletions src/lib/pdfconverter_p.hh
Expand Up @@ -62,6 +62,14 @@ public:
QHash<QString, QWebElement> anchors;
QVector< QPair<QWebElement,QString> > localLinks;
QVector< QPair<QWebElement,QString> > externalLinks;
// height length to reserve for header when printing page
double headerReserveHeight;
// height length to reserve for footer when printing page
double footerReserveHeight;
// keeps preloaded header to calculate header height
QWebPage * measuringHeader;
// keeps preloaded footer to calculate header height
QWebPage * measuringFooter;
#endif

int firstPageNumber;
Expand All @@ -86,7 +94,8 @@ public:
}

PageObject(const settings::PdfObject & set, const QString * d=NULL):
settings(set), loaderObject(0), page(0) {
settings(set), loaderObject(0), page(0), headerReserveHeight(0), footerReserveHeight(0),
measuringHeader(0), measuringFooter(0) {
if (d) data=*d;
};

Expand Down Expand Up @@ -127,6 +136,7 @@ private:
int pageNumber;
QWebPrinter * webPrinter;
int objectPage;

QWebPage * currentHeader;
QWebPage * currentFooter;

Expand All @@ -137,6 +147,9 @@ private:
bool pageHasHeaderFooter;

#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
// loader for measuringHeader and measuringFooter
MultiPageLoader measuringHFLoader;

MultiPageLoader hfLoader;
MultiPageLoader tocLoader1;
MultiPageLoader tocLoader2;
Expand All @@ -152,7 +165,12 @@ private:
void fillParms(QHash<QString, QString> & parms, int page, const PageObject & object);
QString hfreplace(const QString & q, const QHash<QString, QString> & parms);
QWebPage * loadHeaderFooter(QString url, const QHash<QString, QString> & parms, const settings::PdfObject & ps);


#endif
qreal calculateHeaderHeight(PageObject & object, QWebPage & header);
QPrinter * createPrinter();

void handleTocPage(PageObject & obj);
void preprocessPage(PageObject & obj);
void spoolPage(size_t page);
Expand All @@ -165,7 +183,8 @@ private:
void loadTocs();
void loadHeaders();
public slots:
void pagesLoaded(bool ok);
void measuringHeadersLoaded(bool ok);
void pagesLoaded(bool ok);
void tocLoaded(bool ok);
void headersLoaded(bool ok);

Expand Down
4 changes: 2 additions & 2 deletions src/lib/pdfsettings.cc
Expand Up @@ -365,9 +365,9 @@ HeaderFooter::HeaderFooter():
spacing(0.0) {}

Margin::Margin():
top(UnitReal(10,QPrinter::Millimeter)),
top(UnitReal(-1,QPrinter::Millimeter)),
right(UnitReal(10,QPrinter::Millimeter)),
bottom(UnitReal(10,QPrinter::Millimeter)),
bottom(UnitReal(-1,QPrinter::Millimeter)),
left(UnitReal(10,QPrinter::Millimeter)) {}

PdfGlobal::PdfGlobal():
Expand Down

0 comments on commit 80cbcc5

Please sign in to comment.