前些日子由于工作需要需要将pdf文件变小,市面上不少软件都有这个功能,但都不属于免费功能。此外部分网站提供免费的在线压缩功能,但是考虑到文件比较重要,使用在线压缩存在文件泄漏风险,所以萌发了自己写一个基于html页面压缩pdf大小页面的念头。
在不借助软件和工具的情况下,我能想到压缩方案是将pdf每一页截图然后通过调整图片像素的方法,来实现pdf压缩,而该过程也是我后面实现的思路。网上搜索场景的pdf压缩方案是基于jspdf.js将canvas转成图片生成pdf文件,通过调整生成图片时候的尺寸来实现pdf文件大小调整的功能。
将整个任务分解,可以分为两个部分:
1.将pdf展示在页面上;
2.将页面上展示的pdf压缩大小重新生成pdf;
通过一番搜索,第一步选定借助pdf.js来展示页面,由于本人前端水平有限,最终页面整个采用pdfjs在github上的模板(Getting Started (mozilla.github.io))。该样例页面提供了pdf在线预览、打印、下载等功能,同时支持在线预览调整页面比例,完美的匹配了第一步的要求,由于浏览器支持问题,我选择的是pdfjs-2.10.377-legacy-dist版本。
第二步是在页面中引入jspdf,来实现将canvas标签转化为pdf文件。由于jspdf在github上的最新源码ES6模块部署,这就进入知识的盲区,而且本人也不希望开发一个需要部署的页面,所以采用了一个历史版本jspdf.debug.js(版本号1.3.3)。
两个js整合的过程异常顺利,基于当前页面canvas的标签下载pdf文件代码如下:
var pdf = new jsPDF('p','mm','a4', true);
var canvasList = document.body.getElementsByTagName("canvas");
for(var i=0; i < canvasList.length; i++){
var canvas = canvasList[i];
var imgData = canvas.toDataURL('image/jpeg', 1.0);
pdf.addImage(imgData, 'JPEG', 0, 0,210,297);
if(i!=canvasList.length-1){
pdf.addPage();
}
}
pdf.save(PDFViewerApplication.baseUrl);
因为pdf.js展示的页面根据pdf文件每一页生成对应的一个canvas,所以整个页面中不需要去换算canvas尺寸与a4纸尺寸的比例,进而进行分页操作,只需要将页面中的canvas标签列表获取,并将每个标签作为一页内容生成文件。
正因为页面完全使用了pdf.js的demo,使得我在后续添加压缩下载功能的时候吃尽了苦头。碰到的具体问题以及解决方案如下:
问题1:demo页面预览只有10页,如果打开大于10页的pdf文件,只能看到前面10页的内容。
解决方案:
需要修改viewer.js中DEFAULT_CACHE_SIZE、DEFAULT_VIEW_HISTORY_CACHE_SIZE两个参数进行扩增,我把这两个参数调整为20。
问题2:demo中页面采用了延迟加载的技术,所以打开文件后,整个页面只会生成当前页面和下一页的canvas标签并进行渲染,然后通过滚动滑轮触发滚动事件,加载当前页与下一页内容。在这种情况下如果打开文件后直接下载会导致只有前面2页的内容,想要获得完整的内容需要手动滚动到最后一页,实现全部页面加载。
其解决方案:
在压缩下载之前实现全页面的加载,由于整个页面加载、渲染都是通过事件异步进行且整个流程异常复杂,一开始尝试在点击事件中试图直接加载全部内容,然而整个流程过于复杂,无法将页面渲染的所有逻辑都捕捉完全,所以最终没能走通这条路(方案一)。继而考虑发起事件,实现从头到尾页面自动翻页,从而触发页面渲染逻辑,然而实际操作结果是只有最后一页的内容被加载,因为js线程是单线程的,与页面渲染线程是互斥的,两者同时只能有一个线程操作dom树。所以指望js线程中发起一个翻页事件,又指望另外一个线程同步执行页面渲染的想法终究不可行(方案二)。
在方案一和方案二反复多次横跳后,最终想到采用异步发起翻页事件,并且在翻页事件之间提供时间间隔,让渲染页面的线程有足够的时间执行渲染工作。通过采用setTimeout方法,将翻页事件按照等待时间加入等待队列,而在等待的时间里页面渲染线程执行之前事件触发的工作。
代码一(原始版):
var setTimeWaitStep = 200;
var setTime = 0;
var viewer = PDFViewerApplication.pdfViewer;
var pageList = viewer._pages;
for(var i=0; i<pageList.length;i++){
setTime += setTimeWaitStep;
setTimeout("<发起翻页事件>", setTime);
}
//根据当前页面缩放比例以及清晰度导出调整后的pdf文件
setTime += setTimeWaitStep;
setTimeout("<执行下载>", setTime);
由于原始版指定了时间间隔,然而在时间间隔内仍旧存在部分页面加载时间不足,导致最终生成的pdf文件缺失部分页面的情况,进而进行优化,得到代码二。
代码二(最终版):
var setTimeWaitStep = 300;
var setTimeWaitAddStep = 100;
function compressdownload(){
//确保所有页面被加载
var viewer = PDFViewerApplication.pdfViewer;
var pageList = viewer._pages;
var setTime = 0;
var check = true;
var count = 0;
var length = pageList.length;
for(var i=0; i< length;i++){
//确定对应页面canvas标签生成,true:已生成 false:未生成
if(!loadConfirm(i+1)){
check = false;
setTime += setTimeWaitStep;
setTimeout("<发起翻页事件>", setTime);
count++;
}
}
//检测有页面没有加载成功
if(!check){
//如果有半数以上页面加载失败,则增加渲染间隔时间
if(count >= (length/2)){
setTimeWaitStep += setTimeWaitAddStep;
}
//递归添加压缩导出执行
setTime += setTimeWaitStep;
setTimeout("compressdownload()", setTime);
return;
}
//根据当前页面缩放比例以及清晰度导出调整后的pdf文件
var pdf = new jsPDF('p','mm','a4', true);
var canvasList = document.body.getElementsByTagName("canvas");
//console.log(canvasList);
for(var i=0; i < canvasList.length; i++){
var canvas = canvasList[i];
var imgData = canvas.toDataURL('image/jpeg', 1.0);
pdf.addImage(imgData, 'JPEG', 0, 0,210,297);
if(i!=canvasList.length-1){
pdf.addPage();
}
}
pdf.save(PDFViewerApplication.baseUrl);
}