GithubHelp home page GithubHelp logo

bouquet's Introduction

Bouquet - Jetpack Compose PDF reader

Bouquet is a PDF reader library written completely in Jetpack Compose, this was created using PDFRender and coroutine. The library supports both horizontal and vertical viewing.

Features

  • Different sources available out of box (Base64, URL, URI)
  • Double tap to zoom and pan
  • You can retrieve the open file to share
  • Loading progress indication
  • Current page and total page number
  • Error handling

How to

Step 1. Add INTERNET permissions on your AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

Step 2. Add the MavenCentral repository to your build file Add it in your root build.gradle at the end of repositories:

allprojects {
	repositories {
		mavenCentral()
	}
}

Step 3. Add the dependency

dependencies {
        implementation 'io.github.grizzi91:bouquet:1.1.2'
}

Step 4. You can use the library by creating the state in a Composable This is for portrait viewing

val pdfState = rememberVerticalPdfReaderState(
	resource = ResourceType.Remote("https://myreport.altervista.org/Lorem_Ipsum.pdf"),
	isZoomEnable = true
)

This is for landscape viewing

val pdfState = rememberHorizontalPdfReaderState(
	resource = ResourceType.Remote("https://myreport.altervista.org/Lorem_Ipsum.pdf"),
	isZoomEnable = true
)

Or you can host the state in a ViewModel

class BouquetViewModel : ViewModel() {
  
    val pdfHorizontalReaderState = HorizontalPdfReaderState(
        resource = ResourceType.Remote("https://myreport.altervista.org/Lorem_Ipsum.pdf"),
	isZoomEnable = true
    )
    
    val pdfVerticallReaderState = VerticalPdfReaderState(
        resource = ResourceType.Remote("https://myreport.altervista.org/Lorem_Ipsum.pdf"),
	isZoomEnable = true
    )
}

Step 5. Then pass the state to the PDFReader function

VerticalPDFReader(
	state = pdfState,
	modifier = Modifier
		.fillMaxSize()
		.background(color = Color.Gray)
)

or

HorizontalPDFReader(
	state = pdfState,
	modifier = Modifier
		.fillMaxSize()
		.background(color = Color.Gray)
)

You can use Modifier to modify dimension, background color or shape and other parameter of the PDFReader view.

Chose your source

You can choose different sources using the sealed class ResourceType

Remote source, with support for headers

ResourceType.Remote(
	url = "https://myreport.altervista.org/Lorem_Ipsum.pdf",
    headers = hashMapOf("headerKey" to "headerValue")
)

Local source

ResourceType.Local(
	// URI
)

Base64 source

ResourceType.Base64(
	// Base64 string
)

Asset source

ResourceType.Asset(R.raw.pdf)

Error handling

In case of errors, for example with the remote resource, you can recover the error from the state.

LaunchedEffect(key1 = pdfState.error) {
	pdfState.error?.let {
		// Show error
	}
}

File sharing

After the opening of the pdf you can share it from the 'file' field of the state.

state.file?.let {
	FloatingActionButton(
		onClick = {
			shareFile(it)
		}
	) {
		Icon(
			painter = painterResource(id = android.R.drawable.ic_menu_share),
			contentDescription = "share"
		)
	}
}

Sample app

For managing the download progress, current page number, total page number, scrolling status etc. you can refer to the sample app.

License

   Copyright [2022] [Graziano Rizzi]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

bouquet's People

Contributors

grizzi91 avatar jbayangosb avatar valerii-fr avatar

Stargazers

