主要解决问题:

  1. 解决PdfAcroForm,使用AcroFields填充字体,修改字体样式有兼容性问题,不支持嵌入字体子集嵌入等问题
  2. 可以设置pdf表单文本空间中多行文本行间距
    通过AcroFields获取表单控件位置,使用Paragraph+ColumnText处理文本位置和段落行间距,解决表单字段AcroFields不支持设置行间距问题

依赖:

<!--pdfbox生成PDF,开源,但是表单控件填充中文字体时会报错,这个问题没有解决所以采用itextpdf-->
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>3.0.3</version>
</dependency>

<!--itext生成PDF-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.3</version>
</dependency>
<!--输出中文-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

代码:

import org.springframework.core.io.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.xxx.PdfRectangleDO;
import com.xxx.PdfTemplateVarDO;
import com.xxx.StringUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import net.kr36.wuli.core.constant.DigitConstant;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * pdf工具
 *
 * @author qungmu
 */
@Slf4j
public class PdfItextUtil {

    private static final String TEMPLATE_PATH = "/template/pdf_template.pdf";		// 2025.06.10 可以用Adobe Acrobat DC 或 福昕PDF编辑器个人版,绘制pdf模板,包含表单控件
    private final static String FONTS_FEI_TTF = "/template/feihuasongti.ttf";

    /**
     * pdf生成
     *
     * @param outputStream     outputStream
     * @param pdfTemplateVarDO pdfBoxTemplateVarDO
     * @return void
     */
    public static void outputPdfDocument(OutputStream outputStream, PdfTemplateVarDO pdfTemplateVarDO) {
        log.info("pdf写入开始 入参 dto=【{}】", pdfTemplateVarDO);
        PdfStamper stamper = null;
        InputStream inputStream = null;
        try {
            inputStream = new ClassPathResource(TEMPLATE_PATH).getInputStream();
            PdfReader pdfReader = new PdfReader(inputStream);
            stamper = new PdfStamper(pdfReader, outputStream);
            BaseFont baseFont = BaseFont.createFont(FONTS_FEI_TTF, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            // BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);  
            Font contentFont = new Font(baseFont, 10F);
            
            AcroFields acroFields = stamper.getAcroFields();
            PdfContentByte overContent = stamper.getOverContent(1);
            if (ObjectUtil.isNotEmpty(pdfTemplateVarDO)) {
                acroFields.addSubstitutionFont(baseFont);
                baseFont.setSubset(true);

                Map<String, AcroFields.Item> fields = acroFields.getFields();
                HashMap<String, Object> formData = StringUtil.objToBeanBySpring(pdfTemplateVarDO, new TypeReference<HashMap<String, Object>>() {
                });

                if (ObjectUtil.isNotEmpty(fields) && ObjectUtil.isNotEmpty(formData)) {
                    for (String keyElem : fields.keySet()) {
                        AcroFields.Item elemItem = fields.get(keyElem);
                        if (elemItem == null) {
                            continue;
                        }
                        
                        PdfArray asArray = elemItem.getWidget(0).getAsArray(PdfName.RECT);			// 2025.06.10 获取pdf表单控件位置
                        PdfRectangleDO rectangleDO = new PdfRectangleDO()
                            .setLlx(asArray.getAsNumber(DigitConstant.N_0).floatValue())
                            .setLly(asArray.getAsNumber(DigitConstant.N_1).floatValue())
                            .setUrx(asArray.getAsNumber(DigitConstant.N_2).floatValue())
                            .setUry(asArray.getAsNumber(DigitConstant.N_3).floatValue())
                            .setContent(Optional.ofNullable(formData.get(keyElem)).map(elemValue -> elemValue.toString()).orElse(""));
                        PdfItextUtil.setRectangle(overContent, contentFont, rectangleDO);
                    }
                }
            }

            // 如果为false,生成的PDF文件可以编辑,如果为true,生成的PDF文件不可以编辑
            stamper.setFormFlattening(true);
            stamper.close();
        } catch (Exception e) {
            log.error("pdf生成工具.异常 e.message=【{}】", e.getMessage(), e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    log.error("pdf生成工具.inputStream.close 异常 e.message=【{}】", e.getMessage(), e);
                }
            }
            if (stamper != null) {
                try {
                    stamper.close();
                } catch (Exception e) {
                    log.error("pdf生成工具.pdfDocument.close 异常 e.message=【{}】", e.getMessage(), e);
                }
            }
            log.info("pdf写入完成 入参 dto=【{}】", pdfTemplateVarDO);
        }

    }

    /**
     * 设置段落文本
     * @param directContent directContent
     * @param contentFont contentFont
     * @return void
     */
    private static void setRectangle(PdfContentByte directContent, Font contentFont, PdfRectangleDO rectangleDO) throws DocumentException {
        log.info("pdf生成工具.设置段落文本 入参 dto=【{}】", rectangleDO);
        if (rectangleDO == null) {
            return;
        }
        
        Paragraph paragraph = new Paragraph();
        Phrase phrase = new Phrase();
        phrase.add(new Chunk(rectangleDO.getContent(), contentFont));
        paragraph.add(phrase);
        paragraph.setSpacingBefore(0f);
        paragraph.setLeading(0, 1.5f);


        // 文本框位置
        Rectangle rectangle = new Rectangle(rectangleDO.getLlx(), rectangleDO.getLly(), rectangleDO.getUrx(), rectangleDO.getUry());
        // 显示边框,默认不显示,常量值:LEFT, RIGHT, TOP, BOTTOM,BOX,
        rectangle.setBorder(Rectangle.BOX);
        // 边框线条粗细
        rectangle.setBorderWidth(1f);
        // 边框颜色
        rectangle.setBorderColor(BaseColor.GREEN);
        // 背景颜色
        rectangle.setBackgroundColor(BaseColor.GRAY);
        // directContent.rectangle(rectangle);				        // 2025.06.10 绘制图形,需要先绘制Rectangle,再设置ColumnText,否则会覆盖ColumnText中的文本
        
        ColumnText columnText = new ColumnText(directContent);			// 2025.06.10 设置段落文本内容,并设置行间距,首行缩进
        columnText.setSimpleColumn(rectangle);
        columnText.addElement(paragraph);
        columnText.go();
    }
    
    
}



 posted on 2025-06-10 16:48  倾目  阅读(200)  评论(0)    收藏  举报