diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfContinuousPage.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfContinuousPage.kt index d1ffb8a..720645a 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfContinuousPage.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfContinuousPage.kt @@ -190,6 +190,11 @@ internal fun PdfContinuousPage( onMoveImageToPage: (fromPage: Int, img: PdfMarkup.ImageMarkup, toPage: Int) -> Boolean = { _, _, _ -> false } ) { var imageDragging by remember { mutableStateOf(false) } + // A dragged text box / shape / note can swing below this page's bottom edge toward the next + // page while still being moved. Compose doesn't clip a page's overlay to its own bounds, and + // the next page is a later LazyColumn item, so without raising this page too it drew on top + // of the markup being dragged over it. + var markupDragging by remember { mutableStateOf(false) } var draftPoints by remember(page, activeTool) { mutableStateOf>(emptyList()) } var draftRectStart by remember(page, activeTool) { mutableStateOf(null) } var draftRectEnd by remember(page, activeTool) { mutableStateOf(null) } @@ -245,9 +250,10 @@ internal fun PdfContinuousPage( Box( modifier - // While an image is dragged across a page boundary, this page draws above its - // neighbours so the image isn't hidden behind the next page. - .zIndex(if (imageDragging) 1f else 0f) + // While an image or a selected text box / shape / note is dragged across a page + // boundary, this page draws above its neighbours so the markup isn't hidden behind + // the next page. + .zIndex(if (imageDragging || markupDragging) 1f else 0f) .fillMaxWidth() .padding(vertical = 6.dp) // The placeholder is trimmed too, so the page keeps its height when the bitmap lands. @@ -734,6 +740,7 @@ internal fun PdfContinuousPage( total = Offset.Zero val pPage = Offset(local.x + (bb?.left ?: 0f) - pad, local.y + (bb?.top ?: 0f) - pad) mode = if (bb != null && cur.isResizable() && (pPage - bb.bottomRight).getDistance() <= 64f) 2 else 1 + markupDragging = true onInteraction() }, onDrag = { ch, drag -> @@ -745,8 +752,8 @@ internal fun PdfContinuousPage( marks[selectedMarkupIndex] = if (mode == 2) m0.resizedFrom(b0, total) else m0.translated(total) onInteraction() }, - onDragEnd = { mode = 0; frozen = null }, - onDragCancel = { mode = 0; frozen = null } + onDragEnd = { mode = 0; frozen = null; markupDragging = false }, + onDragCancel = { mode = 0; frozen = null; markupDragging = false } ) } ) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfViewerViewModel.kt b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfViewerViewModel.kt index f62cf00..32f7054 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfViewerViewModel.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfViewerViewModel.kt @@ -287,6 +287,10 @@ class PdfViewerViewModel(private val openPdfUseCase: OpenPdfUseCase) : ViewModel // gives that race a head start. private const val PREFETCH_AHEAD = 2 private const val PREFETCH_BEHIND = 1 + // Above this, the on-open form/link scan is skipped outright rather than risking PDFBox's + // temp-file-backed load on a huge document for a feature (form fields, in-document links) + // that scanned books essentially never have. + private const val FORM_SCAN_SIZE_LIMIT_BYTES = 150L * 1024 * 1024 } fun openPdf(context: Context, uri: Uri, password: String? = null) { @@ -394,14 +398,24 @@ class PdfViewerViewModel(private val openPdfUseCase: OpenPdfUseCase) : ViewModel renderedByOfficeEngine = renderedUri.path?.contains("/office-pdf/") == true ) // Fillable fields and links, read off the main thread in one pass; most PDFs have - // no form and stay empty. + // no form and stay empty. This used to load the whole file into PDFBox's in-memory + // model unconditionally on every open — a multi-hundred-MB scanned book (all image + // XObjects, no forms) OOM'd right after opening, before the user did anything. Above + // FORM_SCAN_SIZE_LIMIT_BYTES skip the scan (forms/links are rare in huge scans and + // not worth the risk); below it, load through a temp-file-backed buffer instead of + // the heap. val formDocUri = doc.uri viewModelScope.launch { val (widgets, links) = withContext(Dispatchers.IO) { - runCatching { + if (doc.sizeBytes > FORM_SCAN_SIZE_LIMIT_BYTES) { + emptyMap>() to emptyMap() + } else runCatching { com.kyant.pdfcore.internal.PdfBox.ensureInitialized(context) context.contentResolver.openInputStream(formDocUri)?.use { input -> - com.tom_roush.pdfbox.pdmodel.PDDocument.load(input).use { pd -> + com.tom_roush.pdfbox.pdmodel.PDDocument.load( + input, + com.tom_roush.pdfbox.io.MemoryUsageSetting.setupTempFileOnly() + ).use { pd -> val w = runCatching { com.kyant.pdfcore.form.PdfFormWidgets.read(pd) }.getOrDefault(emptyMap()) val l = runCatching { com.kyant.pdfcore.link.PdfLinks.read(pd) }.getOrDefault(emptyMap()) w to l diff --git a/readme.md b/readme.md index 43f2177..ed2b837 100644 --- a/readme.md +++ b/readme.md @@ -8,6 +8,7 @@ Read and annotate PDFs, sign forms, work with Office documents, edit spreadsheet

Download the latest ClearPDF + · Add to Obtainium · All releases · Report a problem or request a feature · Contribute @@ -20,6 +21,8 @@ Read and annotate PDFs, sign forms, work with Office documents, edit spreadsheet MIT license Android 6.0 or newer Android build status + Add to Obtainium + Number 2 on GitHub Trending

@@ -36,13 +39,17 @@ ClearPDF was featured by HowToMen. [Watch the video from 9:08](https://www.youtu ClearPDF home screen in light mode ClearPDF PDF tools in light mode ClearPDF settings in light mode - ClearPDF PDF editing tools + ClearPDF opening a Word document

ClearPDF home screen in dark mode ClearPDF PDF tools in dark mode ClearPDF settings in dark mode - ClearPDF annotation tools + ClearPDF editing a spreadsheet +

+ +

+ Scrolling a PDF in the ClearPDF viewer

Short walkthroughs: [onboarding](demo/1.mp4) · [PDF viewer](demo/2.mp4). @@ -87,7 +94,7 @@ ClearPDF annotates PDFs and edits form fields, signatures, and pages, but it doe ## Download -The current stable release is **V4 (app version 2.0.0)**. Open the [V4 release page](https://github.com/Chethan616/ClearPDF/releases/tag/v4) and choose the APK that matches your device: +The current stable release is **V5.1 (app version 2.1.1)**. Open the [latest release page](https://github.com/Chethan616/ClearPDF/releases/latest) and choose the APK that matches your device: | APK suffix | Choose it for | |---|---| @@ -96,7 +103,15 @@ The current stable release is **V4 (app version 2.0.0)**. Open the [V4 release p | -x86_64.apk or -x86.apk | x86-based emulators and devices | | -universal.apk | A larger package containing the release's supported ABIs | -The GitHub APKs are the FOSS distribution. The optional Office engine is downloaded only when requested in Settings. For future builds, use the [latest release page](https://github.com/Chethan616/ClearPDF/releases/latest). You can also add the repository to [Obtainium](https://github.com/ImranR98/Obtainium) for release update notifications. +The GitHub APKs are the FOSS distribution. The optional Office engine is downloaded only when requested in Settings. + +### Install via Obtainium + +[Obtainium](https://github.com/ImranR98/Obtainium) tracks this repository's releases and installs/updates the APK for you, no manual downloads. + +1. Open Obtainium → **Add App**. +2. Paste `https://github.com/Chethan616/ClearPDF`, or tap **[Add to Obtainium](https://apps.obtainium.imranr.dev/redirect?r=https://github.com/Chethan616/ClearPDF)** on a device with Obtainium installed. +3. Pick the APK suffix for your device from the table above when prompted. ## Privacy and network use diff --git a/screenshots/dark home screen.jpg b/screenshots/dark home screen.jpg index 2fd5073..2bdaac4 100644 Binary files a/screenshots/dark home screen.jpg and b/screenshots/dark home screen.jpg differ diff --git a/screenshots/dark pdf tools.jpg b/screenshots/dark pdf tools.jpg index e147fb7..0d4fe8f 100644 Binary files a/screenshots/dark pdf tools.jpg and b/screenshots/dark pdf tools.jpg differ diff --git a/screenshots/dark settings screen.jpg b/screenshots/dark settings screen.jpg index 1e1fc7b..06d470b 100644 Binary files a/screenshots/dark settings screen.jpg and b/screenshots/dark settings screen.jpg differ diff --git a/screenshots/docx editing.jpg b/screenshots/docx editing.jpg new file mode 100644 index 0000000..a788fe6 Binary files /dev/null and b/screenshots/docx editing.jpg differ diff --git a/screenshots/light home screen.jpg b/screenshots/light home screen.jpg index 8bc444c..b818f7e 100644 Binary files a/screenshots/light home screen.jpg and b/screenshots/light home screen.jpg differ diff --git a/screenshots/light pdf tools.jpg b/screenshots/light pdf tools.jpg index 71dcf7e..8d76656 100644 Binary files a/screenshots/light pdf tools.jpg and b/screenshots/light pdf tools.jpg differ diff --git a/screenshots/light settings screen.jpg b/screenshots/light settings screen.jpg index 1b0cdc7..26946cf 100644 Binary files a/screenshots/light settings screen.jpg and b/screenshots/light settings screen.jpg differ diff --git a/screenshots/pdf_scroll.gif b/screenshots/pdf_scroll.gif new file mode 100644 index 0000000..ffee6de Binary files /dev/null and b/screenshots/pdf_scroll.gif differ diff --git a/screenshots/social_preview_1280x640.png b/screenshots/social_preview_1280x640.png new file mode 100644 index 0000000..b4265bc Binary files /dev/null and b/screenshots/social_preview_1280x640.png differ diff --git a/screenshots/xlsx editing.jpg b/screenshots/xlsx editing.jpg new file mode 100644 index 0000000..0201e5e Binary files /dev/null and b/screenshots/xlsx editing.jpg differ