npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@reekon-tools/react-native-pdf-canvas

v0.2.0

Published

PDF pages as Skia images, drawn at exact doc-space rects inside somebody else's transform. One PDFium engine on iOS, Android and web. Owns no canvas, no gestures, no viewport state.

Readme

@reekon-tools/react-native-pdf-canvas

npm license

Renders PDF pages as Skia images positioned at exact PDF-space rectangles within a host-supplied transform. A single PDFium engine serves iOS, Android and web, so a given tile is byte-identical across all three platforms.

Overview

The package does not provide a PDF viewer. It owns no <Canvas>, no gesture handlers, no viewport state and no document model; each of those remains the responsibility of the host application. What it provides is rasterized page tiles, each paired with the document-space rectangle it occupies, for the host to draw inside its own transform alongside its own content.

<Canvas>
  <Group transform={worldTransform}>
    <PdfContentView content={content} />
    {/* Host content: annotations, measurements, overlays — same doc space, same transform */}
  </Group>
</Canvas>

Conventional PDF components encapsulate the scroll view, the zoom transform and the rendered output. Content drawn above such a component must be registered against its internal viewport, and that registration is approximate. This package inverts the arrangement: it emits rectangles in PDF points and never reads the host's transform. Four properties follow.

  • Exact registration. A single transform is applied once, to the page and to the host's content alike. No error term is introduced, because the package performs no conversion to screen space.
  • No contention for the frame budget. The package contributes zero Reanimated shared values to the Skia subtree, so a host driving its world transform from the UI thread retains a per-frame budget of one.
  • Deferred rasterization. Rendering occurs when the host reports that its viewport has settled, rather than on every frame. A stale tile is soft but never mispositioned, which is the property that makes deferral safe.
  • Bounded memory at any zoom level. Detail tiles track the viewport rather than the page, so a 36-inch sheet at 8× magnification costs no more than the same sheet at 1×.

Intended use

React Native and web applications that render their own content above a PDF and require it to register exactly: markup and annotation tools, field measurement, takeoff and estimating, CAD and drawing review, and form overlays.

Out of scope

The package supplies no viewer chrome — no scroll view, page controls or navigation — and deliberately requires the host to provide the canvas, the gesture handling and the page layout. It does not extract text, perform search, or modify documents; its function is rasterization.

Project status

Version 0.2.0 is a pre-release. The core, the cadence controller, the React layer and a deterministic fake backend are implemented and tested. All three platform backends render with PDFium, so annotations, form fields, passwords and page rotation behave identically on each. The native core is covered by a host-machine C++ suite, and the web backend by 45 tests driving real PDFium WebAssembly under Node.

Two areas remain unverified: the web backend has not yet been exercised in a browser, and the iOS binding requires a device pass.

Requirements

| Peer dependency | Range | Required | | ---------------------------- | ---------- | ------------------------------------------ | | react | >=19.0.0 | Always | | @shopify/react-native-skia | >=2.8.0 | Always | | react-native | >=0.78.0 | Native platforms only | | react-native-reanimated | >=3.19.1 | Only if the host transform is driven by it | | @embedpdf/pdfium | >=2.15.0 | Web only |

PDFium is not an npm dependency. It is retrieved at build time from pdfium-binaries, pinned by SHA-256 digest in scripts/pdfium-manifest.json, and adds approximately 6.5 MB per platform. iOS retrieves it during CocoaPods podspec evaluation; Android retrieves it from a Gradle task. Air-gapped builds may direct PDFCANVAS_PDFIUM_CACHE or PDFCANVAS_PDFIUM_MIRROR at a local copy, which is still verified against the manifest.

Installation

npm install @reekon-tools/react-native-pdf-canvas

The platform backend is not registered automatically. It resides on a dedicated subpath so that importing the package never introduces a native module into a web or test bundle.

iOS and Android

Autolinking handles the native module. Register the backend once, during application startup:

import {setDefaultRasterizer} from '@reekon-tools/react-native-pdf-canvas';
import {getDefaultRasterizer} from '@reekon-tools/react-native-pdf-canvas/rasterizer';

setDefaultRasterizer(getDefaultRasterizer());

Then run pod install in the ios directory.

Web

Web configuration requires three steps, performed once before the first render.

1. Load CanvasKit. This step may be omitted by hosts that already load CanvasKit themselves, whether through WithSkiaWeb, RN Skia's LoadSkiaWeb, or a <script> tag that assigns globalThis.CanvasKit; the package detects any of these. loadPdfCanvasSkiaWeb is the awaitable equivalent:

import {loadPdfCanvasSkiaWeb} from '@reekon-tools/react-native-pdf-canvas/web-init';

// The host supplies the locator. The package never hardcodes a CanvasKit URL, because
// the version that must match is the one installed in the host's dependency tree.
await loadPdfCanvasSkiaWeb({
  locateFile: file =>
    `https://cdn.jsdelivr.net/npm/canvaskit-wasm@${canvasKitVersion}/bin/full/${file}`,
});

2. Configure the PDFium worker. The package ships the worker but constructs neither a Worker nor a URL of its own, as worker bundling and the location of pdfium.wasm are properties of the host's build pipeline. Under Vite:

import PdfCanvasWorker from '@reekon-tools/react-native-pdf-canvas/worker?worker';
import wasmUrl from '@embedpdf/pdfium/pdfium.wasm?url';
import {configureWebPdfium} from '@reekon-tools/react-native-pdf-canvas/rasterizer';
import {setDefaultRasterizer} from '@reekon-tools/react-native-pdf-canvas';

setDefaultRasterizer(
  configureWebPdfium({
    createWorker: () => new PdfCanvasWorker(),
    wasm: {url: wasmUrl},
  }),
);

