← Back to team overview

qpdfview team mailing list archive

Re: Support other file formats

 

Hello Alexander,

After thinking about it for some time, I'd say that I would be
interested in supporting multiple formats in qpdfview if:
* We agree to use an abstract document model separating data and
presentation completely.
* We agree that the interfaces are modelled on Poppler's interfaces in
the beginning, generalizing them later on.
* We support at least PDF, PS, and DjVu.
* We take our time for the next version which will be 0.4 and no rush
w.r.t. a new name... :-)

Modelling Poppler's interfaces will make it possible to keep the
settings as they are even though some might not make sense for other
backends. I attach some sample code on how I would like to proceed with
the abstract model. Your thoughts?

Of course, the "fun" part will be adding (probably polymorphic)
interfaces for annotations and form fields which is current missing. But
maybe we just make them struct's containing their boundary on the page
and a "handler object" derived from QDialog which should make it
possible to use the current implementation with minimal changes.

Best regards, Adam.

Am 21.12.2012 12:50, schrieb Александр Волков:
> Hello Adam,
> 
>>> Ok, it makes sense to create intermediate intefaces for links and
>>> maybe annotations, but there is no reason to do it for form
>>> fields, as no other format supports them.
>> While this is currently so, maybe we want to use a different
>> PDF-backend in the future or there is a new format that does this. As
>> I said, we have all the time in the world to do this right. If we
>> decide to do it, that is.
> 
> I don't think that it's right to put all format specific features to
> core library. It looks like "early optimization" in code design.
> You can spend a lot of time on the things that actually will never
> be needed in the future.
> I'd prefer to do first so that it just worked. And create common
> interfaces only as you need them.
> On the other hand it may be possible to create common ancestor
> class for links, annotations and everything else and use it everywhere
> instead of specific classes.
> 
>> With "presentation-specific" code, I meant everything related to
>> handling displaying content and interacting with it, i.e. most of
>> DocumentView, PageItem and PresentationView.
> 
> Sorry for the misunderstanding.
> 
> Best regards, Alexander.
> 
> 
#include "djvumodel.h"

#include <QImage>

#include <libdjvu/ddjvuapi.h>

DjVuPage::DjVuPage(QMutex* mutex, ddjvu_context_t* context, ddjvu_page_t* page) :
    m_mutex(mutex),
    m_context(context),
    m_page(page)
{
}

DjVuPage::~DjVuPage()
{
}

QSizeF DjVuPage::size() const
{
    return QSizeF();
}

QImage DjVuPage::render(qreal horizontalResolution, qreal verticalResolution, Page::Rotation rotation, const QRect& boundingRect) const
{
    return QImage();
}

Document* DjVuDocument::load(const QString& filePath)
{
    return 0;
}

DjVuDocument::DjVuDocument(ddjvu_context_t* context, ddjvu_document_t* document) :
    m_mutex(),
    m_context(context),
    m_document(document)
{
}

DjVuDocument::~DjVuDocument()
{
}

int DjVuDocument::numberOfPages() const
{
    return -1;
}

Page* DjVuDocument::page(int index) const
{
    return 0;
}
#ifndef DJVUMODEL_H
#define DJVUMODEL_H

#include <QMutex>

typedef struct ddjvu_context_s ddjvu_context_t;
typedef struct ddjvu_document_s ddjvu_document_t;
typedef struct ddjvu_page_s ddjvu_page_t;

#include "model.h"

class DjVuPage : public Page
{
    friend class DjVuDocument;

public:
    ~DjVuPage();

    QSizeF size() const;

    QImage render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, const QRect& boundingRect) const;

private:
    DjVuPage(QMutex* mutex, ddjvu_context_t* context, ddjvu_page_t* page);

    mutable QMutex* m_mutex;
    ddjvu_context_t* m_context;
    ddjvu_page_t* m_page;

};

class DjVuDocument : public Document
{
public:
    static Document* load(const QString& filePath);

    ~DjVuDocument();

    int numberOfPages() const;

    Page* page(int index) const;

private:
    DjVuDocument(ddjvu_context_t* context, ddjvu_document_t* document);

    mutable QMutex m_mutex;
    ddjvu_context_t* m_context;
    ddjvu_document_t* m_document;

};

#endif // DJVUMODEL_H
#include "model.h"

#include <QString>
#include <QFileInfo>

#include "pdfmodel.h"
#include "psmodel.h"
#include "djvumodel.h"

