@@ -11,13 +11,18 @@ import SwiftSoup
1111import  UIKit
1212import  WebKit
1313
14- public  protocol  EPUBNavigatorDelegate :  VisualNavigatorDelegate ,  SelectableNavigatorDelegate  { 
14+ @MainActor   public  protocol  EPUBNavigatorDelegate :  VisualNavigatorDelegate ,  SelectableNavigatorDelegate  { 
15+     /// Called when the viewport is updated.
16+     func  navigator( _ navigator:  EPUBNavigatorViewController ,  viewportDidChange viewport:  EPUBNavigatorViewController . Viewport ? ) 
17+ 
1518    // MARK: - WebView Customization
1619
1720    func  navigator( _ navigator:  EPUBNavigatorViewController ,  setupUserScripts userContentController:  WKUserContentController ) 
1821} 
1922
2023public  extension  EPUBNavigatorDelegate  { 
24+     func  navigator( _ navigator:  EPUBNavigatorViewController ,  viewportDidChange viewport:  EPUBNavigatorViewController . Viewport ? )  { } 
25+ 
2126    func  navigator( _ navigator:  EPUBNavigatorViewController ,  setupUserScripts userContentController:  WKUserContentController )  { } 
2227} 
2328
@@ -117,6 +122,24 @@ open class EPUBNavigatorViewController: InputObservableViewController,
117122
118123    public  weak var  delegate :  EPUBNavigatorDelegate ? 
119124
125+     /// Information about the visible portion of the publication, when rendered.
126+     public  private( set)   var  viewport :  Viewport ?   { 
127+         didSet { 
128+             if  oldValue !=  viewport { 
129+                 delegate? . navigator ( self ,  viewportDidChange:  viewport) 
130+             } 
131+         } 
132+     } 
133+ 
134+     /// Information about the visible portion of the publication.
135+     public  struct  Viewport :  Equatable  { 
136+         /// Indices of the visible reading order resources.
137+         public  var  readingOrderIndices :  ClosedRange < [ Link ] . Index> 
138+ 
139+         /// Range of visible positions.
140+         public  var  positions :  ClosedRange < Int > ? 
141+     } 
142+ 
120143    /// Navigation state.
121144    private  enum  State :  Equatable  { 
122145        /// Initializing the navigator.
@@ -488,15 +511,6 @@ open class EPUBNavigatorViewController: InputObservableViewController,
488511        paginationView? . currentIndex ??  0 
489512    } 
490513
491-     // Reading order index of the left-most resource in the visible spread.
492-     private  var  currentResourceIndex :  Int ?   { 
493-         guard  spreads. indices. contains ( currentSpreadIndex)  else  { 
494-             return  nil 
495-         } 
496- 
497-         return  readingOrder. firstIndexWithHREF ( spreads [ currentSpreadIndex] . left. url ( ) ) 
498-     } 
499- 
500514    private  var  reloadSpreadsContinuations   =  [ CheckedContinuation < Void ,  Never > ] ( ) 
501515    private  var  needsReloadSpreads   =  false 
502516
@@ -542,8 +556,12 @@ open class EPUBNavigatorViewController: InputObservableViewController,
542556            spread:  viewModel. spreadEnabled
543557        ) 
544558
545-         let  initialIndex :  Int  =  { 
546-             if  let  href =  locator? . href,  let  foundIndex =  self . spreads. firstIndexWithHREF ( href)  { 
559+         let  initialIndex :  ReadingOrder . Index  =  { 
560+             if 
561+                 let  href =  locator? . href, 
562+                 let  index =  readingOrder. firstIndexWithHREF ( href) , 
563+                 let  foundIndex =  self . spreads. firstIndexWithReadingOrderIndex ( index) 
564+             { 
547565                return  foundIndex
548566            }  else  { 
549567                return  0 
@@ -561,9 +579,16 @@ open class EPUBNavigatorViewController: InputObservableViewController,
561579    } 
562580
563581    private  func  loadedSpreadViewForHREF< T:  URLConvertible > ( _ href:  T )  ->  EPUBSpreadView ?   { 
564-         paginationView? . loadedViews
582+         guard 
583+             let  loadedViews =  paginationView? . loadedViews, 
584+             let  index =  readingOrder. firstIndexWithHREF ( href) 
585+         else  { 
586+             return  nil 
587+         } 
588+ 
589+         return  loadedViews
565590            . compactMap  {  _,  view in  view as?  EPUBSpreadView  } 
566-             . first  {  $0. spread. links . firstWithHREF ( href )   !=   nil  } 
591+             . first  {  $0. spread. contains ( index :  index )  } 
567592    } 
568593
569594    // MARK: - Navigator
@@ -582,45 +607,64 @@ open class EPUBNavigatorViewController: InputObservableViewController,
582607        ) 
583608    } 
584609
585-     private  func  computeCurrentLocation ( )  async  ->  Locator ?   { 
610+     private  func  computeCurrentLocationAndViewport ( )  async  ->  ( Locator ? ,   Viewport ? )  { 
586611        if  case . initializing =  state { 
587612            assertionFailure ( " Cannot update current location when initializing the navigator " ) 
588-             return  nil 
613+             return  ( nil ,   nil ) 
589614        } 
590615
591616        // Returns any pending locator to prevent returning invalid locations
592617        // while loading it.
593618        if  let  pendingLocator =  state. pendingLocator { 
594-             return  pendingLocator
619+             return  ( pendingLocator,   nil ) 
595620        } 
596621
597622        guard  let  spreadView =  paginationView? . currentView as?  EPUBSpreadView  else  { 
598-             return  nil 
623+             return  ( nil ,   nil ) 
599624        } 
600625
601-         let  link  =  spreadView. focusedResource ??  spreadView. spread. leading
626+         let  index  =  spreadView. focusedResource ??  spreadView. spread. leading
627+         let  link  =  readingOrder [ index] 
602628        let  href  =  link. url ( ) 
603-         let  progression  =  min ( max ( spreadView. progression ( in:  href) ,  0.0 ) ,  1.0 ) 
629+         let  progressionRange  =  spreadView. progression ( in:  index) 
630+         let  firstProgression  =  min ( max ( progressionRange. lowerBound,  0.0 ) ,  1.0 ) 
631+         let  lastProgression  =  min ( max ( progressionRange. upperBound,  0.0 ) ,  1.0 ) 
632+ 
633+         let  location :  Locator ? 
634+         var  viewport  =  Viewport ( 
635+             readingOrderIndices:  spreadView. spread. readingOrderIndices, 
636+             positions:  nil 
637+         ) 
604638
605639        if 
606640            // The positions are not always available, for example a Readium
607641            // WebPub doesn't have any unless a Publication Positions Web
608642            // Service is provided
609643            let  index =  readingOrder. firstIndexWithHREF ( href) , 
610644            let  positionList =  positionsByReadingOrder. getOrNil ( index) , 
611-             positionList. count >  0 
645+             positionList. count >  0 , 
646+             let  positionOffset =  positionList [ 0 ] . locations. position
612647        { 
613648            // Gets the current locator from the positionList, and fill its missing data.
614-             let  positionIndex  =  Int ( ceil ( progression *  Double( positionList. count -  1 ) ) ) 
615-             return  await  positionList [ positionIndex] . copy ( 
649+             let  firstPositionIndex  =  Int ( ceil ( firstProgression *  Double( positionList. count -  1 ) ) ) 
650+             let  lastPositionIndex  =  ( lastProgression ==  1.0 ) 
651+                 ?  positionList. count -  1 
652+                 :  max ( firstPositionIndex,  Int ( ceil ( lastProgression *  Double( positionList. count -  1 ) ) )  -  1 ) 
653+ 
654+             location =  await  positionList [ firstPositionIndex] . copy ( 
616655                title:  tableOfContentsTitleByHref [ equivalent:  href] , 
617-                 locations:  {  $0. progression =  progression  } 
656+                 locations:  {  $0. progression =  firstProgression  } 
618657            ) 
658+ 
659+             viewport. positions =  ( positionOffset +  firstPositionIndex)  ...  ( positionOffset +  lastPositionIndex) 
660+ 
619661        }  else  { 
620-             return  await  publication. locate ( link) ? . copy ( 
621-                 locations:  {  $0. progression =  progression  } 
662+             location  =  await  publication. locate ( link) ? . copy ( 
663+                 locations:  {  $0. progression =  firstProgression  } 
622664            ) 
623665        } 
666+ 
667+         return  ( location,  viewport) 
624668    } 
625669
626670    public  func  firstVisibleElementLocator( )  async  ->  Locator ?   { 
@@ -643,7 +687,7 @@ open class EPUBNavigatorViewController: InputObservableViewController,
643687            return 
644688        } 
645689
646-         currentLocation  =  await  computeCurrentLocation ( ) 
690+         ( currentLocation,  viewport )   =  await  computeCurrentLocationAndViewport ( ) 
647691
648692        if 
649693            let  delegate =  delegate, 
@@ -660,7 +704,8 @@ open class EPUBNavigatorViewController: InputObservableViewController,
660704
661705        guard 
662706            let  paginationView =  paginationView, 
663-             let  spreadIndex =  spreads. firstIndexWithHREF ( locator. href) , 
707+             let  index =  readingOrder. firstIndexWithHREF ( locator. href) , 
708+             let  spreadIndex =  spreads. firstIndexWithReadingOrderIndex ( index) , 
664709            on ( . jump( locator) ) 
665710        else  { 
666711            return  false 
@@ -900,7 +945,8 @@ extension EPUBNavigatorViewController: EPUBNavigatorViewModelDelegate {
900945                for  (_,  view)    in  paginationView. loadedViews { 
901946                    guard 
902947                        let  view =  view as?  EPUBSpreadView , 
903-                         view. spread. links. firstWithHREF ( href)  !=  nil 
948+                         let  index =  readingOrder. firstIndexWithHREF ( href) , 
949+                         view. spread. contains ( index:  index) 
904950                    else  { 
905951                        continue 
906952                    } 
@@ -943,7 +989,10 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {
943989            } 
944990            . joined ( separator:  " \n " ) 
945991
946-         for  link  in  spreadView. spread. links { 
992+         let  links  =  spreadView. spread. readingOrderIndices
993+             . compactMap  {  readingOrder. getOrNil ( $0)  } 
994+ 
995+         for  link  in  links { 
947996            let  href  =  link. url ( ) 
948997            for  (group,  decorations)    in  decorations { 
949998                let  decorations  =  decorations
0 commit comments