宋黎晓的空间

我们一直在努力....

使用flyingsaucer将网页转换为pdf之中文问题彻底解决

标签: html转换pdf 网页转换pdf flyingsaucer中文 导出pdf

    前几天遇到个导出pdf的需求,在网络上查找了一下java导出pdf的方案.多数人推荐使用iText,研究了一下,感觉直接写pdf的方法太笨,可维护性差,一旦pdf格式要变化改起来很费劲.还有一个方案,可以先预先定义一个pdf作为模板文件,然后用业务数据进行填空.是个不错的方案,只可惜不适合我的需求.需求中有些行是动态加行的,这个方案无法实现.后来发现有可以将网页直接转成pdf的开源包flyingsaucer(中文名:灰碟),逐将注意力转移到这上面,发现是个不错的选择.只要写网页就可以了,而且pdf格式变化维护起来也方便,代码也会比较干净.只是它对中文支持的不好,但这不是无法解决的.下面就来说说这个flyingsaucer.

    Flyingsaucer使用iText2.0.8作为其pdf输出的基础工具,另外增加了解析html/xml并形成pdf式排版的功能.最重要的它还支持css样式表.组合这些能力后,它就可以将网页变成pdf了.但是,它也有他的问题.大家知道iText的版本现在已经升级为5.0以上了,而flyingsaucer却依然沿用2.0.8的版本.为什么呢? 因为这个灰碟貌似自2007年就已经停止维护了,最终版本flyingsaucer-R8.也就是说这是个几年前的工具包,至于新的替代此功能的包又在哪里?我没有找到,倒是有个功能相似但是收费的,不知道是不是它的成长版.这是题外话我们不研究.只看这个flyingsaucer-R8这个版本能否满足我们的基本要求吧.
    在使用过程中发现flyingsaucer-R8对导出pdf的网页有一些要求.
    1. 所有的标签必须都闭合.
    2. 网页开头引入的DTD必须与网页体中使用的标签一致.
    3. 部分不太常用的html标签貌似不认.比如<u>.
    因为flyingsaucer解析html文档是遵照xml标准来的,所以这个网页写起来不能像我们平时的网页那么随意.xml该有的规则都要遵守.这个要求并不高我们可以做到,而且试了下导出的pdf文档没啥问题,因此我们还是可以使用它来满足我们的需求.(至于,这个工具包怎么用,这里有不多说了,google一下一大片.这里只写目前google不到的东西.)
    既然要用它不可回避的就会遇到其对中文支持不好的问题.
    问题1:来源自渲染器输出时没有使用支持中文的字体.虽然我们看到iText有亚洲语言包iTextAsian.jar,但是仅仅引入此包并不能使我们的中文字符输出.以至于网上有个哥们写到:
    打开ITextOutputDevice这个类找到:
java 代码
  1. cb.setFontAndSize(_font.getFontDescription().getFont(), _font.getSize2D() / _dotsPerPoint);    
改成
java 代码
  1. try {         
  2.             cb.setFontAndSize(BaseFont.createFont("STSong-Light""UniGB-UCS2-H", BaseFont.NOT_EMBEDDED), _font.getSize2D()/_dotsPerPoint);         
  3.         } catch (Exception e) {         
  4.         }   

    Ok,改了以后我们终于可以看到pdf里有中文了.但是别高兴的太早哦,问题并没有完全解决.如果一段标签中有且只有中文字符的时候,导出pdf后内容便会消失.比如<div>中文</div>,这样的代码将什么也输出不了,而<div>中文a</div>则会将标签内容全部输出.通过测试我们发现,纯中文是无法输出的,但是加上一点点英文、数字或符号就可以输出了.有同学可能要说我们把纯中文后面加上空格不就行了?我只能说很不幸,加空格是不管用的.如果你的页面上纯中文的地方可以随便让你加字母/数字/符号,那可以不必往下看了.但是我觉得大多数的人恐怕不会这么干的,即使我们想客户也不让啊.那就要解决这个问题.
开源的东西有个好处,可以看源代码.从源码中我发现是字体的问题,于是乎,google一下,找到以下方案:
在导出的代码里加入这两句引入字体
java 代码
  1. ITextFontResolver fontResolver = renderer.getFontResolver();            
  2. fontResolver.addFont("C:/WINDOWS/Fonts/ARIALUNI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);    

于是我照做了.杯具的是情况没有任何好转.即使有好转这个绝对路径的字体引入方式也很让人不舒服吧.所以这个不是我们想要的解决方案.
    Google不到只能继续从代码里找办法了.通过跟深入的研究代码,发现问题根源所在.输出PDF时在计算字符宽度的时候,对中文字符计算出的宽度居然是0.这也就能解释为什么纯中文字符串输出后会看不到了,因为字符总体的宽度被计算为0也就失去了被输出的机会,而加上一点非中文字符的则至少会有一点宽度,即获得了输出的机会,即使实际宽度比计算的宽度要宽也是可以输出的.
    于是修改BaseFont中的getWidth(String text)方法
java 代码
  1. if (char1 < 128 || (char1 >= 160 && char1 <= 255))         
  2.     total += widths[char1];         
  3. else        
  4.     total += widths[PdfEncodings.winansi.get(char1)];    
这几行改为:
java 代码
  1. if (char1 < 128 || (char1 >= 160 && char1 <= 255))         
  2.     total += widths[char1];         
  3. else if ((char1 >= 19968) && (char1 <= 40869)) // 如果是中文字符加宽度500         
  4.     total += 500;         
  5. else        
  6.     total += widths[PdfEncodings.winansi.get(char1)];    

    再次测试,通过.至此,使用flyingsaucer网页导出成pdf中文问题总算解决了.可是总觉得这个解决的方法有点不太正宗,因为修改了父类嘛.但又没有找到其他正宗的解决方案,只能先这样解决一下了.发出此文,只当抛砖引玉,如果有哪位高人有更好的解决方案请不吝赐教啊.

    附件提供修改了flyingsaucer-R8的两个jar包: core-renderer.jar和iText2.0.8.jar另有一个iText亚洲语言包.

提供该文档的机构为 百洋软件研究实验室 ,更多的博客文章可以到 百洋软件研究实验室博客 查看。该文档附件欢迎各位转载,但是在没有获得文章作者许可之前,不得对文章内容或者版权信息进行更改,版权归百洋软件研究实验室所有,仅此声明。

附件:


    分享: 收藏到CSDN 收藏到javaeye 收藏到博客园 收藏&分享
  • 浏览 (395)
  • 评论 (0)
  • 发表于 2010-09-14 10:16
  • 技术类
    评分: 请先登录再投票,同一篇博客一月只能投票一次!
    无人投票

相关博客:


评论


发表评论

关注此文的人们还关注