@webority-technologies/mobile-ui 0.0.7 → 0.0.9

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.
@@ -5,21 +5,57 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = exports.DocumentViewer = void 0;
7
7
  var _mobileCore = require("@webority-technologies/mobile-core");
8
+ var _download = require("@webority-technologies/mobile-core/download");
8
9
  var _react = require("react");
9
10
  var _reactNative = require("react-native");
10
- var _reactNativePdf = _interopRequireDefault(require("react-native-pdf"));
11
+ var _NativePdfRasterizer = _interopRequireDefault(require("../../specs/NativePdfRasterizer.js"));
11
12
  var _index = require("../Spinner/index.js");
12
13
  var _jsxRuntime = require("react/jsx-runtime");
13
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
16
+ const MAX_RENDER_SCALE = 3;
17
+ /** How many pages beyond the current one stay mounted, each direction. */
18
+ const RENDER_WINDOW = 1;
14
19
  const toError = value => value instanceof Error ? value : new Error(String(value));
15
20
 
21
+ /** The renderPage scale is capped so a very wide view never asks the native
22
+ * rasterizer for an unbounded bitmap (see react-native.md's ARGB_8888 4-bytes/px
23
+ * guidance) — both native sides additionally clamp their own pixel ceiling. */
24
+ const scaleForWidth = (viewWidthPx, pageWidthPt) => {
25
+ if (pageWidthPt <= 0) {
26
+ return 1;
27
+ }
28
+ const raw = viewWidthPx / pageWidthPt * _reactNative.PixelRatio.get();
29
+ return Math.min(Math.max(raw, 1), MAX_RENDER_SCALE);
30
+ };
31
+ const localPathFromSource = async source => {
32
+ if (!HAS_SCHEME.test(source.uri) || source.uri.startsWith('file://')) {
33
+ return source.uri.replace(/^file:\/\//, '');
34
+ }
35
+ const result = await (0, _download.downloadFile)({
36
+ url: source.uri,
37
+ headers: source.headers,
38
+ authenticated: false,
39
+ overwrite: source.cache === false
40
+ });
41
+ return result.path;
42
+ };
43
+
16
44
  /**
17
- * Renders a PDF only the WebView fallback consumer apps use for non-PDF
18
- * office documents is a distinct rendering strategy and stays app-side.
45
+ * Renders a PDF via our own native rasterizer (PDFKit on iOS, PdfRenderer on
46
+ * Android) no third-party PDF dependency. Pages are paged through a plain
47
+ * `ScrollView` with manual windowed mounting (render the visible page ±
48
+ * `RENDER_WINDOW`), not a `FlatList` — a `VirtualizedList`-based component
49
+ * would trip React Native's "VirtualizedLists should never be nested inside
50
+ * plain ScrollViews" the moment a consumer embeds DocumentViewer in their own
51
+ * scrolling screen, which is a completely ordinary way to use it. Avoiding the
52
+ * VirtualizedList primitive entirely removes that whole class of warning
53
+ * rather than working around one instance of it. The WebView fallback
54
+ * consumer apps use for non-PDF office documents is a distinct rendering
55
+ * strategy and stays app-side.
19
56
  */
20
57
  const DocumentViewer = ({
21
58
  source,
22
- trustAllCerts = false,
23
59
  onLoadComplete,
24
60
  onPageChanged,
25
61
  onError,
@@ -28,29 +64,162 @@ const DocumentViewer = ({
28
64
  testID,
29
65
  accessibilityLabel
30
66
  }) => {
31
- const renderActivityIndicator = (0, _react.useCallback)(() => loadingIndicator ? loadingIndicator() : /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Spinner, {
32
- size: "lg"
33
- }), [loadingIndicator]);
34
- const handleError = (0, _react.useCallback)(error => {
35
- const err = toError(error);
36
- _mobileCore.Logger.error('[@webority-technologies/mobile-ui] DocumentViewer failed to load.', err);
37
- onError?.(err);
38
- }, [onError]);
39
- const handleLoadComplete = (0, _react.useCallback)(numberOfPages => onLoadComplete?.(numberOfPages), [onLoadComplete]);
67
+ const {
68
+ width: windowWidth
69
+ } = (0, _reactNative.useWindowDimensions)();
70
+ const [pageCount, setPageCount] = (0, _react.useState)(0);
71
+ const [pageSizePt, setPageSizePt] = (0, _react.useState)({
72
+ width: 612,
73
+ height: 792
74
+ }); // US Letter fallback
75
+ const pageAspect = pageSizePt.height / pageSizePt.width;
76
+ const [renderedPages, setRenderedPages] = (0, _react.useState)({});
77
+ const [currentPage, setCurrentPage] = (0, _react.useState)(0);
78
+ const handleRef = (0, _react.useRef)(null);
79
+ const onErrorRef = (0, _react.useRef)(onError);
80
+ onErrorRef.current = onError;
81
+ const onLoadCompleteRef = (0, _react.useRef)(onLoadComplete);
82
+ onLoadCompleteRef.current = onLoadComplete;
83
+ const onPageChangedRef = (0, _react.useRef)(onPageChanged);
84
+ onPageChangedRef.current = onPageChanged;
85
+ const documentSource = (0, _react.useMemo)(() => ({
86
+ uri: source.uri,
87
+ headers: source.headers,
88
+ cache: source.cache
89
+ }), [source.uri, source.headers, source.cache]);
90
+ (0, _react.useEffect)(() => {
91
+ let cancelled = false;
92
+ const load = async () => {
93
+ if (!_NativePdfRasterizer.default) {
94
+ onErrorRef.current?.(new Error('[@webority-technologies/mobile-ui] PdfRasterizer native module is not linked. ' + 'Run pod install / rebuild the app after adding @webority-technologies/mobile-ui.'));
95
+ return;
96
+ }
97
+ try {
98
+ const path = await localPathFromSource(documentSource);
99
+ if (cancelled) {
100
+ return;
101
+ }
102
+ const opened = await _NativePdfRasterizer.default.open(path);
103
+ if (cancelled) {
104
+ await _NativePdfRasterizer.default.close(opened.handle).catch(() => undefined);
105
+ return;
106
+ }
107
+ handleRef.current = opened.handle;
108
+ setPageCount(opened.pageCount);
109
+ setCurrentPage(0);
110
+ setRenderedPages({});
111
+ if (opened.pageWidth > 0 && opened.pageHeight > 0) {
112
+ setPageSizePt({
113
+ width: opened.pageWidth,
114
+ height: opened.pageHeight
115
+ });
116
+ }
117
+ onLoadCompleteRef.current?.(opened.pageCount);
118
+ } catch (error) {
119
+ if (!cancelled) {
120
+ const err = toError(error);
121
+ _mobileCore.Logger.error('[@webority-technologies/mobile-ui] DocumentViewer failed to load.', err);
122
+ onErrorRef.current?.(err);
123
+ }
124
+ }
125
+ };
126
+ void load();
127
+ return () => {
128
+ cancelled = true;
129
+ };
130
+ }, [documentSource]);
131
+ (0, _react.useEffect)(() => () => {
132
+ const openHandle = handleRef.current;
133
+ if (openHandle) {
134
+ _NativePdfRasterizer.default?.close(openHandle).catch(() => undefined);
135
+ }
136
+ }, []);
137
+ const renderPageImage = (0, _react.useCallback)(async pageIndex => {
138
+ const openHandle = handleRef.current;
139
+ if (!openHandle || !_NativePdfRasterizer.default) {
140
+ return;
141
+ }
142
+ try {
143
+ const scale = scaleForWidth(windowWidth, pageSizePt.width);
144
+ const uri = await _NativePdfRasterizer.default.renderPage(openHandle, pageIndex, scale);
145
+ setRenderedPages(prev => prev[pageIndex] ? prev : {
146
+ ...prev,
147
+ [pageIndex]: uri
148
+ });
149
+ } catch (error) {
150
+ _mobileCore.Logger.error(`[@webority-technologies/mobile-ui] DocumentViewer failed to render page ${pageIndex}.`, toError(error));
151
+ }
152
+ }, [pageSizePt.width, windowWidth]);
153
+ const pageHeight = windowWidth * pageAspect;
154
+
155
+ // Mount and rasterize the current page plus a small window around it;
156
+ // everything else stays an empty placeholder slot until scrolled near.
157
+ (0, _react.useEffect)(() => {
158
+ if (pageCount === 0) {
159
+ return;
160
+ }
161
+ const start = Math.max(0, currentPage - RENDER_WINDOW);
162
+ const end = Math.min(pageCount - 1, currentPage + RENDER_WINDOW);
163
+ for (let index = start; index <= end; index += 1) {
164
+ void renderPageImage(index);
165
+ }
166
+ }, [currentPage, pageCount, renderPageImage]);
167
+ const handleScroll = (0, _react.useCallback)(event => {
168
+ if (pageHeight <= 0) {
169
+ return;
170
+ }
171
+ const page = Math.min(pageCount - 1, Math.max(0, Math.round(event.nativeEvent.contentOffset.y / pageHeight)));
172
+ setCurrentPage(prev => {
173
+ if (prev !== page) {
174
+ onPageChangedRef.current?.(page + 1, pageCount);
175
+ }
176
+ return page;
177
+ });
178
+ }, [pageCount, pageHeight]);
179
+ const pages = (0, _react.useMemo)(() => Array.from({
180
+ length: pageCount
181
+ }, (_unused, i) => i), [pageCount]);
182
+ const renderWindowStart = Math.max(0, currentPage - RENDER_WINDOW);
183
+ const renderWindowEnd = Math.min(pageCount - 1, currentPage + RENDER_WINDOW);
40
184
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
41
185
  style: [styles.root, style],
42
186
  testID: testID,
43
187
  accessible: accessibilityLabel !== undefined,
44
188
  accessibilityLabel: accessibilityLabel,
45
189
  accessibilityRole: "none",
46
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativePdf.default, {
47
- source: source,
48
- trustAllCerts: trustAllCerts,
49
- style: styles.pdf,
50
- renderActivityIndicator: renderActivityIndicator,
51
- onLoadComplete: handleLoadComplete,
52
- onPageChanged: onPageChanged,
53
- onError: handleError
190
+ children: pageCount === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
191
+ style: styles.pagePlaceholder,
192
+ children: loadingIndicator ? loadingIndicator() : /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Spinner, {
193
+ size: "lg"
194
+ })
195
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
196
+ onScroll: handleScroll,
197
+ scrollEventThrottle: 32,
198
+ showsVerticalScrollIndicator: true,
199
+ removeClippedSubviews: true,
200
+ children: pages.map(index => {
201
+ const withinWindow = index >= renderWindowStart && index <= renderWindowEnd;
202
+ const uri = withinWindow ? renderedPages[index] : undefined;
203
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
204
+ style: [styles.page, {
205
+ width: windowWidth,
206
+ height: pageHeight
207
+ }],
208
+ children: uri ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
209
+ source: {
210
+ uri
211
+ },
212
+ style: styles.pageImage,
213
+ resizeMode: "contain",
214
+ accessible: false
215
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
216
+ style: styles.pagePlaceholder,
217
+ children: withinWindow ? loadingIndicator?.() ?? /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Spinner, {
218
+ size: "lg"
219
+ }) : null
220
+ })
221
+ }, index);
222
+ })
54
223
  })
