`
salever
  • 浏览: 250015 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【Java】利用HTML生成PDF之问题整理

阅读更多

首先,技术为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的字体读取类生成的,例如:

java -cp build\fop.jar;lib\avalon-framework.jar;lib\commons-logging.jar;lib\commons-io.jar
org.apache.fop.fonts.apps.TTFReader [options]
C:\myfonts\cmr10.ttf ttfcm.xm

通过 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);

 具体效果为:

写道
The parameters for the constructor of PDFEncryptionParams are:

1. userPassword: String, may be null
2. ownerPassword: String, may be null
3. allowPrint: true if printing is allowed
4. allowCopyContent: true if copying content is allowed
5. allowEditContent: true if editing content is allowed
6. allowEditAnnotations: true if editing annotations is allowed

 更多参考http://xmlgraphics.apache.org/fop/trunk/pdfencryption.html

 PS:加密以后,<a href=""></a>中定义的链接将不可用,转换时会自动生成一个与本地路径关联的地址,不知道是不是fop的bug,注释了加密代码后又都完全正常。

 

pdf生成失败检测

官方给出的例子中,无法得知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  "&#126;">
  <!ENTITY florin "&#131;">
  <!ENTITY elip   "&#133;">
  <!ENTITY dag    "&#134;">

  声明,还会遇到pdf中莫名其妙的乱码,比如黑体中的空格,这些都需要自己慢慢调试程序和标准化html。

 

  附件还包含一个可用的fop.xconf文件、html测试文件以及xhtml2fo.xsl 转换文件。

 

  PS:xhtml2fo.xsl文件为根据网上的原始文件修改和完善的,可能功能还不太完整,但是处理基本的html已经足够了。

 

  20110126 更新

    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>
 

 

1
0
分享到:
评论
5 楼 salever 2011-07-11  
donghustone 写道
请问它支不支持css样式表呢?

这个不太清楚,我没有试过呢,你去fop的官网看看
4 楼 donghustone 2011-07-08  
请问它支不支持css样式表呢?
3 楼 enet_java 2011-01-14  
图片过大是一个原因;后来自己看了看配置文件。是因为配置了
引用
<!-- encodes binary data with hex representation (default off)
This filter is not recommended as it doubles the data size -->
<value>ascii-hex</value>


导致体积增大了很多。

楼主的文章写得很好,多谢了。
2 楼 salever 2011-01-14  
enet_java 写道
请教一个问题,因为添加了字体的样式,导致生成pdf的文件占用空间较大,LZ是怎么解决的呀?
PDF有没有API对内容进行压缩呀?

多谢了!!!


我觉得不是字体的原因吧,是不是你添加了很多图片导致pdf太大?

FOP好像没有专门进行pdf压缩的api,不过你可以看看其他的pdf java api,比如pdf box
1 楼 enet_java 2011-01-13  
请教一个问题,因为添加了字体的样式,导致生成pdf的文件占用空间较大,LZ是怎么解决的呀?
PDF有没有API对内容进行压缩呀?

多谢了!!!

相关推荐

Global site tag (gtag.js) - Google Analytics