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 }