Skip to content

Commit b642af1

Browse files
authored
Merge pull request #415 from doo/dd/EPIC-6675-pdf-generation-with-OCR
[EPIC-6675] re-implement pdf-generation example
2 parents 057234b + 95532a7 commit b642af1

File tree

7 files changed

+372
-163
lines changed

7 files changed

+372
-163
lines changed

classic-components-example/pdf-generation/build.gradle

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ android {
2323
}
2424
}
2525

26-
kotlin {
27-
jvmToolchain(project.ext.jvmToolchainVersion)
28-
}
29-
3026
packagingOptions {
3127
exclude 'META-INF/LICENSE.txt'
3228
exclude 'META-INF/LICENSE'
@@ -35,13 +31,15 @@ android {
3531
exclude 'META-INF/DEPENDENCIES'
3632
}
3733

38-
buildFeatures {
39-
viewBinding = true
40-
}
34+
buildFeatures.viewBinding = true
4135
}
4236

37+
kotlin.jvmToolchain(project.ext.jvmToolchainVersion)
38+
4339
dependencies {
4440
implementation(project(":common"))
4541
implementation("androidx.appcompat:appcompat:${project.ext.androidxAppcompatVersion}")
46-
implementation("io.scanbot:sdk-package-1:${project.ext.scanbotSdkVersion}")
42+
43+
// NOTE: use `sdk-package-1` when OCR feature is not needed:
44+
implementation("io.scanbot:sdk-package-2:${project.ext.scanbotSdkVersion}")
4745
}

classic-components-example/pdf-generation/src/main/AndroidManifest.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,22 @@
1616
android:exported="true">
1717
<intent-filter>
1818
<action android:name="android.intent.action.MAIN" />
19-
2019
<category android:name="android.intent.category.LAUNCHER" />
2120
</intent-filter>
2221
</activity>
2322

23+
<activity android:name=".PdfActivity" />
24+
<activity android:name=".PdfWithOcrActivity" />
25+
2426
<provider
2527
android:name="androidx.core.content.FileProvider"
2628
android:authorities="${applicationId}.provider"
2729
android:exported="false"
2830
android:grantUriPermissions="true">
31+
2932
<meta-data
3033
android:name="android.support.FILE_PROVIDER_PATHS"
3134
android:resource="@xml/provider_paths" />
3235
</provider>
33-
3436
</application>
35-
3637
</manifest>
Lines changed: 8 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,28 @@
11
package io.scanbot.example
22

33
import android.content.Intent
4-
import android.content.pm.PackageManager
5-
import android.net.Uri
64
import android.os.Bundle
7-
import android.util.Log
8-
import android.view.View
9-
import androidx.activity.result.PickVisualMediaRequest
10-
import androidx.activity.result.contract.ActivityResultContracts
115
import androidx.appcompat.app.AppCompatActivity
12-
import androidx.core.content.FileProvider
13-
import androidx.core.net.toFile
14-
import androidx.lifecycle.lifecycleScope
15-
import io.scanbot.common.mapSuccess
16-
import io.scanbot.common.onSuccess
17-
import io.scanbot.example.common.Const
186
import io.scanbot.example.common.applyEdgeToEdge
19-
import io.scanbot.example.common.showToast
207
import io.scanbot.example.databinding.ActivityMainBinding
21-
import io.scanbot.sdk.ScanbotSDK
22-
import io.scanbot.sdk.image.ImageRef
23-
import io.scanbot.sdk.imageprocessing.ParametricFilter
24-
import io.scanbot.sdk.ocr.OcrEngineManager
25-
import io.scanbot.sdk.pdfgeneration.PageSize
26-
import io.scanbot.sdk.pdfgeneration.PdfConfiguration
27-
import io.scanbot.sdk.pdfgeneration.PdfGenerator
28-
import io.scanbot.sdk.util.PolygonHelper
29-
import kotlinx.coroutines.Dispatchers
30-
import kotlinx.coroutines.launch
31-
import kotlinx.coroutines.runBlocking
32-
import kotlinx.coroutines.withContext
33-
import java.io.File
34-
35-
/**
36-
Ths example uses new sdk APIs presented in Scanbot SDK v.8.x.x
37-
Please, check the official documentation for more details:
38-
Result API https://docs.scanbot.io/android/document-scanner-sdk/detailed-setup-guide/result-api/
39-
ImageRef API https://docs.scanbot.io/android/document-scanner-sdk/detailed-setup-guide/image-ref-api/
40-
*/
418

