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

使用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();
        }
    }

}

问题根源与修复方案

  1. 手动绘制矩形与表单控件层级冲突:你调用drawRect手动绘制复选框边框,同时又通过PDAnnotationWidget创建表单控件,两者在PDF内容流和注释层产生覆盖,导致浏览器渲染时只能显示其中一层。PDF表单的复选框控件本身支持配置边框,无需手动绘制。
  2. 未关联AcroForm到文档Catalog:代码创建了PDAcroForm但未添加到文档目录,导致表单控件渲染逻辑不完整。
  3. 外观流未生成:复选框的边框和勾选标记需要主动生成外观流才能正常显示。

修复后的完整代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:14:51