Initial commit

This commit is contained in:
Aryan 2026-02-24 17:37:40 +05:30
commit 6072b2ba29
844 changed files with 220532 additions and 0 deletions

1
pdfiumandroid/arrow/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

193
pdfiumandroid/arrow/build.gradle.kts vendored Normal file
View file

@ -0,0 +1,193 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jreleaser.model.Active
import org.jreleaser.model.Signing
plugins {
id("com.android.library")
alias(libs.plugins.kotlin.android)
alias(libs.plugins.detekt)
alias(libs.plugins.kover)
alias(libs.plugins.ktlint)
alias(libs.plugins.jreleaser)
`maven-publish`
signing
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
android {
namespace = "io.legere.pdfiumandroid.arrow"
compileSdk = 35
defaultConfig {
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
publishing {
singleVariant("release") {
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
compileOptions {
sourceCompatibility(JavaVersion.VERSION_17)
targetCompatibility(JavaVersion.VERSION_17)
}
}
dependencies {
implementation(project(":pdfiumandroid"))
implementation(libs.kotlinx.coroutines.android)
implementation(libs.arrow.core)
implementation(libs.androidx.runner)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.truth)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.core.testing)
androidTestImplementation(libs.arrow.fx.coroutines)
}
fun isReleaseBuild(): Boolean = !findProject("VERSION_NAME").toString().contains("SNAPSHOT")
fun getReleaseRepositoryUrl(): String =
if (rootProject.hasProperty("RELEASE_REPOSITORY_URL")) {
rootProject.properties["RELEASE_REPOSITORY_URL"] as String
} else {
"https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
fun getSnapshotRepositoryUrl(): String =
if (rootProject.hasProperty("SNAPSHOT_REPOSITORY_URL")) {
rootProject.properties["SNAPSHOT_REPOSITORY_URL"] as String
} else {
"https://oss.sonatype.org/content/repositories/snapshots/"
}
fun getRepositoryUrl(): String = if (isReleaseBuild()) getReleaseRepositoryUrl() else getSnapshotRepositoryUrl()
fun getRepositoryUsername(): String =
if (rootProject.hasProperty("JRELEASER_MAVENCENTRAL_USERNAME")) {
rootProject.properties["JRELEASER_MAVENCENTRAL_USERNAME"] as String
} else {
""
}
fun getRepositoryPassword(): String =
if (rootProject.hasProperty("JRELEASER_MAVENCENTRAL_TOKEN")) {
rootProject.properties["JRELEASER_MAVENCENTRAL_TOKEN"] as String
} else {
""
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "io.legere"
artifactId = "pdfium-android-kt-arrow"
version = project.property("VERSION_NAME") as String
pom {
name.set("pdfiumandroid.arrow")
description.set("Arrow support for PdfiumAndroid")
url.set(rootProject.properties["POM_URL"] as String)
licenses {
license {
name.set(rootProject.properties["POM_LICENCE_NAME"] as String)
url.set(rootProject.properties["POM_LICENCE_URL"] as String)
distribution.set(rootProject.properties["POM_LICENCE_DIST"] as String)
}
}
developers {
developer {
id.set(rootProject.properties["POM_DEVELOPER_ID"] as String)
name.set(rootProject.properties["POM_DEVELOPER_NAME"] as String)
}
}
scm {
connection.set(rootProject.properties["POM_SCM_CONNECTION"] as String)
developerConnection.set(rootProject.properties["POM_SCM_DEV_CONNECTION"] as String)
url.set(rootProject.properties["POM_SCM_URL"] as String)
}
}
afterEvaluate {
from(components["release"])
}
}
}
repositories {
maven {
url =
uri(layout.buildDirectory.dir("target/staging-deploy"))
}
}
}
jreleaser {
project {
inceptionYear = "2023"
author("@johngray1965")
description = "Arrow support for PdfiumAndroid"
version = rootProject.properties["VERSION_NAME"] as String
license = "http://www.apache.org/licenses/LICENSE-2.0.txt"
links {
homepage = "https://github.com/johngray1965/PdfiumAndroidKt"
license = "http://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
gitRootSearch = true
signing {
active = Active.ALWAYS
mode = Signing.Mode.COMMAND
armored = true
verify = false
command {
executable = "gpg"
keyName = "4BBF8FAB"
publicKeyring = "/Users/gray/.gnupg/secring.gpg"
}
}
release {
github {
skipRelease = true
}
}
// distributions {
// create("pdfiumandroid.arrow") {
// artifact {
// path = file("build/distributions/{{distributionName}}-{{projectVersion}}.zip")
// }
// }
// }
deploy {
maven {
mavenCentral.create("sonatype") {
active = Active.ALWAYS
verifyPom = false
url = "https://central.sonatype.com/api/v1/publisher"
stagingRepository(
layout.buildDirectory
.dir("target/staging-deploy")
.get()
.toString(),
)
username = getRepositoryUsername()
}
}
}
}

View file

4
pdfiumandroid/arrow/gradle.properties vendored Normal file
View file

@ -0,0 +1,4 @@
POM_NAME=pdfiumandroid
POM_ARTIFACT_ID=pdfiumandroid
POM_PACKAGING=aar

21
pdfiumandroid/arrow/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,135 @@
package io.legere.pdfiumandroid.arrow
import androidx.test.ext.junit.runners.AndroidJUnit4
import arrow.core.raise.either
import com.google.common.truth.Truth.assertThat
import io.legere.pdfiumandroid.PdfWriteCallback
import io.legere.pdfiumandroid.arrow.base.BasePDFTest
import junit.framework.TestCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PdfDocumentKtFTest : BasePDFTest() {
private lateinit var pdfDocument: PdfDocumentKtF
private var pdfBytes: ByteArray? = null
@Before
fun setUp() =
runBlocking {
pdfBytes = getPdfBytes("f01.pdf")
assertThat(pdfBytes).isNotNull()
pdfDocument = PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).getOrNull()!!
}
@After
fun tearDown() =
runTest {
pdfDocument.close()
}
@Test
fun getPageCount() =
runTest {
either {
val pageCount = pdfDocument.getPageCount().bind()
assertThat(pageCount).isEqualTo(4)
}
}
@Test
fun openPage() =
runTest {
either {
val page = pdfDocument.openPage(0).bind()
assertThat(page).isNotNull()
}
}
@Test
fun openPages() =
runTest {
either {
val page = pdfDocument.openPages(0, 3).bind()
assertThat(page.size).isEqualTo(4)
}
}
@Test
fun getDocumentMeta() =
runTest {
either {
val meta = pdfDocument.getDocumentMeta().bind()
assertThat(meta).isNotNull()
}
}
@Test
fun getTableOfContents() =
runTest {
either {
// I don't think this test document has a table of contents
val toc = pdfDocument.getTableOfContents().bind()
TestCase.assertNotNull(toc)
assertThat(toc.size).isEqualTo(0)
}
}
@Test
fun openTextPage() =
runTest {
either {
val page = pdfDocument.openPage(0).bind()
val textPage = page.openTextPage().bind()
assertThat(textPage).isNotNull()
}
}
@Test
fun openTextPages() =
runTest {
either {
val textPages = pdfDocument.openTextPages(0, 3).bind()
assertThat(textPages.size).isEqualTo(4)
}
}
@Test
fun saveAsCopy() =
runTest {
pdfDocument.saveAsCopy(
object : PdfWriteCallback {
override fun WriteBlock(data: ByteArray?): Int {
// Truth.assertThat(data?.size).isEqualTo(pdfBytes?.size)
// Truth.assertThat(data).isEqualTo(pdfBytes)
return data?.size ?: 0
}
},
)
}
fun close() =
runTest {
either {
var documentAfterClose: PdfDocumentKtF?
PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).bind().use {
documentAfterClose = it
}
documentAfterClose?.openPage(0)
}.mapLeft {
assertThat(it).isInstanceOf(PdfiumKtFErrors.AlreadyClosed::class.java)
}
}
}

View file

@ -0,0 +1,316 @@
package io.legere.pdfiumandroid.arrow
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import androidx.test.ext.junit.runners.AndroidJUnit4
import arrow.core.raise.either
import com.google.common.truth.Truth.assertThat
import io.legere.pdfiumandroid.arrow.base.BasePDFTest
import io.legere.pdfiumandroid.util.Size
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PdfPageKtFTest : BasePDFTest() {
private lateinit var pdfDocument: PdfDocumentKtF
private var pdfBytes: ByteArray? = null
@Before
fun setUp() =
runBlocking {
pdfBytes = getPdfBytes("f01.pdf")
assertThat(pdfBytes).isNotNull()
pdfDocument = PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).getOrNull()!!
}
@After
fun tearDown() {
pdfDocument.close()
}
@Test
fun getPageWidth() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val pageWidth = page.getPageWidth(72).bind()
assertThat(pageWidth).isEqualTo(612) // 8.5 inches * 72 dpi
}
}
}
@Test
fun getPageHeight() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val pageWidth = page.getPageHeight(72).bind()
assertThat(pageWidth).isEqualTo(792) // 11 inches * 72 dpi
}
}
}
@Test
fun getPageWidthPoint() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val pageWidth = page.getPageWidthPoint().bind()
assertThat(pageWidth).isEqualTo(612) // 11 inches * 72 dpi
}
}
}
@Test
fun getPageHeightPoint() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val pageWidth = page.getPageHeightPoint().bind()
assertThat(pageWidth).isEqualTo(792) // 11 inches * 72 dpi
}
}
}
@Test
fun getPageCropBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val cropBox = page.getPageCropBox().bind()
assertThat(cropBox).isEqualTo(noResultRect)
}
}
}
@Test
fun getPageMediaBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val mediaBox = page.getPageMediaBox().bind()
assertThat(mediaBox).isEqualTo(RectF(0.0f, 0.0f, 612.0f, 792.0f))
}
}
}
@Test
fun getPageBleedBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val bleedBox = page.getPageBleedBox().bind()
assertThat(bleedBox).isEqualTo(noResultRect)
}
}
}
@Test
fun getPageTrimBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val trimBox = page.getPageTrimBox().bind()
assertThat(trimBox).isEqualTo(noResultRect)
}
}
}
@Test
fun getPageArtBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val artBox = page.getPageArtBox().bind()
assertThat(artBox).isEqualTo(noResultRect)
}
}
}
@Test
fun getPageBoundingBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val artBox = page.getPageBoundingBox().bind()
assertThat(artBox).isEqualTo(RectF(0f, 792f, 612f, 0f))
}
}
}
@Test
fun getPageSize() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val size = page.getPageSize(72).bind()
assertThat(size).isEqualTo(Size(612, 792))
}
}
}
@Test
fun renderPage() =
runTest {
// I really don't know how to test it
}
@Test
fun testRenderPage() =
runTest {
// I really don't know how to test it
}
@Test
fun renderPageBitmap() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val conf = Bitmap.Config.RGB_565 // see other conf types
val bmp = Bitmap.createBitmap(612, 792, conf) // this creates a MUTABLE bitmap
page.renderPageBitmap(bmp, 0, 0, 612, 792)
// How to verify that it's correct?
// Even if we don't verify the bitmap, we can check that it doesn't crash
}
}
}
@Test
fun testRenderPageBitmap() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val conf = Bitmap.Config.RGB_565 // see other conf types
val bmp = Bitmap.createBitmap(612, 792, conf) // this creates a MUTABLE bitmap
page.renderPageBitmap(bmp, 0, 0, 612, 792, renderAnnot = true, textMask = true)
// How to verify that it's correct?
// Even if we don't verify the bitmap, we can check that it doesn't crash
}
}
}
@Test
fun getPageLinks() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val links = page.getPageLinks().bind()
assertThat(links.size).isEqualTo(0) // The test doc doesn't have links
}
}
}
@Test
fun mapPageCoordsToDevice() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val devicePt = page.mapPageCoordsToDevice(0, 0, 100, 100, 0, 0.0, 0.0).bind()
assertThat(devicePt).isEqualTo(Point(0, 100))
}
}
}
@Test
fun mapDeviceCoordsToPage() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val devicePt = page.mapDeviceCoordsToPage(0, 0, 100, 100, 0, 0, 0).bind()
assertThat(devicePt).isEqualTo(PointF(0f, 792.00006f))
}
}
}
@Test
fun mapRectToDevice() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val devicePt = page.mapRectToDevice(0, 0, 100, 100, 0, RectF(0f, 0f, 100f, 100f)).bind()
assertThat(devicePt).isEqualTo(
Rect(
// 0f in coords to 0f in device
0,
// 0f in corrds in at the bottom, the bottom of the device is 100f
100,
// 100f in coords = 100f/(8.5*72) * 100f = 16f
16,
// 100f in coords = 100 - 100f/(11*72) * 100f = 87f
87,
),
)
}
}
}
@Test
fun mapRectToPage() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
val devicePt = page.mapRectToPage(0, 0, 100, 100, 0, Rect(0, 0, 100, 100)).bind()
assertThat(devicePt).isEqualTo(
RectF(0.0f, 792.00006f, 612.0f, 0.0f),
)
}
}
}
fun close() =
runTest {
either {
var pageAfterClose: PdfPageKtF?
pdfDocument.openPage(0).bind().use { page ->
pageAfterClose = page
}
pageAfterClose!!.getPageWidth(72)
}.mapLeft {
assertThat(it).isInstanceOf(PdfiumKtFErrors.AlreadyClosed::class.java)
}
}
@Test
fun getPage() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
assertThat(page).isNotNull()
}
}
}
}

