You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用iText填充PDF表单域时的行间距控制方案(含换行场景)及Java代码参考

使用iText填充PDF表单域时的行间距控制方案(含换行场景)及Java代码参考

刚巧之前做项目踩过这个坑,来给你唠唠怎么用iText搞定PDF表单域填充时的行间距问题,尤其是遇到换行的场景~

默认情况下,用iText直接给多行表单域填值,行间距是跟着PDF表单域本身的默认样式走的,要是需求里需要调整行高(比如要更宽松的行间距),或者内容里带了手动换行符(\n),直接填的话可能排版会乱。这时候咱们得自定义文本的绘制逻辑,不能用默认的填充方法。

核心实现思路

  • 先获取目标表单域的位置、尺寸信息
  • 创建PdfAppearance对象,这相当于咱们的“画布”,用来手动绘制带指定行间距的文本
  • 自定义行高(比如设置为字号的1.5倍,这个可以按需调整)
  • 处理文本:不管是手动换行的\n还是需要自动换行的长文本,拆分成一行一行的内容
  • 把拆分后的文本逐行绘制到PdfAppearance上,每一行的Y坐标按行高递减(因为PDF的坐标系是左下角为原点)
  • 最后把这个自定义的外观赋值给表单域,替代默认样式

完整Java代码参考

package com.example.demo2.demos.test;

import com.lowagie.text.DocumentException;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfAppearance;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfFormField;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import com.lowagie.text.pdf.AcroFields;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PdfFormFiller {

    // 自定义填充表单域,支持行间距控制
    public static void fillFormWithCustomLineSpacing(InputStream inputStream, String outputPdfPath,
                                                    Map<String, String> fieldValues, float lineSpacingMultiplier) throws IOException, DocumentException {
        PdfReader reader = new PdfReader(inputStream);
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(outputPdfPath));
        AcroFields form = stamper.getAcroFields();

        // 加载基础字体(这里用系统自带的宋体,也可以用项目内的自定义字体文件)
        BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font font = new Font(baseFont, 12); // 设置字号为12
        float fontSize = font.getSize();
        float lineHeight = fontSize * lineSpacingMultiplier; // 计算行高,比如1.5倍行间距

        for (Map.Entry<String, String> entry : fieldValues.entrySet()) {
            String fieldName = entry.getKey();
            String content = entry.getValue();
            if (content == null || content.isEmpty()) continue;

            // 获取表单域的矩形区域
            AcroFields.Item item = form.getFieldItem(fieldName);
            if (item == null) continue;
            PdfFormField field = item.getWidget(0);
            float[] rect = field.getRectangle().getRectangle();
            float x = rect[0];
            float y = rect[1];
            float width = rect[2] - rect[0];
            float height = rect[3] - rect[1];

            // 创建自定义外观
            PdfContentByte cb = stamper.getOverContent(1); // 假设表单在第一页,多页场景需循环处理页码
            PdfAppearance appearance = cb.createAppearance(width, height);
            appearance.setFontAndSize(baseFont, fontSize);

            // 处理文本换行:拆分手动换行和自动换行
            List<String> lines = splitTextIntoLines(content, baseFont, fontSize, width);

            // 从表单域的顶部开始绘制文本(Y坐标从height开始往下减)
            float currentY = height - fontSize; // 顶部留一点边距
            for (String line : lines) {
                appearance.beginText();
                appearance.setTextMatrix(2, currentY); // X坐标留2px边距
                appearance.showText(line);
                appearance.endText();
                currentY -= lineHeight; // 行间距控制
                // 内容超出表单域高度时可添加截断逻辑,按需调整
                if (currentY < 0) break;
            }

            // 把自定义外观赋值给表单域
            form.setFieldAppearance(fieldName, "N", appearance);
        }

        stamper.setFormFlattening(false); // 设为true则表单域变为普通文本,无法再编辑
        stamper.close();
        reader.close();
    }

    // 辅助方法:拆分文本为适配宽度的行,支持手动换行(\n)和自动换行
    private static List<String> splitTextIntoLines(String content, BaseFont baseFont, float fontSize, float maxWidth) {
        List<String> lines = new ArrayList<>();
        String[] manualLines = content.split("\n");
        for (String manualLine : manualLines) {
            if (baseFont.getWidthPoint(manualLine, fontSize) <= maxWidth - 4) { // 减4是预留左右边距
                lines.add(manualLine);
                continue;
            }
            // 自动换行:逐字符拆分,直到一行宽度接近最大值
            StringBuilder currentLine = new StringBuilder();
            float currentWidth = 0;
            for (char c : manualLine.toCharArray()) {
                char[] singleChar = {c};
                float charWidth = baseFont.getWidthPoint(singleChar, fontSize);
                if (currentWidth + charWidth > maxWidth - 4) {
                    lines.add(currentLine.toString().trim());
                    currentLine = new StringBuilder();
                    currentWidth = 0;
                }
                currentLine.append(c);
                currentWidth += charWidth;
            }
            if (currentLine.length() > 0) {
                lines.add(currentLine.toString().trim());
            }
        }
        return lines;
    }

    // 测试方法
    public static void main(String[] args) {
        try {
            Map<String, String> fieldValues = new HashMap<>();
            fieldValues.put("multiLineField", "这是第一行手动换行文\n这是需要自动换行的超长文本,用来测试当内容超过表单域宽度时,是否能自动换行并保持设置的行间距效果");

            fillFormWithCustomLineSpacing(
                    new FileInputStream("input.pdf"),
                    "output.pdf",
                    fieldValues,
                    1.5f // 行间距倍数,可按需调整为1.2、1.8等
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

几个实用注意点

  • 字体适配:如果需要支持中文,一定要加载支持中文的字体,比如代码里的宋体,也可以用项目内的自定义字体文件(示例:BaseFont.createFont("fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED))
  • 行间距灵活调整:lineSpacingMultiplier参数可以按需修改,比如1.2倍是紧凑行高,1.8倍是宽松行高
  • 多页表单适配:代码默认处理第一页的表单域,多页场景需要遍历每一页的表单域信息
  • 表单扁平化:如果需要把填充后的表单域转为不可编辑的普通文本,将stamper.setFormFlattening(true)即可

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 13:23:04