├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── drawable
│ │ │ ├── outline_file_open_24.xml
│ │ │ └── ic_launcher_background.xml
│ │ ├── xml
│ │ │ ├── backup_rules.xml
│ │ │ └── data_extraction_rules.xml
│ │ ├── values-night
│ │ │ └── themes.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ └── io
│ │ │ └── legere
│ │ │ └── pdfiumandroidkt
│ │ │ ├── App.kt
│ │ │ └── ui
│ │ │ ├── theme
│ │ │ ├── Color.kt
│ │ │ ├── Type.kt
│ │ │ └── Theme.kt
│ │ │ ├── PdfiumFetcher.kt
│ │ │ └── PdfViewer.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle.kts
├── pdfiumandroid
├── .gitignore
├── arrow
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── io
│ │ │ │ └── legere
│ │ │ │ └── pdfiumandroid
│ │ │ │ └── arrow
│ │ │ │ ├── PdfiumArrorwExt.kt
│ │ │ │ ├── PdfiumKtFErrors.kt
│ │ │ │ ├── FindResultKtF.kt
│ │ │ │ ├── PdfPageLinkKtF.kt
│ │ │ │ ├── PdfiumCoreKtF.kt
│ │ │ │ ├── PdfPageKtFCache.kt
│ │ │ │ ├── PdfTextPageKtF.kt
│ │ │ │ └── PdfDocumentKtF.kt
│ │ └── androidTest
│ │ │ ├── assets
│ │ │ ├── f01.pdf
│ │ │ └── pdf-test.pdf
│ │ │ └── java
│ │ │ └── io
│ │ │ └── legere
│ │ │ └── pdfiumandroid
│ │ │ └── arrow
│ │ │ ├── base
│ │ │ ├── ByteArrayPdfiumSource.kt
│ │ │ └── BasePDFTest.kt
│ │ │ ├── PdfiumCoreKtFTest.kt
│ │ │ ├── PdfPageLinkKtFTest.kt
│ │ │ └── PdfDocumentKtFTest.kt
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── gradle.properties
├── src
│ ├── androidTest
│ │ ├── assets
│ │ │ ├── f01.pdf
│ │ │ └── pdf-test.pdf
│ │ └── java
│ │ │ └── io
│ │ │ └── legere
│ │ │ └── pdfiumandroid
│ │ │ ├── base
│ │ │ ├── ByteArrayPdfiumSource.kt
│ │ │ └── BasePDFTest.kt
│ │ │ ├── PdfiumCoreTest.kt
│ │ │ ├── suspend
│ │ │ ├── PdfiumCoreKtTest.kt
│ │ │ ├── PdfPageLinkKtTest.kt
│ │ │ ├── PdfDocumentKtTest.kt
│ │ │ └── PdfTextPageKtTest.kt
│ │ │ ├── PdfPageLinkTest.kt
│ │ │ └── PdfDocumentTest.kt
│ ├── main
│ │ ├── jniLibs
│ │ │ ├── x86
│ │ │ │ └── libpdfium.so
│ │ │ ├── x86_64
│ │ │ │ └── libpdfium.so
│ │ │ ├── arm64-v8a
│ │ │ │ └── libpdfium.so
│ │ │ └── armeabi-v7a
│ │ │ │ └── libpdfium.so
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── legere
│ │ │ │ └── pdfiumandroid
│ │ │ │ ├── PdfPasswordException.kt
│ │ │ │ ├── util
│ │ │ │ ├── Size.kt
│ │ │ │ ├── InitLock.kt
│ │ │ │ ├── Config.kt
│ │ │ │ ├── PdfiumNativeSourceBridge.kt
│ │ │ │ └── PdfPageCacheBase.kt
│ │ │ │ ├── PdfWriteCallback.kt
│ │ │ │ ├── PdfiumSource.kt
│ │ │ │ ├── suspend
│ │ │ │ ├── FindResultKt.kt
│ │ │ │ ├── PdfPageLinkKt.kt
│ │ │ │ ├── PdfPageSuspendCacheBase.kt
│ │ │ │ ├── PdfiumCoreKt.kt
│ │ │ │ ├── PdfPageKtCache.kt
│ │ │ │ ├── PdfTextPageKt.kt
│ │ │ │ └── PdfDocumentKt.kt
│ │ │ │ ├── FindResult.kt
│ │ │ │ ├── Logger.kt
│ │ │ │ ├── PdfPageCache.kt
│ │ │ │ └── PdfPageLink.kt
│ │ └── cpp
│ │ │ ├── util.h
│ │ │ ├── include
│ │ │ ├── fpdf_catalog.h
│ │ │ ├── fpdf_flatten.h
│ │ │ ├── fpdf_searchex.h
│ │ │ ├── fpdf_thumbnail.h
│ │ │ ├── cpp
│ │ │ │ ├── fpdf_scopers.h
│ │ │ │ └── fpdf_deleters.h
│ │ │ ├── utils
│ │ │ │ ├── Errors.h
│ │ │ │ └── Mutex.h
│ │ │ ├── fpdf_javascript.h
│ │ │ ├── fpdf_save.h
│ │ │ ├── fpdf_ext.h
│ │ │ ├── fpdf_ppo.h
│ │ │ └── fpdf_fwlevent.h
│ │ │ └── CMakeLists.txt
│ └── test
│ │ └── java
│ │ └── io
│ │ └── legere
│ │ └── pdfiumandroid
│ │ └── util
│ │ └── PdfiumNativeSourceBridgeTest.kt
├── consumer-rules.pro
└── proguard-rules.pro
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .idea
└── kotlinc.xml
├── settings.gradle.kts
├── .github
└── workflows
│ ├── android.yml
│ └── gradle-publish.yml
├── check
├── deploy.sh
├── gradle.properties
├── LICENSE
├── gradlew.bat
├── README.md
├── .gitignore
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/pdfiumandroid/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pdfiumandroid/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=pdfiumandroid
2 | POM_ARTIFACT_ID=pdfiumandroid
3 | POM_PACKAGING=aar
4 |
5 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=pdfiumandroid
2 | POM_ARTIFACT_ID=pdfiumandroid
3 | POM_PACKAGING=aar
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/assets/f01.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/src/androidTest/assets/f01.pdf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/jniLibs/x86/libpdfium.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/src/main/jniLibs/x86/libpdfium.so
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/assets/pdf-test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/src/androidTest/assets/pdf-test.pdf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/assets/f01.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/arrow/src/androidTest/assets/f01.pdf
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/jniLibs/x86_64/libpdfium.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/src/main/jniLibs/x86_64/libpdfium.so
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/jniLibs/arm64-v8a/libpdfium.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/src/main/jniLibs/arm64-v8a/libpdfium.so
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/assets/pdf-test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/arrow/src/androidTest/assets/pdf-test.pdf
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/jniLibs/armeabi-v7a/libpdfium.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johngray1965/PdfiumAndroidKt/HEAD/pdfiumandroid/src/main/jniLibs/armeabi-v7a/libpdfium.so
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PdfiumAndroidKt
3 | MainActivity
4 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 06 14:34:08 EDT 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/PdfPasswordException.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid
4 |
5 | import java.io.IOException
6 |
7 | /**
8 | * PdfPasswordException is thrown when a password is required to open a document
9 | */
10 | class PdfPasswordException(
11 | msg: String? = null,
12 | ) : IOException(msg)
13 |
--------------------------------------------------------------------------------
/app/src/main/java/io/legere/pdfiumandroidkt/App.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroidkt
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 | import timber.log.Timber
6 |
7 | @HiltAndroidApp
8 | class App : Application() {
9 | override fun onCreate() {
10 | super.onCreate()
11 | Timber.plant(Timber.DebugTree())
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/util/Size.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.util
2 |
3 | import androidx.annotation.Keep
4 |
5 | /**
6 | * Size is a simple value class that represents a width and height.
7 | * @property width the width
8 | * @property height the height
9 | */
10 | @Keep
11 | data class Size(
12 | val width: Int,
13 | val height: Int,
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/io/legere/pdfiumandroidkt/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("MagicNumber")
2 |
3 | package io.legere.pdfiumandroidkt.ui.theme
4 |
5 | import androidx.compose.ui.graphics.Color
6 |
7 | val Purple80 = Color(0xFFD0BCFF)
8 | val PurpleGrey80 = Color(0xFFCCC2DC)
9 | val Pink80 = Color(0xFFEFB8C8)
10 |
11 | val Purple40 = Color(0xFF6650a4)
12 | val PurpleGrey40 = Color(0xFF625b71)
13 | val Pink40 = Color(0xFF7D5260)
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/outline_file_open_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/PdfWriteCallback.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | /**
4 | * PdfWriteCallback is the calback interface for saveAsCopy
5 | */
6 | interface PdfWriteCallback {
7 | /**
8 | * WriteBlock is called by native code to write a block of data
9 | * @param data the data to write
10 | *
11 | * note: The name need to be exactly what it is.
12 | * The native call is looking for is as WriteBlock
13 | *
14 | */
15 | @Suppress("FunctionNaming", "FunctionName")
16 | fun WriteBlock(data: ByteArray?): Int
17 | }
18 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfiumArrorwExt.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | import arrow.core.Either
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.withContext
6 |
7 | suspend inline fun wrapEither(
8 | dispatcher: CoroutineDispatcher,
9 | crossinline block: () -> T,
10 | ): Either =
11 | withContext(dispatcher) {
12 | Either
13 | .catch {
14 | block()
15 | }.mapLeft {
16 | exceptionToPdfiumKtFError(it)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/util/InitLock.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.util
2 |
3 | import java.util.concurrent.Semaphore
4 |
5 | class InitLock {
6 | private val semaphore = Semaphore(0)
7 | private var isInitialized = false
8 |
9 | fun markReady() {
10 | isInitialized = true
11 | semaphore.release()
12 | }
13 |
14 | // We use a mutex to make sure only the
15 | // first thread waits on the semaphore
16 | @Synchronized
17 | fun waitForReady() {
18 | if (!isInitialized) {
19 | semaphore.acquire()
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | @Suppress("UnstableApiUsage")
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | @Suppress("UnstableApiUsage")
12 | repositories {
13 | google()
14 | mavenLocal()
15 | mavenCentral()
16 | // maven(url = "https://s01.oss.sonatype.org/content/groups/staging/")
17 | }
18 | }
19 |
20 | rootProject.name = "pdfiumandroid"
21 | include(":app")
22 | include(":pdfiumandroid")
23 | include(":pdfiumandroid:arrow")
24 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: set up JDK 17
17 | uses: actions/setup-java@v4
18 | with:
19 | java-version: '17'
20 | distribution: 'temurin'
21 | cache: gradle
22 | - name: Setup Android SDK
23 | uses: android-actions/setup-android@v2
24 | - name: Grant execute permission for gradlew
25 | run: chmod +x gradlew
26 | - name: Build with Gradle
27 | run: ./gradlew build
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/io/legere/pdfiumandroidkt/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroidkt.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography =
11 | Typography(
12 | bodyLarge =
13 | TextStyle(
14 | fontFamily = FontFamily.Default,
15 | fontWeight = FontWeight.Normal,
16 | fontSize = 16.sp,
17 | lineHeight = 24.sp,
18 | letterSpacing = 0.5.sp,
19 | ),
20 | )
21 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfiumKtFErrors.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | sealed class PdfiumKtFErrors {
4 | data class RuntimeException(
5 | val message: String,
6 | ) : PdfiumKtFErrors()
7 |
8 | data class AlreadyClosed(
9 | val message: String,
10 | ) : PdfiumKtFErrors()
11 |
12 | data object ConstraintError : PdfiumKtFErrors()
13 | }
14 |
15 | fun exceptionToPdfiumKtFError(e: Throwable): PdfiumKtFErrors =
16 | if (e is IllegalStateException && e.message?.contains("Already closed") == true) {
17 | PdfiumKtFErrors.AlreadyClosed(e.message ?: "Unknown error")
18 | } else {
19 | PdfiumKtFErrors.RuntimeException(e.message ?: "Unknown error")
20 | }
21 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/util.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by John Gray on 6/6/23.
3 | //
4 |
5 | #ifndef PDFIUMANDROIDKT_UTIL_H
6 | #define PDFIUMANDROIDKT_UTIL_H
7 |
8 | #include
9 | extern "C" {
10 | #include
11 | }
12 |
13 | #include
14 |
15 | #define JNI_FUNC(retType, bindClass, name) JNIEXPORT retType JNICALL Java_com_shockwave_pdfium_##bindClass##_##name
16 | #define JNI_ARGS JNIEnv *env, jobject thiz
17 |
18 | #define LOG_TAG "jniPdfium"
19 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
20 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
21 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
22 |
23 | #endif //PDFIUMANDROIDKT_UTIL_H
24 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/base/ByteArrayPdfiumSource.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.base
2 |
3 | import io.legere.pdfiumandroid.PdfiumSource
4 |
5 | class ByteArrayPdfiumSource(
6 | private val array: ByteArray,
7 | ) : PdfiumSource {
8 | override val length: Long
9 | get() = array.size.toLong()
10 |
11 | override fun read(
12 | position: Long,
13 | buffer: ByteArray,
14 | size: Int,
15 | ): Int {
16 | array.copyInto(
17 | destination = buffer,
18 | destinationOffset = 0,
19 | startIndex = position.toInt(),
20 | endIndex = position.toInt() + size,
21 | )
22 | return size
23 | }
24 |
25 | override fun close() {
26 | // nothing to close
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/java/io/legere/pdfiumandroid/arrow/base/ByteArrayPdfiumSource.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow.base
2 |
3 | import io.legere.pdfiumandroid.PdfiumSource
4 |
5 | class ByteArrayPdfiumSource(
6 | private val array: ByteArray,
7 | ) : PdfiumSource {
8 | override val length: Long
9 | get() = array.size.toLong()
10 |
11 | override fun read(
12 | position: Long,
13 | buffer: ByteArray,
14 | size: Int,
15 | ): Int {
16 | array.copyInto(
17 | destination = buffer,
18 | destinationOffset = 0,
19 | startIndex = position.toInt(),
20 | endIndex = position.toInt() + size,
21 | )
22 | return size
23 | }
24 |
25 | override fun close() {
26 | // nothing to close
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/util/Config.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.util
2 |
3 | import androidx.annotation.Keep
4 | import io.legere.pdfiumandroid.DefaultLogger
5 | import io.legere.pdfiumandroid.LoggerInterface
6 |
7 | var pdfiumConfig = Config()
8 |
9 | @Keep
10 | enum class AlreadyClosedBehavior {
11 | EXCEPTION,
12 | IGNORE,
13 | }
14 |
15 | @Keep
16 | data class Config(
17 | val logger: LoggerInterface = DefaultLogger(),
18 | val alreadyClosedBehavior: AlreadyClosedBehavior = AlreadyClosedBehavior.EXCEPTION,
19 | )
20 |
21 | fun handleAlreadyClosed(isClosed: Boolean): Boolean {
22 | if (isClosed) {
23 | when (pdfiumConfig.alreadyClosedBehavior) {
24 | AlreadyClosedBehavior.EXCEPTION -> error("Already closed")
25 | AlreadyClosedBehavior.IGNORE ->
26 | pdfiumConfig.logger.d(
27 | "PdfiumCore",
28 | "Already closed",
29 | )
30 | }
31 | }
32 | return isClosed
33 | }
34 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/java/io/legere/pdfiumandroid/arrow/base/BasePDFTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow.base
2 |
3 | import android.graphics.RectF
4 | import android.util.Log
5 | import androidx.test.platform.app.InstrumentationRegistry
6 |
7 | @Suppress("unused")
8 | open class BasePDFTest {
9 | // set to true to skip tests that are not implemented yet
10 | // set to false to force unimplemented tests to fail
11 | val notImplementedAssetValue = false
12 |
13 | val noResultRect = RectF(-1f, -1f, -1f, -1f)
14 |
15 | fun getPdfBytes(filename: String): ByteArray? {
16 | val appContext = InstrumentationRegistry.getInstrumentation().context
17 | val assetManager = appContext.assets
18 | try {
19 | val input = assetManager.open(filename)
20 | return input.readBytes()
21 | } catch (e: Exception) {
22 | Log.e(BasePDFTest::class.simpleName, "Ugh", e)
23 | }
24 | assetManager.close()
25 | return null
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/base/BasePDFTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.base
2 |
3 | import android.graphics.RectF
4 | import android.util.Log
5 | import androidx.test.platform.app.InstrumentationRegistry
6 | import io.legere.pdfiumandroid.PdfiumCoreTest
7 |
8 | @Suppress("unused")
9 | open class BasePDFTest {
10 | // set to true to skip tests that are not implemented yet
11 | // set to false to force unimplemented tests to fail
12 | val notImplementedAssetValue = false
13 |
14 | val noResultRect = RectF(-1f, -1f, -1f, -1f)
15 |
16 | fun getPdfBytes(filename: String): ByteArray? {
17 | val appContext = InstrumentationRegistry.getInstrumentation().context
18 | val assetManager = appContext.assets
19 | try {
20 | val input = assetManager.open(filename)
21 | return input.readBytes()
22 | } catch (e: Exception) {
23 | Log.e(PdfiumCoreTest::class.simpleName, "Ugh", e)
24 | }
25 | assetManager.close()
26 | return null
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/PdfiumSource.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | /**
4 | * An interface for providing custom data source to Pdfium.
5 | */
6 | interface PdfiumSource : AutoCloseable {
7 | /**
8 | * Data length, in bytes
9 | */
10 | val length: Long
11 |
12 | /**
13 | * Read data from the source.
14 | *
15 | * The position and size will never go out of range of the data source [length].
16 | * It may be possible for Pdfium to call this function multiple times for the same position.
17 | *
18 | * @param position byte offset from the beginning of the data source
19 | * @param buffer the buffer to read data into. Always have enough space to read [size] bytes.
20 | * It should be filled starting from index 0.
21 | * @param size the number of bytes to read. Never 0.
22 | * @return number of bytes that was read, or a negative value to indicate an error.
23 | */
24 | fun read(
25 | position: Long,
26 | buffer: ByteArray,
27 | size: Int,
28 | ): Int
29 | }
30 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/PdfiumCoreTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.google.common.truth.Truth.assertThat
5 | import io.legere.pdfiumandroid.base.BasePDFTest
6 | import io.legere.pdfiumandroid.base.ByteArrayPdfiumSource
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | @RunWith(AndroidJUnit4::class)
11 | class PdfiumCoreTest : BasePDFTest() {
12 | @Test
13 | fun newDocument() {
14 | val pdfBytes = getPdfBytes("f01.pdf")
15 |
16 | assertThat(pdfBytes).isNotNull()
17 |
18 | val pdfiumCore = PdfiumCore()
19 | val pdfDocument = pdfiumCore.newDocument(pdfBytes)
20 |
21 | assertThat(pdfDocument).isNotNull()
22 | }
23 |
24 | @Test
25 | fun newDocumentWithCustomSource() {
26 | val pdfBytes = getPdfBytes("f01.pdf")
27 |
28 | assertThat(pdfBytes).isNotNull()
29 |
30 | val pdfiumCore = PdfiumCore()
31 | val pdfDocument = pdfiumCore.newDocument(ByteArrayPdfiumSource(pdfBytes!!))
32 |
33 | assertThat(pdfDocument).isNotNull()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/FindResultKt.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import io.legere.pdfiumandroid.FindResult
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.withContext
6 | import java.io.Closeable
7 |
8 | @Suppress("unused")
9 | class FindResultKt(
10 | private val findResult: FindResult,
11 | private val dispatcher: CoroutineDispatcher,
12 | ) : Closeable {
13 | suspend fun findNext(): Boolean =
14 | withContext(dispatcher) {
15 | findResult.findNext()
16 | }
17 |
18 | suspend fun findPrev(): Boolean =
19 | withContext(dispatcher) {
20 | findResult.findPrev()
21 | }
22 |
23 | suspend fun getSchResultIndex(): Int =
24 | withContext(dispatcher) {
25 | findResult.getSchResultIndex()
26 | }
27 |
28 | suspend fun getSchCount(): Int =
29 | withContext(dispatcher) {
30 | findResult.getSchCount()
31 | }
32 |
33 | suspend fun closeFind() {
34 | withContext(dispatcher) {
35 | findResult.closeFind()
36 | }
37 | }
38 |
39 | override fun close() {
40 | findResult.closeFind()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_catalog.h:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef PUBLIC_FPDF_CATALOG_H_
6 | #define PUBLIC_FPDF_CATALOG_H_
7 |
8 | // NOLINTNEXTLINE(build/include)
9 | #include "fpdfview.h"
10 |
11 | #ifdef __cplusplus
12 | extern "C" {
13 | #endif // __cplusplus
14 |
15 | // Experimental API.
16 | //
17 | // Determine if |document| represents a tagged PDF.
18 | //
19 | // For the definition of tagged PDF, See (see 10.7 "Tagged PDF" in PDF
20 | // Reference 1.7).
21 | //
22 | // document - handle to a document.
23 | //
24 | // Returns |true| iff |document| is a tagged PDF.
25 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
26 | FPDFCatalog_IsTagged(FPDF_DOCUMENT document);
27 |
28 | // Experimental API.
29 | // Sets the language of |document| to |language|.
30 | //
31 | // document - handle to a document.
32 | // language - the language to set to.
33 | //
34 | // Returns TRUE on success.
35 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
36 | FPDFCatalog_SetLanguage(FPDF_DOCUMENT document, FPDF_BYTESTRING language);
37 |
38 | #ifdef __cplusplus
39 | } // extern "C"
40 | #endif // __cplusplus
41 |
42 | #endif // PUBLIC_FPDF_CATALOG_H_
43 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/util/PdfiumNativeSourceBridge.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.util
2 |
3 | import io.legere.pdfiumandroid.Logger
4 | import io.legere.pdfiumandroid.PdfiumSource
5 |
6 | internal class PdfiumNativeSourceBridge(
7 | private val source: PdfiumSource,
8 | ) {
9 | private var buffer: ByteArray? = null
10 |
11 | @Suppress("TooGenericExceptionCaught")
12 | fun read(
13 | position: Long,
14 | size: Long,
15 | ): Int =
16 | try {
17 | require(size <= Int.MAX_VALUE) { "size is too large" }
18 | val trimmedSize = size.toInt()
19 |
20 | var buffer = buffer
21 | if (buffer == null || buffer.size < size) {
22 | buffer = ByteArray(trimmedSize).also { this.buffer = it }
23 | }
24 |
25 | val bytesRead = source.read(position, buffer, trimmedSize)
26 | // Pdfium expects 0 for error while Java/Kotlin usually return a negative value
27 | if (bytesRead <= 0) 0 else bytesRead
28 | } catch (t: Throwable) {
29 | // This is to prevent the exception to go to the native code level
30 | Logger.e("PdfiumNativeSourceBridge", t, "read failed")
31 | 0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/FindResultKtF.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | import arrow.core.Either
4 | import io.legere.pdfiumandroid.FindResult
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import java.io.Closeable
7 |
8 | @Suppress("unused")
9 | class FindResultKtF(
10 | private val findResult: FindResult,
11 | private val dispatcher: CoroutineDispatcher,
12 | ) : Closeable {
13 | suspend fun findNext(): Either =
14 | wrapEither(dispatcher) {
15 | findResult.findNext()
16 | }
17 |
18 | suspend fun findPrev(): Either =
19 | wrapEither(dispatcher) {
20 | findResult.findPrev()
21 | }
22 |
23 | suspend fun getSchResultIndex(): Either =
24 | wrapEither(dispatcher) {
25 | findResult.getSchResultIndex()
26 | }
27 |
28 | suspend fun getSchCount(): Either =
29 | wrapEither(dispatcher) {
30 | findResult.getSchCount()
31 | }
32 |
33 | suspend fun closeFind() {
34 | wrapEither(dispatcher) {
35 | findResult.closeFind()
36 | }
37 | }
38 |
39 | override fun close() {
40 | findResult.closeFind()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_flatten.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 |
7 | #ifndef PUBLIC_FPDF_FLATTEN_H_
8 | #define PUBLIC_FPDF_FLATTEN_H_
9 |
10 | // NOLINTNEXTLINE(build/include)
11 | #include "fpdfview.h"
12 |
13 | // Flatten operation failed.
14 | #define FLATTEN_FAIL 0
15 | // Flatten operation succeed.
16 | #define FLATTEN_SUCCESS 1
17 | // Nothing to be flattened.
18 | #define FLATTEN_NOTHINGTODO 2
19 |
20 | // Flatten for normal display.
21 | #define FLAT_NORMALDISPLAY 0
22 | // Flatten for print.
23 | #define FLAT_PRINT 1
24 |
25 | #ifdef __cplusplus
26 | extern "C" {
27 | #endif // __cplusplus
28 |
29 | // Flatten annotations and form fields into the page contents.
30 | //
31 | // page - handle to the page.
32 | // nFlag - One of the |FLAT_*| values denoting the page usage.
33 | //
34 | // Returns one of the |FLATTEN_*| values.
35 | //
36 | // Currently, all failures return |FLATTEN_FAIL| with no indication of the
37 | // cause.
38 | FPDF_EXPORT int FPDF_CALLCONV FPDFPage_Flatten(FPDF_PAGE page, int nFlag);
39 |
40 | #ifdef __cplusplus
41 | } // extern "C"
42 | #endif // __cplusplus
43 |
44 | #endif // PUBLIC_FPDF_FLATTEN_H_
45 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/suspend/PdfiumCoreKtTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.google.common.truth.Truth.assertThat
5 | import io.legere.pdfiumandroid.base.BasePDFTest
6 | import io.legere.pdfiumandroid.base.ByteArrayPdfiumSource
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.test.runTest
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 |
12 | @RunWith(AndroidJUnit4::class)
13 | class PdfiumCoreKtTest : BasePDFTest() {
14 | @Test
15 | fun newDocument() =
16 | runTest {
17 | val pdfBytes = getPdfBytes("f01.pdf")
18 |
19 | assertThat(pdfBytes).isNotNull()
20 |
21 | val pdfiumCore = PdfiumCoreKt(Dispatchers.Unconfined)
22 | val pdfDocument = pdfiumCore.newDocument(pdfBytes)
23 |
24 | assertThat(pdfDocument).isNotNull()
25 | }
26 |
27 | @Test
28 | fun newDocumentWitCustomSource() =
29 | runTest {
30 | val pdfBytes = getPdfBytes("f01.pdf")
31 |
32 | assertThat(pdfBytes).isNotNull()
33 |
34 | val pdfiumCore = PdfiumCoreKt(Dispatchers.Unconfined)
35 | val pdfDocument = pdfiumCore.newDocument(ByteArrayPdfiumSource(pdfBytes!!))
36 |
37 | assertThat(pdfDocument).isNotNull()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/PdfPageLinkKt.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import android.graphics.RectF
4 | import io.legere.pdfiumandroid.PdfPageLink
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.withContext
7 | import java.io.Closeable
8 |
9 | class PdfPageLinkKt(
10 | val pageLink: PdfPageLink,
11 | private val dispatcher: CoroutineDispatcher,
12 | ) : Closeable {
13 | suspend fun countWebLinks(): Int =
14 | withContext(dispatcher) {
15 | pageLink.countWebLinks()
16 | }
17 |
18 | suspend fun getURL(
19 | index: Int,
20 | length: Int,
21 | ): String? =
22 | withContext(dispatcher) {
23 | pageLink.getURL(index, length)
24 | }
25 |
26 | suspend fun countRects(index: Int): Int =
27 | withContext(dispatcher) {
28 | pageLink.countRects(index)
29 | }
30 |
31 | suspend fun getRect(
32 | linkIndex: Int,
33 | rectIndex: Int,
34 | ): RectF =
35 | withContext(dispatcher) {
36 | pageLink.getRect(linkIndex, rectIndex)
37 | }
38 |
39 | suspend fun getTextRange(index: Int): Pair =
40 | withContext(dispatcher) {
41 | pageLink.getTextRange(index)
42 | }
43 |
44 | override fun close() {
45 | pageLink.close()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/java/io/legere/pdfiumandroid/arrow/PdfiumCoreKtFTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.google.common.truth.Truth.assertThat
5 | import io.legere.pdfiumandroid.arrow.base.BasePDFTest
6 | import io.legere.pdfiumandroid.arrow.base.ByteArrayPdfiumSource
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.test.runTest
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 |
12 | @RunWith(AndroidJUnit4::class)
13 | class PdfiumCoreKtFTest : BasePDFTest() {
14 | @Test
15 | fun newDocument() =
16 | runTest {
17 | val pdfBytes = getPdfBytes("f01.pdf")
18 |
19 | assertThat(pdfBytes).isNotNull()
20 |
21 | val pdfiumCore = PdfiumCoreKtF(Dispatchers.Unconfined)
22 | val pdfDocument = pdfiumCore.newDocument(pdfBytes)
23 |
24 | assertThat(pdfDocument).isNotNull()
25 | }
26 |
27 | @Test
28 | fun newDocumentWithCustomSource() =
29 | runTest {
30 | val pdfBytes = getPdfBytes("f01.pdf")
31 |
32 | assertThat(pdfBytes).isNotNull()
33 |
34 | val pdfiumCore = PdfiumCoreKtF(Dispatchers.Unconfined)
35 | val pdfDocument = pdfiumCore.newDocument(ByteArrayPdfiumSource(pdfBytes!!))
36 |
37 | assertThat(pdfDocument).isNotNull()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_searchex.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 |
7 | #ifndef PUBLIC_FPDF_SEARCHEX_H_
8 | #define PUBLIC_FPDF_SEARCHEX_H_
9 |
10 | // NOLINTNEXTLINE(build/include)
11 | #include "fpdfview.h"
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif // __cplusplus
16 |
17 | // Get the character index in |text_page| internal character list.
18 | //
19 | // text_page - a text page information structure.
20 | // nTextIndex - index of the text returned from FPDFText_GetText().
21 | //
22 | // Returns the index of the character in internal character list. -1 for error.
23 | FPDF_EXPORT int FPDF_CALLCONV
24 | FPDFText_GetCharIndexFromTextIndex(FPDF_TEXTPAGE text_page, int nTextIndex);
25 |
26 | // Get the text index in |text_page| internal character list.
27 | //
28 | // text_page - a text page information structure.
29 | // nCharIndex - index of the character in internal character list.
30 | //
31 | // Returns the index of the text returned from FPDFText_GetText(). -1 for error.
32 | FPDF_EXPORT int FPDF_CALLCONV
33 | FPDFText_GetTextIndexFromCharIndex(FPDF_TEXTPAGE text_page, int nCharIndex);
34 |
35 | #ifdef __cplusplus
36 | } // extern "C"
37 | #endif // __cplusplus
38 |
39 | #endif // PUBLIC_FPDF_SEARCHEX_H_
40 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/FindResult.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | import java.io.Closeable
4 |
5 | @Suppress("TooManyFunctions")
6 | class FindResult(
7 | val handle: FindHandle,
8 | ) : Closeable {
9 | private external fun nativeFindNext(findHandle: Long): Boolean
10 |
11 | private external fun nativeFindPrev(findHandle: Long): Boolean
12 |
13 | private external fun nativeGetSchResultIndex(findHandle: Long): Int
14 |
15 | private external fun nativeGetSchCount(findHandle: Long): Int
16 |
17 | private external fun nativeCloseFind(findHandle: Long)
18 |
19 | fun findNext(): Boolean {
20 | synchronized(PdfiumCore.lock) {
21 | return nativeFindNext(handle)
22 | }
23 | }
24 |
25 | fun findPrev(): Boolean {
26 | synchronized(PdfiumCore.lock) {
27 | return nativeFindPrev(handle)
28 | }
29 | }
30 |
31 | fun getSchResultIndex(): Int {
32 | synchronized(PdfiumCore.lock) {
33 | return nativeGetSchResultIndex(handle)
34 | }
35 | }
36 |
37 | fun getSchCount(): Int {
38 | synchronized(PdfiumCore.lock) {
39 | return nativeGetSchCount(handle)
40 | }
41 | }
42 |
43 | fun closeFind() {
44 | synchronized(PdfiumCore.lock) {
45 | nativeCloseFind(handle)
46 | }
47 | }
48 |
49 | override fun close() {
50 | nativeCloseFind(handle)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/Logger.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | import android.util.Log
4 | import androidx.annotation.Keep
5 |
6 | // At the moment we only do debug log with message, or error log with message and throwable
7 | // in the future, we might expand this.
8 | @Keep
9 | interface LoggerInterface {
10 | fun d(
11 | tag: String,
12 | message: String?,
13 | )
14 |
15 | fun e(
16 | tag: String,
17 | t: Throwable?,
18 | message: String?,
19 | )
20 | }
21 |
22 | @Suppress("MemberNameEqualsClassName")
23 | object Logger : LoggerInterface {
24 | private var logger: LoggerInterface? = null
25 |
26 | override fun d(
27 | tag: String,
28 | message: String?,
29 | ) {
30 | logger?.d(tag, message)
31 | }
32 |
33 | override fun e(
34 | tag: String,
35 | t: Throwable?,
36 | message: String?,
37 | ) {
38 | logger?.e(tag, t, message)
39 | }
40 |
41 | fun setLogger(logger: LoggerInterface) {
42 | this.logger = logger
43 | }
44 | }
45 |
46 | class DefaultLogger : LoggerInterface {
47 | override fun d(
48 | tag: String,
49 | message: String?,
50 | ) {
51 | message?.let { Log.d(tag, message) }
52 | }
53 |
54 | override fun e(
55 | tag: String,
56 | t: Throwable?,
57 | message: String?,
58 | ) {
59 | Log.e(tag, message, t)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfPageLinkKtF.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | import android.graphics.RectF
4 | import arrow.core.Either
5 | import io.legere.pdfiumandroid.PdfPageLink
6 | import kotlinx.coroutines.CoroutineDispatcher
7 | import java.io.Closeable
8 |
9 | class PdfPageLinkKtF(
10 | val pageLink: PdfPageLink,
11 | private val dispatcher: CoroutineDispatcher,
12 | ) : Closeable {
13 | suspend fun countWebLinks(): Either =
14 | wrapEither(dispatcher) {
15 | pageLink.countWebLinks()
16 | }
17 |
18 | suspend fun getURL(
19 | index: Int,
20 | length: Int,
21 | ): Either =
22 | wrapEither(dispatcher) {
23 | pageLink.getURL(index, length)
24 | }
25 |
26 | suspend fun countRects(index: Int): Either =
27 | wrapEither(dispatcher) {
28 | pageLink.countRects(index)
29 | }
30 |
31 | suspend fun getRect(
32 | linkIndex: Int,
33 | rectIndex: Int,
34 | ): Either =
35 | wrapEither(dispatcher) {
36 | pageLink.getRect(linkIndex, rectIndex)
37 | }
38 |
39 | suspend fun getTextRange(index: Int): Either> =
40 | wrapEither(dispatcher) {
41 | pageLink.getTextRange(index)
42 | }
43 |
44 | override fun close() {
45 | pageLink.close()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/io/legere/pdfiumandroidkt/ui/PdfiumFetcher.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroidkt.ui
2 |
3 | import androidx.core.graphics.drawable.toDrawable
4 | import coil.ImageLoader
5 | import coil.decode.DataSource
6 | import coil.fetch.DrawableResult
7 | import coil.fetch.FetchResult
8 | import coil.fetch.Fetcher
9 | import coil.request.Options
10 | import io.legere.pdfiumandroidkt.MainViewModel
11 | import timber.log.Timber
12 |
13 | class PdfiumFetcher(
14 | private val data: PdfiumFetcherData,
15 | private val options: Options,
16 | ) : Fetcher {
17 | override suspend fun fetch(): FetchResult? {
18 | Timber.d("fetch: ${data.page}")
19 | val bitmap = data.viewModel.getPage(data.page, data.width, data.height)
20 | if (bitmap == null) {
21 | Timber.d("fetch: bitmap is null")
22 | return null
23 | }
24 | return DrawableResult(
25 | drawable = bitmap.toDrawable(options.context.resources),
26 | isSampled = false,
27 | dataSource = DataSource.MEMORY,
28 | )
29 | }
30 |
31 | class Factory : Fetcher.Factory {
32 | override fun create(
33 | data: PdfiumFetcherData,
34 | options: Options,
35 | imageLoader: ImageLoader,
36 | ): Fetcher = PdfiumFetcher(data, options)
37 | }
38 | }
39 |
40 | data class PdfiumFetcherData(
41 | val page: Int,
42 | val width: Int,
43 | val height: Int,
44 | val density: Int,
45 | val viewModel: MainViewModel,
46 | )
47 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/util/PdfPageCacheBase.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.util
2 |
3 | import com.google.common.cache.CacheBuilder
4 | import com.google.common.cache.CacheLoader
5 | import com.google.common.cache.LoadingCache
6 | import com.google.common.cache.RemovalListener
7 |
8 | private const val CACHE_SIZE = 64L
9 |
10 | /**
11 | * A thread-safe, concurrent LRU cache for PDF page objects.
12 | * This base class holds the core Guava LoadingCache logic.
13 | */
14 | abstract class PdfPageCacheBase(
15 | maxSize: Long = CACHE_SIZE,
16 | ) : AutoCloseable {
17 | private val removalListener =
18 | RemovalListener {
19 | it.value?.close()
20 | }
21 |
22 | private val pageCache: LoadingCache =
23 | CacheBuilder
24 | .newBuilder()
25 | .maximumSize(maxSize)
26 | .removalListener(removalListener)
27 | .build(
28 | CacheLoader.from { pageIndex ->
29 | openPageAndText(pageIndex)
30 | },
31 | )
32 |
33 | /**
34 | * Abstract method to be implemented by subclasses to open a page and its text page.
35 | */
36 | protected abstract fun openPageAndText(pageIndex: Int): H
37 |
38 | /**
39 | * Gets the page and text page holder from the cache, creating it if necessary.
40 | */
41 | fun get(pageIndex: Int): H = pageCache.get(pageIndex)
42 |
43 | /**
44 | * Closes all currently cached pages.
45 | */
46 | override fun close() {
47 | pageCache.invalidateAll()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.github/workflows/gradle-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
7 |
8 | name: Gradle Package
9 |
10 | on:
11 | release:
12 | types: [created]
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 | permissions:
19 | contents: read
20 | packages: write
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Set up JDK 11
25 | uses: actions/setup-java@v3
26 | with:
27 | java-version: '11'
28 | distribution: 'temurin'
29 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
30 | settings-path: ${{ github.workspace }} # location for the settings.xml file
31 |
32 | - name: Build with Gradle
33 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
34 | with:
35 | arguments: build
36 |
37 | # The USERNAME and TOKEN need to correspond to the credentials environment variables used in
38 | # the publishing section of your build.gradle
39 | - name: Publish to GitHub Packages
40 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
41 | with:
42 | arguments: publish
43 | env:
44 | USERNAME: ${{ github.actor }}
45 | TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 |
--------------------------------------------------------------------------------
/check:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | set -e
4 |
5 | OPTIND=1 # Reset in case getopts has been used previously in the shell.
6 | CLEAN=clean
7 |
8 | while getopts "h?cs" opt; do
9 | case "$opt" in
10 | h|\?)
11 | echo "Usage: $0 [-v] [-n name]"
12 | exit 0
13 | ;;
14 | c)
15 | ;;
16 | s) CLEAN=
17 | ;;
18 | esac
19 | done
20 |
21 | shift $((OPTIND-1))
22 |
23 | [ "${1:-}" = "--" ] && shift
24 |
25 | nohup java -jar adbserver-desktop.jar > /dev/null 2>&1 &
26 | serverPID=$!
27 |
28 | #./gradlew --continue clean detekt lint testDebugUnitTest reader:connectedDebugAndroidTest readerData:connectedDebugAndroidTest
29 | #./gradlew clean detekt lint testDebugUnitTest reader:connectedDebugAndroidTest readerData:connectedDebugAndroidTest && ./gradlew reader:connectedDebugAndroidTest -Pcucumber
30 | # for i in io.legere.readerdata.test voicedream.reader voicedream.reader.test; do
31 | # adb shell pm uninstall $i
32 | # done || true
33 | #./gradlew clean detekt lint testDebugUnitTest reader:pixel2DebugAndroidTest readerData:pixel2DebugAndroidTest
34 | # for i in io.legere.readerdata.test voicedream.reader voicedream.reader.test; do
35 | # adb shell pm uninstall $i
36 | # done || true
37 | #./gradlew reader:pixel2DebugAndroidTest -Pcucumber
38 |
39 | # ./gradlew clean detekt lint testDebugUnitTest reader:pixel2DebugAndroidTest readerData:pixel2DebugAndroidTest && ./gradlew reader:pixel2DebugAndroidTest -Pcucumber
40 | #
41 | #./gradlew clean detekt lint testDebugUnitTest pixel2DebugAndroidTest && ./gradlew pixel2DebugAndroidTest -Pcucumber
42 | ./gradlew $CLEAN detekt lint && \
43 | ./gradlew testDebugUnitTest && \
44 | ./gradlew connectedAndroidTest
45 |
46 |
47 | kill $serverPID
48 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | if [ -z "$JRELEASER_GPG_PUBLIC_KEY" ]; then
4 | echo "❌ ERROR: JRELEASER_GPG_PUBLIC_KEY is not set."
5 | echo "-----------------------------------------------------"
6 | echo "Please set the required environment variables for signing."
7 | echo "You can use the following commands:"
8 | echo ""
9 | echo " export JRELEASER_GPG_PUBLIC_KEY=\$(gpg --export --armor YOUR_KEY_ID)"
10 | echo " export JRELEASER_GPG_SECRET_KEY=\$(gpg --export-secret-keys --armor YOUR_KEY_ID)"
11 | echo " export JRELEASER_GPG_PASSPHRASE='your-secret-gpg-passphrase'"
12 | echo ""
13 | echo "Replace YOUR_KEY_ID and the passphrase with your actual credentials."
14 | echo "-----------------------------------------------------"
15 | exit 1 # Exit with an error code
16 | fi
17 |
18 | # Check for the secret key variable
19 | if [ -z "$JRELEASER_GPG_SECRET_KEY" ]; then
20 | echo "❌ ERROR: JRELEASER_GPG_SECRET_KEY is not set."
21 | echo "--> Please check the instructions above and ensure all three variables are exported."
22 | exit 1
23 | fi
24 |
25 | # Check for the passphrase variable
26 | if [ -z "$JRELEASER_GPG_PASSPHRASE" ]; then
27 | echo "❌ ERROR: JRELEASER_GPG_PASSPHRASE is not set."
28 | echo "--> Please check the instructions above and ensure all three variables are exported."
29 | exit 1
30 | fi
31 |
32 | echo "✅ JReleaser signing environment variables are set. Proceeding with release..."
33 | echo ""
34 |
35 | ./gradlew clean
36 | mkdir -p pdfiumandroid/arrow/build/jreleaser
37 | mkdir -p pdfiumandroid/build/target/staging-deploy
38 | mkdir -p pdfiumandroid/arrow/build/target/staging-deploy
39 | ./gradlew publish
40 | ./gradlew jreleaserFullRelease
41 | ./gradlew :pdfiumandroid:arrow:jreleaserRelease
42 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/pdfiumandroid/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | # Uncomment this to preserve the line number information for
2 | # debugging stack traces.
3 | -keepattributes SourceFile,LineNumberTable
4 |
5 | # If you keep the line number information, uncomment this to
6 | # hide the original source file name.
7 | #-renamesourcefileattribute SourceFile
8 |
9 | -keep class io.legere.pdfiumandroid.** { *; }
10 |
11 | -keep interface io.legere.pdfiumandroid.** { public *; }
12 |
13 | -keepclasseswithmembernames class io.legere.pdfiumandroid.** {
14 | public ;
15 | }
16 |
17 | -keep class * extends io.legere.pdfiumandroid.LoggerInterface { *; }
18 | -keep class io.legere.pdfiumandroid.suspend.PdfDocumentKt { *; }
19 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfDocumentKt {
20 | public (...);
21 | }
22 | -keep class io.legere.pdfiumandroid.suspend.PdfPageKt { *; }
23 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfPageKt {
24 | public (...);
25 | }
26 | -keep class io.legere.pdfiumandroid.suspend.PdfTextPageKt { *; }
27 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfTextPageKt {
28 | public (...);
29 | }
30 | -keep class io.legere.pdfiumandroid.suspend.PdfiumCoreKt { *; }
31 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfiumCoreKt {
32 | public (...);
33 | }
34 | -keep class io.legere.pdfiumandroid.util.AlreadyClosedBehavior { *; }
35 | -keepclassmembers public class io.legere.pdfiumandroid.util.AlreadyClosedBehavior {
36 | public (...);
37 | }
38 | -keep class io.legere.pdfiumandroid.util.Config { *; }
39 | -keepclassmembers public class io.legere.pdfiumandroid.util.Config {
40 | public (...);
41 | }
42 | -keep class io.legere.pdfiumandroid.util.Size { *; }
43 | -keepclassmembers public class io.legere.pdfiumandroid.util.Size {
44 | public (...);
45 | }
46 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/PdfPageSuspendCacheBase.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import com.google.common.cache.Cache
4 | import com.google.common.cache.CacheBuilder
5 | import com.google.common.cache.RemovalListener
6 | import kotlinx.coroutines.CoroutineDispatcher
7 | import kotlinx.coroutines.CoroutineScope
8 | import kotlinx.coroutines.Deferred
9 | import kotlinx.coroutines.async
10 | import kotlinx.coroutines.launch
11 |
12 | private const val CACHE_SIZE = 64L
13 |
14 | /**
15 | * A thread-safe, concurrent LRU cache for PDF page objects.
16 | * This base class holds the core Guava LoadingCache logic.
17 | */
18 | abstract class PdfPageSuspendCacheBase(
19 | dispatcher: CoroutineDispatcher,
20 | maxSize: Long = CACHE_SIZE,
21 | ) : AutoCloseable {
22 | private val scope: CoroutineScope = CoroutineScope(dispatcher)
23 |
24 | private val cache: Cache>
25 |
26 | init {
27 | val removalListener =
28 | RemovalListener> { notification ->
29 | notification.value?.let { deferred ->
30 | if (deferred.isCompleted) {
31 | scope.launch {
32 | runCatching { deferred.await().close() }
33 | }
34 | } else {
35 | deferred.cancel()
36 | }
37 | }
38 | }
39 | cache =
40 | CacheBuilder
41 | .newBuilder()
42 | .maximumSize(maxSize)
43 | .removalListener(removalListener)
44 | .build()
45 | }
46 |
47 | protected abstract suspend fun openPageAndText(pageIndex: Int): H
48 |
49 | suspend fun get(pageIndex: Int): H {
50 | val deferred =
51 | cache.asMap().computeIfAbsent(pageIndex) { key ->
52 | scope.async {
53 | openPageAndText(key)
54 | }
55 | }
56 | return deferred.await()
57 | }
58 |
59 | override fun close() {
60 | cache.invalidateAll()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_thumbnail.h:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef PUBLIC_FPDF_THUMBNAIL_H_
6 | #define PUBLIC_FPDF_THUMBNAIL_H_
7 |
8 | #include
9 |
10 | // NOLINTNEXTLINE(build/include)
11 | #include "fpdfview.h"
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif
16 |
17 | // Experimental API.
18 | // Gets the decoded data from the thumbnail of |page| if it exists.
19 | // This only modifies |buffer| if |buflen| less than or equal to the
20 | // size of the decoded data. Returns the size of the decoded
21 | // data or 0 if thumbnail DNE. Optional, pass null to just retrieve
22 | // the size of the buffer needed.
23 | //
24 | // page - handle to a page.
25 | // buffer - buffer for holding the decoded image data.
26 | // buflen - length of the buffer in bytes.
27 | FPDF_EXPORT unsigned long FPDF_CALLCONV
28 | FPDFPage_GetDecodedThumbnailData(FPDF_PAGE page,
29 | void* buffer,
30 | unsigned long buflen);
31 |
32 | // Experimental API.
33 | // Gets the raw data from the thumbnail of |page| if it exists.
34 | // This only modifies |buffer| if |buflen| is less than or equal to
35 | // the size of the raw data. Returns the size of the raw data or 0
36 | // if thumbnail DNE. Optional, pass null to just retrieve the size
37 | // of the buffer needed.
38 | //
39 | // page - handle to a page.
40 | // buffer - buffer for holding the raw image data.
41 | // buflen - length of the buffer in bytes.
42 | FPDF_EXPORT unsigned long FPDF_CALLCONV
43 | FPDFPage_GetRawThumbnailData(FPDF_PAGE page,
44 | void* buffer,
45 | unsigned long buflen);
46 |
47 | // Experimental API.
48 | // Returns the thumbnail of |page| as a FPDF_BITMAP. Returns a nullptr
49 | // if unable to access the thumbnail's stream.
50 | //
51 | // page - handle to a page.
52 | FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV
53 | FPDFPage_GetThumbnailAsBitmap(FPDF_PAGE page);
54 |
55 | #ifdef __cplusplus
56 | }
57 | #endif
58 |
59 | #endif // PUBLIC_FPDF_THUMBNAIL_H_
60 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
25 | VERSION_NAME=1.0.35
26 | VERSION_CODE=6
27 | GROUP=io.legere
28 |
29 | POM_DESCRIPTION=Pdfium Android binding with Bitmap rendering ( >= API 24 )
30 | POM_URL=https://github.com/johngray1965/PdfiumAndroidKt
31 | POM_SCM_URL=git@github.com:johngray1965/PdfiumAndroidKt.git
32 | POM_SCM_CONNECTION=scm:git@github.com:johngray1965/PdfiumAndroidKt.git
33 | POM_SCM_DEV_CONNECTION=scm:git@github.com:johngray1965/PdfiumAndroidKt.git
34 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
35 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
36 | POM_LICENCE_DIST=repo
37 | POM_DEVELOPER_ID=johngray1965
38 | POM_DEVELOPER_NAME=John Gray
39 | POM_PACKAGING=aar
40 |
41 | #RELEASE_REPOSITORY_URL=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2
42 | #SNAPSHOT_REPOSITORY_URL=https://s01.oss.sonatype.org/content/repositories/snapshots
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Original work Copyright 2015 Bekket McClane
2 | Modified work Copyright 2016 Bartosz Schiller
3 | Modified work Copyright 2023 John Gray
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | // Copyright 2014 PDFium Authors. All rights reserved.
18 | //
19 | // Redistribution and use in source and binary forms, with or without
20 | // modification, are permitted provided that the following conditions are
21 | // met:
22 | //
23 | // * Redistributions of source code must retain the above copyright
24 | // notice, this list of conditions and the following disclaimer.
25 | // * Redistributions in binary form must reproduce the above
26 | // copyright notice, this list of conditions and the following disclaimer
27 | // in the documentation and/or other materials provided with the
28 | // distribution.
29 | // * Neither the name of Google Inc. nor the names of its
30 | // contributors may be used to endorse or promote products derived from
31 | // this software without specific prior written permission.
32 | //
33 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
34 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
35 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
36 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
37 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
40 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
41 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
42 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
43 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/app/src/main/java/io/legere/pdfiumandroidkt/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroidkt.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.SideEffect
13 | import androidx.compose.ui.graphics.toArgb
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.platform.LocalView
16 | import androidx.core.view.WindowCompat
17 |
18 | private val darkColorScheme =
19 | darkColorScheme(
20 | primary = Purple80,
21 | secondary = PurpleGrey80,
22 | tertiary = Pink80,
23 | )
24 |
25 | private val lightColorScheme =
26 | lightColorScheme(
27 | primary = Purple40,
28 | secondary = PurpleGrey40,
29 | tertiary = Pink40,
30 | )
31 |
32 | @Composable
33 | @Suppress("FunctionNaming", "ktlint:standard:function-naming")
34 | fun PdfiumAndroidKtTheme(
35 | darkTheme: Boolean = isSystemInDarkTheme(),
36 | // Dynamic color is available on Android 12+
37 | dynamicColor: Boolean = true,
38 | content: @Composable () -> Unit,
39 | ) {
40 | val colorScheme =
41 | when {
42 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
43 | val context = LocalContext.current
44 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
45 | }
46 |
47 | darkTheme -> darkColorScheme
48 | else -> lightColorScheme
49 | }
50 | val view = LocalView.current
51 | if (!view.isInEditMode) {
52 | SideEffect {
53 | val window = (view.context as Activity).window
54 | window.statusBarColor = colorScheme.primary.toArgb()
55 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
56 | }
57 | }
58 |
59 | MaterialTheme(
60 | colorScheme = colorScheme,
61 | typography = Typography,
62 | content = content,
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class io.legere.pdfiumandroid.** { *; }
23 |
24 | -keep interface io.legere.pdfiumandroid.** { public *; }
25 |
26 | -keepclasseswithmembernames class io.legere.pdfiumandroid.** {
27 | public ;
28 | }
29 |
30 | -keep class * extends io.legere.pdfiumandroid.LoggerInterface { *; }
31 | -keep class io.legere.pdfiumandroid.suspend.PdfDocumentKt { *; }
32 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfDocumentKt {
33 | public (...);
34 | }
35 | -keep class io.legere.pdfiumandroid.suspend.PdfPageKt { *; }
36 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfPageKt {
37 | public (...);
38 | }
39 | -keep class io.legere.pdfiumandroid.suspend.PdfTextPageKt { *; }
40 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfTextPageKt {
41 | public (...);
42 | }
43 | -keep class io.legere.pdfiumandroid.suspend.PdfiumCoreKt { *; }
44 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfiumCoreKt {
45 | public (...);
46 | }
47 | -keep class io.legere.pdfiumandroid.util.AlreadyClosedBehavior { *; }
48 | -keepclassmembers public class io.legere.pdfiumandroid.util.AlreadyClosedBehavior {
49 | public (...);
50 | }
51 | -keep class io.legere.pdfiumandroid.util.Config { *; }
52 | -keepclassmembers public class io.legere.pdfiumandroid.util.Config {
53 | public (...);
54 | }
55 | -keep class io.legere.pdfiumandroid.util.Size { *; }
56 | -keepclassmembers public class io.legere.pdfiumandroid.util.Size {
57 | public (...);
58 | }
59 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/PdfPageCache.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid
4 |
5 | import io.legere.pdfiumandroid.util.PdfPageCacheBase
6 |
7 | /**
8 | * A cache for pages of a PDF document.
9 | *
10 | * This class is thread-safe.
11 | *
12 | * To use this class, you need to provide a `pdfDocument` and a `pageHolderFactory`.
13 | * The `pageHolderFactory` is a lambda that takes a `PdfPage` and a `PdfTextPage` and returns an instance of `H`.
14 | *
15 | * The generic type `H` is a holder for the page and text page. It can be a simple `PageHolder` or a custom class.
16 | *
17 | * Example usage with `PageHolder`:
18 | * ```
19 | * val pageCache = PdfPageCache(pdfDocument) { page, textPage ->
20 | * PageHolder(page, textPage)
21 | * }
22 | * ```
23 | *
24 | * Example usage with a custom holder:
25 | * ```
26 | * data class CustomPageHolder(
27 | * val page: PdfPage,
28 | * val textPage: PdfTextPage,
29 | * val someOtherData: String
30 | * ) : AutoCloseable {
31 | * override fun close() {
32 | * try {
33 | * textPage.close()
34 | * } finally {
35 | * page.close()
36 | * }
37 | * }
38 | * }
39 | *
40 | * val pageCache = PdfPageCache(pdfDocument) { page, textPage ->
41 | * CustomPageHolder(page, textPage, "some other data")
42 | * }
43 | * ```
44 | *
45 | * @param H The type of the holder for the page and text page. Must be [AutoCloseable].
46 | * @property pdfDocument The [PdfDocument] to cache pages from.
47 | * @property pageHolderFactory A factory for creating a holder for a page and text page.
48 | */
49 | class PdfPageCache(
50 | private val pdfDocument: PdfDocument,
51 | private val pageHolderFactory: (PdfPage, PdfTextPage) -> H,
52 | ) : PdfPageCacheBase() {
53 | override fun openPageAndText(pageIndex: Int): H {
54 | val page = pdfDocument.openPage(pageIndex)
55 | val textPage = page.openTextPage()
56 | return pageHolderFactory(page, textPage)
57 | }
58 | }
59 |
60 | data class PageHolder(
61 | val page: TPage,
62 | val textPage: TTextPage,
63 | ) : AutoCloseable {
64 | override fun close() {
65 | try {
66 | textPage.close()
67 | } finally {
68 | page.close()
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/pdfiumandroid/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | -keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class io.legere.pdfiumandroid.** { *; }
24 |
25 | -keep interface io.legere.pdfiumandroid.** { public *; }
26 | -keep class * extends io.legere.pdfiumandroid.LoggerInterface { *; }
27 | -dontwarn java.lang.invoke.StringConcatFactory
28 | -keepclasseswithmembernames class io.legere.pdfiumandroid.** {
29 | public ;
30 | }
31 |
32 | -keep class io.legere.pdfiumandroid.suspend.PdfDocumentKt { *; }
33 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfDocumentKt {
34 | public (...);
35 | }
36 | -keep class io.legere.pdfiumandroid.suspend.PdfPageKt { *; }
37 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfPageKt {
38 | public (...);
39 | }
40 | -keep class io.legere.pdfiumandroid.suspend.PdfTextPageKt { *; }
41 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfTextPageKt {
42 | public (...);
43 | }
44 | -keep class io.legere.pdfiumandroid.suspend.PdfiumCoreKt { *; }
45 | -keepclassmembers public class io.legere.pdfiumandroid.suspend.PdfiumCoreKt {
46 | public (...);
47 | }
48 | -keep class io.legere.pdfiumandroid.util.AlreadyClosedBehavior { *; }
49 | -keepclassmembers public class io.legere.pdfiumandroid.util.AlreadyClosedBehavior {
50 | public (...);
51 | }
52 | -keep class io.legere.pdfiumandroid.util.Config { *; }
53 | -keepclassmembers public class io.legere.pdfiumandroid.util.Config {
54 | public (...);
55 | }
56 | -keep class io.legere.pdfiumandroid.util.Size { *; }
57 | -keepclassmembers public class io.legere.pdfiumandroid.util.Size {
58 | public (...);
59 | }
60 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/cpp/fpdf_scopers.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef PUBLIC_CPP_FPDF_SCOPERS_H_
6 | #define PUBLIC_CPP_FPDF_SCOPERS_H_
7 |
8 | #include
9 | #include
10 |
11 | #include "fpdf_deleters.h"
12 |
13 | // Versions of FPDF types that clean up the object at scope exit.
14 |
15 | using ScopedFPDFAnnotation =
16 | std::unique_ptr::type,
17 | FPDFAnnotationDeleter>;
18 |
19 | using ScopedFPDFAvail =
20 | std::unique_ptr::type, FPDFAvailDeleter>;
21 |
22 | using ScopedFPDFBitmap =
23 | std::unique_ptr::type, FPDFBitmapDeleter>;
24 |
25 | using ScopedFPDFClipPath =
26 | std::unique_ptr::type,
27 | FPDFClipPathDeleter>;
28 |
29 | using ScopedFPDFDocument =
30 | std::unique_ptr::type,
31 | FPDFDocumentDeleter>;
32 |
33 | using ScopedFPDFFont =
34 | std::unique_ptr::type, FPDFFontDeleter>;
35 |
36 | using ScopedFPDFFormHandle =
37 | std::unique_ptr::type,
38 | FPDFFormHandleDeleter>;
39 |
40 | using ScopedFPDFJavaScriptAction =
41 | std::unique_ptr::type,
42 | FPDFJavaScriptActionDeleter>;
43 |
44 | using ScopedFPDFPage =
45 | std::unique_ptr::type, FPDFPageDeleter>;
46 |
47 | using ScopedFPDFPageLink =
48 | std::unique_ptr::type,
49 | FPDFPageLinkDeleter>;
50 |
51 | using ScopedFPDFPageObject =
52 | std::unique_ptr::type,
53 | FPDFPageObjectDeleter>;
54 |
55 | using ScopedFPDFStructTree =
56 | std::unique_ptr::type,
57 | FPDFStructTreeDeleter>;
58 |
59 | using ScopedFPDFTextFind =
60 | std::unique_ptr::type,
61 | FPDFTextFindDeleter>;
62 |
63 | using ScopedFPDFTextPage =
64 | std::unique_ptr::type,
65 | FPDFTextPageDeleter>;
66 |
67 | #endif // PUBLIC_CPP_FPDF_SCOPERS_H_
68 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html
3 |
4 | # Sets the minimum version of CMake required to build the native library.
5 |
6 | cmake_minimum_required(VERSION 3.12...4.1.0)
7 | # Declares and names the project.
8 |
9 | project("pdfiumandroid")
10 |
11 | # Creates and names a library, sets it as either STATIC
12 | # or SHARED, and provides the relative paths to its source code.
13 | # You can define multiple libraries, and CMake builds them for you.
14 | # Gradle automatically packages shared libraries with your APK.
15 |
16 | add_library( # Sets the name of the library.
17 | pdfiumandroid
18 |
19 | # Sets the library as a shared library.
20 | SHARED
21 |
22 | # Provides a relative path to your source file(s).
23 | pdfiumandroid.cpp)
24 |
25 | # Searches for a specified prebuilt library and stores the path as a
26 | # variable. Because CMake includes system libraries in the search path by
27 | # default, you only need to specify the name of the public NDK library
28 | # you want to add. CMake verifies that the library exists before
29 | # completing its build.
30 |
31 | find_library( # Sets the name of the path variable.
32 | log-lib
33 |
34 | # Specifies the name of the NDK library that
35 | # you want CMake to locate.
36 | log)
37 |
38 | find_library( # Sets the name of the path variable.
39 | android-lib
40 |
41 | # Specifies the name of the NDK library that
42 | # you want CMake to locate.
43 | android)
44 |
45 | find_library( # Sets the name of the path variable.
46 | jnigraphics-lib
47 |
48 | # Specifies the name of the NDK library that
49 | # you want CMake to locate.
50 | jnigraphics)
51 |
52 | # Specifies libraries CMake should link to your target library. You
53 | # can link multiple libraries, such as libraries you define in this
54 | # build script, prebuilt third-party libraries, or system libraries.
55 |
56 | target_link_libraries( # Specifies the target library.
57 | pdfiumandroid
58 |
59 | libpdfium
60 |
61 | # Links the target library to the log library
62 | # included in the NDK.
63 | ${log-lib}
64 | ${android-lib}
65 | ${jnigraphics-lib}
66 | )
67 |
68 |
69 | add_library( libpdfium SHARED IMPORTED )
70 |
71 | set_target_properties( libpdfium PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libpdfium.so)
72 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/cpp/fpdf_deleters.h:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef PUBLIC_CPP_FPDF_DELETERS_H_
6 | #define PUBLIC_CPP_FPDF_DELETERS_H_
7 |
8 | #include "../fpdf_annot.h"
9 | #include "../fpdf_dataavail.h"
10 | #include "../fpdf_edit.h"
11 | #include "../fpdf_formfill.h"
12 | #include "../fpdf_javascript.h"
13 | #include "../fpdf_structtree.h"
14 | #include "../fpdf_text.h"
15 | #include "../fpdf_transformpage.h"
16 | #include "../fpdfview.h"
17 |
18 | // Custom deleters for using FPDF_* types with std::unique_ptr<>.
19 |
20 | struct FPDFAnnotationDeleter {
21 | inline void operator()(FPDF_ANNOTATION annot) { FPDFPage_CloseAnnot(annot); }
22 | };
23 |
24 | struct FPDFAvailDeleter {
25 | inline void operator()(FPDF_AVAIL avail) { FPDFAvail_Destroy(avail); }
26 | };
27 |
28 | struct FPDFBitmapDeleter {
29 | inline void operator()(FPDF_BITMAP bitmap) { FPDFBitmap_Destroy(bitmap); }
30 | };
31 |
32 | struct FPDFClipPathDeleter {
33 | inline void operator()(FPDF_CLIPPATH clip_path) {
34 | FPDF_DestroyClipPath(clip_path);
35 | }
36 | };
37 |
38 | struct FPDFDocumentDeleter {
39 | inline void operator()(FPDF_DOCUMENT doc) { FPDF_CloseDocument(doc); }
40 | };
41 |
42 | struct FPDFFontDeleter {
43 | inline void operator()(FPDF_FONT font) { FPDFFont_Close(font); }
44 | };
45 |
46 | struct FPDFFormHandleDeleter {
47 | inline void operator()(FPDF_FORMHANDLE form) {
48 | FPDFDOC_ExitFormFillEnvironment(form);
49 | }
50 | };
51 |
52 | struct FPDFJavaScriptActionDeleter {
53 | inline void operator()(FPDF_JAVASCRIPT_ACTION javascript) {
54 | FPDFDoc_CloseJavaScriptAction(javascript);
55 | }
56 | };
57 |
58 | struct FPDFPageDeleter {
59 | inline void operator()(FPDF_PAGE page) { FPDF_ClosePage(page); }
60 | };
61 |
62 | struct FPDFPageLinkDeleter {
63 | inline void operator()(FPDF_PAGELINK pagelink) {
64 | FPDFLink_CloseWebLinks(pagelink);
65 | }
66 | };
67 |
68 | struct FPDFPageObjectDeleter {
69 | inline void operator()(FPDF_PAGEOBJECT object) {
70 | FPDFPageObj_Destroy(object);
71 | }
72 | };
73 |
74 | struct FPDFStructTreeDeleter {
75 | inline void operator()(FPDF_STRUCTTREE tree) { FPDF_StructTree_Close(tree); }
76 | };
77 |
78 | struct FPDFTextFindDeleter {
79 | inline void operator()(FPDF_SCHHANDLE handle) { FPDFText_FindClose(handle); }
80 | };
81 |
82 | struct FPDFTextPageDeleter {
83 | inline void operator()(FPDF_TEXTPAGE text) { FPDFText_ClosePage(text); }
84 | };
85 |
86 | #endif // PUBLIC_CPP_FPDF_DELETERS_H_
87 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/PdfPageLinkTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | import android.graphics.RectF
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.google.common.truth.Truth.assertThat
6 | import io.legere.pdfiumandroid.base.BasePDFTest
7 | import org.junit.After
8 | import org.junit.Before
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 |
12 | @RunWith(AndroidJUnit4::class)
13 | class PdfPageLinkTest : BasePDFTest() {
14 | private lateinit var pdfDocument: PdfDocument
15 | private lateinit var pdfPage: PdfPage
16 | private lateinit var pdfTextPage: PdfTextPage
17 | private var pdfBytes: ByteArray? = null
18 |
19 | @Before
20 | fun setUp() {
21 | pdfBytes = getPdfBytes("pdf-test.pdf")
22 |
23 | assertThat(pdfBytes).isNotNull()
24 |
25 | pdfDocument = PdfiumCore().newDocument(pdfBytes)
26 |
27 | pdfPage = pdfDocument.openPage(0)
28 | pdfTextPage = pdfPage.openTextPage()
29 | }
30 |
31 | @After
32 | fun tearDown() {
33 | pdfTextPage.close()
34 | pdfPage.close()
35 | pdfDocument.close()
36 | }
37 |
38 | @Test
39 | fun testLink() {
40 | val links = pdfTextPage.loadWebLink()
41 | assertThat(links).isNotNull()
42 | links.close()
43 | }
44 |
45 | @Test
46 | fun testCountWebLinks() {
47 | val links = pdfTextPage.loadWebLink()
48 | assertThat(links).isNotNull()
49 | assertThat(links.countWebLinks()).isEqualTo(1)
50 | links.close()
51 | }
52 |
53 | @Test
54 | fun testGetTextRange() {
55 | val links = pdfTextPage.loadWebLink()
56 | assertThat(links).isNotNull()
57 | assertThat(links.getTextRange(0)).isEqualTo(Pair(351, 31))
58 | links.close()
59 | }
60 |
61 | @Test
62 | fun testGetUrl() {
63 | val links = pdfTextPage.loadWebLink()
64 | assertThat(links).isNotNull()
65 | val (_, count) = links.getTextRange(0)
66 | assertThat(links.getURL(0, count)).isEqualTo("http://www.education.gov.yk.ca/")
67 | links.close()
68 | }
69 |
70 | @Test
71 | fun testCountRects() {
72 | val links = pdfTextPage.loadWebLink()
73 | assertThat(links).isNotNull()
74 | val count = links.countRects(0)
75 | assertThat(count).isEqualTo(1)
76 | links.close()
77 | }
78 |
79 | @Test
80 | fun testGetRect() {
81 | val links = pdfTextPage.loadWebLink()
82 | assertThat(links).isNotNull()
83 | val count = links.getRect(0, 0)
84 | assertThat(count).isEqualTo(RectF(221.46f, 480.624f, 389.66394f, 469.152f))
85 | links.close()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/PdfiumCoreKt.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.suspend
4 |
5 | import android.os.ParcelFileDescriptor
6 | import androidx.annotation.Keep
7 | import io.legere.pdfiumandroid.PdfiumCore
8 | import io.legere.pdfiumandroid.PdfiumSource
9 | import io.legere.pdfiumandroid.util.Config
10 | import kotlinx.coroutines.CoroutineDispatcher
11 | import kotlinx.coroutines.withContext
12 |
13 | /**
14 | * PdfiumCoreKt is the main entry-point for access to the PDFium API.
15 | * @property dispatcher the [CoroutineDispatcher] to use for suspending calls
16 | * @constructor create a [PdfiumCoreKt] from a [PdfiumCore]
17 | */
18 | @Keep
19 | class PdfiumCoreKt(
20 | private val dispatcher: CoroutineDispatcher,
21 | config: Config = Config(),
22 | ) {
23 | private val coreInternal = PdfiumCore(config = config)
24 |
25 | /**
26 | * suspend version of [PdfiumCore.newDocument]
27 | */
28 | suspend fun newDocument(fd: ParcelFileDescriptor): PdfDocumentKt =
29 | withContext(dispatcher) {
30 | PdfDocumentKt(coreInternal.newDocument(fd), dispatcher)
31 | }
32 |
33 | /**
34 | * suspend version of [PdfiumCore.newDocument]
35 | */
36 | suspend fun newDocument(
37 | fd: ParcelFileDescriptor,
38 | password: String?,
39 | ): PdfDocumentKt =
40 | withContext(dispatcher) {
41 | PdfDocumentKt(coreInternal.newDocument(fd, password), dispatcher)
42 | }
43 |
44 | /**
45 | * suspend version of [PdfiumCore.newDocument]
46 | */
47 | suspend fun newDocument(data: ByteArray?): PdfDocumentKt =
48 | withContext(dispatcher) {
49 | PdfDocumentKt(coreInternal.newDocument(data), dispatcher)
50 | }
51 |
52 | /**
53 | * suspend version of [PdfiumCore.newDocument]
54 | */
55 | suspend fun newDocument(
56 | data: ByteArray?,
57 | password: String?,
58 | ): PdfDocumentKt =
59 | withContext(dispatcher) {
60 | PdfDocumentKt(coreInternal.newDocument(data, password), dispatcher)
61 | }
62 |
63 | /**
64 | * suspend version of [PdfiumCore.newDocument]
65 | */
66 | suspend fun newDocument(data: PdfiumSource): PdfDocumentKt =
67 | withContext(dispatcher) {
68 | PdfDocumentKt(coreInternal.newDocument(data), dispatcher)
69 | }
70 |
71 | /**
72 | * suspend version of [PdfiumCore.newDocument]
73 | */
74 | suspend fun newDocument(
75 | data: PdfiumSource,
76 | password: String?,
77 | ): PdfDocumentKt =
78 | withContext(dispatcher) {
79 | PdfDocumentKt(coreInternal.newDocument(data, password), dispatcher)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfiumCoreKtF.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.arrow
4 |
5 | import android.os.ParcelFileDescriptor
6 | import arrow.core.Either
7 | import io.legere.pdfiumandroid.PdfiumCore
8 | import io.legere.pdfiumandroid.PdfiumSource
9 | import io.legere.pdfiumandroid.util.Config
10 | import kotlinx.coroutines.CoroutineDispatcher
11 |
12 | /**
13 | * PdfiumCoreKtF is the main entry-point for access to the PDFium API.
14 | * @property dispatcher the [CoroutineDispatcher] to use for suspending calls
15 | * @constructor create a [PdfiumCoreKtF] from a [PdfiumCore]
16 | */
17 | class PdfiumCoreKtF(
18 | private val dispatcher: CoroutineDispatcher,
19 | config: Config = Config(),
20 | ) {
21 | private val coreInternal = PdfiumCore(config = config)
22 |
23 | /**
24 | * suspend version of [PdfiumCore.newDocument]
25 | */
26 | suspend fun newDocument(fd: ParcelFileDescriptor): Either =
27 | wrapEither(dispatcher) {
28 | PdfDocumentKtF(coreInternal.newDocument(fd), dispatcher)
29 | }
30 |
31 | /**
32 | * suspend version of [PdfiumCore.newDocument]
33 | */
34 | suspend fun newDocument(
35 | fd: ParcelFileDescriptor,
36 | password: String?,
37 | ): Either =
38 | wrapEither(dispatcher) {
39 | PdfDocumentKtF(coreInternal.newDocument(fd, password), dispatcher)
40 | }
41 |
42 | /**
43 | * suspend version of [PdfiumCore.newDocument]
44 | */
45 | suspend fun newDocument(data: ByteArray?): Either =
46 | wrapEither(dispatcher) {
47 | PdfDocumentKtF(coreInternal.newDocument(data), dispatcher)
48 | }
49 |
50 | /**
51 | * suspend version of [PdfiumCore.newDocument]
52 | */
53 | suspend fun newDocument(
54 | data: ByteArray?,
55 | password: String?,
56 | ): Either =
57 | wrapEither(dispatcher) {
58 | PdfDocumentKtF(coreInternal.newDocument(data, password), dispatcher)
59 | }
60 |
61 | /**
62 | * suspend version of [PdfiumCore.newDocument]
63 | */
64 | suspend fun newDocument(data: PdfiumSource): Either =
65 | wrapEither(dispatcher) {
66 | PdfDocumentKtF(coreInternal.newDocument(data), dispatcher)
67 | }
68 |
69 | /**
70 | * suspend version of [PdfiumCore.newDocument]
71 | */
72 | suspend fun newDocument(
73 | data: PdfiumSource,
74 | password: String?,
75 | ): Either =
76 | wrapEither(dispatcher) {
77 | PdfDocumentKtF(coreInternal.newDocument(data, password), dispatcher)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/utils/Errors.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef ANDROID_ERRORS_H
18 | #define ANDROID_ERRORS_H
19 |
20 | #include
21 | #include
22 |
23 | namespace android {
24 |
25 | // use this type to return error codes
26 | #ifdef HAVE_MS_C_RUNTIME
27 | typedef int status_t;
28 | #else
29 | typedef int32_t status_t;
30 | #endif
31 |
32 | /* the MS C runtime lacks a few error codes */
33 |
34 | /*
35 | * Error codes.
36 | * All error codes are negative values.
37 | */
38 |
39 | // Win32 #defines NO_ERROR as well. It has the same value, so there's no
40 | // real conflict, though it's a bit awkward.
41 | #ifdef _WIN32
42 | # undef NO_ERROR
43 | #endif
44 |
45 | enum {
46 | OK = 0, // Everything's swell.
47 | NO_ERROR = 0, // No errors.
48 |
49 | UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value
50 |
51 | NO_MEMORY = -ENOMEM,
52 | INVALID_OPERATION = -ENOSYS,
53 | BAD_VALUE = -EINVAL,
54 | BAD_TYPE = (UNKNOWN_ERROR + 1),
55 | NAME_NOT_FOUND = -ENOENT,
56 | PERMISSION_DENIED = -EPERM,
57 | NO_INIT = -ENODEV,
58 | ALREADY_EXISTS = -EEXIST,
59 | DEAD_OBJECT = -EPIPE,
60 | FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
61 | JPARKS_BROKE_IT = -EPIPE,
62 | #if !defined(HAVE_MS_C_RUNTIME)
63 | BAD_INDEX = -EOVERFLOW,
64 | NOT_ENOUGH_DATA = -ENODATA,
65 | WOULD_BLOCK = -EWOULDBLOCK,
66 | TIMED_OUT = -ETIMEDOUT,
67 | UNKNOWN_TRANSACTION = -EBADMSG,
68 | #else
69 | BAD_INDEX = -E2BIG,
70 | NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3),
71 | WOULD_BLOCK = (UNKNOWN_ERROR + 4),
72 | TIMED_OUT = (UNKNOWN_ERROR + 5),
73 | UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6),
74 | #endif
75 | FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7),
76 | };
77 |
78 | // Restore define; enumeration is in "android" namespace, so the value defined
79 | // there won't work for Win32 code in a different namespace.
80 | #ifdef _WIN32
81 | # define NO_ERROR 0L
82 | #endif
83 |
84 | }; // namespace android
85 |
86 | // ---------------------------------------------------------------------------
87 |
88 | #endif // ANDROID_ERRORS_H
89 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/PdfPageKtCache.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.suspend
4 |
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.Dispatchers
7 |
8 | /**
9 | * A cache for pages of a PDF document, designed to be used with coroutines.
10 | *
11 | * This class is thread-safe.
12 | *
13 | * To use this class, you need to provide a `pdfDocument`, a `dispatcher` and a `pageHolderFactory`.
14 | * The `pageHolderFactory` is a lambda that takes a `PdfPageKt` and a `PdfTextPageKt` and returns an instance of `H`.
15 | *
16 | * The generic type `H` is a holder for the page and text page. It can be a simple [PageHolderKt] or a custom class.
17 | *
18 | * Example usage with [PageHolderKt]:
19 | * ```
20 | * val pageCache = PdfPageKtCache(pdfDocument) { page, textPage ->
21 | * PageHolderKt(page, textPage)
22 | * }
23 | * ```
24 | *
25 | * Example usage with a custom holder:
26 | * ```
27 | * data class CustomPageHolder(
28 | * val page: PdfPageKt,
29 | * val textPage: PdfTextPageKt,
30 | * val someOtherData: String
31 | * ) : AutoCloseable {
32 | * override fun close() {
33 | * try {
34 | * textPage.close()
35 | * } finally {
36 | * page.close()
37 | * }
38 | * }
39 | * }
40 | *
41 | * val pageCache = PdfPageKtCache(pdfDocument) { page, textPage ->
42 | * CustomPageHolder(page, textPage, "some other data")
43 | * }
44 | * ```
45 | *
46 | * @param H The type of the holder for the page and text page. Must be [AutoCloseable].
47 | * @property pdfDocument The [PdfDocumentKt] to cache pages from.
48 | * @param dispatcher The [CoroutineDispatcher] to use for opening pages. Defaults to [Dispatchers.IO].
49 | * @property pageHolderFactory A factory for creating a holder for a page and text page.
50 | */
51 | class PdfPageKtCache(
52 | private val pdfDocument: PdfDocumentKt,
53 | dispatcher: CoroutineDispatcher = Dispatchers.IO,
54 | private val pageHolderFactory: suspend (PdfPageKt, PdfTextPageKt) -> H,
55 | ) : PdfPageSuspendCacheBase(dispatcher) {
56 | override suspend fun openPageAndText(pageIndex: Int): H {
57 | val page = pdfDocument.openPage(pageIndex)
58 | val textPage = page.openTextPage()
59 | return pageHolderFactory(page, textPage)
60 | }
61 | }
62 |
63 | /**
64 | * A holder for a [PdfPageKt] and a [PdfTextPageKt].
65 | *
66 | * @param TPage The type of the page.
67 | * @param TTextPage The type of the text page.
68 | * @property page The page.
69 | * @property textPage The text page.
70 | */
71 | data class PageHolderKt(
72 | val page: TPage,
73 | val textPage: TTextPage,
74 | ) : AutoCloseable {
75 | override fun close() {
76 | try {
77 | textPage.close()
78 | } finally {
79 | page.close()
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_javascript.h:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef PUBLIC_FPDF_JAVASCRIPT_H_
6 | #define PUBLIC_FPDF_JAVASCRIPT_H_
7 |
8 | // NOLINTNEXTLINE(build/include)
9 | #include "fpdfview.h"
10 |
11 | #ifdef __cplusplus
12 | extern "C" {
13 | #endif // __cplusplus
14 |
15 | // Experimental API.
16 | // Get the number of JavaScript actions in |document|.
17 | //
18 | // document - handle to a document.
19 | //
20 | // Returns the number of JavaScript actions in |document| or -1 on error.
21 | FPDF_EXPORT int FPDF_CALLCONV
22 | FPDFDoc_GetJavaScriptActionCount(FPDF_DOCUMENT document);
23 |
24 | // Experimental API.
25 | // Get the JavaScript action at |index| in |document|.
26 | //
27 | // document - handle to a document.
28 | // index - the index of the requested JavaScript action.
29 | //
30 | // Returns the handle to the JavaScript action, or NULL on failure.
31 | // Caller owns the returned handle and must close it with
32 | // FPDFDoc_CloseJavaScriptAction().
33 | FPDF_EXPORT FPDF_JAVASCRIPT_ACTION FPDF_CALLCONV
34 | FPDFDoc_GetJavaScriptAction(FPDF_DOCUMENT document, int index);
35 |
36 | // Experimental API.
37 | // Close a loaded FPDF_JAVASCRIPT_ACTION object.
38 |
39 | // javascript - Handle to a JavaScript action.
40 | FPDF_EXPORT void FPDF_CALLCONV
41 | FPDFDoc_CloseJavaScriptAction(FPDF_JAVASCRIPT_ACTION javascript);
42 |
43 | // Experimental API.
44 | // Get the name from the |javascript| handle. |buffer| is only modified if
45 | // |buflen| is longer than the length of the name. On errors, |buffer| is
46 | // unmodified and the returned length is 0.
47 | //
48 | // javascript - handle to an JavaScript action.
49 | // buffer - buffer for holding the name, encoded in UTF-16LE.
50 | // buflen - length of the buffer in bytes.
51 | //
52 | // Returns the length of the JavaScript action name in bytes.
53 | FPDF_EXPORT unsigned long FPDF_CALLCONV
54 | FPDFJavaScriptAction_GetName(FPDF_JAVASCRIPT_ACTION javascript,
55 | FPDF_WCHAR* buffer,
56 | unsigned long buflen);
57 |
58 | // Experimental API.
59 | // Get the script from the |javascript| handle. |buffer| is only modified if
60 | // |buflen| is longer than the length of the script. On errors, |buffer| is
61 | // unmodified and the returned length is 0.
62 | //
63 | // javascript - handle to an JavaScript action.
64 | // buffer - buffer for holding the name, encoded in UTF-16LE.
65 | // buflen - length of the buffer in bytes.
66 | //
67 | // Returns the length of the JavaScript action name in bytes.
68 | FPDF_EXPORT unsigned long FPDF_CALLCONV
69 | FPDFJavaScriptAction_GetScript(FPDF_JAVASCRIPT_ACTION javascript,
70 | FPDF_WCHAR* buffer,
71 | unsigned long buflen);
72 |
73 | #ifdef __cplusplus
74 | } // extern "C"
75 | #endif // __cplusplus
76 |
77 | #endif // PUBLIC_FPDF_JAVASCRIPT_H_
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PdfiumAndroidKt
2 |
3 | [](https://github.com/johngray1965/PdfiumAndroidKt/actions/workflows/android.yml)
4 |
5 | A Pdfium Android library using the latest stable version Pdfium. Written in Kotlin with coroutines to easily talk to the native code off the main thread.
6 |
7 | Largely rewritten, but based on https://github.com/barteksc/PdfiumAndroid
8 |
9 | This version give you three versions of the API, one that using suspend functions, one that use arrow (and suspend functions) and one plain API with nothing fancy (and that should work from Java).
10 |
11 | This is a object-oriented version. PfdiumCore gives you options to open up the PDF, all the methods on document are from PdfDocumment. Likewise there's an PdfPage and PdfTextPage. You get the Page objects from the PdfDocument, all the things that operation on the pages.
12 |
13 | For using suspend functions, use PdfiumCoreKt, and it'll return PdfDocummentK, PdfDocummentK give PdfPageKt and PdfTextPageKt. All the Kt classes have suspend functions, and will do there work on the dispatcher that's passed into PdfiumCoreKt when its created.
14 |
15 | For using arrow functions, use PdfiumCoreKtF, and it'll return PdfDocummentKF, PdfDocummentKF give PdfPageKtF and PdfTextPageKtF. All the KtF classes have arrow functions, and will do there work on the dispatcher that's passed into PdfiumCoreKtF when its created.
16 |
17 | PdfDocument, PdfPage, PdfTextPage and the Kt and KtF versions all uses Closable.
18 |
19 | To use it, add the following in your app's build.gradle:
20 | ```
21 | implementation("io.legere:pdfiumandroid:1.0.35")
22 | ```
23 |
24 | The arrow support is in a separate module. To use it, add the following in your app's build.gradle:
25 | ```
26 | implementation("io.legere:pdfium-android-kt-arrow:1.0.35")
27 | ```
28 | For more information on arrow-kt, see the https://arrow-kt.io/
29 |
30 | ## A few notes on performance.
31 |
32 | Opening documents and pages (as well the text pages) are relatively expensive operations. You probably want to avoid, for instance, opening the page on demand. Open the page and keep it open until you're done with it.
33 |
34 | The bitmap rendering API supports RGB_565 format, but its slow. The underlying pdfium APIs work with ARGB_888. The RGB_565 support has to allocate a buffer for ARGB_888, get the data, covert the data, release the buffer. The ARGB_888 support writes directly to the bitmap without any buffer allocation or conversion.
35 |
36 | Rendering directly to a Surface is fast, and doesn't require the memory overhead of bitmaps.
37 |
38 | ## What this project does
39 |
40 | We provide Android bindings for Pdfium. Pdfium is a library that Google produces.
41 |
42 | We don't do user interfaces. We are open to helping integrate this with your UI on contract basis.
43 |
44 | We are open to Pull Requests (PRs), but only if they keep the to scope of providing Android binding for Pdfium. And we don't accept PRs that customize the Pdfium libraries.
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/PdfDocumentTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.google.common.truth.Truth.assertThat
5 | import io.legere.pdfiumandroid.base.BasePDFTest
6 | import org.junit.After
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 |
11 | @RunWith(AndroidJUnit4::class)
12 | class PdfDocumentTest : BasePDFTest() {
13 | private lateinit var pdfDocument: PdfDocument
14 | private var pdfBytes: ByteArray? = null
15 |
16 | @Before
17 | fun setUp() {
18 | pdfBytes = getPdfBytes("f01.pdf")
19 |
20 | assertThat(pdfBytes).isNotNull()
21 |
22 | pdfDocument = PdfiumCore().newDocument(pdfBytes)
23 | }
24 |
25 | @After
26 | fun tearDown() {
27 | pdfDocument.close()
28 | }
29 |
30 | @Test
31 | fun getPageCount() {
32 | val pageCount = pdfDocument.getPageCount()
33 |
34 | assertThat(pageCount).isEqualTo(4)
35 | }
36 |
37 | @Test
38 | fun openPage() {
39 | val page = pdfDocument.openPage(0)
40 |
41 | assertThat(page).isNotNull()
42 | }
43 |
44 | @Test
45 | fun openPages() {
46 | val page = pdfDocument.openPages(0, 3)
47 |
48 | assertThat(page.size).isEqualTo(4)
49 | }
50 |
51 | @Test
52 | fun getDocumentMeta() {
53 | val meta = pdfDocument.getDocumentMeta()
54 |
55 | assertThat(meta).isNotNull()
56 | }
57 |
58 | @Test
59 | fun getTableOfContents() {
60 | // I don't think this test document has a table of contents
61 | val toc = pdfDocument.getTableOfContents()
62 |
63 | assertThat(toc).isNotNull()
64 | assertThat(toc.size).isEqualTo(0)
65 | }
66 |
67 | @Test
68 | fun openTextPage() {
69 | val page = pdfDocument.openPage(0)
70 | val textPage = page.openTextPage()
71 | assertThat(textPage).isNotNull()
72 | }
73 |
74 | // @Test
75 | // fun openTextPages() {
76 | // val textPages = pdfDocument.openTextPages(0, 3)
77 | // assertThat(textPages.size).isEqualTo(4)
78 | // }
79 |
80 | @Test
81 | fun saveAsCopy() {
82 | pdfDocument.saveAsCopy(
83 | object : PdfWriteCallback {
84 | override fun WriteBlock(data: ByteArray?): Int {
85 | // assertThat(data?.size).isEqualTo(pdfBytes?.size)
86 | // assertThat(data).isEqualTo(pdfBytes)
87 | return data?.size ?: 0
88 | }
89 | },
90 | )
91 | }
92 |
93 | @Test(expected = IllegalStateException::class)
94 | fun closeDocument() {
95 | var shouldBeClosed: PdfDocument?
96 | PdfiumCore().newDocument(pdfBytes).use { pdfDocument ->
97 | assertThat(pdfDocument).isNotNull()
98 | shouldBeClosed = pdfDocument
99 | }
100 |
101 | // Now it should be closed
102 | shouldBeClosed?.openPage(0) // This should throw an exception
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_save.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 |
7 | #ifndef PUBLIC_FPDF_SAVE_H_
8 | #define PUBLIC_FPDF_SAVE_H_
9 |
10 | // clang-format off
11 | // NOLINTNEXTLINE(build/include)
12 | #include "fpdfview.h"
13 |
14 | #ifdef __cplusplus
15 | extern "C" {
16 | #endif
17 |
18 | // Structure for custom file write
19 | typedef struct FPDF_FILEWRITE_ {
20 | //
21 | // Version number of the interface. Currently must be 1.
22 | //
23 | int version;
24 |
25 | // Method: WriteBlock
26 | // Output a block of data in your custom way.
27 | // Interface Version:
28 | // 1
29 | // Implementation Required:
30 | // Yes
31 | // Comments:
32 | // Called by function FPDF_SaveDocument
33 | // Parameters:
34 | // pThis - Pointer to the structure itself
35 | // pData - Pointer to a buffer to output
36 | // size - The size of the buffer.
37 | // Return value:
38 | // Should be non-zero if successful, zero for error.
39 | int (*WriteBlock)(struct FPDF_FILEWRITE_* pThis,
40 | const void* pData,
41 | unsigned long size);
42 | } FPDF_FILEWRITE;
43 |
44 | // Flags for FPDF_SaveAsCopy()
45 | #define FPDF_INCREMENTAL 1
46 | #define FPDF_NO_INCREMENTAL 2
47 | #define FPDF_REMOVE_SECURITY 3
48 |
49 | // Function: FPDF_SaveAsCopy
50 | // Saves the copy of specified document in custom way.
51 | // Parameters:
52 | // document - Handle to document, as returned by
53 | // FPDF_LoadDocument() or FPDF_CreateNewDocument().
54 | // pFileWrite - A pointer to a custom file write structure.
55 | // flags - Flags above that affect how the PDF gets saved.
56 | // Pass in 0 when there are no flags.
57 | // Return value:
58 | // TRUE for succeed, FALSE for failed.
59 | //
60 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SaveAsCopy(FPDF_DOCUMENT document,
61 | FPDF_FILEWRITE* pFileWrite,
62 | FPDF_DWORD flags);
63 |
64 | // Function: FPDF_SaveWithVersion
65 | // Same as FPDF_SaveAsCopy(), except the file version of the
66 | // saved document can be specified by the caller.
67 | // Parameters:
68 | // document - Handle to document.
69 | // pFileWrite - A pointer to a custom file write structure.
70 | // flags - The creating flags.
71 | // fileVersion - The PDF file version. File version: 14 for 1.4,
72 | // 15 for 1.5, ...
73 | // Return value:
74 | // TRUE if succeed, FALSE if failed.
75 | //
76 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
77 | FPDF_SaveWithVersion(FPDF_DOCUMENT document,
78 | FPDF_FILEWRITE* pFileWrite,
79 | FPDF_DWORD flags,
80 | int fileVersion);
81 |
82 | #ifdef __cplusplus
83 | }
84 | #endif
85 |
86 | #endif // PUBLIC_FPDF_SAVE_H_
87 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/suspend/PdfPageLinkKtTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import android.graphics.RectF
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.google.common.truth.Truth.assertThat
6 | import io.legere.pdfiumandroid.base.BasePDFTest
7 | import junit.framework.TestCase
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.runBlocking
10 | import kotlinx.coroutines.test.TestResult
11 | import kotlinx.coroutines.test.runTest
12 | import org.junit.After
13 | import org.junit.Before
14 | import org.junit.Test
15 | import org.junit.runner.RunWith
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class PdfPageLinkKtTest : BasePDFTest() {
19 | private lateinit var pdfDocument: PdfDocumentKt
20 | private lateinit var pdfPage: PdfPageKt
21 | private lateinit var pdfTextPage: PdfTextPageKt
22 |
23 | private var pdfBytes: ByteArray? = null
24 |
25 | @Before
26 | fun setUp() =
27 | runBlocking {
28 | pdfBytes = getPdfBytes("pdf-test.pdf")
29 |
30 | TestCase.assertNotNull(pdfBytes)
31 |
32 | pdfDocument = PdfiumCoreKt(Dispatchers.Unconfined).newDocument(pdfBytes)
33 | pdfPage = pdfDocument.openPage(0)
34 | pdfTextPage = pdfPage.openTextPage()
35 | }
36 |
37 | @After
38 | fun tearDown() {
39 | pdfTextPage.close()
40 | pdfPage.close()
41 | pdfDocument.close()
42 | }
43 |
44 | @Test
45 | fun testLink(): TestResult =
46 | runTest {
47 | val links = pdfTextPage.loadWebLink()
48 | assertThat(links).isNotNull()
49 | links.close()
50 | }
51 |
52 | @Test
53 | fun testCountWebLinks(): TestResult =
54 | runTest {
55 | val links = pdfTextPage.loadWebLink()
56 | assertThat(links).isNotNull()
57 | assertThat(links.countWebLinks()).isEqualTo(1)
58 | links.close()
59 | }
60 |
61 | @Test
62 | fun testGetTextRange(): TestResult =
63 | runTest {
64 | val links = pdfTextPage.loadWebLink()
65 | assertThat(links).isNotNull()
66 | assertThat(links.getTextRange(0)).isEqualTo(Pair(351, 31))
67 | links.close()
68 | }
69 |
70 | @Test
71 | fun testGetUrl(): TestResult =
72 | runTest {
73 | val links = pdfTextPage.loadWebLink()
74 | assertThat(links).isNotNull()
75 | val (_, count) = links.getTextRange(0)
76 | assertThat(links.getURL(0, count)).isEqualTo("http://www.education.gov.yk.ca/")
77 | links.close()
78 | }
79 |
80 | @Test
81 | fun testCountRects(): TestResult =
82 | runTest {
83 | val links = pdfTextPage.loadWebLink()
84 | assertThat(links).isNotNull()
85 | val count = links.countRects(0)
86 | assertThat(count).isEqualTo(1)
87 | links.close()
88 | }
89 |
90 | @Test
91 | fun testGetRect(): TestResult =
92 | runTest {
93 | val links = pdfTextPage.loadWebLink()
94 | assertThat(links).isNotNull()
95 | val count = links.getRect(0, 0)
96 | assertThat(count).isEqualTo(RectF(221.46f, 480.624f, 389.66394f, 469.152f))
97 | links.close()
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | id("com.android.application")
5 | id("org.jetbrains.kotlin.android")
6 | id("com.google.dagger.hilt.android")
7 | id("com.google.devtools.ksp")
8 | alias(libs.plugins.compose.compiler)
9 | alias(libs.plugins.detekt)
10 | alias(libs.plugins.kover)
11 | alias(libs.plugins.ktlint)
12 | }
13 | kotlin {
14 | compilerOptions {
15 | jvmTarget.set(JvmTarget.JVM_17)
16 | }
17 | }
18 |
19 | android {
20 | namespace = "io.legere.pdfiumandroidkt"
21 | compileSdk = 36
22 |
23 | defaultConfig {
24 | applicationId = "io.legere.pdfiumandroidkt"
25 | minSdk = 24
26 | versionCode = 1
27 | versionName = "1.0"
28 |
29 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
30 | vectorDrawables.useSupportLibrary = true
31 | }
32 |
33 | buildTypes {
34 | release {
35 | isMinifyEnabled = false
36 | proguardFiles(
37 | getDefaultProguardFile("proguard-android-optimize.txt"),
38 | "proguard-rules.pro",
39 | )
40 | }
41 | // maybeCreate("qa")
42 | // getByName("qa") {
43 | // matchingFallbacks += listOf("release")
44 | // isMinifyEnabled = true
45 | // signingConfig = signingConfigs.getByName("debug")
46 | // matchingFallbacks += listOf("release")
47 | // }
48 | }
49 | compileOptions {
50 | sourceCompatibility(JavaVersion.VERSION_17)
51 | targetCompatibility(JavaVersion.VERSION_17)
52 | }
53 |
54 | buildFeatures {
55 | compose = true
56 | }
57 |
58 | packaging {
59 | resources {
60 | excludes +=
61 | setOf(
62 | "/META-INF/{AL2.0,LGPL2.1}",
63 | )
64 | }
65 | }
66 |
67 | lint {
68 | abortOnError = true
69 | disable += "NotificationPermission"
70 | }
71 | }
72 |
73 | dependencies {
74 |
75 | implementation(project(":pdfiumandroid"))
76 | implementation(libs.androidx.core.ktx)
77 | implementation(libs.androidx.appcompat)
78 | implementation(libs.material)
79 | implementation(libs.androidx.lifecycle.runtime.ktx)
80 | implementation(libs.androidx.activity.compose)
81 | implementation(platform(libs.compose.bom))
82 | implementation(libs.compose.ui)
83 | implementation(libs.compose.ui.graphics)
84 | implementation(libs.compose.ui.tooling.preview)
85 | implementation(libs.compose.material3)
86 |
87 | implementation(libs.coil.compose)
88 | implementation(libs.compose.glide)
89 |
90 | implementation(libs.timber)
91 |
92 | implementation(libs.hilt.android)
93 | ksp(libs.hilt.compiler)
94 |
95 | testImplementation(libs.junit)
96 | androidTestImplementation(libs.androidx.junit)
97 | androidTestImplementation(libs.androidx.espresso.core)
98 | androidTestImplementation(platform(libs.compose.bom))
99 | androidTestImplementation(libs.compose.ui.test.junit4)
100 | debugImplementation(libs.compose.ui.tooling)
101 | debugImplementation(libs.compose.ui.test.manifest)
102 | }
103 |
104 | // allprojects {
105 | // tasks.withType(KotlinCompile).configureEach {
106 | // kotlinOptions {
107 | // jvmTarget = "1.8"
108 | // }
109 | // }
110 | // }
111 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/PdfPageLink.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid
2 |
3 | import android.graphics.RectF
4 | import java.io.Closeable
5 | import java.nio.charset.StandardCharsets
6 |
7 | @Suppress("TooManyFunctions")
8 | class PdfPageLink(
9 | private val pageLinkPtr: Long,
10 | ) : Closeable {
11 | fun countWebLinks(): Int {
12 | synchronized(PdfiumCore.lock) {
13 | return nativeCountWebLinks(pageLinkPtr)
14 | }
15 | }
16 |
17 | @Suppress("TooGenericExceptionCaught", "ReturnCount")
18 | fun getURL(
19 | index: Int,
20 | length: Int,
21 | ): String? {
22 | synchronized(PdfiumCore.lock) {
23 | try {
24 | val bytes = ByteArray(length * 2)
25 | val r =
26 | nativeGetURL(
27 | pageLinkPtr,
28 | index,
29 | length,
30 | bytes,
31 | )
32 |
33 | if (r <= 0) {
34 | return ""
35 | }
36 | return String(bytes, StandardCharsets.UTF_16LE)
37 | } catch (e: NullPointerException) {
38 | Logger.e(TAG, e, "mContext may be null")
39 | } catch (e: Exception) {
40 | Logger.e(TAG, e, "Exception throw from native")
41 | }
42 | return null
43 | }
44 | }
45 |
46 | fun countRects(index: Int): Int {
47 | synchronized(PdfiumCore.lock) {
48 | return nativeCountRects(pageLinkPtr, index)
49 | }
50 | }
51 |
52 | @Suppress("MagicNumber")
53 | fun getRect(
54 | linkIndex: Int,
55 | rectIndex: Int,
56 | ): RectF {
57 | synchronized(PdfiumCore.lock) {
58 | return nativeGetRect(pageLinkPtr, linkIndex, rectIndex).let {
59 | RectF(it[0], it[1], it[2], it[3])
60 | }
61 | }
62 | }
63 |
64 | fun getTextRange(index: Int): Pair {
65 | synchronized(PdfiumCore.lock) {
66 | return nativeGetTextRange(pageLinkPtr, index).let {
67 | Pair(it[0], it[1])
68 | }
69 | }
70 | }
71 |
72 | override fun close() {
73 | nativeClosePageLink(pageLinkPtr)
74 | }
75 |
76 | companion object {
77 | private val TAG = PdfPageLink::class.java.name
78 |
79 | @JvmStatic
80 | private external fun nativeClosePageLink(pageLinkPtr: Long)
81 |
82 | @JvmStatic
83 | private external fun nativeCountWebLinks(pageLinkPtr: Long): Int
84 |
85 | @JvmStatic
86 | private external fun nativeGetURL(
87 | pageLinkPtr: Long,
88 | index: Int,
89 | count: Int,
90 | result: ByteArray,
91 | ): Int
92 |
93 | @JvmStatic
94 | private external fun nativeCountRects(
95 | pageLinkPtr: Long,
96 | index: Int,
97 | ): Int
98 |
99 | @JvmStatic
100 | private external fun nativeGetRect(
101 | pageLinkPtr: Long,
102 | linkIndex: Int,
103 | rectIndex: Int,
104 | ): FloatArray
105 |
106 | @JvmStatic
107 | // needs to return a start and an end
108 | private external fun nativeGetTextRange(pageLinkPtr: Long, index: Int): IntArray
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/suspend/PdfDocumentKtTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.google.common.truth.Truth
5 | import io.legere.pdfiumandroid.PdfWriteCallback
6 | import io.legere.pdfiumandroid.base.BasePDFTest
7 | import junit.framework.TestCase
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.runBlocking
10 | import kotlinx.coroutines.test.runTest
11 | import org.junit.After
12 | import org.junit.Before
13 | import org.junit.Test
14 | import org.junit.runner.RunWith
15 |
16 | @RunWith(AndroidJUnit4::class)
17 | class PdfDocumentKtTest : BasePDFTest() {
18 | private lateinit var pdfDocument: PdfDocumentKt
19 | private var pdfBytes: ByteArray? = null
20 |
21 | @Before
22 | fun setUp() =
23 | runBlocking {
24 | pdfBytes = getPdfBytes("f01.pdf")
25 |
26 | TestCase.assertNotNull(pdfBytes)
27 |
28 | pdfDocument = PdfiumCoreKt(Dispatchers.Unconfined).newDocument(pdfBytes)
29 | }
30 |
31 | @After
32 | fun tearDown() =
33 | runTest {
34 | pdfDocument.close()
35 | }
36 |
37 | @Test
38 | fun getPageCount() =
39 | runTest {
40 | val pageCount = pdfDocument.getPageCount()
41 |
42 | assert(pageCount == 4) { "Page count should be 4" }
43 | }
44 |
45 | @Test
46 | fun openPage() =
47 | runTest {
48 | val page = pdfDocument.openPage(0)
49 |
50 | TestCase.assertNotNull(page)
51 | }
52 |
53 | @Test
54 | fun openPages() =
55 | runTest {
56 | val page = pdfDocument.openPages(0, 3)
57 |
58 | assert(page.size == 4) { "Page count should be 4" }
59 | }
60 |
61 | @Test
62 | fun getDocumentMeta() =
63 | runTest {
64 | val meta = pdfDocument.getDocumentMeta()
65 |
66 | TestCase.assertNotNull(meta)
67 | }
68 |
69 | @Test
70 | fun getTableOfContents() =
71 | runTest {
72 | // I don't think this test document has a table of contents
73 | val toc = pdfDocument.getTableOfContents()
74 |
75 | TestCase.assertNotNull(toc)
76 | Truth.assertThat(toc.size).isEqualTo(0)
77 | }
78 |
79 | @Test
80 | fun openTextPage() =
81 | runTest {
82 | val page = pdfDocument.openPage(0)
83 | val textPage = page.openTextPage()
84 | TestCase.assertNotNull(textPage)
85 | }
86 |
87 | @Test
88 | fun openTextPages() =
89 | runTest {
90 | val textPages = pdfDocument.openTextPages(0, 3)
91 | Truth.assertThat(textPages.size).isEqualTo(4)
92 | }
93 |
94 | @Test
95 | fun saveAsCopy() =
96 | runTest {
97 | pdfDocument.saveAsCopy(
98 | object : PdfWriteCallback {
99 | override fun WriteBlock(data: ByteArray?): Int {
100 | // Truth.assertThat(data?.size).isEqualTo(pdfBytes?.size)
101 | // Truth.assertThat(data).isEqualTo(pdfBytes)
102 | return data?.size ?: 0
103 | }
104 | },
105 | )
106 | }
107 |
108 | @Test(expected = IllegalStateException::class)
109 | fun close() =
110 | runTest {
111 | var documentAfterClose: PdfDocumentKt?
112 | PdfiumCoreKt(Dispatchers.Unconfined).newDocument(pdfBytes).use {
113 | documentAfterClose = it
114 | }
115 | documentAfterClose?.openPage(0)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/io/legere/pdfiumandroidkt/ui/PdfViewer.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("FunctionNaming", "ktlint:standard:function-naming")
2 |
3 | package io.legere.pdfiumandroidkt.ui
4 |
5 | import android.graphics.Matrix
6 | import android.graphics.Rect
7 | import android.graphics.RectF
8 | import android.view.Surface
9 | import androidx.compose.foundation.AndroidEmbeddedExternalSurface
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.LaunchedEffect
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableIntStateOf
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.setValue
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.graphics.Color
20 | import androidx.compose.ui.graphics.toArgb
21 | import io.legere.pdfiumandroid.suspend.PdfPageKtCache
22 | import io.legere.pdfiumandroidkt.PdfHolder
23 | import kotlinx.coroutines.Dispatchers
24 | import kotlinx.coroutines.withContext
25 | import kotlin.math.min
26 |
27 | @Composable
28 | fun PdfViewer(
29 | pageCache: PdfPageKtCache,
30 | pageNum: Int,
31 | ) {
32 | var surface: Surface? by remember { mutableStateOf(null) }
33 | var viewWidth by remember { mutableIntStateOf(0) }
34 | var viewHeight by remember { mutableIntStateOf(0) }
35 |
36 | AndroidEmbeddedExternalSurface(
37 | modifier = Modifier.fillMaxSize(),
38 | ) {
39 | onSurface { s, w, h ->
40 |
41 | s.lockCanvas(Rect(0, 0, w, h)).apply {
42 | drawColor(Color.White.toArgb())
43 | s.unlockCanvasAndPost(this)
44 | }
45 | surface = s
46 | viewWidth = w
47 | viewHeight = h
48 | }
49 | }
50 |
51 | LaunchedEffect(surface, pageNum, viewWidth, viewHeight) {
52 | val s = surface
53 | if (s != null && viewWidth > 0 && viewHeight > 0) {
54 | withContext(Dispatchers.IO) {
55 | drawPdf(
56 | s,
57 | viewWidth,
58 | viewHeight,
59 | pageCache,
60 | pageNum,
61 | )
62 | }
63 | }
64 | }
65 | }
66 |
67 | @Suppress("LongParameterList")
68 | suspend fun drawPdf(
69 | surface: Surface,
70 | surfaceWidth: Int,
71 | surfaceHeight: Int,
72 | pageCache: PdfPageKtCache,
73 | currentPage: Int,
74 | ) {
75 | if (!surface.isValid || surfaceWidth == 0 || surfaceHeight == 0) {
76 | // Skip rendering if the Surface is not valid or has zero dimensions
77 | return
78 | }
79 |
80 | val pageHolder = pageCache.get(currentPage)
81 | val pageWidth = pageHolder.pageWidth
82 | val pageHeight = pageHolder.pageHeight
83 | // Calculate scaling factor to fit page within Surface bounds
84 | val scaleFactor =
85 | min(
86 | surfaceWidth.toFloat() / pageWidth,
87 | surfaceHeight.toFloat() / pageHeight,
88 | )
89 |
90 | val height = (pageHeight * scaleFactor).toInt()
91 | val width = (pageWidth * scaleFactor).toInt()
92 |
93 | val startX = (surfaceWidth - width) / 2
94 | val startY = (surfaceHeight - height) / 2
95 |
96 | val matrix = Matrix()
97 | matrix.postScale(scaleFactor, scaleFactor)
98 | matrix.postTranslate(startX.toFloat(), startY.toFloat())
99 | val clipRect =
100 | RectF(0f, 0f, surfaceWidth.toFloat(), surfaceHeight.toFloat())
101 | pageHolder.page.renderPage(
102 | surface = surface,
103 | matrix,
104 | clipRect,
105 | )
106 | }
107 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfPageKtFCache.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.arrow
4 |
5 | import arrow.core.Either
6 | import io.legere.pdfiumandroid.suspend.PdfPageSuspendCacheBase
7 | import kotlinx.coroutines.CoroutineDispatcher
8 | import kotlinx.coroutines.Dispatchers
9 |
10 | /**
11 | * A cache for pages of a PDF document, designed to be used with Arrow and coroutines.
12 | *
13 | * This class is thread-safe.
14 | *
15 | * To use this class, you need to provide a `pdfDocument` and a `pageHolderFactory`.
16 | * The `pageHolderFactory` is a lambda that takes a `PdfPageKtF` and a `PdfTextPageKtF` and returns an instance of `H`.
17 | *
18 | * The generic type `H` is a holder for the page and text page. It can be a simple [PageHolderKtF] or a custom class.
19 | *
20 | * Example usage with [PageHolderKtF]:
21 | * ```
22 | * val pageCache = PdfPageKtFCache(pdfDocument) { page, textPage ->
23 | * PageHolderKtF(page, textPage)
24 | * }
25 | * ```
26 | *
27 | * Example usage with a custom holder:
28 | * ```
29 | * data class CustomPageHolder(
30 | * val page: PdfPageKtF,
31 | * val textPage: PdfTextPageKtF,
32 | * val someOtherData: String
33 | * ) : AutoCloseable {
34 | * override fun close() {
35 | * try {
36 | * textPage.close()
37 | * } finally {
38 | * page.close()
39 | * }
40 | * }
41 | * }
42 | *
43 | * val pageCache = PdfPageKtFCache(pdfDocument) { page, textPage ->
44 | * CustomPageHolder(page, textPage, "some other data")
45 | * }
46 | * ```
47 | *
48 | * @param H The type of the holder for the page and text page. Must be [AutoCloseable].
49 | * @property pdfDocument The [PdfDocumentKtF] to cache pages from.
50 | * @param dispatcher The [CoroutineDispatcher] to use for opening pages. Defaults to [Dispatchers.IO].
51 | * @property pageHolderFactory A factory for creating a holder for a page and text page.
52 | */
53 | class PdfPageKtFCache(
54 | private val pdfDocument: PdfDocumentKtF,
55 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
56 | private val pageHolderFactory: suspend (PdfPageKtF, PdfTextPageKtF) -> H,
57 | ) : AutoCloseable {
58 | private val suspendCache =
59 | object : PdfPageSuspendCacheBase(dispatcher) {
60 | @Suppress("MaxLineLength")
61 | override suspend fun openPageAndText(pageIndex: Int): H {
62 | val page = pdfDocument.document.openPage(pageIndex)
63 | val textPage = page.openTextPage()
64 | return pageHolderFactory(
65 | PdfPageKtF(page, dispatcher),
66 | PdfTextPageKtF(textPage, dispatcher),
67 | )
68 | }
69 | }
70 |
71 | /**
72 | * Get a page from the cache.
73 | *
74 | * @param pageIndex The index of the page to get.
75 | * @return An [Either] with the page holder or an error.
76 | */
77 | suspend fun getF(pageIndex: Int): Either =
78 | Either.catch {
79 | suspendCache.get(pageIndex)
80 | }
81 |
82 | override fun close() {
83 | suspendCache.close()
84 | }
85 | }
86 |
87 | /**
88 | * A holder for a [PdfPageKtF] and a [PdfTextPageKtF].
89 | *
90 | * @param TPage The type of the page.
91 | * @param TTextPage The type of the text page.
92 | * @property page The page.
93 | * @property textPage The text page.
94 | */
95 | data class PageHolderKtF(
96 | val page: TPage,
97 | val textPage: TTextPage,
98 | ) : AutoCloseable {
99 | override fun close() {
100 | try {
101 | textPage.close()
102 | } finally {
103 | page.close()
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/test/java/io/legere/pdfiumandroid/util/PdfiumNativeSourceBridgeTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.util
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import io.legere.pdfiumandroid.PdfiumSource
5 | import junit.framework.TestCase.fail
6 | import org.junit.Test
7 |
8 | class PdfiumNativeSourceBridgeTest {
9 | @Test
10 | @Suppress("SwallowedException")
11 | fun paramsDispatchedCorrectly() {
12 | var lastPosition = -1L
13 | var lastBufferSize = -1
14 | var lastSize = -1
15 | val bridge =
16 | PdfiumNativeSourceBridge(
17 | source =
18 | mockCustomSource { position, buffer, size ->
19 | lastPosition = position
20 | lastBufferSize = buffer.size
21 | lastSize = size
22 | size
23 | },
24 | )
25 |
26 | try {
27 | assertThat(bridge.read(1, 2)).isEqualTo(2)
28 | assertThat(lastPosition).isEqualTo(1)
29 | assertThat(lastBufferSize).isEqualTo(2)
30 | assertThat(lastSize).isEqualTo(2)
31 | } catch (t: Exception) {
32 | fail("Should not throw exception")
33 | }
34 | }
35 |
36 | @Test
37 | @Suppress("SwallowedException")
38 | fun sizeExceedsIntRange() {
39 | val bridge = PdfiumNativeSourceBridge(mockCustomSource())
40 |
41 | try {
42 | assertThat(bridge.read(0, Long.MAX_VALUE)).isEqualTo(0)
43 | } catch (t: Exception) {
44 | fail("Should not throw exception")
45 | }
46 | }
47 |
48 | @Test
49 | @Suppress("SwallowedException")
50 | fun sourceThrowsException() {
51 | val bridge = PdfiumNativeSourceBridge(mockCustomSource { _, _, _ -> error("Exception!") })
52 |
53 | try {
54 | assertThat(bridge.read(0, 1)).isEqualTo(0)
55 | } catch (t: Exception) {
56 | fail("Should not throw exception")
57 | }
58 | }
59 |
60 | @Test
61 | @Suppress("SwallowedException")
62 | fun sourceReturnsNegativeValue() {
63 | val bridge = PdfiumNativeSourceBridge(mockCustomSource { _, _, _ -> -1 })
64 |
65 | try {
66 | assertThat(bridge.read(0, 1)).isEqualTo(0)
67 | } catch (t: Exception) {
68 | fail("Should not throw exception")
69 | }
70 | }
71 |
72 | @Test
73 | @Suppress("SwallowedException")
74 | fun bufferRescales() {
75 | var lastBufferSize = -1
76 | val bridge =
77 | PdfiumNativeSourceBridge(
78 | source =
79 | mockCustomSource { _, buffer, _ ->
80 | lastBufferSize = buffer.size
81 | buffer.size
82 | },
83 | )
84 |
85 | try {
86 | assertThat(bridge.read(0, 1)).isEqualTo(1)
87 | assertThat(lastBufferSize).isEqualTo(1)
88 |
89 | assertThat(bridge.read(0, 2)).isEqualTo(2)
90 | assertThat(lastBufferSize).isEqualTo(2)
91 | } catch (t: Exception) {
92 | fail("Should not throw exception")
93 | }
94 | }
95 |
96 | // todo: replace with MockK or Mockito when available
97 | private fun mockCustomSource(
98 | length: Long = 0L,
99 | read: (Long, ByteArray, Int) -> Int = { _, _, size -> size },
100 | ): PdfiumSource =
101 | object : PdfiumSource {
102 | override val length: Long
103 | get() = length
104 |
105 | override fun read(
106 | position: Long,
107 | buffer: ByteArray,
108 | size: Int,
109 | ): Int = read(position, buffer, size)
110 |
111 | override fun close() {
112 | // nothing to close
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/java/io/legere/pdfiumandroid/arrow/PdfPageLinkKtFTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | import android.graphics.RectF
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import arrow.core.raise.either
6 | import com.google.common.truth.Truth
7 | import io.legere.pdfiumandroid.arrow.base.BasePDFTest
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.runBlocking
10 | import kotlinx.coroutines.test.TestResult
11 | import kotlinx.coroutines.test.runTest
12 | import org.junit.After
13 | import org.junit.Before
14 | import org.junit.Test
15 | import org.junit.runner.RunWith
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class PdfPageLinkKtFTest : BasePDFTest() {
19 | private lateinit var pdfDocument: PdfDocumentKtF
20 | private lateinit var pdfPage: PdfPageKtF
21 | private lateinit var pdfTextPage: PdfTextPageKtF
22 | private var pdfBytes: ByteArray? = null
23 |
24 | @Before
25 | fun setUp() =
26 | runBlocking {
27 | pdfBytes = getPdfBytes("pdf-test.pdf")
28 |
29 | Truth.assertThat(pdfBytes).isNotNull()
30 |
31 | pdfDocument = PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).getOrNull()!!
32 | pdfPage = pdfDocument.openPage(0).getOrNull()!!
33 | pdfTextPage = pdfPage.openTextPage().getOrNull()!!
34 | }
35 |
36 | @After
37 | fun tearDown() {
38 | pdfTextPage.close()
39 | pdfPage.close()
40 | pdfDocument.close()
41 | }
42 |
43 | @Test
44 | fun testLink(): TestResult =
45 | runTest {
46 | either {
47 | pdfTextPage.loadWebLink().bind().use { links ->
48 | Truth.assertThat(links).isNotNull()
49 | }
50 | }
51 | }
52 |
53 | @Test
54 | fun testCountWebLinks(): TestResult =
55 | runTest {
56 | either {
57 | pdfTextPage.loadWebLink().bind().use { links ->
58 | Truth.assertThat(links).isNotNull()
59 | Truth.assertThat(links.countWebLinks().bind()).isEqualTo(1)
60 | }
61 | }
62 | }
63 |
64 | @Test
65 | fun testGetTextRange(): TestResult =
66 | runTest {
67 | either {
68 | pdfTextPage.loadWebLink().bind().use { links ->
69 | Truth.assertThat(links).isNotNull()
70 | Truth.assertThat(links.getTextRange(0).bind()).isEqualTo(Pair(351, 31))
71 | }
72 | }
73 | }
74 |
75 | @Test
76 | fun testGetUrl(): TestResult =
77 | runTest {
78 | either {
79 | pdfTextPage.loadWebLink().bind().use { links ->
80 | Truth.assertThat(links).isNotNull()
81 | val (_, count) = links.getTextRange(0).bind()
82 | Truth
83 | .assertThat(links.getURL(0, count).bind())
84 | .isEqualTo("http://www.education.gov.yk.ca/")
85 | }
86 | }
87 | }
88 |
89 | @Test
90 | fun testCountRects(): TestResult =
91 | runTest {
92 | either {
93 | pdfTextPage.loadWebLink().bind().use { links ->
94 | Truth.assertThat(links).isNotNull()
95 | val count = links.countRects(0).bind()
96 | Truth.assertThat(count).isEqualTo(1)
97 | }
98 | }
99 | }
100 |
101 | @Test
102 | fun testGetRect(): TestResult =
103 | runTest {
104 | either {
105 | pdfTextPage.loadWebLink().bind().use { links ->
106 | Truth.assertThat(links).isNotNull()
107 | val count = links.getRect(0, 0).bind()
108 | Truth
109 | .assertThat(count)
110 | .isEqualTo(RectF(221.46f, 480.624f, 389.66394f, 469.152f))
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/androidTest/java/io/legere/pdfiumandroid/arrow/PdfDocumentKtFTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.arrow
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import arrow.core.raise.either
5 | import com.google.common.truth.Truth.assertThat
6 | import io.legere.pdfiumandroid.PdfWriteCallback
7 | import io.legere.pdfiumandroid.arrow.base.BasePDFTest
8 | import junit.framework.TestCase
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.runBlocking
11 | import kotlinx.coroutines.test.runTest
12 | import org.junit.After
13 | import org.junit.Before
14 | import org.junit.Test
15 | import org.junit.runner.RunWith
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class PdfDocumentKtFTest : BasePDFTest() {
19 | private lateinit var pdfDocument: PdfDocumentKtF
20 | private var pdfBytes: ByteArray? = null
21 |
22 | @Before
23 | fun setUp() =
24 | runBlocking {
25 | pdfBytes = getPdfBytes("f01.pdf")
26 |
27 | assertThat(pdfBytes).isNotNull()
28 |
29 | pdfDocument = PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).getOrNull()!!
30 | }
31 |
32 | @After
33 | fun tearDown() =
34 | runTest {
35 | pdfDocument.close()
36 | }
37 |
38 | @Test
39 | fun getPageCount() =
40 | runTest {
41 | either {
42 | val pageCount = pdfDocument.getPageCount().bind()
43 |
44 | assertThat(pageCount).isEqualTo(4)
45 | }
46 | }
47 |
48 | @Test
49 | fun openPage() =
50 | runTest {
51 | either {
52 | val page = pdfDocument.openPage(0).bind()
53 |
54 | assertThat(page).isNotNull()
55 | }
56 | }
57 |
58 | @Test
59 | fun openPages() =
60 | runTest {
61 | either {
62 | val page = pdfDocument.openPages(0, 3).bind()
63 |
64 | assertThat(page.size).isEqualTo(4)
65 | }
66 | }
67 |
68 | @Test
69 | fun getDocumentMeta() =
70 | runTest {
71 | either {
72 | val meta = pdfDocument.getDocumentMeta().bind()
73 |
74 | assertThat(meta).isNotNull()
75 | }
76 | }
77 |
78 | @Test
79 | fun getTableOfContents() =
80 | runTest {
81 | either {
82 | // I don't think this test document has a table of contents
83 | val toc = pdfDocument.getTableOfContents().bind()
84 |
85 | TestCase.assertNotNull(toc)
86 | assertThat(toc.size).isEqualTo(0)
87 | }
88 | }
89 |
90 | @Test
91 | fun openTextPage() =
92 | runTest {
93 | either {
94 | val page = pdfDocument.openPage(0).bind()
95 | val textPage = page.openTextPage().bind()
96 | assertThat(textPage).isNotNull()
97 | }
98 | }
99 |
100 | @Test
101 | fun openTextPages() =
102 | runTest {
103 | either {
104 | val textPages = pdfDocument.openTextPages(0, 3).bind()
105 | assertThat(textPages.size).isEqualTo(4)
106 | }
107 | }
108 |
109 | @Test
110 | fun saveAsCopy() =
111 | runTest {
112 | pdfDocument.saveAsCopy(
113 | object : PdfWriteCallback {
114 | override fun WriteBlock(data: ByteArray?): Int {
115 | // Truth.assertThat(data?.size).isEqualTo(pdfBytes?.size)
116 | // Truth.assertThat(data).isEqualTo(pdfBytes)
117 | return data?.size ?: 0
118 | }
119 | },
120 | )
121 | }
122 |
123 | fun close() =
124 | runTest {
125 | either {
126 | var documentAfterClose: PdfDocumentKtF?
127 | PdfiumCoreKtF(Dispatchers.Unconfined).newDocument(pdfBytes).bind().use {
128 | documentAfterClose = it
129 | }
130 | documentAfterClose?.openPage(0)
131 | }.mapLeft {
132 | assertThat(it).isInstanceOf(PdfiumKtFErrors.AlreadyClosed::class.java)
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/androidstudio,android,c++,kotlin
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,android,c++,kotlin
3 | ### Android ###
4 | # Gradle files
5 | .gradle/
6 | build/
7 | # Local configuration file (sdk path, etc)
8 | local.properties
9 | # Log/OS Files
10 | *.log
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 | # IntelliJ
18 | *.iml
19 | .idea/
20 | misc.xml
21 | deploymentTargetDropDown.xml
22 | render.experimental.xml
23 | # Keystore files
24 | *.jks
25 | *.keystore
26 | # Google Services (e.g. APIs or Firebase)
27 | google-services.json
28 | # Android Profiling
29 | *.hprof
30 | ### Android Patch ###
31 | gen-external-apklibs
32 | # Replacement of .externalNativeBuild directories introduced
33 | # with Android Studio 3.5.
34 |
35 | ### C++ ###
36 | # Prerequisites
37 | *.d
38 |
39 | # Compiled Object files
40 | *.slo
41 | *.lo
42 | *.o
43 | *.obj
44 |
45 | # Precompiled Headers
46 | *.gch
47 | *.pch
48 |
49 | # Compiled Dynamic libraries
50 | *.dylib
51 | *.dll
52 |
53 | # Fortran module files
54 | *.mod
55 | *.smod
56 |
57 | # Compiled Static libraries
58 | *.lai
59 | *.la
60 | *.a
61 | *.lib
62 |
63 | # Executables
64 | *.exe
65 | *.out
66 | *.app
67 |
68 | ### Kotlin ###
69 | # Compiled class file
70 | *.class
71 |
72 | # Log file
73 |
74 | # BlueJ files
75 | *.ctxt
76 |
77 | # Mobile Tools for Java (J2ME)
78 | .mtj.tmp/
79 |
80 | # Package Files #
81 | *.jar
82 | *.war
83 | *.nar
84 | *.ear
85 | *.zip
86 | *.tar.gz
87 | *.rar
88 |
89 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
90 | hs_err_pid*
91 | replay_pid*
92 |
93 | ### AndroidStudio ###
94 | # Covers files to be ignored for android development using Android Studio.
95 |
96 | # Built application files
97 | *.ap_
98 | *.aab
99 |
100 | # Files for the ART/Dalvik VM
101 | *.dex
102 |
103 | # Java class files
104 |
105 | # Generated files
106 | bin/
107 | gen/
108 | out/
109 |
110 | # Gradle files
111 | .gradle
112 |
113 | # Signing files
114 | .signing/
115 |
116 | # Local configuration file (sdk path, etc)
117 |
118 | # Proguard folder generated by Eclipse
119 | proguard/
120 |
121 | # Log Files
122 |
123 | # Android Studio
124 | /*/build/
125 | /*/local.properties
126 | /*/out
127 | /*/*/build
128 | /*/*/production
129 | .navigation/
130 | *.ipr
131 | *~
132 | *.swp
133 |
134 | # Keystore files
135 |
136 | # Google Services (e.g. APIs or Firebase)
137 | # google-services.json
138 |
139 | # Android Patch
140 |
141 | # External native build folder generated in Android Studio 2.2 and later
142 | .externalNativeBuild
143 |
144 | # NDK
145 | obj/
146 |
147 | # IntelliJ IDEA
148 | *.iws
149 | /out/
150 |
151 | # User-specific configurations
152 | .idea/caches/
153 | .idea/libraries/
154 | .idea/shelf/
155 | .idea/workspace.xml
156 | .idea/tasks.xml
157 | .idea/.name
158 | .idea/compiler.xml
159 | .idea/copyright/profiles_settings.xml
160 | .idea/encodings.xml
161 | .idea/misc.xml
162 | .idea/modules.xml
163 | .idea/scopes/scope_settings.xml
164 | .idea/dictionaries
165 | .idea/vcs.xml
166 | .idea/jsLibraryMappings.xml
167 | .idea/datasources.xml
168 | .idea/dataSources.ids
169 | .idea/sqlDataSources.xml
170 | .idea/dynamic.xml
171 | .idea/uiDesigner.xml
172 | .idea/assetWizardSettings.xml
173 | .idea/gradle.xml
174 | .idea/jarRepositories.xml
175 | .idea/navEditor.xml
176 |
177 | # Legacy Eclipse project files
178 | .classpath
179 | .project
180 | .cproject
181 | .settings/
182 |
183 | # Mobile Tools for Java (J2ME)
184 |
185 | # Package Files #
186 |
187 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
188 |
189 | ## Plugin-specific files:
190 |
191 | # mpeltonen/sbt-idea plugin
192 | .idea_modules/
193 |
194 | # JIRA plugin
195 | atlassian-ide-plugin.xml
196 |
197 | # Mongo Explorer plugin
198 | .idea/mongoSettings.xml
199 |
200 | # Crashlytics plugin (for Android Studio and IntelliJ)
201 | com_crashlytics_export_strings.xml
202 | crashlytics.properties
203 | crashlytics-build.properties
204 | fabric.properties
205 |
206 | ### AndroidStudio Patch ###
207 |
208 | !/gradle/wrapper/gradle-wrapper.jar
209 |
210 | # End of https://www.toptal.com/developers/gitignore/api/androidstudio,android,c++,kotlin
211 |
212 | /*/.cxx
213 | /x/**
214 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_ext.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 |
7 | #ifndef PUBLIC_FPDF_EXT_H_
8 | #define PUBLIC_FPDF_EXT_H_
9 |
10 | #include
11 |
12 | // NOLINTNEXTLINE(build/include)
13 | #include "fpdfview.h"
14 |
15 | #ifdef __cplusplus
16 | extern "C" {
17 | #endif // __cplusplus
18 |
19 | // Unsupported XFA form.
20 | #define FPDF_UNSP_DOC_XFAFORM 1
21 | // Unsupported portable collection.
22 | #define FPDF_UNSP_DOC_PORTABLECOLLECTION 2
23 | // Unsupported attachment.
24 | #define FPDF_UNSP_DOC_ATTACHMENT 3
25 | // Unsupported security.
26 | #define FPDF_UNSP_DOC_SECURITY 4
27 | // Unsupported shared review.
28 | #define FPDF_UNSP_DOC_SHAREDREVIEW 5
29 | // Unsupported shared form, acrobat.
30 | #define FPDF_UNSP_DOC_SHAREDFORM_ACROBAT 6
31 | // Unsupported shared form, filesystem.
32 | #define FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM 7
33 | // Unsupported shared form, email.
34 | #define FPDF_UNSP_DOC_SHAREDFORM_EMAIL 8
35 | // Unsupported 3D annotation.
36 | #define FPDF_UNSP_ANNOT_3DANNOT 11
37 | // Unsupported movie annotation.
38 | #define FPDF_UNSP_ANNOT_MOVIE 12
39 | // Unsupported sound annotation.
40 | #define FPDF_UNSP_ANNOT_SOUND 13
41 | // Unsupported screen media annotation.
42 | #define FPDF_UNSP_ANNOT_SCREEN_MEDIA 14
43 | // Unsupported screen rich media annotation.
44 | #define FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA 15
45 | // Unsupported attachment annotation.
46 | #define FPDF_UNSP_ANNOT_ATTACHMENT 16
47 | // Unsupported signature annotation.
48 | #define FPDF_UNSP_ANNOT_SIG 17
49 |
50 | // Interface for unsupported feature notifications.
51 | typedef struct _UNSUPPORT_INFO {
52 | // Version number of the interface. Must be 1.
53 | int version;
54 |
55 | // Unsupported object notification function.
56 | // Interface Version: 1
57 | // Implementation Required: Yes
58 | //
59 | // pThis - pointer to the interface structure.
60 | // nType - the type of unsupported object. One of the |FPDF_UNSP_*| entries.
61 | void (*FSDK_UnSupport_Handler)(struct _UNSUPPORT_INFO* pThis, int nType);
62 | } UNSUPPORT_INFO;
63 |
64 | // Setup an unsupported object handler.
65 | //
66 | // unsp_info - Pointer to an UNSUPPORT_INFO structure.
67 | //
68 | // Returns TRUE on success.
69 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
70 | FSDK_SetUnSpObjProcessHandler(UNSUPPORT_INFO* unsp_info);
71 |
72 | // Set replacement function for calls to time().
73 | //
74 | // This API is intended to be used only for testing, thus may cause PDFium to
75 | // behave poorly in production environments.
76 | //
77 | // func - Function pointer to alternate implementation of time(), or
78 | // NULL to restore to actual time() call itself.
79 | FPDF_EXPORT void FPDF_CALLCONV FSDK_SetTimeFunction(time_t (*func)());
80 |
81 | // Set replacement function for calls to localtime().
82 | //
83 | // This API is intended to be used only for testing, thus may cause PDFium to
84 | // behave poorly in production environments.
85 | //
86 | // func - Function pointer to alternate implementation of localtime(), or
87 | // NULL to restore to actual localtime() call itself.
88 | FPDF_EXPORT void FPDF_CALLCONV
89 | FSDK_SetLocaltimeFunction(struct tm* (*func)(const time_t*));
90 |
91 | // Unknown page mode.
92 | #define PAGEMODE_UNKNOWN -1
93 | // Document outline, and thumbnails hidden.
94 | #define PAGEMODE_USENONE 0
95 | // Document outline visible.
96 | #define PAGEMODE_USEOUTLINES 1
97 | // Thumbnail images visible.
98 | #define PAGEMODE_USETHUMBS 2
99 | // Full-screen mode, no menu bar, window controls, or other decorations visible.
100 | #define PAGEMODE_FULLSCREEN 3
101 | // Optional content group panel visible.
102 | #define PAGEMODE_USEOC 4
103 | // Attachments panel visible.
104 | #define PAGEMODE_USEATTACHMENTS 5
105 |
106 | // Get the document's PageMode.
107 | //
108 | // doc - Handle to document.
109 | //
110 | // Returns one of the |PAGEMODE_*| flags defined above.
111 | //
112 | // The page mode defines how the document should be initially displayed.
113 | FPDF_EXPORT int FPDF_CALLCONV FPDFDoc_GetPageMode(FPDF_DOCUMENT document);
114 |
115 | #ifdef __cplusplus
116 | } // extern "C"
117 | #endif // __cplusplus
118 |
119 | #endif // PUBLIC_FPDF_EXT_H_
120 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/utils/Mutex.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #ifndef _LIBS_UTILS_MUTEX_H
18 | #define _LIBS_UTILS_MUTEX_H
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | #if defined(HAVE_PTHREADS)
25 | # include
26 | #endif
27 |
28 | #include "Errors.h"
29 |
30 | // ---------------------------------------------------------------------------
31 | namespace android {
32 | // ---------------------------------------------------------------------------
33 |
34 | class Condition;
35 |
36 | /*
37 | * Simple mutex class. The implementation is system-dependent.
38 | *
39 | * The mutex must be unlocked by the thread that locked it. They are not
40 | * recursive, i.e. the same thread can't lock it multiple times.
41 | */
42 | class Mutex {
43 | public:
44 | enum {
45 | PRIVATE = 0,
46 | SHARED = 1
47 | };
48 |
49 | Mutex();
50 | Mutex(const char* name);
51 | Mutex(int type, const char* name = NULL);
52 | ~Mutex();
53 |
54 | // lock or unlock the mutex
55 | status_t lock();
56 | void unlock();
57 |
58 | // lock if possible; returns 0 on success, error otherwise
59 | status_t tryLock();
60 |
61 | // Manages the mutex automatically. It'll be locked when Autolock is
62 | // constructed and released when Autolock goes out of scope.
63 | class Autolock {
64 | public:
65 | inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
66 | inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
67 | inline ~Autolock() { mLock.unlock(); }
68 | private:
69 | Mutex& mLock;
70 | };
71 |
72 | private:
73 | friend class Condition;
74 |
75 | // A mutex cannot be copied
76 | Mutex(const Mutex&);
77 | Mutex& operator = (const Mutex&);
78 |
79 | #if defined(HAVE_PTHREADS)
80 | pthread_mutex_t mMutex;
81 | #else
82 | void _init();
83 | void* mState;
84 | #endif
85 | };
86 |
87 | // ---------------------------------------------------------------------------
88 |
89 | #if defined(HAVE_PTHREADS)
90 |
91 | inline Mutex::Mutex() {
92 | pthread_mutex_init(&mMutex, NULL);
93 | }
94 | inline Mutex::Mutex(__attribute__((unused)) const char* name) {
95 | pthread_mutex_init(&mMutex, NULL);
96 | }
97 | inline Mutex::Mutex(int type, __attribute__((unused)) const char* name) {
98 | if (type == SHARED) {
99 | pthread_mutexattr_t attr;
100 | pthread_mutexattr_init(&attr);
101 | pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
102 | pthread_mutex_init(&mMutex, &attr);
103 | pthread_mutexattr_destroy(&attr);
104 | } else {
105 | pthread_mutex_init(&mMutex, NULL);
106 | }
107 | }
108 | inline Mutex::~Mutex() {
109 | pthread_mutex_destroy(&mMutex);
110 | }
111 | inline status_t Mutex::lock() {
112 | return -pthread_mutex_lock(&mMutex);
113 | }
114 | inline void Mutex::unlock() {
115 | pthread_mutex_unlock(&mMutex);
116 | }
117 | inline status_t Mutex::tryLock() {
118 | return -pthread_mutex_trylock(&mMutex);
119 | }
120 |
121 | #endif // HAVE_PTHREADS
122 |
123 | // ---------------------------------------------------------------------------
124 |
125 | /*
126 | * Automatic mutex. Declare one of these at the top of a function.
127 | * When the function returns, it will go out of scope, and release the
128 | * mutex.
129 | */
130 |
131 | typedef Mutex::Autolock AutoMutex;
132 |
133 | // ---------------------------------------------------------------------------
134 | }; // namespace android
135 | // ---------------------------------------------------------------------------
136 |
137 | #endif // _LIBS_UTILS_MUTEX_H
138 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | kotlin-stdlib = "2.2.21"
3 | kotlin_version = "2.2.21"
4 | ksp_version = "2.3.0"
5 | hilt_version = "2.57.2"
6 | gradleplugin = "8.13.1"
7 | detekt_version = "1.23.8"
8 | kover_version = "0.9.3"
9 | ktlint_version = "14.0.1"
10 | activity-compose = "1.11.0"
11 | compose-bom = "2025.11.00"
12 | androidx-junit = "1.3.0"
13 | appcompat = "1.7.1"
14 | coil-compose = "2.7.0"
15 | glide = "1.0.0-beta08"
16 | core-ktx = "1.17.0"
17 | core-testing = "2.2.0"
18 | espresso-core = "3.7.0"
19 | hilt-android = "2.57.2"
20 | junit = "4.13.2"
21 | kotlinx-coroutines-android = "1.10.2"
22 | lifecycle-runtime-ktx = "2.9.4"
23 | material = "1.13.0"
24 | timber = "5.0.1"
25 | truth = "1.4.5"
26 | arrow = "2.2.0"
27 | annotation-jvm = "1.9.1"
28 | jreleaser = "1.20.0"
29 | runner = "1.7.0"
30 | guava = "33.5.0-android"
31 |
32 | [libraries]
33 | compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
34 | compose-ui = { module = "androidx.compose.ui:ui" }
35 | compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
36 | compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
37 | compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
38 | compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
39 | compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
40 | compose-material3 = { module = "androidx.compose.material3:material3" }
41 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
42 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
43 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
44 | androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "core-testing" }
45 | androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso-core" }
46 | androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
47 | androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
48 | coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" }
49 | compose-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
50 | hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" }
51 | hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt-android" }
52 | junit = { module = "junit:junit", version.ref = "junit" }
53 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-stdlib" }
54 | kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" }
55 | kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines-android" }
56 | material = { module = "com.google.android.material:material", version.ref = "material" }
57 | timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
58 | truth = { module = "com.google.truth:truth", version.ref = "truth" }
59 | arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
60 | arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" }
61 | androidx-annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotation-jvm" }
62 | androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
63 | guava = { module = "com.google.guava:guava", version.ref = "guava" }
64 |
65 | [bundles]
66 |
67 | [plugins]
68 | android-application = { id = "com.android.application", version.ref = "gradleplugin" }
69 | android-library = { id = "com.android.library", version.ref = "gradleplugin" }
70 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin_version" }
71 | hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt_version" }
72 | kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp_version" }
73 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin_version" }
74 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt_version" }
75 | kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover_version" }
76 | ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint_version" }
77 | jreleaser = { id = "org.jreleaser", version.ref = "jreleaser" }
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/PdfTextPageKt.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.suspend
4 |
5 | import android.graphics.RectF
6 | import androidx.annotation.Keep
7 | import io.legere.pdfiumandroid.FindFlags
8 | import io.legere.pdfiumandroid.Logger
9 | import io.legere.pdfiumandroid.PdfTextPage
10 | import io.legere.pdfiumandroid.WordRangeRect
11 | import kotlinx.coroutines.CoroutineDispatcher
12 | import kotlinx.coroutines.withContext
13 | import java.io.Closeable
14 |
15 | /**
16 | * PdfTextPageKt represents a single text page of a PDF file.
17 | * @property page the [PdfTextPage] to wrap
18 | * @property dispatcher the [CoroutineDispatcher] to use for suspending calls
19 | */
20 | @Suppress("TooManyFunctions")
21 | @Keep
22 | class PdfTextPageKt(
23 | val page: PdfTextPage,
24 | private val dispatcher: CoroutineDispatcher,
25 | ) : Closeable {
26 | /**
27 | * suspend version of [PdfTextPage.textPageCountChars]
28 | */
29 | suspend fun textPageCountChars(): Int =
30 | withContext(dispatcher) {
31 | page.textPageCountChars()
32 | }
33 |
34 | /**
35 | * suspend version of [PdfTextPage.textPageGetText]
36 | */
37 | suspend fun textPageGetText(
38 | startIndex: Int,
39 | length: Int,
40 | ): String? =
41 | withContext(dispatcher) {
42 | page.textPageGetText(startIndex, length)
43 | }
44 |
45 | /**
46 | * suspend version of [PdfTextPage.textPageGetUnicode]
47 | */
48 | suspend fun textPageGetUnicode(index: Int): Char =
49 | withContext(dispatcher) {
50 | page.textPageGetUnicode(index)
51 | }
52 |
53 | /**
54 | * suspend version of [PdfTextPage.textPageGetCharBox]
55 | */
56 | suspend fun textPageGetCharBox(index: Int): RectF? =
57 | withContext(dispatcher) {
58 | page.textPageGetCharBox(index)
59 | }
60 |
61 | /**
62 | * suspend version of [PdfTextPage.textPageGetCharIndexAtPos]
63 | */
64 | suspend fun textPageGetCharIndexAtPos(
65 | x: Double,
66 | y: Double,
67 | xTolerance: Double,
68 | yTolerance: Double,
69 | ): Int =
70 | withContext(dispatcher) {
71 | page.textPageGetCharIndexAtPos(x, y, xTolerance, yTolerance)
72 | }
73 |
74 | /**
75 | * suspend version of [PdfTextPage.textPageCountRects]
76 | */
77 | suspend fun textPageCountRects(
78 | startIndex: Int,
79 | count: Int,
80 | ): Int =
81 | withContext(dispatcher) {
82 | page.textPageCountRects(startIndex, count)
83 | }
84 |
85 | /**
86 | * suspend version of [PdfTextPage.textPageGetRect]
87 | */
88 | suspend fun textPageGetRect(rectIndex: Int): RectF? =
89 | withContext(dispatcher) {
90 | page.textPageGetRect(rectIndex)
91 | }
92 |
93 | /**
94 | * suspend version of [PdfTextPage.textPageGetRectsForRanges]
95 | */
96 | suspend fun textPageGetRectsForRanges(wordRanges: IntArray): List? =
97 | withContext(dispatcher) {
98 | page.textPageGetRectsForRanges(wordRanges)
99 | }
100 |
101 | /**
102 | * suspend version of [PdfTextPage.textPageGetBoundedText]
103 | */
104 | suspend fun textPageGetBoundedText(
105 | rect: RectF,
106 | length: Int,
107 | ): String? =
108 | withContext(dispatcher) {
109 | page.textPageGetBoundedText(rect, length)
110 | }
111 |
112 | /**
113 | * suspend version of [PdfTextPage.getFontSize]
114 | */
115 | suspend fun getFontSize(charIndex: Int): Double =
116 | withContext(dispatcher) {
117 | page.getFontSize(charIndex)
118 | }
119 |
120 | suspend fun findStart(
121 | findWhat: String,
122 | flags: Set,
123 | startIndex: Int,
124 | ): FindResultKt? =
125 | withContext(dispatcher) {
126 | val findResult = page.findStart(findWhat, flags, startIndex)
127 | if (findResult == null) {
128 | null
129 | } else {
130 | FindResultKt(findResult, dispatcher)
131 | }
132 | }
133 |
134 | suspend fun loadWebLink(): PdfPageLinkKt =
135 | withContext(dispatcher) {
136 | PdfPageLinkKt(page.loadWebLink(), dispatcher)
137 | }
138 |
139 | /**
140 | * Close the page and free all resources.
141 | */
142 | override fun close() {
143 | page.close()
144 | }
145 |
146 | fun safeClose(): Boolean =
147 | try {
148 | page.close()
149 | true
150 | } catch (e: IllegalStateException) {
151 | Logger.e("PdfTextPageKt", e, "PdfTextPageKt.safeClose")
152 | false
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_ppo.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 |
7 | #ifndef PUBLIC_FPDF_PPO_H_
8 | #define PUBLIC_FPDF_PPO_H_
9 |
10 | // NOLINTNEXTLINE(build/include)
11 | #include "fpdfview.h"
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif
16 |
17 | // Experimental API.
18 | // Import pages to a FPDF_DOCUMENT.
19 | //
20 | // dest_doc - The destination document for the pages.
21 | // src_doc - The document to be imported.
22 | // page_indices - An array of page indices to be imported. The first page is
23 | // zero. If |page_indices| is NULL, all pages from |src_doc|
24 | // are imported.
25 | // length - The length of the |page_indices| array.
26 | // index - The page index at which to insert the first imported page
27 | // into |dest_doc|. The first page is zero.
28 | //
29 | // Returns TRUE on success. Returns FALSE if any pages in |page_indices| is
30 | // invalid.
31 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
32 | FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc,
33 | FPDF_DOCUMENT src_doc,
34 | const int* page_indices,
35 | unsigned long length,
36 | int index);
37 |
38 | // Import pages to a FPDF_DOCUMENT.
39 | //
40 | // dest_doc - The destination document for the pages.
41 | // src_doc - The document to be imported.
42 | // pagerange - A page range string, Such as "1,3,5-7". The first page is one.
43 | // If |pagerange| is NULL, all pages from |src_doc| are imported.
44 | // index - The page index at which to insert the first imported page into
45 | // |dest_doc|. The first page is zero.
46 | //
47 | // Returns TRUE on success. Returns FALSE if any pages in |pagerange| is
48 | // invalid or if |pagerange| cannot be read.
49 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
50 | FPDF_DOCUMENT src_doc,
51 | FPDF_BYTESTRING pagerange,
52 | int index);
53 |
54 | // Experimental API.
55 | // Create a new document from |src_doc|. The pages of |src_doc| will be
56 | // combined to provide |num_pages_on_x_axis x num_pages_on_y_axis| pages per
57 | // |output_doc| page.
58 | //
59 | // src_doc - The document to be imported.
60 | // output_width - The output page width in PDF "user space" units.
61 | // output_height - The output page height in PDF "user space" units.
62 | // num_pages_on_x_axis - The number of pages on X Axis.
63 | // num_pages_on_y_axis - The number of pages on Y Axis.
64 | //
65 | // Return value:
66 | // A handle to the created document, or NULL on failure.
67 | //
68 | // Comments:
69 | // number of pages per page = num_pages_on_x_axis * num_pages_on_y_axis
70 | //
71 | FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV
72 | FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc,
73 | float output_width,
74 | float output_height,
75 | size_t num_pages_on_x_axis,
76 | size_t num_pages_on_y_axis);
77 |
78 | // Experimental API.
79 | // Create a template to generate form xobjects from |src_doc|'s page at
80 | // |src_page_index|, for use in |dest_doc|.
81 | //
82 | // Returns a handle on success, or NULL on failure. Caller owns the newly
83 | // created object.
84 | FPDF_EXPORT FPDF_XOBJECT FPDF_CALLCONV
85 | FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc,
86 | FPDF_DOCUMENT src_doc,
87 | int src_page_index);
88 |
89 | // Experimental API.
90 | // Close an FPDF_XOBJECT handle created by FPDF_NewXObjectFromPage().
91 | // FPDF_PAGEOBJECTs created from the FPDF_XOBJECT handle are not affected.
92 | FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseXObject(FPDF_XOBJECT xobject);
93 |
94 | // Experimental API.
95 | // Create a new form object from an FPDF_XOBJECT object.
96 | //
97 | // Returns a new form object on success, or NULL on failure. Caller owns the
98 | // newly created object.
99 | FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV
100 | FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject);
101 |
102 | // Copy the viewer preferences from |src_doc| into |dest_doc|.
103 | //
104 | // dest_doc - Document to write the viewer preferences into.
105 | // src_doc - Document to read the viewer preferences from.
106 | //
107 | // Returns TRUE on success.
108 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
109 | FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc);
110 |
111 | #ifdef __cplusplus
112 | } // extern "C"
113 | #endif // __cplusplus
114 |
115 | #endif // PUBLIC_FPDF_PPO_H_
116 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfTextPageKtF.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.arrow
4 |
5 | import android.graphics.RectF
6 | import arrow.core.Either
7 | import io.legere.pdfiumandroid.FindFlags
8 | import io.legere.pdfiumandroid.PdfTextPage
9 | import io.legere.pdfiumandroid.WordRangeRect
10 | import kotlinx.coroutines.CoroutineDispatcher
11 | import java.io.Closeable
12 |
13 | /**
14 | * PdfTextPageKtF represents a single text page of a PDF file.
15 | * @property page the [PdfTextPage] to wrap
16 | * @property dispatcher the [CoroutineDispatcher] to use for suspending calls
17 | */
18 | @Suppress("TooManyFunctions")
19 | class PdfTextPageKtF(
20 | val page: PdfTextPage,
21 | private val dispatcher: CoroutineDispatcher,
22 | ) : Closeable {
23 | /**
24 | * suspend version of [PdfTextPage.textPageCountChars]
25 | */
26 | suspend fun textPageCountChars(): Either =
27 | wrapEither(dispatcher) {
28 | page.textPageCountChars()
29 | }
30 |
31 | /**
32 | * suspend version of [PdfTextPage.textPageGetText]
33 | */
34 | suspend fun textPageGetText(
35 | startIndex: Int,
36 | length: Int,
37 | ): Either =
38 | wrapEither(dispatcher) {
39 | page.textPageGetText(startIndex, length)
40 | }
41 |
42 | /**
43 | * suspend version of [PdfTextPage.textPageGetUnicode]
44 | */
45 | suspend fun textPageGetUnicode(index: Int): Either =
46 | wrapEither(dispatcher) {
47 | page.textPageGetUnicode(index)
48 | }
49 |
50 | /**
51 | * suspend version of [PdfTextPage.textPageGetCharBox]
52 | */
53 | suspend fun textPageGetCharBox(index: Int): Either =
54 | wrapEither(dispatcher) {
55 | page.textPageGetCharBox(index)
56 | }
57 |
58 | /**
59 | * suspend version of [PdfTextPage.textPageGetCharIndexAtPos]
60 | */
61 | suspend fun textPageGetCharIndexAtPos(
62 | x: Double,
63 | y: Double,
64 | xTolerance: Double,
65 | yTolerance: Double,
66 | ): Either =
67 | wrapEither(dispatcher) {
68 | page.textPageGetCharIndexAtPos(x, y, xTolerance, yTolerance)
69 | }
70 |
71 | /**
72 | * suspend version of [PdfTextPage.textPageCountRects]
73 | */
74 | suspend fun textPageCountRects(
75 | startIndex: Int,
76 | count: Int,
77 | ): Either =
78 | wrapEither(dispatcher) {
79 | page.textPageCountRects(startIndex, count)
80 | }
81 |
82 | /**
83 | * suspend version of [PdfTextPage.textPageGetRect]
84 | */
85 | suspend fun textPageGetRect(rectIndex: Int): Either =
86 | wrapEither(dispatcher) {
87 | page.textPageGetRect(rectIndex)
88 | }
89 |
90 | /**
91 | * suspend version of [PdfTextPage.textPageGetRectsForRanges]
92 | */
93 | suspend fun textPageGetRectsForRanges(wordRanges: IntArray): Either?> =
94 | wrapEither(dispatcher) {
95 | page.textPageGetRectsForRanges(wordRanges)
96 | }
97 |
98 | /**
99 | * suspend version of [PdfTextPage.textPageGetBoundedText]
100 | */
101 | suspend fun textPageGetBoundedText(
102 | rect: RectF,
103 | length: Int,
104 | ): Either =
105 | wrapEither(dispatcher) {
106 | page.textPageGetBoundedText(rect, length)
107 | }
108 |
109 | /**
110 | * suspend version of [PdfTextPage.getFontSize]
111 | */
112 | suspend fun getFontSize(charIndex: Int): Either =
113 | wrapEither(dispatcher) {
114 | page.getFontSize(charIndex)
115 | }
116 |
117 | suspend fun findStart(
118 | findWhat: String,
119 | flags: Set,
120 | startIndex: Int,
121 | ): Either =
122 | wrapEither(dispatcher) {
123 | val findResult = page.findStart(findWhat, flags, startIndex)
124 | if (findResult == null) {
125 | error("findResult is null")
126 | } else {
127 | FindResultKtF(findResult, dispatcher)
128 | }
129 | }
130 |
131 | suspend fun loadWebLink(): Either =
132 | wrapEither(dispatcher) {
133 | PdfPageLinkKtF(page.loadWebLink(), dispatcher)
134 | }
135 |
136 | /**
137 | * Close the page and free all resources.
138 | */
139 | override fun close() {
140 | page.close()
141 | }
142 |
143 | fun safeClose(): Either =
144 | Either
145 | .catch {
146 | page.close()
147 | true
148 | }.mapLeft { exceptionToPdfiumKtFError(it) }
149 | }
150 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/src/main/java/io/legere/pdfiumandroid/arrow/PdfDocumentKtF.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused", "CanBeVal")
2 |
3 | package io.legere.pdfiumandroid.arrow
4 |
5 | import android.graphics.Matrix
6 | import android.graphics.RectF
7 | import android.view.Surface
8 | import arrow.core.Either
9 | import io.legere.pdfiumandroid.PdfDocument
10 | import io.legere.pdfiumandroid.PdfWriteCallback
11 | import kotlinx.coroutines.CoroutineDispatcher
12 | import kotlinx.coroutines.withContext
13 | import java.io.Closeable
14 |
15 | /**
16 | * PdfDocumentKtF represents a PDF file and allows you to load pages from it.
17 | * @property document the [PdfDocument] to wrap
18 | * @property dispatcher the [CoroutineDispatcher] to use for suspending calls
19 | * @constructor create a [PdfDocumentKtF] from a [PdfDocument]
20 | */
21 | @Suppress("TooManyFunctions", "CanBeVal")
22 | class PdfDocumentKtF(
23 | val document: PdfDocument,
24 | private val dispatcher: CoroutineDispatcher,
25 | ) : Closeable {
26 | /**
27 | * suspend version of [PdfDocument.getPageCount]
28 | */
29 | suspend fun getPageCount(): Either =
30 | wrapEither(dispatcher) {
31 | document.getPageCount()
32 | }
33 |
34 | /**
35 | * suspend version of [PdfDocument.getPageCharCounts]
36 | */
37 | suspend fun getPageCharCounts(): Either =
38 | wrapEither(dispatcher) {
39 | document.getPageCharCounts()
40 | }
41 |
42 | /**
43 | * suspend version of [PdfDocument.openPage]
44 | */
45 | suspend fun openPage(pageIndex: Int): Either =
46 | wrapEither(dispatcher) {
47 | PdfPageKtF(document.openPage(pageIndex), dispatcher)
48 | }
49 |
50 | /**
51 | * suspend version of [PdfDocument.openPages]
52 | */
53 | suspend fun openPages(
54 | fromIndex: Int,
55 | toIndex: Int,
56 | ): Either> =
57 | wrapEither(dispatcher) {
58 | document.openPages(fromIndex, toIndex).map { PdfPageKtF(it, dispatcher) }
59 | }
60 |
61 | /**
62 | * suspend version of [PdfDocument.renderPages]
63 | */
64 | @Suppress("LongParameterList", "ComplexMethod", "ComplexCondition")
65 | suspend fun renderPages(
66 | surface: Surface?,
67 | pages: List,
68 | matrices: List,
69 | clipRects: List,
70 | renderAnnot: Boolean = false,
71 | textMask: Boolean = false,
72 | canvasColor: Int = 0xFF848484.toInt(),
73 | pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
74 | renderCoroutinesDispatcher: CoroutineDispatcher,
75 | ): Boolean {
76 | return withContext(renderCoroutinesDispatcher) {
77 | return@withContext surface
78 | ?.let {
79 | document.renderPages(
80 | surface,
81 | pages.map { page -> page.page },
82 | matrices,
83 | clipRects,
84 | renderAnnot,
85 | textMask,
86 | canvasColor,
87 | pageBackgroundColor,
88 | )
89 | } ?: false
90 | }
91 | }
92 |
93 | /**
94 | * suspend version of [PdfDocument.deletePage]
95 | */
96 | suspend fun deletePage(pageIndex: Int): Unit =
97 | withContext(dispatcher) {
98 | document.deletePage(pageIndex)
99 | }
100 |
101 | /**
102 | * suspend version of [PdfDocument.getDocumentMeta]
103 | */
104 | suspend fun getDocumentMeta(): Either =
105 | wrapEither(dispatcher) {
106 | document.getDocumentMeta()
107 | }
108 |
109 | /**
110 | * suspend version of [PdfDocument.getTableOfContents]
111 | */
112 | suspend fun getTableOfContents(): Either> =
113 | wrapEither(dispatcher) {
114 | document.getTableOfContents()
115 | }
116 |
117 | /**
118 | * suspend version of [PdfDocument.openTextPages]
119 | */
120 | suspend fun openTextPages(
121 | fromIndex: Int,
122 | toIndex: Int,
123 | ): Either> =
124 | wrapEither(dispatcher) {
125 | document.openTextPages(fromIndex, toIndex).map { PdfTextPageKtF(it, dispatcher) }
126 | }
127 |
128 | /**
129 | * suspend version of [PdfDocument.saveAsCopy]
130 | */
131 | suspend fun saveAsCopy(callback: PdfWriteCallback): Either =
132 | wrapEither(dispatcher) {
133 | document.saveAsCopy(callback)
134 | }
135 |
136 | /**
137 | * Close the document
138 | * @throws IllegalArgumentException if document is closed
139 | */
140 | override fun close() {
141 | document.close()
142 | }
143 |
144 | fun safeClose(): Either =
145 | Either
146 | .catch {
147 | document.close()
148 | true
149 | }.mapLeft { exceptionToPdfiumKtFError(it) }
150 | }
151 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/java/io/legere/pdfiumandroid/suspend/PdfDocumentKt.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package io.legere.pdfiumandroid.suspend
4 |
5 | import android.graphics.Matrix
6 | import android.graphics.RectF
7 | import android.view.Surface
8 | import androidx.annotation.Keep
9 | import io.legere.pdfiumandroid.Logger
10 | import io.legere.pdfiumandroid.PdfDocument
11 | import io.legere.pdfiumandroid.PdfWriteCallback
12 | import io.legere.pdfiumandroid.PdfiumCore
13 | import kotlinx.coroutines.CoroutineDispatcher
14 | import kotlinx.coroutines.sync.withLock
15 | import kotlinx.coroutines.withContext
16 | import java.io.Closeable
17 |
18 | /**
19 | * PdfDocumentKt represents a PDF file and allows you to load pages from it.
20 | * @property document the [PdfDocument] to wrap
21 | * @property dispatcher the [CoroutineDispatcher] to use for suspending calls
22 | * @constructor create a [PdfDocumentKt] from a [PdfDocument]
23 | */
24 | @Suppress("TooManyFunctions")
25 | @Keep
26 | class PdfDocumentKt(
27 | val document: PdfDocument,
28 | private val dispatcher: CoroutineDispatcher,
29 | ) : Closeable {
30 | /**
31 | * suspend version of [PdfDocument.getPageCount]
32 | */
33 | suspend fun getPageCount(): Int =
34 | withContext(dispatcher) {
35 | document.getPageCount()
36 | }
37 |
38 | /**
39 | * suspend version of [PdfDocument.getPageCharCounts]
40 | */
41 | suspend fun getPageCharCounts(): IntArray =
42 | withContext(dispatcher) {
43 | document.getPageCharCounts()
44 | }
45 |
46 | /**
47 | * suspend version of [PdfDocument.openPage]
48 | */
49 | suspend fun openPage(pageIndex: Int): PdfPageKt =
50 | withContext(dispatcher) {
51 | PdfPageKt(document.openPage(pageIndex), dispatcher)
52 | }
53 |
54 | /**
55 | * suspend version of [PdfDocument.deletePage]
56 | */
57 | suspend fun deletePage(pageIndex: Int): Unit =
58 | withContext(dispatcher) {
59 | document.deletePage(pageIndex)
60 | }
61 |
62 | /**
63 | * suspend version of [PdfDocument.openPages]
64 | */
65 | suspend fun openPages(
66 | fromIndex: Int,
67 | toIndex: Int,
68 | ): List =
69 | withContext(dispatcher) {
70 | document.openPages(fromIndex, toIndex).map { PdfPageKt(it, dispatcher) }
71 | }
72 |
73 | /**
74 | * suspend version of [PdfDocument.renderPages]
75 | */
76 | @Suppress("LongParameterList", "ComplexMethod", "ComplexCondition")
77 | suspend fun renderPages(
78 | surface: Surface,
79 | pages: List,
80 | matrices: List,
81 | clipRects: List,
82 | renderAnnot: Boolean = false,
83 | textMask: Boolean = false,
84 | canvasColor: Int = 0xFF848484.toInt(),
85 | pageBackgroundColor: Int = 0xFFFFFFFF.toInt(),
86 | renderCoroutinesDispatcher: CoroutineDispatcher,
87 | ): Boolean {
88 | PdfiumCore.surfaceMutex.withLock {
89 | return withContext(renderCoroutinesDispatcher) {
90 | return@withContext document.renderPages(
91 | surface,
92 | pages.map { it.page },
93 | matrices,
94 | clipRects,
95 | renderAnnot,
96 | textMask,
97 | canvasColor,
98 | pageBackgroundColor,
99 | )
100 | }
101 | }
102 | }
103 |
104 | /**
105 | * suspend version of [PdfDocument.getDocumentMeta]
106 | */
107 | suspend fun getDocumentMeta(): PdfDocument.Meta =
108 | withContext(dispatcher) {
109 | document.getDocumentMeta()
110 | }
111 |
112 | /**
113 | * suspend version of [PdfDocument.getTableOfContents]
114 | */
115 | suspend fun getTableOfContents(): List =
116 | withContext(dispatcher) {
117 | document.getTableOfContents()
118 | }
119 |
120 | /**
121 | * suspend version of [PdfDocument.openTextPage]
122 | */
123 | @Deprecated("use PdfPageKt.openTextPage", ReplaceWith("page.openTextPage()"))
124 | @Suppress("DEPRECATION")
125 | suspend fun openTextPage(page: PdfPageKt): PdfTextPageKt =
126 | withContext(dispatcher) {
127 | PdfTextPageKt(document.openTextPage(page.page), dispatcher)
128 | }
129 |
130 | /**
131 | * suspend version of [PdfDocument.openTextPages]
132 | */
133 | suspend fun openTextPages(
134 | fromIndex: Int,
135 | toIndex: Int,
136 | ): List =
137 | withContext(dispatcher) {
138 | document.openTextPages(fromIndex, toIndex).map { PdfTextPageKt(it, dispatcher) }
139 | }
140 |
141 | /**
142 | * suspend version of [PdfDocument.saveAsCopy]
143 | */
144 | suspend fun saveAsCopy(callback: PdfWriteCallback): Boolean =
145 | withContext(dispatcher) {
146 | document.saveAsCopy(callback)
147 | }
148 |
149 | /**
150 | * Close the document
151 | * @throws IllegalArgumentException if document is closed
152 | */
153 | override fun close() {
154 | document.close()
155 | }
156 |
157 | fun safeClose(): Boolean =
158 | try {
159 | document.close()
160 | true
161 | } catch (e: IllegalStateException) {
162 | Logger.e("PdfDocumentKt", e, "PdfDocumentKt.safeClose")
163 | false
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/pdfiumandroid/arrow/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 | import org.jreleaser.model.Active
3 | import org.jreleaser.model.Signing
4 |
5 |
6 | plugins {
7 | alias(libs.plugins.android.library)
8 | alias(libs.plugins.kotlin.android)
9 | alias(libs.plugins.detekt)
10 | alias(libs.plugins.kover)
11 | alias(libs.plugins.ktlint)
12 | alias(libs.plugins.jreleaser)
13 | `maven-publish`
14 | signing
15 | }
16 | kotlin {
17 | compilerOptions {
18 | jvmTarget.set(JvmTarget.JVM_17)
19 | }
20 | }
21 |
22 | android {
23 | namespace = "io.legere.pdfiumandroid.arrow"
24 | compileSdk = 36
25 |
26 | defaultConfig {
27 | minSdk = 24
28 |
29 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
30 | consumerProguardFiles("consumer-rules.pro")
31 | }
32 |
33 | buildTypes {
34 | release {
35 | isMinifyEnabled = false
36 | proguardFiles(
37 | getDefaultProguardFile("proguard-android-optimize.txt"),
38 | "proguard-rules.pro",
39 | )
40 | }
41 | }
42 | compileOptions {
43 | sourceCompatibility(JavaVersion.VERSION_17)
44 | targetCompatibility(JavaVersion.VERSION_17)
45 | }
46 | publishing {
47 | singleVariant("release") {
48 | // if you don't want sources/javadoc, remove these lines
49 | withSourcesJar()
50 | withJavadocJar()
51 | }
52 | }
53 | }
54 |
55 | dependencies {
56 | implementation(project(":pdfiumandroid"))
57 | compileOnly(libs.arrow.core)
58 | compileOnly(libs.kotlinx.coroutines.android)
59 | compileOnly(libs.androidx.annotation.jvm)
60 | compileOnly(libs.kotlin.stdlib)
61 | implementation(libs.guava)
62 |
63 | testImplementation(libs.androidx.runner)
64 | testImplementation(libs.junit)
65 | androidTestImplementation(libs.androidx.junit)
66 | androidTestImplementation(libs.androidx.espresso.core)
67 | androidTestImplementation(libs.truth)
68 | androidTestImplementation(libs.kotlinx.coroutines.test)
69 | androidTestImplementation(libs.androidx.core.testing)
70 | androidTestImplementation(libs.arrow.fx.coroutines)
71 | }
72 |
73 | fun getRepositoryUsername(): String =
74 | if (rootProject.hasProperty("JRELEASER_MAVENCENTRAL_USERNAME")) {
75 | rootProject.properties["JRELEASER_MAVENCENTRAL_USERNAME"] as String
76 | } else {
77 | ""
78 | }
79 |
80 | publishing {
81 | publications {
82 | create("maven") {
83 | groupId = "io.legere"
84 | artifactId = "pdfium-android-kt-arrow"
85 | version = project.property("VERSION_NAME") as String
86 |
87 | pom {
88 | name.set("pdfiumandroid.arrow")
89 | description.set("Arrow support for PdfiumAndroid")
90 | url.set(rootProject.properties["POM_URL"] as String)
91 | licenses {
92 | license {
93 | name.set(rootProject.properties["POM_LICENCE_NAME"] as String)
94 | url.set(rootProject.properties["POM_LICENCE_URL"] as String)
95 | distribution.set(rootProject.properties["POM_LICENCE_DIST"] as String)
96 | }
97 | }
98 | developers {
99 | developer {
100 | id.set(rootProject.properties["POM_DEVELOPER_ID"] as String)
101 | name.set(rootProject.properties["POM_DEVELOPER_NAME"] as String)
102 | }
103 | }
104 | scm {
105 | connection.set(rootProject.properties["POM_SCM_CONNECTION"] as String)
106 | developerConnection.set(rootProject.properties["POM_SCM_DEV_CONNECTION"] as String)
107 | url.set(rootProject.properties["POM_SCM_URL"] as String)
108 | }
109 | }
110 | afterEvaluate {
111 | from(components["release"])
112 | }
113 | }
114 | }
115 | repositories {
116 | maven {
117 | url =
118 | uri(layout.buildDirectory.dir("target/staging-deploy"))
119 | }
120 | }
121 | }
122 |
123 | jreleaser {
124 | project {
125 | inceptionYear = "2023"
126 | author("@johngray1965")
127 | description = "Arrow support for PdfiumAndroid"
128 | version = rootProject.properties["VERSION_NAME"] as String
129 | license = "http://www.apache.org/licenses/LICENSE-2.0.txt"
130 | links {
131 | homepage = "https://github.com/johngray1965/PdfiumAndroidKt"
132 | license = "http://www.apache.org/licenses/LICENSE-2.0.txt"
133 | }
134 | }
135 | gitRootSearch = true
136 | signing {
137 | active = Active.ALWAYS
138 | mode = Signing.Mode.MEMORY
139 | armored = true
140 | verify = true
141 | }
142 | release {
143 | github {
144 | repoOwner = "johngray1965"
145 | overwrite = true
146 | }
147 | }
148 | // distributions {
149 | // create("pdfiumandroid.arrow") {
150 | // artifact {
151 | // path = file("build/distributions/{{distributionName}}-{{projectVersion}}.zip")
152 | // }
153 | // }
154 | // }
155 | deploy {
156 | maven {
157 | mavenCentral.create("sonatype") {
158 | active = Active.ALWAYS
159 | verifyPom = false
160 | url = "https://central.sonatype.com/api/v1/publisher"
161 | stagingRepository(
162 | layout.buildDirectory
163 | .dir("target/staging-deploy")
164 | .get()
165 | .toString(),
166 | )
167 | username = getRepositoryUsername()
168 | }
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/androidTest/java/io/legere/pdfiumandroid/suspend/PdfTextPageKtTest.kt:
--------------------------------------------------------------------------------
1 | package io.legere.pdfiumandroid.suspend
2 |
3 | import android.graphics.RectF
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.google.common.truth.Truth
6 | import io.legere.pdfiumandroid.base.BasePDFTest
7 | import junit.framework.TestCase
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.runBlocking
10 | import kotlinx.coroutines.test.runTest
11 | import org.junit.After
12 | import org.junit.Before
13 | import org.junit.Test
14 | import org.junit.runner.RunWith
15 |
16 | @RunWith(AndroidJUnit4::class)
17 | class PdfTextPageKtTest : BasePDFTest() {
18 | private lateinit var pdfDocument: PdfDocumentKt
19 | private var pdfBytes: ByteArray? = null
20 |
21 | @Before
22 | fun setUp() =
23 | runBlocking {
24 | pdfBytes = getPdfBytes("f01.pdf")
25 |
26 | TestCase.assertNotNull(pdfBytes)
27 |
28 | pdfDocument = PdfiumCoreKt(Dispatchers.Unconfined).newDocument(pdfBytes)
29 | }
30 |
31 | @After
32 | fun tearDown() {
33 | pdfDocument.close()
34 | }
35 |
36 | @Test
37 | fun textPageCountChars() =
38 | runTest {
39 | pdfDocument.openPage(0).use { page ->
40 | page.openTextPage().use { textPage ->
41 | val charCount = textPage.textPageCountChars()
42 |
43 | Truth.assertThat(charCount).isEqualTo(3468)
44 | }
45 | }
46 | }
47 |
48 | @Test
49 | fun textPageGetText() =
50 | runTest {
51 | pdfDocument.openPage(0).use { page ->
52 | page.openTextPage().use { textPage ->
53 | val text = textPage.textPageGetText(0, 100)
54 |
55 | Truth.assertThat(text?.length).isEqualTo(100)
56 | }
57 | }
58 | }
59 |
60 | @Test
61 | fun textPageGetUnicode() =
62 | runTest {
63 | pdfDocument.openPage(0).use { page ->
64 | page.openTextPage().use { textPage ->
65 | val char = textPage.textPageGetUnicode(0)
66 |
67 | Truth.assertThat(char).isEqualTo('T')
68 | }
69 | }
70 | }
71 |
72 | @Test
73 | fun textPageGetCharBox() =
74 | runTest {
75 | pdfDocument.openPage(0).use { page ->
76 | page.openTextPage().use { textPage ->
77 | val rect = textPage.textPageGetCharBox(0)
78 |
79 | Truth
80 | .assertThat(rect)
81 | .isEqualTo(RectF(90.314415f, 715.3187f, 103.44171f, 699.1206f))
82 | }
83 | }
84 | }
85 |
86 | @Test
87 | fun textPageGetCharIndexAtPos() =
88 | runTest {
89 | pdfDocument.openPage(0).use { page ->
90 | page.openTextPage().use { textPage ->
91 | val characterToLookup = 0
92 | val rect = textPage.textPageGetCharBox(characterToLookup)
93 |
94 | val pos =
95 | textPage.textPageGetCharIndexAtPos(
96 | rect?.centerX()?.toDouble() ?: 0.0,
97 | rect?.centerY()?.toDouble() ?: 0.0,
98 | // Shouldn't need much since we're in the middle of the rect
99 | 1.0,
100 | 1.0,
101 | )
102 |
103 | Truth.assertThat(pos).isEqualTo(characterToLookup)
104 | }
105 | }
106 | }
107 |
108 | @Test
109 | fun textPageCountRects() =
110 | runTest {
111 | pdfDocument.openPage(0).use { page ->
112 | page.openTextPage().use { textPage ->
113 | val rectCount = textPage.textPageCountRects(0, 100)
114 |
115 | Truth.assertThat(rectCount).isEqualTo(4)
116 | }
117 | }
118 | }
119 |
120 | @Test
121 | fun textPageGetRect() =
122 | runTest {
123 | pdfDocument.openPage(0).use { page ->
124 | page.openTextPage().use { textPage ->
125 | val rect = textPage.textPageGetRect(0)
126 |
127 | Truth.assertThat(rect).isEqualTo(RectF(0f, 0f, 0f, 0f))
128 | }
129 | }
130 | }
131 |
132 | @Test
133 | fun textPageGetBoundedText() =
134 | runTest {
135 | pdfDocument.openPage(0).use { page ->
136 | page.openTextPage().use { textPage ->
137 | val text = textPage.textPageGetBoundedText(RectF(0f, 97f, 100f, 100f), 100)
138 |
139 | Truth.assertThat(text).isEqualTo("Do")
140 | }
141 | }
142 | }
143 |
144 | @Test
145 | fun getFontSize() =
146 | runTest {
147 | pdfDocument.openPage(0).use { page ->
148 | page.openTextPage().use { textPage ->
149 | val fontSize = textPage.getFontSize(0)
150 |
151 | Truth.assertThat(fontSize).isEqualTo(22.559999465942383)
152 | }
153 | }
154 | }
155 |
156 | @Test(expected = IllegalStateException::class)
157 | fun close() =
158 | runTest {
159 | var pageAfterClose: PdfTextPageKt?
160 | pdfDocument.openPage(0).use { page ->
161 | page.openTextPage().use { textPage ->
162 | pageAfterClose = textPage
163 | }
164 | }
165 | pageAfterClose!!.textPageCountChars()
166 | }
167 |
168 | @Test
169 | fun getPage() =
170 | runTest {
171 | pdfDocument.openPage(0).use { page ->
172 | page.openTextPage().use { textPage ->
173 |
174 | Truth.assertThat(textPage.page).isNotNull()
175 | }
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/pdfiumandroid/src/main/cpp/include/fpdf_fwlevent.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The PDFium Authors
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 |
7 | #ifndef PUBLIC_FPDF_FWLEVENT_H_
8 | #define PUBLIC_FPDF_FWLEVENT_H_
9 |
10 | // NOLINTNEXTLINE(build/include)
11 | #include "fpdfview.h"
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif // __cplusplus
16 |
17 | // Key flags.
18 | typedef enum {
19 | FWL_EVENTFLAG_ShiftKey = 1 << 0,
20 | FWL_EVENTFLAG_ControlKey = 1 << 1,
21 | FWL_EVENTFLAG_AltKey = 1 << 2,
22 | FWL_EVENTFLAG_MetaKey = 1 << 3,
23 | FWL_EVENTFLAG_KeyPad = 1 << 4,
24 | FWL_EVENTFLAG_AutoRepeat = 1 << 5,
25 | FWL_EVENTFLAG_LeftButtonDown = 1 << 6,
26 | FWL_EVENTFLAG_MiddleButtonDown = 1 << 7,
27 | FWL_EVENTFLAG_RightButtonDown = 1 << 8,
28 | } FWL_EVENTFLAG;
29 |
30 | // Virtual keycodes.
31 | typedef enum {
32 | FWL_VKEY_Back = 0x08,
33 | FWL_VKEY_Tab = 0x09,
34 | FWL_VKEY_NewLine = 0x0A,
35 | FWL_VKEY_Clear = 0x0C,
36 | FWL_VKEY_Return = 0x0D,
37 | FWL_VKEY_Shift = 0x10,
38 | FWL_VKEY_Control = 0x11,
39 | FWL_VKEY_Menu = 0x12,
40 | FWL_VKEY_Pause = 0x13,
41 | FWL_VKEY_Capital = 0x14,
42 | FWL_VKEY_Kana = 0x15,
43 | FWL_VKEY_Hangul = 0x15,
44 | FWL_VKEY_Junja = 0x17,
45 | FWL_VKEY_Final = 0x18,
46 | FWL_VKEY_Hanja = 0x19,
47 | FWL_VKEY_Kanji = 0x19,
48 | FWL_VKEY_Escape = 0x1B,
49 | FWL_VKEY_Convert = 0x1C,
50 | FWL_VKEY_NonConvert = 0x1D,
51 | FWL_VKEY_Accept = 0x1E,
52 | FWL_VKEY_ModeChange = 0x1F,
53 | FWL_VKEY_Space = 0x20,
54 | FWL_VKEY_Prior = 0x21,
55 | FWL_VKEY_Next = 0x22,
56 | FWL_VKEY_End = 0x23,
57 | FWL_VKEY_Home = 0x24,
58 | FWL_VKEY_Left = 0x25,
59 | FWL_VKEY_Up = 0x26,
60 | FWL_VKEY_Right = 0x27,
61 | FWL_VKEY_Down = 0x28,
62 | FWL_VKEY_Select = 0x29,
63 | FWL_VKEY_Print = 0x2A,
64 | FWL_VKEY_Execute = 0x2B,
65 | FWL_VKEY_Snapshot = 0x2C,
66 | FWL_VKEY_Insert = 0x2D,
67 | FWL_VKEY_Delete = 0x2E,
68 | FWL_VKEY_Help = 0x2F,
69 | FWL_VKEY_0 = 0x30,
70 | FWL_VKEY_1 = 0x31,
71 | FWL_VKEY_2 = 0x32,
72 | FWL_VKEY_3 = 0x33,
73 | FWL_VKEY_4 = 0x34,
74 | FWL_VKEY_5 = 0x35,
75 | FWL_VKEY_6 = 0x36,
76 | FWL_VKEY_7 = 0x37,
77 | FWL_VKEY_8 = 0x38,
78 | FWL_VKEY_9 = 0x39,
79 | FWL_VKEY_A = 0x41,
80 | FWL_VKEY_B = 0x42,
81 | FWL_VKEY_C = 0x43,
82 | FWL_VKEY_D = 0x44,
83 | FWL_VKEY_E = 0x45,
84 | FWL_VKEY_F = 0x46,
85 | FWL_VKEY_G = 0x47,
86 | FWL_VKEY_H = 0x48,
87 | FWL_VKEY_I = 0x49,
88 | FWL_VKEY_J = 0x4A,
89 | FWL_VKEY_K = 0x4B,
90 | FWL_VKEY_L = 0x4C,
91 | FWL_VKEY_M = 0x4D,
92 | FWL_VKEY_N = 0x4E,
93 | FWL_VKEY_O = 0x4F,
94 | FWL_VKEY_P = 0x50,
95 | FWL_VKEY_Q = 0x51,
96 | FWL_VKEY_R = 0x52,
97 | FWL_VKEY_S = 0x53,
98 | FWL_VKEY_T = 0x54,
99 | FWL_VKEY_U = 0x55,
100 | FWL_VKEY_V = 0x56,
101 | FWL_VKEY_W = 0x57,
102 | FWL_VKEY_X = 0x58,
103 | FWL_VKEY_Y = 0x59,
104 | FWL_VKEY_Z = 0x5A,
105 | FWL_VKEY_LWin = 0x5B,
106 | FWL_VKEY_Command = 0x5B,
107 | FWL_VKEY_RWin = 0x5C,
108 | FWL_VKEY_Apps = 0x5D,
109 | FWL_VKEY_Sleep = 0x5F,
110 | FWL_VKEY_NumPad0 = 0x60,
111 | FWL_VKEY_NumPad1 = 0x61,
112 | FWL_VKEY_NumPad2 = 0x62,
113 | FWL_VKEY_NumPad3 = 0x63,
114 | FWL_VKEY_NumPad4 = 0x64,
115 | FWL_VKEY_NumPad5 = 0x65,
116 | FWL_VKEY_NumPad6 = 0x66,
117 | FWL_VKEY_NumPad7 = 0x67,
118 | FWL_VKEY_NumPad8 = 0x68,
119 | FWL_VKEY_NumPad9 = 0x69,
120 | FWL_VKEY_Multiply = 0x6A,
121 | FWL_VKEY_Add = 0x6B,
122 | FWL_VKEY_Separator = 0x6C,
123 | FWL_VKEY_Subtract = 0x6D,
124 | FWL_VKEY_Decimal = 0x6E,
125 | FWL_VKEY_Divide = 0x6F,
126 | FWL_VKEY_F1 = 0x70,
127 | FWL_VKEY_F2 = 0x71,
128 | FWL_VKEY_F3 = 0x72,
129 | FWL_VKEY_F4 = 0x73,
130 | FWL_VKEY_F5 = 0x74,
131 | FWL_VKEY_F6 = 0x75,
132 | FWL_VKEY_F7 = 0x76,
133 | FWL_VKEY_F8 = 0x77,
134 | FWL_VKEY_F9 = 0x78,
135 | FWL_VKEY_F10 = 0x79,
136 | FWL_VKEY_F11 = 0x7A,
137 | FWL_VKEY_F12 = 0x7B,
138 | FWL_VKEY_F13 = 0x7C,
139 | FWL_VKEY_F14 = 0x7D,
140 | FWL_VKEY_F15 = 0x7E,
141 | FWL_VKEY_F16 = 0x7F,
142 | FWL_VKEY_F17 = 0x80,
143 | FWL_VKEY_F18 = 0x81,
144 | FWL_VKEY_F19 = 0x82,
145 | FWL_VKEY_F20 = 0x83,
146 | FWL_VKEY_F21 = 0x84,
147 | FWL_VKEY_F22 = 0x85,
148 | FWL_VKEY_F23 = 0x86,
149 | FWL_VKEY_F24 = 0x87,
150 | FWL_VKEY_NunLock = 0x90,
151 | FWL_VKEY_Scroll = 0x91,
152 | FWL_VKEY_LShift = 0xA0,
153 | FWL_VKEY_RShift = 0xA1,
154 | FWL_VKEY_LControl = 0xA2,
155 | FWL_VKEY_RControl = 0xA3,
156 | FWL_VKEY_LMenu = 0xA4,
157 | FWL_VKEY_RMenu = 0xA5,
158 | FWL_VKEY_BROWSER_Back = 0xA6,
159 | FWL_VKEY_BROWSER_Forward = 0xA7,
160 | FWL_VKEY_BROWSER_Refresh = 0xA8,
161 | FWL_VKEY_BROWSER_Stop = 0xA9,
162 | FWL_VKEY_BROWSER_Search = 0xAA,
163 | FWL_VKEY_BROWSER_Favorites = 0xAB,
164 | FWL_VKEY_BROWSER_Home = 0xAC,
165 | FWL_VKEY_VOLUME_Mute = 0xAD,
166 | FWL_VKEY_VOLUME_Down = 0xAE,
167 | FWL_VKEY_VOLUME_Up = 0xAF,
168 | FWL_VKEY_MEDIA_NEXT_Track = 0xB0,
169 | FWL_VKEY_MEDIA_PREV_Track = 0xB1,
170 | FWL_VKEY_MEDIA_Stop = 0xB2,
171 | FWL_VKEY_MEDIA_PLAY_Pause = 0xB3,
172 | FWL_VKEY_MEDIA_LAUNCH_Mail = 0xB4,
173 | FWL_VKEY_MEDIA_LAUNCH_MEDIA_Select = 0xB5,
174 | FWL_VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
175 | FWL_VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
176 | FWL_VKEY_OEM_1 = 0xBA,
177 | FWL_VKEY_OEM_Plus = 0xBB,
178 | FWL_VKEY_OEM_Comma = 0xBC,
179 | FWL_VKEY_OEM_Minus = 0xBD,
180 | FWL_VKEY_OEM_Period = 0xBE,
181 | FWL_VKEY_OEM_2 = 0xBF,
182 | FWL_VKEY_OEM_3 = 0xC0,
183 | FWL_VKEY_OEM_4 = 0xDB,
184 | FWL_VKEY_OEM_5 = 0xDC,
185 | FWL_VKEY_OEM_6 = 0xDD,
186 | FWL_VKEY_OEM_7 = 0xDE,
187 | FWL_VKEY_OEM_8 = 0xDF,
188 | FWL_VKEY_OEM_102 = 0xE2,
189 | FWL_VKEY_ProcessKey = 0xE5,
190 | FWL_VKEY_Packet = 0xE7,
191 | FWL_VKEY_Attn = 0xF6,
192 | FWL_VKEY_Crsel = 0xF7,
193 | FWL_VKEY_Exsel = 0xF8,
194 | FWL_VKEY_Ereof = 0xF9,
195 | FWL_VKEY_Play = 0xFA,
196 | FWL_VKEY_Zoom = 0xFB,
197 | FWL_VKEY_NoName = 0xFC,
198 | FWL_VKEY_PA1 = 0xFD,
199 | FWL_VKEY_OEM_Clear = 0xFE,
200 | FWL_VKEY_Unknown = 0,
201 | } FWL_VKEYCODE;
202 |
203 | #ifdef __cplusplus
204 | } // extern "C"
205 | #endif // __cplusplus
206 |
207 | #endif // PUBLIC_FPDF_FWLEVENT_H_
208 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------