How to Generate Multiple Lines in PDF Using Apache PDFbox

How to generate multiple lines in PDF using Apache pdfbox

Adding to the answer of Mark you might want to know where to split your long string. You can use the PDFont method getStringWidth for that.

Putting everything together you get something like this (with minor differences depending on the PDFBox version):

PDFBox 1.8.x

PDDocument doc = null;
try
{
doc = new PDDocument();
PDPage page = new PDPage();
doc.addPage(page);
PDPageContentStream contentStream = new PDPageContentStream(doc, page);

PDFont pdfFont = PDType1Font.HELVETICA;
float fontSize = 25;
float leading = 1.5f * fontSize;

PDRectangle mediabox = page.getMediaBox();
float margin = 72;
float width = mediabox.getWidth() - 2*margin;
float startX = mediabox.getLowerLeftX() + margin;
float startY = mediabox.getUpperRightY() - margin;

String text = "I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox";
List<String> lines = new ArrayList<String>();
int lastSpace = -1;
while (text.length() > 0)
{
int spaceIndex = text.indexOf(' ', lastSpace + 1);
if (spaceIndex < 0)
spaceIndex = text.length();
String subString = text.substring(0, spaceIndex);
float size = fontSize * pdfFont.getStringWidth(subString) / 1000;
System.out.printf("'%s' - %f of %f\n", subString, size, width);
if (size > width)
{
if (lastSpace < 0)
lastSpace = spaceIndex;
subString = text.substring(0, lastSpace);
lines.add(subString);
text = text.substring(lastSpace).trim();
System.out.printf("'%s' is line\n", subString);
lastSpace = -1;
}
else if (spaceIndex == text.length())
{
lines.add(text);
System.out.printf("'%s' is line\n", text);
text = "";
}
else
{
lastSpace = spaceIndex;
}
}

contentStream.beginText();
contentStream.setFont(pdfFont, fontSize);
contentStream.moveTextPositionByAmount(startX, startY);
for (String line: lines)
{
contentStream.drawString(line);
contentStream.moveTextPositionByAmount(0, -leading);
}
contentStream.endText();
contentStream.close();

doc.save("break-long-string.pdf");
}
finally
{
if (doc != null)
{
doc.close();
}
}

(BreakLongString.java test testBreakString for PDFBox 1.8.x)

PDFBox 2.0.x

PDDocument doc = null;
try
{
doc = new PDDocument();
PDPage page = new PDPage();
doc.addPage(page);
PDPageContentStream contentStream = new PDPageContentStream(doc, page);

PDFont pdfFont = PDType1Font.HELVETICA;
float fontSize = 25;
float leading = 1.5f * fontSize;

PDRectangle mediabox = page.getMediaBox();
float margin = 72;
float width = mediabox.getWidth() - 2*margin;
float startX = mediabox.getLowerLeftX() + margin;
float startY = mediabox.getUpperRightY() - margin;

String text = "I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox";
List<String> lines = new ArrayList<String>();
int lastSpace = -1;
while (text.length() > 0)
{
int spaceIndex = text.indexOf(' ', lastSpace + 1);
if (spaceIndex < 0)
spaceIndex = text.length();
String subString = text.substring(0, spaceIndex);
float size = fontSize * pdfFont.getStringWidth(subString) / 1000;
System.out.printf("'%s' - %f of %f\n", subString, size, width);
if (size > width)
{
if (lastSpace < 0)
lastSpace = spaceIndex;
subString = text.substring(0, lastSpace);
lines.add(subString);
text = text.substring(lastSpace).trim();
System.out.printf("'%s' is line\n", subString);
lastSpace = -1;
}
else if (spaceIndex == text.length())
{
lines.add(text);
System.out.printf("'%s' is line\n", text);
text = "";
}
else
{
lastSpace = spaceIndex;
}
}

contentStream.beginText();
contentStream.setFont(pdfFont, fontSize);
contentStream.newLineAtOffset(startX, startY);
for (String line: lines)
{
contentStream.showText(line);
contentStream.newLineAtOffset(0, -leading);
}
contentStream.endText();
contentStream.close();

doc.save(new File(RESULT_FOLDER, "break-long-string.pdf"));
}
finally
{
if (doc != null)
{
doc.close();
}
}

