File indexing completed on 2025-01-26 04:24:56

0001 /* Copyright 2015 Robert Schroll
0002  *
0003  * This file is part of Beru and is distributed under the terms of
0004  * the GPL. See the file COPYING for full details.
0005  */
0006 
0007 #include "pdfreader.h"
0008 #include <QJsonObject>
0009 #include <QJsonArray>
0010 #include <QJsonDocument>
0011 #include <QtGui/QImage>
0012 #include <QBuffer>
0013 #include <QDir>
0014 #include <QCryptographicHash>
0015 #include "quazip/quazip.h"
0016 #include "quazip/quazipfile.h"
0017 #include "../qhttpserver/qhttpresponse.h"
0018 
0019 QString guessMimeType(const QString &filename);
0020 
0021 PDFReader::PDFReader(QObject *parent) :
0022     QObject(parent)
0023 {
0024     this->pdf = NULL;
0025 }
0026 
0027 bool PDFReader::load(const QString &filename)
0028 {
0029     if (this->pdf != NULL) {
0030         delete this->pdf;
0031         this->pdf = NULL;
0032     }
0033     this->_hash = "";
0034     this->spine.clear();
0035     this->metadata.clear();
0036 
0037     this->pdf = Poppler::Document::load(filename.toLatin1());
0038     if (this->pdf == NULL)
0039         return false;
0040     if (!this->parse()) {
0041         delete this->pdf;
0042         this->pdf = NULL;
0043         return false;
0044     }
0045     this->computeHash(filename);
0046     this->readMetadata();
0047     this->pdf->setRenderHint(Poppler::Document::Antialiasing, true);
0048     this->pdf->setRenderHint(Poppler::Document::TextAntialiasing, true);
0049     return true;
0050 }
0051 
0052 bool PDFReader::parse() {
0053     int n = this->pdf->numPages();
0054     for (int i=0; i<n; i++)
0055         this->spine.append(QString::number(i + 1));
0056     return true;
0057 }
0058 
0059 void PDFReader::readMetadata() {
0060     QStringList keys = this->pdf->infoKeys();
0061     foreach (QString k, keys) {
0062         QString value = this->pdf->info(k);
0063         if (value != "")
0064             this->metadata[k.toLower()] = value;
0065     }
0066 }
0067 
0068 QString PDFReader::hash() {
0069     return this->_hash;
0070 }
0071 
0072 void PDFReader::computeHash(const QString &filename) {
0073     // Doing a MD5 hash of the whole file can take a while, so we only
0074     // do the first 10 kB.  Hopefully that's enough to be unique.
0075     QFile file(filename);
0076     if (file.open(QFile::ReadOnly)) {
0077         QByteArray data = file.read(10 * 1024);
0078         QCryptographicHash hash(QCryptographicHash::Md5);
0079         hash.addData(data);
0080         this->_hash = hash.result().toHex();
0081     }
0082 }
0083 
0084 QString PDFReader::title() {
0085     return this->metadata.contains("title") ? this->metadata["title"].toString() : "";
0086 }
0087 
0088 int PDFReader::height() {
0089     return this->_height;
0090 }
0091 
0092 void PDFReader::setHeight(int value) {
0093     this->_height = value;
0094 }
0095 
0096 int PDFReader::width() {
0097     return this->_width;
0098 }
0099 
0100 void PDFReader::setWidth(int value) {
0101     this->_width = value;
0102 }
0103 
0104 QImage PDFReader::renderPage(int pageNum, int maxWidth, int maxHeight) {
0105     Poppler::Page *page = this->pdf->page(pageNum);
0106     QSizeF pageSize = page->pageSizeF();
0107     qreal pageWidth = pageSize.width(), pageHeight = pageSize.height();
0108     double res;
0109     if (maxWidth == -1 && maxHeight == -1) {
0110         maxWidth = this->_width;
0111         maxHeight = this->_height;
0112     }
0113     if (maxHeight == -1) {
0114         res = maxWidth / pageWidth * 72;
0115     } else if (maxWidth == -1) {
0116         res = maxHeight / pageHeight * 72;
0117     } else {
0118         if ((double) pageWidth / pageHeight > maxWidth / maxHeight)
0119             res = maxWidth / pageWidth * 72;
0120         else
0121             res = maxHeight / pageHeight * 72;
0122     }
0123     return page->renderToImage(res, res);
0124 }
0125 
0126 void PDFReader::serveComponent(const QString &filename, QHttpResponse *response)
0127 {
0128     if (!this->pdf) {
0129         response->writeHead(500);
0130         response->end("PDF file not open for reading");
0131         return;
0132     }
0133 
0134     bool success;
0135     int pageNum = filename.toInt(&success) - 1;
0136     if (!success || pageNum >= this->pdf->numPages() || pageNum < 0) {
0137         response->writeHead(404);
0138         response->end("Could not find page" + filename + " in pdf file");
0139         return;
0140     }
0141 
0142     QImage pageImage = this->renderPage(pageNum, -1, -1);
0143     QByteArray byteArray;
0144     QBuffer buffer(&byteArray);
0145     pageImage.save(&buffer, "PNG");
0146 
0147     response->setHeader("Content-Type", guessMimeType("png"));
0148     response->writeHead(200);
0149     // Important -- use write instead of end, so binary data doesn't get messed up!
0150     response->write(byteArray);
0151     response->end();
0152 }
0153 
0154 QVariantList PDFReader::getContents() {
0155     QVariantList res;
0156     QDomDocument *toc = this->pdf->toc();
0157     if (toc) {
0158         res = this->parseContents(toc->firstChildElement());
0159     } else {
0160         for (int i=0; i<this->spine.length(); i++) {
0161             QVariantMap entry;
0162             entry["title"] = "%PAGE% " + QString::number(i + 1);
0163             entry["src"] = this->spine[i];
0164             res.append(entry);
0165         }
0166     }
0167     Q_EMIT contentsReady(res);
0168     return res;
0169 }
0170 
0171 QVariantList PDFReader::parseContents(QDomElement el) {
0172     QVariantList res;
0173     while (!el.isNull()) {
0174         QString title = el.tagName();
0175         Poppler::LinkDestination *destination = NULL;
0176         if (el.hasAttribute("Destination")) {
0177             destination = new Poppler::LinkDestination(el.attribute("Destination"));
0178         } else if (el.hasAttribute("DestinationName")) {
0179             destination = this->pdf->linkDestination(el.attribute("DestinationName"));
0180         }
0181         if (destination) {
0182             QVariantMap entry;
0183             entry["title"] = title;
0184             entry["src"] = QString::number(destination->pageNumber());
0185             QDomElement child = el.firstChildElement();
0186             if (!child.isNull())
0187                 entry["children"] = this->parseContents(child);
0188             res.append(entry);
0189         }
0190         el = el.nextSiblingElement();
0191     }
0192     return res;
0193 }
0194 
0195 void PDFReader::serveBookData(QHttpResponse *response)
0196 {
0197     if (!this->pdf) {
0198         response->writeHead(500);
0199         response->end("PDF file not open for reading");
0200         return;
0201     }
0202 
0203     response->setHeader("Content-Type", guessMimeType("js"));
0204     response->writeHead(200);
0205     QJsonDocument spine(QJsonArray::fromStringList(this->spine));
0206     QJsonDocument contents(QJsonArray::fromVariantList(this->getContents()));
0207     QJsonDocument metadata(QJsonObject::fromVariantMap(this->metadata));
0208     QString res = "var bookData = {" \
0209             "getComponents: function () { return %1; }, " \
0210             "getContents:   function () { return %2; }, " \
0211             "getComponent:  function (component) { return " \
0212             "\"<img style='display: block; margin: auto; max-height: 100% !important' src='\"" \
0213             "+ component + \"' />\"; }, " \
0214             "getMetaData:   function (key) { return %3[key]; } }";
0215     response->write(res.arg(QString(spine.toJson()), QString(contents.toJson()),
0216                             QString(metadata.toJson())));
0217     response->end();
0218 }
0219 
0220 QVariantMap PDFReader::getCoverInfo(int thumbsize, int fullsize)
0221 {
0222     QVariantMap res;
0223     if (!this->pdf)
0224         return res;
0225 
0226     res["title"] = this->metadata.contains("title") ? this->metadata["title"] : "ZZZnone";
0227     res["author"] = this->metadata.contains("author") ? this->metadata["author"] : "";
0228     res["authorsort"] = "zzznone";
0229     res["cover"] = "ZZZnone";
0230 
0231     QImage coverimg = this->renderPage(0, thumbsize, -1);
0232     QByteArray byteArray;
0233     QBuffer buffer(&byteArray);
0234     coverimg.save(&buffer, "PNG");
0235     res["cover"] = "data:image/png;base64," + QString(byteArray.toBase64());
0236 
0237     QImage coverimgf = this->renderPage(0, fullsize, -1);
0238     QByteArray byteArrayf;
0239     QBuffer bufferf(&byteArrayf);
0240     coverimgf.save(&bufferf, "PNG");
0241     res["fullcover"] = "data:image/png;base64," + QString(byteArrayf.toBase64());
0242     return res;
0243 }