Luciano  avatar Fukioston avatar  avatar Ismail avatar Meet Bhavsar avatar Nurumbet avatar Norbert avatar Adarsh Pambhar avatar Kambi Victor avatar Jan Steuer avatar Ali Mahdi Ajani avatar Adem KOCAMAZ avatar  avatar Luca Selvaggio avatar  avatar Farhazul-Islam-Mullick avatar  avatar Priyanshu Kumar avatar Usama Sayed avatar Fawaad Ahmad avatar mort87 avatar Peerzada Burhan Ahmad avatar Fabricio Gonzalez França Ribeiro avatar Dimas Wahyu Ardiyanto avatar Deri Armanda avatar  avatar Martín Fernández avatar  avatar yue wang avatar Nipa Maity avatar Pank Su avatar HamidReza Mohammad Jafari avatar  avatar Eddy Yoel Fresno Hdez avatar Swapnil Musale avatar 吴上阿吉 avatar Pushpal Roy avatar BlueSky avatar Chollan avatar Kevin van Mierlo avatar tmxdyf avatar Rissmon Suresh avatar iamwent avatar Andriy Parkhomov avatar Tobias Raak avatar Rahman Taghizade avatar Artem Ivanchenko avatar Taras Petruk avatar Nguyễn Hoàng Ân avatar metromancn avatar Mikail Yusuf avatar Ilyas Shafigin avatar Warren avatar  avatar Simone Formica avatar Emanuele avatar Daniel Arnaiz avatar Patrick Schneider avatar Moh Sihabudin avatar  avatar  avatar Hassan Tarhini avatar Alexey avatar Filippo Vigani avatar Lexie Ludeman avatar  avatar Joni Sharma avatar Taruna Yoga Pratama avatar Sergey Bernikov avatar  avatar 叫我法露珊前辈 avatar Aleš Kavčič avatar Boy Hoody avatar  avatar woods avatar Jordan Gout avatar Jesús Liarte avatar Litrik De Roy avatar Michael Greifeneder avatar Kyoosik Lee avatar Joel Dean avatar Andrew Hughes avatar Maciej Ciemięga avatar Patel Shakil avatar Bakht Ergashev avatar Zokirjon avatar Mohammad Reza Fekri avatar Daniele Andreoli avatar Romain Vandemaele avatar Tom Bailey avatar Razvan Filea avatar Benoit G avatar Sohanuzzaman Soad avatar sonu pandey avatar Anirudh Parida avatar Tran Do Minh avatar Binay Shaw avatar Aritra Das avatar Mon avatar Malik Mukhametzyanov avatar

Watchers

Ali Rahimpour avatar Michał Kozioł avatar Enes avatar  avatar Aleš Kavčič avatar  avatar João Pinto avatar

bouquet's Issues

Scroll flag does not work when zoomed

With the implementation of the double tap to zoom when the zoom is active the flag that indicates if you are scrolling at that moment does not work

Detect when PDF is scrolled to the bottom/top

I am wanting to display a composable just above and below the PDF viewer depending on if the user is viewing the first or last page. Looking at the available PdfReaderState properties, it looks like the best thing I could use is a check of the currentPage alongside the pdfPageCount to decide when to surface the composable.

Perhaps there is some kind of enhancement that could make this usecase easier?

Error: checkServerTrusted is empty

My lint keeps throwing an error when I use your library:
/Users/saenic/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk15to18/1.75/f16e5252ad7a46d5eaf255231b0a5da307599082/bcpkix-jdk15to18-1.75.jar: Error: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers [TrustAllX509TrustManager]

I looked through your code and couldn't find any trust manager implementations, so no clue why this occurs. Maybe you can find the source of the problem.

Zooming doesn't always work

Hi, thanks for the library, it works pretty well!

However, I find it hard to pinch to zoom. It only works sometimes for me, even on the emulator.
Have you considered adding double tap to zoom?

High Memory consumption

the code runs well, after repeated opening of local file, the app unexpectedly hangs & closed, i ran it through profiler and on opening local file there was a high spike of memory use, took a dump to check which code is causing the high memory usage, i could trace to Page Class in the BouquetPdfRender.kt file

Screenshot 2023-08-16 at 5 09 20 PM
Screenshot 2023-08-16 at 5 12 26 PM

Accessibility support

This library is fantastic for my needs but unfortunately its not quite accessible right now in its current state.

Using talkback with the PDF reader leads to a whole bunch of "Unlabelled" announcements by the accessibility service and a best guess at what the content contains.

Ideally we are able to simply strip text from each bitmap and provide it as part of the ContentDescription when rendering in compose.

