@june24/expo-pdf-reader 0.1.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,675 @@
1
+ import ExpoModulesCore
2
+ import PDFKit
3
+
4
+ class ExpoPdfReaderView: ExpoView {
5
+ let pdfView = PDFView()
6
+ var currentTool: String = "none"
7
+ var currentColor: UIColor = .black
8
+ var currentFontSize: CGFloat = 16.0
9
+ var currentText: String = "Text"
10
+ var currentStrokeWidth: CGFloat = 2.0
11
+ var initialPage: Int = 0
12
+ var minZoom: CGFloat = 1.0
13
+ var maxZoom: CGFloat = 4.0
14
+
15
+ // Events
16
+ let onAnnotationChange = EventDispatcher()
17
+ let onPageChange = EventDispatcher()
18
+ let onScroll = EventDispatcher()
19
+
20
+ // Gesture recognizers for drawing
21
+ private var panGesture: UIPanGestureRecognizer?
22
+ private var tapGesture: UITapGestureRecognizer?
23
+ private var currentAnnotation: PDFAnnotation?
24
+ private var currentPath: UIBezierPath?
25
+
26
+ // Undo/Redo stacks
27
+ private var undoStack: [PDFAnnotation] = []
28
+ private var redoStack: [PDFAnnotation] = []
29
+
30
+ required init(appContext: AppContext? = nil) {
31
+ super.init(appContext: appContext)
32
+ clipsToBounds = true
33
+ addSubview(pdfView)
34
+
35
+ pdfView.autoScales = true
36
+ pdfView.displayMode = .singlePageContinuous
37
+ pdfView.displayDirection = .vertical
38
+ pdfView.minScaleFactor = minZoom
39
+ pdfView.maxScaleFactor = maxZoom
40
+
41
+ // Setup gestures
42
+ panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
43
+ tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
44
+
45
+ // Initially disabled
46
+ panGesture?.isEnabled = false
47
+ tapGesture?.isEnabled = false
48
+
49
+ pdfView.addGestureRecognizer(panGesture!)
50
+ pdfView.addGestureRecognizer(tapGesture!)
51
+
52
+ // Listen for page changes
53
+ NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange), name: .PDFViewPageChanged, object: pdfView)
54
+
55
+ // Setup scroll observer
56
+ setupScrollObserver()
57
+ }
58
+
59
+ private func setupScrollObserver() {
60
+ // Use KVO to observe scroll position changes
61
+ pdfView.addObserver(self, forKeyPath: "bounds", options: [.new, .old], context: nil)
62
+
63
+ // Also observe content offset if available
64
+ if let scrollView = pdfView.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
65
+ scrollView.addObserver(self, forKeyPath: "contentOffset", options: [.new], context: nil)
66
+ }
67
+ }
68
+
69
+ override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
70
+ if keyPath == "bounds" || keyPath == "contentOffset" {
71
+ notifyScroll()
72
+ } else {
73
+ super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
74
+ }
75
+ }
76
+
77
+ private func notifyScroll() {
78
+ let bounds = pdfView.bounds
79
+ let contentSize = pdfView.document?.bounds(for: .mediaBox).size ?? .zero
80
+
81
+ // Get scroll position from PDFView
82
+ let visibleRect = pdfView.visiblePageRect
83
+ let scrollX = visibleRect.origin.x
84
+ let scrollY = visibleRect.origin.y
85
+
86
+ onScroll([
87
+ "x": scrollX,
88
+ "y": scrollY,
89
+ "contentWidth": contentSize.width,
90
+ "contentHeight": contentSize.height,
91
+ "layoutWidth": bounds.width,
92
+ "layoutHeight": bounds.height
93
+ ])
94
+ }
95
+
96
+ deinit {
97
+ NotificationCenter.default.removeObserver(self)
98
+
99
+ // Remove KVO observers
100
+ pdfView.removeObserver(self, forKeyPath: "bounds")
101
+ if let scrollView = pdfView.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
102
+ scrollView.removeObserver(self, forKeyPath: "contentOffset")
103
+ }
104
+ }
105
+
106
+ override func layoutSubviews() {
107
+ pdfView.frame = bounds
108
+ }
109
+
110
+ func load(url: URL) {
111
+ if url.scheme == "http" || url.scheme == "https" {
112
+ DispatchQueue.global().async {
113
+ if let data = try? Data(contentsOf: url), let document = PDFDocument(data: data) {
114
+ DispatchQueue.main.async {
115
+ self.pdfView.document = document
116
+ self.applyInitialPageIfNeeded()
117
+ self.notifyPageChange()
118
+ }
119
+ }
120
+ }
121
+ } else {
122
+ if let document = PDFDocument(url: url) {
123
+ pdfView.document = document
124
+ applyInitialPageIfNeeded()
125
+ notifyPageChange()
126
+ }
127
+ }
128
+ }
129
+
130
+ @objc func handlePageChange() {
131
+ notifyPageChange()
132
+ }
133
+
134
+ func notifyPageChange() {
135
+ guard let document = pdfView.document, let currentPage = pdfView.currentPage else { return }
136
+ let pageIndex = document.index(for: currentPage)
137
+
138
+ onPageChange([
139
+ "page": pageIndex,
140
+ "total": document.pageCount
141
+ ])
142
+ }
143
+
144
+ func setDisplayMode(_ mode: String) {
145
+ switch mode {
146
+ case "single":
147
+ pdfView.displayMode = .singlePage
148
+ case "continuous":
149
+ pdfView.displayMode = .singlePageContinuous
150
+ case "twoUp":
151
+ pdfView.displayMode = .twoUp
152
+ case "twoUpContinuous":
153
+ pdfView.displayMode = .twoUpContinuous
154
+ default:
155
+ pdfView.displayMode = .singlePageContinuous
156
+ }
157
+ }
158
+
159
+ func setInitialPage(_ page: Int) {
160
+ self.initialPage = max(0, page)
161
+ applyInitialPageIfNeeded()
162
+ }
163
+
164
+ private func applyInitialPageIfNeeded() {
165
+ guard let document = pdfView.document else { return }
166
+ let index = max(0, min(initialPage, document.pageCount - 1))
167
+ if let page = document.page(at: index) {
168
+ pdfView.go(to: page)
169
+ }
170
+ }
171
+
172
+ func zoomIn() -> Bool {
173
+ guard pdfView.document != nil else { return false }
174
+ let newFactor = min(pdfView.scaleFactor * 1.25, maxZoom)
175
+ if newFactor == pdfView.scaleFactor { return false }
176
+ pdfView.scaleFactor = newFactor
177
+ return true
178
+ }
179
+
180
+ func zoomOut() -> Bool {
181
+ guard pdfView.document != nil else { return false }
182
+ let newFactor = max(pdfView.scaleFactor / 1.25, minZoom)
183
+ if newFactor == pdfView.scaleFactor { return false }
184
+ pdfView.scaleFactor = newFactor
185
+ return true
186
+ }
187
+
188
+ func setAnnotationTool(_ tool: String) {
189
+ self.currentTool = tool
190
+
191
+ // Enable/disable gestures based on tool
192
+ let isDrawing = tool == "pen" || tool == "highlighter"
193
+ let isEraser = tool == "eraser"
194
+ panGesture?.isEnabled = isDrawing || isEraser
195
+ pdfView.isUserInteractionEnabled = !isDrawing && !isEraser // Disable PDF interaction when drawing/erasing to prevent scrolling
196
+
197
+ // Re-enable scrolling if not drawing or erasing
198
+ if !isDrawing && !isEraser {
199
+ pdfView.isUserInteractionEnabled = true
200
+ }
201
+
202
+ if tool == "text" || tool == "note" {
203
+ tapGesture?.isEnabled = true
204
+ } else {
205
+ tapGesture?.isEnabled = false
206
+ }
207
+ }
208
+
209
+ func setAnnotationColor(_ colorHex: String?) {
210
+ if let hex = colorHex {
211
+ self.currentColor = UIColor(hex: hex) ?? .black
212
+ }
213
+ }
214
+
215
+ func setAnnotationFontSize(_ size: Double) {
216
+ self.currentFontSize = CGFloat(size)
217
+ }
218
+
219
+ func setAnnotationText(_ text: String) {
220
+ self.currentText = text
221
+ }
222
+
223
+ func setAnnotationStrokeWidth(_ width: Double) {
224
+ // Avoid zero / negative
225
+ let w = CGFloat(width)
226
+ self.currentStrokeWidth = w > 0 ? w : 1.0
227
+ }
228
+
229
+ func setMinZoom(_ value: Double) {
230
+ let v = CGFloat(value)
231
+ minZoom = v > 0 ? v : 0.5
232
+ pdfView.minScaleFactor = minZoom
233
+ if pdfView.scaleFactor < minZoom {
234
+ pdfView.scaleFactor = minZoom
235
+ }
236
+ }
237
+
238
+ func setMaxZoom(_ value: Double) {
239
+ let v = CGFloat(value)
240
+ maxZoom = v > minZoom ? v : minZoom
241
+ pdfView.maxScaleFactor = maxZoom
242
+ if pdfView.scaleFactor > maxZoom {
243
+ pdfView.scaleFactor = maxZoom
244
+ }
245
+ }
246
+
247
+ func searchText(_ text: String) -> [[String: Any]] {
248
+ guard let document = pdfView.document else { return [] }
249
+
250
+ let selections = document.findString(text, withOptions: .caseInsensitive)
251
+ var results: [[String: Any]] = []
252
+
253
+ for selection in selections {
254
+ guard let page = selection.pages.first else { continue }
255
+ let pageIndex = document.index(for: page)
256
+ let bounds = selection.bounds(for: page)
257
+
258
+ let highlight = PDFAnnotation(bounds: bounds, forType: .highlight, withProperties: nil)
259
+ highlight.color = .yellow.withAlphaComponent(0.5)
260
+ page.addAnnotation(highlight)
261
+
262
+ results.append([
263
+ "page": pageIndex,
264
+ "x": bounds.origin.x,
265
+ "y": bounds.origin.y,
266
+ "width": bounds.size.width,
267
+ "height": bounds.size.height,
268
+ "textSnippet": selection.string ?? ""
269
+ ])
270
+ }
271
+
272
+ return results
273
+ }
274
+
275
+ func goToPage(_ pageIndex: Int) {
276
+ if let document = pdfView.document,
277
+ let page = document.page(at: pageIndex) {
278
+ pdfView.go(to: page)
279
+ }
280
+ }
281
+
282
+ func undo() -> Bool {
283
+ guard let document = pdfView.document else { return false }
284
+
285
+ // Find the last annotation across all pages
286
+ var lastAnnotation: PDFAnnotation?
287
+ var lastPage: PDFPage?
288
+
289
+ for i in (0..<document.pageCount).reversed() {
290
+ guard let page = document.page(at: i) else { continue }
291
+ if let annotation = page.annotations.last {
292
+ lastAnnotation = annotation
293
+ lastPage = page
294
+ break
295
+ }
296
+ }
297
+
298
+ guard let annotation = lastAnnotation, let page = lastPage else { return false }
299
+
300
+ // Remove from page and add to undo stack
301
+ page.removeAnnotation(annotation)
302
+ undoStack.append(annotation)
303
+ redoStack.removeAll() // Clear redo when new undo is performed
304
+
305
+ // Emit event
306
+ onAnnotationChange([
307
+ "annotations": getAnnotations()
308
+ ])
309
+
310
+ return true
311
+ }
312
+
313
+ func redo() -> Bool {
314
+ guard undoStack.isEmpty == false else { return false }
315
+ guard let document = pdfView.document else { return false }
316
+
317
+ // Get the last undone annotation
318
+ let annotation = undoStack.removeLast()
319
+
320
+ // Find which page it belongs to (we'll need to track this better in a real implementation)
321
+ // For now, try to add it to the current page
322
+ if let currentPage = pdfView.currentPage {
323
+ currentPage.addAnnotation(annotation)
324
+ } else if let firstPage = document.page(at: 0) {
325
+ firstPage.addAnnotation(annotation)
326
+ }
327
+
328
+ // Emit event
329
+ onAnnotationChange([
330
+ "annotations": getAnnotations()
331
+ ])
332
+
333
+ return true
334
+ }
335
+
336
+ func renderThumbnail(pageIndex: Int, width: Double) throws -> String {
337
+ guard let document = pdfView.document else {
338
+ throw NSError(domain: "ExpoPdfReader", code: 3, userInfo: [NSLocalizedDescriptionKey: "No document loaded"])
339
+ }
340
+ guard let page = document.page(at: pageIndex) else {
341
+ throw NSError(domain: "ExpoPdfReader", code: 4, userInfo: [NSLocalizedDescriptionKey: "Invalid page index"])
342
+ }
343
+
344
+ let pageRect = page.bounds(for: .mediaBox)
345
+ let targetWidth = CGFloat(width)
346
+ let scale = targetWidth / pageRect.width
347
+ let size = CGSize(width: targetWidth, height: pageRect.height * scale)
348
+
349
+ let image = page.thumbnail(of: size, for: .mediaBox)
350
+ guard let data = image.pngData() else {
351
+ throw NSError(domain: "ExpoPdfReader", code: 5, userInfo: [NSLocalizedDescriptionKey: "Failed to create PNG data"])
352
+ }
353
+
354
+ let fileName = "thumb_\(pageIndex)_\(Int(Date().timeIntervalSince1970)).png"
355
+ let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
356
+ try data.write(to: url)
357
+ return url.absoluteString
358
+ }
359
+
360
+ func savePdf() throws -> String {
361
+ guard let document = pdfView.document else {
362
+ throw NSError(domain: "ExpoPdfReader", code: 1, userInfo: [NSLocalizedDescriptionKey: "No document loaded"])
363
+ }
364
+
365
+ let fileName = "saved_pdf_\(Int(Date().timeIntervalSince1970)).pdf"
366
+ let fileManager = FileManager.default
367
+ let tempDir = fileManager.temporaryDirectory
368
+ let fileURL = tempDir.appendingPathComponent(fileName)
369
+
370
+ if document.write(to: fileURL) {
371
+ return fileURL.absoluteString
372
+ } else {
373
+ throw NSError(domain: "ExpoPdfReader", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to write PDF to file"])
374
+ }
375
+ }
376
+
377
+ func getAnnotations() -> [[String: Any]] {
378
+ guard let document = pdfView.document else { return [] }
379
+ var annotationsData: [[String: Any]] = []
380
+
381
+ for i in 0..<document.pageCount {
382
+ guard let page = document.page(at: i) else { continue }
383
+ for annotation in page.annotations {
384
+ if annotation.type == "Ink" {
385
+ var points: [[String: Double]] = []
386
+
387
+ // Simplified extraction logic
388
+
389
+ var colorHex = "#000000"
390
+ if let color = annotation.color {
391
+ colorHex = color.toHex() ?? "#000000"
392
+ }
393
+
394
+ var type = "pen"
395
+ if let color = annotation.color {
396
+ var a: CGFloat = 0
397
+ color.getWhite(nil, alpha: &a)
398
+ if a < 0.5 {
399
+ type = "highlighter"
400
+ }
401
+ }
402
+
403
+ annotationsData.append([
404
+ "type": type,
405
+ "color": colorHex,
406
+ "page": i,
407
+ "points": [points],
408
+ "width": annotation.border?.lineWidth ?? 10.0
409
+ ])
410
+ } else if annotation.type == "FreeText" {
411
+ var colorHex = "#000000"
412
+ if let color = annotation.fontColor {
413
+ colorHex = color.toHex() ?? "#000000"
414
+ }
415
+
416
+ annotationsData.append([
417
+ "type": "text",
418
+ "color": colorHex,
419
+ "page": i,
420
+ "text": annotation.contents ?? "",
421
+ "fontSize": annotation.font?.pointSize ?? 16.0,
422
+ "x": annotation.bounds.origin.x,
423
+ "y": annotation.bounds.origin.y
424
+ ])
425
+ }
426
+ }
427
+ }
428
+ return annotationsData
429
+ }
430
+
431
+ func setAnnotations(_ annotations: [[String: Any]]) {
432
+ guard let document = pdfView.document else { return }
433
+
434
+ // Clear undo/redo stacks when setting annotations
435
+ undoStack.removeAll()
436
+ redoStack.removeAll()
437
+
438
+ for data in annotations {
439
+ guard let pageIndex = data["page"] as? Int,
440
+ let page = document.page(at: pageIndex),
441
+ let type = data["type"] as? String,
442
+ let colorHex = data["color"] as? String else { continue }
443
+
444
+ let color = UIColor(hex: colorHex) ?? .black
445
+
446
+ if type == "text" {
447
+ guard let text = data["text"] as? String,
448
+ let fontSize = data["fontSize"] as? Double,
449
+ let x = data["x"] as? Double,
450
+ let y = data["y"] as? Double else { continue }
451
+
452
+ let annotation = PDFAnnotation(bounds: CGRect(x: x, y: y, width: 200, height: 50), forType: .freeText, withProperties: nil)
453
+ annotation.contents = text
454
+ annotation.font = UIFont.systemFont(ofSize: CGFloat(fontSize))
455
+ annotation.fontColor = color
456
+ annotation.color = .clear
457
+ page.addAnnotation(annotation)
458
+
459
+ } else {
460
+ guard let pointsArray = data["points"] as? [[[String: Double]]] else { continue }
461
+
462
+ for stroke in pointsArray {
463
+ if stroke.isEmpty { continue }
464
+
465
+ let path = UIBezierPath()
466
+ let start = stroke[0]
467
+ path.move(to: CGPoint(x: start["x"]!, y: start["y"]!))
468
+
469
+ for i in 1..<stroke.count {
470
+ let pt = stroke[i]
471
+ path.addLine(to: CGPoint(x: pt["x"]!, y: pt["y"]!))
472
+ }
473
+
474
+ let bounds = page.bounds(for: pdfView.displayBox)
475
+
476
+ let annotation = PDFAnnotation(bounds: bounds, forType: .ink, withProperties: nil)
477
+ annotation.add(path)
478
+
479
+ if type == "highlighter" {
480
+ annotation.color = color.withAlphaComponent(0.3)
481
+ } else {
482
+ annotation.color = color
483
+ }
484
+
485
+ page.addAnnotation(annotation)
486
+ }
487
+ }
488
+ }
489
+ }
490
+
491
+ @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
492
+ guard let page = pdfView.currentPage else { return }
493
+ let location = gesture.location(in: pdfView)
494
+ let convertedPoint = pdfView.convert(location, to: page)
495
+
496
+ // Handle eraser
497
+ if currentTool == "eraser" {
498
+ switch gesture.state {
499
+ case .began, .changed:
500
+ // Find annotation at touch point
501
+ let tolerance: CGFloat = 20.0
502
+ for annotation in page.annotations {
503
+ let bounds = annotation.bounds
504
+ let expandedBounds = bounds.insetBy(dx: -tolerance, dy: -tolerance)
505
+
506
+ if expandedBounds.contains(convertedPoint) {
507
+ // Check if point is actually on the annotation path
508
+ if let paths = annotation.paths {
509
+ for path in paths {
510
+ let cgPath = path.cgPath
511
+ if cgPath.contains(convertedPoint) || cgPath.boundingBox.contains(convertedPoint) {
512
+ page.removeAnnotation(annotation)
513
+ undoStack.append(annotation)
514
+ redoStack.removeAll()
515
+
516
+ // Emit event
517
+ onAnnotationChange([
518
+ "annotations": getAnnotations()
519
+ ])
520
+ return
521
+ }
522
+ }
523
+ } else {
524
+ // For non-path annotations (like text), just check bounds
525
+ page.removeAnnotation(annotation)
526
+ undoStack.append(annotation)
527
+ redoStack.removeAll()
528
+
529
+ // Emit event
530
+ onAnnotationChange([
531
+ "annotations": getAnnotations()
532
+ ])
533
+ return
534
+ }
535
+ }
536
+ }
537
+ default:
538
+ break
539
+ }
540
+ return
541
+ }
542
+
543
+ switch gesture.state {
544
+ case .began:
545
+ currentPath = UIBezierPath()
546
+ currentPath?.move(to: convertedPoint)
547
+
548
+ // Create annotation
549
+ let bounds = page.bounds(for: pdfView.displayBox)
550
+
551
+ if currentTool == "pen" {
552
+ let annotation = PDFAnnotation(bounds: bounds, forType: .ink, withProperties: nil)
553
+ annotation.color = currentColor
554
+ let border = PDFBorder()
555
+ border.lineWidth = currentStrokeWidth
556
+ annotation.border = border
557
+ annotation.add(currentPath!)
558
+ page.addAnnotation(annotation)
559
+ currentAnnotation = annotation
560
+ }
561
+ else if currentTool == "highlighter" {
562
+ let annotation = PDFAnnotation(bounds: bounds, forType: .ink, withProperties: nil)
563
+ annotation.color = currentColor.withAlphaComponent(0.3)
564
+ let border = PDFBorder()
565
+ border.lineWidth = currentStrokeWidth * 3.0
566
+ annotation.border = border
567
+ annotation.add(currentPath!)
568
+ page.addAnnotation(annotation)
569
+ currentAnnotation = annotation
570
+ }
571
+
572
+ case .changed:
573
+ guard let path = currentPath, let annotation = currentAnnotation else { return }
574
+ path.addLine(to: convertedPoint)
575
+ annotation.add(path) // Update path
576
+
577
+ case .ended, .cancelled:
578
+ // Clear redo stack when new annotation is added
579
+ redoStack.removeAll()
580
+
581
+ currentPath = nil
582
+ currentAnnotation = nil
583
+
584
+ // Emit event
585
+ onAnnotationChange([
586
+ "annotations": getAnnotations()
587
+ ])
588
+
589
+ default:
590
+ break
591
+ }
592
+ }
593
+
594
+ @objc func handleTap(_ gesture: UITapGestureRecognizer) {
595
+ guard let page = pdfView.currentPage else { return }
596
+ let location = gesture.location(in: pdfView)
597
+ let convertedPoint = pdfView.convert(location, to: page)
598
+
599
+ if currentTool == "text" {
600
+ let annotation = PDFAnnotation(bounds: CGRect(x: convertedPoint.x, y: convertedPoint.y, width: 200, height: 50), forType: .freeText, withProperties: nil)
601
+ annotation.contents = currentText
602
+ annotation.font = UIFont.systemFont(ofSize: currentFontSize)
603
+ annotation.fontColor = currentColor
604
+ annotation.color = .clear // Background
605
+ page.addAnnotation(annotation)
606
+
607
+ // Clear redo stack when new annotation is added
608
+ redoStack.removeAll()
609
+
610
+ onAnnotationChange([
611
+ "annotations": getAnnotations()
612
+ ])
613
+
614
+ } else if currentTool == "note" {
615
+ let annotation = PDFAnnotation(bounds: CGRect(x: convertedPoint.x, y: convertedPoint.y, width: 20, height: 20), forType: .text, withProperties: nil)
616
+ annotation.iconType = .comment
617
+ annotation.color = currentColor
618
+ page.addAnnotation(annotation)
619
+
620
+ // Clear redo stack when new annotation is added
621
+ redoStack.removeAll()
622
+
623
+ onAnnotationChange([
624
+ "annotations": getAnnotations()
625
+ ])
626
+ }
627
+ }
628
+ }
629
+
630
+ extension UIColor {
631
+ convenience init?(hex: String) {
632
+ var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
633
+ hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
634
+
635
+ var rgb: UInt64 = 0
636
+
637
+ var r: CGFloat = 0.0
638
+ var g: CGFloat = 0.0
639
+ var b: CGFloat = 0.0
640
+ var a: CGFloat = 1.0
641
+
642
+ let length = hexSanitized.count
643
+
644
+ guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
645
+
646
+ if length == 6 {
647
+ r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
648
+ g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
649
+ b = CGFloat(rgb & 0x0000FF) / 255.0
650
+
651
+ } else if length == 8 {
652
+ r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
653
+ g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
654
+ b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
655
+ a = CGFloat(rgb & 0x000000FF) / 255.0
656
+
657
+ } else {
658
+ return nil
659
+ }
660
+
661
+ self.init(red: r, green: g, blue: b, alpha: a)
662
+ }
663
+
664
+ func toHex() -> String? {
665
+ var r: CGFloat = 0
666
+ var g: CGFloat = 0
667
+ var b: CGFloat = 0
668
+ var a: CGFloat = 0
669
+
670
+ guard getRed(&r, green: &g, blue: &b, alpha: &a) else { return nil }
671
+
672
+ let rgb: Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
673
+ return String(format: "#%06x", rgb)
674
+ }
675
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@june24/expo-pdf-reader",
3
+ "version": "0.1.0",
4
+ "description": "A PDF reader for Expo",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepublishOnly": "expo-module prepublishOnly"
13
+ },
14
+ "keywords": [
15
+ "react-native",
16
+ "expo",
17
+ "expo-module",
18
+ "pdf"
19
+ ],
20
+ "author": "User",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "expo-modules-core": "^3.0.29"
24
+ },
25
+ "devDependencies": {
26
+ "expo-module-scripts": "^5.0.8",
27
+ "typescript": "^5.9.3",
28
+ "@types/react": "^19.2.9",
29
+ "react": "19.2.3",
30
+ "react-native": "0.83.1"
31
+ },
32
+ "peerDependencies": {
33
+ "expo": "*",
34
+ "react": "*",
35
+ "react-native": "*"
36
+ }
37
+ }