react-native-optimized-pdf 1.0.0

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.
@@ -0,0 +1,499 @@
1
+ package com.reactnativeoptimizedpdf
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import android.graphics.Canvas
6
+ import android.graphics.Color
7
+ import android.graphics.Paint
8
+ import android.graphics.pdf.PdfRenderer
9
+ import android.os.ParcelFileDescriptor
10
+ import android.util.AttributeSet
11
+ import android.view.GestureDetector
12
+ import android.view.MotionEvent
13
+ import android.view.ScaleGestureDetector
14
+ import android.widget.FrameLayout
15
+ import com.facebook.react.bridge.Arguments
16
+ import com.facebook.react.bridge.ReactContext
17
+ import com.facebook.react.uimanager.events.RCTEventEmitter
18
+ import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
19
+ import com.tom_roush.pdfbox.pdmodel.PDDocument
20
+ import com.tom_roush.pdfbox.pdmodel.encryption.InvalidPasswordException
21
+ import java.io.File
22
+ import java.io.FileOutputStream
23
+ import kotlin.math.max
24
+ import kotlin.math.min
25
+
26
+ /**
27
+ * View otimizada para renderização de PDF no Android
28
+ *
29
+ * Usa PdfRenderer do Android para renderização eficiente em memória
30
+ * com suporte a zoom via ScaleGestureDetector e pan via GestureDetector
31
+ */
32
+ class OptimizedPdfView @JvmOverloads constructor(
33
+ context: Context,
34
+ attrs: AttributeSet? = null,
35
+ defStyleAttr: Int = 0
36
+ ) : FrameLayout(context, attrs, defStyleAttr) {
37
+
38
+ private var pdfRenderer: PdfRenderer? = null
39
+ private var fileDescriptor: ParcelFileDescriptor? = null
40
+ private var currentPage: PdfRenderer.Page? = null
41
+ private var currentBitmap: Bitmap? = null
42
+
43
+ private var source: String = ""
44
+ private var pageIndex: Int = 0
45
+ private var maximumZoom: Float = 5.0f
46
+ private var enableAntialiasing: Boolean = true
47
+ private var password: String = ""
48
+
49
+ private var scaleFactor: Float = 1.0f
50
+ private var minScaleFactor: Float = 1.0f
51
+ private var translateX: Float = 0f
52
+ private var translateY: Float = 0f
53
+
54
+ private var pageWidth: Int = 0
55
+ private var pageHeight: Int = 0
56
+
57
+ private var needsLoad: Boolean = false
58
+ private var pendingPageIndex: Int? = null
59
+ private var decryptedFile: File? = null
60
+ private var pdfBoxInitialized: Boolean = false
61
+
62
+ private val paint = Paint().apply {
63
+ isAntiAlias = true
64
+ isFilterBitmap = true
65
+ isDither = true
66
+ }
67
+
68
+ private val scaleGestureDetector: ScaleGestureDetector
69
+ private val gestureDetector: GestureDetector
70
+
71
+ private var lastTouchX: Float = 0f
72
+ private var lastTouchY: Float = 0f
73
+ private var isDragging: Boolean = false
74
+
75
+ init {
76
+ setBackgroundColor(Color.WHITE)
77
+ setWillNotDraw(false)
78
+
79
+ scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
80
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
81
+ val oldScale = scaleFactor
82
+ scaleFactor *= detector.scaleFactor
83
+ scaleFactor = max(minScaleFactor, min(scaleFactor, maximumZoom))
84
+
85
+ if (oldScale != scaleFactor) {
86
+ val focusX = detector.focusX
87
+ val focusY = detector.focusY
88
+
89
+ val oldScaledWidth = pageWidth * oldScale
90
+ val oldScaledHeight = pageHeight * oldScale
91
+ val oldContentX = (width - oldScaledWidth) / 2 + translateX
92
+ val oldContentY = (height - oldScaledHeight) / 2 + translateY
93
+
94
+ val pdfX = (focusX - oldContentX) / oldScale
95
+ val pdfY = (focusY - oldContentY) / oldScale
96
+
97
+ val newScaledWidth = pageWidth * scaleFactor
98
+ val newScaledHeight = pageHeight * scaleFactor
99
+ val newContentX = (width - newScaledWidth) / 2
100
+ val newContentY = (height - newScaledHeight) / 2
101
+
102
+ translateX = focusX - newContentX - (pdfX * scaleFactor)
103
+ translateY = focusY - newContentY - (pdfY * scaleFactor)
104
+
105
+ constrainTranslation()
106
+ invalidate()
107
+ }
108
+ return true
109
+ }
110
+ })
111
+
112
+ gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
113
+ override fun onDoubleTap(e: MotionEvent): Boolean {
114
+ if (scaleFactor > minScaleFactor) {
115
+ animateToScale(minScaleFactor, e.x, e.y)
116
+ } else {
117
+ animateToScale(min(2.5f, maximumZoom), e.x, e.y)
118
+ }
119
+ return true
120
+ }
121
+
122
+ override fun onScroll(
123
+ e1: MotionEvent?,
124
+ e2: MotionEvent,
125
+ distanceX: Float,
126
+ distanceY: Float
127
+ ): Boolean {
128
+ if (!scaleGestureDetector.isInProgress) {
129
+ translateX -= distanceX
130
+ translateY -= distanceY
131
+ constrainTranslation()
132
+ invalidate()
133
+ }
134
+ return true
135
+ }
136
+ })
137
+ }
138
+
139
+ private fun animateToScale(targetScale: Float, focusX: Float, focusY: Float) {
140
+ val startScale = scaleFactor
141
+ val startTranslateX = translateX
142
+ val startTranslateY = translateY
143
+
144
+ val oldScaledWidth = pageWidth * startScale
145
+ val oldScaledHeight = pageHeight * startScale
146
+ val oldContentX = (width - oldScaledWidth) / 2 + startTranslateX
147
+ val oldContentY = (height - oldScaledHeight) / 2 + startTranslateY
148
+ val pdfX = (focusX - oldContentX) / startScale
149
+ val pdfY = (focusY - oldContentY) / startScale
150
+
151
+ android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
152
+ duration = 250
153
+ addUpdateListener { animator ->
154
+ val fraction = animator.animatedValue as Float
155
+ val currentScale = startScale + (targetScale - startScale) * fraction
156
+ scaleFactor = currentScale
157
+
158
+ if (targetScale <= minScaleFactor) {
159
+ translateX = startTranslateX * (1 - fraction)
160
+ translateY = startTranslateY * (1 - fraction)
161
+ } else {
162
+ val newScaledWidth = pageWidth * currentScale
163
+ val newScaledHeight = pageHeight * currentScale
164
+ val newContentX = (width - newScaledWidth) / 2
165
+ val newContentY = (height - newScaledHeight) / 2
166
+
167
+ translateX = focusX - newContentX - (pdfX * currentScale)
168
+ translateY = focusY - newContentY - (pdfY * currentScale)
169
+ }
170
+ constrainTranslation()
171
+ invalidate()
172
+ }
173
+ start()
174
+ }
175
+ }
176
+
177
+ private fun constrainTranslation() {
178
+ val scaledWidth = pageWidth * scaleFactor
179
+ val scaledHeight = pageHeight * scaleFactor
180
+
181
+ val maxTranslateX = max(0f, (scaledWidth - width) / 2)
182
+ val maxTranslateY = max(0f, (scaledHeight - height) / 2)
183
+
184
+ if (scaledWidth <= width) {
185
+ translateX = 0f
186
+ } else {
187
+ translateX = translateX.coerceIn(-maxTranslateX, maxTranslateX)
188
+ }
189
+
190
+ if (scaledHeight <= height) {
191
+ translateY = 0f
192
+ } else {
193
+ translateY = translateY.coerceIn(-maxTranslateY, maxTranslateY)
194
+ }
195
+ }
196
+
197
+ override fun onTouchEvent(event: MotionEvent): Boolean {
198
+ var handled = scaleGestureDetector.onTouchEvent(event)
199
+ handled = gestureDetector.onTouchEvent(event) || handled
200
+ return handled || super.onTouchEvent(event)
201
+ }
202
+
203
+ fun setSource(source: String) {
204
+ if (this.source != source) {
205
+ this.source = source
206
+ needsLoad = true
207
+ pdfRenderer?.close()
208
+ pdfRenderer = null
209
+ pendingPageIndex = pageIndex
210
+ requestLayout()
211
+ invalidate()
212
+ }
213
+ }
214
+
215
+ fun setPage(page: Int) {
216
+ if (this.pageIndex != page) {
217
+ this.pageIndex = page
218
+ if (pdfRenderer != null) {
219
+ displayPage(page)
220
+ } else {
221
+ pendingPageIndex = page
222
+ }
223
+ }
224
+ }
225
+
226
+ fun setMaximumZoom(zoom: Float) {
227
+ this.maximumZoom = zoom
228
+ if (scaleFactor > zoom) {
229
+ scaleFactor = zoom
230
+ invalidate()
231
+ }
232
+ }
233
+
234
+ fun setEnableAntialiasing(enable: Boolean) {
235
+ this.enableAntialiasing = enable
236
+ paint.isAntiAlias = enable
237
+ paint.isFilterBitmap = enable
238
+ invalidate()
239
+ }
240
+
241
+ fun setPassword(pwd: String) {
242
+ if (this.password != pwd) {
243
+ this.password = pwd
244
+ // Se já temos source mas falhou por senha, tenta recarregar
245
+ if (source.isNotEmpty() && pdfRenderer == null) {
246
+ needsLoad = true
247
+ requestLayout()
248
+ }
249
+ }
250
+ }
251
+
252
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
253
+ super.onLayout(changed, left, top, right, bottom)
254
+
255
+ if (width > 0 && height > 0 && needsLoad) {
256
+ needsLoad = false
257
+ loadPdf()
258
+ } else if (changed && currentBitmap != null) {
259
+ calculateFitScale()
260
+ constrainTranslation()
261
+ }
262
+ }
263
+
264
+ private fun loadPdf() {
265
+ if (source.isEmpty()) return
266
+
267
+ try {
268
+ val path = if (source.startsWith("file://")) {
269
+ source.substring(7)
270
+ } else {
271
+ source
272
+ }
273
+
274
+ val file = File(path)
275
+ if (!file.exists()) {
276
+ sendError("PDF file not found: $path")
277
+ return
278
+ }
279
+
280
+ var pdfFile = file
281
+
282
+ try {
283
+ fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
284
+ pdfRenderer = PdfRenderer(fileDescriptor!!)
285
+ } catch (e: SecurityException) {
286
+ fileDescriptor?.close()
287
+ fileDescriptor = null
288
+
289
+ pdfFile = decryptPdfWithPassword(file) ?: return
290
+
291
+ fileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
292
+ pdfRenderer = PdfRenderer(fileDescriptor!!)
293
+ }
294
+
295
+ val pageCount = pdfRenderer!!.pageCount
296
+
297
+ sendPageCount(pageCount)
298
+
299
+ val targetPage = pendingPageIndex ?: 0
300
+ pendingPageIndex = null
301
+ displayPage(targetPage.coerceIn(0, pageCount - 1))
302
+
303
+ } catch (e: Exception) {
304
+ sendError("Failed to load PDF: ${e.message}")
305
+ }
306
+ }
307
+
308
+ private fun decryptPdfWithPassword(file: File): File? {
309
+ if (!pdfBoxInitialized) {
310
+ PDFBoxResourceLoader.init(context)
311
+ pdfBoxInitialized = true
312
+ }
313
+
314
+ val document: PDDocument
315
+ try {
316
+ document = if (password.isNotEmpty()) {
317
+ PDDocument.load(file, password)
318
+ } else {
319
+ try {
320
+ PDDocument.load(file, "")
321
+ } catch (e: InvalidPasswordException) {
322
+ sendPasswordRequired()
323
+ sendError("PDF is password protected")
324
+ return null
325
+ }
326
+ }
327
+ } catch (e: InvalidPasswordException) {
328
+ sendError("Invalid password for PDF")
329
+ return null
330
+ } catch (e: Exception) {
331
+ sendError("Failed to decrypt PDF: ${e.message}")
332
+ return null
333
+ }
334
+
335
+ try {
336
+ if (document.isEncrypted) {
337
+ document.setAllSecurityToBeRemoved(true)
338
+ }
339
+
340
+ val tempFile = File(context.cacheDir, "decrypted_${System.currentTimeMillis()}.pdf")
341
+ document.save(FileOutputStream(tempFile))
342
+ document.close()
343
+
344
+ decryptedFile?.delete()
345
+ decryptedFile = tempFile
346
+
347
+ return tempFile
348
+ } catch (e: Exception) {
349
+ document.close()
350
+ sendError("Failed to save decrypted PDF: ${e.message}")
351
+ return null
352
+ }
353
+ }
354
+
355
+ private fun displayPage(index: Int) {
356
+ val renderer = pdfRenderer ?: return
357
+
358
+ if (index < 0 || index >= renderer.pageCount) {
359
+ sendError("Invalid page index: $index")
360
+ return
361
+ }
362
+
363
+ currentPage?.close()
364
+
365
+ currentPage = renderer.openPage(index)
366
+ val page = currentPage!!
367
+
368
+ pageWidth = page.width
369
+ pageHeight = page.height
370
+
371
+ val scale = calculateRenderScale()
372
+ val bitmapWidth = (pageWidth * scale).toInt()
373
+ val bitmapHeight = (pageHeight * scale).toInt()
374
+
375
+ currentBitmap?.recycle()
376
+
377
+ currentBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
378
+ currentBitmap?.let { bitmap ->
379
+ bitmap.eraseColor(Color.WHITE)
380
+ page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
381
+ }
382
+
383
+ pageIndex = index
384
+ calculateFitScale()
385
+
386
+ scaleFactor = minScaleFactor
387
+ translateX = 0f
388
+ translateY = 0f
389
+
390
+ invalidate()
391
+
392
+ sendLoadComplete(index, pageWidth, pageHeight)
393
+ }
394
+
395
+ private fun calculateRenderScale(): Float {
396
+ val displayMetrics = resources.displayMetrics
397
+ val screenDensity = displayMetrics.density
398
+
399
+ val baseScale = 2.0f * screenDensity
400
+
401
+ val maxDimension = max(pageWidth, pageHeight) * baseScale
402
+ return if (maxDimension > 4096) {
403
+ 4096f / max(pageWidth, pageHeight)
404
+ } else {
405
+ baseScale
406
+ }
407
+ }
408
+
409
+ private fun calculateFitScale() {
410
+ if (pageWidth == 0 || pageHeight == 0 || width == 0 || height == 0) return
411
+
412
+ val scaleX = width.toFloat() / pageWidth
413
+ val scaleY = height.toFloat() / pageHeight
414
+ minScaleFactor = min(scaleX, scaleY)
415
+
416
+ if (scaleFactor < minScaleFactor) {
417
+ scaleFactor = minScaleFactor
418
+ }
419
+ }
420
+
421
+ override fun onDraw(canvas: Canvas) {
422
+ super.onDraw(canvas)
423
+
424
+ val bitmap = currentBitmap ?: return
425
+
426
+ canvas.save()
427
+
428
+ val scaledWidth = pageWidth * scaleFactor
429
+ val scaledHeight = pageHeight * scaleFactor
430
+
431
+ val centerX = (width - scaledWidth) / 2 + translateX
432
+ val centerY = (height - scaledHeight) / 2 + translateY
433
+
434
+ canvas.translate(centerX, centerY)
435
+ canvas.scale(scaleFactor / calculateRenderScale(), scaleFactor / calculateRenderScale())
436
+
437
+ canvas.drawBitmap(bitmap, 0f, 0f, paint)
438
+
439
+ canvas.restore()
440
+ }
441
+
442
+ private fun sendLoadComplete(page: Int, width: Int, height: Int) {
443
+ val reactContext = context as? ReactContext ?: return
444
+ val event = Arguments.createMap().apply {
445
+ putInt("currentPage", page + 1) // 1-indexed como no iOS
446
+ putInt("width", width)
447
+ putInt("height", height)
448
+ }
449
+ reactContext.getJSModule(RCTEventEmitter::class.java)
450
+ .receiveEvent(id, "onLoadComplete", event)
451
+ }
452
+
453
+ private fun sendError(message: String) {
454
+ val reactContext = context as? ReactContext ?: return
455
+ val event = Arguments.createMap().apply {
456
+ putString("message", message)
457
+ }
458
+ reactContext.getJSModule(RCTEventEmitter::class.java)
459
+ .receiveEvent(id, "onError", event)
460
+ }
461
+
462
+ private fun sendPageCount(count: Int) {
463
+ val reactContext = context as? ReactContext ?: return
464
+ val event = Arguments.createMap().apply {
465
+ putInt("numberOfPages", count)
466
+ }
467
+ reactContext.getJSModule(RCTEventEmitter::class.java)
468
+ .receiveEvent(id, "onPageCount", event)
469
+ }
470
+
471
+ private fun sendPasswordRequired() {
472
+ val reactContext = context as? ReactContext ?: return
473
+ val event = Arguments.createMap()
474
+ reactContext.getJSModule(RCTEventEmitter::class.java)
475
+ .receiveEvent(id, "onPasswordRequired", event)
476
+ }
477
+
478
+ fun cleanup() {
479
+ currentPage?.close()
480
+ currentPage = null
481
+
482
+ pdfRenderer?.close()
483
+ pdfRenderer = null
484
+
485
+ fileDescriptor?.close()
486
+ fileDescriptor = null
487
+
488
+ currentBitmap?.recycle()
489
+ currentBitmap = null
490
+
491
+ decryptedFile?.delete()
492
+ decryptedFile = null
493
+ }
494
+
495
+ override fun onDetachedFromWindow() {
496
+ super.onDetachedFromWindow()
497
+ cleanup()
498
+ }
499
+ }
@@ -0,0 +1,68 @@
1
+ package com.reactnativeoptimizedpdf
2
+
3
+ import com.facebook.react.bridge.ReadableMap
4
+ import com.facebook.react.common.MapBuilder
5
+ import com.facebook.react.uimanager.SimpleViewManager
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+
9
+ /**
10
+ * ViewManager que expõe o OptimizedPdfView para o React Native
11
+ *
12
+ * Props suportadas:
13
+ * - source: String (caminho do arquivo PDF)
14
+ * - page: Int (página atual, 0-indexed)
15
+ * - maximumZoom: Float (zoom máximo permitido)
16
+ * - enableAntialiasing: Boolean (habilitar antialiasing)
17
+ */
18
+ class OptimizedPdfViewManager : SimpleViewManager<OptimizedPdfView>() {
19
+
20
+ companion object {
21
+ const val REACT_CLASS = "OptimizedPdfView"
22
+ }
23
+
24
+ override fun getName(): String = REACT_CLASS
25
+
26
+ override fun createViewInstance(reactContext: ThemedReactContext): OptimizedPdfView {
27
+ return OptimizedPdfView(reactContext)
28
+ }
29
+
30
+ @ReactProp(name = "source")
31
+ fun setSource(view: OptimizedPdfView, source: String?) {
32
+ source?.let { view.setSource(it) }
33
+ }
34
+
35
+ @ReactProp(name = "page", defaultInt = 0)
36
+ fun setPage(view: OptimizedPdfView, page: Int) {
37
+ view.setPage(page)
38
+ }
39
+
40
+ @ReactProp(name = "maximumZoom", defaultFloat = 5.0f)
41
+ fun setMaximumZoom(view: OptimizedPdfView, maximumZoom: Float) {
42
+ view.setMaximumZoom(maximumZoom)
43
+ }
44
+
45
+ @ReactProp(name = "enableAntialiasing", defaultBoolean = true)
46
+ fun setEnableAntialiasing(view: OptimizedPdfView, enableAntialiasing: Boolean) {
47
+ view.setEnableAntialiasing(enableAntialiasing)
48
+ }
49
+
50
+ @ReactProp(name = "password")
51
+ fun setPassword(view: OptimizedPdfView, password: String?) {
52
+ view.setPassword(password ?: "")
53
+ }
54
+
55
+ override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
56
+ return MapBuilder.builder<String, Any>()
57
+ .put("onLoadComplete", MapBuilder.of("registrationName", "onLoadComplete"))
58
+ .put("onError", MapBuilder.of("registrationName", "onError"))
59
+ .put("onPageCount", MapBuilder.of("registrationName", "onPageCount"))
60
+ .put("onPasswordRequired", MapBuilder.of("registrationName", "onPasswordRequired"))
61
+ .build()
62
+ }
63
+
64
+ override fun onDropViewInstance(view: OptimizedPdfView) {
65
+ super.onDropViewInstance(view)
66
+ view.cleanup()
67
+ }
68
+ }
@@ -0,0 +1,20 @@
1
+ package com.reactnativeoptimizedpdf
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ /**
9
+ * React Native Package para registrar o módulo OptimizedPdf
10
+ */
11
+ class OptimizedPdfViewPackage : ReactPackage {
12
+
13
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
14
+ return emptyList()
15
+ }
16
+
17
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
18
+ return listOf(OptimizedPdfViewManager())
19
+ }
20
+ }
@@ -0,0 +1,63 @@
1
+ # Android Setup for react-native-optimized-pdf
2
+
3
+ ## Automatic Linking (React Native 0.60+)
4
+
5
+ The package will be automatically linked when you run your project. No manual linking required.
6
+
7
+ ## Manual Setup (if needed)
8
+
9
+ If automatic linking doesn't work, follow these steps:
10
+
11
+ ### 1. Add to `settings.gradle`
12
+
13
+ ```gradle
14
+ include ':react-native-optimized-pdf'
15
+ project(':react-native-optimized-pdf').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-optimized-pdf/android')
16
+ ```
17
+
18
+ ### 2. Add to `app/build.gradle`
19
+
20
+ ```gradle
21
+ dependencies {
22
+ implementation project(':react-native-optimized-pdf')
23
+ }
24
+ ```
25
+
26
+ ### 3. Add to `MainApplication.java` or `MainApplication.kt`
27
+
28
+ **Java:**
29
+
30
+ ```java
31
+ import com.reactnativeoptimizedpdf.OptimizedPdfPackage;
32
+
33
+ @Override
34
+ protected List<ReactPackage> getPackages() {
35
+ List<ReactPackage> packages = new PackageList(this).getPackages();
36
+ packages.add(new OptimizedPdfPackage());
37
+ return packages;
38
+ }
39
+ ```
40
+
41
+ **Kotlin:**
42
+
43
+ ```kotlin
44
+ import com.reactnativeoptimizedpdf.OptimizedPdfPackage
45
+
46
+ override fun getPackages(): List<ReactPackage> =
47
+ PackageList(this).packages.apply {
48
+ add(OptimizedPdfPackage())
49
+ }
50
+ ```
51
+
52
+ ## Requirements
53
+
54
+ - Android SDK 24+
55
+ - Kotlin 1.9+
56
+
57
+ ## Features
58
+
59
+ - High-performance PDF rendering using Android's native PdfRenderer
60
+ - Smooth zoom and pan with gesture support
61
+ - Double-tap to zoom/reset
62
+ - Memory-efficient rendering with proper bitmap recycling
63
+ - Antialiasing support for crisp text
package/index.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export { default } from './src/OptimizedPdfView';
2
+ export { PdfCacheService } from './src/services/pdfCache';
3
+ export { PdfNavigationControls } from './src/components/PdfNavigationControls';
4
+ export { PdfLoadingOverlay, PdfErrorOverlay } from './src/components/PdfOverlays';
5
+ export type {
6
+ PdfSource,
7
+ OptimizedPdfViewProps,
8
+ PdfPageDimensions,
9
+ PdfErrorEvent,
10
+ PdfNavigationControlsProps,
11
+ PdfLoadingOverlayProps,
12
+ PdfErrorOverlayProps,
13
+ NativeLoadCompleteEvent,
14
+ NativePageCountEvent,
15
+ } from './src/types';
package/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export { default } from './src/OptimizedPdfView';
2
+ export { PdfCacheService } from './src/services/pdfCache';
3
+ export { PdfNavigationControls } from './src/components/PdfNavigationControls';
4
+ export { PdfLoadingOverlay, PdfErrorOverlay } from './src/components/PdfOverlays';
5
+ export type {
6
+ PdfSource,
7
+ OptimizedPdfViewProps,
8
+ PdfPageDimensions,
9
+ PdfErrorEvent,
10
+ PdfNavigationControlsProps,
11
+ PdfLoadingOverlayProps,
12
+ PdfErrorOverlayProps,
13
+ } from './src/types';