@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.
Files changed (161) hide show
  1. package/PdfCanvas.podspec +80 -0
  2. package/README.md +1391 -0
  3. package/android/build.gradle +175 -0
  4. package/android/consumer-rules.pro +3 -0
  5. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  6. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  7. package/android/gradle.properties +2 -0
  8. package/android/gradlew +248 -0
  9. package/android/gradlew.bat +98 -0
  10. package/android/settings.gradle +34 -0
  11. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +226 -0
  12. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +239 -0
  13. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +251 -0
  14. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +221 -0
  15. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +783 -0
  16. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +1388 -0
  17. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +98 -0
  18. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +324 -0
  19. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +775 -0
  20. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +20 -0
  21. package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +558 -0
  22. package/android/src/jsi/cpp/CMakeLists.txt +33 -0
  23. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +469 -0
  24. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +140 -0
  25. package/android/src/main/AndroidManifest.xml +7 -0
  26. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +45 -0
  27. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +564 -0
  28. package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +33 -0
  29. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +277 -0
  30. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +214 -0
  31. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +30 -0
  32. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +234 -0
  33. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +47 -0
  34. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +143 -0
  35. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +95 -0
  36. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +626 -0
  37. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasPackage.java +33 -0
  38. package/android/tools/compile-gate.sh +289 -0
  39. package/dist/cache.d.ts +161 -0
  40. package/dist/cache.js +389 -0
  41. package/dist/clock.d.ts +21 -0
  42. package/dist/clock.js +31 -0
  43. package/dist/controller.d.ts +131 -0
  44. package/dist/controller.js +1002 -0
  45. package/dist/geometry.d.ts +96 -0
  46. package/dist/geometry.js +248 -0
  47. package/dist/index.d.ts +52 -0
  48. package/dist/index.js +50 -0
  49. package/dist/index.native.d.ts +21 -0
  50. package/dist/index.native.js +23 -0
  51. package/dist/index.web.d.ts +24 -0
  52. package/dist/index.web.js +26 -0
  53. package/dist/layout.d.ts +78 -0
  54. package/dist/layout.js +152 -0
  55. package/dist/peer-check.d.ts +83 -0
  56. package/dist/peer-check.js +183 -0
  57. package/dist/planner.d.ts +35 -0
  58. package/dist/planner.js +530 -0
  59. package/dist/policy.d.ts +14 -0
  60. package/dist/policy.js +168 -0
  61. package/dist/rasterizer/android.d.ts +121 -0
  62. package/dist/rasterizer/android.js +126 -0
  63. package/dist/rasterizer/buffer.d.ts +18 -0
  64. package/dist/rasterizer/buffer.js +18 -0
  65. package/dist/rasterizer/fake.d.ts +92 -0
  66. package/dist/rasterizer/fake.js +433 -0
  67. package/dist/rasterizer/index.d.ts +38 -0
  68. package/dist/rasterizer/index.js +69 -0
  69. package/dist/rasterizer/index.native.d.ts +19 -0
  70. package/dist/rasterizer/index.native.js +80 -0
  71. package/dist/rasterizer/index.web.d.ts +63 -0
  72. package/dist/rasterizer/index.web.js +75 -0
  73. package/dist/rasterizer/ingest.d.ts +180 -0
  74. package/dist/rasterizer/ingest.js +307 -0
  75. package/dist/rasterizer/ios.d.ts +84 -0
  76. package/dist/rasterizer/ios.js +155 -0
  77. package/dist/rasterizer/native-bridge.d.ts +261 -0
  78. package/dist/rasterizer/native-bridge.js +413 -0
  79. package/dist/rasterizer/web/client.d.ts +84 -0
  80. package/dist/rasterizer/web/client.js +359 -0
  81. package/dist/rasterizer/web/engine.d.ts +69 -0
  82. package/dist/rasterizer/web/engine.js +308 -0
  83. package/dist/rasterizer/web/index.d.ts +26 -0
  84. package/dist/rasterizer/web/index.js +21 -0
  85. package/dist/rasterizer/web/pdfium.d.ts +213 -0
  86. package/dist/rasterizer/web/pdfium.js +170 -0
  87. package/dist/rasterizer/web/protocol.d.ts +138 -0
  88. package/dist/rasterizer/web/protocol.js +37 -0
  89. package/dist/rasterizer/web/session.d.ts +106 -0
  90. package/dist/rasterizer/web/session.js +413 -0
  91. package/dist/rasterizer/web/worker.d.ts +44 -0
  92. package/dist/rasterizer/web/worker.js +76 -0
  93. package/dist/react/PdfContentView.d.ts +98 -0
  94. package/dist/react/PdfContentView.js +189 -0
  95. package/dist/react/index.d.ts +6 -0
  96. package/dist/react/index.js +3 -0
  97. package/dist/react/usePdfDocument.d.ts +134 -0
  98. package/dist/react/usePdfDocument.js +275 -0
  99. package/dist/react/usePdfLayer.d.ts +94 -0
  100. package/dist/react/usePdfLayer.js +388 -0
  101. package/dist/skia/index.d.ts +26 -0
  102. package/dist/skia/index.js +25 -0
  103. package/dist/skia/index.native.d.ts +14 -0
  104. package/dist/skia/index.native.js +16 -0
  105. package/dist/skia/index.web.d.ts +12 -0
  106. package/dist/skia/index.web.js +14 -0
  107. package/dist/skia/resolve.native.d.ts +18 -0
  108. package/dist/skia/resolve.native.js +22 -0
  109. package/dist/skia/resolve.web.d.ts +41 -0
  110. package/dist/skia/resolve.web.js +62 -0
  111. package/dist/skia/skia-api.d.ts +94 -0
  112. package/dist/skia/skia-api.js +113 -0
  113. package/dist/testing/index.d.ts +27 -0
  114. package/dist/testing/index.js +28 -0
  115. package/dist/testing/scenes.d.ts +100 -0
  116. package/dist/testing/scenes.js +267 -0
  117. package/dist/types.d.ts +611 -0
  118. package/dist/types.js +21 -0
  119. package/dist/web-init.d.ts +68 -0
  120. package/dist/web-init.js +64 -0
  121. package/ios/Package.swift +58 -0
  122. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +743 -0
  123. package/ios/Sources/PdfCanvasBridge/include/PdfCanvasModule.h +36 -0
  124. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +38 -0
  125. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +389 -0
  126. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +57 -0
  127. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +44 -0
  128. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +82 -0
  129. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +37 -0
  130. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +158 -0
  131. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +80 -0
  132. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +65 -0
  133. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +548 -0
  134. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +48 -0
  135. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +26 -0
  136. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +122 -0
  137. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +80 -0
  138. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +79 -0
  139. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +129 -0
  140. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +100 -0
  141. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +65 -0
  142. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +111 -0
  143. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +85 -0
  144. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +163 -0
  145. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +297 -0
  146. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +140 -0
  147. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +358 -0
  148. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +262 -0
  149. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +173 -0
  150. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +114 -0
  151. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +158 -0
  152. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +544 -0
  153. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +287 -0
  154. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +396 -0
  155. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +639 -0
  156. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +179 -0
  157. package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +283 -0
  158. package/package.json +144 -0
  159. package/react-native.config.js +40 -0
  160. package/scripts/check-skia-peer.mjs +86 -0
  161. package/scripts/skia-peer-check.mjs +363 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The WEB resolver: build the Skia api from `globalThis.CanvasKit`, lazily.
