Hot questions for Using PDFBox in pdf generation

Top Java Programmings / PDFBox / pdf generation

Question:

I'm trying to generate a PDF that contains Arabic text using PDFBox Apache but the text is generated as separated characters because Apache parses given Arabic string to a sequence of general 'official' Unicode characters that is equivalent to the isolated form of Arabic characters.

Here is an example: Target text to Write in PDF "Should be expected output in PDF File" -> جملة بالعربي What I get in PDF File ->

I tried some methods but it's no use here are some of them: 1. Converting String to Stream of bits and trying to extract right values 2. Treating String a sequence of bytes with UTF-8 && UTF-16 and extracting values from them

There is some approach seems very promising to get the value "Unicode" of each character But it generate general "official Unicode" Here is what I mean

System.out.println( Integer.toHexString( (int)(new String("كلمة").charAt(1))) );  

output is 644 but fee0 was the expected output because this character is in middle from then I should get the middle Unicode fee0

so what I want is some method that generates the correct Unicode not the just the official one

The very Left column in the first table in the following link represents the general Unicode Arabic Unicode Tables Wikipedia


Answer:

Notice:

The sample code in this answer might be outdated please refer to h q's answer for the working sample code


At First I will thank Tilman Hausherr and M.Prokhorov for showing me the library that made writing Arabic possible using PDFBox Apache.
This Answer will be divided into two Sections:

  1. Downloading the library and installing it
  2. How to use the library

Downloading the library and installing it

We are going to use ICU Library. ICU stands for International Components for Unicode and it is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms and between C/C++ and Java software.

To download the Library go to the downloads page from here. Choose the latest version of ICU4J as shown in the following image. You will be transferred to another page and you will find a box with direct links of the needed components .Go ahead and download three Files you will find the highlighted in next image.

  1. icu4j-docs.jar
  2. icu4j-src.jar
  3. icu4j.jar

The following explanation for creating and adding a library in Netbeans IDE

  1. Navigate to the Toolbar and Click tools
  2. Choose Libraries
  3. At the bottom left you will find new Library button Create yours
  4. Navigate to the library that you created in libraries list
  5. Click it and add jar folders like that
  6. Add icu4j.jar in class path
  7. Add icu4j-src.jar in Sources
  8. Add icu4j-docs.jar in Javadoc
  9. View your opened projects from the very right
  10. Expand the project that you want to use the library in
  11. Right Click on the libraries folder and choose add library
  12. Finally choose the library that you had just created.

Now you are ready to use the library just import what you want like that

import com.ibm.icu.What_You_Want_To_Import;


How to use the library

With ArabicShaping Class and reversing the String we can write a correct attached Arabic LINE Here is the Code Notice the comments in the following code

import com.ibm.icu.text.ArabicShaping;
import com.ibm.icu.text.ArabicShapingException;
import java.io.File;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.*;

public class Main {
    public static void main(String[] args) throws IOException , ArabicShapingException
{
        File f = new File("Arabic Font File of format.ttf");
        PDDocument doc = new PDDocument();
        PDPage Page = new PDPage();
        doc.addPage(Page);
        PDPageContentStream Writer = new PDPageContentStream(doc, Page);
        Writer.beginText();
        Writer.setFont(PDType0Font.load(doc, f), 20);
        Writer.newLineAtOffset(0, 700);
        //The Trick in the next Line of Code But Here is some few Notes first  
        //We have to reverse the string because PDFBox is Writting from the left but Arabic is RTL Language  
        //The output will be perfect except every line will be justified to the left "It's not hard to resolve this"
        // So we have to write arabic string to pdf line by line..It will be like this
        String s ="جملة بالعربي لتجربة الكلاس اللذي يساعد علي وصل الحروف بشكل صحيح";
        Writer.showText(new StringBuilder(new ArabicShaping(reverseNumbersInString(ArabicShaping.LETTERS_SHAPE).shape(s))).reverse().toString());
        // Note the previous line of code throws ArabicShapingExcpetion 
        Writer.endText();
        Writer.close();
        doc.save(new File("File_Test.pdf"));
        doc.close();
    }
}

Here is the output

I hope that I had gone over everything.

Update : After reversing make sure to reverse the numbers again in order to get the same proper number Here is a couple of functions that could help

public static boolean isInt(String Input)
{
    try{Integer.parseInt(Input);return true;}
    catch(NumberFormatException e){return false;}
}
public static String reverseNumbersInString(String Input)
{
    char[] Separated = Input.toCharArray();int i = 0;
    String Result = "",Hold = "";
    for(;i<Separated.length;i++ )
    {
        if(isInt(Separated[i]+"") == true)
        {
            while(i < Separated.length && (isInt(Separated[i]+"") == true ||  Separated[i] == '.' ||  Separated[i] == '-'))
            {
                Hold += Separated[i];
                i++;
            }
            Result+=reverse(Hold);
            Hold="";
        }
        else{Result+=Separated[i];}
    }
    return Result;
}

Question:

Currently I am working with PDFBox of Apache to generate pdf. It is working perfectly fine in portrait mode but then my requirement is that 1st two page should be in landscape mode and afterwards all other pages in portrait.

So can anyone please help me on how to create pdf in landscape and achieve this functionality??

Note:I cannot switch from PDFBox to other libraries


Answer:

Another solution would be

PDPage page = new PDPage(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth()));

Question:

I have two PDF files (named : A1.pdf and B1.pdf). Now I want to replace the some pages of the second PDF file (B1.pdf) with the first one (A1.pdf) programatically. In this case I am using PDFBox library.

Here is my sample code:

try {
        File file = new File("/Users/test/Desktop/A1.pdf");
        PDDocument pdDoc = PDDocument.load(file);

        PDDocument document = PDDocument.load(new File("/Users/test/Desktop/B1.pdf"));
        document.removePage(3);
        document.addPage((PDPage) pdDoc.getDocumentCatalog().getAllPages().get(0));
        document.save("/Users/test/Desktop/"+"generatedPDFBox"+".pdf");
        document.close();
     }catch(Exception e){}

The idea is to replace the 3rd page. In this implementation the page is appending to the last page of the output pdf. Can anyone help me to implement this? If not with PDFBOX. Could you please suggest some other libraries in java?


Answer:

This solution creates a third PDF file with the contents like you asked for. Note that pages are zerobased, so the "3" in your question must be a "2".

    PDDocument a1doc = PDDocument.load(file1);
    PDDocument b1doc = PDDocument.load(file2);
    PDDocument resDoc = new PDDocument();

    List<PDPage> a1Pages = a1doc.getDocumentCatalog().getAllPages();
    List<PDPage> b1Pages = b1doc.getDocumentCatalog().getAllPages();

    // replace the 3rd page of the 2nd file with the 1st page of the 1st one
    for (int p = 0; p < b1Pages.size(); ++p)
    {
        if (p == 2)
            resDoc.addPage(a1Pages.get(0));
        else
            resDoc.addPage(b1Pages.get(p));
    }

    resDoc.save(file3);
    a1doc.close();
    b1doc.close();
    resDoc.close();

If you want to work from the command line instead, look here: https://pdfbox.apache.org/commandline/

Then use PDFSplit and PDFMerge.

Question:

I'm currently using Java and the PDFBox library to create some PDFs on the fly.

I need to be able to set the character spacing/tracking of some text but can't seem to figure it out.

It looks as there is a method to do so : http://ci.apache.org/projects/pdfbox/javadoc/index.html?org/apache/pdfbox/util/operator/SetCharSpacing.html

But I'm not quite sure how to apply this in the situation.

cs.beginText();
cs.setFont( font, fontSize );
cs.setNonStrokingColor(color);
cs.moveTextPositionByAmount(position[0], position[1]);
cs.drawString(text);
cs.endText();

Any help would be appreciated! Thanks.


Answer:

You need to do it the hard way, because the "Tc" operator isn't supported by the PDPageContentStream class:

cs.appendRawCommands("0.25 Tc\n");

The SetCharSpacing method you mentioned is for parsing existing PDFs.

PS: don't forget to call close after finishing writing to the content stream!

PPS: setCharacterSpacing() is available in version 2.0.4 and higher.

Question:

I am trying to write content into a PDF file. I have written the code

