// -*- mode: c++; tab-width: 4; indent-tabs-mode: t; eval: (progn (c-set-style "stroustrup") (c-set-offset 'innamespace 0)); -*- // vi:set ts=4 sts=4 sw=4 noet : // // Copyright 2010 wkhtmltopdf authors // // This file is part of wkhtmltopdf. // // wkhtmltopdf is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // wkhtmltopdf is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with wkhtmltopdf. If not, see . #include "outline_p.hh" #include #include using namespace std; #ifdef __EXTENSIVE_WKHTMLTOPDF_QT_HACK__ namespace wkhtmltopdf { /*! \file outline_p.hh \brief Defines the classes OutlinePrivate and OutlineItem */ /*! \class OutlineItem \brief Class describing an item in the outline */ OutlineItem::OutlineItem(): parent(NULL), page(-1), display(true), forwardLinks(false), backLinks(false) {} /*! \brief Recursivily delete the subtree */ OutlineItem::~OutlineItem() { foreach (OutlineItem * i, children) delete i; } void OutlineItem::fillAnchors(const OutlineItem * other, int & anchorCounter, QVector > & local, QHash & anchors) { if (!other || other->children.size() != children.size() || other->document != document || other->value != value || other->display != display) other=0; if (other) { anchor = other->anchor; tocAnchor = other->anchor; } else { anchor = QString("__WKANCHOR_")+QString::number(anchorCounter++,36); tocAnchor = QString("__WKANCHOR_")+QString::number(anchorCounter++,36); } if (forwardLinks) anchors[anchor] = element; if (backLinks) local.push_back( QPair(element, tocAnchor) ); for (int i=0; i < children.size(); ++i) children[i]->fillAnchors(other?other->children[i]:0, anchorCounter, local, anchors); } bool OutlineItem::differentFrom(const OutlineItem * other) const { if (other->children.size() != children.size() || other->page != page || other->document != document || other->value != value || other->display != display) return true; for (int i=0; i < children.size(); ++i) if (children[i]->differentFrom(other->children[i])) return true; return false; } /*! \class OutlinePrivate \brief Class providing implementation details of Outline */ OutlinePrivate::OutlinePrivate(const settings::PdfGlobal & s): settings(s), pageCount(0), anchorCounter(0) { } OutlinePrivate::~OutlinePrivate() { foreach (OutlineItem * i, documentOutlines) delete i; } void OutlinePrivate::fillChildAnchors(OutlineItem * item, QHash & anchors) { foreach (OutlineItem * i, item->children) { if (i->anchor.isEmpty()) continue; anchors[i->anchor] = i->element; fillChildAnchors(i, anchors); } } void OutlinePrivate::outlineChildren(OutlineItem * item, QPrinter * printer, int level) { if (level + 1 > settings.outlineDepth) return; foreach (OutlineItem * i, item->children) { printer->beginSectionOutline(i->value, i->anchor); outlineChildren(i, printer, level+1); printer->endSectionOutline(); } } QString escape(QString str) { return str.replace('&',"&") .replace('<',"<") .replace('>',">") .replace('"',""") .replace('\'',"'"); } void OutlinePrivate::dumpChildren(QTextStream & stream, const QList & items, int level) const { foreach (OutlineItem * item, items) { for (int i=0; i < level; ++i) stream << " "; stream << "value) << "\" page=\"" << (item->page + prefixSum[item->document]+ settings.pageOffset) << "\" link=\"" << escape(item->anchor) << "\" backLink=\"" << escape(item->tocAnchor) << "\""; if (item->children.empty()) stream << "/>" << endl; else { stream << ">" << endl; dumpChildren(stream, item->children, level+1); for (int i=0; i < level; ++i) stream << " "; stream << "" << endl; } } } void OutlinePrivate::buildPrefixSum() { prefixSum.clear(); prefixSum.push_back(0); foreach (int x, documentPages) prefixSum.push_back( prefixSum.back() + x); } void Outline::dump(QTextStream & stream) const { d->buildPrefixSum(); stream << "" << endl; stream << "" << endl; d->dumpChildren(stream, d->documentOutlines, 1); stream << "" << endl; } /*! \file outline.hh \brief Defines the Outline class */ /*! \class Outline \brief Class responsible for building and keeping an outline of a document. */ /*! \brief Construct a new outline class \param settings The settings to use */ Outline::Outline(const settings::PdfGlobal & settings): d(new OutlinePrivate(settings)) {} Outline::~Outline() {delete d;} /*! \brief Replace a webpage in the outline \param document The number of the webpage \param name The name of the webpage \param wp A webprinter for the page \param frame The frame containing the webpage */ bool Outline::replaceWebPage(int document, const QString & name, QWebPrinter & wp, QWebFrame * frame, const settings::PdfObject & ps, QVector > & local, QHash & anchors) { QMap< QPair >, QWebElement> headings; foreach (const QWebElement & e, frame->findAllElements("h1,h2,h3,h4,h5,h6,h7,h8,h9")) { QPair location = wp.elementLocation(e); headings[ qMakePair(location.first, qMakePair(location.second.y(), location.second.x()) ) ] = e; } //This heuristic is a little strange, it tries to create a real tree, //even though someone puts a h5 below a h1 or stuff like that //The way this is handled is having a level stack, indicating what h-tags //a level level in the tree currently represents QVector levelStack; levelStack.push_back(0); OutlineItem * root = new OutlineItem(); root->page = 0; root->document = document; root->value = name; root->display = true; OutlineItem * old = root; for (QMap< QPair >, QWebElement>::iterator i = headings.begin(); i != headings.end(); ++i) { const QWebElement & element = i.value(); uint level = element.tagName().mid(1).toInt(); QString value = element.toPlainText().replace("\n", " ").trimmed(); if (i.key().first == -1 || value == "") continue; OutlineItem * item = new OutlineItem(); item->page = i.key().first; item->document = document; item->value = value; item->element = element; item->forwardLinks = ps.toc.forwardLinks; item->backLinks = ps.toc.backLinks; while (levelStack.back() >= level) { old = old->parent; levelStack.pop_back(); } item->parent = old; old->children.push_back(item); old = item; levelStack.push_back(level); } root->fillAnchors(d->documentOutlines[document], d->anchorCounter, local, anchors); bool changed=d->documentOutlines[document]->differentFrom(root); delete d->documentOutlines[document]; d->documentOutlines[document] = root; if (d->documentPages[document] != wp.pageCount()) { d->pageCount -= d->documentPages[document]; d->documentPages[document] = wp.pageCount(); d->pageCount += d->documentPages[document]; changed=true; } return changed; } /*! \brief Add a new webpage to the outline \param name The name of the webpage \param wp A webprinter for the page \param frame The frame containing the webpage */ void Outline::addWebPage(const QString & name, QWebPrinter & wp, QWebFrame * frame, const settings::PdfObject & ps, QVector > & local, QHash & anchors) { Q_UNUSED(name); addEmptyWebPage(); replaceWebPage(d->documentOutlines.size()-1, name, wp, frame, ps, local, anchors); } void Outline::addEmptyWebPage() { OutlineItem * root = new OutlineItem(); root->page = 0; root->document = d->documentPages.size(); root->value = ""; root->display = true; d->documentOutlines.push_back(root); d->pageCount += 1; d->documentPages.push_back(1); } void OutlinePrivate::buildHFCache(OutlineItem * i, int level) { buildPrefixSum(); if (level >= hfCache.size()) return; foreach (OutlineItem * j, i->children) { int page = j->page + prefixSum[j->document]; while (hfCache[level].size() < page) hfCache[level].push_back(hfCache[level].back()); if (hfCache[level].size() == page) hfCache[level].push_back(j); buildHFCache(j, level+1); } } /*! \brief Fill in header footer parameters for a given page \param page The page to fill in for \param parms The structure to fill */ void Outline::fillHeaderFooterParms(int page, QHash & parms, const settings::PdfObject & ps) { //Build hfcache if (d->hfCache.size() == 0) { for (int i=0; i < 3; ++i) { QList< OutlineItem *> x; x.push_back(NULL); d->hfCache.push_back(x); } foreach (OutlineItem * i, d->documentOutlines) d->buildHFCache(i, 0); } for (int i=0; i < 3; ++i) while (d->hfCache[i].size() <= page) d->hfCache[i].push_back(d->hfCache[i].back()); int off = d->settings.pageOffset; typedef QPair SP; foreach (const SP & rep, ps.replacements) parms[rep.first] = rep.second; parms["frompage"] = QString::number(off+1); parms["topage"] = QString::number(off+d->pageCount); parms["page" ] = QString::number(page+off); parms["webpage"] = ps.page; parms["section" ] = d->hfCache[0][page]?d->hfCache[0][page]->value:QString(""); parms["subsection" ] = d->hfCache[1][page]?d->hfCache[1][page]->value:QString(""); parms["subsubsection" ] = d->hfCache[2][page]?d->hfCache[2][page]->value:QString(""); } /*! \brief Fill in anchor as to add to a given document \param doc The 0 indexed document number (in order of addWebPage) \param anchors The structure to fill */ void Outline::fillAnchors(int doc, QHash & anchors) { if (doc < 0 || doc >= d->documentOutlines.size()) return; d->fillChildAnchors( d->documentOutlines[doc], anchors ); } /*! \brief return the number of pages in the outlined document */ int Outline::pageCount() { return d->pageCount; } /*! \brief Print the document outline to a given printer \param printer The printer to print to */ void Outline::printOutline(QPrinter * printer) { if (!d->settings.outline) return; foreach (OutlineItem * i, d->documentOutlines) d->outlineChildren(i, printer, 0); } } #endif //__EXTENSIVE_WKHTMLTOPDF_QT_HACK__