承接上篇,pdfium的lib文件是已经编译出来了,理论上已经可以开始直接用了,官方提供的测试demo中基本上介绍了用法的整套流程,你可以选择导出一页页的(图片)文件,也可以直接取出Buffer丢给支持图形库去渲染。
但是需要注意的是,他在实际使用中依旧有很多不便:
1、我们能够编译出来的只有vs2015或以上版本的lib,如果我们需要在别的ide中引用,那么就可能不行。
2、编译出来的lib一共24个,所以确定要在项目中,光pdf库就引用这么多个吗= =。
解决方式:基于这些lib的基础上,再包一层,把他编译成动态库,接口用纯C语言,不仅简洁,而且理论上说是跨平台的。
关于动态库,只简单说几句,不了解可以自己查:静态链接库(.lib)cpp里所有代码被被编译成2进制文件,使用时直接会连接到你的项目中,而动态链接库,里面是你项目的代码,也不会编译进你的程序中。
开发环境:windows7+vs2015
打开vs2015,新建一个c++的win32项目,当然,你也可以直接新建成空项目,然后在设置里手动修改输出方式为dll。这里我的项目叫pdf
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是文件名,关闭该pdf
extern "C" _declspec(dllexport) bool __stdcall PDFMANAGER_Closepdf(const char* filename); //关闭pdf
//filename是文件名,关闭该pdf,page是需要显示页数,width是宽,height是高,size是当前页的大小,outBmp是是否导出bmp文件,返回该页buffer
extern "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
#include
#include
#include
#include
#include
#include
#include
#include
#include "fpdf_dataavail.h"
#include "fpdf_edit.h"
#include "fpdf_ext.h"
#include "fpdf_formfill.h"
#include "fpdf_text.h"
#include
#include
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(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(FPDF_GetPageWidth(page) * scale);
// heightdraw = static_cast(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(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(new TestLoader(pBuf, len)));
File_access_ = std::make_unique();
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 File_access_;
FPDF_AVAIL Pdf_avail_;
std::unique_ptr 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、添加模块文件:
LIBRARY "Pdf"
EXPORTS
PDFMANAGER_Loadpdf @ 1
PDFMANAGER_Closepdf @ 2
PDFMANAGER_LoadPage @ 3
PDFMANAGER_ClosePage @ 4
PDFMANAGER_GetPageCount @ 5
PDFMANAGER_GetCurrentPage @ 6
即指定要编译成dll的接口函数。
4、编译,最后整理成pdf库:
直接编译release版本就可以了,通用。
1.1 先新建测试项目
1.2 添加pdf库
1.2 添加代码:
main.cpp:
#include
#include
#include "PdfManager.h"
#include
#include
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.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分,特此说明。