C++やPythonでPDFをさくっと画像で読みこめるQtベースのライブラリ「Poppler」

#意外とないのね。PDFライブラリ。
簡単にPDFを読み込み・画像化できるライブラリくらい、いくらでもあるだろうと思いきや、案外、使い勝手のいいものはなかなか見つけられなかった。
けれど、Qtを使って書かれたPopplerというライブラリが、なかなかよかったので、使ってみた。
Poppler-qt4 (C++) http://people.freedesktop.org/~aacid/docs/qt4/
python-poppler-qt4 https://pypi.python.org/pypi/python-poppler-qt4/

また、Popplerのドキュメントは[こちら](http://people.freedesktop.org/~aacid/docs/qt4/annotated.html)。まずは[Document型](http://people.freedesktop.org/~aacid/docs/qt4/classPoppler_1_1Document.html)から読んで、何ができるのか探ればいいと思う。

Qt5版も存在するが、今回は、Qt4版を使用した。
一部は、C++とPython両方で書いた。Pythonは、Python3を使用した。

ここに載せているコードはすべて[githubに掲載](https://github.com/gyu-don/poppler_demo)している。

#Popplerのいいところ
* Qtとの相性抜群(Qtを使う気がない人にとっては悪いところ?)
* ページの画像化やテキスト抽出が簡単

#使ってみよう
デモとして、以下を作った。
1. PDFを各ページ画像化して保存(Pythonのみ)
2. PDFからのテキスト抽出スクリプト(Pythonのみ)
3. 超簡易のPDFビューア(C++とPythonの両方で実装。記事ではC++版のみ紹介)


#画像を保存しよう
読み込みは`doc = Poppler.Document.load(path)`で行う。
`doc.setRenderHint(Poppler.Document.TextAntialiasing)` を書かないと、出力がとてもきたないので注意。
読み込んだら、`page = doc.page(i)`でPageオブジェクトを取り出す。ページ番号はゼロから始まる。
`image = page.renderToImage()`とすれば、Qtの[QImage型](http://doc.qt.io/qt-4.8/qimage.html)で、画像が返ってくる。
画像は`image.save()`で保存できる。

Pythonで書いた。もしやりたければ、C++にするのも簡単だと思う。

```python3:dump_image.py
import sys
import os.path
from contextlib import closing

from PyQt4 import QtCore
from popplerqt4 import Poppler

FORMAT = 'PNG'
EXT = '.png'

def dump_image(path):
    doc = Poppler.Document.load(path)
    doc.setRenderHint(Poppler.Document.TextAntialiasing)
    filename_fmt = os.path.splitext(path)[0] + '_{0}' + EXT
    for n,page in ((i+1, doc.page(i)) for i in range(doc.numPages())):
        page.renderToImage().save(filename_fmt.format(n), FORMAT)

if __name__ == '__main__':
    app = QtCore.QCoreApplication(sys.argv)
    if len(sys.argv) != 2:
        print('Usage: {0} pdf_path'.format(sys.argv[0]))
    else:
        dump_image(sys.argv[1])
    sys.exit(0)
```

#テキストを抽出しよう
テキスト抽出なら、Popplerの他にもライブラリがいくらでもある気がするが。一応、できますよ、ということで。
page.textList()が、単語の入ったTextboxオブジェクトのリストを返すので、ループを回して各々を表示している。

```py3:dump_text.py
import sys

from PyQt4 import QtCore
from popplerqt4 import Poppler

def dump_text(path):
    doc = Poppler.Document.load(path)
    for n,page in ((i+1, doc.page(i)) for i in range(doc.numPages())):
        print('\n-------- Page {0} -------'.format(n))
        for txtbox in page.textList():
            print(txtbox.text(), end=' ')

if __name__ == '__main__':
    app = QtCore.QCoreApplication(sys.argv)
    if len(sys.argv) != 2:
        print('Usage: {0} pdf_path'.format(sys.argv[0]))
    else:
        dump_text(sys.argv[1])
    sys.exit(0)
```

#PDFビューアを作ろう(C++)

Popplerでは、ページをQImage型の画像に変換することができることが分かった。また、QPainterにページを描画させることもできる。
QPainterを使ってもいいのだが、今回は、QImage型を受け取って、QPixmap型に変換してQLabelウィジェット(を継承したPdfWidget)に表示させることにする。

あまり凝ったもの作りたくなかったので、ファイル読み込みUIとかは持たせてない。ウィジェットのコンストラクタでファイルパスを指定して、それを読み込ませている。
ウィジェットサイズに合わせてリサイズくらいはした方がよかったんじゃないかと自分でも思うが、今回はやってない。

```cpp:pdfwidget.h
#include <QLabel>
#include <QString>

namespace Poppler{
        class Document;
};

class PdfWidget : public QLabel{
        Q_OBJECT
public:
        PdfWidget(QString path, QWidget *parent = 0);
public slots:
        void next_page();
        void prev_page();
private:
        void load_current_page();
        int n_pages;
        int current_page;
        QString path;
        Poppler::Document *doc;
};
```

```cpp:pdfwidget.cpp
#include "pdfwidget.h"
#include <poppler-qt4.h>
#include <QAction>
#include <QPixmap>

PdfWidget::PdfWidget(QString path, QWidget *parent)
                : QLabel(parent), path(path)
{
        doc = Poppler::Document::load(path);
        doc->setRenderHint(Poppler::Document::TextAntialiasing);
        n_pages = doc->numPages();
        current_page = 0;
        load_current_page();

        QAction *next_page = new QAction("Next Page", this);
        next_page->setShortcut(Qt::Key_Right);
        connect(next_page, SIGNAL(triggered()), this, SLOT(next_page()));
        addAction(next_page);

        QAction *prev_page = new QAction("Prev Page", this);
        prev_page->setShortcut(Qt::Key_Left);
        connect(prev_page, SIGNAL(triggered()), this, SLOT(prev_page()));
        addAction(prev_page);
}

void PdfWidget::next_page()
{
        current_page++;
        if(current_page >= n_pages) current_page = 0;
        load_current_page();
}

void PdfWidget::prev_page()
{
        if(current_page) current_page--;
        else current_page = n_pages - 1;
        load_current_page();
}

void PdfWidget::load_current_page()
{
        setPixmap(QPixmap::fromImage(doc->page(current_page)->renderToImage()));
        setWindowTitle(QString("%1/%2 - %3").arg(current_page+1)
                                            .arg(n_pages).arg(path));
}
```

一応、キーボードの右・左で、次ページ・前ページに行くようにしてある。

#まとめ
PDFを画像に変換できるライブラリは、私の知る限り、C++でもPythonでも、あんまりない。
あんまりない中でも、マルチプラットホームで、簡単に使えるライブラリとして、Popplerを紹介した。

Qtとの相性もいいので、GUIでPDFを扱いたい場面では、使い勝手がかなりいい。