@reekon-tools/react-native-pdf-canvas 0.1.1 → 0.2.1

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 (121) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/PdfCanvas.podspec +77 -40
  4. package/README.md +218 -1316
  5. package/android/build.gradle +67 -54
  6. package/android/consumer-rules.pro +7 -0
  7. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasNativeTest.java +254 -0
  8. package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +60 -468
  9. package/android/src/main/cpp/CMakeLists.txt +54 -0
  10. package/android/src/main/cpp/pdfcanvas-jni.cpp +262 -0
  11. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasNative.java +167 -0
  12. package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +14 -4
  13. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +10 -4
  14. package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +160 -376
  15. package/android/tools/compile-gate.sh +81 -101
  16. package/dist/controller.d.ts +14 -0
  17. package/dist/controller.js +4 -3
  18. package/dist/rasterizer/index.native.d.ts +8 -12
  19. package/dist/rasterizer/index.native.js +15 -21
  20. package/dist/rasterizer/native-bridge.d.ts +51 -75
  21. package/dist/rasterizer/native-bridge.js +49 -33
  22. package/dist/rasterizer/native.d.ts +45 -0
  23. package/dist/rasterizer/native.js +123 -0
  24. package/dist/react/usePdfDocument.d.ts +5 -5
  25. package/dist/react/usePdfLayer.d.ts +10 -1
  26. package/dist/react/usePdfLayer.js +10 -2
  27. package/dist/testing/scenes.d.ts +3 -3
  28. package/dist/testing/scenes.js +4 -4
  29. package/dist/types.d.ts +36 -42
  30. package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +234 -536
  31. package/native/.clangd +5 -0
  32. package/native/CMakeLists.txt +89 -0
  33. package/native/core/include/pdfcanvas/document.h +90 -0
  34. package/native/core/include/pdfcanvas/error.h +47 -0
  35. package/native/core/include/pdfcanvas/jsi.h +18 -0
  36. package/native/core/include/pdfcanvas/jsi_config.h +30 -0
  37. package/native/core/include/pdfcanvas/library.h +28 -0
  38. package/native/core/include/pdfcanvas/pixels.h +92 -0
  39. package/native/core/include/pdfcanvas/service.h +87 -0
  40. package/native/core/include/pdfcanvas/slots.h +113 -0
  41. package/native/core/include/pdfcanvas/types.h +110 -0
  42. package/native/core/pdfcanvas-core.cmake +22 -0
  43. package/native/core/src/document.cpp +516 -0
  44. package/native/core/src/error.cpp +29 -0
  45. package/native/core/src/jsi.cpp +66 -0
  46. package/native/core/src/library.cpp +52 -0
  47. package/native/core/src/pixels.cpp +45 -0
  48. package/native/core/src/service.cpp +156 -0
  49. package/native/core/src/slots.cpp +139 -0
  50. package/native/tests/fixtures.cpp +451 -0
  51. package/native/tests/fixtures.h +96 -0
  52. package/native/tests/harness.h +122 -0
  53. package/native/tests/main.cpp +64 -0
  54. package/native/tests/pixels_util.h +126 -0
  55. package/native/tests/test_document.cpp +485 -0
  56. package/native/tests/test_service.cpp +130 -0
  57. package/native/tests/test_slots.cpp +250 -0
  58. package/package.json +27 -7
  59. package/react-native.config.js +5 -2
  60. package/scripts/fetch-pdfium.mjs +559 -0
  61. package/scripts/pdfium-manifest.json +48 -0
  62. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +0 -226
  63. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +0 -239
  64. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +0 -251
  65. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +0 -221
  66. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +0 -783
  67. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +0 -1388
  68. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +0 -98
  69. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +0 -324
  70. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +0 -775
  71. package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +0 -20
  72. package/android/src/jsi/cpp/CMakeLists.txt +0 -33
  73. package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +0 -469
  74. package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +0 -140
  75. package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +0 -45
  76. package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +0 -564
  77. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +0 -277
  78. package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +0 -214
  79. package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +0 -234
  80. package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +0 -47
  81. package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +0 -143
  82. package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +0 -95
  83. package/dist/rasterizer/android.d.ts +0 -121
  84. package/dist/rasterizer/android.js +0 -126
  85. package/dist/rasterizer/ios.d.ts +0 -84
  86. package/dist/rasterizer/ios.js +0 -155
  87. package/ios/Package.swift +0 -58
  88. package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +0 -38
  89. package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +0 -389
  90. package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +0 -57
  91. package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +0 -44
  92. package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +0 -82
  93. package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +0 -37
  94. package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +0 -158
  95. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +0 -80
  96. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +0 -65
  97. package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +0 -548
  98. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +0 -48
  99. package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +0 -26
  100. package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +0 -122
  101. package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +0 -80
  102. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +0 -79
  103. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +0 -129
  104. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +0 -100
  105. package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +0 -65
  106. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +0 -111
  107. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +0 -85
  108. package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +0 -163
  109. package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +0 -297
  110. package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +0 -140
  111. package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +0 -358
  112. package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +0 -262
  113. package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +0 -173
  114. package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +0 -114
  115. package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +0 -158
  116. package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +0 -544
  117. package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +0 -287
  118. package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +0 -396
  119. package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +0 -639
  120. package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +0 -179
  121. package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +0 -283