I may explore ways to resolve this 🙂

Crash when trying to load a non-existent local resource

Thank you for the prompt release of 1.0.2. Much appreciated.

I ran into another problem when trying to test state.error by purposely opening a non-existent local resource.

E  Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaDocumentsProvider uri content://com.android.providers.media.documents/document/test from pid=14210, uid=10320 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
    at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:879)
    at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:707)
    at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:697)
    at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:556)
    at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:327)
    at android.os.Binder.execTransactInternal(Binder.java:1280)
    at android.os.Binder.execTransact(Binder.java:1244)

Would putting the file opening logic within a try-catch statement solve the problem?

coroutineScope.launch(Dispatchers.IO) {
context.contentResolver.openFileDescriptor(res.uri, "r")?.let {
val textForEachPage = if (state.isAccessibleEnable) {
getTextByPage(context, it)
} else emptyList()
state.pdfRender = BouquetPdfRender(it, textForEachPage, width, height, portrait)
state.mFile = context.uriToFile(res.uri)
} ?: run {
state.mError = IOException("File not found")
}
}

How to change file

After setting a new state, PdfReader cannot draw a new PDF image. How can I solve this problem

Zoom with gestures

As a user I would like to have a way to zoom in/out a PDF file by using pinch gestures.

Currently we can zoom in/out a PDF by double tapping the PDF, but this is really limited: we can either see the whole page or just a small portion of it.

Another suggestion, in case this is too difficult to do, you can expose the scale property so that we can have an state were we update it's value based on user's pinch gestures

pdf string format not embed in my app screen

Hi!
I have in my code this

                                    ResourceType.DOCUMENTO ->{
                                        Column(
                                            verticalArrangement = Arrangement.spacedBy(
                                                space = AppTheme.dimens.grid_2,
                                                alignment = Alignment.CenterVertically
                                            ),
                                            horizontalAlignment = Alignment.Start,
                                            modifier = Modifier
                                                .fillMaxWidth()
                                                .fillMaxHeight()
                                                .background(
                                                    colorResource(id = R.color.ayhoCardBackgroundGray),
                                                    shape = RoundedCornerShape(4.dp)
                                                )
                                        ) {
                                            resource?.resourceData?.let {
                                                ShowPdfFromBase64(
                                                    pdfString = it,
                                                    modifier = Modifier
                                                        .fillMaxWidth()
                                                        .fillMaxHeight(0.33f).padding()
                                                )
                                            }
                                            Text(
                                                text = resource?.fileName ?: "",
                                                color = colorBlack,
                                                style = AppTheme.typography.h5,
                                                modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp)
                                            )
                                            DownloadButtonView(
                                                text = "Descargar Documento",
                                                modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
                                            ) {
                                                resource?.resourceData?.let { data ->
                                                    decodeTestPdfString(data,resource?.fileName, context)
                                                }
                                            }
                                        }
                                    }

and I have this function

@Composable
fun ShowPdfFromBase64(pdfString: String, modifier: Modifier) {

    val pdfState = rememberVerticalPdfReaderState(
        resource = ResourceType.Base64(pdfString),
        isZoomEnable = true
    )
    VerticalPDFReader(
        state = pdfState,
        modifier = modifier
            .background(color = colorResource(id = R . color . ayhoCardBackgroundGray))
    )
}

but the pdf is not embedded in my view, that is, what it does is open the pdf in another window like when I use the following function

fun decodeTestPdfString(pdf_string:String,filename: String?, context:Context) {

    //make FileOutPutStream
    var fos: FileOutputStream? = null
    var f: File? = null
    try {
        f = File(context?.cacheDir, filename ?:("testFile" + ".pdf"))
        f!!.createNewFile()

        fos = FileOutputStream(f)
        val decodedString: ByteArray = Base64.decode(pdf_string, Base64.DEFAULT)
        fos.write(decodedString)
        fos.flush()
        fos.close()


    } catch (e: Exception) {
    } finally {
        if (fos != null) {
            fos = null
        }
    }
    val path: Uri = FileProvider.getUriForFile(context!!, context!!.getApplicationContext().getPackageName() + ".provider", f!!)
    val intent = Intent(Intent.ACTION_VIEW)
    intent.setDataAndType(path, "application/pdf")
    intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    try {
        startActivity(context, intent, null)
    } catch (e: ActivityNotFoundException) {

    }
}

