如果只是读取、渲染pdf文件,除了mupdf以外,在Linux系统中还可以使用Poppler库。Poppler的历史非常古老,X窗口系统中的pdf查看工具xpdf,使用的就是poppler。

Poppler在Linux各发行版,以及BSD族系统之中,都有现成的二进制安装包。

而且,Poppler还提供了glib、Qt5、Qt6等多种上层库的支持。本文中的示例,就采用Qt6的接口。

开发环境

Poppler按照上层接口的不同,需要不同的头文件与库文件。

原生API

比如,如果使用原生的C++接口,就需要使用poppler-devel安装包,包含Poppler底层的头文件,头文件目录是/usr/include/poppler,连接/usr/lib64/libpoppler.so动态库。

为了简化这一操作,可以使用pkg-config文件。

如:

~/$ pkg-config --cflags --libs poppler  
-I/usr/include/poppler -lpoppler

GLIB API

如果在glib程序中使用Poppler,就可以使用Poppler的glib绑定。

还是使用pkg-config:

~/$ pkg-config --cflags --libs poppler-glib  
-I/usr/include/poppler/glib -I/usr/include/cairo -I/usr/include/freetype2 -I/usr/include/glib-2.0 -I/usr/li  
b64/glib-2.0/include -I/usr/include/libxml2 -I/usr/include/libpng16 -DWITH_GZFILEOP -I/usr/include/harfbuzz  
-I/usr/include/sysprof-6 -pthread -I/usr/include/pixman-1 -I/usr/include/poppler -lpoppler-glib -lgobject-  
2.0 -lglib-2.0 -lcairo

我们可以看到,头文件目录是/usr/include/poppler/glib,而连接的库也大量使用了glib的底层动态库。

如果我们真的使用Poppler的glib绑定,就会发现把PDF的页面导出成图片的时候,是使用的GdkPixbuf。

Qt API

如果使用Qt的绑定,就根据Qt的版本,还有不同的二进制库。对于Qt5、Qt6的分别是poppler-qt5-devel与poppler-qt6-devel。

我们后文全都使用poppler-qt6-devel举例。

还是使用pkg-config看一下:

~/$ pkg-config --cflags --libs poppler-qt6  
-I/usr/include/poppler/qt6 -I/usr/include/poppler -lpoppler-qt6

如果使用CMake构建,就可以使用CMake的PkgConfig来获取相应的变量,不再赘述。

打开PDF

使用poppler-qt6的时候,PDF相关的类位于Poppler名字空间,PDF文档的类是Document,即我们需要把PDF文档解析成Poppler::Document。

这个解析过程是load()静态方法。

需要注意的是,Poppler的Qt绑定,大量使用了智能指针来方便内存的管理。

比如上文提到的Poppler::Document::load()方法,返回的就是一个unique_ptrPoppler::Document。

所以,我们需要使用unique_ptrPoppler::Document来保存加载的PDF文件,在智能指针的作用域超出以后,Poppler::Document被自动释放。

另外,load函数的第一个参数,是一个QString。如果我们是C++的std::string,需要使用QString::fromLocal8Bit来转化成QString。

比如我们定义一个PDF类:

using namespace Poppler;

class PDF {
public:
    // 加载
    bool load(const std::string &filename);
    // 总页数
    int pageCount();
    // 页面大小
    QSizeF pageSizeF(int pagenum);

    // 搜索
    QList<QRectF> pageSearch (int pagenum, const string &str);

    // 渲染
    QImage pageRender (int pagenum, int ix, int iy, double zoom, int degree);

private:
    // 智能指针
    unique_ptr<Document> m_doc;
};

加载方法:

bool PDF::load(const std::string &filename)
{
    m_doc = Document::load (QString::fromLocal8Bit (filename));  
    if (m_doc == nullptr)  
    {      
         // 如果加载失败,尝试使用密码解锁
        auto text = QInputDialog::getText (nullptr, "password", "input password");  
        auto pass = QByteArray::fromStdString (text.toStdString ());  
        m_doc = Document::load (QString::fromStdString (filename), pass, pass);
    }

    if (m_doc)
        return true;
    else
        return false;

上面的过程,返回的doc就是一个unique_ptrPoppler::Document。

取得页面信息

通过unique_ptrPoppler::Document的numPages()方法,可以取得总页数。

int  
PDF::pageCount ()  
{  
  return m_doc->numPages ();  
}  

另外通过page(int pagenum)方法,可以取得一个unique_ptrPoppler::Page,这个Page支持的方法比较多,可以做各种操作。

比如,可以通过Poppler::Page的pageSizeF()方法,取得页面的大小。

QSizeF  
PDF::pageSize (int pagenum)  
{  
  auto page = m_doc->page (pn);  
  auto size = page->pageSizeF ();  
  return size;
}  

再比如,可以通过Poppler::Page的search()方法,搜索页面的文本:

QList<QRectF> 
PDF::pageSearch (int pn, const string &str)  
{  
  auto page = mDoc->page (pn);  
  auto results = page->search (QString::fromLocal8Bit(str));  
  return results;
}

返回的是一个模板类QList<QRectF>,每一个QRectF都是一个矩形。

渲染PDF

还可以通过Poppler::Page的renderToImage()方法,把一个页面渲染成一个图片。

但是renderToImage()方法,比前面介绍的稍微复杂一点儿。

它的原型是:

 QImage renderToImage(double xres = 72.0, double yres = 72.0, int x = -1, int y = -1, int w = -1, int h  
= -1, Rotation rotate = Rotate0) const;
  • 其中,xres、yres分别是横竖两个方向的字符大小。默认都是72.0。如果我们要缩放页面,就需要根据缩放比例调整这个值。
  • 而x、y是页面的左上角坐标,w是页面宽度,h是页面高度。如果我们要渲染页面的一部分,就可以灵活调整这4个数值。
  • 而Rotation是控制渲染的方向,默认是原始方向,即Rotate0,还可以是Rotate90、Rotate180、Rotate270,可以通过字面意思猜出来,这是页面旋转的角度。

所以,以下代码可以根据输出页面大小、缩放以及方向来渲染一个PDF页面为一个QImage。

QImage
ApvlvPDF::pageRender (int pagenum, int ix, int iy, double zoom, int degree)  
{  
  auto xres = 72.0, yres = 72.0;  
  xres *= zoom;  yres *= zoom;
  auto width = zoom * ix, height = zoom * iy;  
  
  auto prot = Poppler::Page::Rotate0;  
  if (degree == 90)  
    prot = Poppler::Page::Rotate90;  
  if (degree == 180)  
    prot = Poppler::Page::Rotate180;  
  if (degree == 270)  
    prot = Poppler::Page::Rotate270;  
  
  auto page = mDoc->page (pagenum);  
  auto image = page->renderToImage (xres, yres, 0, 0, width, height, prot);  
  
  return image;  
}  
09-21 07:41