@@ -1,33 +1,30 @@
1
1
  #!/bin/bash
2
2
  #
3
- # The Android compile gate: type- and syntax-check every Android source in this
4
- # package WITHOUT gradle, an emulator, or a device. Runs in seconds.
3
+ # The Android compile gate: build and link every Android source in this package
4
+ # WITHOUT gradle, an emulator, or a device. Runs in seconds.
5
5
  #
6
- # WHY THIS EXISTS. A cold gradle build of a consuming app is ~45 minutes here and
6
+ # WHY THIS EXISTS. A cold gradle build of a consuming app is ~45 minutes and
7
7
  # does not reliably produce a runnable app, so the practical alternative to this
8
- # script is "change JNI and find out on an EAS build". Two of the four things it
9
- # checks cannot be caught any other way short of a device:
8
+ # script is "change JNI and find out on an EAS build". What it checks:
10
9
  #
11
- # * src/reactnative/PdfCanvasModule.java is not compiled by the standalone
12
- # build at all. Its own header used to say "nothing in this file has ever
13
- # been compiled or run"; this is what changed that.
14
- # * A JNI signature that drifts from its Java `native` declaration compiles
15
- # clean on BOTH sides and fails only as an UnsatisfiedLinkError at runtime.
16
- # Section 5 makes javac derive the expected prototypes and has clang compare
17
- # them against the definitions, in both directions.
10
+ # 1. The C++ core + JNI compile and LINK into libpdfcanvas.so for arm64 and
11
+ # armv7, in both configurations (standalone / WITH_JSI), against the pinned
12
+ # libpdfium.so and — for WITH_JSI — the react-android AAR's own libjsi.so,
13
+ # with `-Wl,--no-undefined`. A platform function missing at the minSdk, a
14
+ # PDFium symbol the pinned build does not export, or a jsi symbol the app's
15
+ # RN version lacks all fail HERE.
16
+ # 2. The built .so exports every JNI symbol Java will look up.
17
+ # 3. javac: the React-free core (`src/main`), the binding (`src/reactnative`,
18
+ # against the RN classes.jar) and the instrumented suite.
19
+ # 4. JNI SIGNATURES: `javac -h` derives the expected C prototypes from the
20
+ # `native` declarations; force-including that header makes clang compare
21
+ # every definition against its declaration, and the reverse check finds a
22
+ # method declared native with no definition. Both are the class of bug that
23
+ # compiles clean on both sides and fails only as an UnsatisfiedLinkError.
18
24
  #
19
- # HOW FAR THE C++ HALF GOES. Not a syntax check — it compiles and LINKS the real
20
- # shared library, in both configurations, with `-Wl,--no-undefined`, against the
21
- # NDK's own libjnigraphics and the react-android AAR's own libjsi.so. So a missing
22
- # `target_link_libraries` entry, a platform function that does not exist at the
23
- # minSdk, or a jsi symbol the app's React Native version does not export all fail
24
- # HERE. It then checks that the built .so exports every JNI symbol Java expects.
25
- # What is skipped versus a real build is gradle and cmake orchestration only.
26
- #
27
- # WHAT IT IS NOT. It runs no Java, no C++ and no test. Correctness of the
28
- # rasterizer and of the raster transport is proven by
29
- # `cd android && ./gradlew connectedDebugAndroidTest` on a device, and nothing
30
- # here substitutes for that.
25
+ # WHAT IT IS NOT. It runs no Java, no C++ and no test. Rendering correctness is
26
+ # proven by `native/tests` on the host; the JNI seam on a device by
27
+ # `cd android && ./gradlew connectedDebugAndroidTest`.
31
28
  #
