Rendering PDF vector images using Coil

Jermaine Dilao
3 min readJul 16, 2024

--

Using PdfRenderer and custom Decoder implementation

Photo by Mika Baumeister on Unsplash

The problem

I recently faced a challenge where I needed to render PDF vector assets/images in the app.

Our existing API was exclusively used on iOS, which natively supports rendering PDFs as images. But, we know this is not the same case on Android.

Possible solutions

One possible solution is to convert all our PDF assets from the server to SVGs. However, we’re talking about hundreds of them, making this our last resort.

We could also manually implement downloading the PDF files, converting them to Bitmap, and displaying them inside an Image. But this would also mean handling the caching manually, along with all the other necessary work to make it performant, all of which come for free when using industry-standard image loading libraries.

A better solution

Coil allows us to write our custom decoder exactly for these types of scenarios. This is the same approach they use for their provided SvgDecoder, GifDecoder, etc.

So, how do we do it?

  1. First, let’s create a class that implements the Decoder interface from Coil. In this case, we’re calling it PdfDecoder.
class PdfDecoder(
private val source: ImageSource,
private val options: Options,
) : Decoder {
// implementation
}

2. Inside this class, we’re going to create a decoder factory. The purpose of this class is for Coil to be able to tell if this Decoder supports a certain type of file. In this case, we’re checking against application/pdf mime type.

class Factory : Decoder.Factory {
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder? {
if (!isApplicable(result)) return null
return PdfDecoder(result.source, options)
}

private fun isApplicable(result: SourceResult): Boolean = result.mimeType == "application/pdf"
}

3. Let’s go back to our PdfDecoder and implement the decode() function. Let’s use the SourceResultcoming from our factory to decode the file. We can obtain the already downloaded file by Coil through ImageSource .

Let’s convert the PDF file to Drawable using PdfRenderer and return it as a DecodeResult.

override suspend fun decode(): DecodeResult {
val context = options.context
val pdfRenderer = PdfRenderer(
ParcelFileDescriptor.open(
source.file().toFile(),
ParcelFileDescriptor.MODE_READ_ONLY,
),
)
val page = pdfRenderer.openPage(0)

// For better bitmap quality: https://stackoverflow.com/a/32327174/5285687
val densityDpi = context.resources.displayMetrics.densityDpi
val bitmap = Bitmap.createBitmap(
densityDpi * page.width / 72,
densityDpi * page.height / 72,
Bitmap.Config.ARGB_8888,
)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
page.close()
pdfRenderer.close()

return DecodeResult(
drawable = bitmap.toDrawable(context.resources),
isSampled = false,
)
}

4. Finally, what’s left for us to do is to add this custom PdfDecoder to our ImageLoader instance (ideally inside your Application class).

override fun newImageLoader(): ImageLoader =
ImageLoader.Builder(this)
.components {
add(PdfDecoder.Factory())
}
.build()

5. Now you can render PDF images inside an Image.

// Jetpack Compose
// URL
AsyncImage(model = "https://example.com/image.pdf")


// Views
// URL
imageView.load("https://example.com/image.pdf")

// File
imageView.load(File("/path/to/image.pdf"))

And, that’s it!

I hope I was able to help someone out there with this article. Thank you for reading and happy coding!

You can view the full gist of this solution here.

Important Note: Please adapt the decode() function to your specific requirements. The method shown here for converting a PDF to a bitmap might not be ideal for all situations, especially when handling large PDF files. However, it is a suitable solution for my use case, which only involves rendering vector assets and icons from our backend.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Jermaine Dilao
Jermaine Dilao

Written by Jermaine Dilao

A Work in Progress Android Developer. You can check me @ https://jermainedilao.framer.website. | 💻 Senior Android Engineer @ Speakap

No responses yet

Write a response