@reekon-tools/react-native-pdf-canvas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PdfCanvas.podspec +80 -0
- package/README.md +1391 -0
- package/android/build.gradle +175 -0
- package/android/consumer-rules.pro +3 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +2 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +98 -0
- package/android/settings.gradle +34 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +226 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +239 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +251 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +221 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +783 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +1388 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +98 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +324 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +775 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +20 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +558 -0
- package/android/src/jsi/cpp/CMakeLists.txt +33 -0
- package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +469 -0
- package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +140 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +45 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +564 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +33 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +277 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +214 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +30 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +234 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +47 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +143 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +95 -0
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +626 -0
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasPackage.java +33 -0
- package/android/tools/compile-gate.sh +289 -0
- package/dist/cache.d.ts +161 -0
- package/dist/cache.js +389 -0
- package/dist/clock.d.ts +21 -0
- package/dist/clock.js +31 -0
- package/dist/controller.d.ts +131 -0
- package/dist/controller.js +1002 -0
- package/dist/geometry.d.ts +96 -0
- package/dist/geometry.js +248 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +50 -0
- package/dist/index.native.d.ts +21 -0
- package/dist/index.native.js +23 -0
- package/dist/index.web.d.ts +24 -0
- package/dist/index.web.js +26 -0
- package/dist/layout.d.ts +78 -0
- package/dist/layout.js +152 -0
- package/dist/peer-check.d.ts +83 -0
- package/dist/peer-check.js +183 -0
- package/dist/planner.d.ts +35 -0
- package/dist/planner.js +530 -0
- package/dist/policy.d.ts +14 -0
- package/dist/policy.js +168 -0
- package/dist/rasterizer/android.d.ts +121 -0
- package/dist/rasterizer/android.js +126 -0
- package/dist/rasterizer/buffer.d.ts +18 -0
- package/dist/rasterizer/buffer.js +18 -0
- package/dist/rasterizer/fake.d.ts +92 -0
- package/dist/rasterizer/fake.js +433 -0
- package/dist/rasterizer/index.d.ts +38 -0
- package/dist/rasterizer/index.js +69 -0
- package/dist/rasterizer/index.native.d.ts +19 -0
- package/dist/rasterizer/index.native.js +80 -0
- package/dist/rasterizer/index.web.d.ts +63 -0
- package/dist/rasterizer/index.web.js +75 -0
- package/dist/rasterizer/ingest.d.ts +180 -0
- package/dist/rasterizer/ingest.js +307 -0
- package/dist/rasterizer/ios.d.ts +84 -0
- package/dist/rasterizer/ios.js +155 -0
- package/dist/rasterizer/native-bridge.d.ts +261 -0
- package/dist/rasterizer/native-bridge.js +413 -0
- package/dist/rasterizer/web/client.d.ts +84 -0
- package/dist/rasterizer/web/client.js +359 -0
- package/dist/rasterizer/web/engine.d.ts +69 -0
- package/dist/rasterizer/web/engine.js +308 -0
- package/dist/rasterizer/web/index.d.ts +26 -0
- package/dist/rasterizer/web/index.js +21 -0
- package/dist/rasterizer/web/pdfium.d.ts +213 -0
- package/dist/rasterizer/web/pdfium.js +170 -0
- package/dist/rasterizer/web/protocol.d.ts +138 -0
- package/dist/rasterizer/web/protocol.js +37 -0
- package/dist/rasterizer/web/session.d.ts +106 -0
- package/dist/rasterizer/web/session.js +413 -0
- package/dist/rasterizer/web/worker.d.ts +44 -0
- package/dist/rasterizer/web/worker.js +76 -0
- package/dist/react/PdfContentView.d.ts +98 -0
- package/dist/react/PdfContentView.js +189 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +3 -0
- package/dist/react/usePdfDocument.d.ts +134 -0
- package/dist/react/usePdfDocument.js +275 -0
- package/dist/react/usePdfLayer.d.ts +94 -0
- package/dist/react/usePdfLayer.js +388 -0
- package/dist/skia/index.d.ts +26 -0
- package/dist/skia/index.js +25 -0
- package/dist/skia/index.native.d.ts +14 -0
- package/dist/skia/index.native.js +16 -0
- package/dist/skia/index.web.d.ts +12 -0
- package/dist/skia/index.web.js +14 -0
- package/dist/skia/resolve.native.d.ts +18 -0
- package/dist/skia/resolve.native.js +22 -0
- package/dist/skia/resolve.web.d.ts +41 -0
- package/dist/skia/resolve.web.js +62 -0
- package/dist/skia/skia-api.d.ts +94 -0
- package/dist/skia/skia-api.js +113 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +28 -0
- package/dist/testing/scenes.d.ts +100 -0
- package/dist/testing/scenes.js +267 -0
- package/dist/types.d.ts +611 -0
- package/dist/types.js +21 -0
- package/dist/web-init.d.ts +68 -0
- package/dist/web-init.js +64 -0
- package/ios/Package.swift +58 -0
- package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +743 -0
- package/ios/Sources/PdfCanvasBridge/include/PdfCanvasModule.h +36 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +38 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +389 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +57 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +44 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +82 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +37 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +158 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +80 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +65 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +548 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +48 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +26 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +122 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +80 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +79 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +129 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +100 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +65 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +111 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +85 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +163 -0
- package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +297 -0
- package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +140 -0
- package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +358 -0
- package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +262 -0
- package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +173 -0
- package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +114 -0
- package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +158 -0
- package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +544 -0
- package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +287 -0
- package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +396 -0
- package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +639 -0
- package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +179 -0
- package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +283 -0
- package/package.json +144 -0
- package/react-native.config.js +40 -0
- package/scripts/check-skia-peer.mjs +86 -0
- package/scripts/skia-peer-check.mjs +363 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React binding for the raster controller.
|
|
3
|
+
*
|
|
4
|
+
* THIS HOOK BELONGS ABOVE THE `<Canvas>`, NOT INSIDE IT. Two reasons, and both
|
|
5
|
+
* are load-bearing:
|
|
6
|
+
*
|
|
7
|
+
* 1. React context does not cross the Canvas boundary. RN Skia mounts its own
|
|
8
|
+
* reconciler for the Skia subtree, so a provider placed inside a `<Canvas>`
|
|
9
|
+
* is invisible to the host's tree — and a provider outside it is invisible to
|
|
10
|
+
* the Skia children. Anything shared has to be a prop, which is exactly what
|
|
11
|
+
* `content` is.
|
|
12
|
+
* 2. A magnifier loupe is a SECOND `<Canvas>` looking at the same document. If
|
|
13
|
+
* the cache were instantiated inside the canvas subtree there would be two of
|
|
14
|
+
* them, live at the same time, each with its own copy of every base raster —
|
|
15
|
+
* double the memory for pixels that are identical. Hoisting the hook above
|
|
16
|
+
* both canvases and passing the same `content` down is what makes the loupe
|
|
17
|
+
* free.
|
|
18
|
+
*
|
|
19
|
+
* The hook contributes ZERO Reanimated shared values to the Skia subtree. The
|
|
20
|
+
* host's transform does the panning and zooming; this hook only ever hears about
|
|
21
|
+
* the viewport through the `controller` methods the host calls.
|
|
22
|
+
*/
|
|
23
|
+
import type { PdfContent, PdfController, PdfDiagnostic, PdfPageLayout, RasterPolicy } from '../types.js';
|
|
24
|
+
import type { PdfDocument } from './usePdfDocument.js';
|
|
25
|
+
/**
|
|
26
|
+
* Only the pages the viewport actually intersects may hold rasters.
|
|
27
|
+
*
|
|
28
|
+
* THE HONEST SPELLING OF THE DEFAULT, and the one to use. It carries no tuning
|
|
29
|
+
* number because there is no tuning to do: see `PdfPageWindow` for the knob this
|
|
30
|
+
* replaces and why that knob was a lie.
|
|
31
|
+
*/
|
|
32
|
+
export interface PdfOnScreenPages {
|
|
33
|
+
readonly onScreen: true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated THE RADIUS IS NOT IMPLEMENTED. `{window: 3}` behaves exactly like
|
|
37
|
+
* `{window: 0}` and always has — a window resolves to no allow-list at all (see
|
|
38
|
+
* `resolveAllowedPages`), so the number has nowhere to go. Use
|
|
39
|
+
* `{onScreen: true}`, which means what this does; or an explicit `number[]` if
|
|
40
|
+
* you want a genuine allow-list. Implementing the radius needs
|
|
41
|
+
* `PdfControllerOptions` to accept a per-viewport allow-list — an
|
|
42
|
+
* `(visiblePages: readonly number[]) => ReadonlySet<number>` in place of the set,
|
|
43
|
+
* or a plain `pageWindow` radius the planner applies — and until then this member
|
|
44
|
+
* exists only so an existing `{window: n}` call site keeps compiling.
|
|
45
|
+
*/
|
|
46
|
+
export interface PdfPageWindow {
|
|
47
|
+
readonly window: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Which pages may hold rasters: an explicit allow-list, or every page the
|
|
51
|
+
* viewport is currently over.
|
|
52
|
+
*/
|
|
53
|
+
export type PdfPageSelection = readonly number[] | PdfOnScreenPages | PdfPageWindow;
|
|
54
|
+
/**
|
|
55
|
+
* THE DEFAULT IS THE PAGES ON SCREEN, NEVER "ALL". A 400-page architectural spec
|
|
56
|
+
* book at ARCH-D holds roughly 10 MiB in a single base raster; "all" is therefore
|
|
57
|
+
* about 4 GiB of standing allocation before a single detail raster exists. The
|
|
58
|
+
* controller retains the bases of pages two either side of the plan's window, so
|
|
59
|
+
* a page-turn in a continuous layout still lands on an already-warm base.
|
|
60
|
+
*/
|
|
61
|
+
export declare const DEFAULT_PAGE_SELECTION: PdfPageSelection;
|
|
62
|
+
export interface UsePdfLayerOptions {
|
|
63
|
+
/** From `usePdfDocument`. Null while loading; the hook returns empty content. */
|
|
64
|
+
document: PdfDocument | null;
|
|
65
|
+
/**
|
|
66
|
+
* @default {onScreen: true} — see DEFAULT_PAGE_SELECTION.
|
|
67
|
+
*
|
|
68
|
+
* A one-element list is what single-page (paged) mode is made of:
|
|
69
|
+
* `{layout: singlePage(), pages: [pageIndex]}`. Changing the list REBUILDS the
|
|
70
|
+
* controller and clears its raster cache, which for a stacked layout is the
|
|
71
|
+
* correct trade — see the note on the memo below.
|
|
72
|
+
*/
|
|
73
|
+
pages?: PdfPageSelection;
|
|
74
|
+
/**
|
|
75
|
+
* Where pages sit in doc space. The package has no opinion; see `./layout`.
|
|
76
|
+
*
|
|
77
|
+
* @default continuousVertical()
|
|
78
|
+
*/
|
|
79
|
+
layout?: PdfPageLayout;
|
|
80
|
+
/** Overrides merged over the default `RasterPolicy`. */
|
|
81
|
+
policy?: Partial<RasterPolicy>;
|
|
82
|
+
onDiagnostic?: (diagnostic: PdfDiagnostic) => void;
|
|
83
|
+
}
|
|
84
|
+
export interface PdfLayer {
|
|
85
|
+
/** Pass straight to `<PdfContentView content={...} />`. */
|
|
86
|
+
content: PdfContent;
|
|
87
|
+
/**
|
|
88
|
+
* Drive this from the host's gesture handlers. Identity is stable for the life
|
|
89
|
+
* of the component — including across document changes — so it can be captured
|
|
90
|
+
* in a gesture callback without a dependency array.
|
|
91
|
+
*/
|
|
92
|
+
controller: PdfController;
|
|
93
|
+
}
|
|
94
|
+
export declare function usePdfLayer({ document, pages: pageSelection, layout, policy, onDiagnostic, }: UsePdfLayerOptions): PdfLayer;
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React binding for the raster controller.
|
|
3
|
+
*
|
|
4
|
+
* THIS HOOK BELONGS ABOVE THE `<Canvas>`, NOT INSIDE IT. Two reasons, and both
|
|
5
|
+
* are load-bearing:
|
|
6
|
+
*
|
|
7
|
+
* 1. React context does not cross the Canvas boundary. RN Skia mounts its own
|
|
8
|
+
* reconciler for the Skia subtree, so a provider placed inside a `<Canvas>`
|
|
9
|
+
* is invisible to the host's tree — and a provider outside it is invisible to
|
|
10
|
+
* the Skia children. Anything shared has to be a prop, which is exactly what
|
|
11
|
+
* `content` is.
|
|
12
|
+
* 2. A magnifier loupe is a SECOND `<Canvas>` looking at the same document. If
|
|
13
|
+
* the cache were instantiated inside the canvas subtree there would be two of
|
|
14
|
+
* them, live at the same time, each with its own copy of every base raster —
|
|
15
|
+
* double the memory for pixels that are identical. Hoisting the hook above
|
|
16
|
+
* both canvases and passing the same `content` down is what makes the loupe
|
|
17
|
+
* free.
|
|
18
|
+
*
|
|
19
|
+
* The hook contributes ZERO Reanimated shared values to the Skia subtree. The
|
|
20
|
+
* host's transform does the panning and zooming; this hook only ever hears about
|
|
21
|
+
* the viewport through the `controller` methods the host calls.
|
|
22
|
+
*/
|
|
23
|
+
import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore, } from 'react';
|
|
24
|
+
import { createRasterCache } from '../cache.js';
|
|
25
|
+
import { createPdfController } from '../controller.js';
|
|
26
|
+
import { continuousVertical, hasOverlappingPages } from '../layout.js';
|
|
27
|
+
import { resolvePolicy } from '../policy.js';
|
|
28
|
+
// Through the `./skia` barrel, not `../rasterizer/ingest.js` directly: that
|
|
29
|
+
// barrel is the documented home of the ingest seam, and routing every internal
|
|
30
|
+
// consumer through it is what keeps "which modules can touch Skia" a list of one
|
|
31
|
+
// directory rather than a grep. Nothing is paid for it — ingest itself no longer
|
|
32
|
+
// names `Skia` at module scope (see `../skia/skia-api.ts`), so this hook stays
|
|
33
|
+
// safe to evaluate before CanvasKit exists.
|
|
34
|
+
import { ingestRaster } from '../skia/index.js';
|
|
35
|
+
/** Same derivation as `PdfContentView`'s, restated rather than imported: that
|
|
36
|
+
* module reaches RN Skia's leaf components, and this hook must stay able to be
|
|
37
|
+
* evaluated before CanvasKit exists. */
|
|
38
|
+
const IS_DEV = typeof __DEV__ !== 'undefined'
|
|
39
|
+
? __DEV__ === true
|
|
40
|
+
: typeof process !== 'undefined' &&
|
|
41
|
+
process.env?.['NODE_ENV'] !== 'production';
|
|
42
|
+
/**
|
|
43
|
+
* THE DEFAULT IS THE PAGES ON SCREEN, NEVER "ALL". A 400-page architectural spec
|
|
44
|
+
* book at ARCH-D holds roughly 10 MiB in a single base raster; "all" is therefore
|
|
45
|
+
* about 4 GiB of standing allocation before a single detail raster exists. The
|
|
46
|
+
* controller retains the bases of pages two either side of the plan's window, so
|
|
47
|
+
* a page-turn in a continuous layout still lands on an already-warm base.
|
|
48
|
+
*/
|
|
49
|
+
export const DEFAULT_PAGE_SELECTION = Object.freeze({
|
|
50
|
+
onScreen: true,
|
|
51
|
+
});
|
|
52
|
+
const isPageList = (selection) => Array.isArray(selection);
|
|
53
|
+
/**
|
|
54
|
+
* An explicit list becomes a static allow-list, clamped to the pages the document
|
|
55
|
+
* actually has — an out-of-range index would otherwise plan a raster the backend
|
|
56
|
+
* can only reject.
|
|
57
|
+
*
|
|
58
|
+
* THE ON-SCREEN SELECTION RESOLVES TO `undefined`, i.e. no allow-list at all —
|
|
59
|
+
* the pages it names depend on the viewport, and no viewport exists when the
|
|
60
|
+
* controller is constructed. `undefined` is not "all": the controller plans only
|
|
61
|
+
* the pages intersecting the snapshot it is handed, and retains their bases two
|
|
62
|
+
* pages either side, so the standing cost is bounded by the screen.
|
|
63
|
+
*
|
|
64
|
+
* "Bounded by the screen" assumes page rects are DISJOINT. Under `singlePage()`
|
|
65
|
+
* every page rect is the same rect, every page is therefore on screen, and this
|
|
66
|
+
* returning `undefined` means the whole document is planned — N pages of bytes to
|
|
67
|
+
* draw one page, measured in `__tests__/single-page.test.ts`. That is what the
|
|
68
|
+
* `__DEV__` warning below is for, and why a stacked layout must be paired with a
|
|
69
|
+
* one-element list.
|
|
70
|
+
*/
|
|
71
|
+
function resolveAllowedPages(selection, pageCount) {
|
|
72
|
+
if (!isPageList(selection)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const allowed = new Set();
|
|
76
|
+
for (const page of selection) {
|
|
77
|
+
if (Number.isInteger(page) && page >= 0 && page < pageCount) {
|
|
78
|
+
allowed.add(page);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return allowed;
|
|
82
|
+
}
|
|
83
|
+
/* ------------------------------------------------------------------ *
|
|
84
|
+
* Owned resources
|
|
85
|
+
* ------------------------------------------------------------------ */
|
|
86
|
+
/**
|
|
87
|
+
* `continuousVertical()` builds a fresh closure per call, and `layout` is a
|
|
88
|
+
* dependency of the controller. One module-level instance keeps the DEFAULT from
|
|
89
|
+
* looking like a layout change on every render and tearing the raster cache down
|
|
90
|
+
* with it.
|
|
91
|
+
*
|
|
92
|
+
* IT USED TO BE `singlePage()`, AND THAT WAS WRONG AS A DEFAULT. A stacked layout
|
|
93
|
+
* is correct only alongside a one-element `pages` list, and the default selection
|
|
94
|
+
* is the pages on screen — under `singlePage()` that is every page in the
|
|
95
|
+
* document, at one doc rect, drawn on top of each other. The two defaults
|
|
96
|
+
* contradicted each other, and nothing exercised the pair: both dev probes pass an
|
|
97
|
+
* explicit `continuousVertical(12)`. A continuous layout is safe with every
|
|
98
|
+
* selection, which is the only property a default needs to have.
|
|
99
|
+
*/
|
|
100
|
+
const DEFAULT_LAYOUT = continuousVertical();
|
|
101
|
+
/**
|
|
102
|
+
* Idempotent by requirement, not by Skia's grace: on web a second
|
|
103
|
+
* `SkImage.dispose()` THROWS where native tolerates it. The cache guards its own
|
|
104
|
+
* paths, but this callback is the last thing holding the image and has to survive
|
|
105
|
+
* being reached twice — an eviction racing a teardown flush, or the same image
|
|
106
|
+
* somehow reaching two caches.
|
|
107
|
+
*
|
|
108
|
+
* Keyed on the SkImage rather than the PdfRaster, because the image is the thing
|
|
109
|
+
* that can be freed twice.
|
|
110
|
+
*/
|
|
111
|
+
const disposedImages = new WeakSet();
|
|
112
|
+
function disposeRaster(raster) {
|
|
113
|
+
if (disposedImages.has(raster.image)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
disposedImages.add(raster.image);
|
|
117
|
+
raster.image.dispose();
|
|
118
|
+
}
|
|
119
|
+
/** Intrinsic geometry for every page, in page order. Cheap: loads no page. */
|
|
120
|
+
function allPageGeometry(document) {
|
|
121
|
+
const geometry = [];
|
|
122
|
+
for (let index = 0; index < document.pageCount; index += 1) {
|
|
123
|
+
geometry.push(document.pageGeometry(index));
|
|
124
|
+
}
|
|
125
|
+
return geometry;
|
|
126
|
+
}
|
|
127
|
+
/* ------------------------------------------------------------------ *
|
|
128
|
+
* Empty state
|
|
129
|
+
* ------------------------------------------------------------------ */
|
|
130
|
+
const EMPTY_PAGES = Object.freeze([]);
|
|
131
|
+
/**
|
|
132
|
+
* Returned before a document exists. A STABLE frozen reference: `getSnapshot`
|
|
133
|
+
* handing back a fresh `{pages: [], epoch: 0}` on every call is the classic
|
|
134
|
+
* useSyncExternalStore infinite-render loop.
|
|
135
|
+
*/
|
|
136
|
+
const EMPTY_CONTENT = Object.freeze({
|
|
137
|
+
pages: EMPTY_PAGES,
|
|
138
|
+
epoch: 0,
|
|
139
|
+
settling: false,
|
|
140
|
+
});
|
|
141
|
+
const NOOP_UNSUBSCRIBE = () => { };
|
|
142
|
+
const NOOP_SUBSCRIBE = () => NOOP_UNSUBSCRIBE;
|
|
143
|
+
const getEmptyContent = () => EMPTY_CONTENT;
|
|
144
|
+
/* ------------------------------------------------------------------ *
|
|
145
|
+
* Hook
|
|
146
|
+
* ------------------------------------------------------------------ */
|
|
147
|
+
/**
|
|
148
|
+
* Stable structural key for a page selection, so an inline literal does not tear
|
|
149
|
+
* down and rebuild the controller (and its whole raster cache) every render.
|
|
150
|
+
*
|
|
151
|
+
* A list is SORTED and DEDUPED first, because the allow-list it resolves to is a
|
|
152
|
+
* Set and cannot tell `[3, 2]` from `[2, 3]`. Page navigation is exactly where a
|
|
153
|
+
* host builds these arrays programmatically, and rebuilding a controller over a
|
|
154
|
+
* reordering it cannot observe is a raster cache thrown away for nothing.
|
|
155
|
+
*
|
|
156
|
+
* Every window spelling collapses to the same key, since they all resolve to the
|
|
157
|
+
* same thing: no allow-list. `{window: 3}` and `{onScreen: true}` are the same
|
|
158
|
+
* controller.
|
|
159
|
+
*/
|
|
160
|
+
function pageSelectionKey(pages) {
|
|
161
|
+
if (!isPageList(pages)) {
|
|
162
|
+
return 'on-screen';
|
|
163
|
+
}
|
|
164
|
+
const sorted = [...new Set(pages)].sort((a, b) => a - b);
|
|
165
|
+
return `l:${sorted.join(',')}`;
|
|
166
|
+
}
|
|
167
|
+
/** Same, for a policy override object. Every RasterPolicy field is a number or a
|
|
168
|
+
* boolean, so a sorted key/value join is a complete description. */
|
|
169
|
+
function policyKey(policy) {
|
|
170
|
+
if (policy === undefined) {
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
const keys = Object.keys(policy).sort();
|
|
174
|
+
let out = '';
|
|
175
|
+
for (const key of keys) {
|
|
176
|
+
out += `${key}=${String(policy[key])};`;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
export function usePdfLayer({ document,
|
|
181
|
+
// The PUBLIC name stays `pages`; locally it is the SELECTION, and the
|
|
182
|
+
// controller's own `pages` is the per-page geometry. Two very different things
|
|
183
|
+
// one rename apart.
|
|
184
|
+
pages: pageSelection = DEFAULT_PAGE_SELECTION, layout, policy, onDiagnostic, }) {
|
|
185
|
+
// The diagnostic callback is almost always an inline arrow. Route it through a
|
|
186
|
+
// ref so a fresh identity on every render never counts as a reason to rebuild
|
|
187
|
+
// the controller.
|
|
188
|
+
const onDiagnosticRef = useRef(onDiagnostic);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
onDiagnosticRef.current = onDiagnostic;
|
|
191
|
+
}, [onDiagnostic]);
|
|
192
|
+
const emitDiagnostic = useCallback((diagnostic) => {
|
|
193
|
+
onDiagnosticRef.current?.(diagnostic);
|
|
194
|
+
}, []);
|
|
195
|
+
const pagesKey = pageSelectionKey(pageSelection);
|
|
196
|
+
const policySignature = policyKey(policy);
|
|
197
|
+
/**
|
|
198
|
+
* Split out of the controller memo so the `__DEV__` check below can see the
|
|
199
|
+
* rects without re-running the layout, and so a warning lives in an EFFECT
|
|
200
|
+
* rather than inside a memo React is free to invoke twice.
|
|
201
|
+
*/
|
|
202
|
+
const pageRects = useMemo(() => document === null
|
|
203
|
+
? null
|
|
204
|
+
: (layout ?? DEFAULT_LAYOUT)(document.pageSizes()), [document, layout]);
|
|
205
|
+
/**
|
|
206
|
+
* A stacked layout (`singlePage()`) is correct ONLY with a one-element `pages`
|
|
207
|
+
* list. Any other selection lets every page hold a base at the same doc rect,
|
|
208
|
+
* and `PdfContentView` draws them in page order — so the memory is N pages and
|
|
209
|
+
* the page on screen is the highest allowed index, not the one the host meant.
|
|
210
|
+
*
|
|
211
|
+
* A warning and not a correction: silently narrowing the selection would make a
|
|
212
|
+
* host's `pages` prop mean something it does not say, and silently swapping the
|
|
213
|
+
* layout would move doc space under the host's transform.
|
|
214
|
+
*/
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (!IS_DEV || pageRects === null || document === null) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!hasOverlappingPages(pageRects)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const allowed = resolveAllowedPages(pageSelection, document.pageCount);
|
|
223
|
+
if (allowed !== undefined && allowed.size === 1) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
console.warn('[react-native-pdf-canvas] usePdfLayer was given a layout that STACKS pages ' +
|
|
227
|
+
`(${pageRects.length} page rects overlap in doc space, e.g. singlePage()) ` +
|
|
228
|
+
`together with a page selection of ${allowed === undefined
|
|
229
|
+
? 'every page on screen'
|
|
230
|
+
: `${allowed.size} pages`}. Every page whose rect is on screen will be rastered at that same rect ` +
|
|
231
|
+
'and drawn on top of the others, so the visible page is the highest ' +
|
|
232
|
+
'allowed index and the cost is one base per page. Pass a one-element list ' +
|
|
233
|
+
'— usePdfLayer({layout: singlePage(), pages: [pageIndex]}) — or use ' +
|
|
234
|
+
'continuousVertical() / spread().');
|
|
235
|
+
}, [document, pageRects, pageSelection]);
|
|
236
|
+
const layer = useMemo(() => {
|
|
237
|
+
if (document === null || pageRects === null) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const resolvedPolicy = resolvePolicy(policy);
|
|
241
|
+
const cache = createRasterCache({
|
|
242
|
+
budgetBytes: resolvedPolicy.cacheBudgetBytes,
|
|
243
|
+
dispose: disposeRaster,
|
|
244
|
+
});
|
|
245
|
+
const controller = createPdfController({
|
|
246
|
+
handle: document.handle,
|
|
247
|
+
pageRects,
|
|
248
|
+
pages: allPageGeometry(document),
|
|
249
|
+
policy: resolvedPolicy,
|
|
250
|
+
ingest: ingestRaster,
|
|
251
|
+
cache,
|
|
252
|
+
allowedPages: resolveAllowedPages(pageSelection, document.pageCount),
|
|
253
|
+
// The BACKEND's number, never a constant here. Absent (a backend written
|
|
254
|
+
// before the field existed) means 1, which is the sequential behaviour
|
|
255
|
+
// every backend was proven against.
|
|
256
|
+
maxConcurrentRenders: document.capabilities.maxConcurrentRenders,
|
|
257
|
+
onDiagnostic: emitDiagnostic,
|
|
258
|
+
backendId: document.rasterizerId,
|
|
259
|
+
});
|
|
260
|
+
return { controller, cache };
|
|
261
|
+
},
|
|
262
|
+
// `pageSelection` and `policy` are intentionally NOT deps: they are object
|
|
263
|
+
// literals at every realistic call site, and rebuilding the controller
|
|
264
|
+
// discards the entire raster cache. Their structural keys stand in for them,
|
|
265
|
+
// which is stronger than identity — a caller who memoises AND mutates gets
|
|
266
|
+
// the correct rebuild.
|
|
267
|
+
//
|
|
268
|
+
// A PAGE TURN THEREFORE REBUILDS EVERYTHING, AND THAT IS DELIBERATE. `pages`
|
|
269
|
+
// going from [2] to [3] changes `pagesKey`, so the controller is destroyed and
|
|
270
|
+
// the cache cleared: page 3 is rendered from scratch and turning back to 2
|
|
271
|
+
// re-renders it rather than hitting the cache. Keeping the controller alive
|
|
272
|
+
// across the change (a mutable allow-list the controller reads per plan) would
|
|
273
|
+
// buy that hit, and was rejected — the pages inside the controller's base
|
|
274
|
+
// retention radius stay MOUNTED, so in a stacked layout the page you left is
|
|
275
|
+
// still painted at the rect of the page you turned to, and even in a continuous
|
|
276
|
+
// layout the resident cost goes from one page to as many as five. The measured
|
|
277
|
+
// cost of the teardown is one page's rasters, which is the page you are turning
|
|
278
|
+
// to and would have paid for anyway. See `__tests__/single-page.test.ts`.
|
|
279
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
280
|
+
[document, pageRects, pagesKey, policySignature, emitDiagnostic]);
|
|
281
|
+
const instance = layer?.controller ?? null;
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (layer === null) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
return () => {
|
|
287
|
+
// Ordering: this cleanup runs after the render that replaced `layer` has
|
|
288
|
+
// already committed, so the Skia tree is no longer drawing anything the
|
|
289
|
+
// cache holds and freeing is safe.
|
|
290
|
+
layer.controller.destroy();
|
|
291
|
+
// destroy() flushes what it had referenced; clear() also takes the entries
|
|
292
|
+
// that were cached but never referenced (a put whose acquire lost a race),
|
|
293
|
+
// which nothing else will ever free. The hook owns this cache exclusively.
|
|
294
|
+
layer.cache.clear();
|
|
295
|
+
// The DOCUMENT is deliberately untouched. The controller only borrowed its
|
|
296
|
+
// handle, and the document may outlive it — a thumbnail strip on the same
|
|
297
|
+
// handle — so closing it here would pull the file descriptor out from
|
|
298
|
+
// under a live consumer. `usePdfDocument` owns that.
|
|
299
|
+
};
|
|
300
|
+
}, [layer]);
|
|
301
|
+
const subscribe = useMemo(() => {
|
|
302
|
+
if (instance === null) {
|
|
303
|
+
return NOOP_SUBSCRIBE;
|
|
304
|
+
}
|
|
305
|
+
// Wrapped rather than passed as `instance.subscribe`, so the controller is
|
|
306
|
+
// free to implement it as a class method without losing `this`. The wrapper
|
|
307
|
+
// identity is stable per instance, which is what useSyncExternalStore needs.
|
|
308
|
+
return (listener) => instance.subscribe(listener);
|
|
309
|
+
}, [instance]);
|
|
310
|
+
const getSnapshot = useMemo(() => {
|
|
311
|
+
if (instance === null) {
|
|
312
|
+
return getEmptyContent;
|
|
313
|
+
}
|
|
314
|
+
// MUST return a cached reference. React calls getSnapshot during render and
|
|
315
|
+
// again after every store notification, and compares with Object.is — a freshly
|
|
316
|
+
// built object here is an infinite render loop, not a stale-data bug. The
|
|
317
|
+
// controller guarantees one frozen PdfContent per epoch.
|
|
318
|
+
return () => instance.getContent();
|
|
319
|
+
}, [instance]);
|
|
320
|
+
const content = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
321
|
+
// A facade whose identity never changes, so hosts can wire it into gesture
|
|
322
|
+
// callbacks and animation completion handlers without dependency arrays. It
|
|
323
|
+
// reads the live instance through a ref rather than closing over one.
|
|
324
|
+
const instanceRef = useRef(instance);
|
|
325
|
+
instanceRef.current = instance;
|
|
326
|
+
/**
|
|
327
|
+
* The last viewport the host handed us, whichever entry point it used.
|
|
328
|
+
*
|
|
329
|
+
* A REF AND NOT STATE: it is written from a gesture callback at gesture rate and
|
|
330
|
+
* nothing renders from it.
|
|
331
|
+
*/
|
|
332
|
+
const lastViewportRef = useRef(null);
|
|
333
|
+
const controller = useMemo(() => ({
|
|
334
|
+
hint(viewport) {
|
|
335
|
+
lastViewportRef.current = viewport;
|
|
336
|
+
instanceRef.current?.hint(viewport);
|
|
337
|
+
},
|
|
338
|
+
settle(viewport) {
|
|
339
|
+
lastViewportRef.current = viewport;
|
|
340
|
+
instanceRef.current?.settle(viewport);
|
|
341
|
+
},
|
|
342
|
+
suppress() {
|
|
343
|
+
instanceRef.current?.suppress();
|
|
344
|
+
},
|
|
345
|
+
resume() {
|
|
346
|
+
instanceRef.current?.resume();
|
|
347
|
+
},
|
|
348
|
+
}), []);
|
|
349
|
+
/**
|
|
350
|
+
* A FRESH CONTROLLER HAS NEVER SEEN A VIEWPORT, AND NOTHING ELSE WILL TELL IT.
|
|
351
|
+
*
|
|
352
|
+
* Every entry point is host-driven, and a host drives them from viewport CHANGES
|
|
353
|
+
* — a gesture, a wheel tick, a re-fit. Change `pages`, `layout` or `policy` while
|
|
354
|
+
* the viewport sits still and the memo above builds a new controller with an
|
|
355
|
+
* empty cache, no `latestViewport` and no epoch: it plans nothing, commits
|
|
356
|
+
* nothing, and the canvas goes BLANK until the user happens to touch the
|
|
357
|
+
* document. MEASURED in rock-desktop's probe before this replay existed: turning
|
|
358
|
+
* from page 0 to page 1 in single-page mode left `epoch 0`, `mounted: none`,
|
|
359
|
+
* `rasters` unchanged and an empty canvas, indefinitely — a page turn is exactly
|
|
360
|
+
* the case where the viewport must NOT move, so nothing ever arrived.
|
|
361
|
+
*
|
|
362
|
+
* The host cannot fix this itself. That the controller was rebuilt is an
|
|
363
|
+
* internal detail of this hook — the `controller` facade's identity is
|
|
364
|
+
* deliberately stable across the swap — so a host has nothing to react to.
|
|
365
|
+
*
|
|
366
|
+
* `settle` and not `hint`: this IS the discrete event (the layer changed under a
|
|
367
|
+
* stationary viewport), and a hint would only arm the quiet timer. It is skipped
|
|
368
|
+
* on the very first controller, where `lastViewportRef` is still null and the
|
|
369
|
+
* host's own first settle is what starts things.
|
|
370
|
+
*
|
|
371
|
+
* DELIBERATELY NOT GATED ON THE DOCUMENT BEING THE SAME ONE. A new document
|
|
372
|
+
* usually arrives with a re-frame, which supersedes this epoch and makes it a
|
|
373
|
+
* wasted raster — but a host that swaps a same-sized document and keeps its
|
|
374
|
+
* viewport gets exactly the right one, and for that host the alternative is the
|
|
375
|
+
* blank page above. One superseded epoch is the cheaper mistake of the two.
|
|
376
|
+
*/
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
if (instance === null) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const viewport = lastViewportRef.current;
|
|
382
|
+
if (viewport === null) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
instance.settle(viewport);
|
|
386
|
+
}, [instance]);
|
|
387
|
+
return { content, controller };
|
|
388
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@reekon-tools/react-native-pdf-canvas/skia` — THE SKIA-ONLY SUBPATH.
|
|
3
|
+
*
|
|
4
|
+
* Everything in this package that needs a live Skia runtime is reached from
|
|
5
|
+
* here, and nothing that is reachable from the ROOT barrel needs one at import
|
|
6
|
+
* time. That split is the point of the subpath, not tidiness:
|
|
7
|
+
*
|
|
8
|
+
* root (`.`) pure core, layouts, policy, controller, cache, planner, the
|
|
9
|
+
* React hooks, `PdfContentView`. Safe to name from a module
|
|
10
|
+
* that is evaluated in Node during SSR — no CanvasKit, no
|
|
11
|
+
* `Skia.web.js`, no decision taken at import.
|
|
12
|
+
* `./skia` `ingestRaster` / `imageFromPixels` / `rasterByteLength`, and
|
|
13
|
+
* the `skiaApi()` accessor they use.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTING THIS SUBPATH IS STILL SAFE BEFORE CANVASKIT IS LOADED. The accessor
|
|
16
|
+
* defers to first use (see `./skia-api.ts`), so a route module may import it at
|
|
17
|
+
* the top and only pay for it when a raster is actually ingested. The subpath
|
|
18
|
+
* boundary is a second line of defence, not the only one — belt and braces,
|
|
19
|
+
* because the failure it guards is silent and permanent.
|
|
20
|
+
*
|
|
21
|
+
* On web, load CanvasKit first with `loadPdfCanvasSkiaWeb()` from the
|
|
22
|
+
* `./web-init` subpath.
|
|
23
|
+
*/
|
|
24
|
+
export { ingestRaster, imageFromPixels, rasterByteLength, colorTypeFor, alphaTypeFor, BYTES_PER_PIXEL, } from '../rasterizer/ingest.js';
|
|
25
|
+
export { skiaApi, setSkiaApiResolver } from './skia-api.js';
|
|
26
|
+
export type { SkiaApi, SkiaApiResolver } from './skia-api.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@reekon-tools/react-native-pdf-canvas/skia` — THE SKIA-ONLY SUBPATH.
|
|
3
|
+
*
|
|
4
|
+
* Everything in this package that needs a live Skia runtime is reached from
|
|
5
|
+
* here, and nothing that is reachable from the ROOT barrel needs one at import
|
|
6
|
+
* time. That split is the point of the subpath, not tidiness:
|
|
7
|
+
*
|
|
8
|
+
* root (`.`) pure core, layouts, policy, controller, cache, planner, the
|
|
9
|
+
* React hooks, `PdfContentView`. Safe to name from a module
|
|
10
|
+
* that is evaluated in Node during SSR — no CanvasKit, no
|
|
11
|
+
* `Skia.web.js`, no decision taken at import.
|
|
12
|
+
* `./skia` `ingestRaster` / `imageFromPixels` / `rasterByteLength`, and
|
|
13
|
+
* the `skiaApi()` accessor they use.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTING THIS SUBPATH IS STILL SAFE BEFORE CANVASKIT IS LOADED. The accessor
|
|
16
|
+
* defers to first use (see `./skia-api.ts`), so a route module may import it at
|
|
17
|
+
* the top and only pay for it when a raster is actually ingested. The subpath
|
|
18
|
+
* boundary is a second line of defence, not the only one — belt and braces,
|
|
19
|
+
* because the failure it guards is silent and permanent.
|
|
20
|
+
*
|
|
21
|
+
* On web, load CanvasKit first with `loadPdfCanvasSkiaWeb()` from the
|
|
22
|
+
* `./web-init` subpath.
|
|
23
|
+
*/
|
|
24
|
+
export { ingestRaster, imageFromPixels, rasterByteLength, colorTypeFor, alphaTypeFor, BYTES_PER_PIXEL, } from '../rasterizer/ingest.js';
|
|
25
|
+
export { skiaApi, setSkiaApiResolver } from './skia-api.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native entry point for `./skia`.
|
|
3
|
+
*
|
|
4
|
+
* Same mechanism as `rasterizer/index.native.ts`: an `exports` condition is
|
|
5
|
+
* matched LITERALLY, with no platform-extension expansion, so the `react-native`
|
|
6
|
+
* condition needs a real `dist/skia/index.native.js` to point at.
|
|
7
|
+
*
|
|
8
|
+
* The one thing it adds over the shared barrel is installing the native
|
|
9
|
+
* resolver, so a consumer who imports ONLY this subpath still gets a working
|
|
10
|
+
* `skiaApi()`. The call is a statement using an imported binding, not a bare
|
|
11
|
+
* `import './x.js'` — this package declares `sideEffects: false`, and a bare
|
|
12
|
+
* side-effect import is exactly what that flag licenses a bundler to delete.
|
|
13
|
+
*/
|
|
14
|
+
export * from './index.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native entry point for `./skia`.
|
|
3
|
+
*
|
|
4
|
+
* Same mechanism as `rasterizer/index.native.ts`: an `exports` condition is
|
|
5
|
+
* matched LITERALLY, with no platform-extension expansion, so the `react-native`
|
|
6
|
+
* condition needs a real `dist/skia/index.native.js` to point at.
|
|
7
|
+
*
|
|
8
|
+
* The one thing it adds over the shared barrel is installing the native
|
|
9
|
+
* resolver, so a consumer who imports ONLY this subpath still gets a working
|
|
10
|
+
* `skiaApi()`. The call is a statement using an imported binding, not a bare
|
|
11
|
+
* `import './x.js'` — this package declares `sideEffects: false`, and a bare
|
|
12
|
+
* side-effect import is exactly what that flag licenses a bundler to delete.
|
|
13
|
+
*/
|
|
14
|
+
import { installNativeSkiaResolver } from './resolve.native.js';
|
|
15
|
+
export * from './index.js';
|
|
16
|
+
installNativeSkiaResolver();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web/default entry point for `./skia`.
|
|
3
|
+
*
|
|
4
|
+
* Installs the web resolver so a consumer who imports only this subpath still
|
|
5
|
+
* gets a working `skiaApi()` — provided CanvasKit has been loaded by then. The
|
|
6
|
+
* install itself decides nothing: it stores a closure, and
|
|
7
|
+
* `globalThis.CanvasKit` is not read until the first raster.
|
|
8
|
+
*
|
|
9
|
+
* See `index.native.ts` for why this is a statement and not a bare
|
|
10
|
+
* `import './resolve.web.js'`.
|
|
11
|
+
*/
|
|
12
|
+
export * from './index.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web/default entry point for `./skia`.
|
|
3
|
+
*
|
|
4
|
+
* Installs the web resolver so a consumer who imports only this subpath still
|
|
5
|
+
* gets a working `skiaApi()` — provided CanvasKit has been loaded by then. The
|
|
6
|
+
* install itself decides nothing: it stores a closure, and
|
|
7
|
+
* `globalThis.CanvasKit` is not read until the first raster.
|
|
8
|
+
*
|
|
9
|
+
* See `index.native.ts` for why this is a statement and not a bare
|
|
10
|
+
* `import './resolve.web.js'`.
|
|
11
|
+
*/
|
|
12
|
+
import { installWebSkiaResolver } from './resolve.web.js';
|
|
13
|
+
export * from './index.js';
|
|
14
|
+
installWebSkiaResolver();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The NATIVE resolver: RN Skia's own `Skia`, imported normally.
|
|
3
|
+
*
|
|
4
|
+
* NATIVE ONLY, and the bare-specifier import below is deliberate. On native
|
|
5
|
+
* `lib/module/skia/Skia.js` is `export const Skia = SkiaApi;` — a read of the
|
|
6
|
+
* JSI global that the package's `NativeSetup` side effect installs during that
|
|
7
|
+
* same import — so there is no asynchronous initialisation and therefore no
|
|
8
|
+
* ordering hazard. Importing the barrel here is what GUARANTEES `NativeSetup`
|
|
9
|
+
* has run, which is stronger than reading `globalThis.SkiaApi` and hoping
|
|
10
|
+
* somebody else imported it.
|
|
11
|
+
*
|
|
12
|
+
* The web build never resolves this file: every `exports` entry that reaches it
|
|
13
|
+
* does so through a `react-native` condition. That is the same mechanism
|
|
14
|
+
* `rasterizer/index.native.ts` uses to keep `require('react-native')` out of a
|
|
15
|
+
* browser bundle.
|
|
16
|
+
*/
|
|
17
|
+
/** Install the native resolver. Idempotent. */
|
|
18
|
+
export declare function installNativeSkiaResolver(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The NATIVE resolver: RN Skia's own `Skia`, imported normally.
|
|
3
|
+
*
|
|
4
|
+
* NATIVE ONLY, and the bare-specifier import below is deliberate. On native
|
|
5
|
+
* `lib/module/skia/Skia.js` is `export const Skia = SkiaApi;` — a read of the
|
|
6
|
+
* JSI global that the package's `NativeSetup` side effect installs during that
|
|
7
|
+
* same import — so there is no asynchronous initialisation and therefore no
|
|
8
|
+
* ordering hazard. Importing the barrel here is what GUARANTEES `NativeSetup`
|
|
9
|
+
* has run, which is stronger than reading `globalThis.SkiaApi` and hoping
|
|
10
|
+
* somebody else imported it.
|
|
11
|
+
*
|
|
12
|
+
* The web build never resolves this file: every `exports` entry that reaches it
|
|
13
|
+
* does so through a `react-native` condition. That is the same mechanism
|
|
14
|
+
* `rasterizer/index.native.ts` uses to keep `require('react-native')` out of a
|
|
15
|
+
* browser bundle.
|
|
16
|
+
*/
|
|
17
|
+
import { Skia } from '@shopify/react-native-skia';
|
|
18
|
+
import { setSkiaApiResolver } from './skia-api.js';
|
|
19
|
+
/** Install the native resolver. Idempotent. */
|
|
20
|
+
export function installNativeSkiaResolver() {
|
|
21
|
+
setSkiaApiResolver(() => Skia);
|
|
22
|
+
}
|