Skip to content

Commit f7c9451

Browse files
committed
docs: update README with enhanced web zero-copy details and platform specifics
Expand README to include clarification on web zero-copy rendering via Skia's wasm heap and the use of `Data.makeUninitialized`. Consolidate and refine platform-specific implementation details for `jvmMain`, `androidMain`, `iosMain`, and `webMain`.
1 parent 2ea194d commit f7c9451

1 file changed

Lines changed: 30 additions & 15 deletions

File tree

README.md

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
A Kotlin Multiplatform PDF rendering and text-extraction library built on top of
44
[bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries) and
5-
Compose Multiplatform. Zero-copy render pipeline on JVM / Android / iOS (Web
6-
posts pixels from a worker), a Compose-first API, and a sample desktop/mobile
7-
reader with thumbnails, progressive rendering, and selectable text.
5+
Compose Multiplatform. Zero-copy render pipeline on every target — on the web
6+
the transferred pixel `ArrayBuffer` is written straight into Skia's wasm heap,
7+
no intermediate Kotlin `ByteArray`. A Compose-first API and a sample
8+
desktop/mobile reader with thumbnails, progressive rendering, and selectable
9+
text round it out.
810

911
## Features
1012

1113
- **Compose Multiplatform composables** — drop `PdfPage` or `PdfThumbnail` into
1214
any Compose UI.
13-
- **Zero-copy rendering** on JVM / Android / iOS: PDFium writes directly into
14-
Skia / Android `Bitmap` pixel memory.
15+
- **Zero-copy rendering** on every target. JVM / Android / iOS hand PDFium a
16+
raw pixel pointer into Skia / Android `Bitmap` memory. Web allocates the
17+
destination buffer inside Skia's wasm heap via `Data.makeUninitialized` and
18+
writes the worker's transferred `ArrayBuffer` straight in — no Kotlin
19+
`ByteArray` round-trip, no `installPixels` second copy.
1520
- **Progressive rendering** (preview → full) with a debounced size flow, so
1621
scroll and zoom feel instant.
1722
- **Two-tier LRU cache** (reader bitmaps + thumbnails) with off-screen prefetch.
@@ -594,10 +599,13 @@ screenH = (top - bottom) × scaleY
594599
│ PdfThumbnail ─┘ │
595600
│ PdfRenderCache PageTextLayout textClipEntry (expect) │
596601
├──────────────────┬───────────────┬─────────────┬─────────────────────────────┤
597-
│ jvmMain │ androidMain │ iosMain │ jsMain / wasmJsMain (web) │
598-
│ JNI glue │ JNI + NDK │ cinterop │ pdfium.wasm in a Web Worker │
599-
│ → Skia Bitmap │ AndroidBitmap │ libpdfium.a │ RPC via postMessage, │
600-
│ zero-copy │ zero-copy │ + Skia │ transferable pixels → Skia │
602+
│ jvmMain │ androidMain │ iosMain │ webMain (js + wasmJs) │
603+
│ JNI glue │ JNI + NDK │ cinterop │ pdfium.wasm in a Web │
604+
│ → Skia Bitmap │ AndroidBitmap │ libpdfium.a │ Worker; RPC via │
605+
│ zero-copy │ zero-copy │ + Skia │ postMessage transferables │
606+
│ │ │ │ → Skia heap zero-copy │
607+
│ │ │ │ jsMain / wasmJsMain just │
608+
│ │ │ │ host a small PlatformBridge │
601609
└──────────────────┴───────────────┴─────────────┴─────────────────────────────┘
602610
```
603611

@@ -613,6 +621,13 @@ Key facts:
613621
`Bitmap.peekPixels().addr` and pass it to `FPDFBitmap_CreateEx`. PDFium
614622
writes BGRA pixels straight into Skia's bitmap memory. On Android we lock
615623
the `android.graphics.Bitmap` via `AndroidBitmap_lockPixels` and do the same.
624+
On web, the pdfium worker transfers the pixel `ArrayBuffer` to the main
625+
thread; we allocate the destination inside Skia's own wasm heap via
626+
`Data.makeUninitialized` and copy the transferred buffer directly there with
627+
a typed-array `.set()` on the Skia memory view obtained through
628+
`org.jetbrains.skiko.wasm.awaitSkiko`. One memcpy into the final
629+
destination, no `installPixels` round-trip. Pattern cribbed from
630+
[coil3.decode.WebWorker](https://github.com/coil-kt/coil/blob/69b8383a3f95300ddb466afdbe9c54ce2eccb652/coil-core/src/jsCommonMain/kotlin/coil3/decode/WebWorker.kt).
616631

617632
- **Native binary delivery.** `pdfium/build.gradle.kts` registers a set of
618633
Gradle tasks that download the bblanchon archives, extract them, and stage
@@ -707,12 +722,12 @@ if available.
707722
bounding boxes, not glyph positioning from the embedded PDF font. Copied
708723
text is exact, but highlight rectangles can differ slightly from what
709724
Chrome / PDF.js render when they can access the original font metrics.
710-
- **Web: no zero-copy to Skia.** On wasmJs/JS, `pdfium.wasm` runs inside a
711-
dedicated Web Worker (so the main thread never blocks). Pixels are posted
712-
to the main thread via `postMessage` transferables and bulk-copied once
713-
into a Skia `Bitmap` via `installPixels` — the only unavoidable copy
714-
in the pipeline, since Skia has its own wasm heap with no direct
715-
`ArrayBuffer` install.
725+
- **Web: still one memcpy per render.** "Zero-copy" here means "no
726+
intermediate Kotlin `ByteArray`, no `installPixels`" — the worker's
727+
transferred `ArrayBuffer` is written straight into Skia's wasm heap. A true
728+
zero-memcpy pipeline would need `SharedArrayBuffer` (which in turn requires
729+
COOP/COEP headers) so the pdfium worker and Skia share one linear memory.
730+
Not currently implemented.
716731
- **Licensing.** PDFium is dual-licensed BSD-3-Clause / Apache-2.0 (see
717732
PDFium's `LICENSE`). bblanchon's binaries carry that license forward. If
718733
you ship this code, include the upstream PDFium notices.

0 commit comments

Comments
 (0)