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 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
- ## [Unreleased]
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 local path) |
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 = 5.0
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
- let path = source.hasPrefix("file://")
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.
@@ -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 = 5.0 {
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 = source.hasPrefix("file://") ? String(source.dropFirst(7)) : source
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.2.0",
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": "echo \"Error: no test specified\" && exit 1",
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
- "crypto-js": "^4.2.0",
67
- "react-native-fs": "^2.20.0"
71
+ "@dr.pogodin/react-native-fs": "^2.41.0",
72
+ "crypto-js": "^4.2.0"
68
73
  }
69
74
  }
@@ -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
@@ -8,6 +8,4 @@ export const DEFAULT_SHOW_NAVIGATION_CONTROLS = true;
8
8
 
9
9
  export const ERROR_MESSAGES = {
10
10
  DOWNLOAD_FAILED: 'Failed to download PDF',
11
- LOAD_FAILED: 'Failed to load PDF',
12
- INVALID_SOURCE: 'Invalid PDF source',
13
11
  } as const;
@@ -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
- return now - stat.mtime < source.expiration;
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
@@ -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
  }