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

        前些日子由于工作需要需要将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);
		}

    原文作者:hbkhbk907
    原文地址: https://blog.csdn.net/hbkhbk907/article/details/121150485
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