16

How do I make a PDF file downloadable in a link?

I'm building a web application using JSF, when the user clicks in a "Save as PDF" link a PDF should be available to be downloaded.

So far I've a working code that generates the PDF file, but the file is saved on my desktop and what I want to do is that when the user clicks on the link the pdf file should be downloadable instead of being stored in the app.

UPDATE 3: Thank you for your help guys, I modifed my code with your suggestions and it's working.

UPDATE 2: I'm getting the following error: Adoble Reader could not open "yourfile.pdf" because is either not a supported file type or because the file has been damaged

UPDATE 1: I'm adding my current code with the changes you have pointed me out, however I'm still struggling to make this work

This is my method that generated the PDF:

public ByteArrayOutputStream createPDF() throws IOException, COSVisitorException {

    PDDocument document;
    PDPage page;
    PDFont font;
    PDPageContentStream contentStream;
    PDJpeg front;
    PDJpeg back;

    InputStream inputFront;
    InputStream inputBack;
    ByteArrayOutputStream output = new ByteArrayOutputStream(); 

    // Creating Document
    document = new PDDocument();

    // Creating Pages
    for(int i=0; i<2; i++) {

        page = new PDPage();

        // Adding page to document
        document.addPage(page); 

        // Adding FONT to document
        font = PDType1Font.HELVETICA;           

        // Retrieve Image to be added to the PDF
        inputFront = new FileInputStream(new File("D:/Media/imageFront.jpg"));  
        inputBack = new FileInputStream(new File("D:/Media/imageBack.jpg"));

        BufferedImage buffFront = ImageIO.read(inputFront);
        BufferedImage resizedFront = Scalr.resize(buffFront, 460);

        BufferedImage buffBack = ImageIO.read(inputBack);
        BufferedImage resizedBack = Scalr.resize(buffBack, 460); 

        front = new PDJpeg(document, resizedFront);
        back = new PDJpeg(document, resizedBack);

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

        // Let's define the content stream
        contentStream.beginText();
        contentStream.setFont(font, 8);
        contentStream.moveTextPositionByAmount(10, 770);
        contentStream.drawString("Amount: $1.00");
        contentStream.endText();

        contentStream.beginText();
        contentStream.setFont(font, 8);
        contentStream.moveTextPositionByAmount(200, 770);
        contentStream.drawString("Sequence Number: 123456789");
        contentStream.endText();

        contentStream.beginText();
        contentStream.setFont(font, 8);
        contentStream.moveTextPositionByAmount(10, 760);
        contentStream.drawString("Account: 123456789");
        contentStream.endText();

        contentStream.beginText();
        contentStream.setFont(font, 8);
        contentStream.moveTextPositionByAmount(200, 760);
        contentStream.drawString("Captura Date: 04/25/2011");
        contentStream.endText();

        contentStream.beginText();
        contentStream.setFont(font, 8);
        contentStream.moveTextPositionByAmount(10, 750);
        contentStream.drawString("Bank Number: 123456789");
        contentStream.endText();

        contentStream.beginText();
        contentStream.setFont(font, 8);
        contentStream.moveTextPositionByAmount(200, 750);
        contentStream.drawString("Check Number: 123456789");
        contentStream.endText();            

        // Let's close the content stream       
        contentStream.close();

    }

    // Finally Let's save the PDF
    document.save(output);
    document.close();

    return output;
}

This is my servlet that call the previous code and generates the output and set the header:

try {

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        output = createPDF();

        response.addHeader("Content-Type", "application/force-download"); 
        response.addHeader("Content-Disposition", "attachment; filename=\"yourFile.pdf\"");
        response.getOutputStream().write(output.toByteArray());

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

I'm not sure what I'm missing since when I try to open the PDF I got the error: Adoble Reader could not open "yourfile.pdf" because is either not a supported file type or because the file has been damaged

Night Elve
  • 217
  • 1
  • 3
  • 11
  • Re "Update 2", this might be this bug: http://issues.apache.org/jira/browse/PDFBOX-2026 . It will be fixed in 1.8.5. Or download a snapshot. – Tilman Hausherr Apr 16 '14 at 10:46
  • Hello @Night. I am trying to implement something similar to what you accomplished. Could you please put the response object declaration inside your servlet? Or maybe post the entire code? – Erick May 24 '16 at 01:46

2 Answers2

7

You need to set the proper http headers in order to tell the browser to download the file.

response.addHeader("Content-Type", "application/force-download")
response.addHeader("Content-Disposition", "attachment; filename=\"yourFile.pdf\"")
jloper3
  • 384
  • 1
  • 4
  • I added a sample code to my question also I added the headers but I get the following error when trying to open the PDF File: Adoble Reader could not open "yourfile.pdf" because is either not a supported file type or because the file has been damaged. – Night Elve Jan 18 '12 at 17:30
  • The "yourfile.pdf" should be a filename you are generating. I only included that as a sample name. Try removing the backslashes and quotes. – jloper3 Jan 18 '12 at 19:49
  • response.addHeader("Content-Disposition", "attachment; filename=yourFile.pdf") – jloper3 Jan 18 '12 at 19:50
  • I'm still struggling to make this work, I mean thanks to your response and Ben Brunk response now I'm able to generate the downloadable file however it seems to be corrupted, maybe the problem resides in how I'm handling the save or the outputstream in the response. – Night Elve Jan 18 '12 at 23:19
4

I have not done this in awhile, so bear with me, but what you do is instead of saving the pdf to a file via a stream, you save the stream in memory as a byte array and then when the user clicks on the link, you set the MIME type to PDF and then open up the byte array as a stream which you return as the response. I apologize for being a bit hazy on details. I think I also used jpedal and iText to get it done.

I can't show you all of the code, but here is some:

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.DottedLineSeparator;

... // class, etc.

public ByteArrayOutputStream createOrderFormPdf(OrderFormDTO dto)
        throws DocumentException {
    Document document = new Document();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PdfWriter.getInstance(document, baos);
    document.open();

    Paragraph header = new Paragraph();
    header.add(new Phrase(...));

    OrderFormDtoPdfAdapter pdfAdapter = new OrderFormDtoPdfAdapter(dto);
    header.add(pdfAdapter.getPdfHeaderTable());

    document.add(header);

            ... // other code

    Paragraph footer = new Paragraph();
    footer.add(pdfAdapter.getPDFFooterTable());

    document.add(footer);
    Paragraph paragraph = new Paragraph();
    PdfTableUtils.addEmptyLine(paragraph, 2);

    document.add(paragraph);
    document.add(new DottedLineSeparator());

    document.close();

    return baos;
}

You can then write out the baos on your response as a pdf using the correct MIME type.