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

使用PDFBox动态添加PDF页面时遭遇IllegalStateException错误求助

动态添加PDF页面时的IllegalStateException问题及解决方案

错误信息

Exception in thread "main" java.lang.IllegalStateException: Error: You must call beginText() before calling endText.
at org.apache.pdfbox.pdmodel.PDPageContentStream.endText(PDPageContentStream.java:385)
at PDFUtility.content(PDFUtility.java:123)
at StudyConfigImpl.getStudyConfigPDF(StudyConfigImpl.java:39)
at Main.main(Main.java:6)

问题场景

尝试在内容超出页面限制时动态添加PDF页面,触发上述异常。

原代码

addParagraph方法

private float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                 float sy, String text, boolean justify,float initY,float pageHeight,PDDocument document) throws IOException {
    PDFont FONT = PDType1Font.HELVETICA;
    float FONT_SIZE = 7;
    float LEADING = -1.5f * FONT_SIZE;
    java.util.List<String> lines = parseLines(text, width);
    contentStream.setFont(FONT, FONT_SIZE);
    contentStream.newLineAtOffset(sx, sy);
    float totalHeight = initY;
    for (String line: lines) {
        float charSpacing = 0;
        if (justify){
            if (line.length() > 1) {
                float size = FONT_SIZE * FONT.getStringWidth(line) / 1000;
                float free = width - size;
                if (free > 0 && !lines.get(lines.size() - 1).equals(line)) {
                    charSpacing = free / (line.length() - 1);
                }
            }
        }
        contentStream.setCharacterSpacing(charSpacing);
        float fontHeight = FONT.getFontDescriptor().getFontBoundingBox().getHeight() / 1000*FONT_SIZE;
        totalHeight -=fontHeight;
        System.out.println(totalHeight);
        if(totalHeight - StudyConfigImpl.PAGE_MARGIN <= 0){
            PDPage nextPage = new PDPage();
            nextPage.setMediaBox(PDRectangle.A4);
            document.addPage(nextPage);
            totalHeight = pageHeight - StudyConfigImpl.PAGE_MARGIN;
            contentStream.endText();
            contentStream.close();
            contentStream = new PDPageContentStream(document,nextPage);
            contentStream.beginText();
            contentStream.setFont(FONT,FONT_SIZE);
            contentStream.newLineAtOffset(StudyConfigImpl.INIT_POSITION_X,StudyConfigImpl.INIT_POSITION_Y);
        }
        contentStream.showText(line);
        contentStream.newLineAtOffset(0, LEADING);
    }
    return (lines.size()*(7+3));
}

content方法

public float content(
        PDPageContentStream contentStream,
        float initX,
        float initY,
        float pageHeight,
        float width,
        float height,
        Map<Object, Object> studyData,
        PDDocument document
        ) throws IOException {
    int counter = 0;
    for(Map.Entry<Object, Object> entry : studyData.entrySet()){
        if(entry.getKey().equals("Additional Notes") || entry.getKey().equals("Study Desc.")){
            initY-=25;
            contentStream.setFont(PDType1Font.HELVETICA_BOLD,7);
            contentStream.beginText();
            initX = 20;
            contentStream.newLineAtOffset(initX,initY);
            contentStream.showText(entry.getKey().toString());
            float sy = addParagraph(contentStream,555,0,-10,entry.getValue().toString(),true,initY,pageHeight,document);
            contentStream.endText();
            initY -=sy;

        }else{
            contentStream.addRect(initX, pageHeight - initY, width, height);
            contentStream.setFont(PDType1Font.HELVETICA_BOLD, 7);
            contentStream.beginText();
            contentStream.newLineAtOffset(initX, initY);
            contentStream.showText(entry.getKey().toString());
            contentStream.newLine();
            contentStream.setFont(PDType1Font.HELVETICA, 7);
            contentStream.showText(entry.getValue().toString());
            contentStream.endText();
            initX += width;
            counter++;
            if (counter == 5) {
                initX = 20;
                initY = initY - 20;
                counter = 0;
            }
        }
    }
    return  initY;
}

问题根源

  1. Java值传递导致引用失效:addParagraph方法中重新赋值contentStream(创建新页面的流)不会影响content方法中的原始引用。分页发生后,content方法调用contentStream.endText()时,操作的是已被关闭的旧流,而旧流在addParagraph中已经执行过endText()并关闭,触发异常。
  2. 文本状态不一致:新创建的流在addParagraph中已调用beginText(),但外部方法对旧流的操作仍会导致状态错误。