public ByteArrayOutputStream createPDF(String text) throws IOException, COSVisitorException {

  PDDocument document;
  PDPage page;
  PDFont font1;
  PDPageContentStream contentStream;
  ByteArrayOutputStream output = new ByteArrayOutputStream();
  document = new PDDocument();


  try {
    page = new PDPage();      
    document.addPage(page);
    contentStream = new PDPageContentStream(document, page);

    contentStream.beginText();
    contentStream.moveTextPositionByAmount( 100, 700 );
    contentStream.drawString("Hello World Hello World Hello World Hello World Hello World");
    contentStream.endText();
    System.out.println("output " + output);
    document.save(output);
    document.close();
    contentStream.close();    

  } catch (Exception e) {
    e.printStackTrace();

  } finally
  {
    logInfo("output completed");
  }
  return output;
}

The produced PDF file is empty. The content of the file is:

%▒▒▒▒
1 0 obj
<<
/Type /Catalog
/Version /1.4
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/MediaBox [0.0 0.0 612.0 792.0]
/Parent 2 0 R
/Contents 4 0 R
/Resources 5 0 R
>>
endobj
4 0 obj
<<
/Filter [/FlateDecode]
/Length 6 0 R
>>
stream
x▒
endstream
endobj
5 0 obj
<<
>>
endobj
6 0 obj
8
endobj
xref
0 7
0000000000 65535 f
0000000015 00000 n
0000000078 00000 n
0000000135 00000 n
0000000247 00000 n
0000000333 00000 n
0000000354 00000 n
trailer
<<
/Root 1 0 R
/ID [<C68578F989B81BF7DD279AE1745F6E8F> <D41D8CD98F00B204E9800998ECF8427E>]
/Size 7
>>
startxref
371
%%EOF

Answer:

You made two mistakes:

  1. You have closed the contentStream after saving the document instead of before.

  2. You haven't set a font.

Code that works for me (exception handling removed):

PDDocument document;
PDPage page;
PDPageContentStream contentStream;
document = new PDDocument();

page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);

contentStream.setFont(PDType1Font.COURIER, 10);

contentStream.beginText();
contentStream.moveTextPositionByAmount(100, 700);
contentStream.drawString("Hello World Hello World Hello World Hello World Hello World");
contentStream.endText();
contentStream.close();
document.save(....);
document.close();

Question:

I am processing some large pdf files, (up to 100MB and about 2000 pages), with pdfbox. Some of the pages contain a QR code, I want to split those files into smaller ones with the pages from one QR code to the next. I got this, but the result file sizes are the same as the source file. I mean, if I cut a 100MB pdf file into a ten files I am getting ten files 100MB each.

This is the code:

 PDDocument documentoPdf = 
        PDDocument.loadNonSeq(new File("myFile.pdf"), 
                           new RandomAccessFile(new File("./tmp/temp"), "rw"));

    int numPages = documentoPdf.getNumberOfPages();
    List pages = documentoPdf.getDocumentCatalog().getAllPages();

    int previusQR = 0;
    for(int i =0; i<numPages; i++){
       PDPage page = (PDPage) pages.get(i);
       BufferedImage firstPageImage =    
           page.convertToImage(BufferedImage.TYPE_USHORT_565_RGB , 200);

       String qrText = readQRWithQRCodeMultiReader(firstPageImage, hintMap);

       if(qrText != null and i!=0){
         PDDocument outputDocument = new PDDocument();
         for(int j = previusQR; j<i; j++){
           outputDocument.importPage((PDPage)pages.get(j));
          }
         File f = new File("./splitting_files/"+previusQR+".pdf");
         outputDocument.save(f);
         outputDocument.close();
         documentoPdf.close();
    }

I also tried the following code for storing the new file:

PDDocument outputDocument = new PDDocument();

for(int j = previusQR; j<i; j++){
 PDStream src = ((PDPage)pages.get(j)).getContents();
 PDStream streamD = new PDStream(outputDocument);
 streamD.addCompression();

 PDPage newPage = new PDPage(new   
           COSDictionary(((PDPage)pages.get(j)).getCOSDictionary()));
 newPage.setContents(streamD);

 byte[] buf = new byte[10240];
 int amountRead = 0;
 InputStream is = null;
 OutputStream os = null;
 is = src.createInputStream();
 os = streamD.createOutputStream();
 while((amountRead = is.read(buf,0,10240)) > -1) {
    os.write(buf, 0, amountRead);
  }

 outputDocument.addPage(newPage);
}

File f = new File("./splitting_files/"+previusQR+".pdf");

outputDocument.save(f);
outputDocument.close();

But this code creates files which lacks some content and also have the same size than the original.

How can I create smaller pdfs files from a larger one? Is it posible with PDFBox? Is there any other library with which I can transform a single page into an image (for qr recognition), and also allows me to split a big pdf file into smaller ones?

Thx!


Answer:

Thx! Tilman you are right, the PDFSplit command generates smaller files. I checked the PDFSplit code out and found that it removes the page links to avoid not needed resources.

Code extracted from Splitter.class :

private void processAnnotations(PDPage imported) throws IOException
    {
        List<PDAnnotation> annotations = imported.getAnnotations();
        for (PDAnnotation annotation : annotations)
        {
            if (annotation instanceof PDAnnotationLink)
            {
                PDAnnotationLink link = (PDAnnotationLink)annotation;   
                PDDestination destination = link.getDestination();
                if (destination == null && link.getAction() != null)
                {
                    PDAction action = link.getAction();
                    if (action instanceof PDActionGoTo)
                    {
                        destination = ((PDActionGoTo)action).getDestination();
                    }
                }
                if (destination instanceof PDPageDestination)
                {
                    // TODO preserve links to pages within the splitted result  
                    ((PDPageDestination) destination).setPage(null);
                }
            }
            else
            {
                // TODO preserve links to pages within the splitted result  
                annotation.setPage(null);
            }
        }
    }

So eventually my code looks like this:

PDDocument documentoPdf = 
        PDDocument.loadNonSeq(new File("docs_compuestos/50.pdf"), new RandomAccessFile(new File("./tmp/t"), "rw"));

        int numPages = documentoPdf.getNumberOfPages();
        List pages = documentoPdf.getDocumentCatalog().getAllPages();


        int previusQR = 0;
        for(int i =0; i<numPages; i++){
            PDPage firstPage = (PDPage) pages.get(i);
            String qrText ="";


            BufferedImage firstPageImage = firstPage.convertToImage(BufferedImage.TYPE_USHORT_565_RGB , 200);


            firstPage =null;

            try {
                qrText = readQRWithQRCodeMultiReader(firstPageImage, hintMap);
            } catch (NotFoundException e) {
                e.printStackTrace();
            } finally {
                firstPageImage = null;
            }


        if(i != 0 && qrText!=null){
                    PDDocument outputDocument = new PDDocument();
                    outputDocument.setDocumentInformation(documentoPdf.getDocumentInformation());
                    outputDocument.getDocumentCatalog().setViewerPreferences(
                            documentoPdf.getDocumentCatalog().getViewerPreferences());


                    for(int j = previusQR; j<i; j++){
                        PDPage importedPage = outputDocument.importPage((PDPage)pages.get(j));

                        importedPage.setCropBox( ((PDPage)pages.get(j)).findCropBox() );
                        importedPage.setMediaBox( ((PDPage)pages.get(j)).findMediaBox() );
                        // only the resources of the page will be copied
                        importedPage.setResources( ((PDPage)pages.get(j)).getResources() );
                        importedPage.setRotation( ((PDPage)pages.get(j)).findRotation() );

                        processAnnotations(importedPage);


                    }


                    File f = new File("./splitting_files/"+previusQR+".pdf");

                    previusQR = i;

                    outputDocument.save(f);
                    outputDocument.close();
                }
            }


        }

Thank you very much!!

Question:

I am having an issue serving a PDF file from our Restlet API.

I am using the basic example code from the Apache PDFBox documentation, it works fine outside of the Restlet context.

PDDocument document = new PDDocument();

System.err.println("before instantiating new PDPage");
// Create a new blank page and add it to the document
PDPage page = new PDPage(); // LINE FAILING IN RESTLET
System.err.println("after instantiating new PDPage");

document.addPage(page);
document.save("pdf.pdf");
document.close();

