Building MOC Office: An Offline Android PDF Suite
Re-Engineering PDF Operations for Mobile
Most PDF editors on Android are heavy wrappers around web services that upload your personal files to distant servers to perform basic actions like merging, splitting, or watermarking.
For MOC Office, we built an offline-first architecture where every action is processed locally, keeping your files safe from data profiling.
Document manipulation is a prime target for privacy leaks. Users should never have to upload a bank statement or a passport scan to merge two pages.
Under the Hood: Hybrid Rendering
Rather than shipping a heavy native C++ PDF engine that bloated our app size by 40MB, MOC Office utilizes a custom WebKit-based implementation of Mozilla's PDF.js. This allowed us to offer seamless pinch-to-zoom, horizontal page scrubbers, and full-text document searches in a lightweight container.
However, rendering large PDFs (>100MB) inside a mobile WebView introduces memory limits. WebKit processes run in isolated memory spaces, and loading extensive arrays of high-resolution page canvas elements can trigger Out-Of-Memory (OOM) app crashes.
To solve this:
- Viewport rendering: We intercept canvas creation and only render pages currently inside the active viewport + 1 buffer page.
- Dynamic Downscaling: If device RAM metrics indicate high usage, canvas drawing resolutions are scaled down by 25%.
Safe Scoped Storage File Editing
On Android 11+ (API 30+), apps must respect scoped storage constraints. They cannot write files directly using absolute paths. Instead, they must obtain a ParcelFileDescriptor to write to shared directories.
MOC Office uses iText Core to parse and modify files locally via stream descriptors. Below is the Kotlin method we use to unlock password-protected PDF files, decrypting the payload using Bouncy Castle cryptographic providers and writing the results back to the user's selected location:
package com.rebelroot.mocoffice.pdfimport android.content.Contextimport android.net.Uriimport com.itextpdf.kernel.pdf.PdfDocumentimport com.itextpdf.kernel.pdf.PdfReaderimport com.itextpdf.kernel.pdf.PdfWriterimport com.itextpdf.kernel.pdf.ReaderPropertiesimport java.io.InputStreamimport java.io.OutputStreamclass SecurePdfEngine(private val context: Context) {fun decryptPdfFile(inputUri: Uri, outputUri: Uri, passwordBytes: ByteArray) {var inputStream: InputStream? = nullvar outputStream: OutputStream? = nulltry {inputStream = context.contentResolver.openInputStream(inputUri)outputStream = context.contentResolver.openOutputStream(outputUri)// Setup iText properties with user's decryption passwordval properties = ReaderProperties().setReaderPassword(passwordBytes)val reader = PdfReader(inputStream, properties)// Open the document and write it decrypted to the output streamval pdfDoc = PdfDocument(reader, PdfWriter(outputStream))// Closing commits modifications and writes file descriptor headerspdfDoc.close()} finally {inputStream?.close()outputStream?.close()}}}
Dynamic Theming with Jetpack Compose & Material You
MOC Office integrates a Material Design 3 UI layer. We map system colors to the reader theme, enabling support for dynamic background adaptation (adapt-to-wallpaper) on Android 12+ devices.
Furthermore, we configure custom CSS style rules on our WebView layout to match the app theme, providing a unified appearance (light, dark, or AMOLED true-black) across both native pages and webview canvas components.