在项目中,利用iText和flying saucer生成pdf文件,网上所说的中文不显示的问题倒是没有遇到,不过就是中文字体时,由于字符宽度是按字母计算的,同样字数会导致一行显示很长不换行,从而超过版面宽度显示不全的问题。经过分析和查找,终于的到解决方案,另外也解决了中文标点符号出现在行首的现象。
首先,我们要明白这几个东西的真实含义:
Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS : 4E00-9FBF:CJK 统一表意符号
Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS :F900-FAFF:CJK 兼容象形文字Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A :3400-4DBF:CJK 统一表意符号扩展 A
CJK的意思是“Chinese,Japanese,Korea”的简写 ,实际上就是指中日韩三国的象形文字的Unicode编码
Character.UnicodeBlock.GENERAL_PUNCTUATION :2000-206F:常用标点Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION :3000-303F:CJK 符号和标点Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS :FF00-FFEF:半角及全角形式
在flying saucer生成pdf的过程中,源代码中是按组和行宽来换行的,具体逻辑如下:
以空格为分组符,任意两个相邻空格之间的字符都是不可换行的,当该行多组字符的总长到达最接近行宽的时候,也就是再加一个字符组就会超过行宽的时候,就会换行
这种方式对英文是很凑效的,但是对中文是不行的,因为中文不是以空格分隔的。yye_javaeye老兄对源码做了改进,判断是否是中文字符,如果是中文字符,则把每个中文字符做一组,然后再用组+行宽来换行。他添加了两个方法:isChinese(char c)和getStrRight(String s,int left), 改过后的部分源码如下:
package org.xhtmlrenderer.layout; import org.xhtmlrenderer.css.constants.IdentValue; import org.xhtmlrenderer.css.style.CalculatedStyle; import org.xhtmlrenderer.render.FSFont; /** * A utility class that scans the text of a single inline box, looking for the * next break point. * @author Torbjrn Gannholm */ public class Breaker { 。。。。。。 public static void breakText(LayoutContext c, LineBreakContext context, int avail, CalculatedStyle style) { 。。。。。。 String currentString = context.getStartSubstring(); int left = 0; // int right = currentString.indexOf(WhitespaceStripper.SPACE, left + 1); int right = getStrRight(currentString,left); int lastWrap = 0; int graphicsLength = 0; int lastGraphicsLength = 0; while (right > 0 && graphicsLength <= avail) { lastGraphicsLength = graphicsLength; graphicsLength += c.getTextRenderer().getWidth( c.getFontContext(), font, currentString.substring(left, right)); lastWrap = left; left = right; // right = currentString.indexOf(WhitespaceStripper.SPACE, left + 1); right = getStrRight(currentString,left+1); } 。。。。。。 } private static boolean isChinese(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { return true; } return false; } private static int getStrRight(String s,int left){ if(left>=s.length()) return -1; char[] ch = s.toCharArray(); for(int i = left;i<ch.length;i++){ if(isChinese(ch[i]) || ' ' == ch[i]){ return i==0?i+1:i; } } return -1; } }
这种方法确实实现了中文汉字的换行,但是也带来了“标点符号在行首”的问题,因为他把标点符号也视作汉字了,这样标点符号也被分组被换行了,解决的方法就是把标点符号从isChinese方法中去掉。其源码如下:
/* * Breaker.java * Copyright (c) 2004, 2005 Torbj锟絩n Gannholm, * Copyright (c) 2005 Wisconsin Court System * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.xhtmlrenderer.layout; import org.xhtmlrenderer.css.constants.IdentValue; import org.xhtmlrenderer.css.style.CalculatedStyle; import org.xhtmlrenderer.render.FSFont; /** * A utility class that scans the text of a single inline box, looking for the * next break point. * @author Torbj锟絩n Gannholm */ public class Breaker { public static void breakFirstLetter(LayoutContext c, LineBreakContext context, int avail, CalculatedStyle style) { FSFont font = style.getFSFont(c); context.setEnd(getFirstLetterEnd(context.getMaster(), context.getStart())); context.setWidth(c.getTextRenderer().getWidth( c.getFontContext(), font, context.getCalculatedSubstring())); if (context.getWidth() > avail) { context.setNeedsNewLine(true); context.setUnbreakable(true); } } private static int getFirstLetterEnd(String text, int start) { int i = start; while (i < text.length()) { char c = text.charAt(i); int type = Character.getType(c); if (type == Character.START_PUNCTUATION || type == Character.END_PUNCTUATION || type == Character.INITIAL_QUOTE_PUNCTUATION || type == Character.FINAL_QUOTE_PUNCTUATION || type == Character.OTHER_PUNCTUATION) { i++; } else { break; } } if (i < text.length()) { i++; } return i; } public static void breakText(LayoutContext c, LineBreakContext context, int avail, CalculatedStyle style) { FSFont font = style.getFSFont(c); IdentValue whitespace = style.getWhitespace(); // ====== handle nowrap if (whitespace == IdentValue.NOWRAP) { context.setEnd(context.getLast()); context.setWidth(c.getTextRenderer().getWidth( c.getFontContext(), font, context.getCalculatedSubstring())); return; } //check if we should break on the next newline if (whitespace == IdentValue.PRE || whitespace == IdentValue.PRE_WRAP || whitespace == IdentValue.PRE_LINE) { int n = context.getStartSubstring().indexOf(WhitespaceStripper.EOL); if (n > -1) { context.setEnd(context.getStart() + n + 1); context.setWidth(c.getTextRenderer().getWidth( c.getFontContext(), font, context.getCalculatedSubstring())); context.setNeedsNewLine(true); context.setEndsOnNL(true); } else if (whitespace == IdentValue.PRE) { context.setEnd(context.getLast()); context.setWidth(c.getTextRenderer().getWidth( c.getFontContext(), font, context.getCalculatedSubstring())); } } //check if we may wrap if (whitespace == IdentValue.PRE || (context.isNeedsNewLine() && context.getWidth() <= avail)) { return; } context.setEndsOnNL(false); String currentString = context.getStartSubstring(); int left = 0; // int right = currentString.indexOf(WhitespaceStripper.SPACE, left + 1); int right = getStrRight(currentString,left); int lastWrap = 0; int graphicsLength = 0; int lastGraphicsLength = 0; while (right > 0 && graphicsLength <= avail) { lastGraphicsLength = graphicsLength; graphicsLength += c.getTextRenderer().getWidth( c.getFontContext(), font, currentString.substring(left, right)); lastWrap = left; left = right; // right = currentString.indexOf(WhitespaceStripper.SPACE, left + 1); right = getStrRight(currentString,left+1); } if (graphicsLength <= avail) { //try for the last bit too! lastWrap = left; lastGraphicsLength = graphicsLength; graphicsLength += c.getTextRenderer().getWidth( c.getFontContext(), font, currentString.substring(left)); } if (graphicsLength <= avail) { context.setWidth(graphicsLength); context.setEnd(context.getMaster().length()); //It fit! return; } context.setNeedsNewLine(true); if (lastWrap != 0) {//found a place to wrap context.setEnd(context.getStart() + lastWrap); context.setWidth(lastGraphicsLength); } else {//unbreakable string if (left == 0) { left = currentString.length(); } context.setEnd(context.getStart() + left); context.setUnbreakable(true); if (left == currentString.length()) { context.setWidth(c.getTextRenderer().getWidth( c.getFontContext(), font, context.getCalculatedSubstring())); } else { context.setWidth(graphicsLength); } } return; } private static boolean isChinese(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) { return true; } return false; } private static int getStrRight(String s,int left){ if(left>=s.length()) return -1; char[] ch = s.toCharArray(); for(int i = left;i<ch.length;i++){ if(isChinese(ch[i]) || ' ' == ch[i]){ return i==0?i+1:i; } } return -1; } }
由于我使用的是maven管理项目依赖,包名有所变化
依赖:
<dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf-itext5</artifactId> <version>9.0.1</version> </dependency>
上面会引入以下几个依赖项:
flying-saucer-parent-9.0.1.pom
flying-saucer-pdf-itext5-9.0.1.jar
flying-saucer-core-9.0.1.jar
core-renderer-R8.jar
上面对应的代码就在flying-saucer-core-9.0.1.jar包中,修改之即可。
另外需要注意的就是,目前flying-saucer支持的最高版本是CSS 2.1,如果用了CSS 3的内容,可能会不起作用而导致版面错乱。
http://bettereveryday.iteye.com/blog/611561