@thatkid02/react-native-pdf-viewer 0.0.1 → 0.0.3

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.
Files changed (24) hide show
  1. package/README.md +40 -10
  2. package/android/src/main/java/com/margelo/nitro/pdfviewer/HybridPdfViewer.kt +24 -0
  3. package/android/src/main/java/com/margelo/nitro/pdfviewer/PdfViewer.kt +187 -14
  4. package/ios/PdfViewer.swift +107 -18
  5. package/lib/module/index.js.map +1 -1
  6. package/lib/typescript/src/PdfViewer.nitro.d.ts +19 -0
  7. package/lib/typescript/src/PdfViewer.nitro.d.ts.map +1 -1
  8. package/lib/typescript/src/index.d.ts.map +1 -1
  9. package/nitrogen/generated/android/c++/JHybridPdfViewerSpec.cpp +36 -0
  10. package/nitrogen/generated/android/c++/JHybridPdfViewerSpec.hpp +8 -0
  11. package/nitrogen/generated/android/c++/views/JHybridPdfViewerStateUpdater.cpp +16 -0
  12. package/nitrogen/generated/android/kotlin/com/margelo/nitro/pdfviewer/HybridPdfViewerSpec.kt +24 -0
  13. package/nitrogen/generated/ios/c++/HybridPdfViewerSpecSwift.hpp +28 -0
  14. package/nitrogen/generated/ios/c++/views/HybridPdfViewerComponent.mm +20 -0
  15. package/nitrogen/generated/ios/swift/HybridPdfViewerSpec.swift +4 -0
  16. package/nitrogen/generated/ios/swift/HybridPdfViewerSpec_cxx.swift +68 -0
  17. package/nitrogen/generated/shared/c++/HybridPdfViewerSpec.cpp +8 -0
  18. package/nitrogen/generated/shared/c++/HybridPdfViewerSpec.hpp +8 -0
  19. package/nitrogen/generated/shared/c++/views/HybridPdfViewerComponent.cpp +48 -0
  20. package/nitrogen/generated/shared/c++/views/HybridPdfViewerComponent.hpp +4 -0
  21. package/nitrogen/generated/shared/json/PdfViewerConfig.json +4 -0
  22. package/package.json +1 -1
  23. package/src/PdfViewer.nitro.ts +21 -0
  24. package/src/index.tsx +2 -1
package/README.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # react-native-pdf-viewer
2
2
 
