itext7史上最全实战总结

2023-11-21 16:10

本文主要是介绍itext7史上最全实战总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. itext7史上最全实战总结

1.1. 前言

最近有个需求需要我用Java手动写一份PDF报告,经过考察几种pdf开源代码,最终选取了itext7,此版本为7.1.11,由于发现网上关于该工具的博文比较少,特别是实战博文几乎没有,在我踩完各种坑,最终把PDF成型后,打算把经验分享出来,本文通过摘录解释来说明,内容来自本人GitHub itext-pdf

1.2. 配置文件

项目采用了Spring Cloud config所以配置在git上,仅仅研究itext7不需要用到数据库等功能,请直接运行PdfMain类的main方法,即可生成模拟的PDF报告

1.3. 版本POM

itext7相关pom

<properties><itext.version>7.1.11</itext.version>
</properties>
<dependencies><!-- itext7 --><dependency><groupId>com.itextpdf</groupId><artifactId>kernel</artifactId><version>${itext.version}</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>io</artifactId><version>${itext.version}</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>layout</artifactId><version>${itext.version}</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>forms</artifactId><version>${itext.version}</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>pdfa</artifactId><version>${itext.version}</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>pdftest</artifactId><version>${itext.version}</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>font-asian</artifactId><version>${itext.version}</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.18</version></dependency><!--itext7 html转pdf用到的包--><dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>3.0.0</version></dependency>
</dependencies>

1.4. 干货

itext7语义本身和前端css很像,所以有点前端基础还是比较容易掌握的

1.4.1. 添加图片

  1. 读取项目中图片文件
  2. 设置边距
  3. 设置宽高扩大缩小
Image indexImage = new Image(ImageDataFactory.create(GenoReportBuilder.class.getClassLoader().getResource("image/gene.png")));
indexImage.setMargins(-50, -60, -60, -60);
indexImage.scale(1, 1.05f);

1.4.2. 添加指定空白页

  1. 添加第2页为空白页,立即刷新后再继续添加
pdf.addNewPage(2).flush();

1.4.3. Div、Paragraph

    Div div = new Div();div.setWidth(UnitValue.createPercentValue(100));div.setHeight(UnitValue.createPercentValue(100));div.setHorizontalAlignment(HorizontalAlignment.CENTER);Paragraph p1 = new Paragraph();p1.setHorizontalAlignment(HorizontalAlignment.CENTER);p1.setMaxWidth(UnitValue.createPercentValue(75));p1.setMarginTop(180f);p1.setCharacterSpacing(0.4f);Style large = new Style();large.setFontSize(22);large.setFontColor(GenoColor.getThemeColor());p1.add(new Text("尊敬的 ").addStyle(large));...Paragraph p2 = new Paragraph();...div.add(p1);div.add(p2);
  1. 整块的内容用Div包裹,这里整块包裹的好处是什么?一方面排版分明成体系,另一方面若需求是整块的内容必须在同一个版面,你可以对Div设置div.setKeepTogether(true);,尽量保证若整块的内容超出了一页,那这块内容会自动整块出现在下一页,上一页剩下的就留白了
  2. 可以看到DivParagraph可以设置很多属性,实际上我们常用的组件除了这两种,还有TableCellList,他们大部分的属性都是一样的,只是部分属性只在部分组件起效果,所以当你设置某个属性没起效果也不用奇怪
  3. Paragraph需要特别注意的一点,想要段落文字居中,不要用setHorizontalAlignment(HorizontalAlignment.CENTER);这是组件的居中对段落无效,甚至对段落里你放Text也无效,需要改用setTextAlignment(TextAlignment.CENTER);
  4. Paragraph段落的行距也是个高频问题,这里给出官方我看到的解释,参考https://itextpdf.com/en/resources/books/itext-7-building-blocks/chapter-4-adding-abstractelement-objects-part-1,搜关键字setFixedLeading,我的理解该方法设值行高绝对值,官方解释是两行文字中间基线之间的距离
  5. 如果想了解详细的什么属性哪里能起作用哪里不行,请访问该地址

UTOOLS1590980957507.png

1.4.4. Table

  1. useAllAvailableWidth表示页面有多宽,我就有多宽
  2. table.startNewRow();表示新起一行,table每画一行都要新起一行
  3. 同样table内容需要居中,和段落一样,请设置new Cell().setTextAlignment(TextAlignment.CENTER)
  4. 每个table中cell都有默认高度,会比实际输入字体高些,此时设置setHeight,若更大没有问题,若高度小于或接近字体大小文字可能就消失了,若想让Cell高度更接近文字高度,请设置Cellpadding,即cell.setPadding(-2),设置负值即可

1.4.5. Tab,\t

  1. itext7中如果要表示段落前的空格,不能使用\t,但换行可以使用\n

  2. 若要实现Tab效果可以有多个方法

    1. \u00a0符号,大概7、8个该符号可表示tab,可能不是很准确
    p1.add(new Text("\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0壹基因衷心祝愿您身体健康、享受品质生活!"));
    
    1. p1.setFirstLineIndent(24),表示段落前留多少空,需要知道一个字多大,设置成两倍就行
    2. Tab也是集成AbstractElement的组件,通过以下方式也可实现相同的效果
      p2.add(new Tab());p2.addTabStops(new TabStop(20, TabAlignment.LEFT));
    

