How to wrap text in PDFBox

Mon, Apr 7, 2014

Java Programming #java #programming

PDFBox(http://pdfbox.apache.org/) is a library to generate PDF in Java. We can put in text or embed some images into the generated pdf file. But it is a bit tedious when we are to format the text properly, especially when we have to manually specify the location( position x and y ) of the element in the pdf file. And it becomes harder when we trying to put a lengthy text into fixed width area and want to wrap the text so it will not exceed the boundary.

After a few reading I managed to find a working code from StackOverflow(http://stackoverflow.com/questions/10640152/how-can-i-create-fixed-width-paragraphs-with-pdfbox) and this article is mainly to demonstrate how I put the solution into working environment.

First we need to define a class Paragraph. Basically this class will take all the necessary parameters and perform the line breaking logic in it.

public class Paragraph {

        /** width of this paragraph */
        private int width;

        /** text to write */
        private String text;

        /** font to use */
        private PDFont font;

        /** font size to use */
        private int fontSize;

        public Paragraph(int width, PDFont font, int fontSize, String text) {
            this.text = text;
            this.font = font;
            this.width = width;
            this.fontSize = fontSize;
        }

        /**
         * Break the text in lines
         * @return
         */
        public List getLines() throws IOException {
            List result = new ArrayList();

            String[] split = text.split("(?<=\W)");
            int[] possibleWrapPoints = new int[split.length];
            possibleWrapPoints[0] = split[0].length();
            for ( int i = 1 ; i < split.length ; i++ ) {
                possibleWrapPoints[i] = possibleWrapPoints[i-1] + split[i].length();
            }

            int start = 0;
            int end = 0;
            for ( int i : possibleWrapPoints ) {
                float width = font.getStringWidth(text.substring(start,i)) / 1000 * fontSize;
                if ( start < end && width > this.width ) {
                    result.add(text.substring(start,end));
                    start = end;
                }
                end = i;
            }
            // Last piece of text
            result.add(text.substring(start));
            return result;
        }

        public float getFontHeight() {
            return font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
        }

        public int getWidth() {
            return width;
        }

        public String getText() {
            return text;
        }

        public PDFont getFont() {
            return font;
        }

        public int getFontSize() {
            return fontSize;
        }

    }

A little explanation on this class. You might find that this code is slightly different from the one in the StackOverflow page. This is due to I am modifying the code in order to fit my use case better. The constructor will take the following parameters.

int width - the width of the paragraph
PDFont font - the font to be used
int fontSize - the font size
String text - the lengthy text

After that we will call the

getLines

function to retrieve a list of strings. This function will loop the characters in the string and append it to a temporary string. Then it will calculate whether this temporary string exceeds the intended width or not, if yes, then break to another string, else, append character again. Here is how to use the class.

PDFFont font = PDType1Font.HELVETICA;
int fontSize = 11;
Color color = new Color(16225054);
Paragraph paragraph = new Paragraph(130, font, fontSize, longText);
PDPageContentStream contentStream = new PDPageContentStream(document, page, true, false);
// the position Y of the text element
float initY = 9.275f;
for(String line : paragraph.getLines()) {
    new TextPdfElement(
        line,
        font, fontSize, color, 0.900f, initY, TextPdfElement.TextAlignment.LEFT).draw(contentStream, mediaBox);
        // increase value of position Y to mimic line breaking
    initY+=0.15f;
}
contentStream.close();

Leave a comment if you need more information on this. Thank you for reading.

**Updates 2014-08-04

Carlos pointed out as class TextPdfElement is a custom class, hence I am including the source code to it.

import java.awt.Color;
import java.io.IOException;

import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;

public class TextPdfElement implements PdfElement {
    public enum TextAlignment {
        CENTER,
        LEFT,
        RIGHT
    };
   
    private String message;
    private PDFont font;
    private float fontSize;
    private Color fontcolor;
    private float x;
    private float y;
    private TextAlignment align = TextAlignment.LEFT;
        
    public TextPdfElement() {}

    public TextPdfElement( String message, PDFont font, float fontSize, Color fontColor, float x, float y, TextAlignment align ) {
        this.message = message;
        this.font = font;
        this.fontSize = fontSize;
        this.fontcolor = fontColor;
        this.x = x;
        this.y = y;        
        this.align = align;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public void setFont(PDFont font) {
        this.font = font;
    }
    
    public void setFontSize(float fontSize) {
        this.fontSize = fontSize;
    }
    
    public void setFontColor(Color fontColor) {
        this.fontcolor = fontColor;
    }
    
    public void setPosition(float x, float y) {
        this.x = x;
        this.y = y;        
    }
    
    public void setTextAlignment(TextAlignment align) {
        this.align = align;
    }
    
    @Override
    public void draw(PDPageContentStream contentStream, PDRectangle region) {        
        if ( this.message == null) 
            return;
        
        try {
             contentStream.beginText();
             
             contentStream.setFont( this.font, this.fontSize );
             contentStream.setNonStrokingColor(this.fontcolor);
             if (this.align == TextAlignment.CENTER) {
                 float stringWidth = font.getStringWidth( this.message )*fontSize/1000f;
                 float centerXPos = ( region.getWidth() - stringWidth ) / 2f;
                 contentStream.setTextTranslation(centerXPos, region.getHeight() -  (this.y*72) );
             } else {
                 contentStream.setTextTranslation(this.x * 72, region.getHeight() - (this.y*72) );
             }
             
             contentStream.drawString( this.message );
             contentStream.endText();
        } catch (IOException e) {            
            e.printStackTrace();
        }
    }
}

**Updates 2014-08-18

Again, I forgot to attach another class. Really sorry about it. Here is the code.

import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;

public interface PdfElement {
    public void draw(PDPageContentStream stream, PDRectangle region);
}