Create PDF and save to the sdcard in Android

Create PDF file and save it to sdcard in Android
Create PDF file and save it to sdcard in Android

In this article, we will create PDF file of the current view and then save it to the sdcard(internal storage) as pdf file. Here we use PdfDocument (https://developer.android.com/reference/android/graphics/pdf/PdfDocument) which added from API 19(Android KitKat). So you can use it above KitKat version of Android.

First of all we will create Bitmap of the view which we want to save on the PDF file. So for that we need to get the heigh and width of the view, so here we do all the operation in the ‘onWindowFocusChanged’ method instead of ‘onCreate’ method. So the basic structure of the file will look like this.

private RelativeLayout rlContainer;

private int width, height;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_pdf);

  rlContainer = findViewById(R.id.rl_container);
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);

  width = rlContainer.getWidth();
  height = rlContainer.getHeight();
}

Here we have on RelativeLayout which contains all the elements we need to print on the PDF file. So we will initialise it in onCreate and then get its width and height in onWindowFocusChanged. If you try to get the width and height in onCreate, it will be 0 because at that time the view did not rendered on the screen. So we will use the above method for that.

Now we will create a Bitmap of that relative layout using following code. Later we will draw this Bitmap on PDF file using Canvas of the PDF file.

Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c1 = new Canvas(b);
rlContainer.draw(c1);

Now we initialise PdfDocument and create pages in that PDF file. For that we use PdfDocument.Page class. If we have 5 pages in our PDF file then we have to create 5 object of PdfDocument.Page and add them to PdfDocument. Then we fetch the canvas of the PDF page and draw our Bitmap on it. Please check the code below.

PdfDocument pd = new PdfDocument();

PdfDocument.PageInfo pi = new PdfDocument.PageInfo.Builder(width, height, 1).create();
PdfDocument.Page p = pd.startPage(pi);
Canvas c = p.getCanvas();
c.drawBitmap(b, 0, 0, new Paint());
pd.finishPage(p);

pd.close();

Here we can iterate the code from line 3 to line 7 to add more pages to the PDF files. After that we need to close the PdfDocument. This will draw our bitmap to the PDF file. Now to write this PdfDocument to the file on sdcard(internal storage), we need to create a File and write PdfDocument to its OutputStream. But make sure that you write this code before closing the PdfDocument pd.close(). Here is the code for that.

try {
  //make sure you have asked for storage permission before this
  File f = new File(Environment.getExternalStorageDirectory() + File.separator + "a-computer-engineer-pdf-test.pdf");
  pd.writeTo(new FileOutputStream(f));
} catch (FileNotFoundException fnfe) {
  fnfe.printStackTrace();
} catch (IOException ioe) {
  ioe.printStackTrace();
}

pd.close();

Please make sure that before running above code, you have the storage permission granted by user otherwise you will get error. Please check for the runtime Android permission for that. Now your PDF file is saved at your provided location on your sdcard(internal storage).

Here is the full code.

private RelativeLayout rlContainer;

private int width, height;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_pdf);

  rlContainer = findViewById(R.id.rl_container);
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);

  width = rlContainer.getWidth();
  height = rlContainer.getHeight();

  Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  Canvas c1 = new Canvas(b);
  rlContainer.draw(c1);

  PdfDocument pd = new PdfDocument();

  PdfDocument.PageInfo pi = new PdfDocument.PageInfo.Builder(width, height, 1).create();
  PdfDocument.Page p = pd.startPage(pi);
  Canvas c = p.getCanvas();
  c.drawBitmap(b, 0, 0, new Paint());
  pd.finishPage(p);

  try {
    //make sure you have asked for storage permission before this
    File f = new File(Environment.getExternalStorageDirectory() + File.separator + "a-computer-engineer-pdf-test.pdf");
    pd.writeTo(new FileOutputStream(f));
  } catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
  } catch (IOException ioe) {
    ioe.printStackTrace();
  }

  pd.close();
}

If you find any problem or doubt, please mention in comments. Do not forget to share!

Download Source Code: https://github.com/dakshbhatt21/a-computer-engineer

