mirror of
https://invent.kde.org/graphics/okular
synced 2024-09-06 00:15:42 +00:00
420 lines
12 KiB
C++
420 lines
12 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "generator_tiff.h"
|
|
|
|
#include <QBuffer>
|
|
#include <QDateTime>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QImage>
|
|
#include <QList>
|
|
#include <QPainter>
|
|
#include <QPrinter>
|
|
|
|
#include <KAboutData>
|
|
#include <KLocalizedString>
|
|
#include <QDebug>
|
|
|
|
#include <core/document.h>
|
|
#include <core/fileprinter.h>
|
|
#include <core/page.h>
|
|
#include <core/utils.h>
|
|
|
|
#include <tiff.h>
|
|
#include <tiffio.h>
|
|
|
|
#define TiffDebug 4714
|
|
|
|
tsize_t okular_tiffReadProc(thandle_t handle, tdata_t buf, tsize_t size)
|
|
{
|
|
QIODevice *device = static_cast<QIODevice *>(handle);
|
|
return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
|
|
}
|
|
|
|
tsize_t okular_tiffWriteProc(thandle_t handle, tdata_t buf, tsize_t size)
|
|
{
|
|
QIODevice *device = static_cast<QIODevice *>(handle);
|
|
return device->write(static_cast<char *>(buf), size);
|
|
}
|
|
|
|
toff_t okular_tiffSeekProc(thandle_t handle, toff_t offset, int whence)
|
|
{
|
|
QIODevice *device = static_cast<QIODevice *>(handle);
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
device->seek(offset);
|
|
break;
|
|
case SEEK_CUR:
|
|
device->seek(device->pos() + offset);
|
|
break;
|
|
case SEEK_END:
|
|
device->seek(device->size() + offset);
|
|
break;
|
|
}
|
|
|
|
return device->pos();
|
|
}
|
|
|
|
int okular_tiffCloseProc(thandle_t handle)
|
|
{
|
|
Q_UNUSED(handle)
|
|
return 0;
|
|
}
|
|
|
|
toff_t okular_tiffSizeProc(thandle_t handle)
|
|
{
|
|
QIODevice *device = static_cast<QIODevice *>(handle);
|
|
return device->size();
|
|
}
|
|
|
|
int okular_tiffMapProc(thandle_t, tdata_t *, toff_t *)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void okular_tiffUnmapProc(thandle_t, tdata_t, toff_t)
|
|
{
|
|
}
|
|
|
|
class TIFFGenerator::Private
|
|
{
|
|
public:
|
|
Private()
|
|
: tiff(nullptr)
|
|
, dev(nullptr)
|
|
{
|
|
}
|
|
|
|
TIFF *tiff;
|
|
QByteArray data;
|
|
QIODevice *dev;
|
|
};
|
|
|
|
static QDateTime convertTIFFDateTime(const char *tiffdate)
|
|
{
|
|
if (!tiffdate) {
|
|
return QDateTime();
|
|
}
|
|
|
|
return QDateTime::fromString(QString::fromLatin1(tiffdate), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
|
}
|
|
|
|
static void adaptSizeToResolution(TIFF *tiff, ttag_t whichres, double dpi, uint32_t *size)
|
|
{
|
|
float resvalue = 1.0;
|
|
uint16_t resunit = 0;
|
|
if (!TIFFGetField(tiff, whichres, &resvalue) || !TIFFGetFieldDefaulted(tiff, TIFFTAG_RESOLUTIONUNIT, &resunit)) {
|
|
return;
|
|
}
|
|
|
|
float newsize = *size / resvalue;
|
|
switch (resunit) {
|
|
case RESUNIT_INCH:
|
|
*size = (uint32_t)(newsize * dpi);
|
|
break;
|
|
case RESUNIT_CENTIMETER:
|
|
*size = (uint32_t)(newsize * 10.0 / 25.4 * dpi);
|
|
break;
|
|
case RESUNIT_NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static Okular::Rotation readTiffRotation(TIFF *tiff)
|
|
{
|
|
uint32_t tiffOrientation = 0;
|
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_ORIENTATION, &tiffOrientation)) {
|
|
return Okular::Rotation0;
|
|
}
|
|
|
|
Okular::Rotation ret = Okular::Rotation0;
|
|
switch (tiffOrientation) {
|
|
case ORIENTATION_TOPLEFT:
|
|
case ORIENTATION_TOPRIGHT:
|
|
ret = Okular::Rotation0;
|
|
break;
|
|
case ORIENTATION_BOTRIGHT:
|
|
case ORIENTATION_BOTLEFT:
|
|
ret = Okular::Rotation180;
|
|
break;
|
|
case ORIENTATION_LEFTTOP:
|
|
case ORIENTATION_LEFTBOT:
|
|
ret = Okular::Rotation270;
|
|
break;
|
|
case ORIENTATION_RIGHTTOP:
|
|
case ORIENTATION_RIGHTBOT:
|
|
ret = Okular::Rotation90;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
OKULAR_EXPORT_PLUGIN(TIFFGenerator, "libokularGenerator_tiff.json")
|
|
|
|
TIFFGenerator::TIFFGenerator(QObject *parent, const QVariantList &args)
|
|
: Okular::Generator(parent, args)
|
|
, d(new Private)
|
|
{
|
|
setFeature(Threaded);
|
|
setFeature(PrintNative);
|
|
setFeature(PrintToFile);
|
|
setFeature(ReadRawData);
|
|
}
|
|
|
|
TIFFGenerator::~TIFFGenerator()
|
|
{
|
|
if (d->tiff) {
|
|
TIFFClose(d->tiff);
|
|
d->tiff = nullptr;
|
|
}
|
|
|
|
delete d;
|
|
}
|
|
|
|
bool TIFFGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector)
|
|
{
|
|
QFile *qfile = new QFile(fileName);
|
|
qfile->open(QIODevice::ReadOnly);
|
|
d->dev = qfile;
|
|
d->data = QFile::encodeName(QFileInfo(*qfile).fileName());
|
|
return loadTiff(pagesVector, d->data.constData());
|
|
}
|
|
|
|
bool TIFFGenerator::loadDocumentFromData(const QByteArray &fileData, QVector<Okular::Page *> &pagesVector)
|
|
{
|
|
d->data = fileData;
|
|
QBuffer *qbuffer = new QBuffer(&d->data);
|
|
qbuffer->open(QIODevice::ReadOnly);
|
|
d->dev = qbuffer;
|
|
return loadTiff(pagesVector, "<stdin>");
|
|
}
|
|
|
|
bool TIFFGenerator::loadTiff(QVector<Okular::Page *> &pagesVector, const char *name)
|
|
{
|
|
d->tiff = TIFFClientOpen(name, "r", d->dev, okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc, okular_tiffCloseProc, okular_tiffSizeProc, okular_tiffMapProc, okular_tiffUnmapProc);
|
|
if (!d->tiff) {
|
|
delete d->dev;
|
|
d->dev = nullptr;
|
|
d->data.clear();
|
|
return false;
|
|
}
|
|
|
|
loadPages(pagesVector);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TIFFGenerator::doCloseDocument()
|
|
{
|
|
// closing the old document
|
|
if (d->tiff) {
|
|
TIFFClose(d->tiff);
|
|
d->tiff = nullptr;
|
|
delete d->dev;
|
|
d->dev = nullptr;
|
|
d->data.clear();
|
|
m_pageMapping.clear();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QImage TIFFGenerator::image(Okular::PixmapRequest *request)
|
|
{
|
|
bool generated = false;
|
|
QImage img;
|
|
|
|
if (TIFFSetDirectory(d->tiff, mapPage(request->page()->number()))) {
|
|
int rotation = request->page()->rotation();
|
|
uint32_t width = 1;
|
|
uint32_t height = 1;
|
|
uint32_t orientation = 0;
|
|
TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width);
|
|
TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height);
|
|
|
|
if (!TIFFGetField(d->tiff, TIFFTAG_ORIENTATION, &orientation)) {
|
|
orientation = ORIENTATION_TOPLEFT;
|
|
}
|
|
|
|
QImage image(width, height, QImage::Format_RGB32);
|
|
uint32_t *data = reinterpret_cast<uint32_t *>(image.bits());
|
|
|
|
// read data
|
|
if (TIFFReadRGBAImageOriented(d->tiff, width, height, data, orientation) != 0) {
|
|
// an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue
|
|
uint32_t size = width * height;
|
|
for (uint32_t i = 0; i < size; ++i) {
|
|
uint32_t red = (data[i] & 0x00FF0000) >> 16;
|
|
uint32_t blue = (data[i] & 0x000000FF) << 16;
|
|
data[i] = (data[i] & 0xFF00FF00) + red + blue;
|
|
}
|
|
|
|
int reqwidth = request->width();
|
|
int reqheight = request->height();
|
|
if (rotation % 2 == 1) {
|
|
qSwap(reqwidth, reqheight);
|
|
}
|
|
img = image.scaled(reqwidth, reqheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
|
|
generated = true;
|
|
}
|
|
}
|
|
|
|
if (!generated) {
|
|
img = QImage(request->width(), request->height(), QImage::Format_RGB32);
|
|
img.fill(qRgb(255, 255, 255));
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
Okular::DocumentInfo TIFFGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const
|
|
{
|
|
Okular::DocumentInfo docInfo;
|
|
if (d->tiff) {
|
|
if (keys.contains(Okular::DocumentInfo::MimeType)) {
|
|
docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("image/tiff"));
|
|
}
|
|
|
|
if (keys.contains(Okular::DocumentInfo::Description)) {
|
|
char *buffer = nullptr;
|
|
TIFFGetField(d->tiff, TIFFTAG_IMAGEDESCRIPTION, &buffer);
|
|
docInfo.set(Okular::DocumentInfo::Description, buffer ? QString::fromLatin1(buffer) : QString());
|
|
}
|
|
|
|
if (keys.contains(Okular::DocumentInfo::Producer)) {
|
|
char *buffer = nullptr;
|
|
TIFFGetField(d->tiff, TIFFTAG_SOFTWARE, &buffer);
|
|
docInfo.set(Okular::DocumentInfo::Producer, buffer ? QString::fromLatin1(buffer) : QString());
|
|
}
|
|
|
|
if (keys.contains(Okular::DocumentInfo::Copyright)) {
|
|
char *buffer = nullptr;
|
|
TIFFGetField(d->tiff, TIFFTAG_COPYRIGHT, &buffer);
|
|
docInfo.set(Okular::DocumentInfo::Copyright, buffer ? QString::fromLatin1(buffer) : QString());
|
|
}
|
|
|
|
if (keys.contains(Okular::DocumentInfo::Author)) {
|
|
char *buffer = nullptr;
|
|
TIFFGetField(d->tiff, TIFFTAG_ARTIST, &buffer);
|
|
docInfo.set(Okular::DocumentInfo::Author, buffer ? QString::fromLatin1(buffer) : QString());
|
|
}
|
|
|
|
if (keys.contains(Okular::DocumentInfo::CreationDate)) {
|
|
char *buffer = nullptr;
|
|
TIFFGetField(d->tiff, TIFFTAG_DATETIME, &buffer);
|
|
QDateTime date = convertTIFFDateTime(buffer);
|
|
docInfo.set(Okular::DocumentInfo::CreationDate, date.isValid() ? QLocale().toString(date, QLocale::LongFormat) : QString());
|
|
}
|
|
}
|
|
|
|
return docInfo;
|
|
}
|
|
|
|
void TIFFGenerator::loadPages(QVector<Okular::Page *> &pagesVector)
|
|
{
|
|
if (!d->tiff) {
|
|
return;
|
|
}
|
|
|
|
tdir_t dirs = TIFFNumberOfDirectories(d->tiff);
|
|
pagesVector.resize(dirs);
|
|
tdir_t realdirs = 0;
|
|
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
|
|
const QSizeF dpi = Okular::Utils::realDpi(nullptr);
|
|
for (tdir_t i = 0; i < dirs; ++i) {
|
|
if (!TIFFSetDirectory(d->tiff, i)) {
|
|
continue;
|
|
}
|
|
|
|
if (TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width) != 1 || TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height) != 1) {
|
|
continue;
|
|
}
|
|
|
|
adaptSizeToResolution(d->tiff, TIFFTAG_XRESOLUTION, dpi.width(), &width);
|
|
adaptSizeToResolution(d->tiff, TIFFTAG_YRESOLUTION, dpi.height(), &height);
|
|
|
|
Okular::Page *page = new Okular::Page(realdirs, width, height, readTiffRotation(d->tiff));
|
|
pagesVector[realdirs] = page;
|
|
|
|
m_pageMapping[realdirs] = i;
|
|
|
|
++realdirs;
|
|
}
|
|
|
|
pagesVector.resize(realdirs);
|
|
}
|
|
|
|
Okular::Document::PrintError TIFFGenerator::print(QPrinter &printer)
|
|
{
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
|
|
QPainter p(&printer);
|
|
|
|
QList<int> pageList = Okular::FilePrinter::pageList(printer, document()->pages(), document()->currentPage() + 1, document()->bookmarkedPageList());
|
|
|
|
for (int i = 0; i < pageList.count(); ++i) {
|
|
if (!TIFFSetDirectory(d->tiff, mapPage(pageList[i] - 1))) {
|
|
continue;
|
|
}
|
|
|
|
if (TIFFGetField(d->tiff, TIFFTAG_IMAGEWIDTH, &width) != 1 || TIFFGetField(d->tiff, TIFFTAG_IMAGELENGTH, &height) != 1) {
|
|
continue;
|
|
}
|
|
|
|
QImage image(width, height, QImage::Format_RGB32);
|
|
uint32_t *data = reinterpret_cast<uint32_t *>(image.bits());
|
|
|
|
// read data
|
|
if (TIFFReadRGBAImageOriented(d->tiff, width, height, data, ORIENTATION_TOPLEFT) != 0) {
|
|
// an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue
|
|
uint32_t size = width * height;
|
|
for (uint32_t j = 0; j < size; ++j) {
|
|
uint32_t red = (data[j] & 0x00FF0000) >> 16;
|
|
uint32_t blue = (data[j] & 0x000000FF) << 16;
|
|
data[j] = (data[j] & 0xFF00FF00) + red + blue;
|
|
}
|
|
}
|
|
|
|
if (i != 0) {
|
|
printer.newPage();
|
|
}
|
|
|
|
QSize targetSize = printer.pageRect(QPrinter::Unit::DevicePixel).size().toSize();
|
|
|
|
if ((image.width() < targetSize.width()) && (image.height() < targetSize.height())) {
|
|
// draw small images at 100% (don't scale up)
|
|
p.drawImage(0, 0, image);
|
|
} else {
|
|
// fit to page
|
|
p.drawImage(0, 0, image.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
|
}
|
|
}
|
|
|
|
return Okular::Document::NoPrintError;
|
|
}
|
|
|
|
int TIFFGenerator::mapPage(int page) const
|
|
{
|
|
QHash<int, int>::const_iterator it = m_pageMapping.find(page);
|
|
if (it == m_pageMapping.end()) {
|
|
qCWarning(OkularTiffDebug) << "Requesting unmapped page" << page << ":" << m_pageMapping;
|
|
return -1;
|
|
}
|
|
return it.value();
|
|
}
|
|
|
|
Q_LOGGING_CATEGORY(OkularTiffDebug, "org.kde.okular.generators.tiff", QtWarningMsg)
|
|
|
|
#include "generator_tiff.moc"
|