如果只是读取、渲染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;
}