Here is my attempt to use PDFBox in a resource, ultimately I want to return a OutputRepresentation and save the PDDcoument to the stream.

The following code stops working at PDPage page = new PDPage();, I do not get any exception, the Restlet server does not return any response. The text "after instantiating new PDPage" is never printed.

EDIT: I simplified my code as much as I can but I still have the issue

Here is my basic Router:

public class ApiRestletApplication extends Application {

  @Override
  public Restlet createInboundRoot() {
    Router router = new Router(getContext());
    router.attach("/v1/myresource", MyResource.class);
    return router;
  }
}

Here is my resource

public class MyResource extends ServerResource {

  protected static final Logger logger = LoggerFactory.getLogger(MyResource.class);

  @Get
  public Representation toPDF() {

    PDDocument document = new PDDocument();
    System.err.println("before instantiating new PDPage");
    PDPage page = new PDPage();
    System.err.println("after instantiating new PDPage"); //<= never printed
    document.addPage(page);

    return new PDFRepresentation(document);
  }
}

here is my web.xml

<!-- Restlet application -->
<context-param>
  <param-name>org.restlet.application</param-name>
  <param-value>com.xxx.api.ApiRestletApplication</param-value>
</context-param>

<!-- Restlet adapter -->
<servlet>
  <servlet-name>RestletServlet</servlet-name>
  <servlet-class>
    org.restlet.ext.servlet.ServerServlet
  </servlet-class>
</servlet>

<!-- Catch all requests -->
<servlet-mapping>
  <servlet-name>RestletServlet</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

If I comment out line with the PDPage declaration and return some StringRepresentation, everything works fine. I am able to serve some Json, xml and excel. But this PDF is driving me crazy.

Here is the version I am using:

[INFO] +- org.restlet.jee:org.restlet:jar:2.2.1:compile
[INFO] +- org.restlet.jee:org.restlet.ext.crypto:jar:2.2.1:compile
[INFO] +- org.restlet.jee:org.restlet.ext.servlet:jar:2.2.1:compile

[INFO] |  +- org.apache.pdfbox:pdfbox:jar:1.8.10:compile
[INFO] |  |  +- org.apache.pdfbox:fontbox:jar:1.8.10:compile
[INFO] |  |  \- org.apache.pdfbox:jempbox:jar:1.8.10:compile
[INFO] |  \- com.sun:tools:jar:jdk:system

Here is the curl request:

curl "http://localhost:8889/v1/myresource" -H "Content-Type: application/pdf" -H "Accept: application/pdf"

here is the logs in eclipse:

2015-09-21 15:08:15.933:INFO::Started SelectChannelConnector@0.0.0.0:8889
2015-09-21 15:08:20.698:INFO:/:RestletServlet: [Restlet] Attaching application: com.xxx.api.ApiRestletApplication@2613622c to URI: 
before instantiating new PDPage
//then nothing

Thanks for your help.

EDIT 2: the following code works, but I still have no clue why my code does not:

public class RestletServerTest extends Application {

  @Override
  public Restlet createInboundRoot() {
    Router router = new Router(getContext());
    router.attach("/v1/myresource", MyResource.class);
    return router;
  }

  public static void main(String[] args) throws Exception {
    Component component = new Component();
    component.getServers().add(Protocol.HTTP, 8182);

    component.getDefaultHost().attach("", new RestletServerTest());
    component.start();
  }
}

EDIT 3: the issue does not seem to be related to Restlet but to PDFBox and servlets: PDFBox: Unable to save pdf while running on tomcat

EDIT 4: here is the solution https://stackoverflow.com/a/32706385/1039265


Answer:

I've tried your sample code and it worked for me.

I've just set up a class PDDocumentRepresentation that wraps a PDDocument:

import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.restlet.data.MediaType;
import org.restlet.representation.OutputRepresentation;

public class PDDocumentRepresentation extends OutputRepresentation {

    private PDDocument document = new PDDocument();

    public PDDocumentRepresentation(PDDocument document) {
        super(MediaType.APPLICATION_PDF);
        this.document = document;
    }

    @Override
    public void write(OutputStream outputStream) throws IOException {
        try {
            document.save(outputStream);
            document.close();
        } catch (COSVisitorException e) {
            throw new IOException(e);
        }
    }
}

Here is the code of the Resource:

public class MyResource extends ServerResource {

    @Get
    public Representation getPdf() {
        PDDocument document = new PDDocument();
        PDPage page = new PDPage();
        document.addPage(page);

        return new PDDocumentRepresentation(document);
    }
}

Question:

I have to generate a pdf file depending on some input .Each time the code runs , the input length may vary , so how can I add pages to the document dynamically depending on my input content .

public class pdfproject
     {
      static int lineno=768;
      public static void main (String[] args) throws Exception 
       {
        PDDocument doc= new PDDocument();
        PDPage page = new PDPage();
        doc.addPage(page);
        PDPageContentStream cos = new PDPageContentStream(doc, page);
        for(int i=0;i<2000;i++)
         {           
           renderText("hello"+i,cos,60);
         }
        cos.close();
        doc.save("test.pdf");
        doc.close();
     }

       static void renderText(String Info,PDPageContentStream cos,int marginwidth) throws Exception
    {
         lineno-=12;    
         System.out.print("lineno="+lineno);
         PDFont fontPlain = PDType1Font.HELVETICA;
         cos.beginText();
         cos.setFont(fontPlain, 10);
         cos.moveTextPositionByAmount(marginwidth,lineno);
         cos.drawString(Info);
         cos.endText();
     }
     }

How do i ensure that the content is rendered on the next page by adding a new page dynamically when there is no space on the current page ?


Answer:

Pdfbox does not include any automatic layouting support. Thus, you have to keep track of how full a page is, and you have to close the current page, create a new one, reset fill indicators, etc

This obviously should not be done in static members in some project class but instead in some dedicated class and its instance members. E.g.

public class PdfRenderingSimple implements AutoCloseable
{
    //
    // rendering
    //
    public void renderText(String Info, int marginwidth) throws IOException
    {
        if (content == null || textRenderingLineY < 12)
            newPage();

        textRenderingLineY-=12;    
        System.out.print("lineno=" + textRenderingLineY);
        PDFont fontPlain = PDType1Font.HELVETICA;
        content.beginText();
        content.setFont(fontPlain, 10);
        content.moveTextPositionByAmount(marginwidth, textRenderingLineY);
        content.drawString(Info);
        content.endText();
    }

    //
    // constructor
    //
    public PdfRenderingSimple(PDDocument doc)
    {
        this.doc = doc;
    }

    //
    // AutoCloseable implementation
    //
    /**
     * Closes the current page
     */
    @Override
    public void close() throws IOException
    {
        if (content != null)
        {
            content.close();
            content = null;
        }
    }

    //
    // helper methods
    //
    void newPage() throws IOException
    {
        close();

        PDPage page = new PDPage();
        doc.addPage(page);
        content = new PDPageContentStream(doc, page);
        content.setNonStrokingColor(Color.BLACK);

        textRenderingLineY = 768;
    }

    //
    // members
    //
    final PDDocument doc;

    private PDPageContentStream content = null;
    private int textRenderingLineY = 0;
}

(PdfRenderingSimple.java)

You can use it like this

PDDocument doc = new PDDocument();

PdfRenderingSimple renderer = new PdfRenderingSimple(doc);
for (int i = 0; i < 2000; i++)
{
    renderer.renderText("hello" + i, 60);
}
renderer.close();

doc.save(new File("renderSimple.pdf"));
doc.close();

(RenderSimple.java)

For more specialized rendering support you will implement improved rendering classes, e.g. PdfRenderingEndorsementAlternative.java from this answer.

Question:

I'm not a big fan of asking this kind of questions but well, it's been 3 whole days now trying to solve this bug on my code.

I know it's a logic problem and I know how to solve it in my mind, but when it comes to translate my ideas into code I just can't make it work as I want.

I'm working on a contract endorsement (a modification of a contract) which compares the data from 2 tables and if any of them has changed then it paints only that information.

Sometimes the information that changes such as conditions grows up or lowers in length, here's where the problem lies.

I made an algorithm which gets the minimum Y coord once it finished painting the information but when I change page Y coord must be reseted to 700f and start painting again from there.

