I am trying to achieve generating a pdf file from the rendered html within webview, but it gives me a pdf with single page but page is blank instead of showing the html content.
fun createPdfFromHtml(
context: Context,
htmlContent: String,
onSuccess: (File) -> Unit,
onFail: (Throwable) -> Unit
) {
val webView = WebView(context).apply {
settings.javaScriptEnabled = true
}
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
val printAttributes = PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
.setResolution(PrintAttributes.Resolution("RES1", "LABEL", 300, 300))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build()
val pdfDocument = PrintedPdfDocument(context, printAttributes)
val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create()
val page = pdfDocument.startPage(pageInfo)
webView.draw(page.canvas)
pdfDocument.finishPage(page)
// Save the PDF to the Downloads folder
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadsDir, "output.pdf")
try {
FileOutputStream(file).use { output ->
pdfDocument.writeTo(output)
}
onSuccess(file)
} catch (e: Exception) {
e.printStackTrace()
onFail(e)
} finally {
pdfDocument.close()
}
}
}
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null)
}
I ended up achieving the result using print adapter like below using builder pattern (you can skip builder pattern if you don't need)
class HtmlToPdfConverter private constructor(
private val webView: WebView,
private val htmlContent: String,
private val fileName: String,
private val pdfMediaSize: MediaSize,
private val pdfColorMode: Int,
private val converterListener: ConverterListener? = null
) {
companion object {
private const val FILE_EXTENSION_PDF = "pdf"
private const val MIME_TYPE_HTML = "text/html"
private const val ENCODING = "UTF-8"
private const val PRINT_ATTRIBUTE_RESOLUTION_ID = "PDF_RESOLUTION"
private const val PRINT_ADAPTER_DOCUMENT_LABEL = "Pdf resolution"
private const val PRINT_ADAPTER_DEFAULT_DPI = 300
private const val PRINT_ADAPTER_DOCUMENT_NAME = "Document"
}
interface ConverterListener {
fun onSuccess(file: File)
fun onFail(throwable: Throwable? = null)
}
class Builder {
private var htmlContent: String = ""
private var fileName: String = ""
private var converterListener: ConverterListener? = null
private var pdfMediaSize: MediaSize = MediaSize.ISO_A4
private var pdfColorMode: Int = PrintAttributes.COLOR_MODE_COLOR
fun setHtmlContent(htmlContent: String) = apply { this.htmlContent = htmlContent }
fun setFileName(fileName: String) = apply { this.fileName = fileName }
fun setListener(converterListener: ConverterListener) = apply { this.converterListener = converterListener }
fun setPdfMediaSize(mediaSize: MediaSize) = apply { this.pdfMediaSize = mediaSize }
/**
* @param colorMode A valid color mode or zero.
* @see PrintAttributes#COLOR_MODE_MONOCHROME
* @see PrintAttributes#COLOR_MODE_COLOR
*/
fun setPdfColorMode(colorMode: Int) = apply { this.pdfColorMode = colorMode }
fun build(context: Context) = HtmlToPdfConverter(
webView = createWebView(context),
htmlContent = htmlContent,
fileName = fileName,
pdfMediaSize = pdfMediaSize,
pdfColorMode = pdfColorMode,
converterListener = converterListener
)
private fun createWebView(context: Context) = WebView(context).apply {
settings.apply {
javaScriptEnabled = true
useWideViewPort = true
loadWithOverviewMode = true
}
}
}
fun convert() {
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
val printAdapter = webView.createPrintDocumentAdapter(PRINT_ADAPTER_DOCUMENT_NAME)
printAdapter.onLayout(
null,
getPrintAttributes(),
null,
PrintAdapterLayoutResultCallback(printAdapter),
null
)
}
}
webView.loadDataWithBaseURL(null, htmlContent, MIME_TYPE_HTML, ENCODING, null)
}
private inner class PrintAdapterLayoutResultCallback(
private val printAdapter: PrintDocumentAdapter
) : LayoutResultCallback() {
override fun onLayoutFinished(info: PrintDocumentInfo?, changed: Boolean) {
super.onLayoutFinished(info, changed)
val outputFile = File(webView.context.cacheDir, "$fileName.$FILE_EXTENSION_PDF")
writeContentToOutputFile(printAdapter = printAdapter, file = outputFile)
}
override fun onLayoutFailed(error: CharSequence?) {
super.onLayoutFailed(error)
converterListener?.onFail()
}
}
private fun writeContentToOutputFile(printAdapter: PrintDocumentAdapter, file: File) {
try {
val fileDescriptor = getOutputFile(file)
val pageRangeOfAllPages = arrayOf(PageRange.ALL_PAGES)
printAdapter.onWrite(pageRangeOfAllPages, fileDescriptor, null,
object : PrintDocumentAdapter.WriteResultCallback() {
override fun onWriteFinished(pages: Array<out PageRange>?) {
super.onWriteFinished(pages)
converterListener?.onSuccess(file)
}
override fun onWriteFailed(error: CharSequence?) {
super.onWriteFailed(error)
converterListener?.onFail()
}
})
} catch (e: Exception) {
e.printStackTrace()
converterListener?.onFail(e)
}
}
private fun getOutputFile(file: File): ParcelFileDescriptor? {
val fileDirectory = file.parentFile
if (fileDirectory != null && !fileDirectory.exists()) {
fileDirectory.mkdirs()
}
return try {
file.createNewFile()
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
} catch (exception: IOException) {
throw exception
}
}
private fun getPrintAttributes(): PrintAttributes {
return PrintAttributes.Builder()
.setMediaSize(pdfMediaSize)
.setResolution(
PrintAttributes.Resolution(
PRINT_ATTRIBUTE_RESOLUTION_ID,
PRINT_ADAPTER_DOCUMENT_LABEL,
PRINT_ADAPTER_DEFAULT_DPI,
PRINT_ADAPTER_DEFAULT_DPI
)
)
.setColorMode(pdfColorMode)
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build()
}
}