3
+ [![npm version](https://badge.fury.io/js/@thatkid02%2Freact-native-pdf-viewer.svg)](https://www.npmjs.com/package/@thatkid02/react-native-pdf-viewer)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@thatkid02/react-native-pdf-viewer.svg)](https://www.npmjs.com/package/@thatkid02/react-native-pdf-viewer)
5
+
3
6
  High-performance PDF viewer for React Native, built with [Nitro Modules](https://nitro.margelo.com/) for native rendering performance.
4
7
 
8
+ <video src="docs/pdf.mp4" controls></video>
9
+
5
10
  ## Features
6
11
 
7
12
  ✅ **High-performance PDF rendering** with native APIs (PDFKit on iOS, PdfRenderer on Android)
@@ -28,7 +33,7 @@ High-performance PDF viewer for React Native, built with [Nitro Modules](https:/
28
33
  ## Installation
29
34
 
30
35
  ```sh
31
- npm install react-native-pdf-viewer react-native-nitro-modules
36
+ npm install @thatkid02/react-native-pdf-viewer react-native-nitro-modules
32
37
  ```
33
38
 
34
39
  > `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/).
@@ -38,7 +43,7 @@ npm install react-native-pdf-viewer react-native-nitro-modules
38
43
  ### Basic Example
39
44
 
40
45
  ```tsx
41
- import { PdfViewerView } from 'react-native-pdf-viewer';
46
+ import { PdfViewerView } from '@thatkid02/react-native-pdf-viewer';
42
47
 
43
48
  export default function App() {
44
49
  return (
@@ -58,10 +63,12 @@ export default function App() {
58
63
 
59
64
  ### Advanced Example with All Features
60
65
 
66
+ - callback is exposed by nitro modules to avoid re-renders [here](https://nitro.margelo.com/docs/view-components#callbacks-have-to-be-wrapped)
67
+
61
68
  ```tsx
62
69
  import React, { useRef } from 'react';
63
70
  import { View, Button, StyleSheet } from 'react-native';
64
- import { PdfViewerView, type PdfViewer } from 'react-native-pdf-viewer';
71
+ import { PdfViewerView, type PdfViewer } from '@thatkid02/react-native-pdf-viewer';
65
72
 
66
73
  export default function AdvancedPdfViewer() {
67
74
  const pdfRef = useRef<PdfViewer>(null);
@@ -94,10 +101,11 @@ export default function AdvancedPdfViewer() {
94
101
  pdfRef.current?.setScale(2.0);
95
102
  };
96
103
 
104
+
97
105
  return (
98
106
  <View style={styles.container}>
99
107
  <PdfViewerView
100
- ref={pdfRef}
108
+ hybridRef={callback((ref: PdfViewerRef | null) => {
101
109
  source="https://example.com/document.pdf"
102
110
  style={styles.pdf}
103
111
  // Layout options
@@ -111,12 +119,12 @@ export default function AdvancedPdfViewer() {
111
119
  // Loading indicator
112
120
  showsActivityIndicator={true}
113
121
  // Event handlers
114
- onLoadComplete={handleLoadComplete}
115
- onPageChange={handlePageChange}
116
- onScaleChange={handleScaleChange}
117
- onThumbnailGenerated={handleThumbnailGenerated}
118
- onError={(event) => console.error(event.message)}
119
- onLoadingChange={(event) => console.log('Loading:', event.isLoading)}
122
+ onLoadComplete={callback(handleLoadComplete)}
123
+ onPageChange={callback(handlePageChange)}
124
+ onScaleChange={callback(handleScaleChange)}
125
+ onError={callback(handleError)}
126
+ onThumbnailGenerated={callback(handleThumbnailGenerated)}
127
+ onLoadingChange={callback(handleLoadingChange)}
120
128
  />
121
129
 
122
130
  <View style={styles.controls}>
@@ -183,6 +191,10 @@ const styles = StyleSheet.create({
183
191
  | `showsActivityIndicator` | `boolean` | `true` | Show loading indicator |
184
192
  | `horizontal` | `boolean` | `false` | Horizontal scroll (iOS only) |
185
193
  | `enablePaging` | `boolean` | `false` | Enable paging mode (iOS only) |
194
+ | `contentInsetTop` | `number` | `0` | Top content inset (for glass topbars) |
195
+ | `contentInsetBottom` | `number` | `0` | Bottom content inset (for glass toolbars) |
196
+ | `contentInsetLeft` | `number` | `0` | Left content inset |
197
+ | `contentInsetRight` | `number` | `0` | Right content inset |
186
198
  | `onLoadComplete` | `(event) => void` | - | Called when PDF loads |
187
199
  | `onPageChange` | `(event) => void` | - | Called when page changes |
188
200
  | `onScaleChange` | `(event) => void` | - | Called when zoom changes |
@@ -190,6 +202,24 @@ const styles = StyleSheet.create({
190
202
  | `onThumbnailGenerated` | `(event) => void` | - | Called when thumbnail is ready |
191
203
  | `onLoadingChange` | `(event) => void` | - | Called when loading state changes |
192
204
 
205
+ #### Glass UI / Transparent Bars
206
+
207
+ Use `contentInset` props to make the PDF scroll behind transparent headers and toolbars:
208
+
209
+ ```tsx
210
+ <PdfViewerView
211
+ source="https://example.com/document.pdf"
212
+ contentInsetTop={80} // Height of your transparent top bar
213
+ contentInsetBottom={60} // Height of your transparent bottom toolbar
214
+ style={{ flex: 1 }}
215
+ />
216
+ ```
217
+
218
+ This creates a modern "glass" effect where:
219
+ - PDF content starts below the top bar
220
+ - Content scrolls behind transparent bars
221
+ - Content ends above the bottom toolbar
222
+
193
223
  ### Methods
194
224
 
195
225
  Access methods via ref:
@@ -138,6 +138,30 @@ class HybridPdfViewer(private val reactContext: ThemedReactContext) : HybridPdfV
138
138
  value?.let { documentViewer.showsActivityIndicator = it }
139
139
  }
140
140
 
141
+ override var contentInsetTop: Double? = null
142
+ set(value) {
143
+ field = value
144
+ value?.let { documentViewer.contentInsetTop = it.toFloat() }
145
+ }
146
+
147
+ override var contentInsetBottom: Double? = null
148
+ set(value) {
149
+ field = value
150
+ value?.let { documentViewer.contentInsetBottom = it.toFloat() }
151
+ }
152
+
153
+ override var contentInsetLeft: Double? = null
154
+ set(value) {
155
+ field = value
156
+ value?.let { documentViewer.contentInsetLeft = it.toFloat() }
157
+ }
158
+
159
+ override var contentInsetRight: Double? = null
160
+ set(value) {
161
+ field = value
162
+ value?.let { documentViewer.contentInsetRight = it.toFloat() }
163
+ }
164
+
141
165
  // Event callbacks - these are set by Nitro
142
166
  override var onLoadComplete: ((event: LoadCompleteEvent) -> Unit)? = null
143
167
  override var onPageChange: ((event: PageChangeEvent) -> Unit)? = null
@@ -102,6 +102,28 @@ class PdfViewer(context: Context) : FrameLayout(context) {
102
102
  var minScale: Float = 0.5f
103
103
  var maxScale: Float = 4.0f
104
104
 
105
+ // Content insets for glass UI / transparent bars
106
+ var contentInsetTop: Float = 0f
107
+ set(value) {
108
+ field = value
109
+ updateContentInsets()
110
+ }
111
+ var contentInsetBottom: Float = 0f
112
+ set(value) {
113
+ field = value
114
+ updateContentInsets()
115
+ }
116
+ var contentInsetLeft: Float = 0f
117
+ set(value) {
118
+ field = value
119
+ updateContentInsets()
120
+ }
121
+ var contentInsetRight: Float = 0f
122
+ set(value) {
123
+ field = value
124
+ updateContentInsets()
125
+ }
126
+
105
127
  // Callbacks for HybridPdfViewer integration
106
128
  var onLoadCompleteCallback: ((pageCount: Int, pageWidth: Int, pageHeight: Int) -> Unit)? = null
107
129
  var onPageChangeCallback: ((page: Int, pageCount: Int) -> Unit)? = null
@@ -125,6 +147,9 @@ class PdfViewer(context: Context) : FrameLayout(context) {
125
147
 
126
148
  // Allow parent to intercept touch events for pinch-to-zoom
127
149
  requestDisallowInterceptTouchEvent(false)
150
+
151
+ // Enable drawing content under padding (for glass UI effect)
152
+ clipToPadding = false
128
153
  }
129
154
 
130
155
  loadingIndicator = ProgressBar(context).apply {
@@ -191,6 +216,17 @@ class PdfViewer(context: Context) : FrameLayout(context) {
191
216
  })
192
217
  }
193
218
 
219
+ private fun updateContentInsets() {
220
+ // Apply content insets as padding
221
+ // clipToPadding=false allows content to draw under padding (glass UI effect)
222
+ recyclerView.setPadding(
223
+ contentInsetLeft.toInt(),
224
+ contentInsetTop.toInt(),
225
+ contentInsetRight.toInt(),
226
+ contentInsetBottom.toInt()
227
+ )
228
+ }
229
+
194
230
  private fun setupRecyclerView() {
195
231
  // Android always uses vertical scroll (horizontal is iOS-only)
196
232
  val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
@@ -329,12 +365,7 @@ class PdfViewer(context: Context) : FrameLayout(context) {
329
365
  pendingThumbnails.clear()
330
366
  lastReportedPage = -1
331
367
 
332
- // Preload page dimensions in background
333
- componentScope.launch(Dispatchers.IO) {
334
- preloadPageDimensions(renderer)
335
- }
336
-
337
- // Get first page dimensions immediately
368
+ // Get first page dimensions immediately for initial render
338
369
  val firstDim = try {
339
370
  renderMutex.withLock {
340
371
  renderer.openPage(0).use { page ->
@@ -348,6 +379,12 @@ class PdfViewer(context: Context) : FrameLayout(context) {
348
379
  Pair(612, 792)
349
380
  }
350
381
 
382
+ // Preload a few more page dimensions in background (non-blocking)
383
+ // This helps with initial scrolling but doesn't delay the initial render
384
+ componentScope.launch(Dispatchers.IO) {
385
+ preloadInitialPageDimensions(renderer)
386
+ }
387
+
351
388
  adapter = PdfPageAdapter()
352
389
  recyclerView.adapter = adapter
353
390
 
@@ -359,6 +396,13 @@ class PdfViewer(context: Context) : FrameLayout(context) {
359
396
  }
360
397
  emitLoadComplete(renderer.pageCount, firstDim.first, firstDim.second)
361
398
 
399
+ // Force initial layout and render by scrolling slightly
400
+ // This ensures content appears without requiring user interaction
401
+ post {
402
+ recyclerView.scrollBy(0, 2)
403
+ recyclerView.scrollBy(0, -2)
404
+ }
405
+
362
406
  schedulePreload()
363
407
  }
364
408
  } catch (e: SecurityException) {
@@ -386,18 +430,67 @@ class PdfViewer(context: Context) : FrameLayout(context) {
386
430
  }
387
431
  }
388
432
 
389
- private suspend fun preloadPageDimensions(renderer: PdfRenderer) {
390
- for (i in 0 until renderer.pageCount) {
433
+ // Preload only first few pages (non-blocking) for better initial experience
434
+ private suspend fun preloadInitialPageDimensions(renderer: PdfRenderer) {
435
+ // Preload first 5-10 pages to improve initial scrolling
436
+ val pagesToPreload = minOf(10, renderer.pageCount)
437
+ for (i in 1 until pagesToPreload) { // Start from 1 since 0 is already loaded
391
438
  try {
439
+ kotlinx.coroutines.delay(50) // Small delay to not block other operations
392
440
  renderMutex.withLock {
393
- renderer.openPage(i).use { page ->
394
- pageDimensions[i] = Pair(page.width, page.height)
441
+ if (pageDimensions[i] == null) { // Only if not already loaded
442
+ renderer.openPage(i).use { page ->
443
+ pageDimensions[i] = Pair(page.width, page.height)
444
+ }
395
445
  }
396
446
  }
397
447
  } catch (e: Exception) {
398
448
  Log.e(TAG, "Error preloading page $i dimensions", e)
399
449
  }
400
450
  }
451
+
452
+ // Lazily load remaining pages with lower priority
453
+ if (renderer.pageCount > pagesToPreload) {
454
+ for (i in pagesToPreload until renderer.pageCount) {
455
+ try {
456
+ kotlinx.coroutines.delay(100) // Longer delay for non-critical pages
457
+ renderMutex.withLock {
458
+ if (pageDimensions[i] == null) {
459
+ renderer.openPage(i).use { page ->
460
+ pageDimensions[i] = Pair(page.width, page.height)
461
+ }
462
+ }
463
+ }
464
+ } catch (e: Exception) {
465
+ Log.e(TAG, "Error preloading page $i dimensions", e)
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ // Get page dimensions on-demand if not already cached
472
+ private suspend fun getPageDimensions(pageIndex: Int): Pair<Int, Int> {
473
+ // Return cached if available
474
+ pageDimensions[pageIndex]?.let { return it }
475
+
476
+ // Load on-demand
477
+ val renderer = pdfRenderer ?: return Pair(612, 792) // Standard page size fallback
478
+
479
+ return try {
480
+ renderMutex.withLock {
481
+ // Check again inside lock in case another thread loaded it
482
+ pageDimensions[pageIndex] ?: run {
483
+ renderer.openPage(pageIndex).use { page ->
484
+ Pair(page.width, page.height).also {
485
+ pageDimensions[pageIndex] = it
486
+ }
487
+ }
488
+ }
489
+ }
490
+ } catch (e: Exception) {
491
+ Log.e(TAG, "Error loading page $pageIndex dimensions", e)
492
+ Pair(612, 792) // Fallback
493
+ }
401
494
  }
402
495
 
403
496
  private fun closePdfRenderer() {
@@ -445,18 +538,81 @@ class PdfViewer(context: Context) : FrameLayout(context) {
445
538
  uri.startsWith("http://") || uri.startsWith("https://") -> {
446
539
  val hash = uri.hashCode().toString()
447
540
  val file = File(context.cacheDir, "pdf_$hash.pdf")
541
+ val tempFile = File(context.cacheDir, "pdf_${hash}_temp.pdf")
542
+
543
+ // Check if cached file exists and is reasonably fresh (< 1 hour)
544
+ if (file.exists() && (System.currentTimeMillis() - file.lastModified() < 3600_000)) {
545
+ if (BuildConfig.DEBUG) {
546
+ Log.d(TAG, "Using cached PDF file: ${file.absolutePath}")
547
+ }
548
+ return@withContext file
549
+ }
448
550
 
449
- if (!file.exists()) {
450
- val connection = URL(uri).openConnection().apply {
551
+ // Download with better error handling and progress
552
+ try {
553
+ val url = URL(uri)
554
+ val connection = url.openConnection().apply {
451
555
  connectTimeout = 30_000 // 30 seconds
452
556
  readTimeout = 60_000 // 60 seconds
557
+ setRequestProperty("Accept", "application/pdf")
558
+
559
+ // Add cache headers for better HTTP caching
560
+ if (file.exists()) {
561
+ setRequestProperty("If-Modified-Since",
562
+ java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", java.util.Locale.US)
563
+ .apply { timeZone = java.util.TimeZone.getTimeZone("GMT") }
564
+ .format(java.util.Date(file.lastModified())))
565
+ }
453
566
  }
567
+
568
+ val contentLength = connection.contentLength
569
+ if (BuildConfig.DEBUG) {
570
+ Log.d(TAG, "Downloading PDF: $uri, size: $contentLength bytes")
571
+ }
572
+
454
573
  connection.getInputStream().use { input ->
455
- FileOutputStream(file).use { output ->
456
- input.copyTo(output)
574
+ FileOutputStream(tempFile).use { output ->
575
+ val buffer = ByteArray(8192)
576
+ var bytesRead: Int
577
+ var totalRead = 0
578
+
579
+ while (input.read(buffer).also { bytesRead = it } != -1) {
580
+ output.write(buffer, 0, bytesRead)
581
+ totalRead += bytesRead
582
+
583
+ // Log progress for large files
584
+ if (BuildConfig.DEBUG && contentLength > 0 && totalRead % (contentLength / 10 + 1) == 0) {
585
+ val progress = (totalRead * 100) / contentLength
586
+ Log.d(TAG, "Download progress: $progress%")
587
+ }
588
+ }
589
+
590
+ output.flush()
457
591
  }
458
592
  }
593
+
594
+ // Move temp file to final location
595
+ if (tempFile.exists()) {
596
+ file.delete() // Remove old cached file
597
+ tempFile.renameTo(file)
598
+ }
599
+
600
+ if (BuildConfig.DEBUG) {
601
+ Log.d(TAG, "Download completed: ${file.absolutePath}")
602
+ }
603
+ } catch (e: Exception) {
604
+ // Clean up temp file on error
605
+ tempFile.delete()
606
+
607
+ // If download failed but we have an old cached version, use it
608
+ if (file.exists()) {
609
+ Log.w(TAG, "Download failed, using cached version: ${e.message}")
610
+ return@withContext file
611
+ }
612
+
613
+ throw e
459
614
  }
615
+
460
616
  file
461
617
  }
462
618
  else -> File(uri)
@@ -850,6 +1006,23 @@ class PdfViewer(context: Context) : FrameLayout(context) {
850
1006
  val scale = (viewWidth.toFloat() / dimensions.first) * currentScale
851
1007
  val targetHeight = (dimensions.second * scale).toInt().coerceAtLeast(100)
852
1008
  holder.imageView.layoutParams.height = targetHeight
1009
+ } else {
1010
+ // Dimensions not loaded yet, use default height and trigger lazy load
1011
+ holder.imageView.layoutParams.height = (viewWidth * 1.414f).toInt().coerceAtLeast(100)
1012
+
1013
+ // Trigger lazy loading of dimensions in background
1014
+ componentScope.launch(Dispatchers.IO) {
1015
+ val dims = getPageDimensions(position)
1016
+ withContext(Dispatchers.Main) {
1017
+ // Update the view if it's still showing this position
1018
+ if (holder.bindingAdapterPosition == position) {
1019
+ val scale = (viewWidth.toFloat() / dims.first) * currentScale
1020
+ val targetHeight = (dims.second * scale).toInt().coerceAtLeast(100)
1021
+ holder.imageView.layoutParams.height = targetHeight
1022
+ holder.imageView.requestLayout()
1023
+ }
1024
+ }
1025
+ }
853
1026
  }
854
1027
 
855
1028
  // Check cache for valid bitmap at current scale
@@ -127,6 +127,8 @@ class HybridPdfViewer: HybridPdfViewerSpec {
127
127
  ensureMainThread {
128
128
  self.pdfView.displayMode = enablePaging ? .singlePage : .singlePageContinuous
129
129
  self.pdfView.usePageViewController(enablePaging, withViewOptions: nil)
130
+ // Ensure autoScales is enabled for proper width fitting in both modes
131
+ self.pdfView.autoScales = true
130
132
  }
131
133
  }
132
134
  }
@@ -145,6 +147,61 @@ class HybridPdfViewer: HybridPdfViewerSpec {
145
147
  }
146
148
  }
147
149
 
150
+ // Content insets for glass UI / transparent bars
151
+ var contentInsetTop: Double? {
152
+ didSet {
153
+ updateContentInsets()
154
+ }
155
+ }
156
+
157
+ var contentInsetBottom: Double? {
158
+ didSet {
159
+ updateContentInsets()
160
+ }
161
+ }
162
+
163
+ var contentInsetLeft: Double? {
164
+ didSet {
165
+ updateContentInsets()
166
+ }
167
+ }
168
+
169
+ var contentInsetRight: Double? {
170
+ didSet {
171
+ updateContentInsets()
172
+ }
173
+ }
174
+
175
+ private func updateContentInsets() {
176
+ ensureMainThread {
177
+ // Get scroll view from PDFView's view hierarchy
178
+ // PDFView uses PDFDocumentView which contains a scroll view
179
+ if let scrollView = self.findScrollView(in: self.pdfView) {
180
+ let insets = UIEdgeInsets(
181
+ top: CGFloat(self.contentInsetTop ?? 0),
182
+ left: CGFloat(self.contentInsetLeft ?? 0),
183
+ bottom: CGFloat(self.contentInsetBottom ?? 0),
184
+ right: CGFloat(self.contentInsetRight ?? 0)
185
+ )
186
+ scrollView.contentInset = insets
187
+ // Adjust scroll indicator insets to match
188
+ scrollView.scrollIndicatorInsets = insets
189
+ }
190
+ }
191
+ }
192
+
193
+ private func findScrollView(in view: UIView) -> UIScrollView? {
194
+ if let scrollView = view as? UIScrollView {
195
+ return scrollView
196
+ }
197
+ for subview in view.subviews {
198
+ if let scrollView = findScrollView(in: subview) {
199
+ return scrollView
200
+ }
201
+ }
202
+ return nil
203
+ }
204
+
148
205
  var enableZoom: Bool? {
149
206
  didSet {
150
207
  guard let enableZoom = enableZoom else { return }
@@ -228,7 +285,7 @@ class HybridPdfViewer: HybridPdfViewerSpec {
228
285
  // Create PDF view
229
286
  pdfView = PDFView()
230
287
  pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
231
- pdfView.autoScales = false
288
+ pdfView.autoScales = true // Enable autoScales to fit width by default
232
289
  pdfView.displayDirection = .vertical
233
290
  pdfView.displayMode = .singlePageContinuous
234
291
  pdfView.usePageViewController(false, withViewOptions: nil)
@@ -257,8 +314,11 @@ class HybridPdfViewer: HybridPdfViewerSpec {
257
314
  activityIndicator.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
258
315
  ])
259
316
 
260
- let config = URLSessionConfiguration.ephemeral
317
+ // Use default configuration with caching enabled for better performance
318
+ let config = URLSessionConfiguration.default
261
319
  config.timeoutIntervalForRequest = 30.0
320
+ config.requestCachePolicy = .returnCacheDataElseLoad
321
+ config.urlCache = URLCache.shared
262
322
  urlSession = URLSession(configuration: config)
263
323
 
264
324
  super.init()
@@ -287,7 +347,13 @@ class HybridPdfViewer: HybridPdfViewerSpec {
287
347
 
288
348
  // Observe bounds changes to update scale factor when view is resized
289
349
  boundsObservation = pdfView.observe(\.bounds, options: [.new]) { [weak self] _, _ in
290
- self?.updateScaleToFitWidthIfNeeded()
350
+ guard let self = self else { return }
351
+ // Only re-enable autoScales during bounds change if we're at default scale
352
+ // This allows manual zoom to persist through view lifecycle changes
353
+ if self.pdfView.displayMode == .singlePageContinuous && self.pdfView.scaleFactor == 1.0 {
354
+ self.pdfView.autoScales = true
355
+ }
356
+ self.updateScaleToFitWidthIfNeeded()
291
357
  }
292
358
  }
293
359
 
@@ -434,9 +500,19 @@ class HybridPdfViewer: HybridPdfViewerSpec {
434
500
  pdfView.document = document
435
501
  thumbnailCache.removeAllObjects()
436
502
 
503
+ // Ensure autoScales is enabled for proper width fitting
504
+ pdfView.autoScales = true
505
+
437
506
  // Update scale after document is loaded
438
507
  updateScaleToFitWidthIfNeeded()
439
508
 
509
+ // If bounds were zero, schedule a delayed update
510
+ if pdfView.bounds.width == 0 {
511
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
512
+ self?.updateScaleToFitWidthIfNeeded()
513
+ }
514
+ }
515
+
440
516
  if let firstPage = document.page(at: 0) {
441
517
  let pageRect = firstPage.bounds(for: .mediaBox)
442
518
  onLoadComplete?(LoadCompleteEvent(
@@ -454,16 +530,31 @@ class HybridPdfViewer: HybridPdfViewerSpec {
454
530
  let pageRect = firstPage.bounds(for: .mediaBox)
455
531
  let viewWidth = pdfView.bounds.width
456
532
 
457
- // Only update scale if view has been laid out (width > 0) and zoom is enabled
533
+ // Only update scale if view has been laid out (width > 0)
458
534
  guard viewWidth > 0 && pageRect.width > 0 else { return }
459
535
 
460
- let scale = viewWidth / pageRect.width
461
- let minScale = CGFloat(self.minScale ?? 0.5)
462
- let maxScale = CGFloat(self.maxScale ?? 4.0)
463
-
464
- // Clamp the scale between min and max
465
- let clampedScale = max(minScale, min(maxScale, scale))
466
- pdfView.scaleFactor = clampedScale
536
+ // In continuous scroll mode, check if we should enable autoScales
537
+ if pdfView.displayMode == .singlePageContinuous {
538
+ // Only enable autoScales if at default scale (hasn't been manually zoomed)
539
+ // This preserves user's manual zoom level through view lifecycle
540
+ let currentScale = pdfView.scaleFactor
541
+ let isDefaultScale = abs(currentScale - 1.0) < 0.01
542
+
543
+ if isDefaultScale {
544
+ pdfView.autoScales = true
545
+ // Force PDFView to recalculate scale if needed
546
+ pdfView.layoutDocumentView()
547
+ }
548
+ } else {
549
+ // For paging mode, calculate and set the scale to fit width
550
+ let scale = viewWidth / pageRect.width
551
+ let minScale = CGFloat(self.minScale ?? 0.5)
552
+ let maxScale = CGFloat(self.maxScale ?? 4.0)
553
+
554
+ // Clamp the scale between min and max
555
+ let clampedScale = max(minScale, min(maxScale, scale))
556
+ pdfView.scaleFactor = clampedScale
557
+ }
467
558
  }
468
559
 
469
560
  private func resolveURL(from source: String) -> URL? {
@@ -532,13 +623,11 @@ class HybridPdfViewer: HybridPdfViewerSpec {
532
623
  let maxScale = CGFloat(self.maxScale ?? 4.0)
533
624
  let clampedScale = max(minScale, min(maxScale, CGFloat(scale)))
534
625
 
535
- // Ensure UI update happens on main thread
536
- if Thread.isMainThread {
537
- pdfView.scaleFactor = clampedScale
538
- } else {
539
- DispatchQueue.main.async { [weak self] in
540
- self?.pdfView.scaleFactor = clampedScale
541
- }
626
+ // Disable autoScales when manually setting scale
627
+ // This allows programmatic zoom control to work
628
+ ensureMainThread {
629
+ self.pdfView.autoScales = false
630
+ self.pdfView.scaleFactor = clampedScale
542
631
  }
543
632
  }
544
633
 
@@ -1 +1 @@
1
- {"version":3,"names":["getHostComponent","callback","PdfViewerConfig","require","PdfViewerView"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,gBAAgB,EAAEC,QAAQ,QAAQ,4BAA4B;AAEvE,MAAMC,eAAe,GAAGC,OAAO,CAAC,wDAAwD,CAAC;AAGzF,OAAO,MAAMC,aAAa,GAAGJ,gBAAgB,CAC3C,WAAW,EACX,MAAME,eACR,CAAC;;AAED;;AAGA;;AAYA;AACA,SAASD,QAAQ","ignoreList":[]}
1
+ {"version":3,"names":["getHostComponent","callback","PdfViewerConfig","require","PdfViewerView"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,gBAAgB,EAAEC,QAAQ,QAAQ,4BAA4B;AAIvE,MAAMC,eAAe,GAAGC,OAAO,CAAC,wDAAwD,CAAC;AAEzF,OAAO,MAAMC,aAAa,GAAGJ,gBAAgB,CAC3C,WAAW,EACX,MAAME,eACR,CAAC;;AAED;;AAGA;;AAYA;AACA,SAASD,QAAQ","ignoreList":[]}
@@ -46,6 +46,25 @@ export interface PdfViewerProps extends HybridViewProps {
46
46
  */
47
47
  enablePaging?: boolean;
48
48
  spacing?: number;
49
+ /**
50
+ * Top content inset in pixels
51
+ * Useful for transparent/glass top bars - content starts below but scrolls behind
52
+ * @example 80 // Start content below 80px top bar
53
+ */
54
+ contentInsetTop?: number;
55
+ /**
56
+ * Bottom content inset in pixels
57
+ * Useful for transparent/glass bottom bars - content ends above but scrolls behind
58
+ */
59
+ contentInsetBottom?: number;
60
+ /**
61
+ * Left content inset in pixels
62
+ */
63
+ contentInsetLeft?: number;
64
+ /**
65
+ * Right content inset in pixels
66
+ */
67
+ contentInsetRight?: number;
49
68
  enableZoom?: boolean;
50
69
  minScale?: number;
51
70
  maxScale?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"PdfViewer.nitro.d.ts","sourceRoot":"","sources":["../../../src/PdfViewer.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;CACpB;AAGD,MAAM,WAAW,iBAAiB;IAEhC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAGjC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChE,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IAEzD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG7B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAGtC,qBAAqB,IAAI,IAAI,CAAC;CAC/B;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"PdfViewer.nitro.d.ts","sourceRoot":"","sources":["../../../src/PdfViewer.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;CACpB;AAGD,MAAM,WAAW,iBAAiB;IAEhC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAGjC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChE,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IAEzD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG7B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAGtC,qBAAqB,IAAI,IAAI,CAAC;CAC/B;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE1E,eAAO,MAAM,aAAa,wFAGzB,CAAC;AAGF,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;AAGvE,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAI1E,eAAO,MAAM,aAAa,wFAGzB,CAAC;AAGF,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;AAGvE,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -101,6 +101,42 @@ namespace margelo::nitro::pdfviewer {
101
101
  static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* spacing */)>("setSpacing");
102
102
  method(_javaPart, spacing.has_value() ? jni::JDouble::valueOf(spacing.value()) : nullptr);
103
103
  }
104
+ std::optional<double> JHybridPdfViewerSpec::getContentInsetTop() {
105
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JDouble>()>("getContentInsetTop");
106
+ auto __result = method(_javaPart);
107
+ return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt;
108
+ }
109
+ void JHybridPdfViewerSpec::setContentInsetTop(std::optional<double> contentInsetTop) {
110
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* contentInsetTop */)>("setContentInsetTop");
111
+ method(_javaPart, contentInsetTop.has_value() ? jni::JDouble::valueOf(contentInsetTop.value()) : nullptr);
112
+ }
113
+ std::optional<double> JHybridPdfViewerSpec::getContentInsetBottom() {
114
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JDouble>()>("getContentInsetBottom");
115
+ auto __result = method(_javaPart);
116
+ return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt;
117
+ }
118
+ void JHybridPdfViewerSpec::setContentInsetBottom(std::optional<double> contentInsetBottom) {
119
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* contentInsetBottom */)>("setContentInsetBottom");
120
+ method(_javaPart, contentInsetBottom.has_value() ? jni::JDouble::valueOf(contentInsetBottom.value()) : nullptr);
121
+ }
122
+ std::optional<double> JHybridPdfViewerSpec::getContentInsetLeft() {
123
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JDouble>()>("getContentInsetLeft");
124
+ auto __result = method(_javaPart);
125
+ return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt;
126
+ }
127
+ void JHybridPdfViewerSpec::setContentInsetLeft(std::optional<double> contentInsetLeft) {
128
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* contentInsetLeft */)>("setContentInsetLeft");
129
+ method(_javaPart, contentInsetLeft.has_value() ? jni::JDouble::valueOf(contentInsetLeft.value()) : nullptr);
130
+ }
131
+ std::optional<double> JHybridPdfViewerSpec::getContentInsetRight() {
132
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JDouble>()>("getContentInsetRight");
133
+ auto __result = method(_javaPart);
134
+ return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt;
135
+ }
136
+ void JHybridPdfViewerSpec::setContentInsetRight(std::optional<double> contentInsetRight) {
137
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* contentInsetRight */)>("setContentInsetRight");
138
+ method(_javaPart, contentInsetRight.has_value() ? jni::JDouble::valueOf(contentInsetRight.value()) : nullptr);
139
+ }
104
140
  std::optional<bool> JHybridPdfViewerSpec::getEnableZoom() {
105
141
  static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JBoolean>()>("getEnableZoom");
106
142
  auto __result = method(_javaPart);
@@ -57,6 +57,14 @@ namespace margelo::nitro::pdfviewer {
57
57
  void setEnablePaging(std::optional<bool> enablePaging) override;
58
58
  std::optional<double> getSpacing() override;
59
59
  void setSpacing(std::optional<double> spacing) override;
60
+ std::optional<double> getContentInsetTop() override;
61
+ void setContentInsetTop(std::optional<double> contentInsetTop) override;
62
+ std::optional<double> getContentInsetBottom() override;
63
+ void setContentInsetBottom(std::optional<double> contentInsetBottom) override;
64
+ std::optional<double> getContentInsetLeft() override;
65
+ void setContentInsetLeft(std::optional<double> contentInsetLeft) override;
66
+ std::optional<double> getContentInsetRight() override;
67
+ void setContentInsetRight(std::optional<double> contentInsetRight) override;
60
68
  std::optional<bool> getEnableZoom() override;
61
69
  void setEnableZoom(std::optional<bool> enableZoom) override;
62
70
  std::optional<double> getMinScale() override;
@@ -52,6 +52,22 @@ void JHybridPdfViewerStateUpdater::updateViewProps(jni::alias_ref<jni::JClass> /
52
52
  view->setSpacing(props.spacing.value);
53
53
  // TODO: Set isDirty = false
54
54
  }
55
+ if (props.contentInsetTop.isDirty) {
56
+ view->setContentInsetTop(props.contentInsetTop.value);
57
+ // TODO: Set isDirty = false
58
+ }
59
+ if (props.contentInsetBottom.isDirty) {
60
+ view->setContentInsetBottom(props.contentInsetBottom.value);
61
+ // TODO: Set isDirty = false
62
+ }
63
+ if (props.contentInsetLeft.isDirty) {
64
+ view->setContentInsetLeft(props.contentInsetLeft.value);
65
+ // TODO: Set isDirty = false
66
+ }
67
+ if (props.contentInsetRight.isDirty) {
68
+ view->setContentInsetRight(props.contentInsetRight.value);
69
+ // TODO: Set isDirty = false
70
+ }
55
71
  if (props.enableZoom.isDirty) {
56
72
  view->setEnableZoom(props.enableZoom.value);
57
73
  // TODO: Set isDirty = false
@@ -62,6 +62,30 @@ abstract class HybridPdfViewerSpec: HybridView() {
62
62
  @set:Keep
63
63
  abstract var spacing: Double?
64
64
 
65
+ @get:DoNotStrip
66
+ @get:Keep
67
+ @set:DoNotStrip
68
+ @set:Keep
69
+ abstract var contentInsetTop: Double?
70
+
71
+ @get:DoNotStrip
72
+ @get:Keep
73
+ @set:DoNotStrip
74
+ @set:Keep
75
+ abstract var contentInsetBottom: Double?
76
+
77
+ @get:DoNotStrip
78
+ @get:Keep
79
+ @set:DoNotStrip
80
+ @set:Keep
81
+ abstract var contentInsetLeft: Double?
82
+
83
+ @get:DoNotStrip
84
+ @get:Keep
85
+ @set:DoNotStrip
86
+ @set:Keep
87
+ abstract var contentInsetRight: Double?
88
+
65
89
  @get:DoNotStrip
66
90
  @get:Keep
67
91
  @set:DoNotStrip
@@ -100,6 +100,34 @@ namespace margelo::nitro::pdfviewer {
100
100
  inline void setSpacing(std::optional<double> spacing) noexcept override {
101
101
  _swiftPart.setSpacing(spacing);
102
102
  }
103
+ inline std::optional<double> getContentInsetTop() noexcept override {
104
+ auto __result = _swiftPart.getContentInsetTop();
105
+ return __result;
106
+ }
107
+ inline void setContentInsetTop(std::optional<double> contentInsetTop) noexcept override {
108
+ _swiftPart.setContentInsetTop(contentInsetTop);
109
+ }
110
+ inline std::optional<double> getContentInsetBottom() noexcept override {
111
+ auto __result = _swiftPart.getContentInsetBottom();
112
+ return __result;
113
+ }
114
+ inline void setContentInsetBottom(std::optional<double> contentInsetBottom) noexcept override {
115
+ _swiftPart.setContentInsetBottom(contentInsetBottom);
116
+ }
117
+ inline std::optional<double> getContentInsetLeft() noexcept override {
118
+ auto __result = _swiftPart.getContentInsetLeft();
119
+ return __result;
120
+ }
121
+ inline void setContentInsetLeft(std::optional<double> contentInsetLeft) noexcept override {
122
+ _swiftPart.setContentInsetLeft(contentInsetLeft);
123
+ }
124
+ inline std::optional<double> getContentInsetRight() noexcept override {
125
+ auto __result = _swiftPart.getContentInsetRight();
126
+ return __result;
127
+ }
128
+ inline void setContentInsetRight(std::optional<double> contentInsetRight) noexcept override {
129
+ _swiftPart.setContentInsetRight(contentInsetRight);
130
+ }
103
131
  inline std::optional<bool> getEnableZoom() noexcept override {
104
132
  auto __result = _swiftPart.getEnableZoom();
105
133
  return __result;
@@ -91,6 +91,26 @@ using namespace margelo::nitro::pdfviewer::views;
91
91
  swiftPart.setSpacing(newViewProps.spacing.value);
92
92
  newViewProps.spacing.isDirty = false;
93
93
  }
94
+ // contentInsetTop: optional
95
+ if (newViewProps.contentInsetTop.isDirty) {
96
+ swiftPart.setContentInsetTop(newViewProps.contentInsetTop.value);
97
+ newViewProps.contentInsetTop.isDirty = false;
98
+ }
99
+ // contentInsetBottom: optional
100
+ if (newViewProps.contentInsetBottom.isDirty) {
101
+ swiftPart.setContentInsetBottom(newViewProps.contentInsetBottom.value);
102
+ newViewProps.contentInsetBottom.isDirty = false;
103
+ }
104
+ // contentInsetLeft: optional
105
+ if (newViewProps.contentInsetLeft.isDirty) {
106
+ swiftPart.setContentInsetLeft(newViewProps.contentInsetLeft.value);
107
+ newViewProps.contentInsetLeft.isDirty = false;
108
+ }
109
+ // contentInsetRight: optional
110
+ if (newViewProps.contentInsetRight.isDirty) {
111
+ swiftPart.setContentInsetRight(newViewProps.contentInsetRight.value);
112
+ newViewProps.contentInsetRight.isDirty = false;
113
+ }
94
114
  // enableZoom: optional
95
115
  if (newViewProps.enableZoom.isDirty) {
96
116
  swiftPart.setEnableZoom(newViewProps.enableZoom.value);
@@ -15,6 +15,10 @@ public protocol HybridPdfViewerSpec_protocol: HybridObject, HybridView {
15
15
  var horizontal: Bool? { get set }
16
16
  var enablePaging: Bool? { get set }
17
17
  var spacing: Double? { get set }
18
+ var contentInsetTop: Double? { get set }
19
+ var contentInsetBottom: Double? { get set }
20
+ var contentInsetLeft: Double? { get set }
21
+ var contentInsetRight: Double? { get set }
18
22
  var enableZoom: Bool? { get set }
19
23
  var minScale: Double? { get set }
20
24
  var maxScale: Double? { get set }
@@ -181,6 +181,74 @@ open class HybridPdfViewerSpec_cxx {
181
181
  }
182
182
  }
183
183
 
184
+ public final var contentInsetTop: bridge.std__optional_double_ {
185
+ @inline(__always)
186
+ get {
187
+ return { () -> bridge.std__optional_double_ in
188
+ if let __unwrappedValue = self.__implementation.contentInsetTop {
189
+ return bridge.create_std__optional_double_(__unwrappedValue)
190
+ } else {
191
+ return .init()
192
+ }
193
+ }()
194
+ }
195
+ @inline(__always)
196
+ set {
197
+ self.__implementation.contentInsetTop = newValue.value
198
+ }
199
+ }
200
+
201
+ public final var contentInsetBottom: bridge.std__optional_double_ {
202
+ @inline(__always)
203
+ get {
204
+ return { () -> bridge.std__optional_double_ in
205
+ if let __unwrappedValue = self.__implementation.contentInsetBottom {
206
+ return bridge.create_std__optional_double_(__unwrappedValue)
207
+ } else {
208
+ return .init()
209
+ }
210
+ }()
211
+ }
212
+ @inline(__always)
213
+ set {
214
+ self.__implementation.contentInsetBottom = newValue.value
215
+ }
216
+ }
217
+
218
+ public final var contentInsetLeft: bridge.std__optional_double_ {
219
+ @inline(__always)
220
+ get {
221
+ return { () -> bridge.std__optional_double_ in
222
+ if let __unwrappedValue = self.__implementation.contentInsetLeft {
223
+ return bridge.create_std__optional_double_(__unwrappedValue)
224
+ } else {
225
+ return .init()
226
+ }
227
+ }()
228
+ }
229
+ @inline(__always)
230
+ set {
231
+ self.__implementation.contentInsetLeft = newValue.value
232
+ }
233
+ }
234
+
235
+ public final var contentInsetRight: bridge.std__optional_double_ {
236
+ @inline(__always)
237
+ get {
238
+ return { () -> bridge.std__optional_double_ in
239
+ if let __unwrappedValue = self.__implementation.contentInsetRight {
240
+ return bridge.create_std__optional_double_(__unwrappedValue)
241
+ } else {
242
+ return .init()
243
+ }
244
+ }()
245
+ }
246
+ @inline(__always)
247
+ set {
248
+ self.__implementation.contentInsetRight = newValue.value
249
+ }
250
+ }
251
+
184
252
  public final var enableZoom: bridge.std__optional_bool_ {
185
253
  @inline(__always)
186
254
  get {
@@ -22,6 +22,14 @@ namespace margelo::nitro::pdfviewer {
22
22
  prototype.registerHybridSetter("enablePaging", &HybridPdfViewerSpec::setEnablePaging);
23
23
  prototype.registerHybridGetter("spacing", &HybridPdfViewerSpec::getSpacing);
24
24
  prototype.registerHybridSetter("spacing", &HybridPdfViewerSpec::setSpacing);
25
+ prototype.registerHybridGetter("contentInsetTop", &HybridPdfViewerSpec::getContentInsetTop);
26
+ prototype.registerHybridSetter("contentInsetTop", &HybridPdfViewerSpec::setContentInsetTop);
27
+ prototype.registerHybridGetter("contentInsetBottom", &HybridPdfViewerSpec::getContentInsetBottom);
28
+ prototype.registerHybridSetter("contentInsetBottom", &HybridPdfViewerSpec::setContentInsetBottom);
29
+ prototype.registerHybridGetter("contentInsetLeft", &HybridPdfViewerSpec::getContentInsetLeft);
30
+ prototype.registerHybridSetter("contentInsetLeft", &HybridPdfViewerSpec::setContentInsetLeft);
31
+ prototype.registerHybridGetter("contentInsetRight", &HybridPdfViewerSpec::getContentInsetRight);
32
+ prototype.registerHybridSetter("contentInsetRight", &HybridPdfViewerSpec::setContentInsetRight);
25
33
  prototype.registerHybridGetter("enableZoom", &HybridPdfViewerSpec::getEnableZoom);
26
34
  prototype.registerHybridSetter("enableZoom", &HybridPdfViewerSpec::setEnableZoom);
27
35
  prototype.registerHybridGetter("minScale", &HybridPdfViewerSpec::getMinScale);
@@ -71,6 +71,14 @@ namespace margelo::nitro::pdfviewer {
71
71
  virtual void setEnablePaging(std::optional<bool> enablePaging) = 0;
72
72
  virtual std::optional<double> getSpacing() = 0;
73
73
  virtual void setSpacing(std::optional<double> spacing) = 0;
74
+ virtual std::optional<double> getContentInsetTop() = 0;
75
+ virtual void setContentInsetTop(std::optional<double> contentInsetTop) = 0;
76
+ virtual std::optional<double> getContentInsetBottom() = 0;
77
+ virtual void setContentInsetBottom(std::optional<double> contentInsetBottom) = 0;
78
+ virtual std::optional<double> getContentInsetLeft() = 0;
79
+ virtual void setContentInsetLeft(std::optional<double> contentInsetLeft) = 0;
80
+ virtual std::optional<double> getContentInsetRight() = 0;
81
+ virtual void setContentInsetRight(std::optional<double> contentInsetRight) = 0;
74
82
  virtual std::optional<bool> getEnableZoom() = 0;
75
83
  virtual void setEnableZoom(std::optional<bool> enableZoom) = 0;
76
84
  virtual std::optional<double> getMinScale() = 0;
@@ -65,6 +65,46 @@ namespace margelo::nitro::pdfviewer::views {
65
65
  throw std::runtime_error(std::string("PdfViewer.spacing: ") + exc.what());
66
66
  }
67
67
  }()),
68
+ contentInsetTop([&]() -> CachedProp<std::optional<double>> {
69
+ try {
70
+ const react::RawValue* rawValue = rawProps.at("contentInsetTop", nullptr, nullptr);
71
+ if (rawValue == nullptr) return sourceProps.contentInsetTop;
72
+ const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
73
+ return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.contentInsetTop);
74
+ } catch (const std::exception& exc) {
75
+ throw std::runtime_error(std::string("PdfViewer.contentInsetTop: ") + exc.what());
76
+ }
77
+ }()),
78
+ contentInsetBottom([&]() -> CachedProp<std::optional<double>> {
79
+ try {
80
+ const react::RawValue* rawValue = rawProps.at("contentInsetBottom", nullptr, nullptr);
81
+ if (rawValue == nullptr) return sourceProps.contentInsetBottom;
82
+ const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
83
+ return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.contentInsetBottom);
84
+ } catch (const std::exception& exc) {
85
+ throw std::runtime_error(std::string("PdfViewer.contentInsetBottom: ") + exc.what());
86
+ }
87
+ }()),
88
+ contentInsetLeft([&]() -> CachedProp<std::optional<double>> {
89
+ try {
90
+ const react::RawValue* rawValue = rawProps.at("contentInsetLeft", nullptr, nullptr);
91
+ if (rawValue == nullptr) return sourceProps.contentInsetLeft;
92
+ const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
93
+ return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.contentInsetLeft);
94
+ } catch (const std::exception& exc) {
95
+ throw std::runtime_error(std::string("PdfViewer.contentInsetLeft: ") + exc.what());
96
+ }
97
+ }()),
98
+ contentInsetRight([&]() -> CachedProp<std::optional<double>> {
99
+ try {
100
+ const react::RawValue* rawValue = rawProps.at("contentInsetRight", nullptr, nullptr);
101
+ if (rawValue == nullptr) return sourceProps.contentInsetRight;
102
+ const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
103
+ return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.contentInsetRight);
104
+ } catch (const std::exception& exc) {
105
+ throw std::runtime_error(std::string("PdfViewer.contentInsetRight: ") + exc.what());
106
+ }
107
+ }()),
68
108
  enableZoom([&]() -> CachedProp<std::optional<bool>> {
69
109
  try {
70
110
  const react::RawValue* rawValue = rawProps.at("enableZoom", nullptr, nullptr);
@@ -182,6 +222,10 @@ namespace margelo::nitro::pdfviewer::views {
182
222
  horizontal(other.horizontal),
183
223
  enablePaging(other.enablePaging),
184
224
  spacing(other.spacing),
225
+ contentInsetTop(other.contentInsetTop),
226
+ contentInsetBottom(other.contentInsetBottom),
227
+ contentInsetLeft(other.contentInsetLeft),
228
+ contentInsetRight(other.contentInsetRight),
185
229
  enableZoom(other.enableZoom),
186
230
  minScale(other.minScale),
187
231
  maxScale(other.maxScale),
@@ -200,6 +244,10 @@ namespace margelo::nitro::pdfviewer::views {
200
244
  case hashString("horizontal"): return true;
201
245
  case hashString("enablePaging"): return true;
202
246
  case hashString("spacing"): return true;
247
+ case hashString("contentInsetTop"): return true;
248
+ case hashString("contentInsetBottom"): return true;
249
+ case hashString("contentInsetLeft"): return true;
250
+ case hashString("contentInsetRight"): return true;
203
251
  case hashString("enableZoom"): return true;
204
252
  case hashString("minScale"): return true;
205
253
  case hashString("maxScale"): return true;
@@ -53,6 +53,10 @@ namespace margelo::nitro::pdfviewer::views {
53
53
  CachedProp<std::optional<bool>> horizontal;
54
54
  CachedProp<std::optional<bool>> enablePaging;
55
55
  CachedProp<std::optional<double>> spacing;
56
+ CachedProp<std::optional<double>> contentInsetTop;
57
+ CachedProp<std::optional<double>> contentInsetBottom;
58
+ CachedProp<std::optional<double>> contentInsetLeft;
59
+ CachedProp<std::optional<double>> contentInsetRight;
56
60
  CachedProp<std::optional<bool>> enableZoom;
57
61
  CachedProp<std::optional<double>> minScale;
58
62
  CachedProp<std::optional<double>> maxScale;
@@ -8,6 +8,10 @@
8
8
  "horizontal": true,
9
9
  "enablePaging": true,
10
10
  "spacing": true,
11
+ "contentInsetTop": true,
12
+ "contentInsetBottom": true,
13
+ "contentInsetLeft": true,
14
+ "contentInsetRight": true,
11
15
  "enableZoom": true,
12
16
  "minScale": true,
13
17
  "maxScale": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thatkid02/react-native-pdf-viewer",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Pdf viewer with page thumbnailsthumbnails",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -63,6 +63,27 @@ export interface PdfViewerProps extends HybridViewProps {
63
63
  enablePaging?: boolean;
64
64
  spacing?: number;
65
65
 
66
+ // Content insets - allows content to start below transparent headers/toolbars
67
+ /**
68
+ * Top content inset in pixels
69
+ * Useful for transparent/glass top bars - content starts below but scrolls behind
70
+ * @example 80 // Start content below 80px top bar
71
+ */
72
+ contentInsetTop?: number;
73
+ /**
74
+ * Bottom content inset in pixels
75
+ * Useful for transparent/glass bottom bars - content ends above but scrolls behind
76
+ */
77
+ contentInsetBottom?: number;
78
+ /**
79
+ * Left content inset in pixels
80
+ */
81
+ contentInsetLeft?: number;
82
+ /**
83
+ * Right content inset in pixels
84
+ */
85
+ contentInsetRight?: number;
86
+
66
87
  // Zoom controls
67
88
  enableZoom?: boolean;
68
89
  minScale?: number;
package/src/index.tsx CHANGED
@@ -1,8 +1,9 @@
1
1
  import { getHostComponent, callback } from 'react-native-nitro-modules';
2
2
  import type { HybridRef } from 'react-native-nitro-modules';
3
- const PdfViewerConfig = require('../nitrogen/generated/shared/json/PdfViewerConfig.json');
4
3
  import type { PdfViewerMethods, PdfViewerProps } from './PdfViewer.nitro';
5
4
 
5
+ const PdfViewerConfig = require('../nitrogen/generated/shared/json/PdfViewerConfig.json');
6
+
6
7
  export const PdfViewerView = getHostComponent<PdfViewerProps, PdfViewerMethods>(
7
8
  'PdfViewer',
8
9
  () => PdfViewerConfig