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.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(LOG_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 { private const val LOG_TAG = "DocumentProcessing" }}
This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.