1.4.6. 换页

我常用的换页方法为如下,该方法可保证立即换页

doc.add(new AreaBreak(AreaBreakType.NEXT_PAGE));

当然PdfDocumentaddNewPage其实也可以用,但有时候你没把握好刷新时间可能导致某些混乱

1.4.7. 画图或画文字

能画出多么复杂的图形看是谁画了,在我的PDF中,我画的最复杂的图形如下

UTOOLS1590982696170.png

该图形由多个弧形区域加线段加文字组成,包括数字上的小箭头也是画出来的,画这个的代码过多,想要了解详细的可以自行下载研究,这里介绍API功能

  1. lineTo画线段
  2. roundRectangle可用来画角是弧形的方形,也可以用来画圆
  3. showText用来画文字

以上几种结合填充即可把三角形,多边形画出来了

    PdfPage page = pdf.getPage(pdf.getNumberOfPages());pageSize = pdf.getDefaultPageSize();PdfCanvas pdfCanvas = new PdfCanvas(page);pdfCanvas.saveState().moveTo(pageSize.getWidth() / 2 - 100 + i * 40, yOffset - 203).lineTo(pageSize.getWidth() / 2 - 100 + i * 40, yOffset - 208).stroke().restoreState();pdfCanvas.setLineWidth(2);pdfCanvas.setStrokeColor(color);pdfCanvas.roundRectangle(pageSize.getWidth() / 2 - 3 + posXOffset, yOffset - 188, 6, 6, 3).stroke();pdfCanvas.beginText().setFontAndSize(font, 12).moveText(pageSize.getWidth() / 2 - text.length() * 12 / 2, yOffset - 45);pdfCanvas.showText(text);pdfCanvas.endText();

1.4.8. Html段落转Pdf段落

我们可能遇到把一段Html文本转换成itext7的段落放进来,此时需要用到它的htmlToPdf模块,该模块对应POM

    <!--itext7 html转pdf用到的包--><dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>3.0.0</version></dependency>

至于使用,设置好配置属性,使用也很简单,通常我们需要支持中文,所有配置如下,字体可以自己换

    ConverterProperties proper = new ConverterProperties();//字体设置,解决中文不显示问题FontSet fontSet = new FontSet();fontSet.addFont(GenoReportBuilder.class.getClassLoader().getResource("font/SourceHanSansCN-Regular.ttf").getPath(), PdfEncodings.IDENTITY_H);FontProvider fontProvider = new FontProvider(fontSet);proper.setFontProvider(fontProvider);String content = "html内容";List<IElement> elements = HtmlConverter.convertToElements(content, proper);

转换的内容是IElement集合,而IElement是什么呢?给张图就了解了

UTOOLS1590990939918.png

也就是说只要你的html内容是<div></div>包裹的,你直接把元素转成itext7的Div然后adddocument就可以实现html内容的添加了,当然你也可以用instanceof判断不同内容不同处理

如下是我的处理例子供参考,我把输入html内容样式进行了一定修改后转成itext7组件,这里特别提心,html转过来的itext7组件可能会不支持部分样式的修改,所以需要在html中进行css样式的添加,这里我就把字体和高度统一用css设值了

    Div overall = new Div();java.util.List<IElement> iElements = getFixContent(value);for (IElement iElement : iElements) {Style style = new Style();style.setFontSize(10);style.setCharacterSpacing(0.7f);if (iElement instanceof Div) {Div div = (Div) iElement;java.util.List<IElement> children = div.getChildren();// 全部段落改成相同样式this.addParagraphStyleCircle(style, children);overall.add(div);} else if (iElement instanceof Paragraph) {Paragraph element = (Paragraph) iElement;overall.add(element.addStyle(style));}}doc.add(overall);
  • getFixContent
    private java.util.List<IElement> getFixContent(String content) {if (content.startsWith("<div>")) {content = content.replaceAll("<div>", "<div style='line-height:18pt;font-size:16px;'>");} else {content = "<div style='line-height:18pt;font-size:16px;'>" + content + "</div>";}return HtmlConverter.convertToElements(content, proper);}
  • addParagraphStyleCircle
    private void addParagraphStyleCircle(Style style, java.util.List<IElement> children) {for (IElement child : children) {if (child instanceof Paragraph) {Paragraph element = (Paragraph) child;element.addStyle(style);java.util.List<IElement> children1 = element.getChildren();this.addParagraphStyleCircle(style, children1);}if (child instanceof Div) {Div div = (Div) child;java.util.List<IElement> children1 = div.getChildren();this.addParagraphStyleCircle(style, children1);}if (child instanceof Text) {Text text = (Text) child;text.addStyle(style);}}}

1.4.9. 监听事件

在编写pdf的时候,比如一篇整体的文章,我们需要在页眉位置添加关于这篇文章的固定文本或者图形,类似于打个标签,表示你翻了这么多页一直在看这篇文章,当第二篇文章的时候就换一个,举个例子

  • 第一页

UTOOLS1590992499119.png

  • 第二页

UTOOLS1590992555997.png

这种需求我们如何实现呢?思路分析发现,我们需要知道什么时候文章内容一页写不起了,换了一页的时候我们需要添加一个同样的页眉。这样我们就需要知道页是何时添加的,监听事件就是处理这种问题的

  • pdf是PdfDocument,可添加的事件有START_PAGEINSERT_PAGEREMOVE_PAGEEND_PAGE共四个,如上需求我们需要监听START_PAGE事件,在事件处理中做相应的处理,我在事件中使用PdfCanvas画了头部内容
HeaderTextEvent headerTextEvent = new HeaderTextEvent(title, font);
pdf.addEventHandler(PdfDocumentEvent.START_PAGE, headerTextEvent);
  • HeaderTextEvent类,Painting仅仅是封装了PdfCanvas
public class HeaderTextEvent implements IEventHandler {private String text;private PdfFont font;public HeaderTextEvent(String text,PdfFont font) {this.text = text;this.font = font;}@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent) event;PdfDocument pdfDoc = docEvent.getDocument();Painting painting = new Painting(pdfDoc, font);painting.drawHeader();painting.drawHeaderText(text);painting.close();}
}

