Editing PDFs in Kotlin for Android
Use the PdfProcessor class for document processing. Get additional resources by visiting Android PDF editor library or our PdfProcessorTask API guide.
/* *   Copyright © 2020-2025 PSPDFKit GmbH. All rights reserved. * *   The PSPDFKit Sample applications are licensed with a modified BSD license. *   Please see License for details. This notice may not be removed from this file. */
// We're temporarily suppressing the ProgressDialog being deprecated warning.// Issue: https://github.com/PSPDFKit/PSPDFKit/issues/32215@file:Suppress("DEPRECATION")
package com.pspdfkit.catalog.examples.kotlin
import android.app.AlertDialogimport android.app.ProgressDialogimport android.content.Contextimport android.graphics.BitmapFactoryimport android.graphics.Colorimport android.net.Uriimport android.util.Logimport android.view.Menuimport android.view.MenuItemimport com.pspdfkit.annotations.AnnotationTypeimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.SdkExample.Companion.TAGimport com.pspdfkit.catalog.tasks.ExtractAssetTaskimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.document.processor.NewPageimport com.pspdfkit.document.processor.PageImageimport com.pspdfkit.document.processor.PagePatternimport com.pspdfkit.document.processor.PagePositionimport com.pspdfkit.document.processor.PdfProcessorimport com.pspdfkit.document.processor.PdfProcessor.ProcessorProgressimport com.pspdfkit.document.processor.PdfProcessorTaskimport com.pspdfkit.ui.PdfActivityimport com.pspdfkit.ui.PdfActivityIntentBuilderimport io.reactivex.rxjava3.android.schedulers.AndroidSchedulersimport io.reactivex.rxjava3.disposables.CompositeDisposableimport io.reactivex.rxjava3.schedulers.Schedulersimport io.reactivex.rxjava3.subscribers.DisposableSubscriberimport java.io.Fileimport java.io.IOExceptionimport java.util.HashSetimport kotlin.math.ceil
/** * This example shows how to use the [PdfProcessor] to split a document, remove annotations from * the document, and flatten annotations on a document. */class DocumentProcessingExample(context: Context) : SdkExample(context, R.string.documentProcessingExampleTitle, R.string.documentProcessingExampleDescription) {
    override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) {        // This example uses a custom activity which showcases several document processing features.        // For the sake of simplicity, deactivate actions in the processing activity.        configuration.annotationListEnabled(false)            .searchEnabled(false)            .outlineEnabled(false)            .thumbnailGridEnabled(false)
        // First extract the example document from the assets.        ExtractAssetTask.extract(ANNOTATIONS_EXAMPLE, title, context) { documentFile ->            // This example opens up a compound document, by providing the extracted document twice.            // This is just an example of how you can merge two, or more documents.            val documentUri1 = Uri.fromFile(documentFile)            val documentUri2 = Uri.fromFile(documentFile)
            // To start the DocumentProcessingExampleActivity create a launch intent using the builder.            val intent = PdfActivityIntentBuilder.fromUri(context, documentUri1, documentUri2)                .configuration(configuration.build())                .activityClass(DocumentProcessingExampleActivity::class)                .build()            context.startActivity(intent)        }    }}
/** * This activity uses the [PdfProcessor] to split a document, removing annotations from the document, and flatten annotation * on a document. */class DocumentProcessingExampleActivity : PdfActivity() {
    private val disposables = CompositeDisposable()
    /**     * Creates menu items that will trigger document processing.     */    override fun onCreateOptionsMenu(menu: Menu): Boolean {        super.onCreateOptionsMenu(menu)        // This will add actions for all document processing examples provided by this activity.        menuInflater.inflate(R.menu.processor_example, menu)        return true    }
    /**     * Triggered by selecting an action from the overflow menu in the action bar.     */    override fun onOptionsItemSelected(item: MenuItem): Boolean {        return when (item.itemId) {            R.id.item_extract_range -> {                createDocumentFromRange()                true            }            R.id.item_flatten_annotations -> {                createFlattenedDocument()                true            }            R.id.item_remove_link_annotations -> {                createDocumentWithoutLinkAnnotations()                true            }            R.id.item_rotate_pages -> {                createDocumentWithRotatedPages()                true            }            R.id.item_new_page -> {                createDocumentWithNewPages()                true            }            else -> super.onOptionsItemSelected(item)        }    }
    private fun createDocumentFromRange() {        val document = document ?: return
        // Define the output file. This example writes to the internal app directory, into a file based on the document's Uid.        val outputFile = File(filesDir, document.uid + "-range.pdf")
        // Extract pages with indexes 1, 2, 3, 5, 6, 14. All other pages won't be copied.        val task = PdfProcessorTask.fromDocument(document)            .removePages(HashSet(listOf(0, 4, 7, 8, 9, 10, 11, 12, 13)))
        // Start document processing, but without annotation flattening.        PdfProcessor.processDocumentAsync(task, outputFile)            // Drop update events to avoid back pressure on slow devices.            .onBackpressureDrop()            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe(ProcessorProgressHandler("Extracting pages.", outputFile))    }
    private fun createFlattenedDocument() {        // Define the output file. This example writes to the internal app directory, into a file based on the document's Uid.        val document = document        val outputFile = File(filesDir, document!!.uid + "-flattened.pdf")
        // Start document processing, requesting a flattening of all annotations.        val task = PdfProcessorTask.fromDocument(document)            .changeAllAnnotations(PdfProcessorTask.AnnotationProcessingMode.FLATTEN)
        PdfProcessor.processDocumentAsync(task, outputFile)            // Drop update events to avoid back pressure on slow devices.            .onBackpressureDrop()            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe(ProcessorProgressHandler("Flattening annotations.", outputFile))    }
    private fun createDocumentWithoutLinkAnnotations() {        val document = document ?: return
        // Define the output file. This example writes to the internal app directory, into a file based on the document's Uid.        val outputFile = File(filesDir, document.uid + "-without-link-annotations.pdf")
        // Start document processing, requesting a flattening of all annotations.        val task = PdfProcessorTask.fromDocument(document)            .changeAnnotationsOfType(AnnotationType.LINK, PdfProcessorTask.AnnotationProcessingMode.DELETE)
        PdfProcessor.processDocumentAsync(task, outputFile)            // Drop update events to avoid back pressure on slow devices.            .onBackpressureDrop()            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe(ProcessorProgressHandler("Removing link annotations.", outputFile))    }
    private fun createDocumentWithRotatedPages() {        val document = document ?: return
        // Define the output file. This example writes to the internal app directory, into a file based on the document's Uid.        val outputFile = File(filesDir, document.uid + "-rotated.pdf")
        // Rotate all pages of the document by 90°.        val task = PdfProcessorTask.fromDocument(document)        var pageIndex = 0        val pageCount = document.pageCount        while (pageIndex < pageCount) {            task.rotatePage(pageIndex, 90)            pageIndex++        }
        // Start document processing.        PdfProcessor.processDocumentAsync(task, outputFile)            // Drop update events to avoid back pressure on slow devices.            .onBackpressureDrop()            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe(ProcessorProgressHandler("Rotating pages.", outputFile))    }
    private fun createDocumentWithNewPages() {        val document = document ?: return
        // Define the output file. This example writes to the internal app directory, into a file based on the document's Uid.        val outputFile = File(filesDir, document.uid + "-new-page.pdf")
        // Create a yellow A5 page with a line pattern as first page.        val task = PdfProcessorTask.fromDocument(document)        task.addNewPage(            NewPage.patternPage(NewPage.PAGE_SIZE_A5, PagePattern.LINES_7MM)                .backgroundColor(Color.rgb(241, 236, 121))                .build(),            0        )
        // Create an A0 page with an image as second page.        try {            val bitmap = BitmapFactory.decodeStream(assets.open("media/images/cover.jpg"))            task.addNewPage(                NewPage.emptyPage(NewPage.PAGE_SIZE_A0)                    .withPageItem(PageImage(bitmap, PagePosition.CENTER))                    .build(),                1            )        } catch (e: IOException) {            Log.e(TAG, "Could not read page image.")        }
        // The third page is cloned from the last page of the document, but rotated by 90°.        task.addNewPage(            NewPage.fromPage(document, document.pageCount - 1)                .rotation(90)                .build(),            2        )
        // Start document processing.        PdfProcessor.processDocumentAsync(task, outputFile)            // Drop update events to avoid back pressure on slow devices.            .onBackpressureDrop()            .subscribeOn(Schedulers.io())            .observeOn(AndroidSchedulers.mainThread())            .subscribe(ProcessorProgressHandler("Creating new pages.", outputFile))    }
    override fun onStop() {        super.onStop()        // Dispose all active disposables when activity goes to background.        disposables.clear()    }
    private fun showProcessedDocument(processedDocumentFile: File) {        val context = this@DocumentProcessingExampleActivity        val intent = PdfActivityIntentBuilder.fromUri(context, Uri.fromFile(processedDocumentFile))            .configuration(configuration)            .build()        startActivity(intent)    }
    /**     * Helper class for showing a progress dialog and opening the processed document.     */    @Suppress("DEPRECATION")    private inner class ProcessorProgressHandler(        progressMessage: String,        private val outputFile: File    ) : DisposableSubscriber<ProcessorProgress>() {
        private val progressDialog: ProgressDialog = ProgressDialog.show(            this@DocumentProcessingExampleActivity,            "Processing document",            progressMessage,            false,            true        ) { cancel() }
        init {            disposables.add(this)        }
        override fun onNext(processorProgress: ProcessorProgress) {            progressDialog.progress = ceil((processorProgress.pagesProcessed / processorProgress.totalPages).toDouble()).toInt()        }
        override fun onError(e: Throwable) {            AlertDialog.Builder(this@DocumentProcessingExampleActivity)                .setMessage("Error while processing file: " + e.localizedMessage)                .show()            progressDialog.dismiss()        }
        override fun onComplete() {            showProcessedDocument(outputFile)            progressDialog.dismiss()        }    }
    companion object {    }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.