基于PDF和JSPDF实现调整pdf文件大小功能

时间:2024-09-30 18:15:38

        前些日子由于工作需要需要将pdf文件变小,市面上不少软件都有这个功能,但都不属于免费功能。此外部分网站提供免费的在线压缩功能,但是考虑到文件比较重要,使用在线压缩存在文件泄漏风险,所以萌发了自己写一个基于html页面压缩pdf大小页面的念头。

        在不借助软件和工具的情况下,我能想到压缩方案是将pdf每一页截图然后通过调整图片像素的方法,来实现pdf压缩,而该过程也是我后面实现的思路。网上搜索场景的pdf压缩方案是基于将canvas转成图片生成pdf文件,通过调整生成图片时候的尺寸来实现pdf文件大小调整的功能。

        将整个任务分解,可以分为两个部分:

                1.将pdf展示在页面上;

                2.将页面上展示的pdf压缩大小重新生成pdf;

        通过一番搜索,第一步选定借助来展示页面,由于本人前端水平有限,最终页面整个采用pdfjs在github上的模板(Getting Started ())。该样例页面提供了pdf在线预览、打印、下载等功能,同时支持在线预览调整页面比例,完美的匹配了第一步的要求,由于浏览器支持问题,我选择的是pdfjs-2.10.377-legacy-dist版本。

        第二步是在页面中引入jspdf,来实现将canvas标签转化为pdf文件。由于jspdf在github上的最新源码ES6模块部署,这就进入知识的盲区,而且本人也不希望开发一个需要部署的页面,所以采用了一个历史版本(版本号1.3.3)。
        两个js整合的过程异常顺利,基于当前页面canvas的标签下载pdf文件代码如下:

  1. var pdf = new jsPDF('p','mm','a4', true);
  2. var canvasList = document.body.getElementsByTagName("canvas");
  3. for(var i=0; i < canvasList.length; i++){
  4. var canvas = canvasList[i];
  5. var imgData = canvas.toDataURL('image/jpeg', 1.0);
  6. pdf.addImage(imgData, 'JPEG', 0, 0,210,297);
  7. if(i!=canvasList.length-1){
  8. pdf.addPage();
  9. }
  10. }
  11. pdf.save(PDFViewerApplication.baseUrl);

因为展示的页面根据pdf文件每一页生成对应的一个canvas,所以整个页面中不需要去换算canvas尺寸与a4纸尺寸的比例,进而进行分页操作,只需要将页面中的canvas标签列表获取,并将每个标签作为一页内容生成文件。

        正因为页面完全使用了的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方法,将翻页事件按照等待时间加入等待队列,而在等待的时间里页面渲染线程执行之前事件触发的工作。

代码一(原始版):

  1. var setTimeWaitStep = 200;
  2. var setTime = 0;
  3. var viewer = PDFViewerApplication.pdfViewer;
  4. var pageList = viewer._pages;
  5. for(var i=0; i<pageList.length;i++){
  6. setTime += setTimeWaitStep;
  7. setTimeout("<发起翻页事件>", setTime);
  8. }
  9. //根据当前页面缩放比例以及清晰度导出调整后的pdf文件
  10. setTime += setTimeWaitStep;
  11. setTimeout("<执行下载>", setTime);

由于原始版指定了时间间隔,然而在时间间隔内仍旧存在部分页面加载时间不足,导致最终生成的pdf文件缺失部分页面的情况,进而进行优化,得到代码二。

代码二(最终版):

  1. var setTimeWaitStep = 300;
  2. var setTimeWaitAddStep = 100;
  3. function compressdownload(){
  4. //确保所有页面被加载
  5. var viewer = PDFViewerApplication.pdfViewer;
  6. var pageList = viewer._pages;
  7. var setTime = 0;
  8. var check = true;
  9. var count = 0;
  10. var length = pageList.length;
  11. for(var i=0; i< length;i++){
  12. //确定对应页面canvas标签生成,true:已生成 false:未生成
  13. if(!loadConfirm(i+1)){
  14. check = false;
  15. setTime += setTimeWaitStep;
  16. setTimeout("<发起翻页事件>", setTime);
  17. count++;
  18. }
  19. }
  20. //检测有页面没有加载成功
  21. if(!check){
  22. //如果有半数以上页面加载失败,则增加渲染间隔时间
  23. if(count >= (length/2)){
  24. setTimeWaitStep += setTimeWaitAddStep;
  25. }
  26. //递归添加压缩导出执行
  27. setTime += setTimeWaitStep;
  28. setTimeout("compressdownload()", setTime);
  29. return;
  30. }
  31. //根据当前页面缩放比例以及清晰度导出调整后的pdf文件
  32. var pdf = new jsPDF('p','mm','a4', true);
  33. var canvasList = document.body.getElementsByTagName("canvas");
  34. //(canvasList);
  35. for(var i=0; i < canvasList.length; i++){
  36. var canvas = canvasList[i];
  37. var imgData = canvas.toDataURL('image/jpeg', 1.0);
  38. pdf.addImage(imgData, 'JPEG', 0, 0,210,297);
  39. if(i!=canvasList.length-1){
  40. pdf.addPage();
  41. }
  42. }
  43. pdf.save(PDFViewerApplication.baseUrl);
  44. }
'
运行