The code I'm using was first made by another programmer who's not here anymore, I don't know how to do an MCVE with PDF's.

But here are the methods I think may help you helping me.

An example of how I compare and paint information on the PDF:

sOffA and sOffE are objects from a Class called SubscriptionOffer and A stands for Agreement while E for Endorsement. Endorsement is a copy of the table Agreement on MySQL but with the information modified. Probably this is irrelevant information, probably not.

if (!nullOrEmpty(sOffA.getGeneralConditions()) &&
    !nullOrEmpty(sOffE.getGeneralConditions())) {
    if (!getStringValue(sOffA.getGeneralConditions()).
    equals(getStringValue(sOffE.getGeneralConditions()))) {
    minYs[0] = pdf.rText(LEFT_MARGIN, y, 10, 
                 constants.generalConditions(),
                 getStringValue(sOffA.
                        getGeneralConditions()));

    minYs[1] = pdf.rText(HALF_PAGE, y, 10, 
                 constants.generalConditions(),
                 getStringValue(sOffE.
                        getGeneralConditions()));

    y = checkY(pdf, minYs);
    }
} else if (nullOrEmpty(sOffA.getGeneralConditions()) &&
       !nullOrEmpty(sOffE.getGeneralConditions())) {
    minYs[0] = pdf.rText(LEFT_MARGIN, y, 10, "", "");

    minYs[1] = pdf.rText(HALF_PAGE, y, 10, 
             constants.generalConditions(),
             getStringValue(sOffE.
                    getGeneralConditions()));

    y = checkY(pdf, minYs);
} else if (!nullOrEmpty(sOffA.getGeneralConditions()) &&
       nullOrEmpty(sOffE.getGeneralConditions())) {
    minYs[0] = pdf.rText(LEFT_MARGIN, y, 10, 
             constants.generalConditions(),
             getStringValue(sOffA.
                    getGeneralConditions()));

    minYs[1] = pdf.rText(HALF_PAGE, y, 10, "", "");

    y = checkY(pdf, minYs);
}

The array I use is this one:

private float[] minYs = new float[] {700, 700, 700, 700, 700, 700, 700,
                                     700, 700};

This is the method checkY, it checks which Y on the array (as seen above) is the lowest one, then it restarts the whole array back to 700f for all elements. And after that it checks if the space below the lowest Y coord is enough to paint next item.

private float checkY(PdfRenderingEndorsement pdf, float... ys) throws Exception {
    float y2 = getMinY(pdf, ys);
    for (int i = 0; i < ys.length; i++) {
        ys[i] = 700;
    }
    y2 = pdf.checkContentStream(y2, 5, 10);
    return y2;      
}

This method is where I guess my logic problem is, and as you can see I've tried diferent things, the original algorithm was only the for-each part then I've tried different things w/o success.

private float getMinY(PdfRenderingEndorsement pdf, float... ys) {
    float result = 700f;
    float lowest1 = 0, lowest2 = 0;
    for (int i = 0; i < ys.length; i++) {
        for (int j = 0; j < ys.length; j++) {
        if (ys[j] > ys[i]) {
            float aux = ys[i];
            ys[i] = ys[j];
            ys[j] = aux;
            //lowest1 = ys[i];
            //lowest2 = ys[j];
        }
        }
    }
    if (ys.length > 1) {
        lowest1 = ys[0];
        lowest2 = ys[1];
    }
    LOGGER.trace("lowest1: " + lowest1);
    LOGGER.trace("lowest2: " + lowest2);
    LOGGER.trace("newPage " + pdf.getNewPage());
    /*if(pdf.getNewPage()) {
        return lowest1 > lowest2 ? lowest2 : lowest1;
      }
    */
    /*for (float y : ys) {
        if (y < result) {
        result = y;
        }
        }*/
    return lowest1 > lowest2 && pdf.getNewPage() ? lowest1 : lowest2;
    //return lowest1;
}

The methods where I get String value of the objects and check if they're null or empty:

private boolean nullOrEmpty(String s) {
    return s == null || s.isEmpty();
}

private String getStringValue(Object o) {
    if (o == null) {
        return "";
    }
    return getStringValue(o, null);
}

private String getStringValue(Object o, Class< ? extends Unit> clazz) {
    if (o instanceof Boolean) {
        Boolean bd = (Boolean) o;
        if (bd) {
        return constants.getString("dbeditorYes");
        } else {
        return constants.getString("dbeditorNo");
        }
    } else if (o instanceof Date) {
        Date date = (Date) o;
        DateFormat df = new SimpleDateFormat(DateConstants.DATE_FORMAT);
        return df.format(date);
    } else if (o instanceof Enum) {
        Enum en = (Enum) o;
        return constants.enumMap().get(en.toString());
    } else if (o instanceof Integer) {
        Integer integer = (Integer) o;
        if (clazz == null) {
        return String.valueOf(integer);
        } else {
        Unit entry = unitSvc.get(clazz, integer);
        String[] params = entry.toString().split("\\|");
        String label = params.length == 1 ? params[0] : params[1];
        return label;
        }
    } else if (o instanceof BigDecimal) {
        BigDecimal bd = (BigDecimal) o;
        DecimalFormat df = new DecimalFormat("#,##0");
        df = new DecimalFormat("#,##0.00");
        return df.format(bd);
    } else if (o instanceof Float) {
        Float bd = (Float) o;
        DecimalFormat df = new DecimalFormat("#,##0");
        df = new DecimalFormat("#,##0.00");
        return df.format(bd);
    } else if (o instanceof String) {
        String td = (String) o;
        return td;
    }
    return "";
}

All of the above methods correspond to PdfEndorsement class.

And this is PdfRenderingEndorsement class which is the class where actually the data is painted (this is the complete class since all methods are used):

public class PdfRenderingEndorsement {

    private static final Logger LOGGER = Logger.
    getLogger(PdfRenderingEndorsement.class);

    private static final float BOTTOM_MARGIN = 60;

    private static final int DESC_WIDTH = 269; //For description fields

    private static final int FIELD_WIDTH = 70;

    private static final int FIELD_WIDTH2 = 60;

    private static final int FIELD_WIDTH3 = 60;

    private static final int FIELD1 = 112;

    private static final int VALUE1 = 112;

    private static final int FIELD2 = 80;

    private static final int VALUE2 = 80;

    private static final int VALUE_WIDTH = 80;

    private static final int VALUE_WIDTH2 = 60;

    private static final int VALUE_WIDTH3 = 80;

    private static final int HALF_WIDTH = 325;

    private static final int TEXT_WIDTH = 410; //For text fields

    private final RwaConstants constants = ConstantsGetter.getInstance();

    private final PDDocument doc;

    private final String logoPath;

    private final String[] header;

    private int count = 0;

    private boolean newPage;

    private PDPageContentStream content;

    /**
     * Empty constructor. Used only to initialize the rendering class and call
     * it's methods.
     */
    public PdfRenderingEndorsement(PDDocument doc, String logoPath, 
                   String[] header) {
    this.doc = doc;
    this.logoPath = logoPath;
    this.header = header;
    }

    public float checkContentStream2(float y, int lines, int space) 
    throws Exception {
    float newY = checkYCoord2(y, lines, space);
    if (newY == 700) {
        if (content != null) {
        content.close();
        }

        File file = new File(logoPath);
        PDJpeg logoImg = new PDJpeg(doc, new FileInputStream(file));
        PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
        doc.addPage(page);
        content = new PDPageContentStream(doc, page);
        content.drawImage(logoImg, 50, 720);
        rHeader();
    }
    return newY;
    }

    private float checkYCoord2(float y, int lines, int space) {
    float newY = y;
    for (int i = 0; i < lines; i++) {
        if ((newY - space) <= BOTTOM_MARGIN) {
        newY = 700f;
        return newY;
        } else {
        newY = newY - space;
        }
    }
    return y;
    }

    public boolean getNewPage() {
    return newPage;
    }

    public float checkContentStream(float y) throws Exception {
    float newY = checkYCoord(y, 1, 10);
    if (newY == 700) {
        if (content != null) {
        content.close();
        }
        File file = new File(logoPath);
        PDJpeg logoImg = new PDJpeg(doc, new FileInputStream(file));
        PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
        doc.addPage(page);
        content = new PDPageContentStream(doc, page);
        content.drawImage(logoImg, 50, 720);
        rHeader();
    }
    return newY;
    }

