Document Comparison
Compare PDFs using a different stroke color for each document.
/* * Copyright © 2018-2026 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.annotation.SuppressLintimport android.app.ProgressDialogimport android.content.Contextimport android.net.Uriimport android.os.Buildimport android.os.Bundleimport android.util.Logimport android.util.TypedValueimport android.view.Gravityimport android.view.WindowInsetsimport android.widget.RelativeLayoutimport android.widget.Toastimport androidx.core.graphics.toColorIntimport com.google.android.material.floatingactionbutton.ExtendedFloatingActionButtonimport com.pspdfkit.annotations.BlendModeimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.configuration.activity.ThumbnailBarModeimport com.pspdfkit.configuration.sharing.ShareFeaturesimport com.pspdfkit.document.DocumentSourceimport com.pspdfkit.document.PdfDocumentimport com.pspdfkit.document.PdfDocumentLoaderimport com.pspdfkit.document.processor.ComparisonDialogListenerimport com.pspdfkit.document.processor.ComparisonDocumentimport com.pspdfkit.document.processor.DocumentComparisonDialogimport com.pspdfkit.document.processor.PagePdfimport com.pspdfkit.document.processor.PdfProcessorimport com.pspdfkit.document.processor.PdfProcessorTaskimport com.pspdfkit.document.providers.AssetDataProviderimport com.pspdfkit.ui.DocumentDescriptorimport com.pspdfkit.ui.PdfActivityimport com.pspdfkit.ui.PdfActivityIntentBuilderimport com.pspdfkit.ui.tabs.PdfTabBarCloseModeimport io.reactivex.rxjava3.android.schedulers.AndroidSchedulersimport io.reactivex.rxjava3.core.Singleimport io.reactivex.rxjava3.functions.Consumerimport io.reactivex.rxjava3.schedulers.Schedulersimport java.io.Fileimport java.util.EnumSet
/** * This example shows how to use [PdfProcessor] for comparing PDF pages using a different * stroke color for each and blending these colored pages into a single document. Furthermore, it shows how to integrate the * {@link com.pspdfkit.document.processor.DocumentComparisonDialog} to align two documents for better comparison results. */class DocumentComparisonExample(context: Context) : SdkExample(context, R.string.documentComparisonExampleTitle, R.string.documentComparisonExampleDescription) { private val documentAIndex = 0 // Destination page index private val documentBIndex = 0 // Source page index
@SuppressLint("CheckResult") override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { // We'll show progress dialog while processing documents with PdfProcessor. val progressDialog = createProgressDialog(context) progressDialog.show()
configuration.apply { // The example uses a custom activity with a floating action button to start document alignment. layout(R.layout.activity_document_comparison) // Disable features that are not important for this example. setThumbnailBarMode(ThumbnailBarMode.THUMBNAIL_BAR_MODE_NONE) annotationEditingEnabled(false) outlineEnabled(false) settingsMenuEnabled(false) bookmarkListEnabled(false) thumbnailGridEnabled(false) annotationListEnabled(false) documentInfoViewEnabled(false) searchEnabled(false) printingEnabled(false) contentEditingEnabled(false) setEnabledShareFeatures(EnumSet.noneOf(ShareFeatures::class.java)) invertColors(false) }
// Comparison process consists from 2 steps: // 1. Color strokes from both documents in different colors. // 2. Merge 2 pages from these documents with colored strokes together. val processDocumentsForComparison = Single.defer { // Color strokes in the old document to GREEN. val greenDocumentUri = changeStrokeColorForDocumentFromAssets( context, "comparison/Document-A.pdf", oldDocumentColor, "Document-A", documentAIndex )
// Color strokes in the new document to RED. val redDocumentUri = changeStrokeColorForDocumentFromAssets( context, "comparison/Document-B.pdf", newDocumentColor, "Document-B", documentBIndex )
// Now generate document by merging both colored pages. val mergedDocumentUri = generateComparisonDocument(context, greenDocumentUri, redDocumentUri, "Comparison")
// Return Single emitting triple of red, green and merged documents. return@defer Single.just(Triple(greenDocumentUri, redDocumentUri, mergedDocumentUri)) }
processDocumentsForComparison.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doFinally { // Hide the progress dialog when processing finishes. progressDialog.hide() } .subscribe( Consumer { // Start the PdfActivity with all 3 documents loaded in tabs. This will show documents A and B, as well as the // comparison result. val documentTabs = arrayOf( DocumentDescriptor.fromUri(it.first), DocumentDescriptor.fromUri(it.second), DocumentDescriptor.fromUri(it.third).apply { setTitle("Comparison") } ) val intent = PdfActivityIntentBuilder.fromDocumentDescriptor(context, *documentTabs) .configuration(configuration.build()) // Make the tab with comparison document visible after starting the activity. .visibleDocument(2) .activityClass(DocumentComparisonActivity::class.java) .build() context.startActivity(intent) } ) }
private fun changeStrokeColorForDocumentFromAssets(context: Context, documentAsset: String, color: Int, outputFileName: String, pageIndex: Int): Uri { val outputFile = File(context.filesDir, "$outputFileName.pdf")
val sourceDocument = PdfDocumentLoader.openDocument(context, DocumentSource(AssetDataProvider(documentAsset))) val task = PdfProcessorTask.fromDocument(sourceDocument).changeStrokeColorOnPage(pageIndex, color) PdfProcessor.processDocument(task, outputFile)
return Uri.fromFile(outputFile) }
private fun generateComparisonDocument(context: Context, oldDocumentUri: Uri, newDocumentUri: Uri, outputFileName: String): Uri { val outputFile = File(context.filesDir, "$outputFileName.pdf")
val oldDocument = PdfDocumentLoader.openDocument(context, oldDocumentUri) val task = PdfProcessorTask.fromDocument(oldDocument) .mergePage(PagePdf(context, newDocumentUri, documentBIndex), documentAIndex, BlendMode.DARKEN) PdfProcessor.processDocument(task, outputFile)
return Uri.fromFile(outputFile) }
private fun createProgressDialog(context: Context): ProgressDialog { val progressDialog = ProgressDialog(context) progressDialog.setTitle("Comparing documents") progressDialog.setProgressNumberFormat(null) progressDialog.setProgressPercentFormat(null) progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) progressDialog.isIndeterminate = true progressDialog.setCancelable(false) return progressDialog }
companion object { /** Tint color used for the old document. */ val oldDocumentColor = "#F5281B".toColorInt()
/** Tint color used for the new document. */ val newDocumentColor = "#31C1FF".toColorInt() }}
/** * This activity displays two documents to compare and the merged document. * It receives the selected points to updates the aligned comparison document. */class DocumentComparisonActivity : PdfActivity(), ComparisonDialogListener { private lateinit var oldDocumentSource: DocumentSource private lateinit var newDocumentSource: DocumentSource
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (documentCoordinator.documents.size != 3) { error("This example is built to be launched with exactly 3 documents only (document A, B, and comparison result).") }
// This example assumes that the first two documents are the original documents A and B. oldDocumentSource = documentCoordinator.documents[0].documentSource newDocumentSource = documentCoordinator.documents[1].documentSource initViews()
if (savedInstanceState != null) { // If this activity is recreated after a configuration change, calling restore() ensures that the document will have the // correct callback (this activity) if it was shown before the configuration change. If no dialog was shown, this call is a // no-op. DocumentComparisonDialog.restore(this, this) } }
private fun initViews() { pspdfKitViews.tabBar?.apply { setCloseMode(PdfTabBarCloseMode.CLOSE_DISABLED) // Center the tabs. gravity = Gravity.CENTER_HORIZONTAL } val alignDocumentsButton = findViewById<ExtendedFloatingActionButton>(R.id.pspdf_fab_align)
// Make sure the floating action button is not hidden behind the navigation bar (neither in normal mode nor in immersive mode). alignDocumentsButton.setOnApplyWindowInsetsListener { _, insets -> alignDocumentsButton.layoutParams = (alignDocumentsButton.layoutParams as RelativeLayout.LayoutParams).apply { val margin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt()
bottomMargin = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { margin + insets.getInsets(WindowInsets.Type.systemBars()).bottom } else { @Suppress("DEPRECATION") margin + insets.systemWindowInsetBottom } } insets }
alignDocumentsButton.setOnClickListener { val outputFile = filesDir.resolve("comparison-result.pdf") val oldDocument = ComparisonDocument(oldDocumentSource, 0, DocumentComparisonExample.oldDocumentColor) val newDocument = ComparisonDocument(newDocumentSource, 0, DocumentComparisonExample.newDocumentColor) DocumentComparisonDialog.show(this, configuration, oldDocument, newDocument, outputFile, this) } }
override fun onSetActivityTitle(configuration: PdfActivityConfiguration, document: PdfDocument?) { super.onSetActivityTitle(configuration, document) supportActionBar?.title = "Compare Documents" }
override fun onComparisonSuccessful(alignedDocument: DocumentSource) { // Amit note: not sure if this code needs to handle non-file sources, but it doesn't, so return if not. val fileUri = alignedDocument.fileUri ?: return // Try to replace comparison document. try { documentCoordinator.removeDocument(documentCoordinator.documents[2]) documentCoordinator.addDocument(DocumentDescriptor.fromUri(fileUri), 2) documentCoordinator.documents[2].setTitle("Comparison") documentCoordinator.setVisibleDocument(documentCoordinator.documents[2]) } catch (e: IndexOutOfBoundsException) { e.printStackTrace() } }
override fun onError(error: Throwable) { Log.e("Example", "An error happened while comparing the documents.", error) Toast.makeText(this, "There was a comparison error. Check logcat for details.", Toast.LENGTH_LONG).show() }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.