View file

@ -0,0 +1,114 @@
package io.legere.pdfiumandroid.arrow
import android.graphics.RectF
import androidx.test.ext.junit.runners.AndroidJUnit4
import arrow.core.raise.either
import com.google.common.truth.Truth
import io.legere.pdfiumandroid.arrow.base.BasePDFTest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PdfPageLinkKtFTest : BasePDFTest() {
private lateinit var pdfDocument: PdfDocumentKtF
private lateinit var pdfPage: PdfPageKtF
private lateinit var pdfTextPage: PdfTextPageKtF
private var pdfBytes: ByteArray? = null
@Before
fun setUp() =
runBlocking {
pdfBytes = getPdfBytes("pdf-test.pdf")
Truth.assertThat(pdfBytes).isNotNull()
pdfDocument = PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).getOrNull()!!
pdfPage = pdfDocument.openPage(0).getOrNull()!!
pdfTextPage = pdfPage.openTextPage().getOrNull()!!
}
@After
fun tearDown() {
pdfTextPage.close()
pdfPage.close()
pdfDocument.close()
}
@Test
fun testLink(): TestResult =
runTest {
either {
pdfTextPage.loadWebLink().bind().use { links ->
Truth.assertThat(links).isNotNull()
}
}
}
@Test
fun testCountWebLinks(): TestResult =
runTest {
either {
pdfTextPage.loadWebLink().bind().use { links ->
Truth.assertThat(links).isNotNull()
Truth.assertThat(links.countWebLinks().bind()).isEqualTo(1)
}
}
}
@Test
fun testGetTextRange(): TestResult =
runTest {
either {
pdfTextPage.loadWebLink().bind().use { links ->
Truth.assertThat(links).isNotNull()
Truth.assertThat(links.getTextRange(0).bind()).isEqualTo(Pair(351, 31))
}
}
}
@Test
fun testGetUrl(): TestResult =
runTest {
either {
pdfTextPage.loadWebLink().bind().use { links ->
Truth.assertThat(links).isNotNull()
val (_, count) = links.getTextRange(0).bind()
Truth
.assertThat(links.getURL(0, count).bind())
.isEqualTo("http://www.education.gov.yk.ca/")
}
}
}
@Test
fun testCountRects(): TestResult =
runTest {
either {
pdfTextPage.loadWebLink().bind().use { links ->
Truth.assertThat(links).isNotNull()
val count = links.countRects(0).bind()
Truth.assertThat(count).isEqualTo(1)
}
}
}
@Test
fun testGetRect(): TestResult =
runTest {
either {
pdfTextPage.loadWebLink().bind().use { links ->
Truth.assertThat(links).isNotNull()
val count = links.getRect(0, 0).bind()
Truth
.assertThat(count)
.isEqualTo(RectF(221.46f, 480.624f, 389.66394f, 469.152f))
}
}
}
}