32
29
  # Usage: bash android/tools/compile-gate.sh
33
30
  #
@@ -37,6 +34,7 @@
37
34
  set -u
38
35
  here=$(cd "$(dirname "$0")/.." && pwd) # <repo>/android
39
36
  PKG=$here
37
+ ROOT=$(cd "$here/.." && pwd)
40
38
  fail=0
41
39
  skipped=0
42
40
 
@@ -55,7 +53,6 @@ SDK=${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/AppData/Local/Android/Sdk}}
55
53
  [ -d "$SDK" ] || SDK=$HOME/Library/Android/sdk
56
54
  [ -d "$SDK" ] || SDK=$HOME/Android/Sdk
57
55
 
58
- # Newest installed NDK, unless one is pinned.
59
56
  NDK=${ANDROID_NDK_HOME:-}
60
57
  if [ -z "$NDK" ]; then
61
58
  NDK=$(ls -d "$SDK"/ndk/* 2>/dev/null | sort -V | tail -1)
@@ -68,13 +65,10 @@ for host in windows-x86_64 darwin-x86_64 linux-x86_64; do
68
65
  done
69
66
  done
70
67
 
71
- # Newest platform android.jar. -bootclasspath needs a Windows-style path on
72
- # Windows because javac is a native binary; `cygpath -m` is a no-op elsewhere.
73
68
  winpath() { if command -v cygpath >/dev/null 2>&1; then cygpath -m "$1"; else echo "$1"; fi; }
74
69
  AJAR=$(ls -d "$SDK"/platforms/android-*/android.jar 2>/dev/null | sort -V | tail -1)
75
70
  [ -n "$AJAR" ] && AJAR=$(winpath "$AJAR")
76
71
 
77
- # javac 17: the module targets VERSION_17. A JDK 8 javac cannot compile it.
78
72
  JAVAC=""
79
73
  for cand in "${JAVA_HOME:-}/bin/javac" \
80
74
  "/c/Program Files/Android/Android Studio/jbr/bin/javac.exe" \
@@ -84,8 +78,6 @@ for cand in "${JAVA_HOME:-}/bin/javac" \
84
78
  [ "${v:-0}" -ge 11 ] 2>/dev/null && JAVAC="$cand" && break
85
79
  done
86
80
 
87
- # jsi/jsi.h and the react-android classes.jar come from a consuming app's
88
- # node_modules. RN_ROOT can point straight at one.
89
81
  RN=${RN_ROOT:-}
90
82
  if [ -z "$RN" ]; then
91
83
  for cand in "$here/../node_modules/react-native" \
@@ -99,11 +91,6 @@ JSI="$RN/ReactCommon/jsi"
99
91
  OUT=${TMPDIR:-/tmp}/pdfcanvas-gate
100
92
  rm -rf "$OUT"; mkdir -p "$OUT"/{standalone,autolinked,androidtest,h,classes,rn}
101
93
 
102
- # react-android's classes.jar is inside an AAR in the gradle cache. Needed only
103
- # for section 3.
104
- # Extracted WHOLE rather than with a `jni/*` member filter: MSYS unzip extracts
105
- # nothing for that pattern, which silently loses libjsi.so and turns the
106
- # WITH_JSI link into a skip. The AAR is a couple of megabytes; just unpack it.
107
94
  RNJAR=""
108
95
  aar=$(ls -t "$HOME"/.gradle/caches/modules-2/files-2.1/com.facebook.react/react-android/*/*/*-release.aar 2>/dev/null | head -1)
109
96
  [ -z "$aar" ] && aar=$(ls -t "$HOME"/.gradle/caches/modules-2/files-2.1/com.facebook.react/react-android/*/*/*-debug.aar 2>/dev/null | head -1)
@@ -120,104 +107,107 @@ for host in windows-x86_64 darwin-x86_64 linux-x86_64; do
120
107
  done
121
108
  done
122
109
 
123
- # libjsi.so ships inside the same AAR, one copy per ABI, already unpacked above.
124
110
  JSILIB=""
125
111
  [ -f "$OUT/rn/jni/arm64-v8a/libjsi.so" ] && JSILIB="$OUT/rn/jni"
126
112
 
