react-native-pdf-jsi 1.0.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.
- package/DoubleTapView.js +125 -0
- package/INTEGRATION_GUIDE.md +419 -0
- package/LICENSE +21 -0
- package/PdfManager.js +26 -0
- package/PdfPageView.js +53 -0
- package/PdfView.js +421 -0
- package/PdfViewFlatList.js +30 -0
- package/PinchZoomView.js +125 -0
- package/README.md +693 -0
- package/README_JSI.md +348 -0
- package/android/.gradle/5.6.1/fileChanges/last-build.bin +0 -0
- package/android/.gradle/5.6.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/5.6.1/gc.properties +0 -0
- package/android/.gradle/8.5/checksums/checksums.lock +0 -0
- package/android/.gradle/8.5/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.5/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.5/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.5/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.5/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.5/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.5/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +198 -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 +249 -0
- package/android/gradlew.bat +92 -0
- package/android/project.properties +12 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/cpp/Android.mk +50 -0
- package/android/src/main/cpp/CMakeLists.txt +76 -0
- package/android/src/main/cpp/PDFJSI.cpp +190 -0
- package/android/src/main/cpp/PDFJSI.h +95 -0
- package/android/src/main/cpp/PDFJSIBridge.cpp +32 -0
- package/android/src/main/cpp/PDFJSIModule.cpp +31 -0
- package/android/src/main/java/org/wonday/pdf/EnhancedPdfJSIBridge.java +281 -0
- package/android/src/main/java/org/wonday/pdf/PDFJSIManager.java +317 -0
- package/android/src/main/java/org/wonday/pdf/PDFJSIModule.java +189 -0
- package/android/src/main/java/org/wonday/pdf/PdfManager.java +180 -0
- package/android/src/main/java/org/wonday/pdf/PdfView.java +505 -0
- package/android/src/main/java/org/wonday/pdf/RNPDFPackage.java +43 -0
- package/android/src/main/java/org/wonday/pdf/events/TopChangeEvent.java +26 -0
- package/android/src/main/jniLibs/arm64-v8a/libpdfjsi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libpdfjsi.so +0 -0
- package/android/src/main/jniLibs/x86/libpdfjsi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libpdfjsi.so +0 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNPDFPdfViewManagerDelegate.java +92 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNPDFPdfViewManagerInterface.java +35 -0
- package/fabric/RNPDFPdfNativeComponent.js +48 -0
- package/index.d.ts +72 -0
- package/index.js +603 -0
- package/index.js.flow +67 -0
- package/ios/RNPDFPdf/PdfManager.h +23 -0
- package/ios/RNPDFPdf/PdfManager.mm +152 -0
- package/ios/RNPDFPdf/RNPDFPdfPageView.h +21 -0
- package/ios/RNPDFPdf/RNPDFPdfPageView.mm +185 -0
- package/ios/RNPDFPdf/RNPDFPdfPageViewManager.h +18 -0
- package/ios/RNPDFPdf/RNPDFPdfPageViewManager.mm +30 -0
- package/ios/RNPDFPdf/RNPDFPdfView.h +63 -0
- package/ios/RNPDFPdf/RNPDFPdfView.mm +1092 -0
- package/ios/RNPDFPdf/RNPDFPdfViewManager.h +18 -0
- package/ios/RNPDFPdf/RNPDFPdfViewManager.mm +68 -0
- package/ios/RNPDFPdf.xcodeproj/project.pbxproj +321 -0
- package/package.json +78 -0
- package/react-native-pdf.podspec +31 -0
- package/src/EnhancedPdfView.js +362 -0
- package/src/PDFJSI.js +519 -0
- package/src/examples/PDFJSIExample.js +296 -0
- package/src/hooks/usePDFJSI.js +346 -0
- package/src/index.js +32 -0
- package/windows/RCTPdf/PropertySheet.props +16 -0
- package/windows/RCTPdf/RCTPdf.def +3 -0
- package/windows/RCTPdf/RCTPdf.vcxproj +180 -0
- package/windows/RCTPdf/RCTPdf.vcxproj.filters +38 -0
- package/windows/RCTPdf/RCTPdfControl.cpp +667 -0
- package/windows/RCTPdf/RCTPdfControl.h +119 -0
- package/windows/RCTPdf/RCTPdfControl.idl +10 -0
- package/windows/RCTPdf/RCTPdfControl.xaml +33 -0
- package/windows/RCTPdf/RCTPdfViewManager.cpp +69 -0
- package/windows/RCTPdf/RCTPdfViewManager.h +51 -0
- package/windows/RCTPdf/ReactPackageProvider.cpp +15 -0
- package/windows/RCTPdf/ReactPackageProvider.h +16 -0
- package/windows/RCTPdf/ReactPackageProvider.idl +9 -0
- package/windows/RCTPdf/packages.config +4 -0
- package/windows/RCTPdf/pch.cpp +1 -0
- package/windows/RCTPdf/pch.h +31 -0
- package/windows/README.md +21 -0
package/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025-present, Punith M (punithm300@gmail.com)
|
|
3
|
+
* Enhanced version with JSI integration for high-performance PDF operations
|
|
4
|
+
*
|
|
5
|
+
* Original work Copyright (c) 2017-present, Wonday (@wonday.org)
|
|
6
|
+
* All rights reserved.
|
|
7
|
+
*
|
|
8
|
+
* This source code is licensed under the MIT-style license found in the
|
|
9
|
+
* LICENSE file in the root directory of this source tree.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
import React, {Component} from 'react';
|
|
14
|
+
import PropTypes from 'prop-types';
|
|
15
|
+
import {
|
|
16
|
+
View,
|
|
17
|
+
Platform,
|
|
18
|
+
StyleSheet,
|
|
19
|
+
Image,
|
|
20
|
+
Text,
|
|
21
|
+
requireNativeComponent
|
|
22
|
+
} from 'react-native';
|
|
23
|
+
import PdfViewNativeComponent, {
|
|
24
|
+
Commands as PdfViewCommands,
|
|
25
|
+
} from './fabric/RNPDFPdfNativeComponent';
|
|
26
|
+
import ReactNativeBlobUtil from 'react-native-blob-util'
|
|
27
|
+
import {ViewPropTypes} from 'deprecated-react-native-prop-types';
|
|
28
|
+
const SHA1 = require('crypto-js/sha1');
|
|
29
|
+
import PdfView from './PdfView';
|
|
30
|
+
import PDFJSI from './src/PDFJSI';
|
|
31
|
+
|
|
32
|
+
export default class Pdf extends Component {
|
|
33
|
+
|
|
34
|
+
static propTypes = {
|
|
35
|
+
...ViewPropTypes,
|
|
36
|
+
source: PropTypes.oneOfType([
|
|
37
|
+
PropTypes.shape({
|
|
38
|
+
uri: PropTypes.string,
|
|
39
|
+
cache: PropTypes.bool,
|
|
40
|
+
cacheFileName: PropTypes.string,
|
|
41
|
+
expiration: PropTypes.number,
|
|
42
|
+
}),
|
|
43
|
+
// Opaque type returned by require('./test.pdf')
|
|
44
|
+
PropTypes.number,
|
|
45
|
+
]).isRequired,
|
|
46
|
+
page: PropTypes.number,
|
|
47
|
+
scale: PropTypes.number,
|
|
48
|
+
minScale: PropTypes.number,
|
|
49
|
+
maxScale: PropTypes.number,
|
|
50
|
+
horizontal: PropTypes.bool,
|
|
51
|
+
spacing: PropTypes.number,
|
|
52
|
+
password: PropTypes.string,
|
|
53
|
+
renderActivityIndicator: PropTypes.func,
|
|
54
|
+
enableAntialiasing: PropTypes.bool,
|
|
55
|
+
enableAnnotationRendering: PropTypes.bool,
|
|
56
|
+
showsHorizontalScrollIndicator: PropTypes.bool,
|
|
57
|
+
showsVerticalScrollIndicator: PropTypes.bool,
|
|
58
|
+
scrollEnabled: PropTypes.bool,
|
|
59
|
+
enablePaging: PropTypes.bool,
|
|
60
|
+
enableRTL: PropTypes.bool,
|
|
61
|
+
fitPolicy: PropTypes.number,
|
|
62
|
+
trustAllCerts: PropTypes.bool,
|
|
63
|
+
singlePage: PropTypes.bool,
|
|
64
|
+
onLoadComplete: PropTypes.func,
|
|
65
|
+
onPageChanged: PropTypes.func,
|
|
66
|
+
onError: PropTypes.func,
|
|
67
|
+
onPageSingleTap: PropTypes.func,
|
|
68
|
+
onScaleChanged: PropTypes.func,
|
|
69
|
+
onPressLink: PropTypes.func,
|
|
70
|
+
|
|
71
|
+
// Props that are not available in the earlier react native version, added to prevent crashed on android
|
|
72
|
+
accessibilityLabel: PropTypes.string,
|
|
73
|
+
importantForAccessibility: PropTypes.string,
|
|
74
|
+
renderToHardwareTextureAndroid: PropTypes.string,
|
|
75
|
+
testID: PropTypes.string,
|
|
76
|
+
onLayout: PropTypes.bool,
|
|
77
|
+
accessibilityLiveRegion: PropTypes.string,
|
|
78
|
+
accessibilityComponentType: PropTypes.string,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
static defaultProps = {
|
|
82
|
+
password: "",
|
|
83
|
+
scale: 1,
|
|
84
|
+
minScale: 1,
|
|
85
|
+
maxScale: 3,
|
|
86
|
+
spacing: 10,
|
|
87
|
+
fitPolicy: 2, //fit both
|
|
88
|
+
horizontal: false,
|
|
89
|
+
page: 1,
|
|
90
|
+
enableAntialiasing: true,
|
|
91
|
+
enableAnnotationRendering: true,
|
|
92
|
+
showsHorizontalScrollIndicator: true,
|
|
93
|
+
showsVerticalScrollIndicator: true,
|
|
94
|
+
scrollEnabled: true,
|
|
95
|
+
enablePaging: false,
|
|
96
|
+
enableRTL: false,
|
|
97
|
+
trustAllCerts: true,
|
|
98
|
+
usePDFKit: true,
|
|
99
|
+
singlePage: false,
|
|
100
|
+
onLoadProgress: (percent) => {
|
|
101
|
+
},
|
|
102
|
+
onLoadComplete: (numberOfPages, path) => {
|
|
103
|
+
},
|
|
104
|
+
onPageChanged: (page, numberOfPages) => {
|
|
105
|
+
},
|
|
106
|
+
onError: (error) => {
|
|
107
|
+
},
|
|
108
|
+
onPageSingleTap: (page, x, y) => {
|
|
109
|
+
},
|
|
110
|
+
onScaleChanged: (scale) => {
|
|
111
|
+
},
|
|
112
|
+
onPressLink: (url) => {
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
constructor(props) {
|
|
117
|
+
|
|
118
|
+
super(props);
|
|
119
|
+
this.state = {
|
|
120
|
+
path: '',
|
|
121
|
+
isDownloaded: false,
|
|
122
|
+
progress: 0,
|
|
123
|
+
jsiAvailable: false,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
this.lastRNBFTask = null;
|
|
127
|
+
this.pdfJSI = new PDFJSI();
|
|
128
|
+
this.initializeJSI();
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
initializeJSI = async () => {
|
|
133
|
+
try {
|
|
134
|
+
const isAvailable = await this.pdfJSI.checkJSIAvailability();
|
|
135
|
+
if (this._mounted) {
|
|
136
|
+
this.setState({ jsiAvailable: isAvailable });
|
|
137
|
+
}
|
|
138
|
+
if (isAvailable) {
|
|
139
|
+
console.log('🚀 PDFJSI: High-performance JSI mode enabled');
|
|
140
|
+
} else {
|
|
141
|
+
console.log('📱 PDFJSI: Using standard bridge mode');
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.warn('PDFJSI: Failed to initialize JSI', error);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
componentDidUpdate(prevProps) {
|
|
149
|
+
|
|
150
|
+
const nextSource = Image.resolveAssetSource(this.props.source);
|
|
151
|
+
const curSource = Image.resolveAssetSource(prevProps.source);
|
|
152
|
+
|
|
153
|
+
if ((nextSource.uri !== curSource.uri)) {
|
|
154
|
+
// if has download task, then cancel it.
|
|
155
|
+
if (this.lastRNBFTask && this.lastRNBFTask.cancel) {
|
|
156
|
+
this.lastRNBFTask.cancel(err => {
|
|
157
|
+
this._loadFromSource(this.props.source);
|
|
158
|
+
});
|
|
159
|
+
this.lastRNBFTask = null;
|
|
160
|
+
} else {
|
|
161
|
+
this._loadFromSource(this.props.source);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
componentDidMount() {
|
|
167
|
+
this._mounted = true;
|
|
168
|
+
this._loadFromSource(this.props.source);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
componentWillUnmount() {
|
|
172
|
+
this._mounted = false;
|
|
173
|
+
if (this.lastRNBFTask) {
|
|
174
|
+
// this.lastRNBFTask.cancel(err => {
|
|
175
|
+
// });
|
|
176
|
+
this.lastRNBFTask = null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
_loadFromSource = (newSource) => {
|
|
182
|
+
|
|
183
|
+
const source = Image.resolveAssetSource(newSource) || {};
|
|
184
|
+
|
|
185
|
+
let uri = source.uri || '';
|
|
186
|
+
// first set to initial state
|
|
187
|
+
if (this._mounted) {
|
|
188
|
+
this.setState({isDownloaded: false, path: '', progress: 0});
|
|
189
|
+
}
|
|
190
|
+
const filename = source.cacheFileName || SHA1(uri) + '.pdf';
|
|
191
|
+
const cacheFile = ReactNativeBlobUtil.fs.dirs.CacheDir + '/' + filename;
|
|
192
|
+
|
|
193
|
+
if (source.cache) {
|
|
194
|
+
ReactNativeBlobUtil.fs
|
|
195
|
+
.stat(cacheFile)
|
|
196
|
+
.then(stats => {
|
|
197
|
+
if (!Boolean(source.expiration) || (source.expiration * 1000 + stats.lastModified) > (new Date().getTime())) {
|
|
198
|
+
if (this._mounted) {
|
|
199
|
+
this.setState({path: cacheFile, isDownloaded: true});
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// cache expirated then reload it
|
|
203
|
+
this._prepareFile(source);
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
.catch(() => {
|
|
207
|
+
this._prepareFile(source);
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
} else {
|
|
211
|
+
this._prepareFile(source);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
_prepareFile = async (source) => {
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
if (source.uri) {
|
|
219
|
+
let uri = source.uri || '';
|
|
220
|
+
|
|
221
|
+
const isNetwork = !!(uri && uri.match(/^https?:\/\//));
|
|
222
|
+
const isAsset = !!(uri && uri.match(/^bundle-assets:\/\//));
|
|
223
|
+
const isBase64 = !!(uri && uri.match(/^data:application\/pdf;base64/));
|
|
224
|
+
|
|
225
|
+
const filename = source.cacheFileName || SHA1(uri) + '.pdf';
|
|
226
|
+
const cacheFile = ReactNativeBlobUtil.fs.dirs.CacheDir + '/' + filename;
|
|
227
|
+
|
|
228
|
+
// delete old cache file
|
|
229
|
+
this._unlinkFile(cacheFile);
|
|
230
|
+
|
|
231
|
+
if (isNetwork) {
|
|
232
|
+
this._downloadFile(source, cacheFile);
|
|
233
|
+
} else if (isAsset) {
|
|
234
|
+
ReactNativeBlobUtil.fs
|
|
235
|
+
.cp(uri, cacheFile)
|
|
236
|
+
.then(() => {
|
|
237
|
+
if (this._mounted) {
|
|
238
|
+
this.setState({path: cacheFile, isDownloaded: true, progress: 1});
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
.catch(async (error) => {
|
|
242
|
+
this._unlinkFile(cacheFile);
|
|
243
|
+
this._onError(error);
|
|
244
|
+
})
|
|
245
|
+
} else if (isBase64) {
|
|
246
|
+
let data = uri.replace(/data:application\/pdf;base64,/i, '');
|
|
247
|
+
ReactNativeBlobUtil.fs
|
|
248
|
+
.writeFile(cacheFile, data, 'base64')
|
|
249
|
+
.then(() => {
|
|
250
|
+
if (this._mounted) {
|
|
251
|
+
this.setState({path: cacheFile, isDownloaded: true, progress: 1});
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
.catch(async (error) => {
|
|
255
|
+
this._unlinkFile(cacheFile);
|
|
256
|
+
this._onError(error)
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
if (this._mounted) {
|
|
260
|
+
this.setState({
|
|
261
|
+
path: decodeURIComponent(uri.replace(/file:\/\//i, '')),
|
|
262
|
+
isDownloaded: true,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
this._onError(new Error('no pdf source!'));
|
|
268
|
+
}
|
|
269
|
+
} catch (e) {
|
|
270
|
+
this._onError(e)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
_downloadFile = async (source, cacheFile) => {
|
|
277
|
+
|
|
278
|
+
if (this.lastRNBFTask) {
|
|
279
|
+
this.lastRNBFTask.cancel(err => {
|
|
280
|
+
});
|
|
281
|
+
this.lastRNBFTask = null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const tempCacheFile = cacheFile + '.tmp';
|
|
285
|
+
this._unlinkFile(tempCacheFile);
|
|
286
|
+
|
|
287
|
+
this.lastRNBFTask = ReactNativeBlobUtil.config({
|
|
288
|
+
// response data will be saved to this path if it has access right.
|
|
289
|
+
path: tempCacheFile,
|
|
290
|
+
trusty: this.props.trustAllCerts,
|
|
291
|
+
})
|
|
292
|
+
.fetch(
|
|
293
|
+
source.method ? source.method : 'GET',
|
|
294
|
+
source.uri,
|
|
295
|
+
source.headers ? source.headers : {},
|
|
296
|
+
source.body ? source.body : ""
|
|
297
|
+
)
|
|
298
|
+
// listen to download progress event
|
|
299
|
+
.progress((received, total) => {
|
|
300
|
+
this.props.onLoadProgress && this.props.onLoadProgress(received / total);
|
|
301
|
+
if (this._mounted) {
|
|
302
|
+
this.setState({progress: received / total});
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
.catch(async (error) => {
|
|
306
|
+
this._onError(error);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
this.lastRNBFTask
|
|
310
|
+
.then(async (res) => {
|
|
311
|
+
|
|
312
|
+
this.lastRNBFTask = null;
|
|
313
|
+
|
|
314
|
+
if (res && res.respInfo && res.respInfo.headers && !res.respInfo.headers["Content-Encoding"] && !res.respInfo.headers["Transfer-Encoding"] && res.respInfo.headers["Content-Length"]) {
|
|
315
|
+
const expectedContentLength = res.respInfo.headers["Content-Length"];
|
|
316
|
+
let actualContentLength;
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const fileStats = await ReactNativeBlobUtil.fs.stat(res.path());
|
|
320
|
+
|
|
321
|
+
if (!fileStats || !fileStats.size) {
|
|
322
|
+
throw new Error("FileNotFound:" + source.uri);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
actualContentLength = fileStats.size;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
throw new Error("DownloadFailed:" + source.uri);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (expectedContentLength != actualContentLength) {
|
|
331
|
+
throw new Error("DownloadFailed:" + source.uri);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this._unlinkFile(cacheFile);
|
|
336
|
+
ReactNativeBlobUtil.fs
|
|
337
|
+
.cp(tempCacheFile, cacheFile)
|
|
338
|
+
.then(() => {
|
|
339
|
+
if (this._mounted) {
|
|
340
|
+
this.setState({path: cacheFile, isDownloaded: true, progress: 1});
|
|
341
|
+
}
|
|
342
|
+
this._unlinkFile(tempCacheFile);
|
|
343
|
+
})
|
|
344
|
+
.catch(async (error) => {
|
|
345
|
+
throw error;
|
|
346
|
+
});
|
|
347
|
+
})
|
|
348
|
+
.catch(async (error) => {
|
|
349
|
+
this._unlinkFile(tempCacheFile);
|
|
350
|
+
this._unlinkFile(cacheFile);
|
|
351
|
+
this._onError(error);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
_unlinkFile = async (file) => {
|
|
357
|
+
try {
|
|
358
|
+
await ReactNativeBlobUtil.fs.unlink(file);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
setNativeProps = nativeProps => {
|
|
365
|
+
if (this._root){
|
|
366
|
+
this._root.setNativeProps(nativeProps);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
setPage( pageNumber ) {
|
|
371
|
+
if ( (pageNumber === null) || (isNaN(pageNumber)) ) {
|
|
372
|
+
throw new Error('Specified pageNumber is not a number');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Use JSI for enhanced performance if available
|
|
376
|
+
if (this.state.jsiAvailable && this.state.path) {
|
|
377
|
+
try {
|
|
378
|
+
const pdfId = this.generatePdfId();
|
|
379
|
+
this.pdfJSI.setCurrentPage(pdfId, pageNumber);
|
|
380
|
+
console.log(`🚀 JSI: Set page ${pageNumber} for PDF ${pdfId}`);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.warn('JSI setPage failed, falling back to standard method:', error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!!global?.nativeFabricUIManager ) {
|
|
387
|
+
if (this._root) {
|
|
388
|
+
PdfViewCommands.setNativePage(
|
|
389
|
+
this._root,
|
|
390
|
+
pageNumber,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
this.setNativeProps({
|
|
395
|
+
page: pageNumber
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 🚀 JSI Enhanced Methods
|
|
402
|
+
|
|
403
|
+
generatePdfId = () => {
|
|
404
|
+
// Generate a unique ID for this PDF instance
|
|
405
|
+
return `pdf_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Enhanced page rendering with JSI
|
|
409
|
+
renderPageWithJSI = async (pageNumber, scale = 1.0) => {
|
|
410
|
+
if (!this.state.jsiAvailable || !this.state.path) {
|
|
411
|
+
console.warn('JSI not available, using standard rendering');
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
const pdfId = this.generatePdfId();
|
|
417
|
+
const result = await this.pdfJSI.renderPageDirect(
|
|
418
|
+
pdfId,
|
|
419
|
+
pageNumber,
|
|
420
|
+
scale,
|
|
421
|
+
this.state.path
|
|
422
|
+
);
|
|
423
|
+
console.log(`🚀 JSI: Rendered page ${pageNumber} in ${result.renderTimeMs}ms`);
|
|
424
|
+
return result;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error('JSI renderPageDirect failed:', error);
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Get page metrics via JSI
|
|
432
|
+
getPageMetricsWithJSI = async (pageNumber) => {
|
|
433
|
+
if (!this.state.jsiAvailable) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
const pdfId = this.generatePdfId();
|
|
439
|
+
return await this.pdfJSI.getPageMetrics(pdfId, pageNumber);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('JSI getPageMetrics failed:', error);
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// Preload pages via JSI
|
|
447
|
+
preloadPagesWithJSI = async (startPage, endPage) => {
|
|
448
|
+
if (!this.state.jsiAvailable) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const pdfId = this.generatePdfId();
|
|
454
|
+
const success = await this.pdfJSI.preloadPagesDirect(pdfId, startPage, endPage);
|
|
455
|
+
console.log(`🚀 JSI: Preloaded pages ${startPage}-${endPage}: ${success}`);
|
|
456
|
+
return success;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
console.error('JSI preloadPagesDirect failed:', error);
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Get JSI performance metrics
|
|
464
|
+
getJSIPerformanceMetrics = async () => {
|
|
465
|
+
if (!this.state.jsiAvailable) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
const pdfId = this.generatePdfId();
|
|
471
|
+
return await this.pdfJSI.getPerformanceMetrics(pdfId);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error('JSI getPerformanceMetrics failed:', error);
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// Get JSI stats
|
|
479
|
+
getJSIStats = async () => {
|
|
480
|
+
if (!this.state.jsiAvailable) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
return await this.pdfJSI.getJSIStats();
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error('JSI getJSIStats failed:', error);
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
_onChange = (event) => {
|
|
493
|
+
|
|
494
|
+
let message = event.nativeEvent.message.split('|');
|
|
495
|
+
//__DEV__ && console.log("onChange: " + message);
|
|
496
|
+
if (message.length > 0) {
|
|
497
|
+
if (message.length > 5) {
|
|
498
|
+
message[4] = message.splice(4).join('|');
|
|
499
|
+
}
|
|
500
|
+
if (message[0] === 'loadComplete') {
|
|
501
|
+
let tableContents;
|
|
502
|
+
try {
|
|
503
|
+
tableContents = message[4]&&JSON.parse(message[4]);
|
|
504
|
+
} catch(e) {
|
|
505
|
+
tableContents = message[4];
|
|
506
|
+
}
|
|
507
|
+
this.props.onLoadComplete && this.props.onLoadComplete(Number(message[1]), this.state.path, {
|
|
508
|
+
width: Number(message[2]),
|
|
509
|
+
height: Number(message[3]),
|
|
510
|
+
},
|
|
511
|
+
tableContents
|
|
512
|
+
);
|
|
513
|
+
} else if (message[0] === 'pageChanged') {
|
|
514
|
+
this.props.onPageChanged && this.props.onPageChanged(Number(message[1]), Number(message[2]));
|
|
515
|
+
} else if (message[0] === 'error') {
|
|
516
|
+
this._onError(new Error(message[1]));
|
|
517
|
+
} else if (message[0] === 'pageSingleTap') {
|
|
518
|
+
this.props.onPageSingleTap && this.props.onPageSingleTap(Number(message[1]), Number(message[2]), Number(message[3]));
|
|
519
|
+
} else if (message[0] === 'scaleChanged') {
|
|
520
|
+
this.props.onScaleChanged && this.props.onScaleChanged(Number(message[1]));
|
|
521
|
+
} else if (message[0] === 'linkPressed') {
|
|
522
|
+
this.props.onPressLink && this.props.onPressLink(message[1]);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
_onError = (error) => {
|
|
529
|
+
|
|
530
|
+
this.props.onError && this.props.onError(error);
|
|
531
|
+
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
render() {
|
|
535
|
+
if (Platform.OS === "android" || Platform.OS === "ios" || Platform.OS === "windows") {
|
|
536
|
+
return (
|
|
537
|
+
<View style={[this.props.style,{overflow: 'hidden'}]}>
|
|
538
|
+
{!this.state.isDownloaded?
|
|
539
|
+
(<View
|
|
540
|
+
style={[styles.progressContainer, this.props.progressContainerStyle]}
|
|
541
|
+
>
|
|
542
|
+
{this.props.renderActivityIndicator
|
|
543
|
+
? this.props.renderActivityIndicator(this.state.progress)
|
|
544
|
+
: <Text>{`${(this.state.progress * 100).toFixed(2)}%`}</Text>}
|
|
545
|
+
</View>):(
|
|
546
|
+
Platform.OS === "android" || Platform.OS === "windows"?(
|
|
547
|
+
<PdfCustom
|
|
548
|
+
ref={component => (this._root = component)}
|
|
549
|
+
{...this.props}
|
|
550
|
+
style={[{flex:1,backgroundColor: '#EEE'}, this.props.style]}
|
|
551
|
+
path={this.state.path}
|
|
552
|
+
onChange={this._onChange}
|
|
553
|
+
/>
|
|
554
|
+
):(
|
|
555
|
+
this.props.usePDFKit ?(
|
|
556
|
+
<PdfCustom
|
|
557
|
+
ref={component => (this._root = component)}
|
|
558
|
+
{...this.props}
|
|
559
|
+
style={[{backgroundColor: '#EEE',overflow: 'hidden'}, this.props.style]}
|
|
560
|
+
path={this.state.path}
|
|
561
|
+
onChange={this._onChange}
|
|
562
|
+
/>
|
|
563
|
+
):(<PdfView
|
|
564
|
+
{...this.props}
|
|
565
|
+
style={[{backgroundColor: '#EEE',overflow: 'hidden'}, this.props.style]}
|
|
566
|
+
path={this.state.path}
|
|
567
|
+
onLoadComplete={this.props.onLoadComplete}
|
|
568
|
+
onPageChanged={this.props.onPageChanged}
|
|
569
|
+
onError={this._onError}
|
|
570
|
+
onPageSingleTap={this.props.onPageSingleTap}
|
|
571
|
+
onScaleChanged={this.props.onScaleChanged}
|
|
572
|
+
onPressLink={this.props.onPressLink}
|
|
573
|
+
/>)
|
|
574
|
+
)
|
|
575
|
+
)}
|
|
576
|
+
</View>);
|
|
577
|
+
} else {
|
|
578
|
+
return (null);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (Platform.OS === "android" || Platform.OS === "ios") {
|
|
586
|
+
var PdfCustom = PdfViewNativeComponent;
|
|
587
|
+
} else if (Platform.OS === "windows") {
|
|
588
|
+
var PdfCustom = requireNativeComponent('RCTPdf', Pdf, {
|
|
589
|
+
nativeOnly: {path: true, onChange: true},
|
|
590
|
+
})
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const styles = StyleSheet.create({
|
|
594
|
+
progressContainer: {
|
|
595
|
+
flex: 1,
|
|
596
|
+
justifyContent: 'center',
|
|
597
|
+
alignItems: 'center'
|
|
598
|
+
},
|
|
599
|
+
progressBar: {
|
|
600
|
+
width: 200,
|
|
601
|
+
height: 2
|
|
602
|
+
}
|
|
603
|
+
});
|
package/index.js.flow
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flow strict
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Component } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { Node } from 'react';
|
|
8
|
+
import type { FormField, Methods } from 'rn-fetch-blob';
|
|
9
|
+
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'
|
|
10
|
+
|
|
11
|
+
export type AssetId = number;
|
|
12
|
+
|
|
13
|
+
export type FitWidth = 0;
|
|
14
|
+
export type FitHeight = 1;
|
|
15
|
+
export type FitBoth = 2;
|
|
16
|
+
|
|
17
|
+
export type Source = {
|
|
18
|
+
body?: string | FormField[],
|
|
19
|
+
cache?: boolean,
|
|
20
|
+
cacheFileName?: string,
|
|
21
|
+
expiration?: number,
|
|
22
|
+
headers?: { [key: string]: string },
|
|
23
|
+
method?: Methods,
|
|
24
|
+
uri: string
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type TableContent = {
|
|
28
|
+
children: TableContent[],
|
|
29
|
+
mNativePtr: number,
|
|
30
|
+
pageIdx: number,
|
|
31
|
+
title: string,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type Props = {
|
|
35
|
+
renderActivityIndicator?: (progress: number) => Node,
|
|
36
|
+
enableAnnotationRendering?: boolean,
|
|
37
|
+
enableAntialiasing?: boolean,
|
|
38
|
+
enablePaging?: boolean,
|
|
39
|
+
enableRTL?: boolean,
|
|
40
|
+
fitPolicy?: FitWidth | FitHeight | FitBoth,
|
|
41
|
+
horizontal?: boolean,
|
|
42
|
+
showsHorizontalScrollIndicator?: boolean,
|
|
43
|
+
showsVerticalScrollIndicator?: boolean,
|
|
44
|
+
scrollEnabled?: boolean,
|
|
45
|
+
maxScale?: number,
|
|
46
|
+
minScale?: number,
|
|
47
|
+
singlePage?: boolean,
|
|
48
|
+
onError?: (error: Error) => void,
|
|
49
|
+
onLoadComplete?: (numberOfPages: number, path: string, size: { height: number, width: number }, tableContents: ?TableContent[]) => void,
|
|
50
|
+
onLoadProgress?: (percent: number) => void,
|
|
51
|
+
onPageChanged?: (page: number, numberOfPages: number) => void,
|
|
52
|
+
onPageSingleTap?: (page: number, x: number, y: number) => void,
|
|
53
|
+
onScaleChanged?: (scale: number) => void,
|
|
54
|
+
onPressLink?: (url: string) => void,
|
|
55
|
+
page?: number,
|
|
56
|
+
password?: string,
|
|
57
|
+
progressContainerStyle?: ViewStyleProp,
|
|
58
|
+
scale?: number,
|
|
59
|
+
source: AssetId | Source,
|
|
60
|
+
spacing?: number,
|
|
61
|
+
style?: ViewStyleProp,
|
|
62
|
+
testID?: string
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
declare export default class Pdf extends Component<Props> {
|
|
66
|
+
setPage: (pageNumber: number) => void;
|
|
67
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2017-present, Wonday (@wonday.org)
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import <Foundation/Foundation.h>
|
|
10
|
+
#import <UIKit/UIKit.h>
|
|
11
|
+
|
|
12
|
+
#if __has_include(<React/RCTAssert.h>)
|
|
13
|
+
#import <React/RCTBridgeModule.h>
|
|
14
|
+
#else
|
|
15
|
+
#import "RCTBridgeModule.h"
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@interface PdfManager : NSObject <RCTBridgeModule>
|
|
20
|
+
|
|
21
|
+
+ (CGPDFDocumentRef) getPdf:(NSUInteger) index;
|
|
22
|
+
|
|
23
|
+
@end
|