View file

@ -0,0 +1,235 @@
package io.legere.pdfiumandroid.arrow
import android.graphics.RectF
import androidx.test.ext.junit.runners.AndroidJUnit4
import arrow.core.raise.either
import com.google.common.truth.Truth.assertThat
import io.legere.pdfiumandroid.arrow.base.BasePDFTest
import junit.framework.TestCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PdfTextPageKtTest : BasePDFTest() {
private lateinit var pdfDocument: PdfDocumentKtF
private var pdfBytes: ByteArray? = null
@Before
fun setUp() =
runBlocking {
pdfBytes = getPdfBytes("f01.pdf")
TestCase.assertNotNull(pdfBytes)
pdfDocument = PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).getOrNull()!!
}
@After
fun tearDown() {
pdfDocument.close()
}
@Test
fun textPageCountChars() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val charCount = textPage.textPageCountChars().bind()
assertThat(charCount).isEqualTo(3468)
}
}
}
}
@Test
fun textPageGetText() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val text = textPage.textPageGetText(0, 100).bind()
assertThat(text?.length).isEqualTo(100)
}
}
}
}
@Test
fun textPageGetUnicode() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val char = textPage.textPageGetUnicode(0).bind()
assertThat(char).isEqualTo('T')
}
}
}
}
@Test
fun textPageGetCharBox() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val rect = textPage.textPageGetCharBox(0).bind()
assertThat(rect)
.isEqualTo(RectF(90.314415f, 715.3187f, 103.44171f, 699.1206f))
}
}
}
}
@Test
fun textPageGetCharIndexAtPos() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val characterToLookup = 0
val rect = textPage.textPageGetCharBox(characterToLookup).bind()
val pos =
textPage
.textPageGetCharIndexAtPos(
rect?.centerX()?.toDouble() ?: 0.0,
rect?.centerY()?.toDouble() ?: 0.0,
// Shouldn't need much since we're in the middle of the rect
1.0,
1.0,
).bind()
assertThat(pos).isEqualTo(characterToLookup)
}
}
}
}
@Test
fun textPageCountRects() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val rectCount = textPage.textPageCountRects(0, 100).bind()
assertThat(rectCount).isEqualTo(4)
}
}
}
}
@Test
fun textPageGetRect() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val rect = textPage.textPageGetRect(0).bind()
assertThat(rect).isEqualTo(RectF(0f, 0f, 0f, 0f))
}
}
}
}
@Test
fun textPageGetBoundedText() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val text = textPage.textPageGetBoundedText(RectF(0f, 97f, 100f, 100f), 100).bind()
assertThat(text).isEqualTo("Do")
}
}
}
}
@Test
fun getFontSize() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val fontSize = textPage.getFontSize(0).bind()
assertThat(fontSize).isEqualTo(22.559999465942383)
}
}
}
}
@Test
fun findStart(): TestResult =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
val findWhat = "children's"
val startIndex = 0
textPage.findStart(findWhat, emptySet(), startIndex).bind().use { findHandle ->
var result = findHandle.findNext()
assertThat(result.bind()).isTrue()
var index = findHandle.getSchResultIndex().bind()
var count = findHandle.getSchCount().bind()
var text = textPage.textPageGetText(index, count).bind()
assertThat(index).isEqualTo(1525)
assertThat(count).isEqualTo(10)
assertThat(text).isEqualTo(findWhat)
result = findHandle.findNext()
assertThat(result.bind()).isTrue()
index = findHandle.getSchResultIndex().bind()
count = findHandle.getSchCount().bind()
text = textPage.textPageGetText(index, count).bind()
assertThat(index).isEqualTo(2761)
assertThat(count).isEqualTo(10)
assertThat(text).isEqualTo(findWhat)
result = findHandle.findNext()
assertThat(result.bind()).isFalse()
}
}
}
}
}
fun close() =
runTest {
either {
var pageAfterClose: PdfTextPageKtF?
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
pageAfterClose = textPage
}
}
pageAfterClose!!.textPageCountChars()
}.mapLeft {
assertThat(it).isInstanceOf(PdfiumKtFErrors.AlreadyClosed::class.java)
}
}
@Test
fun getPage() =
runTest {
either {
pdfDocument.openPage(0).bind().use { page ->
page.openTextPage().bind().use { textPage ->
assertThat(textPage.page).isNotNull()
}
}
}
}
}

