用js生成PDF的方案

在java里,我们常用Itext来生成pdf,在pdf文件里组合图片,文字,画表格,画线等操作,还会遇到中文支持的问题。

那好,现在想直接在web前端就生成pdf怎么办,目前有以下几个解决方案

1:JSPDF.js

这个库支持不同类型的PDF文件格式,包括:文本,数字,图形,图片,同时你可以自由的编辑标题或者其它类型元素。

还支持互动的内容制作,例如,你可以输入文字或者数字,然后jsPDF帮助生成最后的PDF内容。

缺点,不支持中文,不支持svg导入(解决方案:svg+html转换成canvas)

http://jspdf.com/

示例代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> 
<head> 
<title>Downloadify</title> 
<meta http-equiv="content-type" content="text/html;charset=utf-8"> 
<style type="text/css" media="screen"> 
body {background: #fff; width: 500px; margin: 20px auto;} 
input, textarea, p { font-family: 宋体, 黑体; font-size: 12pt;} 
input, textarea { border: solid 1px #aaa; padding: 4px; width: 98%;} 
</style> 
<script type="text/javascript" src="js/swfobject.js"></script> 
<script type="text/javascript" src="js/downloadify.js"></script> 
<script type="text/javascript" src="js/jspdf.js"></script> 
<!-- <script type="text/javascript" src="js/downloadify.min.js"></script> --> 
<script type="text/javascript"> 
window.load=function(){ 
Downloadify.create('downloadify',{ 
filename: function(){ 
return document.getElementById('filename').value; 
}, 
data: function(){ 
var doc = new jsPDF(); 
doc.text(20, 20, document.getElementById('data').value); 
doc.addPage(); 
doc.text(20, 20, document.getElementById('data').value); 
return doc.output(); 
}, 
onComplete: function(){ alert('成功保存文件!'); }, 
onCancel: function(){ alert('您已经取消保存文件'); }, 
onError: function(){ alert('出现错误了'); }, 
swf: 'js/downloadify.swf', 
downloadImage: 'js/download.png', 
width: 100, 
height: 30, 
transparent: true, 
append: false 
}); 

</script> 
</head> 
<body onload="load();"> 
<input type="text" name="filename" value="文件名.pdf" id="filename" /><br /> 
<textarea cols="60" rows="10" name="data" id="data">it seem do not support to Chinese</textarea> 
<p id="downloadify">You must have Flash 10 installed to download this file.</p> 
</body> 
</html>

2:pdfmake

pdfmake的基本使用方法

1.包含以下两个文件

1
2
<script src= "build/pdfmake.min.js" ></script>
  <script src= "build/vfs_fonts.js" ></script>

2.在JS代码中声明一个Document-definition对象,这个是pdfmake自己的术语。简单点说,就是创建一个至少包含content属性的对象。然后就可以调用pdfMake的方法导出PDF,具体见如下代码:

1
2
3
4
5
6
7
8
9
10
11
<script type= "text/javascript" >
  //创建Document-definition对象 
  var  dd = {
       content: [
        'One paragraph' ,
        'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines'
        ]
     };
  //导出PDF
  pdfMake.createPdf(dd).download();
  </script>

pdfmake支持中文

三个步骤:

1.到pdfmake项目网站,把工程都下载下来,然后进入工程目录将字体文件(比如微软雅黑.ttf)放到examples/fonts目录下,然后使用grunt dump_dir生成新的vfs_fonts.js文件;

从上面描述可知该工程是通过grunt管理的,如果无相关知识,请上网先补习下。

grunt dump_dir命令会将fonts目录下所有文件都打包,因此无用文件请别放进去。

微软雅黑.ttf网上一搜一大把,WINDOWS电脑系统盘下存放字体的目录也找得到。

2.回到自己的例子代码中,JS代码中修改pdfMake的fonts对象,声明当前要用到字体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pdfMake.fonts = {
      Roboto: {
        normal: 'Roboto-Regular.ttf' ,
        bold: 'Roboto-Medium.ttf' ,
        italics: 'Roboto-Italic.ttf' ,
        bolditalics: 'Roboto-Italic.ttf'
      },
      微软雅黑: {
        normal: '微软雅黑.ttf' ,
        bold: '微软雅黑.ttf' ,
        italics: '微软雅黑.ttf' ,
        bolditalics: '微软雅黑.ttf' ,
      }
    };

3.Document-definition对象中声明默认要使用的字体,具体来说:就是声明一个对象,除了content属性,还要有一个defaultStyle属性,该defaultStyle下面还有再有一个font属性:

1
2
3
4
5
6
7
8
9
var  dd = {
       content: [
        '中英文测试' ,
        'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines'
        ],
        defaultStyle: {
          font: '微软雅黑'
        }
     };

以下为根据如上步骤做的一个完整例子源码:

<!DOCTYPE html>
<html lang= "zh-CN" >
   <head>
   <meta charset= "utf-8" >
   <title>my first export PDF</title>
   <script src= "build/pdfmake.min.js" ></script>
   <script src= "build/vfs_fonts.js" ></script>
   <script>
   function  down() {
     var  dd = {
       content: [
        '中英文测试' ,
        'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines'
        ],
        defaultStyle: {
          font:  '微软雅黑'
        }
     };
     pdfMake.fonts = {
       Roboto: {
         normal:  'Roboto-Regular.ttf' ,
         bold:  'Roboto-Medium.ttf' ,
         italics:  'Roboto-Italic.ttf' ,
         bolditalics:  'Roboto-Italic.ttf'
       },
       微软雅黑: {
         normal:  '微软雅黑.ttf' ,
         bold:  '微软雅黑.ttf' ,
         italics:  '微软雅黑.ttf' ,
         bolditalics:  '微软雅黑.ttf' ,
       }
     };
     pdfMake.createPdf(dd).download();
   }
   </script>
   </head>
   <body>
   <button onclick= "down()" >下载</button>
   </body>
</html>

插入图片

在插入图片方面,jsPDF要求先将图片转换成Data URL才行,而pdfmake允许直接指定路径,看起来是很方便,但这是有条件的,必须是以node.js作为服务器,或者将图片放到vfs_fonts.js中,所以总的来说,用处不大,还是一样得将图片转换成Data URL形式才行。

为解决此问题,我写了一个ImageDataURL的函数对象,可同时传入多个图片地址。在图片都加载完成后,ImageDataURL.oncomplete将被触发,在回调中通过this.imgdata取出各个图片的Data URL,根据pdfmake的要求组织下,就可正确生成pdf了。

ImageDataURL的原理是通过H5的canvas标签,将图片绘制在canvas上,然后通过canvas的toDataURL得到图像的Data URL。使用时请注意浏览器兼容性问题。

以下为将sampleImage.jpg, sampleage.jpg, sampleImage.jpg依次写入PDF的例子,测试时sampleage.jpg不存在,PDF直接忽略。

<!DOCTYPE html>
<html lang= "zh-CN" >
   <head>
   <meta charset= "utf-8" >
   <title>my second export PDF</title>
   <script src= "build/pdfmake.min.js" ></script>
   <script src= "build/vfs_fonts.js" ></script>
   <script>
    
   function  down() {
     var  x =  new  ImageDataURL([ "sampleImage.jpg" "samplage.jpg" "sampleImage.jpg" ]);
     x.oncomplete =  function () {
       var  imgs =  new  Array();
       console.log( "complete" );
       for  (key  in  this .imgdata) {
         if  ( this .imgdata[key] ==  this .emptyobj) //不存在的图片直接忽略
           continue ;
         imgs.push({image: this .imgdata[key]}); //pdfmake的图片格式{image:image dataurl}
       }
       var  dd = {
         content: [
          'Title' ,
          imgs,
          ],
       };
       pdfMake.createPdf(dd).download();
     }
   }
   </script>
   </head>
   <body>
   <button onclick= "down()" >下载</button>
   <script>
   function  ImageDataURL(urls) { //urls必须是字符串或字符串数组
     this .completenum = 0;
     this .totalnum = 0;
     this .imgdata =  new  Array();
     this .emptyobj =  new  Object();
     this .oncomplete =  function (){};
     this .getDataURL =  function (url, index) {
       var  c = document.createElement( "canvas" );
       var  cxt = c.getContext( "2d" );
       var  img =  new  Image();
       var  dataurl;
       var  p;
       p =  this ;
       img.src = url;
       img.onload =  function () {
         var  i;
         var  maxwidth = 500;
         var  scale = 1.0;
         if  (img.width > maxwidth) {
           scale = maxwidth / img.width;
           c.width = maxwidth;
           c.height = Math.floor(img.height * scale);
         else  {
           c.width= img.width;
           c.height= img.height;
         }
         cxt.drawImage(img, 0, 0, c.width, c.height);
  
         p.imgdata[index] = c.toDataURL( 'image/jpeg' );
         for  (i = 0; i < p.totalnum; ++i) {
           if  (p.imgdata[i] ==  null )
             break ;
         }
         if  (i == p.totalnum) {
           p.oncomplete();
         }
       };
       img.onerror =  function () {
         p.imgdata[index] = p.emptyobj;
         for  (i = 0; i < p.totalnum; ++i) {
           if  (p.imgdata[i] ==  null )
             break ;
         }
         if  (i == p.totalnum) {
           p.oncomplete();
         }
       };
     }
     if  (urls  instanceof  Array) {
       this .totalnum = urls.length; 
       this .imgdata =  new  Array( this .totalnum);
       for  (key  in  urls) {
         this .getDataURL(urls[key], key);
       }
     else  {
       this .imgdata =  new  Array(1);
       this .totalnum = 1;
       this .getDataURL(urls, 0);
     }
   }
  
   </script>
   </body>
</html>
 
 
 
 
 

引入依赖脚本

原理上需要先把需要保存的节点渲染到一个canvas,然后利用这个canvas制作pdf,所以先引入以下两个依赖代码文件:

<script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.js"></script> <script src="https://cdn.bootcss.com/jspdf/1.3.4/jspdf.debug.js"></script>
  • 1
  • 2

编写所需代码

假设所需要打印的dom的父容器的class名是right-aside,就可以写这样一个函数:

function () {
  var target = document.getElementsByClassName("right-aside")[0]; target.style.background = "#FFFFFF"; html2canvas(target, { onrendered:function(canvas) { var contentWidth = canvas.width; var contentHeight = canvas.height; //一页pdf显示html页面生成的canvas高度; var pageHeight = contentWidth / 592.28 * 841.89; //未生成pdf的html页面高度 var leftHeight = contentHeight; //页面偏移 var position = 0; //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高 var imgWidth = 595.28; var imgHeight = 592.28/contentWidth * contentHeight; var pageData = canvas.toDataURL('image/jpeg', 1.0); var pdf = new jsPDF('', 'pt', 'a4'); //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89) //当内容未超过pdf一页显示的范围,无需分页 if (leftHeight < pageHeight) { pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight ); } else { while(leftHeight > 0) { pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight) leftHeight -= pageHeight; position -= 841.89; //避免添加空白页 if(leftHeight > 0) { pdf.addPage(); } } } pdf.save("content.pdf"); } }) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

以上代码将会以a4纸的尺寸来生成pdf文件,可以分页,代码来自于网络!

坑点提示

jsPDF打印成pdf文件时,注意页面要回到打印区域的顶部,我在该demo设置了先回到页面顶部,再打印,这样就不会出现黑块了。

jsPDF打印成pdf文件时,注意设置打印区域dom的背景色为白色,即#FFFFFF

猜你喜欢

转载自www.cnblogs.com/Alex80/p/9105408.html