    public float checkYCoord(float y, int lines, int space) {
    float newY = y;
    for (int i = 0; i < lines; i++) {
        if ((newY - space) <= BOTTOM_MARGIN) {
        newY = 700f;
        return newY;
        } else {
        newY = newY - space;
        }
    }
    return y;
    }

    public float checkContentStream(float y, int lines, int space) 
    throws Exception {
    float newY = checkYCoord(y, lines, space);
    if (newY == 700) {
        if (content != null) {
        content.close();
        }
        File file = new File(logoPath);
        PDJpeg logoImg = new PDJpeg(doc, new FileInputStream(file));
        PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
        doc.addPage(page);
        content = new PDPageContentStream(doc, page);
        content.drawImage(logoImg, 50, 720);
        rHeader();
    }
    return newY;
    }

    public void closeContentStream() throws Exception {
    if (content != null) {
        content.close();
    }
    }

    /**
     * Renders the header for slip documents.
     */
    public void rHeader() throws Exception {
    float y = 760f;
    content.setLineWidth(.5f);
    content.setFont(PDType1Font.TIMES_ROMAN, 9);
    content.setNonStrokingColor(Color.GRAY);
    content.drawLine(50, 710, 562, 710);

    y = rText(150, y + 19, 10, constants.endorsement(), null,
          TEXT_WIDTH, 0);
    y = rText(150, y + 9, 10, header[0], null, TEXT_WIDTH, 0);
    y = rText(150, y + 9, 10, header[1], null, TEXT_WIDTH, 0);
    y = rText(150, y + 9, 10, header[2], null, TEXT_WIDTH, 0);
    y = rText(150, y + 9, 10, header[3], null, TEXT_WIDTH, 0);
    content.setNonStrokingColor(Color.BLACK);
    content.setFont(PDType1Font.TIMES_ROMAN, 9);
    }

    public float rText(float x, float y, int space, String labelField,
               String value) 
    throws Exception {
    return rText(x, y, space, labelField, value, FIELD_WIDTH, 
             HALF_WIDTH - 2 * FIELD_WIDTH - 10);    
    }

    public float rTextLR(float x, float y, int space, String labelField,
               String value) 
    throws Exception {
    return rText(x, y, space, labelField, value, 0, 
             HALF_WIDTH - 2 * FIELD_WIDTH - 10);    
    }

    public float rText(float x, float y, int space, String labelField,
               String value, int fieldWidth) 
    throws Exception {
    if (fieldWidth == 0) {
        return rText(x, y, space, labelField, value, FIELD_WIDTH2, 
             VALUE_WIDTH2);
    } else if (fieldWidth == 1) {
        return rText(x, y, space, labelField, value, FIELD_WIDTH3,
             VALUE_WIDTH3);
    } else if (fieldWidth == 2) {
        return rText(x, y, space, labelField, value, TEXT_WIDTH,
             TEXT_WIDTH);
    }
    return y;
    }


    public float getFieldSize(int fs) {
    switch(fs) {
    case 1:
        return (FIELD_WIDTH + VALUE_WIDTH);
    case 2:
        return (FIELD_WIDTH + DESC_WIDTH);
    case 3:
        return (FIELD_WIDTH + TEXT_WIDTH);
    case 4:
        return (HALF_WIDTH - FIELD_WIDTH);
    case 5:
        return (FIELD_WIDTH + TEXT_WIDTH);
    case 6:
        return (FIELD_WIDTH + TEXT_WIDTH);
    case 7:
        return (FIELD1 + 19) / 2;
    case 8:
        return (FIELD2 + 19) / 2;
    default:
        return 0;
    }
    }

    public void paintLinesH(float y) throws Exception {
    content.drawLine(49, y - 6, 327, y - 6);
    content.drawLine(335, y - 6, 563, y - 6);
    }

    public void paintLinesV(float x, float yMax, float yMin)
    throws Exception {
    content.drawLine(x - 1, yMax - 6, x - 1, yMin - 6);
    }

    public float rText(float x, float y, int space, String labelField,
               String value, int fieldWidth, int valueWidth) 
    throws Exception {
    PDFont font = PDType1Font.TIMES_BOLD;
    content.setFont(font, 9);
    float y1 = 0f;
    float y2 = 0f;
    if (value == null) {
        return rText(labelField, fieldWidth, x, y - 19, space, font, false);
    } else {
        if (labelField == null) {
        font = PDType1Font.TIMES_ROMAN;
        content.setFont(font, 9);
        return rText(value, valueWidth, x, y - 19, space, font, true);
        } else {
        y1 = rText(labelField, fieldWidth, x, y - 30, space, font, 
               false);
        font = PDType1Font.TIMES_ROMAN;
        content.setFont(font, 9);
        float y3 = y;
        y2 = rText(value, valueWidth, x + fieldWidth + 10, y - 30,
               space, font, true);
        if (y3 < y2) {
            return y2;
        } else {
            if (y1 >= y2) {
            return y2;
            } else {
            return y1;
            }
        }
        }
    }
    }

    private ArrayList<String> getRows(String text, int width, PDFont font)
    throws Exception {
    float textWidth = font.getStringWidth(text) / 1000f * 9f;
    ArrayList<String> result = Lists.newArrayList();
    if (textWidth < width) {
        result.add(text);
        return result;
    }

    float spaceWidth = font.getStringWidth(" ") / 1000f * 9f;
    String[] paragraphs = text.split("\n|\r\n|\r");
    for (String paragraph : paragraphs) {
        float pWidth = font.getStringWidth(paragraph) / 1000f * 9f;
        if (pWidth < width) {
        result.add(paragraph);
        continue;
        }

        float widthCount = 0f;
        String[] words = paragraph.trim().split(" ");
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < words.length; j++) {
        if (words[j].trim().length() == 0) {
            continue;
        }

        float wWidth = font.getStringWidth(words[j]) / 1000f * 9f;
        float totalWidth = widthCount + wWidth + spaceWidth;
        if (totalWidth < width + spaceWidth) {
            sb.append(words[j]);
            sb.append(" ");
            widthCount = totalWidth;
        } else {
            result.add(sb.toString().trim());
            sb = new StringBuilder();
            sb.append(words[j]);
            sb.append(" ");
            widthCount = totalWidth - widthCount;
        }
        }
        result.add(sb.toString().trim());
    }
    return result;
    }

    private float rText(String text, int width, float x, float y, int space,
            PDFont font, boolean isValue) throws Exception {
    float newY = y;
    int rowHeight = 0;
    newPage = false;
    ArrayList<String> rowList = getRows(text, width, font);
    if (isValue) {
        for (String row : rowList) {
        if (rowHeight >= 10) {
            newY = checkContentStream(newY - 10);
            newY = newY == 700 ? 680 : newY;
            if (newY <= 700 && !newPage) {
            newPage = true;
            }
            rowHeight = newY == 680 ? 0 : rowHeight;
        }
        content.beginText();
        content.moveTextPositionByAmount(x, newY);
        content.drawString(row);
        content.endText();
        rowHeight = rowHeight + 10;
        }
    } else {
        for (String row : rowList) {
        content.beginText();
        content.moveTextPositionByAmount(x, newY - rowHeight);
        content.drawString(row);
        content.endText();
        rowHeight = rowHeight + 10;
        }
        newY -= (rowHeight - 10);
    }
    return newY;
    }
}

This is a PDF Example of the output with the original (for-each) method.

If you can't see the PDF let me know but here are also some Screenshots from it:

This is a PDF Example of the output with the non-commented code on getMinY method.

And from this modification here's the output:

As you can see the 1st output "jumps" into the next page, because let's say the text on the right on "Texto de Poliza" ended on Y coord 680, 670 or something like that but on the left I painted an empty field ("") which ended in 90 or 100 something near of those numbers.

Then it compares 100 < 670 ? Yes, then I take 50 to 100 and it's lower than my BOTTOM_MARGIN (which is 60), so it closes actual page (which is now where the text ended but thinks it's on the page before it) and creates a new one.

I've asked a similar question almost a year before here, these Classes are almost a copy from those files but this bug on my logic comes only with these files because data is handled a bit different.

Well after that wall of text I hope someone could read it and actually give me a hand, maybe I'm missing an important part but can't find it atm.