View file

@ -0,0 +1,39 @@
package io.legere.pdfiumandroid.arrow
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import io.legere.pdfiumandroid.arrow.base.BasePDFTest
import io.legere.pdfiumandroid.arrow.base.ByteArrayPdfiumSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PdfiumCoreKtFTest : BasePDFTest() {
@Test
fun newDocument() =
runTest {
val pdfBytes = getPdfBytes("f01.pdf")
assertThat(pdfBytes).isNotNull()
val pdfiumCore = PdfiumCoreKtF(Dispatchers.Unconfined)
val pdfDocument = pdfiumCore.newDocument(pdfBytes)
assertThat(pdfDocument).isNotNull()
}
@Test
fun newDocumentWithCustomSource() =
runTest {
val pdfBytes = getPdfBytes("f01.pdf")
assertThat(pdfBytes).isNotNull()
val pdfiumCore = PdfiumCoreKtF(Dispatchers.Unconfined)
val pdfDocument = pdfiumCore.newDocument(ByteArrayPdfiumSource(pdfBytes!!))
assertThat(pdfDocument).isNotNull()
}
}

View file

@ -0,0 +1,27 @@
package io.legere.pdfiumandroid.arrow.base
import android.graphics.RectF
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
@Suppress("unused")
open class BasePDFTest {
// set to true to skip tests that are not implemented yet
// set to false to force unimplemented tests to fail
val notImplementedAssetValue = false
val noResultRect = RectF(-1f, -1f, -1f, -1f)
fun getPdfBytes(filename: String): ByteArray? {
val appContext = InstrumentationRegistry.getInstrumentation().context
val assetManager = appContext.assets
try {
val input = assetManager.open(filename)
return input.readBytes()
} catch (e: Exception) {
Log.e(BasePDFTest::class.simpleName, "Ugh", e)
}
assetManager.close()
return null
}
}

View file

