Opened from the filesystem. The iOS API reference will be blank — it fetches its content and needs a real origin. Run ./serve.sh in this directory.

PDFCora for Android

pdfcoraThe core: documents, pages, text, forms, annotations, signatures, redaction pdfcora-uiThe Compose viewer and its toolbar, theme and review bar pdfcora-aiAnswers, summaries, proposals, alt text, remediation, diff

Install

dependencies {
    implementation("com.pdfcora:pdfcora:0.1.0")
    implementation("com.pdfcora:pdfcora-ui:0.1.0")   // optional, the viewer
    implementation("com.pdfcora:pdfcora-ai:0.1.0")   // optional, AI features
}

The packages are not published yet. Until they are, build from source: see Getting a working checkout.

Or drop pdfcora-release.aar into libs/. All four ABIs are included.

Open and render

val document = PdfDocument.open(bytes)
val page = document.page(0)
val bitmap = page.render(width = 1024, options = PdfRenderOptions(annotations = true, forms = true))

Every engine call runs on one engine thread that all documents share, so concurrent use is safe and calls on separate documents take turns. Most calls are suspend; close(), isClosed, fingerprint and page.text are not. Close it when done — PdfDocument is Closeable.

Read a signature

for (signature in document.signatures()) {
    if (!signature.coversWholeDocument) {
        // Valid, but content was appended after signing and is not protected.
    }
}

Verify it

when (val verdict = document.verify(signature, PdfVerifyOptions(trustAnchors = roots))) {
    is PdfSignatureValidity.Valid -> println(verdict.signer.commonName)
    is PdfSignatureValidity.DigestMismatch -> println("the bytes changed after signing")
    is PdfSignatureValidity.Untrusted -> println("intact, but not trusted")
    else -> Unit
}

Integrity and trust are separate answers, and revocation is not checked.

Redact it

val report = document.redact(
    listOf(
        PdfRedactionTarget.Region(rect, pageIndex = 0),
        PdfRedactionTarget.TextMatches("account number"),
    ),
)
val clean = document.toByteArray(PdfSaveMode.FULL)   // full, always

Content is removed and the page's content stream regenerated, so the words leave the file — a black box drawn over text would leave every one of them in it. Save with full; an incremental save does not write the regenerated stream at all. Images overlapping the region are removed whole rather than repainted, and a signed document cannot be redacted without breaking its signature.

Sign it

val signed = document.sign(
    PdfSignOptions(
        signer = Pkcs12Signer(p12, password),
        reason = "Approved",
        timestampUrl = "https://your-tsa.example/tsr",   // optional, PAdES B-T
    ),
)

Signing appends an incremental update, so a signature already on the document keeps verifying. The first release signs invisibly: a signature is valid without a visible appearance. Signing uses the bytes the document was opened from, so edits made in memory, such as the redaction above, are not in the signed file. To sign edits, save, reopen, then sign.

Place a control over a form field

val transform = PdfViewTransform.fittingWidth(page.size(), viewportPx)
page.formFields().forEach { field -> val box = transform.toView(field.base.rect) }

All platforms