Document* Document::load(const QString& filePath)
{
    QFileInfo fileInfo(filePath);

    if(fileInfo.suffix() == "pdf")
    {
        return PDFDocument::load(filePath);
    }

    if(fileInfo.suffix() == "ps")
    {
        return PSDocument::load(filePath);
    }

    if(fileInfo.suffix() == "djvu")
    {
        return DjVuDocument::load(filePath);
    }

    return 0;
}
#ifndef DOCUMENTMODEL_H
#define DOCUMENTMODEL_H

#include <QtGlobal>
#include <QList>
#include <QRect>
#include <QRectF>
#include <QString>

class QImage;
class QSizeF;
class QStandardItemModel;

struct Link
{
    QRectF boundary;

    int page;
    qreal left;
    qreal top;

    QString url;

    Link() : boundary(), page(-1), left(0.0), top(0.0), url() {}
    Link(const QRectF& boundary, int page, qreal left, qreal top) : boundary(boundary), page(page), left(left), top(top), url() {}
    Link(const QRectF& boundary, const QString& url) : boundary(boundary), page(-1), left(0.0), top(0.0), url(url) {}

};

class Page
{
public:
    virtual ~Page() {}

    virtual QSizeF size() const = 0;

    enum Rotation
    {
        DoNotRotate = 0,
        RotateBy90 = 1,
        RotateBy180 = 2,
        RotateBy270 = 3
    };

    virtual QImage render(qreal horizontalResolution = 72.0, qreal verticalResolution = 72.0, Rotation rotation = DoNotRotate, const QRect& boundingRect = QRect()) const = 0;

    virtual QList< Link > links() const { return QList< Link >(); }

};

class Document
{
public:
    static Document* load(const QString& filePath);

    virtual ~Document() {}

    virtual int numberOfPages() const = 0;

    virtual Page* page(int index) const = 0;

    virtual void setAntialiasing(bool on) { Q_UNUSED(on); }
    virtual void setTextAntialiasing(bool on) { Q_UNUSED(on); }
    virtual void setTextHinting(bool on) { Q_UNUSED(on); }

    virtual void loadOutline(QStandardItemModel* outlineModel) { Q_UNUSED(outlineModel); }
    virtual void loadProperties(QStandardItemModel* propertiesModel) { Q_UNUSED(propertiesModel); }

};

#endif // DOCUMENTMODEL_H
#include "pdfmodel.h"

#include <QImage>

#include <poppler-qt4.h>

PDFPage::PDFPage(QMutex* mutex, Poppler::Page* page) :
    m_mutex(mutex),
    m_page(page)
{
}

PDFPage::~PDFPage()
{
    delete m_page;
}

QSizeF PDFPage::size() const
{
    QMutexLocker mutexLocker(m_mutex);

    return m_page->pageSizeF();
}

QImage PDFPage::render(qreal horizontalResolution, qreal verticalResolution, Page::Rotation rotation, const QRect& boundingRect) const
{
    QMutexLocker mutexLocker(m_mutex);

    double xres;
    double yres;

    switch(rotation)
    {
    default:
    case Page::DoNotRotate:
    case Page::RotateBy180:
        xres = horizontalResolution;
        yres = verticalResolution;
        break;
    case Page::RotateBy90:
    case Page::RotateBy270:
        xres = verticalResolution;
        yres = horizontalResolution;
        break;
    }

    Poppler::Page::Rotation rotate;

    switch(rotation)
    {
    default:
    case Page::DoNotRotate:
        rotate = Poppler::Page::Rotate0;
        break;
    case Page::RotateBy90:
        rotate = Poppler::Page::Rotate90;
        break;
    case Page::RotateBy180:
        rotate = Poppler::Page::Rotate180;
        break;
    case Page::RotateBy270:
        rotate = Poppler::Page::Rotate270;
        break;
    }

    int x = -1;
    int y = -1;
    int w = -1;
    int h = -1;

    if(!boundingRect.isNull())
    {
        x = boundingRect.x();
        y = boundingRect.y();
        w = boundingRect.width();
        h = boundingRect.height();
    }

    return m_page->renderToImage(xres, yres, x, y, w, h, rotate);
}

QList< Link > PDFPage::links() const
{
    QMutexLocker mutexLocker(m_mutex);

    QList< Link > links;

    foreach(Poppler::Link* link, m_page->links())
    {
        if(link->linkType() == Poppler::Link::Goto)
        {
            Poppler::LinkGoto* linkGoto = static_cast< Poppler::LinkGoto* >(link);

            if(!linkGoto->isExternal())
            {
                QRectF boundary = linkGoto->linkArea().normalized();
                int page = linkGoto->destination().pageNumber();
                qreal left = linkGoto->destination().isChangeLeft() ? linkGoto->destination().left() : 0.0;
                qreal top = linkGoto->destination().isChangeTop() ? linkGoto->destination().top() : 0.0;

                links.append(Link(boundary, page, left, top));
            }
        }
        else if(link->linkType() == Poppler::Link::Browse)
        {
            Poppler::LinkBrowse* linkBrowse = static_cast< Poppler::LinkBrowse* >(link);

            QRectF boundary = linkBrowse->linkArea().normalized();
            QString url = linkBrowse->url();

            links.append(Link(boundary, url));
        }

        delete link;
    }

    return links;
}