9 thoughts on “Create PDF and save to the sdcard in Android

  1. Johann March 14, 2019 / 8:08 pm

    How would you go about generating a PDF from a scrollable view like a RecyclerView with items that extend beyond what is visible on screen?

    • dakshbhatt May 9, 2019 / 4:51 am

      Hi Johann, sorry for the late reply. You can create pages in PDF and crop the view according to the size of PDF page and then add the according content to the each PDF page.

      Like if you have scrollable content with 3 page length of A4 page size. Then you have to measure height of your one view item from the recyclverview and according to that calculate how many items you can accomodate in one page. Then you have to manually inflate them and add them to your PDF. Once the first page is occupied, go to the next page and so on.

      Please let me know if you need any help.

  2. Sureshtrb July 23, 2019 / 8:26 pm

    Could you make a sample on the subject. I am new and struggling a lot for the past few weeks

      • Sureshtrb July 23, 2019 / 11:57 pm

        I want to convert my Spannable String from the TextView to Save as pdf. Pdf is created successfully, but printing the first page repeatedly for the number of pages specified. My Spannable string is approximately 9 pages. Any suggestion?

      • dakshbhatt July 24, 2019 / 8:48 am

        I think you can use scrollview for displaying text and then split that page by height of each page and then print it to the PDF file.

  3. Sureshtrb July 24, 2019 / 9:05 am

    Attached the code below:
    @TargetApi(Build.VERSION_CODES.KITKAT)
    class MyPrintDocumentAdapter(private var context: Context) : PrintDocumentAdapter() {
    private var pageHeight: Int = 0
    private var pageWidth: Int = 0
    var myPdfDocument: PdfDocument? = null

    private var bm: SpannableStringBuilder? = null

    override fun onLayout(oldAttributes: PrintAttributes, newAttributes: PrintAttributes, cancellationSignal: CancellationSignal, callback: LayoutResultCallback, metadata: Bundle) {
    myPdfDocument = PrintedPdfDocument(context, newAttributes)
    pageHeight = newAttributes.mediaSize!!.heightMils / 300 * 72
    pageWidth = newAttributes.mediaSize!!.widthMils / 300 * 72

    if (cancellationSignal.isCanceled) {
    callback.onLayoutCancelled()
    return
    }

    if (totalpages > 0) {
    val builder = PrintDocumentInfo.Builder( “TestPDFPrint.pdf”)
    .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
    .setPageCount(totalpages)

    val info = builder.build()
    callback.onLayoutFinished(info, true)
    } else {
    callback.onLayoutFailed(“Page count is zero.”)
    }

    }

    private fun pageInRange(pageRanges: Array, page: Int): Boolean {
    for (i in pageRanges.indices) {
    if (page >= pageRanges[i].start && page <= pageRanges[i].end)
    return true
    }
    return false
    }

    private fun drawPage(page: PdfDocument.Page, pagnumber: Int) {
    var pagnumber = pagnumber
    val canvas = page.canvas

    pagnumber++ // Make sure page numbers start at 1

    // val titleBaseLine = 72
    // val leftMargin = 38
    val paint = Paint()
    paint.color = Color.BLACK
    paint.textSize = 40f
    val pageInfo = page.info

    val logo = BitmapFactory.decodeResource(context.resources, R.drawable.mediumlogo75px)

    canvas.drawBitmap(logo, (pageInfo.pageWidth / 6 – logo.width / 2).toFloat(), 0f, paint)

    // val toScale = (bm!!.width / pageWidth).toDouble()
    val mTextPaint = TextPaint()

    val mTextLayout = StaticLayout(bm, mTextPaint, canvas.getWidth(), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, true);

    // Trying to split pages like 40 line per page. Still dont know how to do it. Googling.
    val mTLineCount = mTextLayout.lineCount
    println("mTLineCount : $mTLineCount")
    val pagesTotal = (Math.round(mTLineCount.toDouble() / 40)).toInt()
    totalpages = pagesTotal
    println("totalpages : $totalpages")

    // code prints. But Repeating the pages from begining.
    mTextLayout.draw(canvas)

    paint.textSize = 18f
    canvas.drawText("Page No. $pagnumber", (pageInfo.pageWidth / 2 – "Page No. $pagnumber".length).toFloat(), (pageInfo.pageHeight – 10).toFloat(), paint
    )

    }

    override fun onWrite(pageRanges: Array, destination: ParcelFileDescriptor, cancellationSignal: CancellationSignal, callback: WriteResultCallback) {
    for (i in 0 until totalpages) {
    if (pageInRange(pageRanges, i)) {
    val newPage = PdfDocument.PageInfo.Builder(pageWidth, pageHeight, i).create()

    val page = myPdfDocument!!.startPage(newPage)

    if (cancellationSignal.isCanceled) {
    callback.onWriteCancelled()
    myPdfDocument!!.close()
    myPdfDocument = null
    return
    }
    drawPage(page, i)
    myPdfDocument!!.finishPage(page)
    }
    }

    try {
    myPdfDocument!!.writeTo(FileOutputStream(destination.fileDescriptor))
    } catch (e: IOException) {
    callback.onWriteFailed(e.toString())
    return
    } finally {
    myPdfDocument!!.close()
    myPdfDocument = null
    System.err.println(“FINISHED!!”)
    }

    callback.onWriteFinished(pageRanges)
    }
    }

    printBtn.setOnClickListener {
    val printManager = this.getSystemService(Context.PRINT_SERVICE) as PrintManager

    val jobName = this.getString(R.string.app_name) + “TestPDFPrint”
    println(“jobName : $jobName”)
    printManager.print(jobName, MyPrintDocumentAdapter(this), null)
    }
    Can you suggest the place where I am doing the mistake? As I said earlier I am new to Android Studio. Any help will be appreciated. Thanks

    • dakshbhatt July 24, 2019 / 2:12 pm

      Hello Suresh, I will check that code and update you.

      • Sureshtrb July 25, 2019 / 1:46 pm

        Thanks for your time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s