在添加内容前添加相应事件,同时需要记得在不需要的时候移除

// 移除监听器
pdf.removeEventHandler(PdfDocumentEvent.START_PAGE, headerTextEvent);

1.4.10. 添加目录

我没有找到itext7原生是否有目录添加,根据我自己的需求,我用Table组件来实现了自定义目录,由于我的PDF是用来打印的,所以我并没有给目录添加Link,也就是页面跳转,不过当你彻底理解了我的项目,我想这个需求实现也不难

  • 实现效果如下,随着内容的增长,目录自动增长

UTOOLS1590993651033.png

先说下遇到的困难,目录顾明思意,必须要有内容才会有目录,所以实际上目录是最后添加的,但如果我们添加内容到最后再跳转到前面的页面来添加目录,有三个问题:

  1. 目录有几页如何知道?
  2. 目录有几页不知道,如何知道内容在第几页?
  3. 由于目录不确定,所以后续内容的页码其实也是不确定的,也就是说页码也不是一页页可以添加过去的

而经过实践你会发现,我们不能够回到前几页去修改已存在的页面,因为会提示你已经flush了,不能修改。

这时我看到了movePage这个方法,也就是可以通过移动页面,把目录在内容之后生成,后再移动到前几页,但是页码还是不能修改,发现脑袋不够想了只能用上屁股,灵光一闪,不能一遍生成为什么不能二次渲染呢?于是研究读取原pdf在原pdf上修改,二次渲染的时候填上页码及移动页面,主要代码如下,包括了读取中间文件,移动目录,添加每页页码

PdfReader reader = null;
PdfWriter writer = null;
String inPath = getInPath();
try {reader = new PdfReader(new File(inPath));writer = new PdfWriter(new File(outPath));
} catch (IOException e) {e.printStackTrace();
}
PdfDocument pdf = new PdfDocument(reader, writer);
Document doc = new Document(pdf);
int startPage = 7;
int numberOfPages = pdf.getNumberOfPages();
for (int i = 0; i < catalogSize; i++) {pdf.movePage(numberOfPages, startPage);
}
String forbidPage = properties.getProperty("forbidPage");
for (int pageNumber = 1; pageNumber < numberOfPages + 1; pageNumber++) {if (pageNumber > 6 + catalogSize && pageNumber != 8 + catalogSize) {if (forbidPage != null && (pageNumber - catalogSize) >= Integer.parseInt(forbidPage)) {continue;}PageSize pageSize = pdf.getDefaultPageSize();doc.showTextAligned(new Paragraph(String.format("- %d -", pageNumber)), pageSize.getWidth() / 2, 30, pageNumber, TextAlignment.CENTER, VerticalAlignment.MIDDLE, 0);}
}

1.5. 总结

经过上述总结,我基本上把项目中的大多基本点和难点都概括进去了,初次用itext7写PDF的同学基本会遇到的问题基本都在上述这些,不理解的就把项目下下来运行Main方法慢慢调试,理解透我这个项目,还有其它问题那基本只能翻官网了

项目Github: https://github.com/tzxylao/onegeno-itext-pdf
itext7官网:https://itextpdf.com/

这篇关于itext7史上最全实战总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/403449

相关文章

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Java向kettle8.0传递参数的方式总结

《Java向kettle8.0传递参数的方式总结》介绍了如何在Kettle中传递参数到转换和作业中,包括设置全局properties、使用TransMeta和JobMeta的parameterValu... 目录1.传递参数到转换中2.传递参数到作业中总结1.传递参数到转换中1.1. 通过设置Trans的

C# Task Cancellation使用总结

《C#TaskCancellation使用总结》本文主要介绍了在使用CancellationTokenSource取消任务时的行为,以及如何使用Task的ContinueWith方法来处理任务的延... 目录C# Task Cancellation总结1、调用cancellationTokenSource.

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount