iText 基础
基础介绍
iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。
本文仅讨论如何使用iTextApi生成PDF文件,不讨论关于xml,html相关的pdf转换特性。
基础API
iText Document
Document是iText中一个通用的文档类,所有我本内容最终都会被附加或者说填充到一个文档中,此文档对象也会在子元素被添加的同时回调一些由listener指定的回调接口!
注意
- 当一个文档对象创建完成得到实例之后,我们就可以为这个文档实例添加一些元数据,比如文档所属用户,文档简要描述等等。
- 当一个文档对象创建完成得到实例之后,我们可以为文档指定文档页眉和页脚
- 只有先打开文档(调用
document.open()
)之后我们才能够向文档中写入内容 - 打开文档之前,需要使用一个DocumentWriter的实现类比如PdfWriter的实例来将Document和一个OutputStream绑定到一起,当调用
close()
方法,文档内容将被写入到输出流中 - 一旦文档被打开,将无法再向文档中添加元数据
- 如果你在某一页面调整了页眉或者页脚,接下来的后续页面都会被改变
- 一旦文档被关闭,与当前文档关联的所有listener也都将被关闭
核心API
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
44
45
46public class Document implements DocListener, IAccessibleElement {
/**
* Methods extended from DocListener
*/
public void open(); //打开文档
public void close(); //关闭万恶的囊
public boolean newPage(); //开始新的一页
public boolean setPageSize(Rectangle pageSize); //设置文档页面大小
public boolean setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom); //设置文档页面边距
public void setPageCount(int pageN); //设置文档页码
public void resetPageCount(); //重置当前文档页码为0
/**
* Fields declared document itself
*/
protected ArrayList<DocListener> listeners = new ArrayList<>(); //关联的listener
protected boolean open; //文档是否已被打开
protected boolean close; //文档是否已被关闭
protected Rectangle pageSize; //文档页面大小
protected float marginLeft = 0; //文档左边距
protected float marginRight = 0; //文档右边距
protected float marginTop = 0; //文档上边距
protected float marginBottom = 0; //文档下边距
/**
* Constructor method
*/
public Document(); //same as Document(PageSize.A4); A4 = new RectangleReadOnly(595,842)
public Document(Rectangle pageSize); //same as Document(pageSize, 36, 36, 36, 36)
public Document(Rectangle pageSize, float marginLeft, float marginRight,float marginTop, float marginBottom);
/**
* Methods declared document itself
*/
public void addDocListener(DocListener listener);
public void removeDocListener(DocListener listener);
public boolean add(Element element);
public boolean addHeader(String name, String content); //为文档添加一个header
public boolean addTitle(String title); //为文档添加一个title
public boolean addSubject(String subject); //为文档添加一个主题
public boolean addKeywords(String keywords); //为文档添加一系列关键字
public boolean addAuthor(String author); //添加文档的作者
public boolean addCreator(String creator); //添加文档创建者名称
public boolean addCreationDate(); //设定文档创建时间,即当前时间
}
实例代码
1 | public class DocumentApiTest{ |
iText Element
文本元素的通用接口。
核心API
1 | public interface Element{ |
iText Chunk
可以被添加到文档中的最小的文本单位。许多其他元素都被拆分成一个或多个Chunk,每一个Chunk都关联了一个字符串以及对应的字体。关于chunk的布局参数等则应该在被拆分的元素内部进行定义。
核心API
1 | public class Chunk implements Element, IAccessibleElement { |
示例代码
1 | public class ChunkApiTest{ |
iText Phrase
一个Phrase是一系列Chunk的集合。
每一个Phrase都有一个主字体,所有添加到此Phrase中的Chunk都默认使用这个字体,但是每一个Chunk仍然可以自定义字体。所有的Chunk使用同一个leading(行间距)
核心API
1 | public class Phrase extends ArrayList<Element> implements TextElementArray { |
示例代码
1 | public class PhraseApiTest{ |
iText BaseColor
iText 颜色工具类
核心API
1 | public class BaseColor { |
iText BaseFont
BaseFont为iText字体基类,可根据自体文件创建或加载对应字体。
核心API
1 | public abstract class BaseFont{ |
示例代码
1 | public class BaseFontApiTest{ |
注意
在使用maven等包管理工具管理项目时,一般我们会将字体等资源文件放到项目的resources资源文件目录,在项目的打包阶段,maven编译插件通常会对资源文件进行编译,这会导致字体文件被损坏,若要保证自体文件的正常使用,请设置插件的filtering为false1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--省略部分无关代码-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.tty</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
iText Font
一个字体描述类,包含字体类型,大小,样式以及颜色
核心API
1 | public class Font implements Comparable<Font> { |
示例代码
1 | public class FontApiTest { |
iText Rectangle
Rectangle是对一个矩形区域的抽象。
核心API
1 | public class Rectangle implements Element { |
iText PdfPCell
iText单元格。是PdfPTable的基本元素。关于单元格的设计可以参考html的盒子模型。之后所有的控件都是由PdfPCell扩展而来。具体细节之后会详述。
核心API
1 | public class PdfPCell extends Rectangle implements IAccessibleElement { |
注意
如果某个表格被拆分为多列,比如列数为columnCountOfTable,如果最后一行中所有单元格的列数加起来都不足columnCountOfTable,那么这一行内的单元格将不会被展示出来。如果想让单元格展示出来,必须利用一些空白单元格对最后一行进行补齐。
示例代码
1 | public class PdfPCellApiTest{ |
iText PdfPTable
iText表格类,一个表格可以被给定一个绝对位置,也可以作为一个Element被放置到一个Document中,如果被放置到一个Document中,那么这个表格占据这个document除开上下左右边距之后的整个空间。
在日常的项目实现中我们一般会使用表格来进行布局。参照bootstrap中的一些思想。我们一般会将表格框架拆分为12列或者是24列。具体为什么是12列或者24列,大家可以自己想一下。
核心API
PdfPTable包含的API众多,我们此处只罗列几个比较重要的。其实对于PdfPTable主要是理解为什么需要将其拆分为12列或者是24列,其他关于具体怎么使用它进行布局的部分实在是太过简单,毕竟流式布局实在没什么好说的。1
2
3
4
5
6
7
8
9
10
11
12public class PdfPTable implements LargeElement, Spaceable, IAccessibleElement {
protected ArrayList<PdfPRow> rows; //当前表格的行
protected float widthPercentage; //当前表格占据的宽度百分比
private boolean splitRows; //是否拆分页脚最后一个单元格(如果页面剩余宽度不够一个单元格的高度)
private boolean splitLate ;
public PdfPTable(final int numColumns); //构造函数指定表格列数目
public PdfPCell addCell(final PdfPCell cell); //向当前表格中添加一个单元格
public void setWidthPercentage(final float widthPercentage); //设置当前表格宽度百分比
public void setSplitRows(final boolean splitRows);
public boolean isSplitLate();
}
示例代码
1 | public class PdfPTableTest{ |
iText控件扩展
前面也提到过,对于iText,想要进行控件实现,那么就必须依赖于PdfPCell类以及PdfPCellEvent。
PdfPCellEvent接口定义
1 | public interface PdfPCellEvent { |
How to example
本例中我们将会使用一个比较复杂的例子来讲述具体如何通过扩展PdfPCell来得到一个控件,这个控件的名称叫南丁格尔玫瑰图,不知道什么是南丁格尔玫瑰图的同学可以看百度一下。下面是南丁格尔玫瑰图的报读百科描述。
南丁格尔玫瑰图是弗罗伦斯·南丁格尔所发明的。又名为极区图 。是一种圆形的直方图。 南丁格尔自己常昵称这类图为鸡冠花图(coxcomb),并且用以表达军医院季节性的死亡率,对象是那些不太能理解传统统计报表的公务人员。
弗罗伦斯·南丁格尔 (英语:Florence Nightingale,1820年5月12日-1910年8月13日),英国护士和统计学家,出生于意大利的一个英国上流社会的家庭。在德国学习护理后,曾往伦敦的医院工作。于1853年成为伦敦慈善医院的护士长。
出于对资料统计的结果会不受人重视的忧虑,她发展出一种色彩缤纷的图表形式,让数据能够更加让人印象深刻。 这种图表形式有时也被称作「南丁格尔的玫瑰」,是一种圆形的直方图。 南丁格尔自己常昵称这类图为鸡冠花图(coxcomb),并且用以表达军医院季节性的死亡率,对象是那些不太能理解传统统计报表的公务人员。 她的方法打动了当时的高层,包括军方人士和维多利亚女王本人,于是医事改良的提案才得到支持。
接下来我会一步一步的展示如何实现这个控件。
注意事项
- 由于PdfPCell有一些基本的样式属性,其中一些属性比如border相关的我们需要禁用,为什么呢,因为通常我们控件的border会靠内一点,即我们会留出一点padding,这样当吧单元格放到表格中时不至于挤到一起。我们通过设置pdfPCell.border = Rectangle.NO_BORDER来禁用。
- 除了border相关的属性,还有一个就是单元格自带的backgroundColor属性,像上一点所说我们要使得控件展示出来时不至于挤到一起,会在padding的部分留白,但PdfPCell的默认backgroundColor是覆盖这一部分的。
需求定义
控件基本属性
编码
第一步,控件基本骨架
前面也提到过,想要扩展控件,那么我们必须使用PdfPCell以及PdfPCellEvent这两个类,让我们的控件继承自PdfPCell并实现PdfPCellEvent,在PdfPCellEvent的doCellLayout回调中进行我们的控件勾画工作。1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 南丁格尔玫瑰图
*
* @author wenchao
*/
public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent {
public NightingaleRoseDiagram() {
}
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
//draw component content here
}
}
第二步,控件基本属性“禁用”
根据前面注意事项所说,我们需要“禁用”边框以及背景色两个属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 南丁格尔玫瑰图
*
* @author wenchao
*/
public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent {
public NightingaleRoseDiagram() {
disableTheDefaultFeature();
}
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
}
/**
* 禁用控件自定义边框以及背景色
*/
private void disableTheDefaultFeature(){
this.setBorder(PdfPCell.NO_BORDER);
this.setBackgroundColor(BaseColor.WHITE);
}
}
第三步,定义控件基本属性
南丁格尔玫瑰图内部是由扇区定义的,在笔者接触到的南丁格尔玫瑰图中这些扇区都是等分的,所以今天实现的南丁格尔玫瑰图扇区也会是等分的。
- 单元格背景色
- 待勾画控件以圆形或者圆环为基础框架,所以需要定义圆心(圆心在cellLayout回调中使用position确定),最大半径,最小半径。
- 具体有多少个扇区,由传入的扇区数据集合大小来确定
- 扇区基本数据,扇区半径,扇区背景色,扇区描述(扇区描述文字,扇区描述字体)
- 单元格高度,我们设定为最大圆半径 × 2 × 1.2
1 | /** |
第四步,勾画控件
1 | /** |
第五步,勾画背景
勾画单元格背景时,我们使用padding缩小一点勾画的范围,就可以预留出一点空白,避免单元格挤到一起1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent {
//...
private void drawBackGround(Rectangle position, PdfContentByte canvas, BaseColor bgColor) {
Rectangle backgroundRectangle = new Rectangle(position);
//预留空白padding
backgroundRectangle.setLeft(backgroundRectangle.getLeft() + DEFAULT_PADDING);
backgroundRectangle.setRight(backgroundRectangle.getRight() - DEFAULT_PADDING);
backgroundRectangle.setBottom(backgroundRectangle.getBottom() + DEFAULT_PADDING);
backgroundRectangle.setTop(backgroundRectangle.getTop() - DEFAULT_PADDING);
//勾画背景
canvas.saveState();
canvas.setLineWidth(0);
canvas.setColorStroke(bgColor);
canvas.setColorFill(bgColor);
canvas.rectangle(backgroundRectangle.getLeft(), backgroundRectangle.getBottom(),
backgroundRectangle.getWidth(), backgroundRectangle.getHeight());
canvas.closePathFillStroke();
canvas.restoreState();
}
//...
}
示例
第五步勾画刻度
1 | public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent { |
示例
第六步,勾画扇区描述
1 | public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent { |
示例
第七步,勾画扇区
1 | public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent { |
示例
第八步,玫瑰图内圈
1 | public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent { |
多个单元格展示
测试代码
1 | public class NightingaleRoseDiagram extends PdfPCell implements PdfPCellEvent { |
结语
本文只是简单的对iText的一些比较常用的API做了一些基本介绍。然后利用南丁格尔玫瑰图这个控件的实现来简单的介绍了一下一般如何在iText中扩展一个控件,当前这个南丁格尔玫瑰图的控件实现只是为了展示整个过程,要想实现一个高可用的控件,还需要考虑更多的情况。