iText 7 写中文必须注册支持中文字体并启用子集嵌入,否则空白或方块且体积暴涨;PDFBox 插图需按DPI换算缩放,透明PNG处理能力弱于iText 7。

用 iText 7 写入中文文本必须设置字体,否则是空白或方块
iText 7 默认不支持中文字体,Document.add(new Paragraph("你好")) 渲染出来要么是空的,要么全是方块。这不是编码问题,是字体缺失导致的底层字形无法映射。
实操上必须显式注册一个支持中文的 TrueType 字体(如 NotoSansCJKsc-Regular.otf 或系统里的 simhei.ttf),再用它构造 FontProvider:
FontProvider provider = new FontProvider();
provider.addFont("NotoSansCJKsc-Regular.otf");
pdfFont = provider.getDefaultFontProgram(); // 或 provider.getFont("NotoSansCJKsc-Regular.otf", true)常见错误包括:只调用 addFont() 却没在 Paragraph 中指定该字体;用绝对路径但部署时文件找不到;字体文件本身不包含 GBK 或 UTF-8 所需字形(比如某些精简版 simsun)。
PDFBox 插入图片时尺寸失真,本质是 DPI 和缩放逻辑没对齐
PDImageXObject.createFromFile() 加载图片后,直接用 contentStream.drawImage() 绘制,很容易拉伸变形。PDFBox 默认按 1:1 像素绘制,但 PDF 坐标单位是「点」(1/72 英寸),而图片原始 DPI 不一定是 72。
立即学习“Java免费学习笔记(深入)”;
正确做法是先获取图片原始宽高(image.getWidth() / image.getHeight()),再按目标显示尺寸反推缩放比例:
float scale = 100f / image.getWidth(); // 想显示为 100 点宽 contentStream.drawImage(image, x, y, image.getWidth() * scale, image.getHeight() * scale);
容易踩的坑:用 image.getRawWidth() 替代 getWidth()(前者是原始像素,后者已考虑 DPI);忽略 PDPageContentStream 的坐标系原点在左下角;批量插入时没重置 saveGraphicsState() / restoreGraphicsState() 导致后续绘制偏移。
iText 7 和 PDFBox 对透明 PNG 的处理差异很大
iText 7 从 7.2 开始才通过 PdfCanvas + ImageDataFactory 支持带 alpha 通道的 PNG;而 PDFBox 一直能原生读取,但渲染效果依赖 PDF 查看器——有些会把透明区域转成白色背景。
如果业务要求保真透明,优先选 iText 7,并确保:
-
ImageDataFactory.create(…)返回的ImageData实例调用过isMasked()判断为true - 写入时用
canvas.addImageAt()而非document.add(),避免自动封装进Paragraph引发格式降级 - 生成后用 Adobe Acrobat 检查「输出预览 → 透明度」是否启用,不是所有 PDF 阅读器都认这个特性
生成含中文的 PDF 后文件体积暴涨,通常是字体子集没生效
用 iText 7 注册完整中文字体文件(如 15MB 的 NotoSansCJK.ttc)后,若未开启子集嵌入,每个中文字符都会把整个字体塞进 PDF,体积动辄翻几倍。
必须显式启用子集:
FontProvider provider = new FontProvider();
provider.addFont("NotoSansCJKsc-Regular.otf", true); // 第二个参数 true 表示启用子集PDFBox 没有等效开关,它默认只嵌入实际用到的字形,但前提是加载字体时用 PDType0Font.load() 并传入 encoding 参数(如 Identity-H)。漏掉这个参数,就会退化成全量嵌入。
真正难调试的是:字体子集生效了,但某些生僻字仍触发全量嵌入——因为 iText 在构建子集时发现该字形依赖其他未声明的 GSUB 特性,这时得换字体或手动拆分文本流。










