androidandroid-layoutandroid-bitmapandroid-pdf-api

Android Bitmap from View to PDF A4


Hi I am trying to create an A4 PDF from a view, following this resource . I have been able to successfully create the pdf but it is cutting the bottom of my view off. I have made several adjustments to the view size and adjusted all the measure functionality but still cant seem to inflate this view with independance from the screen. I believe my issues lie somewhere in this code.

/**
 * Entry point for creating PDF
 *
 * @param context (app context)
 * @param activity
 */
fun createPdf(
    context: Context,
    activity: Activity
) {
    val inflater = LayoutInflater.from(context)
    val view = inflater.inflate(R.layout.layout_pdf_page_one, null)

    val bitmap = createBitmapFromView(context, view, activity, 1)
    convertBitmapToPdf(bitmap, activity, nPage = 1)
}

private fun createBitmapFromView(
    context: Context,
    view: View,
    activity: Activity,
    page: Int
): Bitmap {

    val binding = LayoutPdfPageOneBinding.bind(view)
    binding.lifecycleOwner = binding.root.findViewTreeLifecycleOwner()
    initPageOne(binding, context)

    return createBitmap(context, binding.root, activity, binding)
}

private fun createBitmap(
    context: Context,
    view: View,
    activity: Activity,
    binding: LayoutPdfPageOneBinding
): Bitmap {
    // whats going on here is getting the display metrics I think I need the layout 
 size as losing some of the bottom.
    val displayMetrics = DisplayMetrics()
    activity.display?.getRealMetrics(displayMetrics)
    displayMetrics.densityDpi
    view
    view.measure(
        View.MeasureSpec.makeMeasureSpec(
            displayMetrics.widthPixels, View.MeasureSpec.EXACTLY
        ),
        View.MeasureSpec.makeMeasureSpec(
            displayMetrics.heightPixels, View.MeasureSpec.EXACTLY
        )
    )
    view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)

    val bitmap = Bitmap.createBitmap(
        view.measuredWidth,
        view.measuredWidth,
        Bitmap.Config.ARGB_8888
    )

    Log.d(TAG, "createBitmap: ${binding.pdfLayoutPageOne.width}")
    Log.d(TAG, "createBitmap: ${binding.pdfLayoutPageOne.height}")

    val canvas = Canvas(bitmap)
    view.draw(canvas)
    return Bitmap.createScaledBitmap(bitmap, 595, 842, true)
}

I realise this is currently dependant on the screen but I have adjusted those variables to no success. I have also attempted to change the layouts view sizing working out the dp width and size as well as trying mm width and size. First two blocks of this can be seen here they are currently both match parent but be assured I have tried several different sizing methods.

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
 

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/pdf_layout_page_one"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10sp"
    android:paddingStart="24sp"
    android:paddingTop="24sp"
    android:paddingEnd="24sp"
    android:paddingBottom="88sp">

The question is how can I make a xml of size A4 so the design looks as it would when I export and export that view successfully? Any comments very welcome. I do not really want to use an external library as I am exporting some mpandroidcharts within the view for export from bitmap to pdf.


Solution

  • The problem is with the code

    val displayMetrics = DisplayMetrics()
        activity.display?.getRealMetrics(displayMetrics)
        displayMetrics.densityDpi
        view
        view.measure(
            View.MeasureSpec.makeMeasureSpec(
                displayMetrics.widthPixels, View.MeasureSpec.EXACTLY
            ),
            View.MeasureSpec.makeMeasureSpec(
                displayMetrics.heightPixels, View.MeasureSpec.EXACTLY
            )
        )
        view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)
    

    This sizes the view to the screen size and layouts it out to the screen size.

    replace with (hopefully my Kotlin is correct I usually do this in Java):-

        view.measure(
            // Measure to A4 width
            View.MeasureSpec.makeMeasureSpec(
                595, View.MeasureSpec.EXACTLY
            ),
            // Measure to A4 height
            View.MeasureSpec.makeMeasureSpec(
                842, View.MeasureSpec.EXACTLY
            )
        )
        // Layout the view out to the measure dimensions
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight())
    
    

    You might need to set/reset Layout Params as well to WRAP_CONTENT if the view has never been drawn to the screen before (I usually create a separate view that has never been drawn to the screen to use in PDF generation)

    Also not not sure why you do View -> draw to Bitmap -> draw bitmap to PDF

    when

    View -> draw to PDF can be done and produces a much better PDF

    To do this when you val page = pdfDocument.startPage(pageInfo)

    do instead

    val page = pdfDocument.startPage(pageInfo)
    // Draw the view direct to the PDF's Canvas instead via Bitmap
    view.draw(page.canvas)
    

    Update

    More info on what I actually do.

    Instead of inflating xml I just programmatically create a linearLayout to put the things I want drawn to pdf, this linearLayout is size for the PDF not the screen.

    e.g.

    LinearLayout linearLayout = new LinearLayout(getApplicationContext());
    

    The before measuring it needs layout params set

    e.g.

    linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                            LinearLayout.LayoutParams.WRAP_CONTENT));
    

    But I guess in your xml could be change

    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/pdf_layout_page_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10sp"
        android:paddingStart="24sp"
        android:paddingTop="24sp"
        android:paddingEnd="24sp"
        android:paddingBottom="88sp">