资讯动态

iText PDF 实战:创建、签章、斜字水印与文本替换避坑指南

发布时间:2026/10/21 8:06:24 来源:尧图企业网站定制
简介这是一份面向Java开发者的iText操作PDF实战源码包聚焦创建PDF、电子签章、斜字水印与文本替换四类常见需求适合需要快速上手PDF处理的初中级工程师参考。压缩包共32个文件约8.77MB包含7个java源码、9个class编译文件、5个jar依赖itextpdf、itext-asian、bcprov等、2个p12证书及png、jpg示例图片另附readme说明与Eclipse工程配置结构完整可直接导入运行。目前已有1571人学习下载。通过阅读源码读者可掌握PdfWriter创建文档、PdfStamper签章与文本替换、PdfFormXObject绘制旋转水印等核心API用法并了解证书配置、版本兼容与性能优化思路是学习iText处理PDF的实用参考。1. 从一份“看着简单”的 PDF 需求说起前阵子有个做企业文档系统的朋友找我说他们客户提了个需求合同 PDF 要能自动生成、盖电子章、加斜字水印还要把模板里的占位符替换成真实数据。他一开始觉得用现成工具拼一拼就行结果卡在“斜字水印”和“文本替换”上——水印要么是正的要么旋转后位置飘了文本替换更头疼PDF 里的文字不是按你看到的顺序存的直接搜字符串经常找不到。这类需求在电子合同、报表导出、票据生成场景里非常常见。iText 就是干这个的它是一套 Java 操作 PDF 的库能创建文档、加签章、画水印、做文本替换。适合谁做后端文档服务、需要批量生成 PDF 的开发者或者想从零搭一套合同签署流程的团队。下面我按实际拆过的顺序把创建、签章、斜字水印、文本替换四块讲透顺带把踩过的坑标出来。2. iText 环境搭建与 PDF 创建从依赖到第一个文档2.1 选 iText 5 还是 iText 7这是第一个要做的决定。iText 5 和 iText 7 的 API 差异很大网上搜到的代码经常混着用直接抄会编译不过。iText 5 的包名是com.itextpdf.text写法偏“流式”DocumentPdfWriter组合老项目多。iText 7 是com.itextpdf.kernel和com.itextpdf.layout模块拆得更细链式调用多新项目建议直接上 7。但要注意iText 7 的文本替换能力比 5 弱一些很多替换场景反而要回到 5 的PdfReaderPdfStamper思路。我一般建议新建项目用 7维护老项目或做复杂替换用 5。依赖方面Maven 里 iText 7 核心包是kernel和layout签章还要加sign。iText 5 就一个itextpdf包但版本别用太老的5.5.13 之后对中文支持好很多。!-- iText 7 核心依赖创建和布局 -- dependency groupIdcom.itextpdf/groupId artifactIdkernel/artifactId version7.2.5/version /dependency dependency groupIdcom.itextpdf/groupId artifactIdlayout/artifactId version7.2.5/version /dependency !-- 签章模块后面数字签名要用 -- dependency groupIdcom.itextpdf/groupId artifactIdsign/artifactId version7.2.5/version /dependency版本号别乱升7.2.x 和 7.1.x 之间有些类名变了。如果项目里已经有其他 PDF 库注意bcprov这类加密包的冲突签章那块尤其容易撞。2.2 创建第一个 PDF 并写入中文创建本身不难难的是中文。iText 默认字体不含中文字形直接写中文会输出空白或方块。必须显式指定中文字体文件。// iText 7 创建 PDF 并写入中文 import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; public class CreatePdf { public static void main(String[] args) throws Exception { String dest output.pdf; PdfWriter writer new PdfWriter(dest); PdfDocument pdf new PdfDocument(writer); Document document new Document(pdf); // 关键加载中文字体路径换成你系统里的字体文件 PdfFont font PdfFontFactory.createFont(C:/Windows/Fonts/simsun.ttc,0, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); document.setFont(font); document.add(new Paragraph(这是一段中文测试文本)); document.close(); } }逻辑说明PdfWriter负责写字节流PdfDocument是 PDF 对象模型Document是高层布局。createFont第二个参数控制字体嵌入策略PREFER_EMBEDDED会把字体子集嵌进 PDF保证别的机器打开不乱码。参数上simsun.ttc,0里的,0表示取 TTC 集合的第一个字体Linux 下常用NotoSansCJK-Regular.ttc。注意字体文件路径不要写死成开发机路径生产环境用 classpath 资源或配置项。字体嵌入会让文件变大但中文场景基本躲不掉。3. 电子签章与数字签名把“盖章”这件事做对3.1 签章的原理和两种做法PDF 签章分两种一种是“画个章图”就是视觉上盖个红章没有密码学意义另一种是数字签名用证书对文档做哈希签名能验证文档有没有被改过。实际合同场景通常两者都要先画章图再加数字签名。数字签名的流程是对 PDF 字节范围算摘要用私钥加密摘要把签名值和证书一起写进 PDF 的签名域。验证时用公钥解密摘要重新算文档摘要对比。iText 的sign模块封装了这套流程但证书和密钥库要自己准备。视觉章图就简单准备一张透明背景的 PNG用Image对象画到指定坐标。难点在坐标定位——PDF 坐标系原点在左下角和很多图像库相反算位置时容易翻车。3.2 用 iText 7 做数字签名下面是一个可复现的签名示例用 PKCS12 密钥库。// iText 7 数字签名示例 import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.StampingProperties; import com.itextpdf.signatures.*; import java.io.FileInputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; public class SignPdf { public static void main(String[] args) throws Exception { String src input.pdf; String dest signed.pdf; String keystorePath keystore.p12; char[] password 123456.toCharArray(); // 加载密钥库 KeyStore ks KeyStore.getInstance(PKCS12); ks.load(new FileInputStream(keystorePath), password); String alias ks.aliases().nextElement(); PrivateKey pk (PrivateKey) ks.getKey(alias, password); Certificate[] chain ks.getCertificateChain(alias); PdfReader reader new PdfReader(src); PdfWriter writer new PdfWriter(dest); PdfDocument pdfDoc new PdfDocument(reader, writer, new StampingProperties()); // 创建签名外观和签名器 SignatureUtil signUtil new SignatureUtil(pdfDoc); PdfSigner signer new PdfSigner(reader, writer, new StampingProperties()); signer.setFieldName(Signature1); // 签名外观位置、页码、章图 Rectangle rect new Rectangle(100, 100, 200, 100); signer.setReason(合同签署); signer.setLocation(某公司); // 执行签名SHA256 RSA IExternalSignature pks new PrivateKeySignature(pk, SHA-256, BC); IExternalDigest digest new BouncyCastleDigest(); signer.signDetached(digest, pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS); } }逻辑说明PdfSigner是核心类signDetached做分离签名。参数里digest是摘要算法封装pks是私钥签名器chain是证书链CryptoStandard.CMS是签名格式。setFieldName指定签名域名称重复签名时不能重名。参数坑点PrivateKeySignature第三个参数是 provider用 BouncyCastle 就写BC但要在代码里提前注册Security.addProvider(new BouncyCastleProvider())否则报找不到算法。signDetached最后那个0是估算签名长度设太小会报缓冲区不足一般给 0 让它自动算。注意密钥库密码、别名这些别硬编码走配置中心或环境变量。签名后的 PDF 再被修改验证会失败这是特性不是 bug。4. 斜字水印与文本替换两个最容易翻车的点4.1 斜字水印的坐标与旋转水印本身是画文字或图片斜字就是加旋转。iText 里用setRotationAngle或变换矩阵。坑在于旋转是绕原点转的直接设角度文字会跑到页面外。正确做法是先算好旋转后的位置或者用PdfCanvas配合saveState/restoreState做局部变换。// iText 7 添加斜字水印 import com.itextpdf.kernel.pdf.canvas.PdfCanvas; import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.colors.ColorConstants; public void addWatermark(PdfDocument pdfDoc, String text) { int pages pdfDoc.getNumberOfPages(); PdfFont font PdfFontFactory.createFont(C:/Windows/Fonts/simsun.ttc,0, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); for (int i 1; i pages; i) { Rectangle pageSize pdfDoc.getPage(i).getPageSize(); PdfCanvas canvas new PdfCanvas(pdfDoc.getPage(i)); canvas.saveState(); // 设置透明度和颜色 canvas.setFillColor(ColorConstants.LIGHT_GRAY); canvas.setExtGState(new PdfExtGState().setFillOpacity(0.3f)); // 平移到页面中心再旋转再画文字 canvas.beginText(); canvas.setTextMatrix(1, 0, 0, 1, pageSize.getWidth() / 2 - 100, pageSize.getHeight() / 2); canvas.setFontAndSize(font, 40); // 旋转 45 度 canvas.setTextMatrix((float) Math.cos(Math.PI / 4), (float) Math.sin(Math.PI / 4), (float) -Math.sin(Math.PI / 4), (float) Math.cos(Math.PI / 4), pageSize.getWidth() / 2 - 100, pageSize.getHeight() / 2); canvas.showText(text); canvas.endText(); canvas.restoreState(); } }逻辑说明saveState和restoreState保证变换不影响后续内容。setTextMatrix的六个参数是仿射变换矩阵前四个控制旋转缩放后两个是平移。旋转 45 度时cos和sin都是 0.707 左右。平移量要减去文字宽度的一半否则文字中心不在页面中心。参数说明setFillOpacity(0.3f)控制透明度太深会盖住正文太浅打印看不清。字号 40 到 60 之间比较合适具体看页面尺寸。旋转角度一般 30 到 45 度正负都行负角度是另一个方向。4.2 文本替换为什么直接搜不到PDF 里的文本不是按阅读顺序存的而是按绘制指令存的。一段“甲方____”可能被拆成多个Tj操作中间还夹着定位指令。直接PdfTextExtractor提取再替换位置对不上。常见做法是用PdfReader读内容流遍历PdfDictionary和PdfArray找到Tj或TJ操作符替换其中的字符串。但字体编码是个大坑——如果 PDF 用了子集字体字符串是字形索引不是 Unicode替换会乱码。// iText 5 文本替换思路iText 7 类似但 API 不同 PdfReader reader new PdfReader(input.pdf); PdfStamper stamper new PdfStamper(reader, new FileOutputStream(output.pdf)); AcroFields fields stamper.getAcroFields(); // 如果是表单域直接 setField fields.setField(name, 张三); // 如果是普通文本要操作内容流 PdfDictionary page reader.getPageN(1); PdfArray contentArray page.getAsArray(PdfName.CONTENTS); // 遍历 contentArray找 Tj 操作符替换字符串 stamper.close();逻辑说明表单域替换最简单setField直接搞定。普通文本替换要操作内容流PdfName.CONTENTS拿到页面内容遍历操作符。替换时注意字符串编码用PdfString的toUnicodeString和setHexWriting控制。参数坑点setField对中文表单域要确保字体已嵌入否则显示空白。内容流替换时如果原字符串长度和新字符串不一致后面的定位指令会错位需要重新计算Td或Tm的偏移量。这也是为什么很多替换方案只适合等长替换。注意文本替换没有银弹复杂 PDF 建议用模板引擎先生成而不是事后替换。如果非要替换先确认字体编码方式再决定用表单域还是内容流。5. 避坑与排查签章、水印、替换的常见翻车记录5.1 签名后 PDF 打不开或提示损坏现象签名完成文件能生成但用阅读器打开提示“文档已损坏”或“签名无效”。原因最常见的是PdfReader和PdfWriter同时操作同一个文件流或者StampingProperties没设对。另一个原因是签名长度估算为 0 时iText 预留的空间不够写入时截断了。解决确保PdfReader和PdfWriter操作不同文件或者用StampingProperties().useAppendMode()追加模式。签名长度给一个合理估算值比如 8192 字节。如果还不行检查 BouncyCastle 版本和 JDK 版本是否匹配。5.2 水印旋转后位置偏移现象水印加了旋转但不在页面中心偏到角落去了。原因setTextMatrix的平移参数是在旋转后的坐标系里生效的直接给页面中心坐标旋转后自然偏。解决先平移到目标位置再旋转或者把平移量按旋转矩阵反算。简单办法是用canvas.transform做复合变换顺序是平移 → 旋转 → 画文字。别在setTextMatrix里同时塞旋转和平移容易算错。5.3 中文替换后变成乱码或问号现象替换后的中文显示为乱码、方块或问号。原因PDF 原字体不含中文字形或者替换时用了错误的编码。iText 默认用PdfString的 Latin 编码中文会丢。解决替换前确认字体已嵌入用PdfFontFactory.createFont加载中文字体并setFont。如果是表单域setFieldProperty里指定字体。内容流替换时用PdfString的setHexWriting(true)写十六进制 Unicode。5.4 多页文档只有第一页有水印现象给多页 PDF 加水印结果只有第一页有。原因水印代码只操作了pdfDoc.getPage(1)没遍历所有页。解决用getNumberOfPages()拿总页数循环处理每一页。注意每页的PdfCanvas要重新创建不能复用。5.5 签章图片背景不透明现象盖的章图有白色背景盖上去把正文遮住了。原因章图是 JPG 或没有 alpha 通道的 PNG。解决用带透明通道的 PNGiText 的Image对象会保留透明度。如果只有 JPG可以用PdfCanvas手动做透明处理但麻烦不如换图。6. 进阶技巧用模板 数据绑定替代硬替换文本替换做多了会发现与其事后替换不如一开始就用模板。iText 7 的pdfHTML模块能把 HTML 转 PDF配合模板引擎如 FreeMarker做数据绑定比操作内容流稳得多。具体做法用 FreeMarker 定义 HTML 模板占位符写成${name}渲染成 HTML 字符串再用pdfHTML转 PDF。中文、样式、分页都好控制。签章和水印再在这个基础上叠加。// FreeMarker pdfHTML 生成 PDF Configuration cfg new Configuration(Configuration.VERSION_2_3_31); cfg.setDirectoryForTemplateLoading(new File(templates)); Template template cfg.getTemplate(contract.ftl); String html FreeMarkerTemplateUtils.processTemplateIntoString(template, data); ConverterProperties props new ConverterProperties(); props.setFontProvider(new DefaultFontProvider(true, true, true)); HtmlConverter.convertToPdf(html, new PdfWriter(output.pdf), props);逻辑说明data是 Map里面放业务数据。DefaultFontProvider第三个参数true表示加载系统字体中文能正常显示。ConverterProperties还能设 base URI处理图片相对路径。参数上DefaultFontProvider的三个布尔值分别是标准字体、扩展字体、系统字体。中文场景必须开系统字体。如果模板里有 CSSpdfHTML支持大部分常用样式但 flex 和 grid 支持有限布局别写太花。验证方法生成后随机抽几页用PdfTextExtractor提取文字对比模板数据是否一致。签章用 Adobe Reader 验证签名状态水印看透明度和位置。我一般还会用qpdf --check做一次结构校验确认没有损坏。从那以后我每次做 PDF 生成都强制先走一遍“模板渲染 → 结构校验 → 签名验证”三步不再事后硬替换。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