429
class MainActivity : AppCompatActivity() {
43-
private val runOcr = false
44-
private val scanbotSdk by lazy { ScanbotSDK(this) }
45-
private val pdfGenerator by lazy { scanbotSdk.createPdfGenerator(if (runOcr) OcrEngineManager.OcrConfig() else null) }
46-
47-
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
48-
49-
private val selectGalleryImageResultLauncher =
50-
// limit to 5 images for example purposes
51-
registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(5)) { uris ->
52-
if (uris.isEmpty()) {
53-
this@MainActivity.showToast("No images were selected!")
54-
Log.w(Const.LOG_TAG, "No images were selected!")
55-
return@registerForActivityResult
56-
}
57-
58-
if (!scanbotSdk.licenseInfo.isValid) {
59-
this@MainActivity.showToast("Scanbot SDK license (1-minute trial) has expired!")
60-
Log.w(Const.LOG_TAG, "Scanbot SDK license (1-minute trial) has expired!")
61-
return@registerForActivityResult
62-
}
63-
64-
lifecycleScope.launch { processDocument(uris, isGrayscaleChecked) }
65-
}
66-
67-
private val isGrayscaleChecked: Boolean
68-
get() = binding.grayscaleCheckBox.isChecked
6910

7011
override fun onCreate(savedInstanceState: Bundle?) {
7112
super.onCreate(savedInstanceState)
13+
val binding = ActivityMainBinding.inflate(layoutInflater)
7214
setContentView(binding.root)
7315
supportActionBar!!.hide()
74-
applyEdgeToEdge(findViewById(R.id.root_view))
75-
76-
binding.scanButton.setOnClickListener {
77-
selectGalleryImageResultLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
78-
}
79-
}
80-
81-
private suspend fun processDocument(uris: List<Uri>, applyGrayscale: Boolean) {
82-
val filters =
83-
if (applyGrayscale) listOf(ParametricFilter.grayscaleFilter()) else emptyList()
84-
withContext(Dispatchers.Main) { binding.progressBar.visibility = View.VISIBLE }
85-
86-
withContext(Dispatchers.Default) {
87-
scanbotSdk.createDocumentScanner().mapSuccess { documentScanner ->
88-
//can be handled with .getOrNull() if needed
89-
val document = scanbotSdk.documentApi.createDocument()
90-
.getOrReturn() //can be handled with .getOrNull() if needed
91-
uris.asSequence().forEach { uri ->
92-
val imageRef = contentResolver.openInputStream(uri)?.use { inputStream ->
93-
ImageRef.fromInputStream(inputStream)
94-
}
95-
if (imageRef == null) {
96-
Log.w(Const.LOG_TAG, "Cannot open input stream from URI: $uri")
97-
return@forEach
98-
}
99-
val newPolygon = documentScanner.run(imageRef).getOrNull()?.pointsNormalized
100-
?: PolygonHelper.getFullPolygon()
101-
val page = document.addPage(imageRef)
102-
.getOrReturn() //can be handled with .getOrNull() if needed
103-
page.apply(newPolygon = newPolygon, newFilters = filters)
104-
}
105-
pdfGenerator.generate(
106-
document,
107-
PdfConfiguration.default().copy(pageSize = PageSize.A4)
108-
)
109-
document.pdfUri.toFile()
110-
}.onSuccess { renderedPdfFile ->
111-
runBlocking(Dispatchers.Main) {
112-
binding.progressBar.visibility = View.GONE
113-
openPdfDocument(renderedPdfFile)
114-
}
115-
}
116-
}
117-
}
16+
applyEdgeToEdge(binding.root)
11817

