Qt显示pdf系列4——封装pdfium库为动态库,显示pdf
来源:互联网 发布:2017java面试题 编辑:程序博客网 时间:2024/06/05 17:22
- 序
- 一新建项目
- 二封装pdfium库
- 三使用封装好的pdf动态库
- 1在vs中使用
- 2在qt中使用
- 四结语
承接上篇,pdfium的lib文件是已经编译出来了,理论上已经可以开始直接用了,官方提供的测试demo中基本上介绍了用法的整套流程,你可以选择导出一页页的(图片)文件,也可以直接取出Buffer丢给支持图形库去渲染。
序
但是需要注意的是,他在实际使用中依旧有很多不便:
1、我们能够编译出来的只有vs2015或以上版本的lib,如果我们需要在别的ide中引用,那么就可能不行。
2、编译出来的lib一共24个,所以确定要在项目中,光pdf库就引用这么多个吗= =。
解决方式:基于这些lib的基础上,再包一层,把他编译成动态库,接口用纯C语言,不仅简洁,而且理论上说是跨平台的。
关于动态库,只简单说几句,不了解可以自己查:静态链接库(.lib)cpp里所有代码被被编译成2进制文件,使用时直接会连接到你的项目中,而动态链接库,里面是你项目的代码,也不会编译进你的程序中。
开发环境:windows7+vs2015
一、新建项目:
打开vs2015,新建一个c++的win32项目,当然,你也可以直接新建成空项目,然后在设置里手动修改输出方式为dll。这里我的项目叫pdf
二、封装pdfium库:
1、把上篇编译出来的pdfium库的lib和头文件整理好,并添加到刚才新建的项目中去:
2、在项目中新建PdfManager.h,PdfManager.cpp。开始写代码:
PdfManager.h:
#ifndef _PDF_MANAGER_H_#define _PDF_MANAGER_H_//filename是文件名,需要唯一,dat是打开的pdf的内容(可以用fstream打开,以二进制形式),length是pdf长度extern "C" _declspec(dllexport) bool __stdcall PDFMANAGER_Loadpdf(const char* filename, char* dat, int length); //加载pdf(即加载已经打开的pdf)//filename是文件名,关闭该pdfextern "C" _declspec(dllexport) bool __stdcall PDFMANAGER_Closepdf(const char* filename); //关闭pdf//filename是文件名,关闭该pdf,page是需要显示页数,width是宽,height是高,size是当前页的大小,outBmp是是否导出bmp文件,返回该页bufferextern "C" _declspec(dllexport) char* __stdcall PDFMANAGER_LoadPage(const char* filename, int page, float& width, float& height, int& size, bool OutBmp);//加载页面//filename是文件名,关闭该页面,打开后需要关闭,否则会内存泄漏extern "C" _declspec(dllexport) void __stdcall PDFMANAGER_ClosePage(const char* filename, int page); //关闭页面//获取总页数extern "C" _declspec(dllexport) int __stdcall PDFMANAGER_GetPageCount(const char* filename);//获取下一页该渲染的页extern "C" _declspec(dllexport) int __stdcall PDFMANAGER_GetCurrentPage(const char* filename);#endif
PdfManager.cpp:
#include "PdfManager.h"#include "fpdfview.h"#include <limits.h>#include <stdlib.h>#include <string.h>#include <memory>#include <map>#include <sstream>#include <string>#include <utility>#include <vector>#include "fpdf_dataavail.h"#include "fpdf_edit.h"#include "fpdf_ext.h"#include "fpdf_formfill.h"#include "fpdf_text.h"#include <functional>#include <fstream>enum OutputFormat{ OUTPUT_STR, OUTPUT_BMP,};struct Options{ Options() :pages(false), output_format(OUTPUT_BMP) {} bool pages; //是否指定范围 OutputFormat output_format; int first_page = 0; //起始页数 int last_page = 0; //终止页数 int currentpage = 0; //要打印的页面 float width = 0; //目标宽度 float height = 0; //目标高度};FPDF_BOOL Is_Data_Avail(FX_FILEAVAIL* avail, size_t offset, size_t size){ return true;}class PDFManager{ static std::string WriteBmp(int num, const void* buffer, int stride, int width, int height) { if (stride < 0 || width < 0 || height < 0) return false; if (height > 0 && width > INT_MAX / height) return false; int out_len = stride * height; if (out_len > INT_MAX / 3) return ""; char filename[256]; snprintf(filename, sizeof(filename), "%d.bmp", num); FILE* fp = fopen(filename, "wb"); if (!fp) return ""; BITMAPINFO bmi = {}; bmi.bmiHeader.biSize = sizeof(bmi) - sizeof(RGBQUAD); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // top-down image bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = 0; BITMAPFILEHEADER file_header = {}; file_header.bfType = 0x4d42; file_header.bfSize = sizeof(file_header) + bmi.bmiHeader.biSize + out_len; file_header.bfOffBits = file_header.bfSize - out_len; fwrite(&file_header, sizeof(file_header), 1, fp); fwrite(&bmi, bmi.bmiHeader.biSize, 1, fp); fwrite(buffer, out_len, 1, fp); fclose(fp); return std::string(filename); } struct FPDF_FORMFILLINFO_PDFiumTest : public FPDF_FORMFILLINFO { // Hold a map of the currently loaded pages in order to avoid them // to get loaded twice. std::map<int, FPDF_PAGE> loaded_pages; // Hold a pointer of FPDF_FORMHANDLE so that PDFium app hooks can // make use of it. FPDF_FORMHANDLE form_handle; }; struct AvailDeleter { inline void operator()(FPDF_AVAIL avail) const { FPDFAvail_Destroy(avail); } }; static FPDF_FORMFILLINFO_PDFiumTest* ToPDFiumTestFormFillInfo(FPDF_FORMFILLINFO* form_fill_info) { return static_cast<FPDF_FORMFILLINFO_PDFiumTest*>(form_fill_info); } FPDF_PAGE GetPageForIndex(FPDF_FORMFILLINFO* param, FPDF_DOCUMENT& doc, int index) { FPDF_FORMFILLINFO_PDFiumTest* form_fill_info = ToPDFiumTestFormFillInfo(param); auto& loaded_pages = form_fill_info->loaded_pages; auto iter = loaded_pages.find(index); if (iter != loaded_pages.end()) return iter->second; FPDF_PAGE page = FPDF_LoadPage(doc, index); if (!page) return nullptr; FPDF_FORMHANDLE& form_handle = form_fill_info->form_handle; FORM_OnAfterLoadPage(page, form_handle); FORM_DoPageAAction(page, form_handle, FPDFPAGE_AACTION_OPEN); loaded_pages[index] = page; return page; } char* RenderPage(FPDF_DOCUMENT& doc, FPDF_FORMFILLINFO_PDFiumTest& form_fill_info , float& width, float& height, const Options& options, int& size) { FPDF_PAGE page = GetPageForIndex(&form_fill_info, doc, options.currentpage); if (!page) { return nullptr; } FPDF_TEXTPAGE text_page = FPDFText_LoadPage(page); double scale = 1.0; int widthdraw = static_cast<int>(FPDF_GetPageWidth(page) * scale); int heightdraw = static_cast<int>(FPDF_GetPageHeight(page) * scale); if (options.width > 0 && options.height > 0) { if (options.width > options.height) { double scalewidth = (double)options.height / (double)heightdraw; heightdraw = options.height; widthdraw = scalewidth * widthdraw; } else { double scaleheight = (double)options.width / (double)widthdraw; widthdraw = options.width; heightdraw = scaleheight * heightdraw; } } //else //{ // widthdraw = static_cast<int>(FPDF_GetPageWidth(page) * scale); // heightdraw = static_cast<int>(FPDF_GetPageHeight(page) * scale); //} int alpha = FPDFPage_HasTransparency(page) ? 1 : 0; FPDF_BITMAP bitmap = FPDFBitmap_Create(widthdraw, heightdraw, alpha); const char* buffer = nullptr; char* rtnBuffer = nullptr; int out_len = 0; if (bitmap) { FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF; FPDFBitmap_FillRect(bitmap, 0, 0, widthdraw, heightdraw, fill_color); FPDF_RenderPageBitmap(bitmap, page, 0, 0, widthdraw, heightdraw, 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER); //FPDF_FFLDraw(form, bitmap, page, 0, 0, width, height, 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER); int stride = FPDFBitmap_GetStride(bitmap); int strheight = FPDFBitmap_GetHeight(bitmap); buffer = reinterpret_cast<const char*>(FPDFBitmap_GetBuffer(bitmap)); //开辟新内存 size = stride * strheight; rtnBuffer = new char[size]; memset(rtnBuffer, 0, size); memcpy(rtnBuffer, buffer, size); width = FPDFBitmap_GetWidth(bitmap); height = FPDFBitmap_GetHeight(bitmap); Pages_.insert(std::make_pair(options.currentpage, rtnBuffer)); switch (options.output_format) { case OUTPUT_BMP: WriteBmp(options.currentpage, buffer, stride, widthdraw, heightdraw); break; default: break; } FPDFBitmap_Destroy(bitmap); } /*else fprintf(stderr, "Page was too large to be rendered.\n");*/ form_fill_info.loaded_pages.erase(options.currentpage); /*FORM_DoPageAAction(page, form, FPDFPAGE_AACTION_CLOSE); FORM_OnBeforeClosePage(page, form);*/ FPDFText_ClosePage(text_page); FPDF_ClosePage(page); return rtnBuffer; } class TestLoader { public: TestLoader(const char* buff, size_t len) { m_pBuf = new char[len]; memcpy(m_pBuf, buff, len); m_Len = len; } ~TestLoader() { delete[]m_pBuf; } static int GetBlock(void* param, unsigned long pos, unsigned char* pBuf, unsigned long size) { TestLoader* pLoader = static_cast<TestLoader*>(param); if (pos + size < pos || pos + size > pLoader->m_Len) return 0; memcpy(pBuf, pLoader->m_pBuf + pos, size); return 1; } private: char* m_pBuf; size_t m_Len; }; void RenderPdf(const char* pBuf, size_t len, const Options& options) { memset(&platform_callbacks_, '\0', sizeof(platform_callbacks_)); platform_callbacks_.version = 3; form_callbacks_.version = 1; form_callbacks_.m_pJsPlatform = &platform_callbacks_; Loader_ = std::move(std::unique_ptr<TestLoader>(new TestLoader(pBuf, len))); File_access_ = std::make_unique<FPDF_FILEACCESS>(); memset(File_access_.get(), '\0', sizeof(File_access_.get())); File_access_->m_FileLen = static_cast<unsigned long>(len); File_access_->m_GetBlock = TestLoader::GetBlock; File_access_->m_Param = Loader_.get(); memset(&File_avail_, '\0', sizeof(File_avail_)); File_avail_.version = 1; File_avail_.IsDataAvail = Is_Data_Avail; memset(&Hints_, '\0', sizeof(Hints_)); Hints_.version = 1; int nRet = PDF_DATA_NOTAVAIL; Pdf_avail_ = FPDFAvail_Create(&File_avail_, File_access_.get()); std::unique_ptr<void, PDFManager::AvailDeleter> scoped_pdf_avail_deleter(Pdf_avail_); if (FPDFAvail_IsLinearized(Pdf_avail_) == PDF_LINEARIZED) { this->PdfDoc_ = FPDFAvail_GetDocument(Pdf_avail_, nullptr); if (this->PdfDoc_) { while (nRet == PDF_DATA_NOTAVAIL) nRet = FPDFAvail_IsDocAvail(Pdf_avail_, &Hints_); if (nRet == PDF_DATA_ERROR) { fprintf(stderr, "Unknown error in checking if doc was available.\n"); FPDF_CloseDocument(this->PdfDoc_); return; } nRet = FPDFAvail_IsFormAvail(Pdf_avail_, &Hints_); if (nRet == PDF_FORM_ERROR || nRet == PDF_FORM_NOTAVAIL) { fprintf(stderr, "Error %d was returned in checking if form was available.\n", nRet); FPDF_CloseDocument(this->PdfDoc_); return; } this->bIsLinearized_ = true; } } else { this->PdfDoc_ = FPDF_LoadCustomDocument(File_access_.get(), nullptr); } if (!this->PdfDoc_) { unsigned long err = FPDF_GetLastError(); return; } (void)FPDF_GetDocPermissions(this->PdfDoc_); PageCount = FPDF_GetPageCount(this->PdfDoc_); }public: PDFManager(); ~PDFManager(); void Init(char *dat, int length); void Close(); char* GetPdfPage(int page, float& width, float& height, int& size, bool OutBmp = false);//返回长度 void ClosePdfPage(int page); int PageCount = 0; int PageCurrent = 0; int PageBad = 0;private: FPDF_DOCUMENT PdfDoc_ = nullptr;//文档 //FPDF_FORMHANDLE Form_ = nullptr; bool bIsLinearized_ = false; //是否线性化 FPDF_FORMFILLINFO_PDFiumTest form_callbacks_; IPDF_JSPLATFORM platform_callbacks_; FX_DOWNLOADHINTS Hints_;// FX_FILEAVAIL File_avail_; std::unique_ptr<FPDF_FILEACCESS> File_access_; FPDF_AVAIL Pdf_avail_; std::unique_ptr<TestLoader> Loader_; char* File_contents_ = nullptr; int File_length_ = 0; std::map<int, char*> Pages_; //保存页面数据};PDFManager::PDFManager(){}PDFManager::~PDFManager(){ for (auto &buffer : Pages_) { delete[]buffer.second; } Pages_.clear(); this->Close();}void PDFManager::Init(char *dat, int length){ File_contents_ = dat; File_length_ = length; Options options; options.output_format = OutputFormat::OUTPUT_BMP; if (!File_contents_) return; RenderPdf(File_contents_, File_length_, options);}char* PDFManager::GetPdfPage(int page, float& width, float& height, int& size, bool OutBmp){ if (this->bIsLinearized_) { this->Close(); return 0; } if (page < 0) return 0; Options options; options.currentpage = page; options.width = width; options.height = height; if (!OutBmp) options.output_format = OUTPUT_STR; PageCurrent = page; PageCurrent++; return RenderPage(this->PdfDoc_, form_callbacks_, width, height, options, size);}void PDFManager::ClosePdfPage(int page){ std::map<int, char*>::iterator itor = Pages_.find(page); if (itor != Pages_.end()) { delete[]itor->second; Pages_.erase(page); }}void PDFManager::Close(){ if (!this->PdfDoc_) return; FPDF_CloseDocument(this->PdfDoc_);}//接口函数std::map<std::string, PDFManager*> g_pdfmanagersmap;bool __stdcall PDFMANAGER_Loadpdf(const char* filename, char* dat, int length){ if (!filename) { return false; } if (g_pdfmanagersmap.size() == 0) { FPDF_LIBRARY_CONFIG config; config.version = 2; config.m_pUserFontPaths = nullptr; config.m_pIsolate = nullptr; config.m_v8EmbedderSlot = 0; FPDF_InitLibraryWithConfig(&config); } std::string file = filename; std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file); if (itor != g_pdfmanagersmap.end()) { itor->second->Init(dat, length); } else { PDFManager* pdfmanager = new PDFManager(); g_pdfmanagersmap.insert(std::make_pair(file, pdfmanager)); pdfmanager->Init(dat, length); } return true;}bool __stdcall PDFMANAGER_Closepdf(const char* filename){ if (!filename) { return false; } std::string file = filename; std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file); if (itor != g_pdfmanagersmap.end()) { delete itor->second; g_pdfmanagersmap.erase(file); if (g_pdfmanagersmap.size() == 0) { FPDF_DestroyLibrary(); } } return true;}char* __stdcall PDFMANAGER_LoadPage(const char* filename, int page, float& width, float& height, int& size, bool OutBmp){ if (!filename) { return nullptr; } std::string file = filename; std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file); if (itor != g_pdfmanagersmap.end()) { return itor->second->GetPdfPage(page, width, height, size, OutBmp); } return nullptr;}void __stdcall PDFMANAGER_ClosePage(const char* filename, int page){ if (!filename) { return; } std::string file = filename; std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file); if (itor != g_pdfmanagersmap.end()) { itor->second->ClosePdfPage(page); }}int __stdcall PDFMANAGER_GetPageCount(const char* filename){ if (!filename) { return 0; } std::string file = filename; std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file); if (itor != g_pdfmanagersmap.end()) { return itor->second->PageCount; } return 0;}int __stdcall PDFMANAGER_GetCurrentPage(const char* filename){ if (!filename) { return 0; } std::string file = filename; std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file); if (itor != g_pdfmanagersmap.end()) { return itor->second->PageCurrent; } return 0;}
注释说的比较清楚,就不解释了。
3、添加模块文件:
我取名为PdfManager.def,在里面添加如下代码:
LIBRARY "Pdf"EXPORTSPDFMANAGER_Loadpdf @ 1PDFMANAGER_Closepdf @ 2PDFMANAGER_LoadPage @ 3PDFMANAGER_ClosePage @ 4PDFMANAGER_GetPageCount @ 5PDFMANAGER_GetCurrentPage @ 6
即指定要编译成dll的接口函数。
4、编译,最后整理成pdf库:
直接编译release版本就可以了,通用。
三、使用封装好的pdf动态库:
1、在vs中使用:
1.1 先新建测试项目
1.2 添加pdf库
1.2 添加代码:
main.cpp:
#include <iostream>#include <fstream>#include "PdfManager.h"#include <memory>#include <windows.h>char* GetFileContents(const char* filename, size_t *size){ std::fstream instream; instream.open(filename, std::ios::_Nocreate | std::ios::binary); if (!instream.is_open()) { std::printf("open file Failed!\n"); std::printf("%s\n", filename); instream.close(); return nullptr; } instream.seekg(0, std::ios::end); *size = instream.tellg(); instream.seekg(0, std::ios::beg); char *buffer = new char[*size]; instream.read(buffer, *size); instream.close(); return buffer;}int main(){ char* name = "test.pdf"; size_t length; std::unique_ptr<char> filecontents(GetFileContents(name, &length)); PDFMANAGER_Loadpdf(name, filecontents.get(), length); int counts = PDFMANAGER_GetPageCount(name); for (int i = 0; i < counts; ++i) { if (GetAsyncKeyState(VK_F2) & 0x8000) break; float width, height = 0; int length = 0; PDFMANAGER_LoadPage(name, i, width, height, length, true); PDFMANAGER_ClosePage(name, i); } PDFMANAGER_Closepdf(name); system("pause"); return 0;}
结果如图(数字.bmp是渲染出来的每一页,我只渲染完三页就关了):
2、在qt中使用:
2.1新建qt项目
2.2 导入pdf动态库
2.3关键代码(ui逻辑代码就不贴了,其实就是主要讲怎么用,怎么渲染):
PDFForm::PDFForm(const char *filename, QByteArray &bytearray, QWidget *parent) : QWidget(parent), ui(new Ui::PDFForm){ ui->setupUi(this); FileLength_ = bytearray.length(); File_ = new char[FileLength_]; std::memcpy(File_, bytearray.toStdString().c_str(), FileLength_); FileName_ = filename; Picture_ = new PictureBox(); Picture_->setMode(PictureBox::PB_MODE::FIX_SIZE_CENTRED ); this->ui->verticalLayout_2->addWidget(Picture_);}void PDFForm::InitFile(){ if(HaveLoadPdf_) return; int length = (int)FileLength_; PDFMANAGER_Loadpdf(FileName_.c_str(), File_, length); HaveLoadPdf_ = true; PageCount_ = PDFMANAGER_GetPageCount(FileName_.c_str()); this->LoadPage(0);}void PDFForm::LoadPage(int page){ if(!HaveLoadPdf_) return; int length = 0; QRect rect = this->ui->verticalLayout_2->geometry(); float width = rect.width(); float height = rect.height(); char* Buffer = PDFMANAGER_LoadPage(FileName_.c_str(),page, width, height, length, false); QImage image((uchar*)Buffer, width, height, QImage::Format_RGBA8888); this->Picture_->setImage(image); PDFMANAGER_ClosePage(FileName_.c_str(),page);//释放页面内存 UpdateLabelPage();}
效果如下图:
到这里就完成了需要在qt中显示或打开pdf的需求,还做了适配,可以直接拉伸缩小,而且最主要的是,因为其本质是位图,放大缩小时重新loadpage可以实现分辨率也不断放大缩小。
四、结语:
1、本系列到此结束,其实还有些东西,比如在qt里做显示适配,例如上面的Picture_对象就是自定义的一个image类,在界面上显示时方便居中,要写的话还可以写第五篇,但是我暂时没空,就打住了,以后会不会补需要再看看。
2、本次研究说实话让我提升很多,很多是思维和经验上的改变,独立能力稍微有点加强,希望能早日成为大牛。
17.05.19更新:
代码地址:
传送门
17.07.27更新:
大家不用留邮箱了,我不是经常上这个帐号,下载连接也贴了,也不花积分,直接下载就好了(ps:我自己下了,也叫别人下了,文件没问题,下载下来有问题重新试试,csdn是经常抽风,网络不是很稳定)
17.08.09更新:
鉴于很多人问我要在QT中打开的demo,现在补上(如果觉得不错,不妨点个顶或者关注,你的鼓励是对我最大的支持):
传送门
另外 需要1积分,不知道为啥,现在csdn资源最少1积分,包括我以前的0分资源全部自动调整为1分,特此说明。
- Qt显示pdf系列4——封装pdfium库为动态库,显示pdf
- Qt显示PDF之四pdfium封装
- Qt显示pdf系列3——配置编译谷歌开源项目pdfium
- Qt显示PDF之三 pdfium编译
- Qt显示PDF系列之一
- Qt显示pdf系列1——序言,扯淡,选择相关库及方式等
- Qt显示pdf系列2——QAxWidget打开Office文件及pdf
- PDFium-PDF开源之旅(2)——运行调试pdfium自带的测试程序
- pdf显示
- 基于pdfium获取pdf目录
- Qt显示PDF之二 QAxWidget, QAxWidget
- JFreeChart存储为PDF,支持中文显示
- pdf转化为图片显示知多少
- 如何使SharePoint文档库能为PDF文件显示正确的图标
- 如何使SharePoint文档库能为PDF文件显示正确的图标
- Qt显示pdf之五在Qt中使用
- jasperreport显示PDF格式
- 显示/下载PDF文件
- IVTC/Deinterlace的来龙去脉
- 【ROBOT XRT1】 玩转 WS2812彩色LED灯
- lavarel5.2中下拉菜单类型的搜索
- PTA平台 自测-3 数组元素循环右移问题
- C#定时器的方式实时显示系统时间
- Qt显示pdf系列4——封装pdfium库为动态库,显示pdf
- maven---------pom配置文件标签的含义
- 100:Ugly Number
- pcDuino3B更新为ubuntu14.04系统并安装ros-indigo(armhf)过程说明
- [POI2017] Flappy Bird 解题报告
- SPRING INJECTION WITH @RESOURCE, @AUTOWIRED AND @INJECT
- 20分钟理解React Native For Android原理
- Leetcode OJ:Add Two Numbers
- ~对称矩阵的压缩存储~