File indexing completed on 2024-05-12 16:06:48

0001 /*
0002     SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "generator_tiff.h"
0008 
0009 #include <QBuffer>
0010 #include <QDateTime>
0011 #include <QFile>
0012 #include <QFileInfo>
0013 #include <QImage>
0014 #include <QList>
0015 #include <QPainter>
0016 #include <QPrinter>
0017 
0018 #include <KAboutData>
0019 #include <KLocalizedString>
0020 #include <QDebug>
0021 
0022 #include <core/document.h>
0023 #include <core/fileprinter.h>
0024 #include <core/page.h>
0025 #include <core/utils.h>
0026 
0027 #include <tiff.h>
0028 #include <tiffio.h>
0029 
0030 #define TiffDebug 4714
0031 
0032 tsize_t okular_tiffReadProc(thandle_t handle, tdata_t buf, tsize_t size)
0033 {
0034     QIODevice *device = static_cast<QIODevice *>(handle);
0035     return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
0036 }
0037 
0038 tsize_t okular_tiffWriteProc(thandle_t handle, tdata_t buf, tsize_t size)
0039 {
0040     QIODevice *device = static_cast<QIODevice *>(handle);
0041     return device->write(static_cast<char *>(buf), size);
0042 }
0043 
0044 toff_t okular_tiffSeekProc(thandle_t handle, toff_t offset, int whence)
0045 {
0046     QIODevice *device = static_cast<QIODevice *>(handle);
0047     switch (whence) {
0048     case SEEK_SET:
0049         device->seek(offset);
0050         break;
0051     case SEEK_CUR:
0052         device->seek(device->pos() + offset);
0053         break;
0054     case SEEK_END:
0055         device->seek(device->size() + offset);
0056         break;
0057     }
0058 
0059     return device->pos();
0060 }
0061 
0062 int okular_tiffCloseProc(thandle_t handle)
0063 {
0064     Q_UNUSED(handle)
0065     return 0;
0066 }
0067 
0068 toff_t okular_tiffSizeProc(thandle_t handle)
0069 {
0070     QIODevice *device = static_cast<QIODevice *>(handle);
0071     return device->size();
0072 }
0073 
0074 int okular_tiffMapProc(thandle_t, tdata_t *, toff_t *)
0075 {
0076     return 0;
0077 }
0078 
0079 void okular_tiffUnmapProc(thandle_t, tdata_t, toff_t)
0080 {
0081 }
0082 
0083 class TIFFGenerator::Private
0084 {
0085 public:
0086     Private()
0087         : tiff(nullptr)
0088         , dev(nullptr)
0089     {
0090     }
0091 
0092     TIFF *tiff;
0093     QByteArray data;
0094     QIODevice *dev;
0095 };
0096 
0097 static QDateTime convertTIFFDateTime(const char *tiffdate)
0098 {
0099     if (!tiffdate) {
0100         return QDateTime();
0101     }
0102 
0103     return QDateTime::fromString(QString::fromLatin1(tiffdate), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
0104 }
0105 
0106 static void adaptSizeToResolution(TIFF *tiff, ttag_t whichres, double dpi, uint32_t *size)
0107 {
0108     float resvalue = 1.0;
0109     uint16_t resunit = 0;
0110     if (!TIFFGetField(tiff, whichres, &resvalue) || !TIFFGetFieldDefaulted(tiff, TIFFTAG_RESOLUTIONUNIT, &resunit)) {
0111         return;
0112     }
0113 
0114     float newsize = *size / resvalue;
0115     switch (resunit) {
0116     case RESUNIT_INCH:
0117         *size = (uint32_t)(newsize * dpi);
0118         break;
0119     case RESUNIT_CENTIMETER:
0120         *size = (uint32_t)(newsize * 10.0 / 25.4 * dpi);
0121         break;
0122     case RESUNIT_NONE:
0123         break;
0124     }
0125 }
0126 
0127 static Okular::Rotation readTiffRotation(TIFF *tiff)
0128 {
0129     uint32_t tiffOrientation = 0;
0130 
0131     if (!TIFFGetField(tiff, TIFFTAG_ORIENTATION, &tiffOrientation)) {
0132         return Okular::Rotation0;
0133     }
0134 
0135     Okular::Rotation ret = Okular::Rotation0;
0136     switch (tiffOrientation) {
0137     case ORIENTATION_TOPLEFT:
0138     case ORIENTATION_TOPRIGHT:
0139         ret = Okular::Rotation0;
0140         break;
0141     case ORIENTATION_BOTRIGHT:
0142     case ORIENTATION_BOTLEFT:
0143         ret = Okular::Rotation180;
0144         break;
0145     case ORIENTATION_LEFTTOP:
0146     case ORIENTATION_LEFTBOT:
0147         ret = Okular::Rotation270;
0148         break;
0149     case ORIENTATION_RIGHTTOP:
0150     case ORIENTATION_RIGHTBOT:
0151         ret = Okular::Rotation90;
0152         break;
0153     }
0154 
0155     return ret;
0156 }
0157 
0158 OKULAR_EXPORT_PLUGIN(TIFFGenerator, "libokularGenerator_tiff.json")
0159 
0160 TIFFGenerator::TIFFGenerator(QObject *parent, const QVariantList &args)
0161     : Okular::Generator(parent, args)
0162     , d(new Private)
0163 {
0164     setFeature(Threaded);
0165     setFeature(PrintNative);
0166     setFeature(PrintToFile);
0167     setFeature(ReadRawData);
0168 }
0169 
0170 TIFFGenerator::~TIFFGenerator()
0171 {
0172     if (d->tiff) {
0173         TIFFClose(d->tiff);
0174         d->tiff = nullptr;
0175     }
0176 
0177     delete d;
0178 }
0179 
0180 bool TIFFGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector)
0181 {
0182     QFile *qfile = new QFile(fileName);
0183     qfile->open(QIODevice::ReadOnly);
0184     d->dev = qfile;
0185     d->data = QFile::encodeName(QFileInfo(*qfile).fileName());
0186     return loadTiff(pagesVector, d->data.constData());
0187 }
0188 
0189 bool TIFFGenerator::loadDocumentFromData(const QByteArray &fileData, QVector<Okular::Page *> &pagesVector)
0190 {
0191     d->data = fileData;
0192     QBuffer *qbuffer = new QBuffer(&d->data);
0193     qbuffer->open(QIODevice::ReadOnly);
0194     d->dev = qbuffer;
0195     return loadTiff(pagesVector, "<stdin>");
0196 }
0197 
0198 bool TIFFGenerator::loadTiff(QVector<Okular::Page *> &pagesVector, const char *name)
0199 {
0200     d->tiff = TIFFClientOpen(name, "r", d->dev, okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc, okular_tiffCloseProc, okular_tiffSizeProc, okular_tiffMapProc, okular_tiffUnmapProc);
0201     if (!d->tiff) {
0202         delete d->dev;
0203         d->dev = nullptr;
0204         d->data.clear();
0205         return false;
0206     }
0207 
0208     loadPages(pagesVector);
0209 
0210     return true;
0211 }
0212 
0213 bool TIFFGenerator::doCloseDocument()
0214 {
0215     // closing the old document
0216     if (d->tiff) {
0217         TIFFClose(d->tiff);
0218         d->tiff = nullptr;
0219         delete d->dev;
0220         d->dev = nullptr;
0221         d->data.clear();
0222         m_pageMapping.clear();
0223     }
0224 
0225     return true;
0226 }
0227 
0228 QImage TIFFGenerator::image(Okular::PixmapRequest *request)
0229 {
0230     bool generated = false;
0231     QImage img;
0232 
0233     if (TIFFSetDirectory(d->tiff, mapPage(request->page()->number()))) {
0234         int rotation = request->page()->rotation();
0235         uint32_t width = 1;
0236         uint32_t height = 1;
0237         uint32_t orientation = 0;
0238         TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width);
0239         TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height);
0240 
0241         if (!TIFFGetField(d->tiff, TIFFTAG_ORIENTATION, &orientation)) {
0242             orientation = ORIENTATION_TOPLEFT;
0243         }
0244 
0245         QImage image(width, height, QImage::Format_RGB32);
0246         uint32_t *data = reinterpret_cast<uint32_t *>(image.bits());
0247 
0248         // read data
0249         if (TIFFReadRGBAImageOriented(d->tiff, width, height, data, orientation) != 0) {
0250             // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue
0251             uint32_t size = width * height;
0252             for (uint32_t i = 0; i < size; ++i) {
0253                 uint32_t red = (data[i] & 0x00FF0000) >> 16;
0254                 uint32_t blue = (data[i] & 0x000000FF) << 16;
0255                 data[i] = (data[i] & 0xFF00FF00) + red + blue;
0256             }
0257 
0258             int reqwidth = request->width();
0259             int reqheight = request->height();
0260             if (rotation % 2 == 1) {
0261                 qSwap(reqwidth, reqheight);
0262             }
0263             img = image.scaled(reqwidth, reqheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0264 
0265             generated = true;
0266         }
0267     }
0268 
0269     if (!generated) {
0270         img = QImage(request->width(), request->height(), QImage::Format_RGB32);
0271         img.fill(qRgb(255, 255, 255));
0272     }
0273 
0274     return img;
0275 }
0276 
0277 Okular::DocumentInfo TIFFGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const
0278 {
0279     Okular::DocumentInfo docInfo;
0280     if (d->tiff) {
0281         if (keys.contains(Okular::DocumentInfo::MimeType)) {
0282             docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("image/tiff"));
0283         }
0284 
0285         if (keys.contains(Okular::DocumentInfo::Description)) {
0286             char *buffer = nullptr;
0287             TIFFGetField(d->tiff, TIFFTAG_IMAGEDESCRIPTION, &buffer);
0288             docInfo.set(Okular::DocumentInfo::Description, buffer ? QString::fromLatin1(buffer) : QString());
0289         }
0290 
0291         if (keys.contains(Okular::DocumentInfo::Producer)) {
0292             char *buffer = nullptr;
0293             TIFFGetField(d->tiff, TIFFTAG_SOFTWARE, &buffer);
0294             docInfo.set(Okular::DocumentInfo::Producer, buffer ? QString::fromLatin1(buffer) : QString());
0295         }
0296 
0297         if (keys.contains(Okular::DocumentInfo::Copyright)) {
0298             char *buffer = nullptr;
0299             TIFFGetField(d->tiff, TIFFTAG_COPYRIGHT, &buffer);
0300             docInfo.set(Okular::DocumentInfo::Copyright, buffer ? QString::fromLatin1(buffer) : QString());
0301         }
0302 
0303         if (keys.contains(Okular::DocumentInfo::Author)) {
0304             char *buffer = nullptr;
0305             TIFFGetField(d->tiff, TIFFTAG_ARTIST, &buffer);
0306             docInfo.set(Okular::DocumentInfo::Author, buffer ? QString::fromLatin1(buffer) : QString());
0307         }
0308 
0309         if (keys.contains(Okular::DocumentInfo::CreationDate)) {
0310             char *buffer = nullptr;
0311             TIFFGetField(d->tiff, TIFFTAG_DATETIME, &buffer);
0312             QDateTime date = convertTIFFDateTime(buffer);
0313             docInfo.set(Okular::DocumentInfo::CreationDate, date.isValid() ? QLocale().toString(date, QLocale::LongFormat) : QString());
0314         }
0315     }
0316 
0317     return docInfo;
0318 }
0319 
0320 void TIFFGenerator::loadPages(QVector<Okular::Page *> &pagesVector)
0321 {
0322     if (!d->tiff) {
0323         return;
0324     }
0325 
0326     tdir_t dirs = TIFFNumberOfDirectories(d->tiff);
0327     pagesVector.resize(dirs);
0328     tdir_t realdirs = 0;
0329 
0330     uint32_t width = 0;
0331     uint32_t height = 0;
0332 
0333     const QSizeF dpi = Okular::Utils::realDpi(nullptr);
0334     for (tdir_t i = 0; i < dirs; ++i) {
0335         if (!TIFFSetDirectory(d->tiff, i)) {
0336             continue;
0337         }
0338 
0339         if (TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width) != 1 || TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height) != 1) {
0340             continue;
0341         }
0342 
0343         adaptSizeToResolution(d->tiff, TIFFTAG_XRESOLUTION, dpi.width(), &width);
0344         adaptSizeToResolution(d->tiff, TIFFTAG_YRESOLUTION, dpi.height(), &height);
0345 
0346         Okular::Page *page = new Okular::Page(realdirs, width, height, readTiffRotation(d->tiff));
0347         pagesVector[realdirs] = page;
0348 
0349         m_pageMapping[realdirs] = i;
0350 
0351         ++realdirs;
0352     }
0353 
0354     pagesVector.resize(realdirs);
0355 }
0356 
0357 Okular::Document::PrintError TIFFGenerator::print(QPrinter &printer)
0358 {
0359     uint32_t width = 0;
0360     uint32_t height = 0;
0361 
0362     QPainter p(&printer);
0363 
0364     QList<int> pageList = Okular::FilePrinter::pageList(printer, document()->pages(), document()->currentPage() + 1, document()->bookmarkedPageList());
0365 
0366     for (int i = 0; i < pageList.count(); ++i) {
0367         if (!TIFFSetDirectory(d->tiff, mapPage(pageList[i] - 1))) {
0368             continue;
0369         }
0370 
0371         if (TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width) != 1 || TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height) != 1) {
0372             continue;
0373         }
0374 
0375         QImage image(width, height, QImage::Format_RGB32);
0376         uint32_t *data = reinterpret_cast<uint32_t *>(image.bits());
0377 
0378         // read data
0379         if (TIFFReadRGBAImageOriented(d->tiff, width, height, data, ORIENTATION_TOPLEFT) != 0) {
0380             // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue
0381             uint32_t size = width * height;
0382             for (uint32_t j = 0; j < size; ++j) {
0383                 uint32_t red = (data[j] & 0x00FF0000) >> 16;
0384                 uint32_t blue = (data[j] & 0x000000FF) << 16;
0385                 data[j] = (data[j] & 0xFF00FF00) + red + blue;
0386             }
0387         }
0388 
0389         if (i != 0) {
0390             printer.newPage();
0391         }
0392 
0393         QSize targetSize = printer.pageRect(QPrinter::Unit::DevicePixel).size().toSize();
0394 
0395         if ((image.width() < targetSize.width()) && (image.height() < targetSize.height())) {
0396             // draw small images at 100% (don't scale up)
0397             p.drawImage(0, 0, image);
0398         } else {
0399             // fit to page
0400             p.drawImage(0, 0, image.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
0401         }
0402     }
0403 
0404     return Okular::Document::NoPrintError;
0405 }
0406 
0407 int TIFFGenerator::mapPage(int page) const
0408 {
0409     QHash<int, int>::const_iterator it = m_pageMapping.find(page);
0410     if (it == m_pageMapping.end()) {
0411         qCWarning(OkularTiffDebug) << "Requesting unmapped page" << page << ":" << m_pageMapping;
0412         return -1;
0413     }
0414     return it.value();
0415 }
0416 
0417 Q_LOGGING_CATEGORY(OkularTiffDebug, "org.kde.okular.generators.tiff", QtWarningMsg)
0418 
0419 #include "generator_tiff.moc"