首先,技术为apache 的FOP,初级的问题我就不写了,下面记录一下遇到的问题的解决方案:
这个问题网上的解决方案非常之多,也不详细描述了,每个使用FOP的都会遇见,还是记录一下。
在fop.xconf文件中,修改pdf相关的render,添加中文字体配置
<renderer mime="application/pdf"> <filterList> <!-- provides compression using zlib flate (default is on) --> <value>flate</value> </filterList> <fonts> <font metrics-url="[%FOP_ROOT%]/simsun.xml" kerning="yes" embed-url="[%FOP_ROOT%]/fonts/simsun.ttc"> <font-triplet name="SimSun" style="normal" weight="normal"/> <font-triplet name="宋体" style="normal" weight="normal"/> </font> </fonts> </renderer>
当然你还使用其他的方式来添加字体配置,见http://xmlgraphics.apache.org/fop/0.95/configuration.html
其中的.xml文件是根据fop的字体读取类生成的,例如:
通过 TTFReader类来直接通过代码生成也是一个选择,它提供的main方法中有详细的参数介绍说明。
更多的fop字体相关问题见http://xmlgraphics.apache.org/fop/trunk/fonts.html
很多问题,通过自己读一读官方的介绍问题就明白了。
这是最新的fop还没有解决的问题之一。
对于英文字体,都有normal、bold、italic三种对应的字体,例如上面的arial.ttf(normal)、arialb.ttf(bold),所以英文的粗体、斜体都没有问题的。
中文就有问题了,常用的宋体,一般使用都是windows的simsun(宋体)字体,这种字体没有对应的bold、italic版本,MS word实现中文粗体、斜体是自己计算的,而很可惜fop处理的pdf没有这种功能,因此,宋体的粗体、斜体就没有办法显示了。
如果想实现宋体的粗体,有两种方式实现:
1,下载宋体的粗体字体文件,然后按照处理宋体的一般字体文件一样,生成xml文件,然后在fop.xconf中配置字体,比如
<font metrics-url="[%FOP_ROOT%]/simsunb.xml" kerning="yes" embed-url="[%FOP_ROOT%]/fonts/simsunb.ttf"> <font-triplet name="SimSun" style="normal" weight="bold"/> <font-triplet name="宋体" style="normal" weight="bold"/> <font-triplet name="SimSun" style="italic" weight="bold"/> <font-triplet name="宋体" style="italic" weight="bold"/> </font>
这里的sinsunb.xml就是根据网上下载的粗宋体文件(simsunb.ttf)生成的,附件为一个可用的粗宋体文件,以及对应的xml文件。使用这些字体,生成的pdf就会有你下载的粗宋体的样式了,不过好不好看就再说了。
2,另一种方法,也就是我采用的办法,宋体的粗体其实可以用黑体代替,而fop没有自动进行这种关联,需要我们自己配置了,很简单
<font metrics-url="[%FOP_ROOT%]/simhei.xml" kerning="yes" embed-url="[%FOP_ROOT%]/fonts/simhei.ttf"> <font-triplet name="SimSun" style="normal" weight="bold"/> <font-triplet name="宋体" style="normal" weight="bold"/> <font-triplet name="SimSun" style="italic" weight="bold"/> <font-triplet name="宋体" style="italic" weight="bold"/> </font>
将粗体都交给黑体处理,效果比上一种方式好一点。
附件上传了一些处理过的宋体(simsun.xml.rar)、黑体(simhei.xml.rar)、隶书(simli.xml.rar)、楷体(simkai.xml.rar),对应的字体(ttf或ttc)文件太大,自己直接去C:/windows/fonts/目录下寻找即可。
有时候报告需要添加背景图片、或者水印,这时候需要修改fo文件,由于我们是直接根据html生成的,所以我们修改xsl-fo文件就行了。
我是用的xsl文件没有处理<html:body background=""/>属性,所以直接在xsl的页面配置中添加背景图片。
<fo:layout-master-set> <fo:simple-page-master master-name="first" xsl:use-attribute-sets="page"> <fo:region-body margin-top="{$page-margin-top}" margin-right="{$page-margin-right}" margin-bottom="{$page-margin-bottom}" margin-left="{$page-margin-left}" column-count="{$column-count}" column-gap="{$column-gap}" background-image="url('work/report/bg_header.jpg')" /> <xsl:choose> <xsl:when test="$writing-mode = 'tb-rl'"> <fo:region-before extent="{$page-margin-right}" precedence="true"/> <fo:region-after extent="{$page-margin-left}" precedence="true"/> <fo:region-start region-name="page-header" extent="{$page-margin-top}" writing-mode="lr-tb" display-align="before"/> <fo:region-end region-name="page-footer" extent="{$page-margin-bottom}" writing-mode="lr-tb" display-align="after"/> </xsl:when> <xsl:when test="$writing-mode = 'rl-tb'"> <fo:region-before region-name="page-header" extent="{$page-margin-top}" display-align="before"/> <fo:region-after region-name="page-footer" extent="{$page-margin-bottom}" display-align="after"/> <fo:region-start extent="{$page-margin-right}"/> <fo:region-end extent="{$page-margin-left}"/> </xsl:when> <xsl:otherwise><!-- $writing-mode = 'lr-tb' --> <fo:region-before region-name="page-header" extent="{$page-margin-top}" display-align="before"/> <fo:region-after region-name="page-footer" extent="{$page-margin-bottom}" display-align="after"/> <fo:region-start extent="{$page-margin-left}"/> <fo:region-end extent="{$page-margin-bottom}"/> </xsl:otherwise> </xsl:choose> </fo:simple-page-master> <fo:simple-page-master master-name="normal" xsl:use-attribute-sets="page"> <fo:region-body margin-top="{$page-margin-top}" margin-right="{$page-margin-right}" margin-bottom="{$page-margin-bottom}" margin-left="{$page-margin-left}" column-count="{$column-count}" column-gap="{$column-gap}" background-image="url('work/report/bg_content.jpg')" /> <xsl:choose> <xsl:when test="$writing-mode = 'tb-rl'"> <fo:region-before extent="{$page-margin-right}" precedence="true"/> <fo:region-after extent="{$page-margin-left}" precedence="true"/> <fo:region-start region-name="page-header" extent="{$page-margin-top}" writing-mode="lr-tb" display-align="before"/> <fo:region-end region-name="page-footer" extent="{$page-margin-bottom}" writing-mode="lr-tb" display-align="after"/> </xsl:when> <xsl:when test="$writing-mode = 'rl-tb'"> <fo:region-before region-name="page-header" extent="{$page-margin-top}" display-align="before"/> <fo:region-after region-name="page-footer" extent="{$page-margin-bottom}" display-align="after"/> <fo:region-start extent="{$page-margin-right}"/> <fo:region-end extent="{$page-margin-left}"/> </xsl:when> <xsl:otherwise><!-- $writing-mode = 'lr-tb' --> <fo:region-before region-name="page-header" extent="{$page-margin-top}" display-align="before"/> <fo:region-after region-name="page-footer" extent="{$page-margin-bottom}" display-align="after"/> <fo:region-start extent="{$page-margin-left}"/> <fo:region-end extent="{$page-margin-bottom}"/> </xsl:otherwise> </xsl:choose> </fo:simple-page-master> <fo:page-sequence-master master-name="standard"> <fo:repeatable-page-master-alternatives> <fo:conditional-page-master-reference master-reference="first" page-position="first" /> <fo:conditional-page-master-reference master-reference="normal"/> </fo:repeatable-page-master-alternatives> </fo:page-sequence-master> </fo:layout-master-set>
上面的代码中,在<fo:region-body>添加了background-image="url('work/report/bg_header.jpg')"属性,因此它生成的pdf就具有背景图片,当然如果你把bg_header.jpg透明度降低,就会获得水印效果了。
上面的代码还区别实现了首页和其他页的背景图片分别设置的功能,在
<fo:repeatable-page-master-alternatives> <fo:conditional-page-master-reference master-reference="first" page-position="first" /> <fo:conditional-page-master-reference master-reference="normal"/> </fo:repeatable-page-master-alternatives> </fo:page-sequence-master>
中指定page-position为first时使用first的master,而其他页使用normal的master。
熟悉fo的应该知道fo的页面布局,也就是layout,在定义好了layout时候,页面将分为上、下、左、右和中五个区域,上对应的就是页眉,所谓的page-header,下对应的就是页脚,所谓的page-footer,上下的距离和间距都可以在xsl文件设置。这里我们对页眉进行图片设置,
<fo:page-sequence master-reference="standard"> <fo:title> <xsl:value-of select="/html:html/html:head/html:title"/> </fo:title> <fo:static-content flow-name="page-header"> <fo:block line-height="10pt" font-size="9pt" font-family="宋体" text-align="start"> <fo:external-graphic width="6.26in" height="1in" content-height="scale-to-fit" content-width="scale-to-fit" src="work/report/header.jpg" /> </fo:block> </fo:static-content> <fo:static-content flow-name="page-footer"> <fo:block space-after.conditionality="retain" space-after="{$page-footer-margin}" xsl:use-attribute-sets="page-footer"> <xsl:if test="$page-number-print-in-footer = 'true'"> <xsl:text>- </xsl:text> <fo:page-number/> <xsl:text> -</xsl:text> </xsl:if> </fo:block> </fo:static-content> <fo:flow flow-name="xsl-region-body"> <fo:block xsl:use-attribute-sets="body"> <xsl:call-template name="process-common-attributes"/> <xsl:apply-templates/> </fo:block> </fo:flow> </fo:page-sequence>
使用刚刚定义的standard page-squence,在
<fo:static-content flow-name="page-header">
中添加
<fo:block line-height="10pt" font-size="9pt" font-family="宋体" text-align="start"> <fo:external-graphic width="6.26in" height="1in" content-height="scale-to-fit" content-width="scale-to-fit" src="work/report/header.jpg" /> </fo:block>
以获得页眉效果,这里也可以同时再添加文字,具体效果自己摸索了。
这个就比较容易了,通过fop user agent进行设置就行了。
FOUserAgent userAgent = fopFactory.newFOUserAgent(); useragent.getRendererOptions().put("encryption-params", new PDFEncryptionParams( null, "password", false, false, true, true)); Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent);
具体效果为:
更多参考http://xmlgraphics.apache.org/fop/trunk/pdfencryption.html
PS:加密以后,<a href=""></a>中定义的链接将不可用,转换时会自动生成一个与本地路径关联的地址,不知道是不是fop的bug,注释了加密代码后又都完全正常。
官方给出的例子中,无法得知pdf是否生成成功,失败了仅仅打出一些错误日志,这对于程序员来说是不够的,如果检测失败也是一个问题。下面的代码提供了这种功能,但是不太好
Result res = new SAXResult(fop.getDefaultHandler()); // Start XSLT transformation and FOP processing transformer.transform(src, res); FormattingResults result = fop.getResults(); if (result.getPageSequences() == null) { log.error("Convert to pdf failed"); out.close(); result = null; fop = null; return false; } result = null; fop = null; out.close();
htmld到pdf的过程比较坎坷,还会遇到很多不可预见的问题,比如html中的char set设置,可能需要很多类似
<!ENTITY tilde "~"> <!ENTITY florin "ƒ"> <!ENTITY elip "…"> <!ENTITY dag "†">
声明,还会遇到pdf中莫名其妙的乱码,比如黑体中的空格,这些都需要自己慢慢调试程序和标准化html。
附件还包含一个可用的fop.xconf文件、html测试文件以及xhtml2fo.xsl 转换文件。
PS:xhtml2fo.xsl文件为根据网上的原始文件修改和完善的,可能功能还不太完整,但是处理基本的html已经足够了。
xhtml2fo.xsl无法显示<span></span>中的内容,原因为xsl中处理相关元素的代码有问题,更新一下:(替换相关内容)
<xsl:template match="html:span"> <fo:inline> <xsl:attribute name="role"> <xsl:value-of select="concat('html:', local-name())"/> </xsl:attribute> <!-- 处理span 内的文字 --> <xsl:apply-templates/> </fo:inline> </xsl:template>