this is correct? There is no way to embed the life of the pdf within the screen of my app, without opening the default reader of android

Thanks!

Add CustomPDFReader or additional params for the VerticalPDFReader/HorizontalPDFReader

Hi!
Thanks for the lib.
Can you add the ability to create a custom PDFReader or add some parameters to modify the inner VerticalPDFReader/HorizontalPDFReader LazyColumn?

Example:
I need to add paddings for the pdf items.
Can I do it differently?

// VerticalPDFReader

state.pdfRender?.let { pdf ->
            LazyColumn(
                modifier = Modifier
                    .fillMaxSize()
                    .tapToZoomVertical(state, constraints),
                horizontalAlignment = Alignment.CenterHorizontally,
                
                verticalArrangement = Arrangement.spacedBy(space = 24.dp),
                contentPadding = PaddingValues(24.dp),
                
                state = lazyState
            )
...

Thanks!

Adding Documentation

Hey there. Really great work with this library, we absolutely appreciate.

Is it possible to add some documentation to it though. I have an app that heavily relies on this pdf library, and I might need to add some functionality on top of whats existing and adding some documentation might help me save some time. Also do you have any specific guidelines for collaborating on this project? a readme of that would also help a tonne.

Thanx in advance

Creating tmp files and not clearing them

Hello! thanks for this library, but I found a bug(?) If I pass uri files that are already in the cache, this action creates a copy of the files and when I close the screen it does not delete these copies.... As a result every opening = new file. You need to either add a check that the uri refers to a file already, or add a cleanup of the files created

Снимок экрана 2024-04-02 в 11 16 04 Снимок экрана 2024-04-02 в 11 16 53

Allow headers for remote resource

Thanks for the library! It is especially helpful because there is no official framework that displays PDF easily. (at least to my knowledge)

I am trying to load remote resource from cloud storage such as AWS, thus I need to set some request headers.
It would be great if I could set the headers when declaring ResourceType.Remote.

Hope you would give it a consideration!

Manually scroll to page

Could you add a function to HorizontalPDFReader and VerticalDFReader to manually scroll to a specific page?
Like: horizontalPdfReaderState.scrollToPage(3)

Missing class com.gemalto.jp2.JP2Decoder

Since upgrading Android Studio Flamingo | 2022.2.1 Patch 1 from Electric Eel I get the following error when I Generate Signed Android App Bundle:

Missing class com.gemalto.jp2.JP2Decoder (referenced from: android.graphics.Bitmap com.tom_roush.pdfbox.filter.JPXFilter.readJPX(java.io.InputStream, com.tom_roush.pdfbox.filter.DecodeOptions, com.tom_roush.pdfbox.filter.DecodeResult))

Everything is working ok when debug, just when I build for Play Store, as anyone had this issue?

Crash when loading password-protected file

Hi again! I'm just wondering if you have any plans to support password-protected files? When I tried opening such a file, my application crashes with the following error.

If there are no such immediate plans, I would have to consider a workaround to the problem. Please kindly let me know.

java.lang.SecurityException: password required or incorrect password
  at android.graphics.pdf.PdfRenderer.nativeCreate(Native Method)
  at android.graphics.pdf.PdfRenderer.<init>(PdfRenderer.java:172)
  at com.rizzi.bouquet.BouquetPdfRender.<init>(BouquetPdfRender.kt:20)
  at com.rizzi.bouquet.BouquetKt$load$1$2.invokeSuspend(Bouquet.kt:254)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
  at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
  at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:100)
  at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
  Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@1bd0da2, androidx.compose.runtime.BroadcastFrameClock@4f25a33, StandaloneCoroutine{Cancelling}@91e21f0, Dispatchers.IO]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.