119-
private fun openPdfDocument(file: File) {
120-
val uri = FileProvider.getUriForFile(this, "${this.packageName}.provider", file)
121-
122-
val openIntent = Intent(Intent.ACTION_VIEW).apply {
123-
setDataAndType(uri, "application/pdf")
124-
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
18+
binding.pdfButton.setOnClickListener {
19+
val intent = Intent(this, PdfActivity::class.java)
20+
startActivity(intent)
12521
}
12622

127-
if (intent.resolveActivity(packageManager) != null) {
128-
val chooser = Intent.createChooser(openIntent, file.name)
129-
val resInfoList = this.packageManager.queryIntentActivities(
130-
chooser,
131-
PackageManager.MATCH_DEFAULT_ONLY
132-
)
133-
134-
for (resolveInfo in resInfoList) {
135-
val packageName = resolveInfo.activityInfo.packageName
136-
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
137-
}
138-
startActivity(chooser)
139-
} else {
140-
// Handle the case where no app can open PDF files
141-
showToast("No app found to open PDF files")
142-
Log.w(Const.LOG_TAG, "No app found to open PDF files")
23+
binding.pdfWithOcrButton.setOnClickListener {
24+
val intent = Intent(this, PdfWithOcrActivity::class.java)
25+
startActivity(intent)
14326
}
14427
}
14528
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package io.scanbot.example
2+
3+
import android.content.Intent
4+
import android.content.pm.PackageManager
5+
import android.net.Uri
6+
import android.os.Bundle
7+
import android.util.Log
8+
import android.view.View
9+
import androidx.activity.result.PickVisualMediaRequest
10+
import androidx.activity.result.contract.ActivityResultContracts
11+
import androidx.appcompat.app.AppCompatActivity
12+
import androidx.core.content.FileProvider
13+
import androidx.core.net.toFile
14+
import androidx.lifecycle.lifecycleScope
15+
import io.scanbot.common.mapSuccess
16+
import io.scanbot.common.onFailure
17+
import io.scanbot.common.onSuccess
18+
import io.scanbot.example.common.Const
19+
import io.scanbot.example.common.applyEdgeToEdge
20+
import io.scanbot.example.common.showToast
21+
import io.scanbot.example.databinding.ActivityPdfBinding
22+
import io.scanbot.sdk.ScanbotSDK
23+
import io.scanbot.sdk.image.ImageRef
24+
import io.scanbot.sdk.imageprocessing.ParametricFilter
25+
import io.scanbot.sdk.pdfgeneration.PageSize
26+
import io.scanbot.sdk.pdfgeneration.PdfConfiguration
27+
import io.scanbot.sdk.util.PolygonHelper
28+
import kotlinx.coroutines.Dispatchers
29+
import kotlinx.coroutines.launch
30+
import kotlinx.coroutines.runBlocking
31+
import kotlinx.coroutines.withContext
32+
import java.io.File
33+
34+
/** This example uses new sdk APIs presented in Scanbot SDK v.8.x.x
35+
*
36+
* Please, check the official documentation for more details:
37+
* Result API https://docs.scanbot.io/android/document-scanner-sdk/detailed-setup-guide/result-api/
38+
* ImageRef API https://docs.scanbot.io/android/document-scanner-sdk/detailed-setup-guide/image-ref-api/
39+
*/
40+
class PdfActivity : AppCompatActivity() {
41+
42+
private val scanbotSdk by lazy { ScanbotSDK(this) }
43+
44+
private val binding by lazy { ActivityPdfBinding.inflate(layoutInflater) }
45+
46+
private val isGrayscaleChecked: Boolean
47+
get() = binding.grayscaleCheckBox.isChecked
48+
49+
private val selectGalleryImageResultLauncher =
50+
// limit to 5 images for example purposes
51+
registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(5)) { uris ->
52+
if (uris.isEmpty()) {
53+
this@PdfActivity.showToast("No images were selected!")
54+
Log.w(Const.LOG_TAG, "No images were selected!")
55+
return@registerForActivityResult
56+
}
57+
58+
if (!scanbotSdk.licenseInfo.isValid) {
59+
this@PdfActivity.showToast("Scanbot SDK license (1-minute trial) has expired!")
60+
Log.w(Const.LOG_TAG, "Scanbot SDK license (1-minute trial) has expired!")
61+
return@registerForActivityResult
62+
}
63+
64+
lifecycleScope.launch { processDocument(uris, isGrayscaleChecked) }
65+
}
66+
67+
override fun onCreate(savedInstanceState: Bundle?) {
68+
super.onCreate(savedInstanceState)
69+
setContentView(binding.root)
70+
supportActionBar!!.hide()
71+
applyEdgeToEdge(findViewById(R.id.root_view))
72+
73+
binding.scanButton.setOnClickListener {
74+
selectGalleryImageResultLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
75+
}
76+
}
77+
78+
private suspend fun processDocument(uris: List<Uri>, applyGrayscale: Boolean) {
79+
val filters =
80+
if (applyGrayscale) listOf(ParametricFilter.grayscaleFilter()) else emptyList()
81+
withContext(Dispatchers.Main) { binding.progressBar.visibility = View.VISIBLE }
82+
83+
withContext(Dispatchers.Default) {
84+
scanbotSdk.createDocumentScanner().mapSuccess { documentScanner ->
85+
86+
val document = scanbotSdk.documentApi.createDocument()
87+
.getOrReturn() // can be handled with .getOrNull() if needed
88+
89+
uris.asSequence().forEach { uri ->
90+
val imageRef = contentResolver.openInputStream(uri)?.use { inputStream ->
91+
ImageRef.fromInputStream(inputStream)
92+
}
93+
if (imageRef == null) {
94+
Log.w(Const.LOG_TAG, "Cannot open input stream from URI: $uri")
95+
return@forEach
96+
}
97+
val newPolygon = documentScanner.run(imageRef).getOrNull()?.pointsNormalized
98+
?: PolygonHelper.getFullPolygon()
99+
val page = document.addPage(imageRef).getOrReturn() // can be handled with .getOrNull() if needed
100+
page.apply(newPolygon = newPolygon, newFilters = filters)
101+
}
102+
103+
val pdfOcrGenerator = scanbotSdk.createPdfGenerator()
104+
val pdfConfig = PdfConfiguration.default().copy(pageSize = PageSize.A4)
105+
106+
pdfOcrGenerator.generate(document, pdfConfig).getOrReturn() // can be handled with .getOrNull() if needed
107+
108+
document.pdfUri.toFile()
109+
}.onSuccess { renderedPdfFile ->
110+
runBlocking(Dispatchers.Main) {
111+
binding.progressBar.visibility = View.GONE
112+
openPdfDocument(renderedPdfFile)
113+
}
114+
}.onFailure { error ->
115+
runBlocking(Dispatchers.Main) {
116+
binding.progressBar.visibility = View.GONE
117+
Log.w(Const.LOG_TAG, "Error during PDF generation: ${error.message}", error)
118+
showToast("Error during PDF generation:\n${error.message}")
119+
}
120+
}
121+
}
122+
}
123+
124+
private fun openPdfDocument(file: File) {
125+
val uri = FileProvider.getUriForFile(this, "${this.packageName}.provider", file)
126+
127+
val openIntent = Intent(Intent.ACTION_VIEW).apply {
128+
setDataAndType(uri, "application/pdf")
129+
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
130+
}
131+
132+
if (intent.resolveActivity(packageManager) != null) {
133+
val chooser = Intent.createChooser(openIntent, file.name)
134+
val resInfoList = packageManager.queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY)
135+
136+
for (resolveInfo in resInfoList) {
137+
val packageName = resolveInfo.activityInfo.packageName
138+
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
139+
}
140+
141+
startActivity(chooser)
142+
} else {
143+
// Handle the case where no app can open PDF files
144+
showToast("No app found to open PDF files")
145+
Log.w(Const.LOG_TAG, "No app found to open PDF files")
146+
}
147+
}
148+
}

0 commit comments

Comments
 (0)