source: wkhtmltox/trunk/fuentes/src/lib/pdfconverter.cc @ 51

Last change on this file since 51 was 51, checked in by mabarracus, 4 years ago

wip

File size: 37.4 KB
Line 
1// -*- mode: c++; tab-width: 4; indent-tabs-mode: t; eval: (progn (c-set-style "stroustrup") (c-set-offset 'innamespace 0)); -*-
2// vi:set ts=4 sts=4 sw=4 noet :
3//
4// Copyright 2010, 2011 wkhtmltopdf authors
5//
6// This file is part of wkhtmltopdf.
7//
8// wkhtmltopdf is free software: you can redistribute it and/or modify
9// it under the terms of the GNU Lesser General Public License as published by
10// the Free Software Foundation, either version 3 of the License, or
11// (at your option) any later version.
12//
13// wkhtmltopdf is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16// GNU General Public License for more details.
17//
18// You should have received a copy of the GNU Lesser General Public License
19// along with wkhtmltopdf.  If not, see <http://www.gnu.org/licenses/>.
20
21
22#include "pdfconverter_p.hh"
23#include <QAuthenticator>
24#include <QDateTime>
25#include <QDir>
26#include <QFile>
27#include <QPair>
28#include <QPrintEngine>
29#include <QTimer>
30#include <QWebFrame>
31#include <QWebPage>
32#include <QWebSettings>
33#include <QXmlQuery>
34#include <algorithm>
35#include <qapplication.h>
36#include <qfileinfo.h>
37#ifdef Q_OS_WIN32
38#include <fcntl.h>
39#include <io.h>
40#endif
41
42#include "dllbegin.inc"
43using namespace wkhtmltopdf;
44using namespace wkhtmltopdf::settings;
45
46#define STRINGIZE_(x) #x
47#define STRINGIZE(x) STRINGIZE_(x)
48
49const qreal PdfConverter::millimeterToPointMultiplier = 2.83464567;
50
51DLL_LOCAL QMap<QWebPage *, PageObject *> PageObject::webPageToObject;
52
53struct DLL_LOCAL StreamDumper {
54        QFile out;
55        QTextStream stream;
56
57        StreamDumper(const QString & path): out(path), stream(&out) {
58                out.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
59                stream.setCodec("UTF-8");
60        }
61};
62
63/*!
64  \file pageconverter.hh
65  \brief Defines the PdfConverter class
66*/
67
68/*!
69  \file pageconverter_p.hh
70  \brief Defines the PdfConverterPrivate class
71*/
72
73bool DLL_LOCAL looksLikeHtmlAndNotAUrl(QString str) {
74        QString s = str.split("?")[0];
75        return s.count('<') > 0 || str.startsWith("data:", Qt::CaseInsensitive);
76}
77
78PdfConverterPrivate::PdfConverterPrivate(PdfGlobal & s, PdfConverter & o) :
79        settings(s), pageLoader(s.load, true),
80        out(o), printer(0), painter(0)
81#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
82    , webPrinter(0), measuringHFLoader(s.load), hfLoader(s.load), tocLoader1(s.load), tocLoader2(s.load)
83        , tocLoader(&tocLoader1), tocLoaderOld(&tocLoader2)
84    , outline(0), currentHeader(0), currentFooter(0)
85#endif
86{
87
88#ifdef  __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
89        phaseDescriptions.push_back("Loading pages");
90        phaseDescriptions.push_back("Counting pages");
91        phaseDescriptions.push_back("Loading TOC");
92        phaseDescriptions.push_back("Resolving links");
93        phaseDescriptions.push_back("Loading headers and footers");
94#else
95        phaseDescriptions.push_back("Loading page");
96#endif
97        phaseDescriptions.push_back("Printing pages");
98        phaseDescriptions.push_back("Done");
99
100        connect(&pageLoader, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
101        connect(&pageLoader, SIGNAL(loadFinished(bool)), this, SLOT(pagesLoaded(bool)));
102        connect(&pageLoader, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
103        connect(&pageLoader, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));
104
105#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
106    connect(&measuringHFLoader, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
107    connect(&measuringHFLoader, SIGNAL(loadFinished(bool)), this, SLOT(measuringHeadersLoaded(bool)));
108    connect(&measuringHFLoader, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
109    connect(&measuringHFLoader, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));
110
111    connect(&hfLoader, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
112        connect(&hfLoader, SIGNAL(loadFinished(bool)), this, SLOT(headersLoaded(bool)));
113        connect(&hfLoader, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
114        connect(&hfLoader, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));
115
116    connect(&tocLoader1, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
117        connect(&tocLoader1, SIGNAL(loadFinished(bool)), this, SLOT(tocLoaded(bool)));
118        connect(&tocLoader1, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
119        connect(&tocLoader1, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));
120
121        connect(&tocLoader2, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
122        connect(&tocLoader2, SIGNAL(loadFinished(bool)), this, SLOT(tocLoaded(bool)));
123        connect(&tocLoader2, SIGNAL(error(QString)), this, SLOT(forwardError(QString)));
124        connect(&tocLoader2, SIGNAL(warning(QString)), this, SLOT(forwardWarning(QString)));
125#endif
126
127        if ( ! settings.viewportSize.isEmpty())
128        {
129                QStringList viewportSizeList = settings.viewportSize.split("x");
130                int width = viewportSizeList.first().toInt();
131                int height = viewportSizeList.last().toInt();
132                viewportSize = QSize(width,height);
133        }
134}
135
136PdfConverterPrivate::~PdfConverterPrivate() {
137        clearResources();
138}
139
140
141void PdfConverterPrivate::beginConvert() {
142        error=false;
143        progressString = "0%";
144        currentPhase=0;
145        errorCode=0;
146
147#ifndef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
148        if (objects.size() > 1) {
149                emit out.error("This version of wkhtmltopdf is build against an unpatched version of QT, and does not support more then one input document.");
150                fail();
151                return;
152        }
153#else
154    bool headerHeightsCalcNeeded = false;
155#endif
156
157        for (QList<PageObject>::iterator i=objects.begin(); i != objects.end(); ++i) {
158                PageObject & o=*i;
159                settings::PdfObject & s = o.settings;
160
161#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
162        if (!s.header.htmlUrl.isEmpty() ) {
163            if (looksLikeHtmlAndNotAUrl(s.header.htmlUrl)) {
164                emit out.error("--header-html should be a URL and not a string containing HTML code.");
165                fail();
166                return;
167            }
168
169            // we should auto calculate header if top margin is not specified
170            if (settings.margin.top.first == -1) {
171                headerHeightsCalcNeeded = true;
172                o.measuringHeader = &measuringHFLoader.addResource(
173                    MultiPageLoader::guessUrlFromString(s.header.htmlUrl), s.load)->page;
174            } else {
175                // or just set static values
176                // add spacing to prevent moving header out of page
177                o.headerReserveHeight = settings.margin.top.first + s.header.spacing;
178            }
179        }
180
181        if (!s.footer.htmlUrl.isEmpty()) {
182            if (looksLikeHtmlAndNotAUrl(s.footer.htmlUrl)) {
183                emit out.error("--footer-html should be a URL and not a string containing HTML code.");
184                fail();
185                return;
186            }
187
188            if (settings.margin.bottom.first == -1) {
189                // we should auto calculate footer if top margin is not specified
190                headerHeightsCalcNeeded = true;
191                o.measuringFooter = &measuringHFLoader.addResource(
192                    MultiPageLoader::guessUrlFromString(s.footer.htmlUrl), s.load)->page;
193            } else {
194                // or just set static values
195                // add spacing to prevent moving footer out of page
196                o.footerReserveHeight = settings.margin.bottom.first + s.footer.spacing;
197            }
198        }
199#endif
200
201                if (!s.isTableOfContent) {
202                        o.loaderObject = pageLoader.addResource(s.page, s.load, &o.data);
203                        o.page = &o.loaderObject->page;
204                        PageObject::webPageToObject[o.page] = &o;
205                        updateWebSettings(o.page->settings(), s.web);
206                }
207        }
208
209        emit out.phaseChanged();
210        loadProgress(0);
211
212#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
213    if (headerHeightsCalcNeeded) {
214        // preload header/footer to check their heights
215        measuringHFLoader.load();
216    } else {
217        // set defaults if top or bottom mergin is not specified
218        if (settings.margin.top.first == -1) {
219            settings.margin.top.first = 10;
220        }
221        if (settings.margin.bottom.first == -1) {
222            settings.margin.bottom.first = 10;
223        }
224
225        for (QList<PageObject>::iterator i=objects.begin(); i != objects.end(); ++i) {
226            PageObject & o=*i;
227            o.headerReserveHeight = settings.margin.top.first;
228            o.footerReserveHeight = settings.margin.bottom.first;
229        }
230
231        pageLoader.load();
232    }
233#else
234    pageLoader.load();
235#endif
236}
237
238#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
239// calculates header/footer height
240// returns millimeters
241qreal PdfConverterPrivate::calculateHeaderHeight(PageObject & object, QWebPage & header) {
242    Q_UNUSED(object);
243
244    TempFile   tempObj;
245    QString    tempFile = tempObj.create(".pdf");
246
247    QPainter * testPainter = new QPainter();
248    QPrinter * testPrinter = createPrinter(tempFile);
249
250    if (!testPainter->begin(testPrinter)) {
251        emit out.error("Unable to write to temp location");
252        return 0.0;
253    }
254
255    QWebPrinter wp(header.mainFrame(), testPrinter, *testPainter);
256    qreal height = wp.elementLocation(header.mainFrame()->findFirstElement("body")).second.height();
257
258    delete testPainter;
259    delete testPrinter;
260
261    return (height / PdfConverter::millimeterToPointMultiplier);
262}
263
264#endif
265
266QPrinter * PdfConverterPrivate::createPrinter(const QString & tempFile) {
267    QPrinter * printer = new QPrinter(settings.resolution);
268    if (settings.dpi != -1) printer->setResolution(settings.dpi);
269    //Tell the printer object to print the file <out>
270
271    printer->setOutputFileName(tempFile);
272    printer->setOutputFormat(QPrinter::PdfFormat);
273
274    if ((settings.size.height.first != -1) && (settings.size.width.first != -1)) {
275        printer->setPaperSize(QSizeF(settings.size.width.first,settings.size.height.first + 100), settings.size.height.second);
276    } else {
277        printer->setPaperSize(settings.size.pageSize);
278    }
279
280    printer->setOrientation(settings.orientation);
281    printer->setColorMode(settings.colorMode);
282    printer->setCreator("wkhtmltopdf " STRINGIZE(FULL_VERSION));
283
284    return printer;
285}
286
287#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
288void PdfConverterPrivate::preprocessPage(PageObject & obj) {
289        currentObject++;
290        if (obj.settings.isTableOfContent) {
291                obj.pageCount = 1;
292                pageCount += 1;
293                outline->addEmptyWebPage();
294                return;
295        }
296        if (!obj.loaderObject || obj.loaderObject->skip) return;
297
298        int tot = objects.size();
299        progressString = QString("Object ")+QString::number(currentObject)+QString(" of ")+QString::number(tot);
300        emit out.progressChanged((currentObject)*100 / tot);
301
302        painter->save();
303
304        if (viewportSize.isValid() && ! viewportSize.isEmpty()) {
305                obj.page->setViewportSize(viewportSize);
306                obj.page->mainFrame()->setScrollBarPolicy(Qt::Vertical,Qt::ScrollBarAlwaysOff);
307                obj.page->mainFrame()->setScrollBarPolicy(Qt::Horizontal,Qt::ScrollBarAlwaysOff);
308        }
309
310
311        QWebPrinter wp(obj.page->mainFrame(), printer, *painter);
312        obj.pageCount = obj.settings.pagesCount? wp.pageCount(): 0;
313        pageCount += obj.pageCount;
314
315        if (obj.settings.includeInOutline)
316                outline->addWebPage(obj.page->mainFrame()->title(), wp, obj.page->mainFrame(),
317                                                        obj.settings, obj.localLinks, obj.anchors);
318        else
319                outline->addEmptyWebPage();
320        painter->restore();
321}
322#endif
323
324/*!
325 * Prepares printing out the document to the pdf file
326 */
327void PdfConverterPrivate::pagesLoaded(bool ok) {
328        if (errorCode == 0) errorCode = pageLoader.httpErrorCode();
329        if (!ok) {
330                fail();
331                return;
332        }
333
334        lout = settings.out;
335        if (settings.out == "-") {
336#ifndef Q_OS_WIN32
337                 if (QFile::exists("/dev/stdout"))
338                         lout = "/dev/stdout";
339                 else
340#endif
341                         lout = tempOut.create(".pdf");
342        }
343        if (settings.out.isEmpty())
344          lout = tempOut.create(".pdf");
345
346        printer = new QPrinter(settings.resolution);
347        if (settings.dpi != -1) printer->setResolution(settings.dpi);
348        //Tell the printer object to print the file <out>
349
350        printer->setOutputFileName(lout);
351        printer->setOutputFormat(QPrinter::PdfFormat);
352
353        //We currently only support margins with the same unit
354        if (settings.margin.left.second != settings.margin.right.second ||
355                settings.margin.left.second != settings.margin.top.second ||
356                settings.margin.left.second != settings.margin.bottom.second) {
357                emit out.error("Currently all margin units must be the same!");
358                fail();
359                return;
360        }
361
362    //Setup margins and papersize
363#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
364    double maxHeaderHeight = objects[0].headerReserveHeight;
365    double maxFooterHeight = objects[0].footerReserveHeight;
366    for (QList<PageObject>::iterator i=objects.begin(); i != objects.end(); ++i) {
367        PageObject & o=*i;
368        maxHeaderHeight = std::max(maxHeaderHeight, o.headerReserveHeight);
369        maxFooterHeight = std::max(maxFooterHeight, o.footerReserveHeight);
370    }
371    printer->setPageMargins(settings.margin.left.first, maxHeaderHeight,
372                                settings.margin.right.first, maxFooterHeight,
373                                settings.margin.left.second);
374#else
375    printer->setPageMargins(settings.margin.left.first, settings.margin.top.first,
376                                settings.margin.right.first, settings.margin.bottom.first,
377                                settings.margin.left.second);
378#endif
379
380        if ((settings.size.height.first != -1) && (settings.size.width.first != -1)) {
381                printer->setPaperSize(QSizeF(settings.size.width.first,settings.size.height.first), settings.size.height.second);
382        } else {
383                printer->setPaperSize(settings.size.pageSize);
384        }
385
386        printer->setOrientation(settings.orientation);
387        printer->setColorMode(settings.colorMode);
388        printer->setCreator("wkhtmltopdf " STRINGIZE(FULL_VERSION));
389
390        if (!printer->isValid()) {
391                emit out.error("Unable to write to destination");
392                fail();
393                return;
394        }
395
396#ifndef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
397        //If you do not have the hacks you get this crappy solution
398        printer->setCollateCopies(settings.copies);
399        printer->setCollateCopies(settings.collate);
400
401        printDocument();
402#else
403        printer->printEngine()->setProperty(QPrintEngine::PPK_UseCompression, settings.useCompression);
404        printer->printEngine()->setProperty(QPrintEngine::PPK_ImageQuality, settings.imageQuality);
405        printer->printEngine()->setProperty(QPrintEngine::PPK_ImageDPI, settings.imageDPI);
406
407        painter = new QPainter();
408
409        title = settings.documentTitle;
410        for (int d=0; d < objects.size(); ++d) {
411                if (title != "") break;
412                if (!objects[d].loaderObject || objects[d].loaderObject->skip ||
413                        objects[d].settings.isTableOfContent) continue;
414                title = objects[d].page->mainFrame()->title();
415        }
416        printer->setDocName(title);
417        if (!painter->begin(printer)) {
418                emit out.error("Unable to write to destination");
419                fail();
420                return;
421        }
422
423        currentPhase = 1;
424        emit out.phaseChanged();
425        outline = new Outline(settings);
426        //This is the first render face, it is done to calculate:
427        // * The number of pages of each document
428        // * A visual ordering of the header element
429        // * The location and page number of each header
430        pageCount = 0;
431        currentObject = 0;
432        for (int d=0; d < objects.size(); ++d)
433                preprocessPage(objects[d]);
434        actualPages = pageCount * settings.copies;
435
436        loadTocs();
437#endif
438}
439
440void PdfConverterPrivate::loadHeaders() {
441#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
442        currentPhase = 4;
443        emit out.phaseChanged();
444        bool hf=false;
445
446        int pageNumber=1;
447        for (int d=0; d < objects.size(); ++d) {
448                PageObject & obj = objects[d];
449                if (!obj.loaderObject || obj.loaderObject->skip) continue;
450
451                settings::PdfObject & ps = obj.settings;
452                for (int op=0; op < obj.pageCount; ++op) {
453                        if (!ps.header.htmlUrl.isEmpty() || !ps.footer.htmlUrl.isEmpty()) {
454                                QHash<QString, QString> parms;
455                                fillParms(parms, pageNumber, obj);
456                                parms["sitepage"] = QString::number(op+1);
457                                parms["sitepages"] = QString::number(obj.pageCount);
458                                hf = true;
459                                if (!ps.header.htmlUrl.isEmpty())
460                                        obj.headers.push_back(loadHeaderFooter(ps.header.htmlUrl, parms, ps) );
461                                if (!ps.footer.htmlUrl.isEmpty()) {
462                                        obj.footers.push_back(loadHeaderFooter(ps.footer.htmlUrl, parms, ps) );
463                                }
464                        }
465                        if (ps.pagesCount) ++pageNumber;
466                }
467        }
468        if (hf)
469                hfLoader.load();
470        else
471                printDocument();
472#endif
473}
474
475
476void PdfConverterPrivate::loadTocs() {
477#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
478        std::swap(tocLoaderOld, tocLoader);
479        tocLoader->clearResources();
480
481        bool toc = false;
482        for (int d=0; d < objects.size(); ++d) {
483                PageObject & obj = objects[d];
484                settings::PdfObject & ps = obj.settings;
485                if (!ps.isTableOfContent) continue;
486                obj.clear();
487
488                QString style = ps.tocXsl;
489                if (style.isEmpty()) {
490                        style = obj.tocFile.create(".xsl");
491                        StreamDumper styleDump(style);
492                        dumpDefaultTOCStyleSheet(styleDump.stream, ps.toc);
493                }
494
495                QString path = obj.tocFile.create(".xml");
496                StreamDumper sd(path);
497                outline->dump(sd.stream);
498
499                QFile styleFile(style);
500                if (!styleFile.open(QIODevice::ReadOnly)) {
501                        emit out.error("Could not read the TOC XSL");
502                        fail();
503                }
504
505                QFile xmlFile(path);
506                if (!xmlFile.open(QIODevice::ReadOnly)) {
507                        emit out.error("Could not read the TOC XML");
508                        fail();
509                }
510
511                QString htmlPath = obj.tocFile.create(".html");
512                QFile htmlFile(htmlPath);
513                if (!htmlFile.open(QIODevice::WriteOnly)) {
514                        emit out.error("Could not open the TOC for writing");
515                        fail();
516                }
517
518                QXmlQuery query(QXmlQuery::XSLT20);
519                query.setFocus(&xmlFile);
520                query.setQuery(&styleFile);
521                query.evaluateTo(&htmlFile);
522
523                obj.loaderObject = tocLoader->addResource(htmlPath, ps.load);
524                obj.page = &obj.loaderObject->page;
525                PageObject::webPageToObject[obj.page] = &obj;
526                updateWebSettings(obj.page->settings(), ps.web);
527                toc= true;
528        }
529
530        if (toc) {
531                if (currentPhase != 2) {
532                        currentPhase = 2;
533                        emit out.phaseChanged();
534                }
535                tocLoader->load();
536        } else
537                tocLoaded(true);
538#endif
539}
540
541#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
542void PdfConverterPrivate::findLinks(QWebFrame * frame, QVector<QPair<QWebElement, QString> > & local, QVector<QPair<QWebElement, QString> > & external, QHash<QString, QWebElement> & anchors) {
543        bool ulocal=true, uexternal=true;
544        if (PageObject::webPageToObject.contains(frame->page())) {
545                ulocal = PageObject::webPageToObject[frame->page()]->settings.useLocalLinks;
546                uexternal  = PageObject::webPageToObject[frame->page()]->settings.useExternalLinks;
547        }
548        if (!ulocal && !uexternal) return;
549        foreach (const QWebElement & elm, frame->findAllElements("a")) {
550                QString n=elm.attribute("name");
551                if (n.isEmpty()) n=elm.attribute("ns0:name");
552                if (n.startsWith("__WKANCHOR_")) anchors[n] = elm;
553
554                QString h=elm.attribute("href");
555                if (h.isEmpty()) h=elm.attribute("ns0:href");
556                if (h.startsWith("__WKANCHOR_")) {
557                        local.push_back( qMakePair(elm, h) );
558                } else {
559                        QUrl href(h);
560                        if (href.isEmpty()) continue;
561                        href=frame->baseUrl().resolved(href);
562                        QString key = QUrl::fromPercentEncoding(href.toString(QUrl::RemoveFragment).toLocal8Bit());
563                        if (urlToPageObj.contains(key)) {
564                                if (ulocal) {
565                                        PageObject * p = urlToPageObj[key];
566                                        QWebElement e;
567                                        if (!href.hasFragment())
568                                                e = p->page->mainFrame()->findFirstElement("body");
569                                        else {
570                                                e = p->page->mainFrame()->findFirstElement("a[name=\""+href.fragment()+"\"]");
571                                                if (e.isNull())
572                                                        e = p->page->mainFrame()->findFirstElement("*[id=\""+href.fragment()+"\"]");
573                                                if (e.isNull())
574                                                        e = p->page->mainFrame()->findFirstElement("*[name=\""+href.fragment()+"\"]");
575                                        }
576                                        if (!e.isNull()) {
577                                                p->anchors[href.toString()] = e;
578                                                local.push_back( qMakePair(elm, href.toString()) );
579                                        }
580                                }
581                        } else if (uexternal) {
582                                external.push_back( qMakePair(elm, settings.resolveRelativeLinks ? href.toString() : h) );
583                        }
584                }
585        }
586}
587
588void PdfConverterPrivate::fillParms(QHash<QString, QString> & parms, int page, const PageObject & object) {
589        outline->fillHeaderFooterParms(page, parms, object.settings);
590        parms["doctitle"] = title;
591        parms["title"] = object.page?object.page->mainFrame()->title():"";
592        QDateTime t(QDateTime::currentDateTime());
593        parms["time"] = t.time().toString(Qt::SystemLocaleShortDate);
594        parms["date"] = t.date().toString(Qt::SystemLocaleShortDate);
595        parms["isodate"] = t.date().toString(Qt::ISODate);
596}
597
598
599void PdfConverterPrivate::endPage(PageObject & object, bool hasHeaderFooter, int objectPage, int pageNumber) {
600        typedef QPair<QWebElement, QString> p_t;
601        settings::PdfObject & s = object.settings;
602    // save margin values
603    qreal leftMargin, topMargin, rightMargin, bottomMargin;
604    printer->getPageMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin, settings.margin.left.second);
605        if (hasHeaderFooter) {
606                QHash<QString, QString> parms;
607                fillParms(parms, pageNumber, object);
608                parms["sitepage"]  = QString::number(objectPage+1);
609                parms["sitepages"] = QString::number(object.pageCount);
610
611                //Webkit used all kinds of crazy coordinate transformation, and font setup
612                //We save it here and restore some sane defaults
613                painter->save();
614                painter->resetTransform();
615
616                int h=printer->height();
617                int w=printer->width();
618
619                double spacing = s.header.spacing * printer->height() / printer->heightMM();
620                //If needed draw the header line
621                if (s.header.line) painter->drawLine(0, -spacing, w, -spacing);
622                //Guess the height of the header text
623                painter->setFont(QFont(s.header.fontName, s.header.fontSize));
624                int dy = painter->boundingRect(0, 0, w, h, Qt::AlignTop, "M").height();
625                //Draw the header text
626                QRect r=QRect(0, 0-dy-spacing, w, h);
627                painter->drawText(r, Qt::AlignTop | Qt::AlignLeft, hfreplace(s.header.left, parms));
628                painter->drawText(r, Qt::AlignTop | Qt::AlignHCenter, hfreplace(s.header.center, parms));
629                painter->drawText(r, Qt::AlignTop | Qt::AlignRight, hfreplace(s.header.right, parms));
630
631                spacing = s.footer.spacing * printer->height() / printer->heightMM();
632                //IF needed draw the footer line
633                if (s.footer.line) painter->drawLine(0, h + spacing, w, h + spacing);
634                //Guess the height of the footer text
635                painter->setFont(QFont(s.footer.fontName, s.footer.fontSize));
636                dy = painter->boundingRect(0, 0, w, h, Qt::AlignTop, "M").height();
637                //Draw the footer text
638                r=QRect(0,0,w,h+dy+ spacing);
639                painter->drawText(r, Qt::AlignBottom | Qt::AlignLeft, hfreplace(s.footer.left, parms));
640                painter->drawText(r, Qt::AlignBottom | Qt::AlignHCenter, hfreplace(s.footer.center, parms));
641                painter->drawText(r, Qt::AlignBottom | Qt::AlignRight, hfreplace(s.footer.right, parms));
642
643                //Restore Webkit's crazy scaling and font settings
644                painter->restore();
645        }
646
647        //if (!object.headers.empty()) {
648        //object.headers[objectPage];
649        if (currentHeader) {
650                QWebPage * header = currentHeader;
651                updateWebSettings(header->settings(), object.settings.web);
652                painter->save();
653                painter->resetTransform();
654                double spacing = s.header.spacing * printer->height() / printer->heightMM();
655        // clear vertical margins for proper header rendering
656        printer->setPageMargins(leftMargin, 0, rightMargin, 0, settings.margin.left.second);
657                painter->translate(0, -spacing);
658                QWebPrinter wp(header->mainFrame(), printer, *painter);
659                painter->translate(0,-wp.elementLocation(header->mainFrame()->findFirstElement("body")).second.height());
660                QVector<p_t> local;
661                QVector<p_t> external;
662                QHash<QString, QWebElement> anchors;
663                findLinks(header->mainFrame(), local, external, anchors);
664                foreach (const p_t & p, local) {
665                        QRectF r = wp.elementLocation(p.first).second;
666                        painter->addLink(r, p.second);
667                }
668                foreach (const p_t & p, external) {
669                        QRectF r = wp.elementLocation(p.first).second;
670                        painter->addHyperlink(r, QUrl(p.second));
671                }
672                wp.spoolPage(1);
673        // restore margins
674        printer->setPageMargins(leftMargin, topMargin, rightMargin, bottomMargin, settings.margin.left.second);
675                painter->restore();
676        }
677
678        if (currentFooter) {
679                QWebPage * footer=currentFooter;
680                updateWebSettings(footer->settings(), object.settings.web);
681                painter->save();
682                painter->resetTransform();
683                double spacing = s.footer.spacing * printer->height() / printer->heightMM();
684                painter->translate(0, printer->height()+ spacing);
685        // clear vertical margins for proper header rendering
686        printer->setPageMargins(leftMargin, 0, rightMargin, 0, settings.margin.left.second);
687
688                QWebPrinter wp(footer->mainFrame(), printer, *painter);
689
690                QVector<p_t> local;
691                QVector<p_t> external;
692                QHash<QString, QWebElement> anchors;
693                findLinks(footer->mainFrame(), local, external, anchors);
694                foreach (const p_t & p, local) {
695                        QRectF r = wp.elementLocation(p.first).second;
696                        painter->addLink(r, p.second);
697                }
698                foreach (const p_t & p, external) {
699                        QRectF r = wp.elementLocation(p.first).second;
700                        painter->addHyperlink(r, QUrl(p.second));
701                }
702                wp.spoolPage(1);
703        // restore margins
704        printer->setPageMargins(leftMargin, topMargin, rightMargin, bottomMargin, settings.margin.left.second);
705                painter->restore();
706        }
707
708}
709
710void PdfConverterPrivate::handleTocPage(PageObject & obj) {
711        painter->save();
712        QWebPrinter wp(obj.page->mainFrame(), printer, *painter);
713        int pc = obj.settings.pagesCount? wp.pageCount(): 0;
714        if (pc != obj.pageCount) {
715                obj.pageCount = pc;
716                tocChanged=true;
717        }
718        pageCount += obj.pageCount;
719        tocChanged = outline->replaceWebPage(obj.number, obj.settings.toc.captionText, wp, obj.page->mainFrame(), obj.settings, obj.localLinks, obj.anchors) || tocChanged;
720        painter->restore();
721}
722#endif
723
724
725void PdfConverterPrivate::tocLoaded(bool ok) {
726#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
727        if (errorCode == 0) errorCode = tocLoader->httpErrorCode();
728#endif
729        if (!ok) {
730                fail();
731                return;
732        }
733#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
734        tocChanged = false;
735        pageCount = 0;
736        currentObject = 0;
737        for (int d=0; d < objects.size(); ++d) {
738                ++currentObject;
739                if (!objects[d].loaderObject || objects[d].loaderObject->skip) continue;
740                if (!objects[d].settings.isTableOfContent) {
741                        pageCount += objects[d].pageCount;
742                        continue;
743                }
744                handleTocPage(objects[d]);
745        }
746
747        actualPages = pageCount * settings.copies;
748        if (tocChanged)
749                loadTocs();
750        else {
751                //Find and resolve all local links
752                currentPhase = 3;
753                emit out.phaseChanged();
754
755                QHash<QString, int> urlToDoc;
756                for (int d=0; d < objects.size(); ++d) {
757                        if (!objects[d].loaderObject || objects[d].loaderObject->skip) continue;
758                        if (objects[d].settings.isTableOfContent) continue;
759                        urlToPageObj[ QUrl::fromPercentEncoding(objects[d].page->mainFrame()->url().toString(QUrl::RemoveFragment).toLocal8Bit()) ] = &objects[d];
760                }
761
762                for (int d=0; d < objects.size(); ++d) {
763                        if (!objects[d].loaderObject || objects[d].loaderObject->skip) continue;
764                        progressString = QString("Object ")+QString::number(d+1)+QString(" of ")+QString::number(objects.size());
765                        emit out.progressChanged((d+1)*100 / objects.size());
766                        findLinks(objects[d].page->mainFrame(), objects[d].localLinks, objects[d].externalLinks, objects[d].anchors );
767                }
768
769                loadHeaders();
770        }
771#endif
772}
773
774
775void PdfConverterPrivate::measuringHeadersLoaded(bool ok) {
776#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
777    if (errorCode == 0) errorCode = measuringHFLoader.httpErrorCode();
778#endif
779    if (!ok) {
780        fail();
781        return;
782    }
783
784#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
785    for (int d=0; d < objects.size(); ++d) {
786        PageObject & obj = objects[d];
787        if (obj.measuringHeader) {
788            // add spacing to prevent moving header out of page
789            obj.headerReserveHeight = calculateHeaderHeight(obj, *obj.measuringHeader) + obj.settings.header.spacing;
790        }
791
792        if (obj.measuringFooter) {
793            // add spacing to prevent moving footer out of page
794            obj.footerReserveHeight = calculateHeaderHeight(obj, *obj.measuringFooter) + obj.settings.footer.spacing;
795        }
796    }
797#endif
798
799    pageLoader.load();
800}
801
802void PdfConverterPrivate::headersLoaded(bool ok) {
803#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
804        if (errorCode == 0) errorCode = hfLoader.httpErrorCode();
805#endif
806        if (!ok) {
807                fail();
808                return;
809        }
810        printDocument();
811}
812
813#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
814
815void PdfConverterPrivate::spoolPage(int page) {
816        progressString = QString("Page ") + QString::number(actualPage) + QString(" of ") + QString::number(actualPages);
817        emit out.progressChanged(actualPage * 100 / actualPages);
818        if (actualPage != 1)
819                printer->newPage();
820
821        webPrinter->spoolPage(page+1);
822        foreach (QWebElement elm, pageFormElements[page+1]) {
823                QString type = elm.attribute("type");
824                QString tn = elm.tagName();
825                QString name = elm.attribute("name");
826                if (tn == "TEXTAREA" || type == "text" || type == "password") {
827                        painter->addTextField(
828                                webPrinter->elementLocation(elm).second,
829                                tn == "TEXTAREA"?elm.toPlainText():elm.attribute("value"),
830                                name,
831                                tn == "TEXTAREA",
832                                type == "password",
833                                elm.evaluateJavaScript("this.readonly;").toBool(),
834                                elm.hasAttribute("maxlength")?elm.attribute("maxlength").toInt():-1
835                                );
836                } else if (type == "checkbox") {
837                        painter->addCheckBox(
838                                webPrinter->elementLocation(elm).second,
839                                elm.evaluateJavaScript("this.checked;").toBool(),
840                                name,
841                                elm.evaluateJavaScript("this.readonly;").toBool());
842                }
843        }
844        for (QHash<QString, QWebElement>::iterator i=pageAnchors[page+1].begin();
845                 i != pageAnchors[page+1].end(); ++i) {
846                QRectF r = webPrinter->elementLocation(i.value()).second;
847                painter->addAnchor(r, i.key());
848        }
849        for (QVector< QPair<QWebElement,QString> >::iterator i=pageLocalLinks[page+1].begin();
850                 i != pageLocalLinks[page+1].end(); ++i) {
851                QRectF r = webPrinter->elementLocation(i->first).second;
852                painter->addLink(r, i->second);
853        }
854        for (QVector< QPair<QWebElement,QString> >::iterator i=pageExternalLinks[page+1].begin();
855                 i != pageExternalLinks[page+1].end(); ++i) {
856                QRectF r = webPrinter->elementLocation(i->first).second;
857                painter->addHyperlink(r, QUrl(i->second));
858        }
859        endPage(objects[currentObject], pageHasHeaderFooter, page, pageNumber);
860        actualPage++;
861}
862
863void PdfConverterPrivate::spoolTo(int page) {
864        int pc=settings.collate?1:settings.copies;
865        const settings::PdfObject & ps = objects[currentObject].settings;
866        while (objectPage < page) {
867                for (int pc_=0; pc_ < pc; ++pc_)
868                        spoolPage(objectPage);
869                if (ps.pagesCount) ++pageNumber;
870                ++objectPage;
871
872                //TODO free header and footer
873                currentHeader=NULL;
874                currentFooter=NULL;
875        }
876}
877
878void PdfConverterPrivate::beginPrintObject(PageObject & obj) {
879        if (obj.number != 0)
880                endPrintObject(objects[obj.number-1]);
881        currentObject = obj.number;
882
883        if (!obj.loaderObject || obj.loaderObject->skip) return;
884
885        QPalette pal = obj.loaderObject->page.palette();
886        pal.setBrush(QPalette::Base, Qt::transparent);
887        obj.loaderObject->page.setPalette(pal);
888
889        const settings::PdfObject & ps = obj.settings;
890        pageHasHeaderFooter = ps.header.line || ps.footer.line ||
891                !ps.header.left.isEmpty() || !ps.footer.left.isEmpty() ||
892                !ps.header.center.isEmpty() || !ps.footer.center.isEmpty() ||
893                !ps.header.right.isEmpty() || !ps.footer.right.isEmpty();
894        painter->save();
895
896        if (ps.produceForms) {
897                foreach (QWebElement elm, obj.page->mainFrame()->findAllElements("input"))
898                        elm.setStyleProperty("color","white");
899                foreach (QWebElement elm, obj.page->mainFrame()->findAllElements("textarea"))
900                        elm.setStyleProperty("color","white");
901        }
902
903        //output
904        webPrinter = new QWebPrinter(obj.page->mainFrame(), printer, *painter);
905        QString l1=obj.page->mainFrame()->url().path().split("/").back()+"#";
906        QString l2=obj.page->mainFrame()->url().toString() + "#";
907
908        outline->fillAnchors(obj.number, obj.anchors);
909
910        //Sort anchors and links by page
911        for (QHash<QString, QWebElement>::iterator i=obj.anchors.begin();
912                 i != obj.anchors.end(); ++i)
913                pageAnchors[webPrinter->elementLocation(i.value()).first][i.key()] = i.value();
914
915        for (QVector< QPair<QWebElement,QString> >::iterator i=obj.localLinks.begin();
916                 i != obj.localLinks.end(); ++i)
917                pageLocalLinks[webPrinter->elementLocation(i->first).first].push_back(*i);
918
919        for (QVector< QPair<QWebElement,QString> >::iterator i=obj.externalLinks.begin();
920                 i != obj.externalLinks.end(); ++i)
921                pageExternalLinks[webPrinter->elementLocation(i->first).first].push_back(*i);
922
923        if (ps.produceForms) {
924                foreach (const QWebElement & elm, obj.page->mainFrame()->findAllElements("input"))
925                        pageFormElements[webPrinter->elementLocation(elm).first].push_back(elm);
926                foreach (const QWebElement & elm, obj.page->mainFrame()->findAllElements("textarea"))
927                        pageFormElements[webPrinter->elementLocation(elm).first].push_back(elm);
928        }
929        emit out.producingForms(obj.settings.produceForms);
930        out.emitCheckboxSvgs(obj.settings.load);
931
932        objectPage = 0;
933}
934
935
936void PdfConverterPrivate::handleHeader(QWebPage * frame, int page) {
937        spoolTo(page);
938        currentHeader = frame;
939}
940
941void PdfConverterPrivate::handleFooter(QWebPage * frame, int page) {
942        spoolTo(page);
943        currentFooter = frame;
944}
945
946void PdfConverterPrivate::endPrintObject(PageObject & obj) {
947        Q_UNUSED(obj);
948        // If this page was skipped, we might not have
949        // anything to spool to printer..
950        if (webPrinter != 0) spoolTo(webPrinter->pageCount());
951
952        pageAnchors.clear();
953        pageLocalLinks.clear();
954        pageExternalLinks.clear();
955        pageFormElements.clear();
956
957        if (webPrinter != 0) {
958                QWebPrinter *tmp = webPrinter;
959                webPrinter = 0;
960                delete tmp;
961
962                painter->restore();
963        }
964
965}
966
967#endif
968
969void PdfConverterPrivate::printDocument() {
970#ifndef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
971        currentPhase = 1;
972        emit out.phaseChanged();
973        objects[0].page->mainFrame()->print(printer);
974        progressString = "";
975        emit out.progressChanged(-1);
976#else
977        actualPage=1;
978
979        int cc=settings.collate?settings.copies:1;
980
981
982        currentPhase = 5;
983        emit out.phaseChanged();
984
985        progressString = "Preparing";
986        emit out.progressChanged(0);
987
988        for (int cc_=0; cc_ < cc; ++cc_) {
989                pageNumber=1;
990                for (int d=0; d < objects.size(); ++d) {
991                        beginPrintObject(objects[d]);
992                        // XXX: In some cases nothing gets loaded at all,
993                        //      so we would get no webPrinter instance.
994                        int pageCount = webPrinter != 0 ? webPrinter->pageCount() : 0;
995                        //const settings::PdfObject & ps = objects[d].settings;
996
997                        for(int i=0; i < pageCount; ++i) {
998                                if (!objects[d].headers.empty())
999                                        handleHeader(objects[d].headers[i], i);
1000                                if (!objects[d].footers.empty())
1001                                        handleFooter(objects[d].footers[i], i);
1002                        }
1003
1004                }
1005                endPrintObject(objects[objects.size()-1]);
1006        }
1007        outline->printOutline(printer);
1008
1009        if (!settings.dumpOutline.isEmpty()) {
1010                StreamDumper sd(settings.dumpOutline);
1011                outline->dump(sd.stream);
1012        }
1013
1014        painter->end();
1015#endif
1016        if (settings.out == "-" && lout != "/dev/stdout") {
1017                QFile i(lout);
1018                QFile o;
1019#ifdef Q_OS_WIN32
1020                _setmode(_fileno(stdout), _O_BINARY);
1021#endif
1022                if ( !i.open(QIODevice::ReadOnly) ||
1023                        !o.open(stdout,QIODevice::WriteOnly) ||
1024                        !MultiPageLoader::copyFile(i,o) ) {
1025                        emit out.error("Count not write to stdout");
1026                        tempOut.removeAll();
1027                        fail();
1028                        return;
1029                }
1030                tempOut.removeAll();
1031        }
1032
1033        if (settings.out.isEmpty()) {
1034                QFile i(lout);
1035                if (!i.open(QIODevice::ReadOnly)) {
1036                        emit out.error("Reading output failed");
1037                        tempOut.removeAll();
1038                        fail();
1039                }
1040                outputData = i.readAll();
1041                tempOut.removeAll();
1042        }
1043        clearResources();
1044#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
1045        currentPhase = 6;
1046#else
1047        currentPhase = 2;
1048#endif
1049        emit out.phaseChanged();
1050        convertionDone = true;
1051        emit out.finished(true);
1052
1053        qApp->exit(0); // quit qt's event handling
1054}
1055
1056#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
1057QWebPage * PdfConverterPrivate::loadHeaderFooter(QString url, const QHash<QString, QString> & parms, const settings::PdfObject & ps) {
1058        QUrl u = MultiPageLoader::guessUrlFromString(url);
1059        for (QHash<QString, QString>::const_iterator i=parms.begin(); i != parms.end(); ++i)
1060                u.addQueryItem(i.key(), i.value());
1061        return &hfLoader.addResource(u, ps.load)->page;
1062
1063}
1064
1065/*!
1066 * Replace some variables in a string used in a header or footer
1067 * \param q the string to substitute in
1068 */
1069QString PdfConverterPrivate::hfreplace(const QString & q, const QHash<QString, QString> & parms) {
1070        QString r=q;
1071        for (QHash<QString, QString>::const_iterator i=parms.begin(); i != parms.end(); ++i)
1072                r=r.replace("["+i.key()+"]", i.value(), Qt::CaseInsensitive);
1073        return r;
1074}
1075#endif
1076
1077void PdfConverterPrivate::clearResources() {
1078        objects.clear();
1079        pageLoader.clearResources();
1080#ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__
1081        hfLoader.clearResources();
1082        tocLoader1.clearResources();
1083        tocLoader2.clearResources();
1084
1085        if (outline) {
1086                Outline * tmp = outline;
1087                outline = 0;
1088                delete tmp;
1089        }
1090
1091#endif
1092
1093        if (printer) {
1094                QPrinter * tmp = printer;
1095                printer = 0;
1096                delete tmp;
1097        }
1098
1099        if (painter) {
1100                QPainter * tmp = painter;
1101                painter = 0;
1102                delete tmp;
1103        }
1104}
1105
1106Converter & PdfConverterPrivate::outer() {
1107        return out;
1108}
1109
1110/*!
1111  \class PdfConverter
1112  \brief Class responsible for converting html pages to pdf
1113  \todo explain something about the conversion process here, and mention stages
1114*/
1115
1116/*!
1117  \brief Create a page converter object based on the supplied settings
1118  \param settings Settings for the conversion
1119*/
1120PdfConverter::PdfConverter(settings::PdfGlobal & settings):
1121        d(new PdfConverterPrivate(settings, *this)) {
1122}
1123
1124/*!
1125  \brief The destructor for the page converter object
1126*/
1127PdfConverter::~PdfConverter() {
1128        PdfConverterPrivate *tmp = d;
1129        d = 0;
1130        tmp->deleteLater();;
1131}
1132
1133/*!
1134  \brief add a resource we want to convert
1135  \param url The url of the object we want to convert
1136*/
1137void PdfConverter::addResource(const settings::PdfObject & page, const QString * data) {
1138  d->objects.push_back( PageObject(page, data) );
1139  d->objects.back().number = d->objects.size()-1;
1140}
1141
1142const QByteArray & PdfConverter::output() {
1143  return d->outputData;
1144}
1145
1146
1147/*!
1148  \brief Returns the settings object associated with the page converter
1149*/
1150const settings::PdfGlobal & PdfConverter::globalSettings() const {
1151        return d->settings;
1152}
1153
1154
1155/*!
1156  \fn PdfConverter::warning(const QString & message)
1157  \brief Signal emitted when some non fatal warning occurs during conversion
1158  \param message The warning message
1159*/
1160
1161/*!
1162  \fn PdfConverter::error(const QString & message)
1163  \brief Signal emitted when a fatal error has occurred during conversion
1164  \param message A message describing the fatal error
1165*/
1166
1167/*!
1168  \fn PdfConverter::phaseChanged()
1169  \brief Signal emitted when the converter has reached a new phase
1170*/
1171
1172/*!
1173  \fn PdfConverter::progressChanged()
1174  \brief Signal emitted when some progress has been done in the conversion phase
1175*/
1176
1177/*!
1178  \fn PdfConverter::finised()
1179  \brief Signal emitted when conversion has finished.
1180*/
1181
1182
1183ConverterPrivate & PdfConverter::priv() {
1184        return *d;
1185}
Note: See TracBrowser for help on using the repository browser.