127
- echo "SDK $SDK"
128
- echo "NDK ${NDK:-<none>}"
129
- echo "javac ${JAVAC:-<none>}"
130
- echo "RN ${RN:-<none>}"
113
+ # PDFium for the two ABIs the gate links. Fetched through the same script the
114
+ # build uses, so the gate links exactly what the AAR will carry.
115
+ PDFIUM="$ROOT/native/.pdfium/android"
116
+ if command -v node >/dev/null 2>&1; then
117
+ (cd "$ROOT" && node scripts/fetch-pdfium.mjs android --abis arm64-v8a,armeabi-v7a >/dev/null) || echo "WARN fetch-pdfium failed; using whatever is at $PDFIUM"
118
+ fi
119
+
120
+ CORE_SRCS=$(ls "$ROOT"/native/core/src/*.cpp)
121
+ JNI_SRC="$PKG/src/main/cpp/pdfcanvas-jni.cpp"
122
+ INCLUDES="-I$ROOT/native/core/include -I$PDFIUM/include"
123
+
124
+ echo "SDK $SDK"
125
+ echo "NDK ${NDK:-<none>}"
126
+ echo "javac ${JAVAC:-<none>}"
127
+ echo "RN ${RN:-<none>}"
128
+ echo "PDFium $PDFIUM ($(cat "$PDFIUM/.stamp" 2>/dev/null || echo 'not fetched'))"
131
129
  echo
132
130
 
133
131
  # ---------------------------------------------------------------- 1. C++
134
- # Both #if configurations, both pointer widths. 32-bit is not redundant: it is
135
- # what catches a size_t/jsize/uint64 width slip that arm64 hides.
136
132
  abi_dir() { case "$1" in aarch64*) echo arm64-v8a;; armv7a*) echo armeabi-v7a;; esac; }
137
133
 
138
- if [ -n "$CLANG" ]; then
134
+ if [ -n "$CLANG" ] && [ -f "$PDFIUM/include/fpdfview.h" ]; then
139
135
  for abi in aarch64-linux-android24 armv7a-linux-androideabi24; do
136
+ lib="$PDFIUM/jniLibs/$(abi_dir "$abi")/libpdfium.so"
137
+ if [ ! -f "$lib" ]; then say_skip "cpp+link $abi" "no libpdfium.so for $(abi_dir "$abi")"; continue; fi
140
138
  # --no-undefined is the point of linking rather than syntax-checking: it
141
- # fails on any symbol the platform will not supply at runtime, which is
142
- # exactly the class of mistake a -fsyntax-only pass waves through and a
143
- # device then reports as a dlopen failure and a silent base64 fallback.
139
+ # fails on any symbol the platform (or PDFium) will not supply at runtime.
144
140
  run "cpp+link standalone $abi" "$CLANG" --target=$abi -std=c++17 -O2 \
145
- -Wall -Wextra -Werror -shared -fPIC -o "$OUT/lib-$abi.so" \
146
- "$PKG/src/jsi/cpp/pdfcanvas-jsi.cpp" -ljnigraphics -Wl,--no-undefined
141
+ -Wall -Wextra -Werror -frtti -fexceptions -shared -fPIC $INCLUDES \
142
+ -o "$OUT/lib-$abi.so" $CORE_SRCS "$JNI_SRC" "$lib" -llog -Wl,--no-undefined
147
143
 
148
144
  if [ -f "$JSI/jsi/jsi.h" ] && [ -n "$JSILIB" ] && [ -d "$JSILIB/$(abi_dir "$abi")" ]; then
149
145
  run "cpp+link WITH_JSI $abi" "$CLANG" --target=$abi -std=c++17 -O2 \
150
- -Wall -Wextra -Werror -frtti -fexceptions -shared -fPIC \
146
+ -Wall -Wextra -Werror -frtti -fexceptions -shared -fPIC $INCLUDES \
151
147
  -DPDFCANVAS_WITH_JSI=1 -I"$JSI" -o "$OUT/lib-jsi-$abi.so" \
152
- "$PKG/src/jsi/cpp/pdfcanvas-jsi.cpp" -ljnigraphics \
148
+ $CORE_SRCS "$JNI_SRC" "$lib" -llog \
153
149
  -L"$JSILIB/$(abi_dir "$abi")" -ljsi -Wl,--no-undefined
154
150
  elif [ ! -f "$JSI/jsi/jsi.h" ]; then
155
- say_skip "cpp+link WITH_JSI $abi" \
156
- "no jsi/jsi.h; set RN_ROOT to a react-native install"
151
+ say_skip "cpp+link WITH_JSI $abi" "no jsi/jsi.h; set RN_ROOT to a react-native install"
157
152
  else
158
- say_skip "cpp+link WITH_JSI $abi" \
159
- "no libjsi.so for this ABI in the react-android AAR"
153
+ say_skip "cpp+link WITH_JSI $abi" "no libjsi.so for this ABI in the react-android AAR"
160
154
  fi
161
155
  done
162
156
 
163
- # The gate links with an explicit -l list, so it would keep passing if the
164
- # equivalent line were dropped from CMakeLists.txt — the real build would then
165
- # fail, loudly, but only on a machine running gradle. Assert the two agree.
166
- cml="$PKG/src/jsi/cpp/CMakeLists.txt"
157
+ # The gate links with an explicit list, so it would keep passing if the
158
+ # equivalent line were dropped from CMakeLists.txt. Assert the two agree.
159
+ cml="$PKG/src/main/cpp/CMakeLists.txt"
167
160
  missing_link=""
168
- for lib in jnigraphics; do
161
+ for lib in pdfium log; do
169
162
  grep -q "target_link_libraries.*[ (]$lib[ )]" "$cml" || missing_link="$missing_link $lib"
170
163
  done
171
164
  if [ -z "$missing_link" ]; then
172
165
  echo "PASS CMakeLists links what this gate links"
173
166
  else
174
- echo "FAIL this gate links$missing_link but CMakeLists.txt does not"
175
- fail=1
167
+ echo "FAIL this gate links$missing_link but CMakeLists.txt does not"; fail=1
176
168
  fi
177
169
 
178
- # The built library must actually EXPORT what Java will look up. A visibility
179
- # flag or a namespace slip hides a symbol without failing any compile.
170
+ # The core source list in pdfcanvas-core.cmake must name every file that
171
+ # exists, or a platform build silently misses one.
172
+ for f in $CORE_SRCS; do
173
+ grep -q "$(basename "$f")" "$ROOT/native/core/pdfcanvas-core.cmake" \
174
+ || { echo "FAIL native/core/src/$(basename "$f") is not listed in pdfcanvas-core.cmake"; fail=1; }
175
+ done
176
+
180
177
  if [ -n "$NM" ] && [ -f "$OUT/lib-aarch64-linux-android24.so" ]; then
181
178
  exported=$("$NM" -D --defined-only "$OUT/lib-aarch64-linux-android24.so" 2>/dev/null \
182
- | grep -c 'Java_tools_reekon_pdfcanvas')
183
- if [ "${exported:-0}" -ge 7 ]; then
184
- echo "PASS the linked .so exports $exported Java_tools_reekon_* symbols"
179
+ | grep -c 'Java_tools_reekon_pdfcanvas_PdfCanvasNative_')
180
+ if [ "${exported:-0}" -ge 13 ]; then
181
+ echo "PASS the linked .so exports $exported Java_tools_reekon_pdfcanvas_PdfCanvasNative_* symbols"
185
182
  else
186
- echo "FAIL the linked .so exports only ${exported:-0} JNI symbols (expected >= 7)"
187
- fail=1
183
+ echo "FAIL the linked .so exports only ${exported:-0} JNI symbols (expected >= 13)"; fail=1
188
184
  fi
189
185
  else
190
186
  say_skip "exported JNI symbols" "no llvm-nm, or nothing linked"
191
187
  fi
192
- else
188
+ elif [ -z "$CLANG" ]; then
193
189
  say_skip "cpp (all)" "no NDK clang++ found; set ANDROID_NDK_HOME"
190
+ else
191
+ say_skip "cpp (all)" "no PDFium headers at $PDFIUM; run node scripts/fetch-pdfium.mjs android"
194
192
  fi
195
193
 
196
- # ---------------------------------------------------------------- 2/3/4. Java
194
+ # ---------------------------------------------------------------- 2/3. Java
197
195
  if [ -n "$JAVAC" ] && [ -n "$AJAR" ]; then
198
- # Standalone build: main + jsi, no React anywhere, against the strict Android
199
- # API surface. -Werror here because this half is meant to stay clean.
200
- run "javac standalone (main+jsi)" \
196
+ run "javac standalone (main)" \
201
197
  "$JAVAC" -proc:none -nowarn -Werror -d "$OUT/standalone" -bootclasspath "$AJAR" \
202
198
  -source 8 -target 8 \
203
- "$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java \
204
- "$PKG"/src/jsi/java/tools/reekon/pdfcanvas/jsi/*.java
199
+ "$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java
205
200
 
206
- # Autolinked build: adds src/reactnative. Its lambdas need a real java.base,
207
- # so android.jar moves from -bootclasspath to -cp.
208
201
  if [ -n "$RNJAR" ]; then
209
202
  run "javac autolinked (+reactnative)" \
210
203
  "$JAVAC" -proc:none -nowarn -d "$OUT/autolinked" -cp "$AJAR;$RNJAR" \
211
204
  "$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java \
212
- "$PKG"/src/jsi/java/tools/reekon/pdfcanvas/jsi/*.java \
213
205
  "$PKG"/src/reactnative/java/tools/reekon/pdfcanvas/rn/*.java
214
206
  else
215
207
  say_skip "javac autolinked (+reactnative)" \
216
208
  "no react-android AAR in the gradle cache; build a consuming app once to populate it"
217
209
  fi
218
210
 
219
- # The instrumented suite, against the standalone classes it tests. Compiling
220
- # it is how a change to the rasterizer's API is caught before a device run.
221
211
  TESTCP=""
222
212
  for a in androidx.test/core androidx.test/monitor androidx.test/runner androidx.test.ext/junit; do
223
213
  aa=$(ls -t "$HOME"/.gradle/caches/modules-2/files-2.1/$a/*/*/*.aar 2>/dev/null | head -1)
@@ -241,36 +231,26 @@ else
241
231
  say_skip "javac (all)" "need a JDK 11+ javac and an SDK platform android.jar"
242
232
  fi
243
233
 
244
- # ---------------------------------------------------------------- 5. JNI
245
- # THE SECTION THAT EARNS THIS SCRIPT. javac -h derives the expected C prototypes
246
- # from the Java `native` declarations; force-including that header makes clang
247
- # compare every definition against its declaration, so a drifted parameter list
248
- # or return type is a compile error instead of an UnsatisfiedLinkError on a
249
- # device. The loop then checks the reverse: a method declared `native` in Java
250
- # with no C++ definition compiles clean everywhere and fails only at runtime.
251
- if [ -n "$JAVAC" ] && [ -n "$AJAR" ] && [ -n "$CLANG" ] && [ -f "$JSI/jsi/jsi.h" ]; then
234
+ # ---------------------------------------------------------------- 4. JNI
235
+ if [ -n "$JAVAC" ] && [ -n "$AJAR" ] && [ -n "$CLANG" ] && [ -f "$PDFIUM/include/fpdfview.h" ]; then
252
236
  if "$JAVAC" -proc:none -nowarn -d "$OUT/classes" -h "$OUT/h" -bootclasspath "$AJAR" \
253
237
  -source 8 -target 8 \
254
- "$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java \
255
- "$PKG"/src/jsi/java/tools/reekon/pdfcanvas/jsi/*.java >/dev/null 2>&1; then
238
+ "$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java >/dev/null 2>&1; then
256
239
  for abi in aarch64-linux-android24 armv7a-linux-androideabi24; do
257
- # -fsyntax-only here on purpose: this pass exists to compare declarations,
258
- # and the link is already covered by section 1.
240
+ # -fsyntax-only on purpose: this pass compares declarations; the link is
241
+ # already covered by section 1.
259
242
  run "jni prototypes match $abi" "$CLANG" --target=$abi -std=c++17 -fsyntax-only \
260
- -Wall -Wextra -Werror -frtti -fexceptions -DPDFCANVAS_WITH_JSI=1 -I"$JSI" \
261
- -I"$OUT/h" -include tools_reekon_pdfcanvas_jsi_PdfCanvasPixelBridge.h \
262
- "$PKG/src/jsi/cpp/pdfcanvas-jsi.cpp"
243
+ -Wall -Wextra -Werror -frtti -fexceptions $INCLUDES \
244
+ -I"$OUT/h" -include tools_reekon_pdfcanvas_PdfCanvasNative.h "$JNI_SRC"
263
245
  done
264
246
  declared=$(grep -o 'Java_[A-Za-z0-9_]*' "$OUT"/h/*.h | sed 's/.*://' | sort -u)
265
247
  n=0; missing=""
266
248
  for m in $declared; do
267
249
  n=$((n + 1))
268
- grep -q "^${m}($" "$PKG/src/jsi/cpp/pdfcanvas-jsi.cpp" || missing="$missing $m"
250
+ grep -q "${m}(" "$JNI_SRC" || missing="$missing $m"
269
251
  done
270
- # A floor, because an extraction that silently finds nothing would make this
271
- # check pass forever while testing nothing.
272
- if [ "$n" -lt 7 ]; then
273
- echo "FAIL the JNI gate found only $n declared native methods (expected >= 7)"; fail=1
252
+ if [ "$n" -lt 13 ]; then
253
+ echo "FAIL the JNI gate found only $n declared native methods (expected >= 13)"; fail=1
274
254
  elif [ -n "$missing" ]; then
275
255
  echo "FAIL declared native in Java, undefined in C++:$missing"; fail=1
276
256
  else
@@ -280,7 +260,7 @@ if [ -n "$JAVAC" ] && [ -n "$AJAR" ] && [ -n "$CLANG" ] && [ -f "$JSI/jsi/jsi.h"
280
260
  echo "FAIL could not generate JNI headers"; fail=1
281
261
  fi
282
262
  else
283
- say_skip "jni signature gate" "needs javac, android.jar, NDK clang++ and jsi/jsi.h together"
263
+ say_skip "jni signature gate" "needs javac, android.jar, NDK clang++ and the PDFium headers together"
284
264
  fi
285
265
 
286
266
  echo "---"
@@ -90,6 +90,20 @@ export interface PdfControllerOptions {
90
90
  * epoch never spawns a lane it has no work for.
91
91
  */
92
92
  maxConcurrentRenders?: number;
93
+ /**
94
+ * Whether tiles draw the PDF's own annotations — highlights, callouts,
95
+ * stamps, arrows, AND form fields — into the raster.
96
+ *
97
+ * DEFAULT TRUE: a page on the canvas should look like the same page in any
98
+ * other viewer, which is what the field reports that prompted this were
99
+ * about ("the annotations are missing"). It used to be hardcoded false on the
100
+ * theory that the Skia layer draws the app's OWN annotations and a PDF-embedded
101
+ * copy would double-draw. That theory is only true for a host that IMPORTS
102
+ * PDF annotations into its own layer — none does today — and such a host is
103
+ * who this switch is for: set it false and the tiles carry the content stream
104
+ * alone, on every backend, identically.
105
+ */
106
+ annotations?: boolean;
93
107
  /** Injected so tests drive a fake clock. Nothing below calls Date.now. */
94
108
  now?: () => number;
95
109
  /** Injected so tests drive a fake clock. Nothing below calls setTimeout. */
@@ -95,6 +95,7 @@ function toPdfError(cause) {
95
95
  }
96
96
  export function createPdfController(options) {
97
97
  const { handle, policy, ingest, cache, allowedPages } = options;
98
+ const drawAnnotations = options.annotations ?? true;
98
99
  const now = options.now ?? DEFAULT_NOW;
99
100
  const schedule = options.schedule ?? DEFAULT_SCHEDULE;
100
101
  const backendId = options.backendId ?? 'unknown';
@@ -417,9 +418,9 @@ export function createPdfController(options) {
417
418
  height: item.docRect.height / info.layoutScale,
418
419
  },
419
420
  scale: item.scale * info.layoutScale,
420
- // Never: the Skia layer draws the app's own annotations on top and
421
- // PDF-embedded ones would double-draw.
422
- annotations: false,
421
+ // See `PdfControllerOptions.annotations`: true unless the host
422
+ // draws the PDF's annotations itself.
423
+ annotations: drawAnnotations,
423
424
  background: 'white',
424
425
  }, state.abort.signal);
