@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,289 @@
|
|
|
1
|
+
#!/bin/bash
|
|
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.
|
|
5
|
+
#
|
|
6
|
+
# WHY THIS EXISTS. A cold gradle build of a consuming app is ~45 minutes here and
|
|
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:
|
|
10
|
+
#
|
|
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.
|
|
18
|
+
#
|
|
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.
|
|
31
|
+
#
|
|
32
|
+
# Usage: bash android/tools/compile-gate.sh
|
|
33
|
+
#
|
|
34
|
+
# Paths are discovered, with env overrides for a machine that keeps them
|
|
35
|
+
# elsewhere: ANDROID_SDK_ROOT, ANDROID_NDK_HOME, JAVA_HOME, RN_ROOT.
|
|
36
|
+
|
|
37
|
+
set -u
|
|
38
|
+
here=$(cd "$(dirname "$0")/.." && pwd) # <repo>/android
|
|
39
|
+
PKG=$here
|
|
40
|
+
fail=0
|
|
41
|
+
skipped=0
|
|
42
|
+
|
|
43
|
+
say_skip() { echo "SKIP $1"; echo " $2"; skipped=1; }
|
|
44
|
+
|
|
45
|
+
run() {
|
|
46
|
+
local name="$1"; shift
|
|
47
|
+
local out; out=$("$@" 2>&1); local rc=$?
|
|
48
|
+
out=$(echo "$out" | grep -v "^Note: ")
|
|
49
|
+
if [ $rc -eq 0 ]; then echo "PASS $name"
|
|
50
|
+
else echo "FAIL $name (exit $rc)"; echo "$out" | head -30; fail=1; fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------- discovery
|
|
54
|
+
SDK=${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/AppData/Local/Android/Sdk}}
|
|
55
|
+
[ -d "$SDK" ] || SDK=$HOME/Library/Android/sdk
|
|
56
|
+
[ -d "$SDK" ] || SDK=$HOME/Android/Sdk
|
|
57
|
+
|
|
58
|
+
# Newest installed NDK, unless one is pinned.
|
|
59
|
+
NDK=${ANDROID_NDK_HOME:-}
|
|
60
|
+
if [ -z "$NDK" ]; then
|
|
61
|
+
NDK=$(ls -d "$SDK"/ndk/* 2>/dev/null | sort -V | tail -1)
|
|
62
|
+
fi
|
|
63
|
+
CLANG=""
|
|
64
|
+
for host in windows-x86_64 darwin-x86_64 linux-x86_64; do
|
|
65
|
+
for ext in ".exe" ""; do
|
|
66
|
+
c="$NDK/toolchains/llvm/prebuilt/$host/bin/clang++$ext"
|
|
67
|
+
[ -x "$c" ] && CLANG="$c" && break 2
|
|
68
|
+
done
|
|
69
|
+
done
|
|
70
|
+
|
|
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
|
+
winpath() { if command -v cygpath >/dev/null 2>&1; then cygpath -m "$1"; else echo "$1"; fi; }
|
|
74
|
+
AJAR=$(ls -d "$SDK"/platforms/android-*/android.jar 2>/dev/null | sort -V | tail -1)
|
|
75
|
+
[ -n "$AJAR" ] && AJAR=$(winpath "$AJAR")
|
|
76
|
+
|
|
77
|
+
# javac 17: the module targets VERSION_17. A JDK 8 javac cannot compile it.
|
|
78
|
+
JAVAC=""
|
|
79
|
+
for cand in "${JAVA_HOME:-}/bin/javac" \
|
|
80
|
+
"/c/Program Files/Android/Android Studio/jbr/bin/javac.exe" \
|
|
81
|
+
"$(command -v javac 2>/dev/null)"; do
|
|
82
|
+
[ -n "$cand" ] && [ -x "$cand" ] || continue
|
|
83
|
+
v=$("$cand" -version 2>&1 | sed 's/javac //;s/\..*//')
|
|
84
|
+
[ "${v:-0}" -ge 11 ] 2>/dev/null && JAVAC="$cand" && break
|
|
85
|
+
done
|
|
86
|
+
|
|
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
|
+
RN=${RN_ROOT:-}
|
|
90
|
+
if [ -z "$RN" ]; then
|
|
91
|
+
for cand in "$here/../node_modules/react-native" \
|
|
92
|
+
"$here/../../BOLDR/node_modules/react-native" \
|
|
93
|
+
"$here/../example/node_modules/react-native"; do
|
|
94
|
+
[ -f "$cand/ReactCommon/jsi/jsi/jsi.h" ] && RN=$(cd "$cand" && pwd) && break
|
|
95
|
+
done
|
|
96
|
+
fi
|
|
97
|
+
JSI="$RN/ReactCommon/jsi"
|
|
98
|
+
|
|
99
|
+
OUT=${TMPDIR:-/tmp}/pdfcanvas-gate
|
|
100
|
+
rm -rf "$OUT"; mkdir -p "$OUT"/{standalone,autolinked,androidtest,h,classes,rn}
|
|
101
|
+
|
|
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
|
+
RNJAR=""
|
|
108
|
+
aar=$(ls -t "$HOME"/.gradle/caches/modules-2/files-2.1/com.facebook.react/react-android/*/*/*-release.aar 2>/dev/null | head -1)
|
|
109
|
+
[ -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)
|
|
110
|
+
if [ -n "$aar" ] && command -v unzip >/dev/null 2>&1; then
|
|
111
|
+
(cd "$OUT/rn" && unzip -o -q "$aar")
|
|
112
|
+
[ -f "$OUT/rn/classes.jar" ] && RNJAR=$(winpath "$OUT/rn/classes.jar")
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
NM=""
|
|
116
|
+
for host in windows-x86_64 darwin-x86_64 linux-x86_64; do
|
|
117
|
+
for ext in ".exe" ""; do
|
|
118
|
+
c="$NDK/toolchains/llvm/prebuilt/$host/bin/llvm-nm$ext"
|
|
119
|
+
[ -x "$c" ] && NM="$c" && break 2
|
|
120
|
+
done
|
|
121
|
+
done
|
|
122
|
+
|
|
123
|
+
# libjsi.so ships inside the same AAR, one copy per ABI, already unpacked above.
|
|
124
|
+
JSILIB=""
|
|
125
|
+
[ -f "$OUT/rn/jni/arm64-v8a/libjsi.so" ] && JSILIB="$OUT/rn/jni"
|
|
126
|
+
|
|
127
|
+
echo "SDK $SDK"
|
|
128
|
+
echo "NDK ${NDK:-<none>}"
|
|
129
|
+
echo "javac ${JAVAC:-<none>}"
|
|
130
|
+
echo "RN ${RN:-<none>}"
|
|
131
|
+
echo
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------- 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
|
+
abi_dir() { case "$1" in aarch64*) echo arm64-v8a;; armv7a*) echo armeabi-v7a;; esac; }
|
|
137
|
+
|
|
138
|
+
if [ -n "$CLANG" ]; then
|
|
139
|
+
for abi in aarch64-linux-android24 armv7a-linux-androideabi24; do
|
|
140
|
+
# --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.
|
|
144
|
+
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
|
|
147
|
+
|
|
148
|
+
if [ -f "$JSI/jsi/jsi.h" ] && [ -n "$JSILIB" ] && [ -d "$JSILIB/$(abi_dir "$abi")" ]; then
|
|
149
|
+
run "cpp+link WITH_JSI $abi" "$CLANG" --target=$abi -std=c++17 -O2 \
|
|
150
|
+
-Wall -Wextra -Werror -frtti -fexceptions -shared -fPIC \
|
|
151
|
+
-DPDFCANVAS_WITH_JSI=1 -I"$JSI" -o "$OUT/lib-jsi-$abi.so" \
|
|
152
|
+
"$PKG/src/jsi/cpp/pdfcanvas-jsi.cpp" -ljnigraphics \
|
|
153
|
+
-L"$JSILIB/$(abi_dir "$abi")" -ljsi -Wl,--no-undefined
|
|
154
|
+
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"
|
|
157
|
+
else
|
|
158
|
+
say_skip "cpp+link WITH_JSI $abi" \
|
|
159
|
+
"no libjsi.so for this ABI in the react-android AAR"
|
|
160
|
+
fi
|
|
161
|
+
done
|
|
162
|
+
|
|
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"
|
|
167
|
+
missing_link=""
|
|
168
|
+
for lib in jnigraphics; do
|
|
169
|
+
grep -q "target_link_libraries.*[ (]$lib[ )]" "$cml" || missing_link="$missing_link $lib"
|
|
170
|
+
done
|
|
171
|
+
if [ -z "$missing_link" ]; then
|
|
172
|
+
echo "PASS CMakeLists links what this gate links"
|
|
173
|
+
else
|
|
174
|
+
echo "FAIL this gate links$missing_link but CMakeLists.txt does not"
|
|
175
|
+
fail=1
|
|
176
|
+
fi
|
|
177
|
+
|
|
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.
|
|
180
|
+
if [ -n "$NM" ] && [ -f "$OUT/lib-aarch64-linux-android24.so" ]; then
|
|
181
|
+
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"
|
|
185
|
+
else
|
|
186
|
+
echo "FAIL the linked .so exports only ${exported:-0} JNI symbols (expected >= 7)"
|
|
187
|
+
fail=1
|
|
188
|
+
fi
|
|
189
|
+
else
|
|
190
|
+
say_skip "exported JNI symbols" "no llvm-nm, or nothing linked"
|
|
191
|
+
fi
|
|
192
|
+
else
|
|
193
|
+
say_skip "cpp (all)" "no NDK clang++ found; set ANDROID_NDK_HOME"
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------- 2/3/4. Java
|
|
197
|
+
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)" \
|
|
201
|
+
"$JAVAC" -proc:none -nowarn -Werror -d "$OUT/standalone" -bootclasspath "$AJAR" \
|
|
202
|
+
-source 8 -target 8 \
|
|
203
|
+
"$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java \
|
|
204
|
+
"$PKG"/src/jsi/java/tools/reekon/pdfcanvas/jsi/*.java
|
|
205
|
+
|
|
206
|
+
# Autolinked build: adds src/reactnative. Its lambdas need a real java.base,
|
|
207
|
+
# so android.jar moves from -bootclasspath to -cp.
|
|
208
|
+
if [ -n "$RNJAR" ]; then
|
|
209
|
+
run "javac autolinked (+reactnative)" \
|
|
210
|
+
"$JAVAC" -proc:none -nowarn -d "$OUT/autolinked" -cp "$AJAR;$RNJAR" \
|
|
211
|
+
"$PKG"/src/main/java/tools/reekon/pdfcanvas/*.java \
|
|
212
|
+
"$PKG"/src/jsi/java/tools/reekon/pdfcanvas/jsi/*.java \
|
|
213
|
+
"$PKG"/src/reactnative/java/tools/reekon/pdfcanvas/rn/*.java
|
|
214
|
+
else
|
|
215
|
+
say_skip "javac autolinked (+reactnative)" \
|
|
216
|
+
"no react-android AAR in the gradle cache; build a consuming app once to populate it"
|
|
217
|
+
fi
|
|
218
|
+
|
|
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
|
+
TESTCP=""
|
|
222
|
+
for a in androidx.test/core androidx.test/monitor androidx.test/runner androidx.test.ext/junit; do
|
|
223
|
+
aa=$(ls -t "$HOME"/.gradle/caches/modules-2/files-2.1/$a/*/*/*.aar 2>/dev/null | head -1)
|
|
224
|
+
[ -z "$aa" ] && continue
|
|
225
|
+
d="$OUT/$(basename "$a")"; mkdir -p "$d"
|
|
226
|
+
(cd "$d" && unzip -o -q "$aa" classes.jar) && TESTCP="$TESTCP;$(winpath "$d/classes.jar")"
|
|
227
|
+
done
|
|
228
|
+
for j in junit/junit org.hamcrest/hamcrest-core; do
|
|
229
|
+
jj=$(ls -t "$HOME"/.gradle/caches/modules-2/files-2.1/$j/*/*/*.jar 2>/dev/null | grep -v sources | head -1)
|
|
230
|
+
[ -n "$jj" ] && TESTCP="$TESTCP;$(winpath "$jj")"
|
|
231
|
+
done
|
|
232
|
+
if [ -n "$TESTCP" ]; then
|
|
233
|
+
run "javac androidTest suite" \
|
|
234
|
+
"$JAVAC" -proc:none -nowarn -d "$OUT/androidtest" \
|
|
235
|
+
-cp "$AJAR$TESTCP;$(winpath "$OUT/standalone")" \
|
|
236
|
+
"$PKG"/src/androidTest/java/tools/reekon/pdfcanvas/*.java
|
|
237
|
+
else
|
|
238
|
+
say_skip "javac androidTest suite" "no androidx.test jars in the gradle cache"
|
|
239
|
+
fi
|
|
240
|
+
else
|
|
241
|
+
say_skip "javac (all)" "need a JDK 11+ javac and an SDK platform android.jar"
|
|
242
|
+
fi
|
|
243
|
+
|
|
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
|
|
252
|
+
if "$JAVAC" -proc:none -nowarn -d "$OUT/classes" -h "$OUT/h" -bootclasspath "$AJAR" \
|
|
253
|
+
-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
|
|
256
|
+
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.
|
|
259
|
+
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"
|
|
263
|
+
done
|
|
264
|
+
declared=$(grep -o 'Java_[A-Za-z0-9_]*' "$OUT"/h/*.h | sed 's/.*://' | sort -u)
|
|
265
|
+
n=0; missing=""
|
|
266
|
+
for m in $declared; do
|
|
267
|
+
n=$((n + 1))
|
|
268
|
+
grep -q "^${m}($" "$PKG/src/jsi/cpp/pdfcanvas-jsi.cpp" || missing="$missing $m"
|
|
269
|
+
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
|
|
274
|
+
elif [ -n "$missing" ]; then
|
|
275
|
+
echo "FAIL declared native in Java, undefined in C++:$missing"; fail=1
|
|
276
|
+
else
|
|
277
|
+
echo "PASS all $n declared native methods are defined"
|
|
278
|
+
fi
|
|
279
|
+
else
|
|
280
|
+
echo "FAIL could not generate JNI headers"; fail=1
|
|
281
|
+
fi
|
|
282
|
+
else
|
|
283
|
+
say_skip "jni signature gate" "needs javac, android.jar, NDK clang++ and jsi/jsi.h together"
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
echo "---"
|
|
287
|
+
if [ $fail -ne 0 ]; then echo "GATE: FAILURES"; exit 1; fi
|
|
288
|
+
if [ $skipped -ne 0 ]; then echo "GATE: PASS (with skips — see SKIP lines)"; exit 0; fi
|
|
289
|
+
echo "GATE: ALL PASS"
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refcounted, byte-budgeted raster cache.
|
|
3
|
+
*
|
|
4
|
+
* Two things make this different from a plain LRU:
|
|
5
|
+
*
|
|
6
|
+
* 1. REFCOUNTS, NOT LIVENESS GUESSES. A raster is drawable exactly while some
|
|
7
|
+
* PdfContent slot references it. `acquire`/`release` count those slots, so a
|
|
8
|
+
* raster held by both the live detail set and the retiring set has refs 2 and
|
|
9
|
+
* survives until both are gone. Nothing referenced is ever evicted.
|
|
10
|
+
*
|
|
11
|
+
* 2. THE RETIRE QUEUE. A refcount reaching zero does NOT dispose. The entry goes
|
|
12
|
+
* to a queue that the controller flushes at a point that already causes a
|
|
13
|
+
* React commit, because freeing an SkImage that is still in a mounted Skia
|
|
14
|
+
* node draws a corpse. Retirement must never own a timer or a commit of its
|
|
15
|
+
* own — see `flushRetired`.
|
|
16
|
+
*
|
|
17
|
+
* 3. THE DISPOSAL FENCE, which is what makes point 2 actually true.
|
|
18
|
+
*
|
|
19
|
+
* MEASURED: releasing and flushing in the SAME turn — which is what the
|
|
20
|
+
* controller's commit did — is a delay of zero. The entry left `content` and
|
|
21
|
+
* was freed before React had even been notified, let alone re-rendered, so
|
|
22
|
+
* every Skia node still holding it was holding a deleted object. In a browser
|
|
23
|
+
* that is `BindingError: Cannot pass deleted object as a pointer of type
|
|
24
|
+
* Image const*`, thrown out of RN Skia's own `drawImage -> Image.width` while
|
|
25
|
+
* it RE-RECORDS the picture from the mounted tree: 16 occurrences over six
|
|
26
|
+
* high-zoom pans, 3-4 ms after an epoch commit, every time.
|
|
27
|
+
*
|
|
28
|
+
* So the queue is fenced, and the fence is a CLOCK the cache is told about
|
|
29
|
+
* rather than one it assumes:
|
|
30
|
+
*
|
|
31
|
+
* release(key, contentEpoch) tags the entry with the epoch of the content
|
|
32
|
+
* object that stopped referencing it.
|
|
33
|
+
* flushRetired(contentEpoch) frees only entries tagged STRICTLY OLDER than
|
|
34
|
+
* `contentEpoch` — i.e. one whole epoch of
|
|
35
|
+
* grace, which is the commit clock and is a
|
|
36
|
+
* heuristic: it assumes the renderer mounted
|
|
37
|
+
* epoch N before the controller published N+1.
|
|
38
|
+
* notePresented(epoch) the SOUND clock, IF a caller can honestly
|
|
39
|
+
* supply one: everything tagged `epoch` or older
|
|
40
|
+
* is treated as provably unreferenced and freed
|
|
41
|
+
* at once — sooner than the commit clock would.
|
|
42
|
+
*
|
|
43
|
+
* "PRESENTED" MEANS DRAWN, NOT MOUNTED, AND THE DIFFERENCE IS MEASURED. Wiring
|
|
44
|
+
* this to React — a `PdfContentView` prop reporting `content.epoch` from a
|
|
45
|
+
* PASSIVE effect, which is the latest signal a component can produce —
|
|
46
|
+
* REINTRODUCES the corpse it exists to prevent. Controlled A/B in rock-desktop's
|
|
47
|
+
* probe, 14 high-zoom pans per arm, twice each:
|
|
48
|
+
*
|
|
49
|
+
* onPresented wired 2, 2 `BindingError: ... Image const*`
|
|
50
|
+
* commit clock only 0, 0
|
|
51
|
+
*
|
|
52
|
+
* The reason is that the images are held by the RECORDED PICTURE, not by the
|
|
53
|
+
* React tree. RN Skia re-records on its own rAF, so after React has committed
|
|
54
|
+
* epoch N there is still a picture in hand that references epoch N-1's images,
|
|
55
|
+
* and `play()` -> `drawImage` -> `Image.width` reads one that has just been
|
|
56
|
+
* freed. Every stack in that A/B was exactly that. A commit is therefore NOT a
|
|
57
|
+
* presentation, and no React-reachable signal on web is late enough to be one.
|
|
58
|
+
*
|
|
59
|
+
* So this is NOT wired, deliberately, and must not be wired from an effect. It
|
|
60
|
+
* stays for a caller driving `createPdfController` against a renderer that has
|
|
61
|
+
* a genuine post-paint callback. Absent that, the commit clock is the correct
|
|
62
|
+
* answer and its only cost is holding one epoch's dropped rasters while the
|
|
63
|
+
* viewport is at rest.
|
|
64
|
+
*
|
|
65
|
+
* An UNTAGGED release (no epoch) means "nothing of mine is mounted any more",
|
|
66
|
+
* and so does a `flushRetired()` with no epoch: both are the teardown path,
|
|
67
|
+
* where immediate disposal is the correct answer and the original behaviour.
|
|
68
|
+
*
|
|
69
|
+
* The budget is a SOFT cap. Running out never throws and never blanks a page:
|
|
70
|
+
* the last resort is to refuse the newcomer and keep drawing the base we already
|
|
71
|
+
* have. Degrade, never crash.
|
|
72
|
+
*/
|
|
73
|
+
import type { PdfRaster } from './types.js';
|
|
74
|
+
/** Mirrors the `reason` field of the `evict` diagnostic in types.ts. */
|
|
75
|
+
export type EvictionReason = 'budget' | 'page-window' | 'epoch';
|
|
76
|
+
export interface RasterCacheOptions {
|
|
77
|
+
/** Soft cap on cached bytes. A single raster larger than this is refused outright. */
|
|
78
|
+
budgetBytes: number;
|
|
79
|
+
/**
|
|
80
|
+
* Frees the backing SkImage. Called at most once per raster: on web,
|
|
81
|
+
* double-dispose THROWS where native tolerates it.
|
|
82
|
+
*/
|
|
83
|
+
dispose: (raster: PdfRaster) => void;
|
|
84
|
+
/**
|
|
85
|
+
* How many pages either side of the live window keep their whole-page base.
|
|
86
|
+
* Bases inside this radius are the anti-blank mechanism and are evicted last;
|
|
87
|
+
* a base for page 300 while you read page 4 is just garbage.
|
|
88
|
+
*/
|
|
89
|
+
baseRetentionRadius?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface RasterCacheStats {
|
|
92
|
+
bytes: number;
|
|
93
|
+
entries: number;
|
|
94
|
+
/** Entries sitting in the retire queue, awaiting the next flush. */
|
|
95
|
+
retired: number;
|
|
96
|
+
/** Entries with at least one live reference. */
|
|
97
|
+
referenced: number;
|
|
98
|
+
}
|
|
99
|
+
export interface RasterCache {
|
|
100
|
+
/** Returns the cached raster and marks it recently used. */
|
|
101
|
+
get(key: string): PdfRaster | undefined;
|
|
102
|
+
has(key: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Inserts a raster, evicting as needed to stay under budget.
|
|
105
|
+
*
|
|
106
|
+
* TAKES OWNERSHIP. On `false` the raster has ALREADY been disposed and must
|
|
107
|
+
* not be drawn — the caller keeps drawing whatever it had.
|
|
108
|
+
*
|
|
109
|
+
* On `true` the caller must read the cache's copy back with `acquire`/`get`
|
|
110
|
+
* rather than reusing its own reference: an entry with this key may already
|
|
111
|
+
* have existed, in which case the incumbent stands and the newcomer is freed.
|
|
112
|
+
*/
|
|
113
|
+
put(raster: PdfRaster): boolean;
|
|
114
|
+
/** Takes a reference. Un-retires the entry if it was queued for disposal. */
|
|
115
|
+
acquire(key: string): PdfRaster | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Drops a reference. At zero the entry joins the retire queue, NOT dispose.
|
|
118
|
+
*
|
|
119
|
+
* `contentEpoch` is the epoch of the content object that has just STOPPED
|
|
120
|
+
* referencing this raster — i.e. the epoch being published by the commit doing
|
|
121
|
+
* the releasing. It is what the disposal fence is measured against, so passing
|
|
122
|
+
* it is how a caller says "a renderer may still be holding this". Omit it only
|
|
123
|
+
* when nothing of yours is mounted at all (teardown), which is what the
|
|
124
|
+
* original unfenced behaviour was.
|
|
125
|
+
*/
|
|
126
|
+
release(key: string, contentEpoch?: number): void;
|
|
127
|
+
refCount(key: string): number;
|
|
128
|
+
/**
|
|
129
|
+
* Disposes retired entries the renderer can no longer be holding.
|
|
130
|
+
*
|
|
131
|
+
* Pass the epoch of the content object you have just published: entries tagged
|
|
132
|
+
* with an OLDER epoch go, entries tagged with this one wait for the next commit
|
|
133
|
+
* (or for `notePresented`, which frees them as soon as it is provable). Call it
|
|
134
|
+
* only from a path that is already producing a React commit — retirement owns
|
|
135
|
+
* no timer and no commit of its own.
|
|
136
|
+
*
|
|
137
|
+
* With NO epoch it frees every retired entry outright. That is the teardown
|
|
138
|
+
* call: nothing of this cache's is mounted, so there is nothing to fence
|
|
139
|
+
* against.
|
|
140
|
+
*/
|
|
141
|
+
flushRetired(contentEpoch?: number): void;
|
|
142
|
+
/**
|
|
143
|
+
* The renderer has mounted content epoch `epoch` — so every raster the content
|
|
144
|
+
* objects up to and including it stopped referencing is provably unreferenced,
|
|
145
|
+
* whatever the commit clock would have guessed, and is freed now.
|
|
146
|
+
*
|
|
147
|
+
* OPTIONAL, and the first call ARMS it: until a renderer reports, the fence
|
|
148
|
+
* falls back to the commit clock (see `flushRetired`). A host that reports once
|
|
149
|
+
* and then stops holds retired bytes until the budget evicts them or the cache
|
|
150
|
+
* is cleared, which is the correct failure — late is safe, early draws a corpse.
|
|
151
|
+
*/
|
|
152
|
+
notePresented?(epoch: number): void;
|
|
153
|
+
/** The pages currently on screen. Drives both eviction categories below. */
|
|
154
|
+
setLiveWindow(pages: readonly number[]): void;
|
|
155
|
+
/** Disposes every entry, referenced or not. Teardown only. */
|
|
156
|
+
clear(): void;
|
|
157
|
+
stats(): RasterCacheStats;
|
|
158
|
+
/** Optional so a hand-written fake cache need not implement it. */
|
|
159
|
+
subscribeEvictions?(listener: (raster: PdfRaster, reason: EvictionReason) => void): () => void;
|
|
160
|
+
}
|
|
161
|
+
export declare function createRasterCache(options: RasterCacheOptions): RasterCache;
|