Thanks in advance.

EDIT

After adding @mkl's answer to my methods I find that when no information changed in both sides, it creates a gap and it doesn't look good.

This is how I'm sending data to PdfRenderingEndorsementAlternative:

for (String[] data: sOppData) {
        //float y = renderer.getPreviousBandBase;
        for (int i = 0; i < data.length - 2; i += 3) {
            if (!nullOrEmpty(data[i + 1]) &&
                !nullOrEmpty(data[i + 2])) {
                if (!data[i + 1].equals(data[i + 2])) {
                renderer.
                    render(new BandColumn(leftHalfPageField,
                              data[i], data[i + 1]),
                       new BandColumn(rightHalfPageField,
                              data[i], data[i + 2])
                       );
                }
            } else if (nullOrEmpty(data[i + 1]) &&
                   !nullOrEmpty(data[i + 2])) {
                renderer.
                render(new BandColumn(leftHalfPageField, 
                              "", ""),
                       new BandColumn(rightHalfPageField,
                              data[i], data[i + 2])
                       );
            } else if (!nullOrEmpty(data[i + 1]) &&
                   nullOrEmpty(data[i + 2])) {
                renderer.
                render(new BandColumn(leftHalfPageField,
                              data[i], data[i + 1]),
                       new BandColumn(rightHalfPageField,
                              "", "")
                       );
            }
        }
        //float y2 = renderer.getPreviousBandBase();
        /*if (y2 < y) 
            renderer.gap(20);
        */
        renderer.gap(20); 
  }

Are the above commented validations correct or would I be going back to past methods that originated bugs? Should I add getPreviousBandBase() method on PdfRenderingEndorsementAlternative or think in another way to do it directly on render() method?

And this is how I get data that currently has the same info in both sides.

An example of this would be:

sOppA.getContractName() and sOppE.getContractName() are both "Hello World" and as both are equal then it would leave a gap as seen on screenshot.

private ArrayList<String []> renderSubscriptionOpportunity(
               SubscriptionOpp sOppA, SubscriptionOpp sOppE, 
               ArrayList<Location> lcA, ArrayList<Location> lcE) 
    throws Exception {
    ArrayList <String[]> sOppData = new ArrayList<String[]>();

    sOppData.add(new String[] {constants.currencyId(),
                   getStringValue(sOppA.getCurrencyId(), 
                          Currency.class),
                   getStringValue(sOppE.getCurrencyId(),
                          Currency.class)});

    sOppData.add(new String[] {constants.contractName(),
                   getStringValue(sOppA.getContractName()),
                   getStringValue(sOppE.getContractName())});

    sOppData.add(new String[] {constants.mainActivityId(),
                   getStringValue(sOppA.getMainActivityId(),
                          MainActivity.class),
                   getStringValue(sOppE.getMainActivityId(),
                          MainActivity.class)});

    //here add location table

    if (lcA.size() > 1 && lcE.size() > 1) {
        int lastIdA = 0;
        int lastIdE = 0;
        int size = lcA.size() >= lcE.size() ? lcA.size() : lcE.size();

        LOGGER.trace("size: " + size + " lcA.size(): " + lcA.size() +
             " lcE.size(): " + lcE.size());
        for (int pos = 1; pos < lcA.size(); pos++) {
        StringBuilder aSb = new StringBuilder();
        StringBuilder eSb = new StringBuilder();
        String valueA = "";
        String valueE = "";
        if (pos < lcA.size()) {
            Country countryA = unitSvc.get(Country.class, 
                           lcA.get(pos).getCountryId());
            LOGGER.trace("Entro1");
            if (countryA.getId() != lastIdA) {
            aSb.append(countryA.getName());
            lastIdA = countryA.getId();
            } else {
            aSb.append("");
            }
        } else {
            aSb.append("");
        }       
        if (pos < lcE.size()) {
            Country countryE = unitSvc.get(Country.class, 
                           lcE.get(pos).getCountryId());
            LOGGER.trace("Entro2");
            if (countryE.getId() != lastIdE) {
            eSb.append(countryE.getName());
            lastIdE = countryE.getId();
            } else {
            eSb.append("");
            }
        } else {
            eSb.append("");
        }
        valueA = aSb.toString();
        valueE = eSb.toString();
        sOppData.add(new String[] {pos == 1 ? constants.countryId() : 
                       "", valueA, valueE});
        }
    }
    return sOppData;
}

Answer:

As already hinted at in a comment (actually already in a comment to your former question) I think that the whole architecture of your rendering class needs an overhaul. Based on your PdfRenderingEndorsement I created the following class PdfRenderingEndorsementAlternative which represents an alternative approach at that rendering:

public class PdfRenderingEndorsementAlternative implements AutoCloseable
{
    //
    // misc constants
    //
    static final int FIELD_WIDTH = 70;
    static final int HALF_WIDTH = 325;
    static final int TEXT_WIDTH = 410;

    static final float BOTTOM_MARGIN = 70;
    static final int LEFT_MARGIN = 50;

    //
    // rendering
    //
    public void gap(int size)
    {
        previousBandBase-=size;
    }

    public void render(BandColumn... columns) throws IOException
    {
        if (content == null)
            newPage();

        final List<Chunk> chunks = new ArrayList<Chunk>();
        for (BandColumn column : columns)
        {
            chunks.addAll(column.toChunks());
        }

        float offset = 0;
        while (!chunks.isEmpty())
        {
            float lowestAddedY = previousBandBase;
            float highestBaseBeforeNonAdded = Float.NEGATIVE_INFINITY;
            List<Chunk> added = new ArrayList<Chunk>();
            for (Chunk chunk: chunks)
            {
                float y = previousBandBase + chunk.y + offset; 
                if (y >= BOTTOM_MARGIN)
                {
                    content.beginText();
                    content.setFont(chunk.font, 9);
                    content.moveTextPositionByAmount(chunk.x, y);
                    content.drawString(chunk.text);
                    content.endText();
                    // draw
                    if (y < lowestAddedY)
                        lowestAddedY = y;
                    added.add(chunk);
                }
                else
                {
                    float baseBefore = chunk.y + chunk.space;
                    if (baseBefore > highestBaseBeforeNonAdded)
                        highestBaseBeforeNonAdded = baseBefore;
                }
            }
            chunks.removeAll(added);
            if (!chunks.isEmpty())
            {
                newPage();
                offset = -highestBaseBeforeNonAdded;
            }
            else
            {
                previousBandBase = lowestAddedY;
            }
        }
    }

    static public class BandColumn
    {
        public enum Layout
        {
            headerText(150, TEXT_WIDTH, 0, 10),
            leftHalfPageField(LEFT_MARGIN, FIELD_WIDTH, HALF_WIDTH - 2 * FIELD_WIDTH - 10, 10),
            rightHalfPageField(HALF_WIDTH, FIELD_WIDTH, HALF_WIDTH - 2 * FIELD_WIDTH - 10, 10);

            Layout(float x, int fieldWidth, int valueWidth, int space)
            {
                this.x = x;
                this.fieldWidth = fieldWidth;
                this.valueWidth = valueWidth;
                this.space = space;
            }

            final float x;
            final int fieldWidth, valueWidth, space;
        }

        public BandColumn(Layout layout, String labelField, String value)
        {
            this(layout.x, layout.space, labelField, value, layout.fieldWidth, layout.valueWidth);
        }

        public BandColumn(float x, int space, String labelField, String value, int fieldWidth, int valueWidth)
        {
            this.x = x;
            this.space = space;
            this.labelField = labelField;
            this.value = value;
            this.fieldWidth = fieldWidth;
            this.valueWidth = valueWidth;
        }

        List<Chunk> toChunks() throws IOException
        {
            final List<Chunk> result = new ArrayList<Chunk>();
            result.addAll(toChunks(0, fieldWidth, PDType1Font.TIMES_BOLD, labelField));
            result.addAll(toChunks(10 + fieldWidth, valueWidth, PDType1Font.TIMES_ROMAN, value));
            return result;
        }

        List<Chunk> toChunks(int offset, int width, PDFont font, String text) throws IOException
        {
            if (text == null || text.length() == 0)
                return Collections.emptyList();

            final List<Chunk> result = new ArrayList<Chunk>();
            float y = -space;
            List<String> rows = getRows(text, width, font);
            for (String row: rows)
            {
                result.add(new Chunk(x+offset, y, space, font, row));
                y-= space;
            }
            return result;
        }

