react-native-optimized-pdf 1.2.0 → 1.3.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/CHANGELOG.md +24 -1
- package/README.md +27 -3
- package/ios/Core/PDFConfiguration.swift +1 -1
- package/ios/Core/PDFDocumentLoader.swift +7 -4
- package/ios/Core/PDFError.swift +0 -9
- package/ios/ReactNative/OptimizedPDFViewManager.swift +0 -28
- package/ios/ReactNative/OptimizedPdfViewManager.m +0 -11
- package/ios/Views/OptimizedPDFView.swift +7 -47
- package/ios/Views/TiledPDFPageView.swift +7 -40
- package/package.json +9 -4
- package/src/OptimizedPdfView.tsx +1 -5
- package/src/constants.ts +0 -2
- package/src/services/pdfCache.ts +9 -3
- package/src/types/index.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,18 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [1.3.1] - 2026-11-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Local `file://` URIs and absolute paths are returned directly instead of being passed to the HTTP downloader, fixing local PDF opening on Android.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Replaced the abandoned `react-native-fs` with the maintained `@dr.pogodin/react-native-fs` (`^2.41.0`).
|
|
17
|
+
|
|
18
|
+
## [1.3.0] - 2026-08-08
|
|
9
19
|
|
|
10
20
|
### Added
|
|
11
21
|
|
|
12
22
|
- Annotation (annots) rendering support on Android and iOS
|
|
13
23
|
- Tile-based PDF rendering for high-quality zoom (Android)
|
|
24
|
+
- 100% automated test coverage for the TypeScript/JavaScript surface with a hard gate
|
|
25
|
+
- CI workflow (GitHub Actions) running lint, typecheck, and coverage-gated tests on every push/PR
|
|
26
|
+
- Test infrastructure: Jest, React Native Testing Library, and coverage thresholds in `jest.config.js`
|
|
27
|
+
- Unit suites for `PdfCacheService`, `PdfNavigationControls`, `PdfOverlays`, and constants
|
|
28
|
+
- Integration suite for `OptimizedPdfView` covering the load state machine, events, navigation, source changes, and platform fallback
|
|
14
29
|
|
|
15
30
|
### Changed
|
|
16
31
|
|
|
17
32
|
- Android: migrated to `pdfiumandroid` and replaced previous rendering pipeline with tiled rendering
|
|
18
33
|
- iOS: introduced annotation flattening using PDFKit when needed, while keeping CGPDF rendering for performance
|
|
19
34
|
- Updated dependencies and improved TypeScript configuration
|
|
35
|
+
- Removed unused and dead iOS code (delegate protocol, view/manager methods, error cases, imports)
|
|
36
|
+
- Reconciled `PDF`/`Pdf` naming across the iOS Swift class, `@objc` export, Objective-C manager, and JS registration
|
|
37
|
+
- Extracted duplicated `file://` URL handling into a shared iOS helper
|
|
38
|
+
- Removed the unused `PdfSource.method` field (documented as non-breaking: the field was never consumed)
|
|
39
|
+
- Removed unused `ERROR_MESSAGES.LOAD_FAILED` and `INVALID_SOURCE` entries
|
|
40
|
+
- Aligned the iOS `maximumZoom` default with the JS side
|
|
20
41
|
|
|
21
42
|
### Improved
|
|
22
43
|
|
|
@@ -27,3 +48,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
48
|
### Fixed
|
|
28
49
|
|
|
29
50
|
- Annotations not being rendered
|
|
51
|
+
- `_cancelled` reset semantics in `TiledPDFPageView`
|
|
52
|
+
- Duplicated imports across iOS sources
|
package/README.md
CHANGED
|
@@ -41,6 +41,10 @@ cd ios && pod install
|
|
|
41
41
|
|
|
42
42
|
No additional setup required! The module will be automatically linked.
|
|
43
43
|
|
|
44
|
+
### Local files
|
|
45
|
+
|
|
46
|
+
`source.uri` accepts a remote URL (`http://` or `https://`), a `file://` URI, or an absolute path. Local files are opened directly and are not copied to the cache; only remote URLs are downloaded. Remote downloads use the maintained [`@dr.pogodin/react-native-fs`](https://github.com/birdofpreyru/react-native-fs) package, installed automatically as a dependency.
|
|
47
|
+
|
|
44
48
|
## Usage
|
|
45
49
|
|
|
46
50
|
### Basic Example
|
|
@@ -198,8 +202,8 @@ const isValid = await PdfCacheService.isCacheValid({
|
|
|
198
202
|
|
|
199
203
|
| Property | Type | Default | Description |
|
|
200
204
|
| --------------- | ------------------------ | ------------ | ------------------------------------------------------------ |
|
|
201
|
-
| `uri` | `string` | **required** | PDF file URI (remote URL or
|
|
202
|
-
| `cache` | `boolean` | `true` | Enable local caching
|
|
205
|
+
| `uri` | `string` | **required** | PDF file URI (remote URL, `file://` URI, or absolute path) |
|
|
206
|
+
| `cache` | `boolean` | `true` | Enable local caching for remote URLs (ignored for local files) |
|
|
203
207
|
| `cacheFileName` | `string` | MD5 of URI | Custom filename for cached file |
|
|
204
208
|
| `expiration` | `number` | - | Cache expiration in seconds (0 or undefined = no expiration) |
|
|
205
209
|
| `headers` | `Record<string, string>` | - | HTTP headers for download |
|
|
@@ -210,7 +214,7 @@ Static methods for managing PDF cache:
|
|
|
210
214
|
|
|
211
215
|
- `getCacheFilePath(source: PdfSource): string` - Get local cache path
|
|
212
216
|
- `isCacheValid(source: PdfSource): Promise<boolean>` - Check if cache is valid
|
|
213
|
-
- `downloadPdf(source: PdfSource, onProgress?: (percent: number) => void): Promise<string>` - Download and cache PDF
|
|
217
|
+
- `downloadPdf(source: PdfSource, onProgress?: (percent: number) => void): Promise<string>` - Download and cache a remote PDF; local paths are returned directly
|
|
214
218
|
- `clearCache(source: PdfSource): Promise<void>` - Clear specific cached file
|
|
215
219
|
- `clearAllCache(): Promise<void>` - Clear all cached PDFs
|
|
216
220
|
- `getCacheSize(): Promise<number>` - Get total cache size in bytes
|
|
@@ -276,6 +280,26 @@ import { PdfNavigationControls } from 'react-native-optimized-pdf';
|
|
|
276
280
|
- Pass the password via the `password` prop
|
|
277
281
|
- Check for "Invalid password" in error message to show retry prompt
|
|
278
282
|
|
|
283
|
+
## Development
|
|
284
|
+
|
|
285
|
+
### Commands
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Run the test suite
|
|
289
|
+
yarn test
|
|
290
|
+
|
|
291
|
+
# Run tests with coverage (enforces the 100% gate)
|
|
292
|
+
yarn test:coverage
|
|
293
|
+
|
|
294
|
+
# Lint the codebase
|
|
295
|
+
yarn lint
|
|
296
|
+
|
|
297
|
+
# Type-check the codebase
|
|
298
|
+
yarn typecheck
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The test suite enforces **100% coverage** (lines, statements, functions, and branches) on the TypeScript/JavaScript surface (`src/**` and `index.ts`). `yarn test:coverage` exits non-zero if any threshold drops below 100%. The thresholds live in `jest.config.js`, and CI (GitHub Actions) runs `yarn lint`, `yarn typecheck`, and `yarn test:coverage` on every push and pull request, blocking merges when the gate fails.
|
|
302
|
+
|
|
279
303
|
## License
|
|
280
304
|
|
|
281
305
|
MIT
|
|
@@ -3,7 +3,7 @@ import UIKit
|
|
|
3
3
|
/// Configuration options for PDF rendering.
|
|
4
4
|
struct PDFConfiguration {
|
|
5
5
|
/// Maximum zoom scale allowed.
|
|
6
|
-
var maximumZoom: CGFloat =
|
|
6
|
+
var maximumZoom: CGFloat = 3
|
|
7
7
|
|
|
8
8
|
/// Whether to enable antialiasing for smoother rendering.
|
|
9
9
|
var enableAntialiasing: Bool = true
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import CoreGraphics
|
|
3
3
|
|
|
4
|
+
/// Strips the `file://` scheme prefix from a local PDF source string, if present.
|
|
5
|
+
/// Shared by the document loader and the view so URL normalization stays in one place.
|
|
6
|
+
func normalizedPDFPath(from source: String) -> String {
|
|
7
|
+
source.hasPrefix("file://") ? String(source.dropFirst(7)) : source
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
/// Protocol for PDF document loading operations.
|
|
5
11
|
protocol PDFDocumentLoading {
|
|
6
12
|
func loadDocument(from source: String, password: String?) -> Result<CGPDFDocument, PDFError>
|
|
@@ -40,10 +46,7 @@ final class PDFDocumentLoader: PDFDocumentLoading {
|
|
|
40
46
|
|
|
41
47
|
/// Normalizes the source path to a file URL.
|
|
42
48
|
private func normalizedURL(from source: String) -> URL {
|
|
43
|
-
|
|
44
|
-
? String(source.dropFirst(7))
|
|
45
|
-
: source
|
|
46
|
-
return URL(fileURLWithPath: path)
|
|
49
|
+
URL(fileURLWithPath: normalizedPDFPath(from: source))
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/// Attempts to unlock an encrypted PDF document.
|
package/ios/Core/PDFError.swift
CHANGED
|
@@ -3,29 +3,20 @@ import Foundation
|
|
|
3
3
|
/// Errors that can occur during PDF operations.
|
|
4
4
|
enum PDFError: Error, LocalizedError {
|
|
5
5
|
case emptySource
|
|
6
|
-
case invalidPath(String)
|
|
7
6
|
case documentLoadFailed(String)
|
|
8
7
|
case passwordRequired
|
|
9
8
|
case invalidPassword
|
|
10
|
-
case pageNotFound(Int)
|
|
11
|
-
case renderingFailed
|
|
12
9
|
|
|
13
10
|
var errorDescription: String? {
|
|
14
11
|
switch self {
|
|
15
12
|
case .emptySource:
|
|
16
13
|
return "PDF source path is empty"
|
|
17
|
-
case .invalidPath(let path):
|
|
18
|
-
return "Invalid PDF path: \(path)"
|
|
19
14
|
case .documentLoadFailed(let path):
|
|
20
15
|
return "Failed to open PDF at: \(path)"
|
|
21
16
|
case .passwordRequired:
|
|
22
17
|
return "PDF is password protected"
|
|
23
18
|
case .invalidPassword:
|
|
24
19
|
return "Invalid password for PDF"
|
|
25
|
-
case .pageNotFound(let index):
|
|
26
|
-
return "Page not found at index: \(index)"
|
|
27
|
-
case .renderingFailed:
|
|
28
|
-
return "Failed to render PDF page"
|
|
29
20
|
}
|
|
30
21
|
}
|
|
31
22
|
|
|
@@ -17,32 +17,4 @@ final class OptimizedPDFViewManager: RCTViewManager {
|
|
|
17
17
|
override static func requiresMainQueueSetup() -> Bool {
|
|
18
18
|
true
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
// MARK: - Exported Methods
|
|
22
|
-
|
|
23
|
-
/// Navigates to a specific page in the PDF.
|
|
24
|
-
/// - Parameters:
|
|
25
|
-
/// - node: The React Native view tag.
|
|
26
|
-
/// - page: The page index to navigate to (0-based).
|
|
27
|
-
@objc func goToPage(_ node: NSNumber, page: NSNumber) {
|
|
28
|
-
DispatchQueue.main.async { [weak self] in
|
|
29
|
-
guard let self = self,
|
|
30
|
-
let pdfView = self.bridge?.uiManager.view(forReactTag: node) as? OptimizedPDFView else {
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
pdfView.goToPage(page.intValue)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/// Resets the zoom level to fit the page.
|
|
38
|
-
/// - Parameter node: The React Native view tag.
|
|
39
|
-
@objc func resetZoom(_ node: NSNumber) {
|
|
40
|
-
DispatchQueue.main.async { [weak self] in
|
|
41
|
-
guard let self = self,
|
|
42
|
-
let pdfView = self.bridge?.uiManager.view(forReactTag: node) as? OptimizedPDFView else {
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
pdfView.resetZoom()
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
20
|
}
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
#import <React/RCTViewManager.h>
|
|
9
|
-
#import <React/RCTUIManager.h>
|
|
10
|
-
#import <React/RCTBridgeModule.h>
|
|
11
9
|
|
|
12
10
|
@interface RCT_EXTERN_MODULE(OptimizedPdfViewManager, RCTViewManager)
|
|
13
11
|
|
|
@@ -42,13 +40,4 @@ RCT_EXPORT_VIEW_PROPERTY(onPageCount, RCTDirectEventBlock)
|
|
|
42
40
|
/// Called when the PDF requires a password to open.
|
|
43
41
|
RCT_EXPORT_VIEW_PROPERTY(onPasswordRequired, RCTDirectEventBlock)
|
|
44
42
|
|
|
45
|
-
#pragma mark - Native Methods
|
|
46
|
-
|
|
47
|
-
/// Navigates to a specific page in the PDF.
|
|
48
|
-
RCT_EXTERN_METHOD(goToPage:(nonnull NSNumber *)node
|
|
49
|
-
page:(nonnull NSNumber *)page)
|
|
50
|
-
|
|
51
|
-
/// Resets the zoom level to fit the page.
|
|
52
|
-
RCT_EXTERN_METHOD(resetZoom:(nonnull NSNumber *)node)
|
|
53
|
-
|
|
54
43
|
@end
|
|
@@ -2,18 +2,15 @@ import UIKit
|
|
|
2
2
|
import React
|
|
3
3
|
import PDFKit
|
|
4
4
|
|
|
5
|
-
/// Protocol for receiving PDF view events.
|
|
6
|
-
@objc protocol OptimizedPDFViewDelegate: AnyObject {
|
|
7
|
-
@objc optional func pdfView(_ pdfView: OptimizedPDFView, didFailWithError error: Error)
|
|
8
|
-
@objc optional func pdfView(_ pdfView: OptimizedPDFView, didLoadWithPageCount pageCount: Int)
|
|
9
|
-
@objc optional func pdfView(_ pdfView: OptimizedPDFView, didDisplayPage page: Int, size: CGSize)
|
|
10
|
-
@objc optional func pdfViewDidRequestPassword(_ pdfView: OptimizedPDFView)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
5
|
/// A high-performance PDF view using CATiledLayer for smooth zooming and scrolling.
|
|
14
6
|
///
|
|
15
7
|
/// This view is optimized for React Native integration, providing callbacks for
|
|
16
8
|
/// load completion, errors, and page navigation events.
|
|
9
|
+
///
|
|
10
|
+
/// Naming note: the Swift type is `OptimizedPDFView` while its `@objc` export is
|
|
11
|
+
/// `OptimizedPdfView`, and the Objective-C manager/registration is
|
|
12
|
+
/// `OptimizedPdfViewManager`. The `Pdf` spellings are the native bridge contract
|
|
13
|
+
/// with JavaScript and must not be renamed.
|
|
17
14
|
@objc(OptimizedPdfView)
|
|
18
15
|
final class OptimizedPDFView: UIScrollView {
|
|
19
16
|
|
|
@@ -34,7 +31,7 @@ final class OptimizedPDFView: UIScrollView {
|
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
/// Maximum zoom scale.
|
|
37
|
-
@objc var maximumZoom: NSNumber =
|
|
34
|
+
@objc var maximumZoom: NSNumber = 3 {
|
|
38
35
|
didSet {
|
|
39
36
|
configuration.maximumZoom = CGFloat(truncating: maximumZoom)
|
|
40
37
|
applyZoomConfiguration()
|
|
@@ -66,8 +63,6 @@ final class OptimizedPDFView: UIScrollView {
|
|
|
66
63
|
|
|
67
64
|
// MARK: - Public Properties
|
|
68
65
|
|
|
69
|
-
weak var pdfDelegate: OptimizedPDFViewDelegate?
|
|
70
|
-
|
|
71
66
|
/// Current number of pages in the document.
|
|
72
67
|
private(set) var pageCount: Int = 0
|
|
73
68
|
|
|
@@ -83,7 +78,6 @@ final class OptimizedPDFView: UIScrollView {
|
|
|
83
78
|
private var needsLoad = false
|
|
84
79
|
private var lastBoundsSize: CGSize = .zero
|
|
85
80
|
private var currentPageRect: CGRect?
|
|
86
|
-
private var initialZoomScale: CGFloat = 1.0
|
|
87
81
|
|
|
88
82
|
// MARK: - Initialization
|
|
89
83
|
|
|
@@ -101,14 +95,6 @@ final class OptimizedPDFView: UIScrollView {
|
|
|
101
95
|
setupView()
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
/// Initializes with a custom document loader (useful for testing).
|
|
105
|
-
init(frame: CGRect, documentLoader: PDFDocumentLoading) {
|
|
106
|
-
self.tiledPageView = TiledPDFPageView(frame: frame)
|
|
107
|
-
self.documentLoader = documentLoader
|
|
108
|
-
super.init(frame: frame)
|
|
109
|
-
setupView()
|
|
110
|
-
}
|
|
111
|
-
|
|
112
98
|
// MARK: - Setup
|
|
113
99
|
|
|
114
100
|
private func setupView() {
|
|
@@ -137,7 +123,6 @@ final class OptimizedPDFView: UIScrollView {
|
|
|
137
123
|
|
|
138
124
|
let fitScale = calculateFitScale(for: pageRect)
|
|
139
125
|
minimumZoomScale = fitScale
|
|
140
|
-
initialZoomScale = fitScale
|
|
141
126
|
}
|
|
142
127
|
}
|
|
143
128
|
|
|
@@ -177,7 +162,7 @@ private extension OptimizedPDFView {
|
|
|
177
162
|
func loadDocument() {
|
|
178
163
|
guard !source.isEmpty else { return }
|
|
179
164
|
|
|
180
|
-
let path =
|
|
165
|
+
let path = normalizedPDFPath(from: source)
|
|
181
166
|
pdfURL = URL(fileURLWithPath: path)
|
|
182
167
|
|
|
183
168
|
let result = documentLoader.loadDocument(from: source, password: configuration.password)
|
|
@@ -199,7 +184,6 @@ private extension OptimizedPDFView {
|
|
|
199
184
|
DispatchQueue.main.async { [weak self] in
|
|
200
185
|
guard let self = self else { return }
|
|
201
186
|
self.onPageCount?(["numberOfPages": NSNumber(value: self.pageCount)])
|
|
202
|
-
self.pdfDelegate?.pdfView?(self, didLoadWithPageCount: self.pageCount)
|
|
203
187
|
}
|
|
204
188
|
|
|
205
189
|
// Display pending or first page
|
|
@@ -214,12 +198,10 @@ private extension OptimizedPDFView {
|
|
|
214
198
|
DispatchQueue.main.async { [weak self] in
|
|
215
199
|
guard let self = self else { return }
|
|
216
200
|
self.onPasswordRequired?([:])
|
|
217
|
-
self.pdfDelegate?.pdfViewDidRequestPassword?(self)
|
|
218
201
|
}
|
|
219
202
|
}
|
|
220
203
|
|
|
221
204
|
onError?(error.eventPayload)
|
|
222
|
-
pdfDelegate?.pdfView?(self, didFailWithError: error)
|
|
223
205
|
}
|
|
224
206
|
|
|
225
207
|
func clampPageIndex(_ index: Int, for document: CGPDFDocument) -> Int {
|
|
@@ -270,7 +252,6 @@ private extension OptimizedPDFView {
|
|
|
270
252
|
func applyFitScale(for pageRect: CGRect) {
|
|
271
253
|
let fitScale = calculateFitScale(for: pageRect)
|
|
272
254
|
minimumZoomScale = fitScale
|
|
273
|
-
initialZoomScale = fitScale
|
|
274
255
|
setZoomScale(fitScale, animated: false)
|
|
275
256
|
}
|
|
276
257
|
|
|
@@ -284,7 +265,6 @@ private extension OptimizedPDFView {
|
|
|
284
265
|
DispatchQueue.main.async { [weak self] in
|
|
285
266
|
guard let self = self else { return }
|
|
286
267
|
self.onLoadComplete?(payload)
|
|
287
|
-
self.pdfDelegate?.pdfView?(self, didDisplayPage: index + 1, size: size)
|
|
288
268
|
}
|
|
289
269
|
}
|
|
290
270
|
}
|
|
@@ -379,23 +359,3 @@ extension OptimizedPDFView {
|
|
|
379
359
|
centerContent()
|
|
380
360
|
}
|
|
381
361
|
}
|
|
382
|
-
|
|
383
|
-
// MARK: - Public Methods
|
|
384
|
-
|
|
385
|
-
extension OptimizedPDFView {
|
|
386
|
-
|
|
387
|
-
/// Resets the zoom to fit the page.
|
|
388
|
-
@objc func resetZoom() {
|
|
389
|
-
UIView.animate(withDuration: configuration.zoomResetAnimationDuration) { [weak self] in
|
|
390
|
-
guard let self = self else { return }
|
|
391
|
-
self.setZoomScale(self.initialZoomScale, animated: false)
|
|
392
|
-
self.centerContent()
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/// Navigates to a specific page.
|
|
397
|
-
/// - Parameter index: The page index (0-based).
|
|
398
|
-
@objc func goToPage(_ index: Int) {
|
|
399
|
-
page = NSNumber(value: index)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
@@ -2,10 +2,6 @@ import UIKit
|
|
|
2
2
|
import CoreGraphics
|
|
3
3
|
import PDFKit
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import UIKit
|
|
7
|
-
import CoreGraphics
|
|
8
|
-
|
|
9
5
|
/// A view that renders a PDF page using CATiledLayer for efficient tiled rendering.
|
|
10
6
|
///
|
|
11
7
|
/// CATiledLayer divides the content into tiles, allowing smooth zooming and scrolling
|
|
@@ -29,10 +25,6 @@ final class FlattenedPageCache {
|
|
|
29
25
|
cache.setObject(entry, forKey: key as NSString)
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
func removeEntry(forKey key: String) {
|
|
33
|
-
cache.removeObject(forKey: key as NSString)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
28
|
func removeAll() {
|
|
37
29
|
cache.removeAllObjects()
|
|
38
30
|
}
|
|
@@ -60,6 +52,12 @@ final class OSAtomicFlag {
|
|
|
60
52
|
os_unfair_lock_unlock(&_lock)
|
|
61
53
|
}
|
|
62
54
|
|
|
55
|
+
func reset() {
|
|
56
|
+
os_unfair_lock_lock(&_lock)
|
|
57
|
+
_value = false
|
|
58
|
+
os_unfair_lock_unlock(&_lock)
|
|
59
|
+
}
|
|
60
|
+
|
|
63
61
|
func isSet() -> Bool {
|
|
64
62
|
os_unfair_lock_lock(&_lock)
|
|
65
63
|
let v = _value
|
|
@@ -145,6 +143,7 @@ final class TiledPDFPageView: UIView {
|
|
|
145
143
|
// MARK: - Prepare Page (Flatten Pipeline)
|
|
146
144
|
|
|
147
145
|
func preparePage() {
|
|
146
|
+
_cancelled.reset()
|
|
148
147
|
_generation &+= 1
|
|
149
148
|
let currentGen = _generation
|
|
150
149
|
|
|
@@ -278,35 +277,3 @@ final class TiledPDFPageView: UIView {
|
|
|
278
277
|
tiledLayer.levelsOfDetail == 1 ? .low : .high
|
|
279
278
|
}
|
|
280
279
|
}
|
|
281
|
-
|
|
282
|
-
// MARK: - Public Methods
|
|
283
|
-
|
|
284
|
-
extension TiledPDFPageView {
|
|
285
|
-
|
|
286
|
-
func prepareForReuse() {
|
|
287
|
-
pdfPage = nil
|
|
288
|
-
_activePage = nil
|
|
289
|
-
layer.contents = nil
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/// Updates the view size to match the page dimensions.
|
|
293
|
-
func updateFrame(for page: CGPDFPage) {
|
|
294
|
-
let pageRect = page.getBoxRect(.cropBox)
|
|
295
|
-
frame = CGRect(origin: .zero, size: pageRect.size)
|
|
296
|
-
pdfPage = page
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
func invalidateCache() {
|
|
300
|
-
FlattenedPageCache.shared.removeEntry(forKey: currentCacheKey)
|
|
301
|
-
_activePage = pdfPage
|
|
302
|
-
_pdfKitDocument = nil
|
|
303
|
-
setNeedsDisplay()
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
func invalidateAllCache() {
|
|
307
|
-
FlattenedPageCache.shared.removeAll()
|
|
308
|
-
_pdfKitDocument = nil
|
|
309
|
-
_activePage = pdfPage
|
|
310
|
-
setNeedsDisplay()
|
|
311
|
-
}
|
|
312
|
-
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-optimized-pdf",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "React Native PDF viewer with high performance, zoom, and support for large PDF files. Fast, memory-efficient, and supports annotations (annots) on iOS and Android.",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "jest",
|
|
9
|
+
"test:coverage": "jest --coverage --coverageReporters=text",
|
|
9
10
|
"lint": "eslint . --ext .ts,.tsx",
|
|
10
11
|
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
|
11
12
|
"format": "prettier --write \"**/*.{ts,tsx,js,json,md}\"",
|
|
@@ -44,7 +45,9 @@
|
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@react-native-community/eslint-config": "^3.2.0",
|
|
48
|
+
"@testing-library/react-native": "^13.3.1",
|
|
47
49
|
"@types/crypto-js": "^4.2.2",
|
|
50
|
+
"@types/jest": "^29.5.14",
|
|
48
51
|
"@types/react": "^18.2.6",
|
|
49
52
|
"@types/react-native": "^0.64.19",
|
|
50
53
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
@@ -53,9 +56,11 @@
|
|
|
53
56
|
"eslint-config-prettier": "^10.1.8",
|
|
54
57
|
"eslint-plugin-jest": "^29.11.0",
|
|
55
58
|
"eslint-plugin-prettier": "^5.5.4",
|
|
59
|
+
"jest": "^29.7.0",
|
|
56
60
|
"prettier": "2.8.8",
|
|
57
61
|
"react": "18.2.0",
|
|
58
62
|
"react-native": "0.73.11",
|
|
63
|
+
"react-test-renderer": "18.2.0",
|
|
59
64
|
"typescript": "^5.0.0"
|
|
60
65
|
},
|
|
61
66
|
"repository": {
|
|
@@ -63,7 +68,7 @@
|
|
|
63
68
|
"url": "https://github.com/Herbeth-LKS/react-native-optimized-pdf"
|
|
64
69
|
},
|
|
65
70
|
"dependencies": {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
71
|
+
"@dr.pogodin/react-native-fs": "^2.41.0",
|
|
72
|
+
"crypto-js": "^4.2.0"
|
|
68
73
|
}
|
|
69
74
|
}
|
package/src/OptimizedPdfView.tsx
CHANGED
|
@@ -141,14 +141,10 @@ export default function OptimizedPdfView({
|
|
|
141
141
|
return <PdfErrorOverlay error={error} style={style} />;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (!localPath) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
144
|
return (
|
|
149
145
|
<View style={[{ flex: 1 }, style]}>
|
|
150
146
|
<NativeOptimizedPdfView
|
|
151
|
-
source={localPath}
|
|
147
|
+
source={localPath as string}
|
|
152
148
|
page={showNavigationControls ? internalPage : page || 0}
|
|
153
149
|
enableAntialiasing={enableAntialiasing}
|
|
154
150
|
maximumZoom={maximumZoom}
|
package/src/constants.ts
CHANGED
package/src/services/pdfCache.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import RNFS from 'react-native-fs';
|
|
1
|
+
import * as RNFS from '@dr.pogodin/react-native-fs';
|
|
2
2
|
import md5 from 'crypto-js/md5';
|
|
3
3
|
import type { PdfSource } from '../types';
|
|
4
4
|
|
|
@@ -33,11 +33,12 @@ export class PdfCacheService {
|
|
|
33
33
|
// Check if cache has expired
|
|
34
34
|
const stat = await RNFS.stat(localPath);
|
|
35
35
|
const now = Date.now() / 1000;
|
|
36
|
-
|
|
36
|
+
const modifiedAt = stat.mtime.getTime() / 1000;
|
|
37
|
+
return now - modifiedAt < source.expiration;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Download a PDF file and cache it locally
|
|
41
|
+
* Download a PDF file and cache it locally, or return a local file directly
|
|
41
42
|
* @param source PDF source configuration
|
|
42
43
|
* @param onProgress Optional callback for download progress
|
|
43
44
|
* @returns Local file path
|
|
@@ -46,6 +47,11 @@ export class PdfCacheService {
|
|
|
46
47
|
source: PdfSource,
|
|
47
48
|
onProgress?: (percent: number) => void,
|
|
48
49
|
): Promise<string> {
|
|
50
|
+
// Local files are already readable by the native viewer.
|
|
51
|
+
if (source.uri.startsWith('file://') || source.uri.startsWith('/')) {
|
|
52
|
+
return source.uri;
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
const localPath = this.getCacheFilePath(source);
|
|
50
56
|
|
|
51
57
|
// Check if we can use cached version
|
package/src/types/index.ts
CHANGED
|
@@ -12,8 +12,6 @@ export interface PdfSource {
|
|
|
12
12
|
cacheFileName?: string;
|
|
13
13
|
/** Cache expiration time in seconds. If not set, cache never expires */
|
|
14
14
|
expiration?: number;
|
|
15
|
-
/** HTTP method for download. Default: 'GET' */
|
|
16
|
-
method?: string;
|
|
17
15
|
/** HTTP headers for download request */
|
|
18
16
|
headers?: Record<string, string>;
|
|
19
17
|
}
|