@ -0,0 +1,28 @@
package io.legere.pdfiumandroid.arrow.base
import io.legere.pdfiumandroid.PdfiumSource
class ByteArrayPdfiumSource(
private val array: ByteArray,
) : PdfiumSource {
override val length: Long
get() = array.size.toLong()
override fun read(
position: Long,
buffer: ByteArray,
size: Int,
): Int {
array.copyInto(
destination = buffer,
destinationOffset = 0,
startIndex = position.toInt(),
endIndex = position.toInt() + size,
)
return size
}
override fun close() {
// nothing to close
}
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>

View file

@ -0,0 +1,42 @@
package io.legere.pdfiumandroid.arrow
import arrow.core.Either
import io.legere.pdfiumandroid.FindResult
import kotlinx.coroutines.CoroutineDispatcher
import java.io.Closeable
@Suppress("unused")
class FindResultKtF(
private val findResult: FindResult,
private val dispatcher: CoroutineDispatcher,
) : Closeable {
suspend fun findNext(): Either<PdfiumKtFErrors, Boolean> =
wrapEither(dispatcher) {
findResult.findNext()
}
suspend fun findPrev(): Either<PdfiumKtFErrors, Boolean> =
wrapEither(dispatcher) {
findResult.findPrev()
}
suspend fun getSchResultIndex(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
findResult.getSchResultIndex()
}
suspend fun getSchCount(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
findResult.getSchCount()
}
suspend fun closeFind() {
wrapEither(dispatcher) {
findResult.closeFind()
}
}
override fun close() {
findResult.closeFind()
}
}

View file

@ -0,0 +1,150 @@
@file:Suppress("unused", "CanBeVal")
package io.legere.pdfiumandroid.arrow
import android.graphics.Matrix
import android.graphics.RectF
import android.view.Surface
import arrow.core.Either
import io.legere.pdfiumandroid.PdfDocument
import io.legere.pdfiumandroid.PdfWriteCallback
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import java.io.Closeable
/**
* PdfDocumentKtF represents a PDF file and allows you to load pages from it.
* @property document the [PdfDocument] to wrap
* @property dispatcher the [CoroutineDispatcher] to use for suspending calls
* @constructor create a [PdfDocumentKtF] from a [PdfDocument]
*/
@Suppress("TooManyFunctions", "CanBeVal")
class PdfDocumentKtF(
val document: PdfDocument,
private val dispatcher: CoroutineDispatcher,
) : Closeable {
/**
* suspend version of [PdfDocument.getPageCount]
*/
suspend fun getPageCount(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
document.getPageCount()
}
/**
* suspend version of [PdfDocument.getPageCharCounts]
*/
suspend fun getPageCharCounts(): Either<PdfiumKtFErrors, IntArray> =
wrapEither(dispatcher) {
document.getPageCharCounts()
}
/**
* suspend version of [PdfDocument.openPage]
*/
suspend fun openPage(pageIndex: Int): Either<PdfiumKtFErrors, PdfPageKtF> =
wrapEither(dispatcher) {
PdfPageKtF(document.openPage(pageIndex), dispatcher)
}
/**
* suspend version of [PdfDocument.openPages]
*/
suspend fun openPages(
fromIndex: Int,
toIndex: Int,
): Either<PdfiumKtFErrors, List<PdfPageKtF>> =
wrapEither(dispatcher) {
document.openPages(fromIndex, toIndex).map { PdfPageKtF(it, dispatcher) }
}
/**
* suspend version of [PdfDocument.renderPages]
*/
@Suppress("LongParameterList", "ComplexMethod", "ComplexCondition")
suspend fun renderPages(
surface: Surface?,
pages: List<PdfPageKtF>,
matrices: List<Matrix>,
clipRects: List<RectF>,
renderAnnot: Boolean = false,
textMask: Boolean = false,
canvasColor: Int = 0xFF848484.toInt(),
pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
renderCoroutinesDispatcher: CoroutineDispatcher,
): Boolean {
return withContext(renderCoroutinesDispatcher) {
return@withContext surface
?.let {
document.renderPages(
surface,
pages.map { page -> page.page },
matrices,
clipRects,
renderAnnot,
textMask,
canvasColor,
pageBackgroundColor,
)
} ?: false
}
}
/**
* suspend version of [PdfDocument.deletePage]
*/
suspend fun deletePage(pageIndex: Int): Unit =
withContext(dispatcher) {
document.deletePage(pageIndex)
}
/**
* suspend version of [PdfDocument.getDocumentMeta]
*/
suspend fun getDocumentMeta(): Either<PdfiumKtFErrors, PdfDocument.Meta> =
wrapEither(dispatcher) {
document.getDocumentMeta()
}
/**
* suspend version of [PdfDocument.getTableOfContents]
*/
suspend fun getTableOfContents(): Either<PdfiumKtFErrors, List<PdfDocument.Bookmark>> =
wrapEither(dispatcher) {
document.getTableOfContents()
}
/**
* suspend version of [PdfDocument.openTextPages]
*/
suspend fun openTextPages(
fromIndex: Int,
toIndex: Int,
): Either<PdfiumKtFErrors, List<PdfTextPageKtF>> =
wrapEither(dispatcher) {
document.openTextPages(fromIndex, toIndex).map { PdfTextPageKtF(it, dispatcher) }
}
/**
* suspend version of [PdfDocument.saveAsCopy]
*/
suspend fun saveAsCopy(callback: PdfWriteCallback): Either<PdfiumKtFErrors, Boolean> =
wrapEither(dispatcher) {
document.saveAsCopy(callback)
}
/**
* Close the document
* @throws IllegalArgumentException if document is closed
*/
override fun close() {
document.close()
}
fun safeClose(): Either<PdfiumKtFErrors, Boolean> =
Either
.catch {
document.close()
true
}.mapLeft { exceptionToPdfiumKtFError(it) }
}

View file

@ -0,0 +1,384 @@
@file:Suppress("unused")
package io.legere.pdfiumandroid.arrow
import android.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.view.Surface
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import io.legere.pdfiumandroid.Logger
import io.legere.pdfiumandroid.PdfDocument
import io.legere.pdfiumandroid.PdfPage
import io.legere.pdfiumandroid.PdfiumCore
import io.legere.pdfiumandroid.util.Size
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.Closeable
/**
* PdfPageKtF represents a single page of a PDF file.
* @property page the [PdfPage] to wrap
* @property dispatcher the [CoroutineDispatcher] to use for suspending calls
*/
@Suppress("TooManyFunctions")
class PdfPageKtF(
val page: PdfPage,
private val dispatcher: CoroutineDispatcher,
) : Closeable {
/**
* Open a text page
* @throws IllegalArgumentException if document is closed or the page cannot be loaded
*/
suspend fun openTextPage(): Either<PdfiumKtFErrors, PdfTextPageKtF> =
wrapEither(dispatcher) {
PdfTextPageKtF(page.openTextPage(), dispatcher)
}
/**
* suspend version of [PdfPage.getPageWidth]
*/
suspend fun getPageWidth(screenDpi: Int): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.getPageWidth(screenDpi)
}
/**
* suspend version of [PdfPage.getPageHeight]
*/
suspend fun getPageHeight(screenDpi: Int): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.getPageHeight(screenDpi)
}
/**
* suspend version of [PdfPage.getPageWidthPoint]
*/
suspend fun getPageWidthPoint(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.getPageWidthPoint()
}
/**
* suspend version of [PdfPage.getPageHeightPoint]
*/
suspend fun getPageHeightPoint(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.getPageHeightPoint()
}
/**
* suspend version of [PdfPage.getPageMatrix]
*/
suspend fun getPageMatrix(): Either<PdfiumKtFErrors, Matrix> =
wrapEither(dispatcher) {
page.getPageMatrix() ?: error("Page matrix is null")
}
/**
* suspend version of [PdfPage.getPageRotation]
*/
suspend fun getPageRotation(): Either<PdfiumKtFErrors, Int> =
withContext(dispatcher) {
Either
.catch {
val rotation = page.getPageRotation()
if (rotation < 0) {
error("Invalid rotation: $rotation")
}
rotation
}.mapLeft {
exceptionToPdfiumKtFError(it)
}
}
/**
* suspend version of [PdfPage.getPageCropBox]
*/
suspend fun getPageCropBox(): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.getPageCropBox()
}
/**
* suspend version of [PdfPage.getPageMediaBox]
*/
suspend fun getPageMediaBox(): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.getPageMediaBox()
}
/**
* suspend version of [PdfPage.getPageBleedBox]
*/
suspend fun getPageBleedBox(): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.getPageBleedBox()
}
/**
* suspend version of [PdfPage.getPageTrimBox]
*/
suspend fun getPageTrimBox(): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.getPageTrimBox()
}
/**
* suspend version of [PdfPage.getPageArtBox]
*/
suspend fun getPageArtBox(): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.getPageArtBox()
}
/**
* suspend version of [PdfPage.getPageBoundingBox]
*/
suspend fun getPageBoundingBox(): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.getPageBoundingBox()
}
/**
* suspend version of [PdfPage.getPageSize]
*/
suspend fun getPageSize(screenDpi: Int): Either<PdfiumKtFErrors, Size> =
wrapEither(dispatcher) {
page.getPageSize(screenDpi)
}
/**
* suspend version of [PdfPage.renderPage]
*/
@Suppress("LongParameterList", "ComplexCondition")
suspend fun renderPage(
surface: Surface?,
startX: Int,
startY: Int,
drawSizeX: Int,
drawSizeY: Int,
canvasColor: Int = 0xFF848484.toInt(),
pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
renderCoroutinesDispatcher: CoroutineDispatcher,
): Either<PdfiumKtFErrors, Boolean> {
val sizes = IntArray(2)
val pointers = LongArray(2)
return withContext(renderCoroutinesDispatcher) {
surface?.let {
PdfPage.lockSurface(
it,
sizes,
pointers,
)
val nativeWindow = pointers[0]
val bufferPtr = pointers[1]
val surfaceWidth = sizes[0]
val surfaceHeight = sizes[1]
Logger.d(
"PdfPageKtF",
"page: ${page.pageIndex}, surfaceWidth: $surfaceWidth, " +
"surfaceHeight: $surfaceHeight, nativeWindow: $nativeWindow, " +
"bufferPtr: $bufferPtr, nativeWindow: $nativeWindow",
)
if (bufferPtr == 0L || bufferPtr == -1L || nativeWindow == 0L || nativeWindow == -1L) {
PdfiumKtFErrors.ConstraintError.left()
}
val result =
page.renderPage(
bufferPtr,
startX,
startY,
drawSizeX,
drawSizeY,
canvasColor = canvasColor,
pageBackgroundColor = pageBackgroundColor,
)
PdfPage.unlockSurface(longArrayOf(nativeWindow, bufferPtr))
if (!result) {
PdfiumKtFErrors.ConstraintError.left()
}
true.right()
} ?: PdfiumKtFErrors.ConstraintError.left()
}
}
/**
* suspend version of [PdfPage.renderPage]
*/
@Suppress("LongParameterList")
suspend fun renderPage(
surface: Surface?,
matrix: Matrix,
clipRect: RectF,
renderAnnot: Boolean = false,
textMask: Boolean = false,
canvasColor: Int = 0xFF848484.toInt(),
pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
renderCoroutinesDispatcher: CoroutineDispatcher,
): Either<PdfiumKtFErrors, Boolean> {
return PdfiumCore.surfaceMutex.withLock {
withContext(renderCoroutinesDispatcher) {
return@withContext surface?.let {
val retValue =
page.renderPage(
surface,
matrix,
clipRect,
renderAnnot,
textMask,
canvasColor,
pageBackgroundColor,
)
if (!retValue) {
PdfiumKtFErrors.ConstraintError.left()
} else {
true.right()
}
} ?: PdfiumKtFErrors.ConstraintError.left()
}
}
}
@Suppress("LongParameterList")
/**
* suspend version of [PdfPage.renderPageBitmap]
*/
suspend fun renderPageBitmap(
bitmap: Bitmap,
startX: Int,
startY: Int,
drawSizeX: Int,
drawSizeY: Int,
renderAnnot: Boolean = false,
textMask: Boolean = false,
canvasColor: Int = 0xFF848484.toInt(),
pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
): Either<PdfiumKtFErrors, Boolean> =
wrapEither(dispatcher) {
page.renderPageBitmap(
bitmap,
startX,
startY,
drawSizeX,
drawSizeY,
renderAnnot,
textMask,
canvasColor,
pageBackgroundColor,
)
true
}
@Suppress("LongParameterList")
/**
* suspend version of [PdfPage.renderPageBitmap]
*/
suspend fun renderPageBitmap(
bitmap: Bitmap?,
matrix: Matrix,
clipRect: RectF,
renderAnnot: Boolean = false,
textMask: Boolean = false,
canvasColor: Int = 0xFF848484.toInt(),
pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
): Either<PdfiumKtFErrors, Boolean> =
wrapEither(dispatcher) {
page.renderPageBitmap(bitmap, matrix, clipRect, renderAnnot, textMask, canvasColor, pageBackgroundColor)
true
}
/**
* suspend version of [PdfPage.getPageLinks]
*/
suspend fun getPageLinks(): Either<PdfiumKtFErrors, List<PdfDocument.Link>> =
wrapEither(dispatcher) {
page.getPageLinks()
}
/**
* suspend version of [PdfPage.mapPageCoordsToDevice]
*/
@Suppress("LongParameterList")
suspend fun mapPageCoordsToDevice(
startX: Int,
startY: Int,
sizeX: Int,
sizeY: Int,
rotate: Int,
pageX: Double,
pageY: Double,
): Either<PdfiumKtFErrors, Point> =
wrapEither(dispatcher) {
page.mapPageCoordsToDevice(startX, startY, sizeX, sizeY, rotate, pageX, pageY)
}
@Suppress("LongParameterList")
/**
* suspend version of [PdfPage.mapDeviceCoordsToPage]
*/
suspend fun mapDeviceCoordsToPage(
startX: Int,
startY: Int,
sizeX: Int,
sizeY: Int,
rotate: Int,
deviceX: Int,
deviceY: Int,
): Either<PdfiumKtFErrors, PointF> =
wrapEither(dispatcher) {
page.mapDeviceCoordsToPage(startX, startY, sizeX, sizeY, rotate, deviceX, deviceY)
}
@Suppress("LongParameterList")
/**
* suspend version of [PdfPage.mapRectToDevice]
*/
suspend fun mapRectToDevice(
startX: Int,
startY: Int,
sizeX: Int,
sizeY: Int,
rotate: Int,
coords: RectF,
): Either<PdfiumKtFErrors, Rect> =
wrapEither(dispatcher) {
page.mapRectToDevice(startX, startY, sizeX, sizeY, rotate, coords)
}
@Suppress("LongParameterList")
/**
* suspend version of [PdfPage.mapRectToPage]
*/
suspend fun mapRectToPage(
startX: Int,
startY: Int,
sizeX: Int,
sizeY: Int,
rotate: Int,
coords: Rect,
): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
page.mapRectToPage(startX, startY, sizeX, sizeY, rotate, coords)
}
/**
* Closes the page
*/
override fun close() {
page.close()
}
fun safeClose(): Either<PdfiumKtFErrors, Boolean> =
Either
.catch {
page.close()
true
}.mapLeft { exceptionToPdfiumKtFError(it) }
}