        final float x;
        final int space, fieldWidth, valueWidth;
        final String labelField, value;
    }

    //
    // constructor
    //
    public PdfRenderingEndorsementAlternative(PDDocument doc, InputStream logo, 
            String[] header) throws IOException
    {
        this.doc = doc;
        this.header = header;
        logoImg = new PDJpeg(doc, logo);
    }

    //
    // AutoCloseable implementation
    //
    @Override
    public void close() throws IOException
    {
        if (content != null)
        {
            content.close();
            content = null;
        }
    }

    //
    // helper methods
    //
    void newPage() throws IOException
    {
        close();

        PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
        doc.addPage(page);
        content = new PDPageContentStream(doc, page);
        content.drawImage(logoImg, 50, 720);
        content.setLineWidth(.5f);
        content.setNonStrokingColor(Color.GRAY);
        content.drawLine(50, 710, 562, 710);

        previousBandBase = 770;
        render(new BandColumn(BandColumn.Layout.headerText, "ENDOSO", null));
        for (String head: header)
            render(new BandColumn(BandColumn.Layout.headerText, head, null));

        content.setNonStrokingColor(Color.BLACK);
        previousBandBase = 680;
    }

    // original method
    static List<String> getRows(String text, int width, PDFont font) throws IOException
    {
        float textWidth = font.getStringWidth(text) / 1000f * 9f;
        ArrayList<String> result = new ArrayList<String>();// Lists.newArrayList();
        if (textWidth < width)
        {
            result.add(text);
            return result;
        }

        float spaceWidth = font.getStringWidth(" ") / 1000f * 9f;
        String[] paragraphs = text.split("\n|\r\n|\r");
        for (String paragraph : paragraphs)
        {
            float pWidth = font.getStringWidth(paragraph) / 1000f * 9f;
            if (pWidth < width)
            {
                result.add(paragraph);
                continue;
            }

            float widthCount = 0f;
            String[] words = paragraph.trim().split(" ");
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < words.length; j++)
            {
                if (words[j].trim().length() == 0)
                {
                    continue;
                }

                float wWidth = font.getStringWidth(words[j]) / 1000f * 9f;
                float totalWidth = widthCount + wWidth + spaceWidth;
                if (totalWidth < width + spaceWidth)
                {
                    sb.append(words[j]);
                    sb.append(" ");
                    widthCount = totalWidth;
                }
                else
                {
                    result.add(sb.toString().trim());
                    sb = new StringBuilder();
                    sb.append(words[j]);
                    sb.append(" ");
                    widthCount = totalWidth - widthCount;
                }
            }
            result.add(sb.toString().trim());
        }
        return result;
    }

    //
    // helper classes
    //
    static class Chunk
    {
        Chunk(float x, float y, int space, PDFont font, String text)
        {
            this.x = x;
            this.y = y;
            this.space = space;
            this.font = font;
            this.text = text;
        }

        final float x, y;
        final int space;
        final PDFont font;
        final String text;
    }

    //
    // members
    //
    private final PDDocument doc;
    private final PDJpeg logoImg;
    private final String[] header;

    private PDPageContentStream content = null;
    private float previousBandBase = 0;
}

(PdfRenderingEndorsementAlternative.java)

This class is based on the concept of bands, i.e. horizontal stripes of content, with any number of columns containing field names and / or field values.

It can be used like this:

PDDocument document = new PDDocument();
PdfRenderingEndorsementAlternative renderer = new PdfRenderingEndorsementAlternative(document, logoStream, header);

renderer.render(
        new BandColumn(leftHalfPageField, "Nombre del contrato/asegurado:", "Prueba Jesus Fac No Prop"),
        new BandColumn(rightHalfPageField, "Nombre del contrato/asegurado:", "Prueba Jesus Fac No Prop con Endoso")
        );

renderer.gap(20);

renderer.render(
        new BandColumn(leftHalfPageField, "País:", "México"),
        new BandColumn(rightHalfPageField, "País:", "México")
        );

renderer.close();
document.save(new File(RESULT_FOLDER, "Endorsement.pdf"));

(RenderEndorsement.java)

As you see, the caller does not have to care anymore about y positions, everything is done in the renderer class. And the result:

I used the data from your first sample PDF as input, and this is the result:

As you see, no page jumps and no overlapping texts, either.

Question:

I am trying to insert barcode in the PDF using PDFBox2.0.13. I tried using the BufferedImage for this as given in How to add Code128 Barcode image to existing pdf using pdfbox(1.8.12) with barcode4j library? but this uses "new PDPixelMap(doc, bim)" this PDPixelMap is deprecated in 2.0.x. My question is how do we insert barcode in PDF with APIs available in PDFBox2.0.13(probably replacement of PDPixelMap)and without using PDPixelMap.? Would be great if code snippet provided.


Answer:

Use LosslessFactory like this:

PDImageXObject img = LosslessFactory.createFromImage(doc, bim);
contentStream.drawImage(img, x, y);

Question:


Answer:

This is a segment from the CreateBookmarks example, found in the source code download:

document = PDDocument.load( args[0] );
if( document.isEncrypted() )
{
    System.err.println( "Error: Cannot add bookmarks to encrypted document." );
    System.exit( 1 );
}
PDDocumentOutline outline =  new PDDocumentOutline();
document.getDocumentCatalog().setDocumentOutline( outline );
PDOutlineItem pagesOutline = new PDOutlineItem();
pagesOutline.setTitle( "All Pages" );
outline.appendChild( pagesOutline );
List pages = document.getDocumentCatalog().getAllPages();
for( int i=0; i<pages.size(); i++ )
{
    PDPage page = (PDPage)pages.get( i );
    PDPageFitWidthDestination dest = new PDPageFitWidthDestination();
    dest.setPage( page );
    PDOutlineItem bookmark = new PDOutlineItem();
    bookmark.setDestination( dest );
    bookmark.setTitle( "Page " + (i+1) );
    pagesOutline.appendChild( bookmark );
}
pagesOutline.openNode();
outline.openNode();

document.save( args[1] );

Question:

I have a template.pdf which has header and footer(some text/image). I am generating a new pdf (say result.pdf) which has other data. I need to copy/repeat template.pdf on every page of result.pdf. So basically the template.pdf will act as header and footer on every page of result.pdf.

The problem is template.pdf appears only on 1st page of result.pdf. My result.pdf can be any 'n' number of pages.

public class templateTest {

 public static void main(String[] args) throws IOException { 
   File file = new File("template.pdf");
   PDDocument mainDocument = PDDocument.load(file);     

    PDPage myPage = mainDocument.getPage(0);
    PDPageContentStream contentStream = new PDPageContentStream(mainDocument, myPage, AppendMode.APPEND, true);

    contentStream.beginText();
    // Some text
    // Table 1 (Depending on table 1 size, pdf pages will increase) 

    contentStream.endText();
    contentStream.close();

    mainDocument.save("result.pdf");
    mainDocument.close();
 }
}

Answer:

I have answered my own question. Tilman Hausherr pointed me in the right direction.

public class templateTest {

 public static void main(String[] args) throws IOException { 

   File file = new File("template.pdf");
   PDDocument templatePdf = PDDocument.load(file);
   PDDocument mainDocument = new PDDocument();     

   PDPage myPage = new PDPage();
    mainDocument.addPage(myPage);
   PDPageContentStream contentStream = new PDPageContentStream(mainDocument, myPage, AppendMode.APPEND, true);

   contentStream.beginText();
   // Some text
  // Table 1 (Depending on table 1 size, pdf pages will increase) 

  contentStream.endText();
  contentStream.close();

  // Process of imposing a layer begins here
  PDPageTree destinationPages = mainDocument.getDocumentCatalog().getPages();

  LayerUtility layerUtility = new LayerUtility(mainDocument);

  PDFormXObject firstForm = layerUtility.importPageAsForm(templatePDF, 0);

  AffineTransform affineTransform = new AffineTransform();

  PDPage destPage = destinationPages.get(0);

  layerUtility.wrapInSaveRestore(destPage);
  layerUtility.appendFormAsLayer(destPage, firstForm, affineTransform, "external page");

  mainDocument.save("result.pdf");
  mainDocument.close();
 }
}