(BreakLongString.java test testBreakString for PDFBox 2.0.x)

The result

Screenshot of the result PDF displayed in Acrobat Reader

This looks as expected.

Of course there are numerous improvements to make but this should show how to do it.

Adding unconditional line breaks

In a comment aleskv asked:

could you add line breaks when there are \n in the string?

One can easily extend the solution to unconditionally break at newline characters by first splitting the string at '\n' characters and then iterating over the split result.

E.g. if instead of the long string from above

String text = "I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox"; 

you want to process this even longer string with embedded new line characters

String textNL = "I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox.\nFurthermore, I have added some newline characters to the string at which lines also shall be broken.\nIt should work alright like this...";

you can simply replace

String text = "I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox"; 
List<String> lines = new ArrayList<String>();
int lastSpace = -1;
while (text.length() > 0)
{
[...]
}

in the solutions above by

String textNL = "I am trying to create a PDF file with a lot of text contents in the document. I am using PDFBox.\nFurthermore, I have added some newline characters to the string at which lines also shall be broken.\nIt should work alright like this..."; 
List<String> lines = new ArrayList<String>();
for (String text : textNL.split("\n"))
{
int lastSpace = -1;
while (text.length() > 0)
{
[...]
}
}

(from BreakLongString.java test testBreakStringNL)

The result:

Screenshot

How to move to the next line when adding text using Apache PDFBox

The PDFBox API allows low-level content generation. This implies that you have to do (but also that you are enabled to do) much of the layout work yourself, among that deciding how much to move down to get to the next baseline.

That distance (called leading in this context) depends on a number of factors:

  • the font size used (obviously)
  • how tightly or loosely spaced the text shall appear
  • the presence of elements on the lines involved positioned outside the regular line, e.g. superscripts, subscripts, formulas, ...

The standard is arranged so that the nominal height of tightly spaced lines of text is 1 unit for a font drawn at size 1. Thus, usually you will use a leading of 1..1.5 times the font size unless there is material on the line reaching beyond it.

BTW, if you have to forward to the next line by the same amount very often, you can use the combination of the PDPageContentStream methods setLeading and newLine instead of moveTextPositionByAmount:

content.setFont(font, 12);
content.setLeading(14.5f);
content.moveTextPositionByAmount(x, y);
content.drawString("Some text.");
content.newLine();
content.drawString("Some more text.");
content.newLine();
content.drawString("Still some more text.");

PS: It looks like moveTextPositionByAmount will be deprecated in the 2.0.0 version and be replaced by newLineAtOffset.

PPS: As the OP indicates in a comment,

There is no PDPageContentStream method called setLeading. I'm using PDFBox version 1.8.8.

Indeed, I was looking at the current 2.0.0-SNAPSHOT development version. They are currently implemented like this:

/**
* Sets the text leading.
*
* @param leading The leading in unscaled text units.
* @throws IOException If there is an error writing to the stream.
*/
public void setLeading(double leading) throws IOException
{
writeOperand((float) leading);
writeOperator("TL");
}

/**
* Move to the start of the next line of text. Requires the leading to have been set.
*
* @throws IOException If there is an error writing to the stream.
*/
public void newLine() throws IOException
{
if (!inTextMode)
{
throw new IllegalStateException("Must call beginText() before newLine()");
}
writeOperator("T*");
}

One can easily implement external helper methods doing the equivalent using appendRawCommands((float) leading); appendRawCommands(" TL"); and appendRawCommands("T*");

How to find all internal links in a PDF, using Java Apache PDFBox

The destination of PDPageDestination is not a number (this is only with external page links), it is a page dictionary, so additional efforts are needed to get the number (the method javadoc mentions this). Here a slightly modified excerpt of the PrintBookmarks.java example:

if (dest instanceof PDPageDestination)
{
PDPageDestination pd = (PDPageDestination) dest;
System.out.println("Destination page: " + (pd.retrievePageNumber() + 1));
}

pdfbox wrap text

I don't think it is possible to wrap text automatically. But you can wrap your text yourself. See How to Insert a Linefeed with PDFBox drawString and How can I create fixed-width paragraphs with PDFbox?.



Related Topics



Leave a reply



Submit