Search code examples
javaspring-bootpdfbox

Adding pdf pages Dynamically using PDFBox


I am trying to add pdf page dynamically when content exceeds the limit of page. It gives this error.Please review the below code and help me with a solution.

{ 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) }

 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));
}




 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;
}

Solution

  • The cause

    In addParagraph() you conditionally assign a new PDPageContentStream to contentStream but this new value is not returned to the caller content():

    private float addParagraph(PDPageContentStream contentStream, float width, float sx, float sy, String text, boolean justify, float initY, float pageHeight, PDDocument document) throws IOException {
        ...
                contentStream.endText();
                contentStream.close();
                contentStream = new PDPageContentStream(document,nextPage);
                contentStream.beginText();
        ...
        return (lines.size()*(7+3));
    }
    

    Before re-assigning contentStream you in particular call endText() for the old stream.

    Thus, content() continues working with the old content stream and calls endText() for it again:

    public float content(PDPageContentStream contentStream, float initX, float initY, float pageHeight, float width, float height, Map<Object, Object> studyData, PDDocument document) throws IOException {
        ...
                float sy = addParagraph(contentStream,555,0,-10,entry.getValue().toString(),true,initY,pageHeight,document);
                contentStream.endText();
        ...
        return  initY;
    }
    

    Calling endText for a content stream that has no open text object triggers the exception you observed.

    A work-around

    Both addParagraph and content appear to be methods of the same class (or at least in two classes one of which is derived from the other). Thus, you can make contentStream a member variable of that class instead of forwarding it as parameter:

    public class PDFUtility {
        public PDPageContentStream contentStream = null;
    
        private float addParagraph(float width, float sx, float sy, String text, boolean justify, float initY, float pageHeight, PDDocument document) throws IOException {
            ...
        }
    
        public float content(float initX, float initY, float pageHeight, float width, float height, Map<Object, Object> studyData, PDDocument document) throws IOException {
            ...
        }
        ...
    }
    

    and in StudyConfigImpl.getStudyConfigPDF instead of

    float y = pdfUtility.content(contentStream, initX, ...);
    

    you do

    pdfUtility.contentStream = contentStream;
    float y = pdfUtility.content(initX, ...);
    contentStream = pdfUtility.contentStream;
    

    This work-around obviously requires that the same instance of your class is not used concurrently from different threads.