使用Java 8与PDFBox 2.0.30创建PDF表单:复选框边框与文字无法共存问题
问题:PDFBox创建可填写表单时复选框与文字显示异常
我首次使用Java 8和PDFBox 2.0.30创建可填写PDF表单,需求是给每个配料选项添加带边框的复选框及对应配料文字。但运行代码后,生成的toppingsForm.pdf在浏览器中显示异常:要么显示带边框的复选框但无配料文字,要么显示配料文字但复选框无边框。我怀疑是尺寸重叠问题,或者存在直接设置复选框可见边框的更优实现方式,相关代码如下:
import java.io.*; import java.util.*; import java.awt.Color; import java.awt.geom.Rectangle2D; import org.apache.pdfbox.*; import org.apache.pdfbox.util.*; import org.apache.pdfbox.cos.*; import org.apache.pdfbox.pdmodel.*; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.graphics.color.PDColor; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.interactive.*; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceCharacteristicsDictionary; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; import org.apache.pdfbox.pdmodel.interactive.form.*; import org.apache.pdfbox.text.*; import org.apache.pdfbox.contentstream.*; import org.apache.pdfbox.pdfparser.*; import org.apache.pdfbox.io.*; public class Toppings { private static float getLineWidth( PDAnnotationWidget widget) { PDBorderStyleDictionary bs = widget.getBorderStyle(); if( bs != null) { return bs.getWidth(); } return 1; } private static void drawRect( PDDocument document, PDPage page, PDRectangle rect) { try { PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); float ll_x = rect.getLowerLeftX(); float ll_y = rect.getLowerLeftY(); float height = rect.getHeight(); float length = rect.getWidth(); contentStream.addRect( ll_x, ll_y, height, length); contentStream.setLineWidth(1); contentStream.setNonStrokingColor(Color.WHITE); contentStream.setStrokingColor(Color.BLACK); contentStream.stroke(); contentStream.close(); } catch (IOException e) { e.printStackTrace(); } } // Generate Form Labels private static void addText( PDDocument document, PDPage page, String myText, float x, float y, boolean bold) { try { PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true); contentStream.beginText(); contentStream.setFont(PDType1Font.HELVETICA, 6); if( bold) { contentStream.setFont(PDType1Font.HELVETICA_BOLD, 6); } contentStream.newLineAtOffset(x, y); contentStream.showText(myText); contentStream.endText(); contentStream.close(); } catch (IOException e) { e.printStackTrace(); } } private static void addCheckbox( PDDocument pdfDoc, PDAcroForm acroForm, PDPage page, String name, float x, float y) { try { PDCheckBox checkbox = new PDCheckBox(acroForm); checkbox.setPartialName( name); PDAppearanceCharacteristicsDictionary appearanceCharacteristics = new PDAppearanceCharacteristicsDictionary(new COSDictionary()); appearanceCharacteristics.setBorderColour(new PDColor(new float[] { 1, 0, 0 }, PDDeviceRGB.INSTANCE)); appearanceCharacteristics.setBackground( new PDColor(new float[]{0, 1, 0.3f}, PDDeviceRGB.INSTANCE)); appearanceCharacteristics.setNormalCaption("4"); PDBorderStyleDictionary borderStyleDictionary = new PDBorderStyleDictionary(); borderStyleDictionary.setWidth(1); borderStyleDictionary.setStyle(PDBorderStyleDictionary.STYLE_SOLID); PDAnnotationWidget widget = new PDAnnotationWidget(); widget.setRectangle( new PDRectangle( x, y, 16, 16) ); drawRect( pdfDoc, page, widget.getRectangle()); // comment out this line and toppings appear widget.setAnnotationFlags(4); widget.setBorderStyle(borderStyleDictionary); widget.setPage( page); widget.setParent( checkbox); List<PDAnnotationWidget> widgets = new ArrayList<>(); widgets.add(widget); page.getAnnotations().add(widget); checkbox.setWidgets(widgets); acroForm.getFields().add(checkbox); addText( pdfDoc, page, name, x + 20, y + 6, false); // comment out this line and checkbox outlines appear } catch (IOException e) { e.printStackTrace(); } } private static void addToppings( PDDocument pdfDoc, PDAcroForm acroForm, PDPage page, float x, float y) throws IOException { List<String> options = Arrays.asList("pepperoni","sausage","ham" ,"chicken","canadian bacon","mushrooms" ,"pineapple","onions","green pepper" ,"red pepper","black olives","green olives"); for( int i = 0; i < 12; i++ ) { addCheckbox( pdfDoc, acroForm, page, options.get(i), x, y); y = y - 20; } } public static void main(String[] args) { try { PDDocument pdfDoc = new PDDocument(); // outfile PDPage page = new PDPage(); pdfDoc.addPage(page); PDPageContentStream contentStream = new PDPageContentStream( pdfDoc, page); PDAcroForm acroForm = new PDAcroForm( pdfDoc); float x = 20; float y = 500; addToppings( pdfDoc, acroForm, page, x, y); contentStream.close(); pdfDoc.save("C:/Users/MainUser/toppingsForm.pdf"); pdfDoc.close(); } catch ( IOException e) { e.getMessage(); } } }
问题根源与修复方案
- 手动绘制矩形与表单控件层级冲突:你调用
drawRect手动绘制复选框边框,同时又通过PDAnnotationWidget创建表单控件,两者在PDF内容流和注释层产生覆盖,导致浏览器渲染时只能显示其中一层。PDF表单的复选框控件本身支持配置边框,无需手动绘制。 - 未关联AcroForm到文档Catalog:代码创建了
PDAcroForm但未添加到文档目录,导致表单控件渲染逻辑不完整。 - 外观流未生成:复选框的边框和勾选标记需要主动生成外观流才能正常显示。
修复后的完整代码
import java.io.*; import java.util.*; import java.awt.Color; import org.apache.pdfbox.pdmodel.*; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.graphics.color.PDColor; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceCharacteristicsDictionary; import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; import org.apache.pdfbox.pdmodel.interactive.form.*; public class Toppings { // 生成表单标签 private static void addText(PDDocument document, PDPage page, String myText, float x, float y, boolean bold) { try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true)) { contentStream.beginText(); contentStream.setFont(bold ? PDType1Font.HELVETICA_BOLD : PDType1Font.HELVETICA, 12); contentStream.newLineAtOffset(x, y); contentStream.showText(myText); contentStream.endText(); } catch (IOException e) { e.printStackTrace(); } } private static void addCheckbox(PDDocument pdfDoc, PDAcroForm acroForm, PDPage page, String name, float x, float y) { try { PDCheckBox checkbox = new PDCheckBox(acroForm); checkbox.setPartialName(name); checkbox.setExportValue(name); // 配置复选框外观特性 PDAppearanceCharacteristicsDictionary appearanceCharacteristics = new PDAppearanceCharacteristicsDictionary(new COSDictionary()); appearanceCharacteristics.setBorderColour(new PDColor(new float[]{0, 0, 0}, PDDeviceRGB.INSTANCE)); // 黑色边框 appearanceCharacteristics.setBackground(new PDColor(new float[]{1, 1, 1}, PDDeviceRGB.INSTANCE)); // 白色背景 appearanceCharacteristics.setNormalCaption("4"); // 勾选标记样式 // 配置边框样式 PDBorderStyleDictionary borderStyleDictionary = new PDBorderStyleDictionary(); borderStyleDictionary.setWidth(1); borderStyleDictionary.setStyle(PDBorderStyleDictionary.STYLE_SOLID); PDAnnotationWidget widget = checkbox.getWidgets().get(0); widget.setRectangle(new PDRectangle(x, y, 16, 16)); widget.setAnnotationFlags(4); // 设置为可打印 widget.setBorderStyle(borderStyleDictionary); widget.setAppearanceCharacteristics(appearanceCharacteristics); widget.setPage(page); page.getAnnotations().add(widget); acroForm.getFields().add(checkbox); // 添加配料文字,调整坐标对齐复选框 addText(pdfDoc, page, name, x + 20, y + 4, false); // 强制生成复选框外观流 widget.generateAppearance(); } catch (IOException e) { e.printStackTrace(); } } private static void addToppings(PDDocument pdfDoc, PDAcroForm acroForm, PDPage page, float x, float y) throws IOException { List<String> options = Arrays.asList("pepperoni", "sausage", "ham", "chicken", "canadian bacon", "mushrooms", "pineapple", "onions", "green pepper", "red pepper", "black olives", "green olives"); for (String option : options) { addCheckbox(pdfDoc, acroForm, page, option, x, y); y -= 20; } } public static void main(String[] args) { try (PDDocument pdfDoc = new PDDocument()) { PDPage page = new PDPage(); pdfDoc.addPage(page); PDAcroForm acroForm = new PDAcroForm(pdfDoc); // 将AcroForm关联到文档目录,这是表单正常渲染的必要步骤 pdfDoc.getDocumentCatalog().setAcroForm(acroForm); float x = 50; float y = 750; // 从页面顶部开始布局,避免超出可见区域 addToppings(pdfDoc, acroForm, page, x, y); pdfDoc.save("C:/Users/MainUser/toppingsForm.pdf"); } catch (IOException e) { e.printStackTrace(); } } }
关键修复点说明
- 移除手动绘制矩形的
drawRect方法:依赖PDFBox表单控件自身的边框配置,避免层级覆盖。 - 关联AcroForm到文档Catalog:
pdfDoc.getDocumentCatalog().setAcroForm(acroForm)是表单控件正常渲染的核心步骤。 - 强制生成外观流:调用
widget.generateAppearance()确保复选框的边框和勾选标记正确生成。 - 优化文字布局:调整文字坐标使其与复选框对齐,放大字体提升可读性。
- 简化代码结构:移除不必要的导入和方法,逻辑更清晰。
内容的提问来源于stack exchange,提问作者code_warrior
相关产品推荐
相关产品推荐