View file

@ -0,0 +1,47 @@
package io.legere.pdfiumandroid.arrow
import android.graphics.RectF
import arrow.core.Either
import io.legere.pdfiumandroid.PdfPageLink
import kotlinx.coroutines.CoroutineDispatcher
import java.io.Closeable
class PdfPageLinkKtF(
val pageLink: PdfPageLink,
private val dispatcher: CoroutineDispatcher,
) : Closeable {
suspend fun countWebLinks(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
pageLink.countWebLinks()
}
suspend fun getURL(
index: Int,
length: Int,
): Either<PdfiumKtFErrors, String?> =
wrapEither(dispatcher) {
pageLink.getURL(index, length)
}
suspend fun countRects(index: Int): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
pageLink.countRects(index)
}
suspend fun getRect(
linkIndex: Int,
rectIndex: Int,
): Either<PdfiumKtFErrors, RectF> =
wrapEither(dispatcher) {
pageLink.getRect(linkIndex, rectIndex)
}
suspend fun getTextRange(index: Int): Either<PdfiumKtFErrors, Pair<Int, Int>> =
wrapEither(dispatcher) {
pageLink.getTextRange(index)
}
override fun close() {
pageLink.close()
}
}

View file

@ -0,0 +1,149 @@
@file:Suppress("unused")
package io.legere.pdfiumandroid.arrow
import android.graphics.RectF
import arrow.core.Either
import io.legere.pdfiumandroid.FindFlags
import io.legere.pdfiumandroid.PdfTextPage
import io.legere.pdfiumandroid.WordRangeRect
import kotlinx.coroutines.CoroutineDispatcher
import java.io.Closeable
/**
* PdfTextPageKtF represents a single text page of a PDF file.
* @property page the [PdfTextPage] to wrap
* @property dispatcher the [CoroutineDispatcher] to use for suspending calls
*/
@Suppress("TooManyFunctions")
class PdfTextPageKtF(
val page: PdfTextPage,
private val dispatcher: CoroutineDispatcher,
) : Closeable {
/**
* suspend version of [PdfTextPage.textPageCountChars]
*/
suspend fun textPageCountChars(): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.textPageCountChars()
}
/**
* suspend version of [PdfTextPage.textPageGetText]
*/
suspend fun textPageGetText(
startIndex: Int,
length: Int,
): Either<PdfiumKtFErrors, String?> =
wrapEither(dispatcher) {
page.textPageGetText(startIndex, length)
}
/**
* suspend version of [PdfTextPage.textPageGetUnicode]
*/
suspend fun textPageGetUnicode(index: Int): Either<PdfiumKtFErrors, Char> =
wrapEither(dispatcher) {
page.textPageGetUnicode(index)
}
/**
* suspend version of [PdfTextPage.textPageGetCharBox]
*/
suspend fun textPageGetCharBox(index: Int): Either<PdfiumKtFErrors, RectF?> =
wrapEither(dispatcher) {
page.textPageGetCharBox(index)
}
/**
* suspend version of [PdfTextPage.textPageGetCharIndexAtPos]
*/
suspend fun textPageGetCharIndexAtPos(
x: Double,
y: Double,
xTolerance: Double,
yTolerance: Double,
): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.textPageGetCharIndexAtPos(x, y, xTolerance, yTolerance)
}
/**
* suspend version of [PdfTextPage.textPageCountRects]
*/
suspend fun textPageCountRects(
startIndex: Int,
count: Int,
): Either<PdfiumKtFErrors, Int> =
wrapEither(dispatcher) {
page.textPageCountRects(startIndex, count)
}
/**
* suspend version of [PdfTextPage.textPageGetRect]
*/
suspend fun textPageGetRect(rectIndex: Int): Either<PdfiumKtFErrors, RectF?> =
wrapEither(dispatcher) {
page.textPageGetRect(rectIndex)
}
/**
* suspend version of [PdfTextPage.textPageGetRectsForRanges]
*/
suspend fun textPageGetRectsForRanges(wordRanges: IntArray): Either<PdfiumKtFErrors, List<WordRangeRect>?> =
wrapEither(dispatcher) {
page.textPageGetRectsForRanges(wordRanges)
}
/**
* suspend version of [PdfTextPage.textPageGetBoundedText]
*/
suspend fun textPageGetBoundedText(
rect: RectF,
length: Int,
): Either<PdfiumKtFErrors, String?> =
wrapEither(dispatcher) {
page.textPageGetBoundedText(rect, length)
}
/**
* suspend version of [PdfTextPage.getFontSize]
*/
suspend fun getFontSize(charIndex: Int): Either<PdfiumKtFErrors, Double> =
wrapEither(dispatcher) {
page.getFontSize(charIndex)
}
suspend fun findStart(
findWhat: String,
flags: Set<FindFlags>,
startIndex: Int,
): Either<PdfiumKtFErrors, FindResultKtF> =
wrapEither(dispatcher) {
val findResult = page.findStart(findWhat, flags, startIndex)
if (findResult == null) {
error("findResult is null")
} else {
FindResultKtF(findResult, dispatcher)
}
}
suspend fun loadWebLink(): Either<PdfiumKtFErrors, PdfPageLinkKtF> =
wrapEither(dispatcher) {
PdfPageLinkKtF(page.loadWebLink(), dispatcher)
}
/**
* Close the page and free all resources.
*/
override fun close() {
page.close()
}
fun safeClose(): Either<PdfiumKtFErrors, Boolean> =
Either
.catch {
page.close()
true
}.mapLeft { exceptionToPdfiumKtFError(it) }
}

