主要解決問題:
- 解決PdfAcroForm,使用AcroFields填充字體,修改字體樣式有兼容性問題,不支持嵌入字體子集嵌入等問題
- 可以設置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();
}
}