3
+ *
4
+ * WEB ONLY. Nothing native ever imports this file — the `react-native`
5
+ * condition of every `exports` entry points at an `index.native.js` that reaches
6
+ * `./resolve.native.js` instead — so the whole JsiSk* web implementation stays
7
+ * out of the RN bundle.
8
+ *
9
+ * ------------------------------------------------------------------------
10
+ * WHY `JsiSkApi(CanvasKit)` AND NOT AN IMPORT OF `Skia`
11
+ * ------------------------------------------------------------------------
12
+ *
13
+ * Because it is the same expression, evaluated at the right time.
14
+ * `lib/module/skia/Skia.web.js` IS `JsiSkApi(global.CanvasKit)`; the only thing
15
+ * wrong with it is WHEN it runs. Importing `JsiSkApi` from the deep
16
+ * `skia/web` path gets the factory without evaluating the module that calls it
17
+ * too early — VERIFIED by bundling, not by reading imports: a Vite build of
18
+ * `lib/module/skia/web/index.js` contains no `JsiSkApi(global.CanvasKit)`, while
19
+ * a build of the bare `@shopify/react-native-skia` specifier does.
20
+ *
21
+ * The api this produces is a SECOND JsiSkApi object, distinct from the one
22
+ * `Skia.web.js` builds for RN Skia's own renderer. That is harmless and was
23
+ * checked rather than assumed: both come from the same `skia/web` module, so the
24
+ * classes (`JsiSkImage`, `JsiSkData`, …) are identical objects and an image made
25
+ * here is exactly the image the reconciler expects. Only the factory wrapper is
26
+ * duplicated, and it holds no cross-call state that matters here.
27
+ *
28
+ * The deep path is legal because `@shopify/react-native-skia` publishes `lib/**`
29
+ * and declares no `exports` map. Same standing as boldr-utils' import of
30
+ * `@shopify/react-native-skia/lib/module/web/index.js` for `WithSkiaWeb`. If
31
+ * they ever add an `exports` map, both break together and the fix is whatever
32
+ * public entry replaces them.
33
+ */
34
+ /**
35
+ * Install the web resolver.
36
+ *
37
+ * Idempotent and free: it stores a closure. Nothing reads `globalThis.CanvasKit`
38
+ * until the first raster is ingested, which is the entire point — a route module
39
+ * that merely names this package must not decide anything about CanvasKit.
40
+ */
41
+ export declare function installWebSkiaResolver(): void;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The WEB resolver: build the Skia api from `globalThis.CanvasKit`, lazily.
3
+ *
4
+ * WEB ONLY. Nothing native ever imports this file — the `react-native`
5
+ * condition of every `exports` entry points at an `index.native.js` that reaches
6
+ * `./resolve.native.js` instead — so the whole JsiSk* web implementation stays
7
+ * out of the RN bundle.
8
+ *
9
+ * ------------------------------------------------------------------------
10
+ * WHY `JsiSkApi(CanvasKit)` AND NOT AN IMPORT OF `Skia`
11
+ * ------------------------------------------------------------------------
12
+ *
13
+ * Because it is the same expression, evaluated at the right time.
14
+ * `lib/module/skia/Skia.web.js` IS `JsiSkApi(global.CanvasKit)`; the only thing
15
+ * wrong with it is WHEN it runs. Importing `JsiSkApi` from the deep
16
+ * `skia/web` path gets the factory without evaluating the module that calls it
17
+ * too early — VERIFIED by bundling, not by reading imports: a Vite build of
18
+ * `lib/module/skia/web/index.js` contains no `JsiSkApi(global.CanvasKit)`, while
19
+ * a build of the bare `@shopify/react-native-skia` specifier does.
20
+ *
21
+ * The api this produces is a SECOND JsiSkApi object, distinct from the one
22
+ * `Skia.web.js` builds for RN Skia's own renderer. That is harmless and was
23
+ * checked rather than assumed: both come from the same `skia/web` module, so the
24
+ * classes (`JsiSkImage`, `JsiSkData`, …) are identical objects and an image made
25
+ * here is exactly the image the reconciler expects. Only the factory wrapper is
26
+ * duplicated, and it holds no cross-call state that matters here.
27
+ *
28
+ * The deep path is legal because `@shopify/react-native-skia` publishes `lib/**`
29
+ * and declares no `exports` map. Same standing as boldr-utils' import of
30
+ * `@shopify/react-native-skia/lib/module/web/index.js` for `WithSkiaWeb`. If
31
+ * they ever add an `exports` map, both break together and the fix is whatever
32
+ * public entry replaces them.
33
+ */
34
+ import { JsiSkApi } from '@shopify/react-native-skia/lib/module/skia/web/index.js';
35
+ import { setSkiaApiResolver } from './skia-api.js';
36
+ /**
37
+ * Install the web resolver.
38
+ *
39
+ * Idempotent and free: it stores a closure. Nothing reads `globalThis.CanvasKit`
40
+ * until the first raster is ingested, which is the entire point — a route module
41
+ * that merely names this package must not decide anything about CanvasKit.
42
+ */
43
+ export function installWebSkiaResolver() {
44
+ setSkiaApiResolver(() => {
45
+ const canvasKit = globalThis.CanvasKit;
46
+ // Null, not a throw: `skiaApi()` owns the one error message, and "CanvasKit
47
+ // is not loaded yet" is exactly the state that message describes.
48
+ if (canvasKit === undefined || canvasKit === null) {
49
+ return null;
50
+ }
51
+ // THE CAST IS AN ARTIFACT OF DUPLICATE DECLARATIONS, NOT A LOOSENING.
52
+ //
53
+ // RN Skia ships its types twice — `lib/typescript/src/**` (what the bare
54
+ // specifier resolves to, and what `SkiaApi` is taken from) and co-located
55
+ // `lib/module/**` copies (what this deep import resolves to). The two are
56
+ // generated from the same source, but TypeScript treats their `NodeType`
57
+ // enums as nominally distinct, so the structurally-identical results do not
58
+ // assign. `unknown` in the middle rather than a direct assertion because TS
59
+ // rejects even the direct one for the same reason.
60
+ return JsiSkApi(canvasKit);
61
+ });
62
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The Skia runtime, resolved AT CALL TIME.
3
+ *
4
+ * ------------------------------------------------------------------------
5
+ * WHY THIS FILE EXISTS: `Skia` ON WEB IS BUILT ONCE, AT IMPORT TIME.
6
+ * ------------------------------------------------------------------------
7
+ *
8
+ * `@shopify/react-native-skia`'s web build is, in its entirety:
9
+ *
10
+ * // lib/module/skia/Skia.web.js
11
+ * import {JsiSkApi} from "./web";
12
+ * export const Skia = JsiSkApi(global.CanvasKit);
13
+ *
14
+ * That is a MODULE-SCOPE call. If the module is evaluated before `LoadSkiaWeb`
15
+ * has resolved, `global.CanvasKit` is `undefined`, every factory in the returned
16
+ * object closes over `undefined`, and the damage is PERMANENT and unrecoverable
17
+ * in-bundle: re-importing the specifier returns the identical broken object,
18
+ * because ES module evaluation happens once.
19
+ *
20
+ * And it is not hard to trigger. `Skia.web.js` is reached from the package root
21
+ * barrel (`lib/module/index.js` -> `export * from "./skia"` -> `export {Skia}
22
+ * from "./Skia"`), `@shopify/react-native-skia` declares no `sideEffects: false`
23
+ * and no `exports` map, so **any** value import from the bare specifier — a
24
+ * `<Group>`, a `ColorType` — evaluates it. rock-desktop is `{ssr: true}` with no
25
+ * prerender, so every route module is evaluated in Node on every request; one
26
+ * route that names a barrel which names Skia's barrel is enough.
27
+ *
28
+ * So this module never names `Skia` at module scope. It holds a RESOLVER, and
29
+ * `skiaApi()` calls it on the first raster and caches the answer.
30
+ *
31
+ * ------------------------------------------------------------------------
32
+ * WHO INSTALLS THE RESOLVER
33
+ * ------------------------------------------------------------------------
34
+ *
35
+ * native `./resolve.native.js`, from `src/index.native.ts` and
36
+ * `src/skia/index.native.ts`. It DOES import the bare specifier —
37
+ * correctly, because on native `Skia` is `global.SkiaApi`, installed
38
+ * by RN Skia's own `NativeSetup` side effect at import. There is no
39
+ * ordering hazard on native at all; this whole file is a web problem.
40
+ * web `./resolve.web.js`, from `src/index.web.ts` and
41
+ * `src/skia/index.web.ts`. It builds the api from
42
+ * `globalThis.CanvasKit` on first use, so nothing is decided at
43
+ * import time.
44
+ * host `loadPdfCanvasSkiaWeb()` from the `./web-init` subpath installs it
45
+ * too, after awaiting CanvasKit. That is the explicit, awaitable
46
+ * ordering guarantee a web host should prefer.
47
+ *
48
+ * A host with its own arrangement can call `setSkiaApiResolver` directly.
49
+ */
50
+ import type { Skia as SkiaConst } from '@shopify/react-native-skia';
51
+ /**
52
+ * The shape of RN Skia's `Skia`, taken from the BARE specifier.
53
+ *
54
+ * `import type` is erased entirely — it reaches no module at runtime and is
55
+ * therefore the one form of `@shopify/react-native-skia` import that is always
56
+ * safe here. `typeof` rather than a direct type import because the package's
57
+ * root exports `Skia` as a VALUE (`export {Skia} from "./Skia"`), which shadows
58
+ * the same-named interface its `./types` star-export carries.
59
+ *
60
+ * It must come from the bare specifier and not from a deep `lib/module/...`
61
+ * path: the package ships its declarations twice (`lib/typescript/src/**`, which
62
+ * the bare specifier resolves to, and co-located `lib/module/**` copies), and
63
+ * TypeScript treats the two copies' `NodeType` enums as NOMINALLY distinct. Mix
64
+ * them and every assignment needs a cast.
65
+ */
66
+ export type SkiaApi = typeof SkiaConst;
67
+ /**
68
+ * Produce the Skia api, or null if it is not available YET.
69
+ *
70
+ * Returning null rather than throwing matters: an installed resolver that cannot
71
+ * answer yet falls through to the remaining strategies below instead of
72
+ * short-circuiting them, and the single error message at the end of the chain is
73
+ * the one the caller sees.
74
+ */
75
+ export type SkiaApiResolver = () => SkiaApi | null;
76
+ /**
77
+ * Install the resolver. Returns the previous one so a test can restore it.
78
+ *
79
+ * Installing CLEARS the cache. A resolver swap is a statement that the previous
80
+ * answer is no longer the right one — the alternative (keeping a stale api that
81
+ * a `LoadSkiaWeb` retry has just superseded) is the failure this whole module
82
+ * exists to prevent.
83
+ */
84
+ export declare function setSkiaApiResolver(next: SkiaApiResolver | null): SkiaApiResolver | null;
85
+ /**
86
+ * The Skia api, resolved on first use and cached.
87
+ *
88
+ * Call this INSIDE the function that needs it, never at module scope — the
89
+ * moment the result is stored in a module-level `const` this file has
90
+ * reintroduced the bug it exists to remove.
91
+ */
92
+ export declare function skiaApi(): SkiaApi;
93
+ /** Drop the cached api AND the installed resolver. Tests only. */
94
+ export declare function resetSkiaApiForTests(): void;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The Skia runtime, resolved AT CALL TIME.
3
+ *
4
+ * ------------------------------------------------------------------------
5
+ * WHY THIS FILE EXISTS: `Skia` ON WEB IS BUILT ONCE, AT IMPORT TIME.
6
+ * ------------------------------------------------------------------------
7
+ *
8
+ * `@shopify/react-native-skia`'s web build is, in its entirety:
9
+ *
10
+ * // lib/module/skia/Skia.web.js
11
+ * import {JsiSkApi} from "./web";
12
+ * export const Skia = JsiSkApi(global.CanvasKit);
13
+ *
14
+ * That is a MODULE-SCOPE call. If the module is evaluated before `LoadSkiaWeb`
15
+ * has resolved, `global.CanvasKit` is `undefined`, every factory in the returned
16
+ * object closes over `undefined`, and the damage is PERMANENT and unrecoverable
17
+ * in-bundle: re-importing the specifier returns the identical broken object,
18
+ * because ES module evaluation happens once.
19
+ *
20
+ * And it is not hard to trigger. `Skia.web.js` is reached from the package root
21
+ * barrel (`lib/module/index.js` -> `export * from "./skia"` -> `export {Skia}
22
+ * from "./Skia"`), `@shopify/react-native-skia` declares no `sideEffects: false`
23
+ * and no `exports` map, so **any** value import from the bare specifier — a
24
+ * `<Group>`, a `ColorType` — evaluates it. rock-desktop is `{ssr: true}` with no
25
+ * prerender, so every route module is evaluated in Node on every request; one
26
+ * route that names a barrel which names Skia's barrel is enough.
27
+ *
28
+ * So this module never names `Skia` at module scope. It holds a RESOLVER, and
29
+ * `skiaApi()` calls it on the first raster and caches the answer.
30
+ *
31
+ * ------------------------------------------------------------------------
32
+ * WHO INSTALLS THE RESOLVER
33
+ * ------------------------------------------------------------------------
34
+ *
35
+ * native `./resolve.native.js`, from `src/index.native.ts` and
36
+ * `src/skia/index.native.ts`. It DOES import the bare specifier —
37
+ * correctly, because on native `Skia` is `global.SkiaApi`, installed
38
+ * by RN Skia's own `NativeSetup` side effect at import. There is no
39
+ * ordering hazard on native at all; this whole file is a web problem.
40
+ * web `./resolve.web.js`, from `src/index.web.ts` and
41
+ * `src/skia/index.web.ts`. It builds the api from
42
+ * `globalThis.CanvasKit` on first use, so nothing is decided at
43
+ * import time.
44
+ * host `loadPdfCanvasSkiaWeb()` from the `./web-init` subpath installs it
45
+ * too, after awaiting CanvasKit. That is the explicit, awaitable
46
+ * ordering guarantee a web host should prefer.
47
+ *
48
+ * A host with its own arrangement can call `setSkiaApiResolver` directly.
49
+ */
50
+ import { PdfError } from '../types.js';
51
+ let resolver = null;
52
+ let cached = null;
53
+ /**
54
+ * Install the resolver. Returns the previous one so a test can restore it.
55
+ *
56
+ * Installing CLEARS the cache. A resolver swap is a statement that the previous
57
+ * answer is no longer the right one — the alternative (keeping a stale api that
58
+ * a `LoadSkiaWeb` retry has just superseded) is the failure this whole module
59
+ * exists to prevent.
60
+ */
61
+ export function setSkiaApiResolver(next) {
62
+ const previous = resolver;
63
+ resolver = next;
64
+ cached = null;
65
+ return previous;
66
+ }
67
+ /**
68
+ * The native JSI global, read the way RN Skia itself reads it.
69
+ *
70
+ * `lib/module/skia/Skia.js` — the NATIVE half of the same platform pair — is
71
+ * literally `export const Skia = SkiaApi;`, i.e. a read of the global that
72
+ * `NativeSetup` installs. Consulting it here is not a guess about internals; it
73
+ * is the same one-liner, deferred. It exists so that a native consumer who
74
+ * reached this module through a shared submodule (rather than through
75
+ * `index.native.ts`) still gets a working api instead of an error about web
76
+ * initialisation.
77
+ *
78
+ * On web and under vitest the global is absent, so this contributes nothing.
79
+ */
80
+ function nativeGlobalSkia() {
81
+ const global = globalThis;
82
+ return global.SkiaApi ?? null;
83
+ }
84
+ const UNAVAILABLE = 'The Skia runtime is not available yet. On web, CanvasKit is loaded ' +
85
+ 'asynchronously and `Skia` is built from it ONCE, so it must be loaded ' +
86
+ 'before the first raster: await `loadPdfCanvasSkiaWeb()` from ' +
87
+ "'@reekon-tools/react-native-pdf-canvas/web-init' (or RN Skia's own " +
88
+ '`LoadSkiaWeb`) during startup. On native this means ' +
89
+ '@shopify/react-native-skia never installed its JSI bindings — rebuild the ' +
90
+ 'app rather than reloading the bundle.';
91
+ /**
92
+ * The Skia api, resolved on first use and cached.
93
+ *
94
+ * Call this INSIDE the function that needs it, never at module scope — the
95
+ * moment the result is stored in a module-level `const` this file has
96
+ * reintroduced the bug it exists to remove.
97
+ */
98
+ export function skiaApi() {
99
+ if (cached !== null) {
100
+ return cached;
101
+ }
102
+ const resolved = resolver?.() ?? nativeGlobalSkia();
103
+ if (resolved === null || resolved === undefined) {
104
+ throw new PdfError('unsupported', UNAVAILABLE);
105
+ }
106
+ cached = resolved;
107
+ return resolved;
108
+ }
109
+ /** Drop the cached api AND the installed resolver. Tests only. */
110
+ export function resetSkiaApiForTests() {
111
+ resolver = null;
112
+ cached = null;
113
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The `@reekon-tools/react-native-pdf-canvas/testing` subpath.
3
+ *
4
+ * SKIA-FREE BY CONTRACT. Nothing reachable from this file may import
5
+ * `@shopify/react-native-skia`, react-native or reanimated — it is imported by
6
+ * vitest suites running in a bare node environment, where any of those would
7
+ * fail at module load. In particular it reaches `../rasterizer/fake.js`
8
+ * directly and never `../rasterizer/index.js`, which pulls in the Skia ingest
9
+ * path.
10
+ */
11
+ export { ARCH_D, ARCH_E, LETTER, createFakeRasterizer, decodeFakeDocRect, fakePageColor, fakePixelSize, } from '../rasterizer/fake.js';
12
+ export type { FakeFailureModes, FakeRasterizerOptions, } from '../rasterizer/fake.js';
13
+ /**
14
+ * The reference scenes BUILD STEP 1 produced: the composition contract as data,
15
+ * for a backend suite to replay against its own rasterizer. Pure — it reaches
16
+ * `planRasters` and nothing else.
17
+ */
18
+ export { buildReferenceScenes, docRectToCanvas, docToCanvas, referenceScene, sceneRasterMeta, sceneRasterRequest, } from './scenes.js';
19
+ export type { CanvasPoint, ProbeExpectation, ReferenceScene, SceneProbe, SceneRaster, SceneTransform, } from './scenes.js';
20
+ import type { DocRect, DocSize, PageGeometry, PageRotation, ViewportSnapshot } from '../types.js';
21
+ export declare function makePageGeometry(index: number, size: DocSize, rotation?: PageRotation): PageGeometry;
22
+ /**
23
+ * `scale` is DEVICE PIXELS PER PDF POINT — zoom multiplied by pixel ratio,
24
+ * already combined. Passing a bare zoom here is the single easiest way to write
25
+ * a test that agrees with itself and disagrees with the device.
26
+ */
27
+ export declare function makeViewport(visibleDocRect: DocRect, scale: number): ViewportSnapshot;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The `@reekon-tools/react-native-pdf-canvas/testing` subpath.
3
+ *
4
+ * SKIA-FREE BY CONTRACT. Nothing reachable from this file may import
5
+ * `@shopify/react-native-skia`, react-native or reanimated — it is imported by
6
+ * vitest suites running in a bare node environment, where any of those would
7
+ * fail at module load. In particular it reaches `../rasterizer/fake.js`
8
+ * directly and never `../rasterizer/index.js`, which pulls in the Skia ingest
9
+ * path.
10
+ */
11
+ export { ARCH_D, ARCH_E, LETTER, createFakeRasterizer, decodeFakeDocRect, fakePageColor, fakePixelSize, } from '../rasterizer/fake.js';
12
+ /**
13
+ * The reference scenes BUILD STEP 1 produced: the composition contract as data,
14
+ * for a backend suite to replay against its own rasterizer. Pure — it reaches
15
+ * `planRasters` and nothing else.
16
+ */
17
+ export { buildReferenceScenes, docRectToCanvas, docToCanvas, referenceScene, sceneRasterMeta, sceneRasterRequest, } from './scenes.js';
18
+ export function makePageGeometry(index, size, rotation = 0) {
19
+ return { index, width: size.width, height: size.height, rotation };
20
+ }
21
+ /**
22
+ * `scale` is DEVICE PIXELS PER PDF POINT — zoom multiplied by pixel ratio,
23
+ * already combined. Passing a bare zoom here is the single easiest way to write
24
+ * a test that agrees with itself and disagrees with the device.
25
+ */
26
+ export function makeViewport(visibleDocRect, scale) {
27
+ return { visibleDocRect, scale };
28
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Reference scenes: the composition contract, stated as DATA.
3
+ *
4
+ * BUILD STEP 1 pixel-snapshots the composition of a set of rasters offscreen and
5
+ * calls the result the reference every backend is later checked against. The
6
+ * IMAGES cannot be that reference on their own — they are specific to whichever
7
+ * document produced the bytes, so a PDFium suite and a PDFKit suite would never
8
+ * agree byte for byte. What generalises is the SCENE: which rasters the planner
9
+ * asked for, where the host transform puts them, and which canvas coordinates
10
+ * must be covered by what.
11
+ *
12
+ * A backend suite therefore uses this module as follows:
13
+ *
14
+ * 1. `buildReferenceScenes()` and pick a scene.
15
+ * 2. For each `scene.rasters`, hand `.request` to your own `RasterizerHandle`
16
+ * and `.meta` to your `ingest`.
17
+ * 3. Compose them into a `PdfContent`, draw it through `PdfContentView` inside
18
+ * `<Group transform={referenceTransform(scene.transform)}>` on a
19
+ * `scene.canvas`-sized surface.
20
+ * 4. Assert `scene.probes`: every 'raster' probe must be opaque, every
21
+ * 'nothing' probe must be untouched.
22
+ *
23
+ * SKIA-FREE, like the rest of `src/testing/`. It reaches `planRasters` (pure) and
24
+ * nothing else — the plan is DERIVED here rather than hand-written, so a change
25
+ * to the splitting rule shows up as a changed scene rather than as a scene that
26
+ * quietly stops describing what the planner does.
27
+ */
28
+ import type { RasterIngestMeta } from '../controller.js';
29
+ import type { DocRect, PageGeometry, PixelSize, RasterPlan, RasterPlanItem, RasterPolicy, RasterRequest, ViewportSnapshot } from '../types.js';
30
+ /**
31
+ * The host's doc -> canvas mapping: `canvas = doc * scale + translate`.
32
+ *
33
+ * Deliberately NOT a Skia matrix. `src/testing/` may not import Skia, and the
34
+ * package is transform-agnostic anyway — this is the arithmetic a consumer's
35
+ * own world transform performs, whatever type it stores it in.
36
+ */
37
+ export interface SceneTransform {
38
+ scale: number;
39
+ translateX: number;
40
+ translateY: number;
41
+ }
42
+ export interface CanvasPoint {
43
+ x: number;
44
+ y: number;
45
+ }
46
+ export declare function docToCanvas(transform: SceneTransform, docX: number, docY: number): CanvasPoint;
47
+ export declare function docRectToCanvas(transform: SceneTransform, rect: DocRect): {
48
+ x: number;
49
+ y: number;
50
+ width: number;
51
+ height: number;
52
+ };
53
+ /**
54
+ * What must be true at a doc-space point once the scene is composited.
55
+ *
56
+ * Colour-free on purpose: a probe that named an RGB value would only ever be
57
+ * checkable against the fake. 'raster' means SOMETHING opaque was painted;
58
+ * 'nothing' means the surface was never touched. Those two hold for every
59
+ * backend and catch the placement bugs that matter.
60
+ */
61
+ export type ProbeExpectation = 'raster' | 'nothing';
62
+ export interface SceneProbe {
63
+ docX: number;
64
+ docY: number;
65
+ expect: ProbeExpectation;
66
+ /** Which bug flips this probe. Not decoration — see the suite. */
67
+ why: string;
68
+ }
69
+ /** One raster the scene needs, in both the shapes a backend harness wants. */
70
+ export interface SceneRaster {
71
+ item: RasterPlanItem;
72
+ request: RasterRequest;
73
+ meta: RasterIngestMeta;
74
+ }
75
+ export interface ReferenceScene {
76
+ id: string;
77
+ description: string;
78
+ /** Offscreen surface size, in canvas pixels. */
79
+ canvas: PixelSize;
80
+ transform: SceneTransform;
81
+ pages: PageGeometry[];
82
+ /** Parallel to `pages`. */
83
+ pageRects: DocRect[];
84
+ viewport: ViewportSnapshot;
85
+ policy: RasterPolicy;
86
+ plan: RasterPlan;
87
+ rasters: SceneRaster[];
88
+ probes: SceneProbe[];
89
+ }
90
+ /**
91
+ * Plan item -> backend request.
92
+ *
93
+ * `annotations: false` and `background: 'white'` match what the tile path asks
94
+ * for in production (the app draws its own annotations above, and an unfilled
95
+ * buffer composites as black — see `ingest.ts`).
96
+ */
97
+ export declare function sceneRasterRequest(item: RasterPlanItem, over?: Partial<RasterRequest>): RasterRequest;
98
+ export declare function sceneRasterMeta(item: RasterPlanItem, epoch?: number): RasterIngestMeta;
99
+ export declare function buildReferenceScenes(): ReferenceScene[];
100
+ export declare function referenceScene(id: string): ReferenceScene;