View file

@ -0,0 +1,18 @@
package io.legere.pdfiumandroid.arrow
import arrow.core.Either
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
suspend inline fun <reified T> wrapEither(
dispatcher: CoroutineDispatcher,
crossinline block: () -> T,
): Either<PdfiumKtFErrors, T> =
withContext(dispatcher) {
Either
.catch {
block()
}.mapLeft {
exceptionToPdfiumKtFError(it)
}
}

View file

@ -0,0 +1,79 @@
@file:Suppress("unused")
package io.legere.pdfiumandroid.arrow
import android.os.ParcelFileDescriptor
import arrow.core.Either
import io.legere.pdfiumandroid.PdfiumCore
import io.legere.pdfiumandroid.PdfiumSource
import io.legere.pdfiumandroid.util.Config
import kotlinx.coroutines.CoroutineDispatcher
/**
* PdfiumCoreKtF is the main entry-point for access to the PDFium API.
* @property dispatcher the [CoroutineDispatcher] to use for suspending calls
* @constructor create a [PdfiumCoreKtF] from a [PdfiumCore]
*/
class PdfiumCoreKtF(
private val dispatcher: CoroutineDispatcher,
config: Config = Config(),
) {
private val coreInternal = PdfiumCore(config = config)
/**
* suspend version of [PdfiumCore.newDocument]
*/
suspend fun newDocument(fd: ParcelFileDescriptor): Either<PdfiumKtFErrors, PdfDocumentKtF> =
wrapEither(dispatcher) {
PdfDocumentKtF(coreInternal.newDocument(fd), dispatcher)
}
/**
* suspend version of [PdfiumCore.newDocument]
*/
suspend fun newDocument(
fd: ParcelFileDescriptor,
password: String?,
): Either<PdfiumKtFErrors, PdfDocumentKtF> =
wrapEither(dispatcher) {
PdfDocumentKtF(coreInternal.newDocument(fd, password), dispatcher)
}
/**
* suspend version of [PdfiumCore.newDocument]
*/
suspend fun newDocument(data: ByteArray?): Either<PdfiumKtFErrors, PdfDocumentKtF> =
wrapEither(dispatcher) {
PdfDocumentKtF(coreInternal.newDocument(data), dispatcher)
}
/**
* suspend version of [PdfiumCore.newDocument]
*/
suspend fun newDocument(
data: ByteArray?,
password: String?,
): Either<PdfiumKtFErrors, PdfDocumentKtF> =
wrapEither(dispatcher) {
PdfDocumentKtF(coreInternal.newDocument(data, password), dispatcher)
}
/**
* suspend version of [PdfiumCore.newDocument]
*/
suspend fun newDocument(data: PdfiumSource): Either<PdfiumKtFErrors, PdfDocumentKtF> =
wrapEither(dispatcher) {
PdfDocumentKtF(coreInternal.newDocument(data), dispatcher)
}
/**
* suspend version of [PdfiumCore.newDocument]
*/
suspend fun newDocument(
data: PdfiumSource,
password: String?,
): Either<PdfiumKtFErrors, PdfDocumentKtF> =
wrapEither(dispatcher) {
PdfDocumentKtF(coreInternal.newDocument(data, password), dispatcher)
}
}

View file

@ -0,0 +1,20 @@
package io.legere.pdfiumandroid.arrow
sealed class PdfiumKtFErrors {
data class RuntimeException(
val message: String,
) : PdfiumKtFErrors()
data class AlreadyClosed(
val message: String,
) : PdfiumKtFErrors()
data object ConstraintError : PdfiumKtFErrors()
}
fun exceptionToPdfiumKtFError(e: Throwable): PdfiumKtFErrors =
if (e is IllegalStateException && e.message?.contains("Already closed") == true) {
PdfiumKtFErrors.AlreadyClosed(e.message ?: "Unknown error")
} else {
PdfiumKtFErrors.RuntimeException(e.message ?: "Unknown error")
}