425
426
  }
@@ -1,19 +1,15 @@
1
1
  /**
2
2
  * The native default rasterizer.
3
3
  *
4
- * BOTH PLATFORMS ARE WIRED. `createAndroidRasterizer` and `createIosRasterizer`
5
- * are pure and injectable, and this file is the only place a real native module
6
- * is resolved. On any build where the module is absent — a host that skipped
7
- * autolinking, a platform neither branch knows — the self-describing placeholder
8
- * is returned exactly as before, failing at `open()` rather than at import, so
9
- * the pure core, the Skia ingest path and the fake backend all keep working.
10
- *
11
- * ONE NATIVE MODULE NAME FOR BOTH PLATFORMS (`PdfCanvas`), one wire format, and
12
- * therefore one lookup. What differs is which adapter wraps it, because the
13
- * capabilities and the alpha encoding differ — see `./native-bridge.ts`.
4
+ * ONE NATIVE MODULE NAME FOR BOTH PLATFORMS (`PdfCanvas`), one wire format, one
5
+ * adapter, one engine. `createNativeRasterizer` is pure and injectable, and this
6
+ * file is the only place a real native module is resolved. On any build where
7
+ * the module is absent — a host that skipped autolinking, a platform neither
8
+ * binding knows — the self-describing placeholder is returned, failing at
9
+ * `open()` rather than at import, so the pure core, the Skia ingest path and the
10
+ * fake backend all keep working.
14
11
  */