55
224
  });
56
225
  };
@@ -60,10 +229,16 @@ const styles = _reactNative.StyleSheet.create({
60
229
  root: {
61
230
  flex: 1
62
231
  },
63
- pdf: {
232
+ page: {
233
+ alignSelf: 'center'
234
+ },
235
+ pageImage: {
236
+ flex: 1
237
+ },
238
+ pagePlaceholder: {
64
239
  flex: 1,
65
- width: '100%',
66
- height: '100%'
240
+ alignItems: 'center',
241
+ justifyContent: 'center'
67
242
  }
68
243
  });
69
244
  var _default = exports.default = DocumentViewer;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _reactNative = require("react-native");
8
+ var _default = exports.default = _reactNative.TurboModuleRegistry.get('PdfRasterizer');
9
+ //# sourceMappingURL=NativePdfRasterizer.js.map
@@ -1,20 +1,56 @@
1
1
  "use strict";
2
2
 
3
3
  import { Logger } from '@webority-technologies/mobile-core';
4
- import { useCallback } from 'react';
5
- import { StyleSheet, View } from 'react-native';
6
- import Pdf from 'react-native-pdf';
4
+ import { downloadFile } from '@webority-technologies/mobile-core/download';
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { Image, PixelRatio, ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native';
7
+ import PdfRasterizer from "../../specs/NativePdfRasterizer.js";
7
8
  import { Spinner } from "../Spinner/index.js";
8
9
  import { jsx as _jsx } from "react/jsx-runtime";
10
+ const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
11
+ const MAX_RENDER_SCALE = 3;
12
+ /** How many pages beyond the current one stay mounted, each direction. */
13
+ const RENDER_WINDOW = 1;
9
14
  const toError = value => value instanceof Error ? value : new Error(String(value));
10
15
 
16
+ /** The renderPage scale is capped so a very wide view never asks the native
17
+ * rasterizer for an unbounded bitmap (see react-native.md's ARGB_8888 4-bytes/px
18
+ * guidance) — both native sides additionally clamp their own pixel ceiling. */
19
+ const scaleForWidth = (viewWidthPx, pageWidthPt) => {
20
+ if (pageWidthPt <= 0) {
21
+ return 1;
22
+ }
23
+ const raw = viewWidthPx / pageWidthPt * PixelRatio.get();
24
+ return Math.min(Math.max(raw, 1), MAX_RENDER_SCALE);
25
+ };
26
+ const localPathFromSource = async source => {
27
+ if (!HAS_SCHEME.test(source.uri) || source.uri.startsWith('file://')) {
28
+ return source.uri.replace(/^file:\/\//, '');
29
+ }
30
+ const result = await downloadFile({
31
+ url: source.uri,
32
+ headers: source.headers,
33
+ authenticated: false,
34
+ overwrite: source.cache === false
35
+ });
36
+ return result.path;
37
+ };
38
+
11
39
  /**
12
- * Renders a PDF only the WebView fallback consumer apps use for non-PDF
13
- * office documents is a distinct rendering strategy and stays app-side.
40
+ * Renders a PDF via our own native rasterizer (PDFKit on iOS, PdfRenderer on
41
+ * Android) no third-party PDF dependency. Pages are paged through a plain
42
+ * `ScrollView` with manual windowed mounting (render the visible page ±
43
+ * `RENDER_WINDOW`), not a `FlatList` — a `VirtualizedList`-based component
44
+ * would trip React Native's "VirtualizedLists should never be nested inside
45
+ * plain ScrollViews" the moment a consumer embeds DocumentViewer in their own
46
+ * scrolling screen, which is a completely ordinary way to use it. Avoiding the
47
+ * VirtualizedList primitive entirely removes that whole class of warning
48
+ * rather than working around one instance of it. The WebView fallback
49
+ * consumer apps use for non-PDF office documents is a distinct rendering
50
+ * strategy and stays app-side.
14
51
  */
15
52
  export const DocumentViewer = ({
16
53
  source,
17
- trustAllCerts = false,
18
54
  onLoadComplete,
19
55
  onPageChanged,
20
56
  onError,
@@ -23,29 +59,162 @@ export const DocumentViewer = ({
23
59
  testID,
24
60
  accessibilityLabel
25
61
  }) => {
26
- const renderActivityIndicator = useCallback(() => loadingIndicator ? loadingIndicator() : /*#__PURE__*/_jsx(Spinner, {
27
- size: "lg"
28
- }), [loadingIndicator]);
29
- const handleError = useCallback(error => {
30
- const err = toError(error);
31
- Logger.error('[@webority-technologies/mobile-ui] DocumentViewer failed to load.', err);
32
- onError?.(err);
33
- }, [onError]);
34
- const handleLoadComplete = useCallback(numberOfPages => onLoadComplete?.(numberOfPages), [onLoadComplete]);
62
+ const {
63
+ width: windowWidth
64
+ } = useWindowDimensions();
65
+ const [pageCount, setPageCount] = useState(0);
66
+ const [pageSizePt, setPageSizePt] = useState({
67
+ width: 612,
68
+ height: 792
69
+ }); // US Letter fallback
70
+ const pageAspect = pageSizePt.height / pageSizePt.width;
71
+ const [renderedPages, setRenderedPages] = useState({});
72
+ const [currentPage, setCurrentPage] = useState(0);
73
+ const handleRef = useRef(null);
74
+ const onErrorRef = useRef(onError);
75
+ onErrorRef.current = onError;
76
+ const onLoadCompleteRef = useRef(onLoadComplete);
77
+ onLoadCompleteRef.current = onLoadComplete;
78
+ const onPageChangedRef = useRef(onPageChanged);
79
+ onPageChangedRef.current = onPageChanged;
80
+ const documentSource = useMemo(() => ({
81
+ uri: source.uri,
82
+ headers: source.headers,
83
+ cache: source.cache
84
+ }), [source.uri, source.headers, source.cache]);
85
+ useEffect(() => {
86
+ let cancelled = false;
87
+ const load = async () => {
88
+ if (!PdfRasterizer) {
89
+ onErrorRef.current?.(new Error('[@webority-technologies/mobile-ui] PdfRasterizer native module is not linked. ' + 'Run pod install / rebuild the app after adding @webority-technologies/mobile-ui.'));
90
+ return;
91
+ }
92
+ try {
93
+ const path = await localPathFromSource(documentSource);
94
+ if (cancelled) {
95
+ return;
96
+ }
97
+ const opened = await PdfRasterizer.open(path);
98
+ if (cancelled) {
99
+ await PdfRasterizer.close(opened.handle).catch(() => undefined);
100
+ return;
101
+ }
102
+ handleRef.current = opened.handle;
103
+ setPageCount(opened.pageCount);
104
+ setCurrentPage(0);
105
+ setRenderedPages({});
106
+ if (opened.pageWidth > 0 && opened.pageHeight > 0) {
107
+ setPageSizePt({
108
+ width: opened.pageWidth,
109
+ height: opened.pageHeight
110
+ });
111
+ }
112
+ onLoadCompleteRef.current?.(opened.pageCount);
113
+ } catch (error) {
114
+ if (!cancelled) {
115
+ const err = toError(error);
116
+ Logger.error('[@webority-technologies/mobile-ui] DocumentViewer failed to load.', err);
117
+ onErrorRef.current?.(err);
118
+ }
119
+ }
120
+ };
121
+ void load();
122
+ return () => {
123
+ cancelled = true;
124
+ };
125
+ }, [documentSource]);
126
+ useEffect(() => () => {
127
+ const openHandle = handleRef.current;
128
+ if (openHandle) {
129
+ PdfRasterizer?.close(openHandle).catch(() => undefined);
130
+ }
131
+ }, []);
132
+ const renderPageImage = useCallback(async pageIndex => {
133
+ const openHandle = handleRef.current;
134
+ if (!openHandle || !PdfRasterizer) {
135
+ return;
136
+ }
137
+ try {
138
+ const scale = scaleForWidth(windowWidth, pageSizePt.width);
139
+ const uri = await PdfRasterizer.renderPage(openHandle, pageIndex, scale);
140
+ setRenderedPages(prev => prev[pageIndex] ? prev : {
141
+ ...prev,
142
+ [pageIndex]: uri
143
+ });
144
+ } catch (error) {
145
+ Logger.error(`[@webority-technologies/mobile-ui] DocumentViewer failed to render page ${pageIndex}.`, toError(error));
146
+ }
147
+ }, [pageSizePt.width, windowWidth]);
148
+ const pageHeight = windowWidth * pageAspect;
149
+
150
+ // Mount and rasterize the current page plus a small window around it;
151
+ // everything else stays an empty placeholder slot until scrolled near.
152
+ useEffect(() => {
153
+ if (pageCount === 0) {
154
+ return;
155
+ }
156
+ const start = Math.max(0, currentPage - RENDER_WINDOW);
157
+ const end = Math.min(pageCount - 1, currentPage + RENDER_WINDOW);
158
+ for (let index = start; index <= end; index += 1) {
159
+ void renderPageImage(index);
160
+ }
161
+ }, [currentPage, pageCount, renderPageImage]);
162
+ const handleScroll = useCallback(event => {
163
+ if (pageHeight <= 0) {
164
+ return;
165
+ }
166
+ const page = Math.min(pageCount - 1, Math.max(0, Math.round(event.nativeEvent.contentOffset.y / pageHeight)));
167
+ setCurrentPage(prev => {
168
+ if (prev !== page) {
169
+ onPageChangedRef.current?.(page + 1, pageCount);
170
+ }
171
+ return page;
172
+ });
173
+ }, [pageCount, pageHeight]);
174
+ const pages = useMemo(() => Array.from({
175
+ length: pageCount
176
+ }, (_unused, i) => i), [pageCount]);
177
+ const renderWindowStart = Math.max(0, currentPage - RENDER_WINDOW);
178
+ const renderWindowEnd = Math.min(pageCount - 1, currentPage + RENDER_WINDOW);
35
179
  return /*#__PURE__*/_jsx(View, {
36
180
  style: [styles.root, style],
37
181
  testID: testID,
38
182
  accessible: accessibilityLabel !== undefined,
39
183
  accessibilityLabel: accessibilityLabel,
40
184
  accessibilityRole: "none",
41
- children: /*#__PURE__*/_jsx(Pdf, {
42
- source: source,
43
- trustAllCerts: trustAllCerts,
44
- style: styles.pdf,
45
- renderActivityIndicator: renderActivityIndicator,
46
- onLoadComplete: handleLoadComplete,
47
- onPageChanged: onPageChanged,
48
- onError: handleError
185
+ children: pageCount === 0 ? /*#__PURE__*/_jsx(View, {
186
+ style: styles.pagePlaceholder,
187
+ children: loadingIndicator ? loadingIndicator() : /*#__PURE__*/_jsx(Spinner, {
188
+ size: "lg"
189
+ })
190
+ }) : /*#__PURE__*/_jsx(ScrollView, {
191
+ onScroll: handleScroll,
192
+ scrollEventThrottle: 32,
193
+ showsVerticalScrollIndicator: true,
194
+ removeClippedSubviews: true,
195
+ children: pages.map(index => {
196
+ const withinWindow = index >= renderWindowStart && index <= renderWindowEnd;
197
+ const uri = withinWindow ? renderedPages[index] : undefined;
198
+ return /*#__PURE__*/_jsx(View, {
199
+ style: [styles.page, {
200
+ width: windowWidth,
201
+ height: pageHeight
202
+ }],
203
+ children: uri ? /*#__PURE__*/_jsx(Image, {
204
+ source: {
205
+ uri
206
+ },
207
+ style: styles.pageImage,
208
+ resizeMode: "contain",
209
+ accessible: false
210
+ }) : /*#__PURE__*/_jsx(View, {
211
+ style: styles.pagePlaceholder,
212
+ children: withinWindow ? loadingIndicator?.() ?? /*#__PURE__*/_jsx(Spinner, {
213
+ size: "lg"
214
+ }) : null
215
+ })
216
+ }, index);
217
+ })
49
218
  })
50
219
  });
51
220
  };
@@ -54,10 +223,16 @@ const styles = StyleSheet.create({
54
223
  root: {
55
224
  flex: 1
56
225
  },
57
- pdf: {
226
+ page: {
227
+ alignSelf: 'center'
228
+ },
229
+ pageImage: {
230
+ flex: 1
231
+ },
232
+ pagePlaceholder: {
58
233
  flex: 1,
59
- width: '100%',
60
- height: '100%'
234
+ alignItems: 'center',
235
+ justifyContent: 'center'
61
236
  }
62
237
  });
63
238
  export default DocumentViewer;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.get('PdfRasterizer');
5
+ //# sourceMappingURL=NativePdfRasterizer.js.map
@@ -3,12 +3,11 @@ import type { StyleProp, ViewStyle } from 'react-native';
3
3
  export interface DocumentViewerSource {
4
4
  uri: string;
5
5
  headers?: Record<string, string>;
6
+ /** Reuse a previously downloaded copy of this URL instead of re-fetching. Default true. */
6
7
  cache?: boolean;
7
8
  }
8
9
  export interface DocumentViewerProps {
9
10
  source: DocumentViewerSource;
10
- /** Trust self-signed / invalid certs on the document request. Default false. */
11
- trustAllCerts?: boolean;
12
11
  onLoadComplete?: (numberOfPages: number) => void;
13
12
  onPageChanged?: (page: number, numberOfPages: number) => void;
14
13
  onError?: (error: Error) => void;
@@ -19,8 +18,17 @@ export interface DocumentViewerProps {
19
18
  accessibilityLabel?: string;
20
19
  }
21
20
  /**
22
- * Renders a PDF only the WebView fallback consumer apps use for non-PDF
23
- * office documents is a distinct rendering strategy and stays app-side.
21
+ * Renders a PDF via our own native rasterizer (PDFKit on iOS, PdfRenderer on
22
+ * Android) no third-party PDF dependency. Pages are paged through a plain
23
+ * `ScrollView` with manual windowed mounting (render the visible page ±
24
+ * `RENDER_WINDOW`), not a `FlatList` — a `VirtualizedList`-based component
25
+ * would trip React Native's "VirtualizedLists should never be nested inside
26
+ * plain ScrollViews" the moment a consumer embeds DocumentViewer in their own
27
+ * scrolling screen, which is a completely ordinary way to use it. Avoiding the
28
+ * VirtualizedList primitive entirely removes that whole class of warning
29
+ * rather than working around one instance of it. The WebView fallback
30
+ * consumer apps use for non-PDF office documents is a distinct rendering
31
+ * strategy and stays app-side.
24
32
  */
25
33
  export declare const DocumentViewer: React.FC<DocumentViewerProps>;
26
34
  export default DocumentViewer;
@@ -0,0 +1,40 @@
1
+ import type { TurboModule } from 'react-native';
2
+ export interface PdfDocumentInfo {
3
+ /** Opaque handle for every subsequent call (getPageInfo/renderPage/close) — NOT the file path. */
4
+ handle: string;
5
+ pageCount: number;
6
+ /** Points (1/72 in), the unrotated page size of page 0 — a per-page size read happens in renderPage. */
7
+ pageWidth: number;
8
+ pageHeight: number;
9
+ }
10
+ export interface PdfPageInfo {
11
+ width: number;
12
+ height: number;
13
+ }
14
+ export interface Spec extends TurboModule {
15
+ /**
16
+ * Opens a LOCAL pdf file (a file:// path or bare absolute path — no http(s), no
17
+ * headers, no auth: callers download remote documents with mobile-core's
18
+ * `downloadFile` first). Returns a handle used by every other call. Throws
19
+ * (rejects) on a missing file, a corrupt PDF, or a password-protected PDF.
20
+ */
21
+ open(path: string): Promise<PdfDocumentInfo>;
22
+ /**
23
+ * Reads one page's own size without rasterizing it (pages in a PDF can each
24
+ * have a different size/rotation).
25
+ */
26
+ getPageInfo(handle: string, pageIndex: number): Promise<PdfPageInfo>;
27
+ /**
28
+ * Rasterizes one page to a PNG file in the cache directory and returns its
29
+ * absolute path. `scale` is a multiplier on the page's own point size (pass
30
+ * the view width / page width ratio, capped by the caller — see
31
+ * react-native.md's "RGBA_8888 is 4 bytes/px" guidance). Renders are
32
+ * serialized per handle on both platforms; call sequentially per document.
33
+ */
34
+ renderPage(handle: string, pageIndex: number, scale: number): Promise<string>;
35
+ /** Releases the native document and evicts any cached page renders for it. */
36
+ close(handle: string): Promise<void>;
37
+ }
38
+ declare const _default: Spec | null;
39
+ export default _default;
40
+ //# sourceMappingURL=NativePdfRasterizer.d.ts.map
@@ -3,12 +3,11 @@ import type { StyleProp, ViewStyle } from 'react-native';
3
3
  export interface DocumentViewerSource {
4
4
  uri: string;
5
5
  headers?: Record<string, string>;
6
+ /** Reuse a previously downloaded copy of this URL instead of re-fetching. Default true. */
6
7
  cache?: boolean;
7
8
  }
8
9
  export interface DocumentViewerProps {
9
10
  source: DocumentViewerSource;
10
- /** Trust self-signed / invalid certs on the document request. Default false. */
11
- trustAllCerts?: boolean;
12
11
  onLoadComplete?: (numberOfPages: number) => void;
13
12
  onPageChanged?: (page: number, numberOfPages: number) => void;
14
13
  onError?: (error: Error) => void;
@@ -19,8 +18,17 @@ export interface DocumentViewerProps {
19
18
  accessibilityLabel?: string;
20
19
  }
21
20
  /**
22
- * Renders a PDF only the WebView fallback consumer apps use for non-PDF
23
- * office documents is a distinct rendering strategy and stays app-side.
21
+ * Renders a PDF via our own native rasterizer (PDFKit on iOS, PdfRenderer on
22
+ * Android) no third-party PDF dependency. Pages are paged through a plain
23
+ * `ScrollView` with manual windowed mounting (render the visible page ±
24
+ * `RENDER_WINDOW`), not a `FlatList` — a `VirtualizedList`-based component
25
+ * would trip React Native's "VirtualizedLists should never be nested inside
26
+ * plain ScrollViews" the moment a consumer embeds DocumentViewer in their own
27
+ * scrolling screen, which is a completely ordinary way to use it. Avoiding the
28
+ * VirtualizedList primitive entirely removes that whole class of warning
29
+ * rather than working around one instance of it. The WebView fallback
30
+ * consumer apps use for non-PDF office documents is a distinct rendering
31
+ * strategy and stays app-side.
24
32
  */
25
33
  export declare const DocumentViewer: React.FC<DocumentViewerProps>;
26
34
  export default DocumentViewer;