@skyprint/image2pdf-expo
v0.1.0
Published
Expo / React Native bridge for image2pdf — converts JPG/PNG/HEIC images to a single A4 PDF entirely on-device.
Downloads
12
Maintainers
Readme
@skyprint/image2pdf-expo
React Native / Expo bridge for the
image2pdf WebAssembly
library. Converts JPG / PNG / (pre-converted) HEIC images into a single
A4 PDF entirely on-device, with no network calls and no cloud round-trip.
This package exists because Hermes (the JS engine bundled in Expo
Go) does not implement WebAssembly. The wasm is therefore loaded
inside a hidden react-native-webview, which runs the full browser
kernel (iOS WebKit / Android Blink) and does support wasm. We talk to
the WebView via postMessage and treat the wasm as a black box.
Install
# in your Expo / RN app
npm install @skyprint/image2pdf-expo
# + the peer dependencies (see package.json):
# expo, expo-asset, expo-file-system, react-native-webviewUse
import { useImage2Pdf, Image2PdfBridge } from '@skyprint/image2pdf-expo';
function MyScreen() {
const { convert, status, result, error } = useImage2Pdf();
return (
<>
{/* Mount the bridge once near the top of your tree.
It renders as a 0×0 absolutely-positioned WebView. */}
<Image2PdfBridge />
<Button
title="Convert"
onPress={() => convert(imageUris, { title: 'my-doc', marginMm: 5 })}
/>
<Text>status: {status}</Text>
{result && <Text>PDF: {result.pages} pages, {result.bytes} bytes, {result.durationMs} ms</Text>}
{error && <Text>error: {String(error)}</Text>}
</>
);
}convert() takes a list of file:// URIs (the same shape
expo-image-picker returns) and returns:
interface ConvertResult {
uri: string; // local file:// URI of the saved PDF (in cache dir)
bytes: number; // PDF size in bytes
pages: number; // page count
durationMs: number; // wall time spent in the wasm convert() call
}How the bridge works
┌──────────────────────┐ ┌─────────────────────────────┐
│ React Native side │ post │ Hidden WebView │
│ (useImage2Pdf hook) │ ─────► │ bridge.js glue │
│ │ ◄───── │ └─ image2pdf.js (wasm) │
└──────────────────────┘ │ └─ image2pdf_bg.wasm │
└─────────────────────────────┘For 30 × 8 MB HEIC photos, the wasm call itself takes < 1 s; the WebView's data-URL fetch of the local files is essentially a memcpy.
Options
| Field | Default | Description |
| ---------- | -------------- | ------------------------------------------ |
| title | "image2pdf" | PDF document title |
| author | "" | PDF author metadata |
| marginMm | 10 | Page margin in millimeters |
| dpi | 75 | Pixels-per-inch for image → point scaling |
HEIC support
iOS camera photos are HEIC by default. Our wasm does not decode HEIC on React Native (it would require C++ stdlib in the bundle). The recommended workaround:
import * as ImageManipulator from 'expo-image-manipulator';
const jpegUri = await ImageManipulator.manipulateAsync(
heicUri,
[],
{ compress: 1, format: ImageManipulator.SaveFormat.JPEG },
).then((r) => r.uri);expo-image-manipulator uses iOS ImageIO / Android MediaCodec, so the
HEIC decode never enters our wasm. Functionally equivalent to native
HEIC support, with a small CPU cost (the re-encode is fast on modern
phones and the resulting JPEG is smaller than HEIC anyway).
Example app (SDK 55)
# from repo root — stage wasm into assets/
make expo-build
cd example
npm install
npx expo start --clearThe example depends on this package via "file:..". Metro needs
example/metro.config.js to watch the parent
directory. Do not run npm install in integrations/expo/ (the
library root) — that creates a second node_modules with its own
react-native and breaks the example bundler.
Build / publish
# from the image2pdf repo root, after editing the rust lib:
make expo-build # rebuilds wasm, copies into this package's assets/
# then in this directory:
npm version patch
npm publishLicense
MIT — see the parent repo's LICENSE.