Document* PDFDocument::load(const QString& filePath)
{
    Poppler::Document* document = Poppler::Document::load(filePath);

    return document != 0 ? new PDFDocument(document) : 0;
}

PDFDocument::PDFDocument(Poppler::Document* document) :
    m_mutex(),
    m_document(document)
{
}

PDFDocument::~PDFDocument()
{
    delete m_document;
}

int PDFDocument::numberOfPages() const
{
    QMutexLocker mutexLocker(&m_mutex);

    return m_document->numPages();
}

Page* PDFDocument::page(int index) const
{
    QMutexLocker mutexLocker(&m_mutex);

    Poppler::Page* page = m_document->page(index);

    return page != 0 ? new PDFPage(&m_mutex, page) : 0;
}

void PDFDocument::setAntialiasing(bool on)
{
    QMutexLocker mutexLocker(&m_mutex);

    m_document->setRenderHint(Poppler::Document::Antialiasing, on);
}

void PDFDocument::setTextAntialiasing(bool on)
{
    QMutexLocker mutexLocker(&m_mutex);

    m_document->setRenderHint(Poppler::Document::TextAntialiasing, on);
}

void PDFDocument::setTextHinting(bool on)
{
    QMutexLocker mutexLocker(&m_mutex);

    m_document->setRenderHint(Poppler::Document::TextHinting, on);
}
#ifndef PDFMODEL_H
#define PDFMODEL_H

#include <QSizeF>
#include <QMutex>

namespace Poppler
{
class Page;
class Document;
}

#include "model.h"

class PDFPage : public Page
{
    friend class PDFDocument;

public:
    ~PDFPage();

    QSizeF size() const;

    QImage render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, const QRect& boundingRect) const;

    QList< Link > links() const;

private:
    PDFPage(QMutex* mutex, Poppler::Page* page);

    mutable QMutex* m_mutex;
    Poppler::Page* m_page;

};

class PDFDocument : public Document
{
public:
    static Document* load(const QString &filePath);

    ~PDFDocument();

    int numberOfPages() const;

    Page* page(int index) const;

    void setAntialiasing(bool on);
    void setTextAntialiasing(bool on);
    void setTextHinting(bool on);

private:
    PDFDocument(Poppler::Document* document);

    mutable QMutex m_mutex;
    Poppler::Document* m_document;

};

#endif // PDFMODEL_H
#include "psmodel.h"

#include <QImage>

#include <libspectre/spectre.h>

PSPage::PSPage(QMutex* mutex, SpectrePage* page) :
    m_mutex(mutex),
    m_page(page)
{
}

PSPage::~PSPage()
{
}

QSizeF PSPage::size() const
{
    return QSizeF();
}

QImage PSPage::render(qreal horizontalResolution, qreal verticalResolution, Page::Rotation rotation, const QRect& boundingRect) const
{
    return QImage();
}

Document* PSDocument::load(const QString& filePath)
{
    return 0;
}

PSDocument::PSDocument(SpectreDocument* document) :
    m_mutex(),
    m_document(document)
{
}

PSDocument::~PSDocument()
{
}

int PSDocument::numberOfPages() const
{
    return -1;
}

Page* PSDocument::page(int index) const
{
    return 0;
}
#ifndef PSMODEL_H
#define PSMODEL_H

#include <QMutex>

typedef struct SpectreDocument SpectreDocument;
typedef struct SpectrePage SpectrePage;

#include "model.h"

class PSPage : public Page
{
    friend class PSDocument;

public:
    ~PSPage();

    QSizeF size() const;

    QImage render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, const QRect& boundingRect) const;

private:
    PSPage(QMutex* mutex, SpectrePage* page);

    mutable QMutex* m_mutex;
    SpectrePage* m_page;

};

class PSDocument : public Document
{
public:
    static Document* load(const QString& filePath);

    ~PSDocument();

    int numberOfPages() const;

    Page* page(int index) const;

private:
    PSDocument(SpectreDocument* document);

    mutable QMutex m_mutex;
    SpectreDocument* m_document;

};

#endif // PSMODEL_H

Follow ups

References