Any other means of producing a module worker is equally acceptable, including new Worker(url, {type: 'module'}). The wasm option also accepts {binary} for hosts with their own asset pipeline, at the cost of a 4.6 MB structured clone per document.

3. Do not wrap PdfContentView in WithSkiaWeb. The component mounts within the host's <Group> inside the host's <Canvas>, where a lazy component boundary would break the Skia subtree. A WithSkiaWeb boundary belongs around the canvas as a whole.

If configuration is omitted, every openPdfDocument and usePdfDocument call fails with a descriptive unsupported: No PageRasterizer available error rather than degrading silently. createFakeRasterizer(), exported from the ./testing subpath, keeps the remainder of the API usable under test.

Usage

import {Canvas, Group} from '@shopify/react-native-skia';
import {
  usePdfDocument,
  usePdfLayer,
  PdfContentView,
} from '@reekon-tools/react-native-pdf-canvas';

function Sheet({uri, worldTransform, viewport}) {
  // Both hooks must be mounted above the <Canvas>. See "Hook placement".
  const {document} = usePdfDocument({uri});
  const {content, controller} = usePdfLayer({document});

  // Gesture handling and the transform remain the host's. Report settle events.
  const pan = Gesture.Pan()
    .onBegin(() => runOnJS(controller.suppress)())
    .onFinalize(() => runOnJS(controller.settle)(viewport()));

  return (
    <GestureDetector gesture={pan}>
      <Canvas style={{flex: 1}}>
        <Group transform={worldTransform}>
          <PdfContentView content={content} />
          {/* Remaining host content, in the same doc space */}
        </Group>
      </Canvas>
    </GestureDetector>
  );
}

viewport() returns a plain {visibleDocRect, scale} snapshot. The package never reads the host's shared values; the host pushes the snapshot.

Viewport cadence

The host is required to report when its viewport settles. The package cannot determine this independently: a withDecay fling animates for approximately 38 seconds at Reanimated's default deceleration, and no reliable means exists for a library to observe a consumer's animation from outside it.

| Method | Called on | | ----------------------- | -------------------------------------------------- | | controller.suppress() | Gesture begin | | controller.settle(v) | Gesture end, or an animation's completion callback | | controller.hint(v) | Continuous input: wheel, trackpad, momentum |

hint is debounced behind a trailing quiet timer. settle takes effect immediately.

Hook placement

Both hooks must be mounted above the <Canvas>. React context does not cross the canvas boundary, as RN Skia mounts a separate reconciler for the Skia subtree, and a magnifier loupe constitutes a second canvas with its own copy of every hook. A cache instantiated inside the canvas is therefore created twice, concurrently, doubling tens of megabytes. Hoisting the hook and passing the same content object to both canvases is what makes a loupe free.

API reference

Entry points

| Subpath | Exports | | -------------- | ------------------------------------------------------------------------------------------------------------------- | | . (root) | usePdfDocument, usePdfLayer, PdfContentView, openPdfDocument, createPdfController, layouts, policy, types | | ./rasterizer | getDefaultRasterizer, configureWebPdfium | | ./web-init | loadPdfCanvasSkiaWeb() — web only | | ./worker | The PDFium module worker; a bundler entry point, web only | | ./skia | ingestRaster, imageFromPixels, rasterByteLength, skiaApi() | | ./testing | createFakeRasterizer() and the deterministic scenes |

The root barrel contains no module-scope Skia, which permits it to be evaluated in Node during a server-side rendering pass. The Skia ingest seam is reached through the ./skia subpath.

Page layouts

singlePage(), continuousVertical(gap) and spread(gap) are provided. A host may supply any function of the form (pages: DocSize[]) => DocRect[].

Render policy

Cadence is configured through a partial RasterPolicy. Every constant has an exported default:

usePdfLayer({document, policy: {sharpnessBand: 1.2, quietZoomMs: 400}});

minEpochIntervalMs (default 300) should be measured before production use and set to the observed p95 raster time. A value set too low permits a user adjusting the zoom to supersede each render before it completes.

Platform support

| Platform | Engine | Backend identifier | | -------- | --------------------------------------------- | ------------------ | | iOS | PDFium via PDFium.xcframework | ios-pdfium | | Android | PDFium via libpdfium.so | android-pdfium | | Web | PDFium WebAssembly in a module worker | web-pdfium | | Any | createFakeRasterizer(), for tests and demos | Configurable |

All three platform backends link the same pinned PDFium release. Annotations, form fields, passwords and the /Rotate page attribute are therefore handled identically on each.

License

Licensed under the Apache License, Version 2.0. See LICENSE for the full terms.

The package may be used in commercial and closed-source software. Redistribution requires retaining the copyright notice and the attributions in NOTICE, and stating any modifications made to the files.

Third-party components

Because this package builds against PDFium, an application that ships it also ships PDFium and its bundled components, licensed variously under BSD-3-Clause, MIT, Apache-2.0, the FreeType License, the zlib License and the IJG License. NOTICE enumerates the attributions to reproduce in an application's third-party notices. Complete license texts are included in the licenses/ directory of every PDFium archive retrieved during the build.

Internals

The rationale behind each constant, each measured figure and each upstream defect the package works around is documented in CLAUDE.md in the repository, alongside extended design documentation under docs/. Neither is included in the published tarball. The relevant section should be consulted before altering behavior it describes.

yarn install
yarn ci                  # Format check, typecheck, tests, build
yarn pdfium:fetch host   # Retrieve and verify PDFium for the local machine
yarn native:test         # Build and run the C++ core suite locally