15
12
  import type { PageRasterizer } from '../types.js';
16
13
  export * from './index.js';
17
- export * from './android.js';
18
- export * from './ios.js';
14
+ export * from './native.js';
19
15
  export declare const getDefaultRasterizer: () => PageRasterizer;
@@ -1,27 +1,24 @@
1
1
  /**
2
2
  * The native default rasterizer.
3
3
  *
4
- * BOTH PLATFORMS ARE WIRED. `createAndroidRasterizer` and `createIosRasterizer`
5
- * are pure and injectable, and this file is the only place a real native module
6
- * is resolved. On any build where the module is absent — a host that skipped
7
- * autolinking, a platform neither branch knows — the self-describing placeholder
8
- * is returned exactly as before, failing at `open()` rather than at import, so
9
- * the pure core, the Skia ingest path and the fake backend all keep working.
10
- *
11
- * ONE NATIVE MODULE NAME FOR BOTH PLATFORMS (`PdfCanvas`), one wire format, and
12
- * therefore one lookup. What differs is which adapter wraps it, because the
13
- * capabilities and the alpha encoding differ — see `./native-bridge.ts`.
4
+ * ONE NATIVE MODULE NAME FOR BOTH PLATFORMS (`PdfCanvas`), one wire format, one
5
+ * adapter, one engine. `createNativeRasterizer` is pure and injectable, and this
6
+ * file is the only place a real native module is resolved. On any build where
7
+ * the module is absent — a host that skipped autolinking, a platform neither
8
+ * binding knows — the self-describing placeholder is returned, failing at
9
+ * `open()` rather than at import, so the pure core, the Skia ingest path and the
10
+ * fake backend all keep working.
14
11
  */
