Browse Source

Merge pull request #10 from beatle/master

Add header/footer heights autocalculation; fix disappearing header/footer for huge vertical margins; fix moving headers/footers outside the page for huge spacing values
Jakob Truelsen 13 years ago
parent
commit
7cb5810178
4 changed files with 166 additions and 26 deletions
  1. 142 22
      src/lib/pdfconverter.cc
  2. 1 0
      src/lib/pdfconverter.hh
  3. 21 2
      src/lib/pdfconverter_p.hh
  4. 2 2
      src/lib/pdfsettings.cc

+ 142 - 22
src/lib/pdfconverter.cc

@@ -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
 {
 
@@ -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)));
@@ -137,22 +142,49 @@ 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
+                // add spacing to prevent moving header out of page
+                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
+                // add spacing to prevent moving footer out of page
+                o.footerReserveHeight = settings.margin.bottom.first + s.footer.spacing;
+            }
+        }
 
 		if (!s.isTableOfContent) {
 			o.loaderObject = pageLoader.addResource(s.page, s.load, &o.data);
@@ -162,13 +194,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++;
@@ -239,10 +322,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);
@@ -440,6 +523,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);
@@ -487,6 +573,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());
@@ -503,8 +591,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) {
@@ -513,6 +602,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;
@@ -528,6 +620,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();
 	}
 
@@ -597,6 +691,32 @@ 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) {
+            // add spacing to prevent moving header out of page
+            obj.headerReserveHeight = calculateHeaderHeight(obj, *obj.measuringHeader) + obj.settings.header.spacing;
+        }
+
+        if (obj.measuringFooter) {
+            // add spacing to prevent moving footer out of page
+            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();

+ 1 - 0
src/lib/pdfconverter.hh

@@ -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();

+ 21 - 2
src/lib/pdfconverter_p.hh

@@ -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;
@@ -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;
 	};
 
@@ -127,6 +136,7 @@ private:
 	int pageNumber;
 	QWebPrinter * webPrinter;
 	int objectPage;
+
 	QWebPage * currentHeader;
 	QWebPage * currentFooter;
 
@@ -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;
@@ -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);
@@ -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);
 

+ 2 - 2
src/lib/pdfsettings.cc

@@ -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():