Question:

I create a PDF file using pdfbox 2.0. when i open this pdf file in Adobe reader (windows), by default its open with zoom fit width.

What I need pdf file open with default zoom to page level.

My try: Set zoom level at 100.

PDPageXYZDestination dest = new PDPageXYZDestination();    
dest.setPage(pagea);    
dest.setZoom(1);    
dest.setTop(new Float(PDRectangle.A4.getHeight()).intValue());    
PDActionGoTo action = new PDActionGoTo();    
action.setDestination(dest);    
document.getDocumentCatalog().setOpenAction(action); 

Answer:

Use PDPageFitDestination instead of PDPageXYZDestination - so your code looks like this now:

PDPageFitDestination dest = new PDPageFitDestination();
PDActionGoTo action = new PDActionGoTo();    
action.setDestination(dest);    
document.getDocumentCatalog().setOpenAction(action);

Question:

I am trying to dynamically add PDF pages depending upon content size. for that I did not want to clutter the main method, instead I created a separate method to write the PDF and call the method from main() like below:

//PDF Log Method
PDlog("Create First Page", "b");
PDlog("add more page", "b");
PDlog("close pdf", "b");

I have added system.out.println at the end of each if condition, and all three are getting printed on IDE screen. but an Empty PDF is being generated after 3 method calls end. When I had only one if condition and called the PDlog method only once, the PDF is being saved and closed properly.

How can I call this method multiple times from main and keep on adding page and content multiple times?

Below is the method code:

public static void PDlog(String action, String msg) throws IOException, ClassNotFoundException, SQLException, InterruptedException, COSVisitorException {
    //Master PDF Log File --------------------------------------------------------------------
    String masterPDLog = "X:\\eHub\\QA\\eHub_Automation_Log.pdf";
    // Create a document and add a page to it
    PDDocument document = new PDDocument();
    PDPage page = new PDPage(PDPage.PAGE_SIZE_A4);

    if (action.equals("Create First Page")) {       
            document.addPage(page);
            // Create a new font object selecting one of the PDF base fonts
            PDFont font = PDType1Font.TIMES_ROMAN;
            PDFont boldFont = PDType1Font.TIMES_BOLD;
            //File for CTS Logo --------------------
            InputStream in = new FileInputStream(new File("X:\\eHub\\QA\\img\\cts.jpg"));
            PDJpeg img = new PDJpeg(document, in);

            // Start a new content stream which will "hold" the to be created content
            PDPageContentStream contentStream = new PDPageContentStream(document, page);

            //Place CTS Logo
            //contentStream.drawImage(img, 500, 750);
            contentStream.drawXObject( img, 450, 700, 50, 50 );

            // Define a text content stream using the selected font, moving the cursor and drawing the text "Hello World"
            contentStream.beginText();
            contentStream.setFont( boldFont, 20 );
            contentStream.setNonStrokingColor(Color.BLUE);
            contentStream.moveTextPositionByAmount( 120, 650 );
            contentStream.drawString("eHub Automated Data Quality Report");
            contentStream.endText();

            contentStream.beginText();
            contentStream.setFont( boldFont, 20 );
            contentStream.setNonStrokingColor(Color.BLUE);
            contentStream.moveTextPositionByAmount( 140, 600 );
            contentStream.drawString("Data Profiling/Quality/Analysis");
            contentStream.endText();
            // Make sure that the content stream is closed:
            contentStream.close();
            //document.save(masterPDLog);

            System.out.println("1ST PAGE ADDED");

    }
    else if (action.equals("add more page")) {
            PDFont font = PDType1Font.TIMES_ROMAN;
            document.addPage(page);

            PDPageContentStream contentStream = new PDPageContentStream(document, page);
            contentStream.beginText();
            contentStream.setFont( font, 20 );
            contentStream.setNonStrokingColor(Color.BLACK);
            contentStream.moveTextPositionByAmount( 100, 800 );
            contentStream.drawString("eHub Automated Data Quality Report");
            contentStream.endText();
            contentStream.close();              

            //document.save(masterPDLog);
            System.out.println("2ND PAGE ADDED");
    }
    else if (action.equals("close pdf")) {
        PDFont font = PDType1Font.TIMES_ROMAN;

        PDPageContentStream contentStream = new PDPageContentStream(document, page);
        contentStream.beginText();
        contentStream.setFont( font, 20 );
        contentStream.setNonStrokingColor(Color.BLACK);
        contentStream.moveTextPositionByAmount( 100, 800 );
        contentStream.drawString("eHub Automated Data Quality Report");
        contentStream.endText();
        contentStream.close();


        document.save(masterPDLog);
        document.close();
        System.out.println("PDF CLOSED");
}

Answer:

You create the document each time, that is why.

Just pass the document object to your method, and create the document first - here's your code, corrected:

public static void main(String[] args) throws IOException, COSVisitorException
{
    PDDocument document = new PDDocument();
    pdlog("Create First Page", "b", document);
    pdlog("add more page", "b", document);
    pdlog("close pdf", "b", document);
}

public static void pdlog(String action, String msg, PDDocument document) throws IOException, COSVisitorException
{
    //Master PDF Log File --------------------------------------------------------------------
    String masterPDLog = "X:\\eHub\\QA\\eHub_Automation_Log.pdf";
    // Create a document and add a page to it
    PDPage page = new PDPage(PDPage.PAGE_SIZE_A4);

    if (action.equals("Create First Page"))
    {
        document.addPage(page);
        // Create a new font object selecting one of the PDF base fonts
        PDFont font = PDType1Font.TIMES_ROMAN;
        PDFont boldFont = PDType1Font.TIMES_BOLD;
        //File for CTS Logo --------------------
        InputStream in = new FileInputStream(new File("X:\\eHub\\QA\\img\\cts.jpg"));
        PDJpeg img = new PDJpeg(document, in);

        // Start a new content stream which will "hold" the to be created content
        PDPageContentStream contentStream = new PDPageContentStream(document, page);

            //Place CTS Logo
        //contentStream.drawImage(img, 500, 750);
        contentStream.drawXObject(img, 450, 700, 50, 50);

        // Define a text content stream using the selected font, moving the cursor and drawing the text "Hello World"
        contentStream.beginText();
        contentStream.setFont(boldFont, 20);
        contentStream.setNonStrokingColor(Color.BLUE);
        contentStream.moveTextPositionByAmount(120, 650);
        contentStream.drawString("eHub Automated Data Quality Report");
        contentStream.endText();

        contentStream.beginText();
        contentStream.setFont(boldFont, 20);
        contentStream.setNonStrokingColor(Color.BLUE);
        contentStream.moveTextPositionByAmount(140, 600);
        contentStream.drawString("Data Profiling/Quality/Analysis");
        contentStream.endText();
        // Make sure that the content stream is closed:
        contentStream.close();
        //document.save(masterPDLog);

        System.out.println("1ST PAGE ADDED");

    }
    else if (action.equals("add more page"))
    {
        PDFont font = PDType1Font.TIMES_ROMAN;
        document.addPage(page);

        PDPageContentStream contentStream = new PDPageContentStream(document, page);
        contentStream.beginText();
        contentStream.setFont(font, 20);
        contentStream.setNonStrokingColor(Color.BLACK);
        contentStream.moveTextPositionByAmount(100, 800);
        contentStream.drawString("eHub Automated Data Quality Report");
        contentStream.endText();
        contentStream.close();

        //document.save(masterPDLog);
        System.out.println("2ND PAGE ADDED");
    }
    else if (action.equals("close pdf"))
    {
        PDFont font = PDType1Font.TIMES_ROMAN;

        PDPageContentStream contentStream = new PDPageContentStream(document, page);
        contentStream.beginText();
        contentStream.setFont(font, 20);
        contentStream.setNonStrokingColor(Color.BLACK);
        contentStream.moveTextPositionByAmount(100, 800);
        contentStream.drawString("eHub Automated Data Quality Report");
        contentStream.endText();
        contentStream.close();

        document.save(masterPDLog);
        document.close();
        System.out.println("PDF CLOSED");
    }
}

Your "close pdf" action does not make much sense, you are writing to a PDPage that is never appended.