15
- import { createAndroidRasterizer } from './android.js';
16
- import { createIosRasterizer } from './ios.js';
12
+ import { createNativeRasterizer } from './native.js';
17
13
  import { createMissingBackendRasterizer } from './index.js';
18
14
  export * from './index.js';
19
- export * from './android.js';
20
- export * from './ios.js';
15
+ export * from './native.js';
21
16
  const MISSING = createMissingBackendRasterizer('missing-native', 'The PdfCanvas native module was not found. On iOS that means the pod is ' +
22
17
  'not in the build (run `pod install` after adding the package, and check ' +
23
18
  'that `PdfCanvas` appears in Podfile.lock); on Android it means ' +
24
- 'autolinking did not register PdfCanvasPackage.');
19
+ 'autolinking did not register PdfCanvasPackage. Either way the app binary ' +
20
+ 'predates this package or was built without it — an OTA update cannot add ' +
21
+ 'a native module.');
25
22
  /**
26
23
  * `require`, not `import`, and wrapped in a try.
27
24
  *
@@ -59,9 +56,8 @@ function loadNativeModule() {
59
56
  * reopen a document, so the default must not be a fresh object per call.
60
57
  *
61
58
  * IT ALSO NEGOTIATES THE TRANSPORT, which is why "once" matters more than
62
- * tidiness: `createNativeRasterizer` probes the JSI path at construction, and on
63
- * iOS that probe is what installs `__pdfCanvasTakePixels` into this runtime. A
64
- * fresh rasterizer per call would re-run the probe, and each run parks a slot.
59
+ * tidiness: `createNativeRasterizer` probes the JSI path at construction, and
60
+ * each run parks a probe slot. A fresh rasterizer per call would re-run it.
65
61
  *
66
62
  * NAME COLLISION, deliberate and unresolved here: `react/usePdfDocument.ts`
67
63
  * exports a registry accessor with this same name and a DIFFERENT meaning
@@ -74,7 +70,5 @@ function loadNativeModule() {
74
70
  const resolved = loadNativeModule();
75
71
  const DEFAULT = resolved === null
76
72
  ? MISSING
77
- : resolved.os === 'ios'
78
- ? createIosRasterizer(resolved.module)
79
- : createAndroidRasterizer(resolved.module);
73
+ : createNativeRasterizer(resolved.module, { platform: resolved.os });
80
74
  export const getDefaultRasterizer = () => DEFAULT;