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.AlertDialog
import android.app.ProgressDialog
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Color
import android.net.Uri
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import com.pspdfkit.annotations.AnnotationType
import com.pspdfkit.catalog.R
import com.pspdfkit.catalog.SdkExample
import com.pspdfkit.catalog.SdkExample.Companion.TAG
import com.pspdfkit.catalog.tasks.ExtractAssetTask
import com.pspdfkit.configuration.activity.PdfActivityConfiguration
import com.pspdfkit.document.processor.NewPage
import com.pspdfkit.document.processor.PageImage
import com.pspdfkit.document.processor.PagePattern
import com.pspdfkit.document.processor.PagePosition
import com.pspdfkit.document.processor.PdfProcessor
import com.pspdfkit.document.processor.PdfProcessor.ProcessorProgress
import com.pspdfkit.document.processor.PdfProcessorTask
import com.pspdfkit.ui.PdfActivity
import com.pspdfkit.ui.PdfActivityIntentBuilder
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subscribers.DisposableSubscriber
import java.io.File
import java.io.IOException
import java.util.HashSet
import 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.