@june24/expo-pdf-reader 0.1.20 → 0.1.22

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.
@@ -89,6 +89,7 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
89
89
  private val onTextPress = ViewEvent<Map<String, Any>>("onTextPress", this, null)
90
90
 
91
91
  private var pageHeights = mutableListOf<Int>()
92
+ private var rowHeights = mutableListOf<Int>()
92
93
  private var pageWidthsPdf = mutableListOf<Float>()
93
94
  private var pageHeightsPdf = mutableListOf<Float>()
94
95
  private var pageViews = mutableListOf<ImageView?>()
@@ -383,6 +384,7 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
383
384
  addView(scrollView)
384
385
  scrollView.viewTreeObserver.addOnScrollChangedListener {
385
386
  notifyPageChange()
387
+ scrollView.post { updateVisiblePages() }
386
388
  }
387
389
  viewTreeObserver.addOnGlobalLayoutListener {
388
390
  if (width <= 0) {
@@ -552,13 +554,38 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
552
554
  notifyAnnotationChange()
553
555
  }
554
556
 
557
+ private fun isTwoUpMode(): Boolean = displayMode == "twoUp" || displayMode == "twoUpContinuous"
558
+
555
559
  private fun marginBetweenPages(): Int = if (displayMode == "single") 0 else 16
556
560
 
561
+ private fun getFrameForPage(pageIndex: Int): FrameLayout? {
562
+ if (pageIndex !in pageHeights.indices) return null
563
+ return if (isTwoUpMode()) {
564
+ val row = pageIndex / 2
565
+ val col = pageIndex % 2
566
+ if (row >= container.childCount) null
567
+ else (container.getChildAt(row) as? LinearLayout)?.getChildAt(col) as? FrameLayout
568
+ } else {
569
+ container.getChildAt(pageIndex) as? FrameLayout
570
+ }
571
+ }
572
+
557
573
  private fun scrollToPage(pageIndex: Int) {
558
574
  if (pageHeights.isEmpty()) return
559
- var offset = 0
560
- for (i in 0 until pageIndex.coerceIn(0, pageHeights.size - 1)) {
561
- offset += pageHeights[i] + marginBetweenPages()
575
+ val idx = pageIndex.coerceIn(0, pageHeights.size - 1)
576
+ val gap = marginBetweenPages()
577
+ val offset = if (isTwoUpMode() && rowHeights.isNotEmpty()) {
578
+ var sum = 0
579
+ for (r in 0 until idx / 2) {
580
+ sum += rowHeights[r] + gap
581
+ }
582
+ sum
583
+ } else {
584
+ var sum = 0
585
+ for (i in 0 until idx) {
586
+ sum += pageHeights[i] + gap
587
+ }
588
+ sum
562
589
  }
563
590
  scrollView.post { scrollView.smoothScrollTo(0, offset) }
564
591
  }
@@ -598,6 +625,7 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
598
625
  fileDescriptor?.close()
599
626
  fileDescriptor = null
600
627
  pageHeights.clear()
628
+ rowHeights.clear()
601
629
  pageWidthsPdf.clear()
602
630
  pageHeightsPdf.clear()
603
631
  pageViews.clear()
@@ -632,33 +660,39 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
632
660
  updateVisiblePages()
633
661
  return
634
662
  }
635
- val viewHeight = height.coerceAtLeast(1)
636
663
  val viewWidth = width.coerceAtLeast(1)
664
+ val viewHeight = height.coerceAtLeast(1)
665
+ val isTwoUp = displayMode == "twoUp" || displayMode == "twoUpContinuous"
637
666
  val isSingle = displayMode == "single"
667
+ val effectiveWidth = if (isTwoUp) viewWidth / 2 else viewWidth
638
668
  scope.launch {
639
- val w = viewWidth
669
+ val w = effectiveWidth
670
+ val minSlotHeight = if (isSingle) viewHeight else 0
640
671
  val result = withContext(Dispatchers.Default) {
641
672
  (0 until renderer.pageCount).map { i ->
642
673
  val page = renderer.openPage(i)
643
674
  try {
644
- val pageH = if (isSingle) {
645
- viewHeight
646
- } else {
647
- (w.toFloat() * page.height / page.width).toInt()
648
- }
675
+ val naturalH = (w.toFloat() * page.height / page.width).toInt()
676
+ val pageH = if (isSingle) maxOf(naturalH, minSlotHeight) else naturalH
649
677
  Triple(
650
678
  i,
651
679
  pageH,
652
680
  Pair(page.width.toFloat(), page.height.toFloat())
653
681
  )
654
682
  } finally {
655
- page.close()
683
+ try {
684
+ page.close()
685
+ } catch (e: Exception) {
686
+ // Renderer may have been closed on main thread (e.g. resetRenderer)
687
+ }
656
688
  }
657
689
  }
658
690
  }
659
691
  if (!isActive) return@launch
660
692
  withContext(Dispatchers.Main) {
693
+ if (pdfRenderer == null) return@launch
661
694
  pageHeights.clear()
695
+ rowHeights.clear()
662
696
  pageWidthsPdf.clear()
663
697
  pageHeightsPdf.clear()
664
698
  for ((_, h, pdfSize) in result) {
@@ -666,6 +700,14 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
666
700
  pageWidthsPdf.add(pdfSize.first)
667
701
  pageHeightsPdf.add(pdfSize.second)
668
702
  }
703
+ if (isTwoUp) {
704
+ val rowCount = (renderer.pageCount + 1) / 2
705
+ for (r in 0 until rowCount) {
706
+ val h0 = pageHeights.getOrElse(2 * r) { 0 }
707
+ val h1 = pageHeights.getOrElse(2 * r + 1) { 0 }
708
+ rowHeights.add(maxOf(h0, h1))
709
+ }
710
+ }
669
711
  ensurePlaceholders(renderer)
670
712
  scrollToPage(initialPage.coerceIn(0, (renderer.pageCount - 1).coerceAtLeast(0)))
671
713
  updateVisiblePages()
@@ -682,34 +724,81 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
682
724
  while (pageAnnotations.size < pageCount) {
683
725
  pageAnnotations.add(mutableListOf())
684
726
  }
685
- for (i in 0 until pageCount) {
686
- val frame = FrameLayout(context).apply {
687
- layoutParams = LinearLayout.LayoutParams(
688
- LinearLayout.LayoutParams.MATCH_PARENT,
689
- pageHeights[i]
690
- ).apply { setMargins(0, 0, 0, marginBetween) }
691
- setBackgroundColor(Color.LTGRAY)
727
+ if (isTwoUpMode() && rowHeights.isNotEmpty()) {
728
+ val rowCount = rowHeights.size
729
+ for (r in 0 until rowCount) {
730
+ val rowLayout = LinearLayout(context).apply {
731
+ orientation = LinearLayout.HORIZONTAL
732
+ layoutParams = LinearLayout.LayoutParams(
733
+ LinearLayout.LayoutParams.MATCH_PARENT,
734
+ rowHeights[r]
735
+ ).apply { setMargins(0, 0, 0, marginBetween) }
736
+ }
737
+ for (col in 0..1) {
738
+ val i = 2 * r + col
739
+ if (i >= pageCount) break
740
+ val frame = FrameLayout(context).apply {
741
+ layoutParams = LinearLayout.LayoutParams(
742
+ 0,
743
+ LinearLayout.LayoutParams.MATCH_PARENT,
744
+ 1f
745
+ ).apply { setMargins(0, 0, if (col == 0) 8 else 0, 0) }
746
+ setBackgroundColor(Color.LTGRAY)
747
+ }
748
+ val overlay = AnnotationOverlayView(context, i).apply {
749
+ layoutParams = FrameLayout.LayoutParams(
750
+ FrameLayout.LayoutParams.MATCH_PARENT,
751
+ FrameLayout.LayoutParams.MATCH_PARENT
752
+ )
753
+ }
754
+ frame.addView(overlay)
755
+ rowLayout.addView(frame)
756
+ overlayViews[i] = overlay
757
+ }
758
+ container.addView(rowLayout)
692
759
  }
693
- val overlay = AnnotationOverlayView(context, i).apply {
694
- layoutParams = FrameLayout.LayoutParams(
695
- FrameLayout.LayoutParams.MATCH_PARENT,
696
- FrameLayout.LayoutParams.MATCH_PARENT
697
- )
760
+ } else {
761
+ for (i in 0 until pageCount) {
762
+ val frame = FrameLayout(context).apply {
763
+ layoutParams = LinearLayout.LayoutParams(
764
+ LinearLayout.LayoutParams.MATCH_PARENT,
765
+ pageHeights[i]
766
+ ).apply { setMargins(0, 0, 0, marginBetween) }
767
+ setBackgroundColor(Color.LTGRAY)
768
+ }
769
+ val overlay = AnnotationOverlayView(context, i).apply {
770
+ layoutParams = FrameLayout.LayoutParams(
771
+ FrameLayout.LayoutParams.MATCH_PARENT,
772
+ FrameLayout.LayoutParams.MATCH_PARENT
773
+ )
774
+ }
775
+ frame.addView(overlay)
776
+ container.addView(frame)
777
+ overlayViews[i] = overlay
698
778
  }
699
- frame.addView(overlay)
700
- container.addView(frame)
701
- overlayViews[i] = overlay
702
779
  }
703
780
  }
704
781
 
705
782
  private fun currentPageFromScroll(): Int {
706
783
  if (pageHeights.isEmpty()) return 0
707
784
  val scrollY = scrollView.scrollY
708
- var offset = 0
709
785
  val gap = marginBetweenPages()
786
+ if (isTwoUpMode() && rowHeights.isNotEmpty()) {
787
+ var offset = 0
788
+ for (r in rowHeights.indices) {
789
+ val rowH = rowHeights[r]
790
+ if (scrollY < offset + rowH / 2) return (2 * r).coerceAtMost(pageHeights.size - 1)
791
+ if (scrollY < offset + rowH) return (2 * r + 1).coerceAtMost(pageHeights.size - 1)
792
+ offset += rowH + gap
793
+ }
794
+ return (pageHeights.size - 1).coerceAtLeast(0)
795
+ }
796
+ val viewportCenter = scrollY + scrollView.height / 2
797
+ var offset = 0
710
798
  for (i in pageHeights.indices) {
711
- if (scrollY < offset + pageHeights[i] / 2) return i
712
- offset += pageHeights[i] + gap
799
+ val pageEnd = offset + pageHeights[i]
800
+ if (viewportCenter < pageEnd) return i
801
+ offset = pageEnd + gap
713
802
  }
714
803
  return (pageHeights.size - 1).coerceAtLeast(0)
715
804
  }
@@ -717,13 +806,17 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
717
806
  private fun updateVisiblePages() {
718
807
  val renderer = pdfRenderer ?: return
719
808
  val currentPage = currentPageFromScroll()
720
- val from = (currentPage - renderWindowBefore).coerceAtLeast(0)
721
- val to = (currentPage + renderWindowAfter).coerceAtMost(renderer.pageCount - 1)
809
+ val (from, to) = if (displayMode == "single") {
810
+ currentPage to currentPage
811
+ } else {
812
+ (currentPage - renderWindowBefore).coerceAtLeast(0) to
813
+ (currentPage + renderWindowAfter).coerceAtMost(renderer.pageCount - 1)
814
+ }
722
815
 
723
816
  for (i in pageViews.indices) {
724
817
  if (i in from..to) continue
725
818
  val iv = pageViews.getOrNull(i) ?: continue
726
- val frame = container.getChildAt(i) as? FrameLayout ?: continue
819
+ val frame = getFrameForPage(i) ?: continue
727
820
  val bmp = (iv.drawable as? BitmapDrawable)?.bitmap
728
821
  iv.setImageBitmap(null)
729
822
  bmp?.recycle()
@@ -740,11 +833,10 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
740
833
 
741
834
  private fun renderPageToView(pageIndex: Int) {
742
835
  val renderer = pdfRenderer ?: return
743
- val frame = container.getChildAt(pageIndex) as? FrameLayout ?: return
744
- // Frame always has overlay; only skip if we already have an ImageView for this page
836
+ val frame = getFrameForPage(pageIndex) ?: return
745
837
  if (pageViews.getOrNull(pageIndex) != null) return
746
838
  val page = renderer.openPage(pageIndex)
747
- val w = width.coerceAtLeast(1)
839
+ val w = if (isTwoUpMode()) (width / 2).coerceAtLeast(1) else width.coerceAtLeast(1)
748
840
  val h = pageHeights[pageIndex]
749
841
  val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
750
842
  page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
@@ -766,18 +858,7 @@ class ExpoPdfReaderView(context: Context, appContext: AppContext) : ExpoView(con
766
858
 
767
859
  private fun notifyPageChange() {
768
860
  if (pageHeights.isEmpty()) return
769
- val scrollY = scrollView.scrollY
770
- var offset = 0
771
- var detectedPage = 0
772
- val gap = marginBetweenPages()
773
- for (i in pageHeights.indices) {
774
- if (scrollY < offset + pageHeights[i] / 2) {
775
- detectedPage = i
776
- break
777
- }
778
- offset += pageHeights[i] + gap
779
- detectedPage = i
780
- }
861
+ val detectedPage = currentPageFromScroll()
781
862
  if (detectedPage != lastEmittedPageIndex) {
782
863
  lastEmittedPageIndex = detectedPage
783
864
  onPageChange(mapOf(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@june24/expo-pdf-reader",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "A PDF reader for Expo apps with annotation and zoom controls",
5
5
  "homepage": "git@gitlab.com:june_241/expo-pdf-reader.git",
6
6
  "main": "build/index.js",