使用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; }
问题根源
- Java值传递导致引用失效:
addParagraph方法中重新赋值contentStream(创建新页面的流)不会影响content方法中的原始引用。分页发生后,content方法调用contentStream.endText()时,操作的是已被关闭的旧流,而旧流在addParagraph中已经执行过endText()并关闭,触发异常。 - 文本状态不一致:新创建的流在
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
相关产品推荐
相关产品推荐