解决方案

步骤1:创建辅助类传递结果

定义辅助类,同时返回更新后的流和段落总高度:

class ParagraphResult {
    PDPageContentStream contentStream;
    float totalHeight;

    public ParagraphResult(PDPageContentStream contentStream, float totalHeight) {
        this.contentStream = contentStream;
        this.totalHeight = totalHeight;
    }
}

步骤2:修改addParagraph方法

调整返回类型为ParagraphResult,确保新流能传递回调用方:

private ParagraphResult addParagraph(PDPageContentStream contentStream, float width, float sx,
                                     float sy, String text, boolean justify, float initY, float pageHeight, PDDocument document) throws IOException {
    PDFont FONT = PDType1Font.HELVETICA;
    float FONT_SIZE = 7;
    float LEADING = -1.5f * FONT_SIZE;
    java.util.List<String> lines = parseLines(text, width);
    contentStream.setFont(FONT, FONT_SIZE);
    contentStream.newLineAtOffset(sx, sy);
    float totalHeight = initY;

    for (String line : lines) {
        float charSpacing = 0;
        if (justify) {
            if (line.length() > 1) {
                float size = FONT_SIZE * FONT.getStringWidth(line) / 1000;
                float free = width - size;
                if (free > 0 && !lines.get(lines.size() - 1).equals(line)) {
                    charSpacing = free / (line.length() - 1);
                }
            }
        }
        contentStream.setCharacterSpacing(charSpacing);
        float fontHeight = FONT.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * FONT_SIZE;
        totalHeight -= fontHeight;

        if (totalHeight - StudyConfigImpl.PAGE_MARGIN <= 0) {
            // 关闭旧流
            contentStream.endText();
            contentStream.close();
            // 创建新页面和新流
            PDPage nextPage = new PDPage(PDRectangle.A4);
            document.addPage(nextPage);
            contentStream = new PDPageContentStream(document, nextPage);
            contentStream.beginText();
            contentStream.setFont(FONT, FONT_SIZE);
            contentStream.newLineAtOffset(StudyConfigImpl.INIT_POSITION_X, StudyConfigImpl.INIT_POSITION_Y);
            // 重置总高度为新页面起始位置
            totalHeight = pageHeight - StudyConfigImpl.PAGE_MARGIN;
        }

        contentStream.showText(line);
        contentStream.newLineAtOffset(0, LEADING);
    }

    return new ParagraphResult(contentStream, lines.size() * (7 + 3));
}

步骤3:修改content方法

接收返回的新流,确保后续操作有效流:

public float content(
        PDPageContentStream contentStream,
        float initX,
        float initY,
        float pageHeight,
        float width,
        float height,
        Map<Object, Object> studyData,
        PDDocument document
) throws IOException {
    int counter = 0;
    for (Map.Entry<Object, Object> entry : studyData.entrySet()) {
        if (entry.getKey().equals("Additional Notes") || entry.getKey().equals("Study Desc.")) {
            initY -= 25;
            contentStream.setFont(PDType1Font.HELVETICA_BOLD, 7);
            contentStream.beginText();
            initX = 20;
            contentStream.newLineAtOffset(initX, initY);
            contentStream.showText(entry.getKey().toString());
            // 获取更新后的流和高度
            ParagraphResult result = addParagraph(contentStream, 555, 0, -10, entry.getValue().toString(), true, initY, pageHeight, document);
            contentStream = result.contentStream;
            // 对有效流执行endText
            contentStream.endText();
            initY -= result.totalHeight;

        } else {
            contentStream.addRect(initX, pageHeight - initY, width, height);
            contentStream.setFont(PDType1Font.HELVETICA_BOLD, 7);
            contentStream.beginText();
            contentStream.newLineAtOffset(initX, initY);
            contentStream.showText(entry.getKey().toString());
            contentStream.newLine();
            contentStream.setFont(PDType1Font.HELVETICA, 7);
            contentStream.showText(entry.getValue().toString());
            contentStream.endText();
            initX += width;
            counter++;
            if (counter == 5) {
                initX = 20;
                initY = initY - 20;
                counter = 0;
            }
        }
    }
    return initY;
}

额外注意事项

  • 确保所有PDPageContentStream使用完毕后正确关闭,避免资源泄漏。
  • 分页时的坐标计算需与页面边距保持一致,防止内容超出页面范围。
  • 测试不同长度的文本,验证分页逻辑稳定性。

内容的提问来源于stack exchange,提问作者Sagar Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 17:06:32