feat(wxread): align pagination, rendering, and docs
This commit is contained in:
@@ -58,6 +58,11 @@ public final class RDEPUBReaderController: UIViewController {
|
||||
currentVisibleLocation()
|
||||
}
|
||||
|
||||
public var currentPageNumber: Int? {
|
||||
guard readerView.currentPage >= 0 else { return nil }
|
||||
return readerView.currentPage + 1
|
||||
}
|
||||
|
||||
public private(set) var currentSelection: RDEPUBSelection?
|
||||
|
||||
public var highlights: [RDEPUBHighlight] {
|
||||
@@ -263,6 +268,27 @@ public final class RDEPUBReaderController: UIViewController {
|
||||
restoreReadingLocation(location)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func go(toPageNumber pageNumber: Int, animated: Bool = false) -> Bool {
|
||||
guard pageNumber > 0 else { return false }
|
||||
|
||||
if textBook != nil {
|
||||
guard let location = resolvedTextLocation(forPageNumber: pageNumber) else {
|
||||
return false
|
||||
}
|
||||
return restoreReadingLocation(location, animated: animated)
|
||||
}
|
||||
|
||||
guard activePages.indices.contains(pageNumber - 1) else {
|
||||
return false
|
||||
}
|
||||
readerView.transitionToPage(pageNum: pageNumber - 1, animated: animated)
|
||||
if let location = currentVisibleLocation() {
|
||||
persist(location: location)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func clearSelection() {
|
||||
updateCurrentSelection(nil)
|
||||
}
|
||||
@@ -489,12 +515,17 @@ public final class RDEPUBReaderController: UIViewController {
|
||||
}
|
||||
|
||||
private func applyReaderViewConfiguration() {
|
||||
let displayTypeDidChange = readerView.currentDisplayType != configuration.displayType
|
||||
let preservedLocation = displayTypeDidChange ? currentVisibleLocation() : nil
|
||||
view.backgroundColor = configuration.theme.contentBackgroundColor
|
||||
readerView.landscapeDualPageEnabled = configuration.landscapeDualPageEnabled
|
||||
readerView.pageDirection = resolvedPageDirection()
|
||||
updateReaderChrome()
|
||||
if readerView.currentDisplayType != configuration.displayType {
|
||||
if displayTypeDidChange {
|
||||
readerView.switchReaderDisplayType(configuration.displayType)
|
||||
if let preservedLocation {
|
||||
_ = restoreReadingLocation(preservedLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,7 +781,8 @@ public final class RDEPUBReaderController: UIViewController {
|
||||
href: selection.location.href,
|
||||
progression: selection.location.progression,
|
||||
lastProgression: selection.location.lastProgression,
|
||||
fragment: selection.location.fragment
|
||||
fragment: selection.location.fragment,
|
||||
rangeAnchor: selection.location.rangeAnchor
|
||||
)
|
||||
return RDEPUBSelection(
|
||||
bookIdentifier: currentBookIdentifier,
|
||||
@@ -772,7 +804,8 @@ public final class RDEPUBReaderController: UIViewController {
|
||||
href: highlight.location.href,
|
||||
progression: highlight.location.progression,
|
||||
lastProgression: highlight.location.lastProgression,
|
||||
fragment: highlight.location.fragment
|
||||
fragment: highlight.location.fragment,
|
||||
rangeAnchor: highlight.location.rangeAnchor
|
||||
)
|
||||
return RDEPUBHighlight(
|
||||
id: highlight.id,
|
||||
@@ -1367,6 +1400,11 @@ extension RDEPUBReaderController {
|
||||
return false
|
||||
}
|
||||
|
||||
if let bookmarkAnchor = bookmark.location.rangeAnchor,
|
||||
let locationAnchor = location.rangeAnchor {
|
||||
return bookmarkAnchor == locationAnchor
|
||||
}
|
||||
|
||||
if let bookmarkFragment = bookmark.location.fragment,
|
||||
let locationFragment = location.fragment {
|
||||
return bookmarkFragment == locationFragment
|
||||
@@ -1394,7 +1432,8 @@ extension RDEPUBReaderController {
|
||||
href: location.href,
|
||||
progression: location.progression,
|
||||
lastProgression: location.lastProgression,
|
||||
fragment: location.fragment
|
||||
fragment: location.fragment,
|
||||
rangeAnchor: location.rangeAnchor
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1407,7 +1446,8 @@ extension RDEPUBReaderController {
|
||||
href: location.href,
|
||||
progression: location.progression,
|
||||
lastProgression: location.lastProgression,
|
||||
fragment: location.fragment
|
||||
fragment: location.fragment,
|
||||
rangeAnchor: location.rangeAnchor
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1499,21 +1539,32 @@ extension RDEPUBReaderController {
|
||||
href: searchMatch.href,
|
||||
progression: searchMatch.progression,
|
||||
lastProgression: searchMatch.progression,
|
||||
fragment: nil
|
||||
fragment: nil,
|
||||
rangeAnchor: searchMatch.rangeAnchor
|
||||
)
|
||||
return restoreReadingLocation(location, animated: animated)
|
||||
}
|
||||
|
||||
private func pageNumber(for searchMatch: RDEPUBSearchMatch) -> Int? {
|
||||
if let textBook,
|
||||
let publication,
|
||||
let rangeLocation = searchMatch.rangeLocation,
|
||||
let chapter = textBook.chapters.first(where: {
|
||||
(publication.resourceResolver.normalizedHref($0.href) ?? $0.href) ==
|
||||
(publication.resourceResolver.normalizedHref(searchMatch.href) ?? searchMatch.href)
|
||||
}),
|
||||
let page = chapter.pages.first(where: { rangeLocation >= $0.pageStartOffset && rangeLocation <= $0.pageEndOffset }) {
|
||||
return page.absolutePageIndex + 1
|
||||
if let chapterData = textChapterData(forNormalizedHref: searchMatch.href) {
|
||||
let location = RDEPUBLocation(
|
||||
bookIdentifier: currentBookIdentifier,
|
||||
href: chapterData.href,
|
||||
progression: searchMatch.progression,
|
||||
lastProgression: searchMatch.progression,
|
||||
fragment: nil,
|
||||
rangeAnchor: searchMatch.rangeAnchor
|
||||
)
|
||||
if let pageNumber = chapterData.pageNumber(for: location) {
|
||||
return pageNumber
|
||||
}
|
||||
|
||||
if let rangeLocation = searchMatch.rangeLocation,
|
||||
let page = chapterData.pages.first(where: {
|
||||
rangeLocation >= $0.pageStartOffset && rangeLocation <= $0.pageEndOffset
|
||||
}) {
|
||||
return page.absolutePageIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
let location = RDEPUBLocation(
|
||||
@@ -1521,7 +1572,8 @@ extension RDEPUBReaderController {
|
||||
href: searchMatch.href,
|
||||
progression: searchMatch.progression,
|
||||
lastProgression: searchMatch.progression,
|
||||
fragment: nil
|
||||
fragment: nil,
|
||||
rangeAnchor: searchMatch.rangeAnchor
|
||||
)
|
||||
|
||||
if let textBook, let publication {
|
||||
@@ -1554,9 +1606,12 @@ extension RDEPUBReaderController {
|
||||
}
|
||||
|
||||
let currentMatch = searchState.currentMatch
|
||||
let normalizedCurrentHref = currentMatch.map { publication.resourceResolver.normalizedHref($0.href) ?? $0.href }
|
||||
let resources = pageHrefs.map { href in
|
||||
let matchCount = searchState.matches.filter { $0.href == href }.count
|
||||
let activeLocalMatchIndex = currentMatch?.href == href ? currentMatch?.localMatchIndex : nil
|
||||
let matchCount = searchState.matches.filter {
|
||||
(publication.resourceResolver.normalizedHref($0.href) ?? $0.href) == href
|
||||
}.count
|
||||
let activeLocalMatchIndex = normalizedCurrentHref == href ? currentMatch?.localMatchIndex : nil
|
||||
return RDEPUBSearchPresentationResource(
|
||||
href: href,
|
||||
matchCount: matchCount,
|
||||
@@ -1611,6 +1666,11 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
}
|
||||
|
||||
private func textHighlights(for page: RDEPUBTextPage) -> [RDEPUBHighlight] {
|
||||
if let textBook,
|
||||
let chapterData = textBook.chapterData(for: page.href) {
|
||||
return chapterData.highlights(on: page, from: activeHighlights)
|
||||
}
|
||||
|
||||
guard let publication else {
|
||||
return activeHighlights.filter { $0.location.href == page.href }
|
||||
}
|
||||
@@ -1620,6 +1680,14 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
|
||||
guard let textBook, let publication else { return nil }
|
||||
let normalizedHref = publication.resourceResolver.normalizedHref(href) ?? href
|
||||
return textBook.chapters.lazy
|
||||
.first(where: { (publication.resourceResolver.normalizedHref($0.href) ?? $0.href) == normalizedHref })
|
||||
.flatMap { textBook.chapterData(for: $0.href) }
|
||||
}
|
||||
|
||||
public func topToolView(readerView: RDReaderView) -> UIView? {
|
||||
topToolView
|
||||
}
|
||||
@@ -1728,27 +1796,22 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
|
||||
|
||||
private func normalizedTextSelection(_ selection: RDEPUBSelection) -> RDEPUBSelection? {
|
||||
guard let textBook,
|
||||
let chapter = textBook.chapters.first(where: { $0.href == selection.location.href }) else {
|
||||
let chapterData = textBook.chapterData(for: selection.location.href) else {
|
||||
return scopedSelection(selection, relativeToSpineIndex: nil)
|
||||
}
|
||||
guard let payload = RDEPUBTextOffsetRangeInfo.decode(from: selection.rangeInfo) else {
|
||||
return scopedSelection(selection, relativeToSpineIndex: nil)
|
||||
}
|
||||
|
||||
let contentLength = max(chapter.attributedContent.length, 1)
|
||||
let contentLength = max(chapterData.attributedContent.length, 1)
|
||||
let lastInclusiveOffset = max(contentLength - 1, 1)
|
||||
let start = max(0, min(payload.start, lastInclusiveOffset))
|
||||
let endExclusive = max(start + 1, min(payload.end, contentLength))
|
||||
let lastSelectedOffset = max(start, min(endExclusive - 1, lastInclusiveOffset))
|
||||
let absoluteRange = NSRange(location: start, length: endExclusive - start)
|
||||
let location = chapterData.location(for: absoluteRange, bookIdentifier: currentBookIdentifier)
|
||||
return RDEPUBSelection(
|
||||
bookIdentifier: currentBookIdentifier,
|
||||
location: RDEPUBLocation(
|
||||
bookIdentifier: currentBookIdentifier,
|
||||
href: selection.location.href,
|
||||
progression: Double(start) / Double(lastInclusiveOffset),
|
||||
lastProgression: Double(lastSelectedOffset) / Double(lastInclusiveOffset),
|
||||
fragment: nil
|
||||
),
|
||||
location: location,
|
||||
text: selection.text,
|
||||
rangeInfo: selection.rangeInfo,
|
||||
createdAt: selection.createdAt
|
||||
@@ -1757,6 +1820,13 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
|
||||
|
||||
private func pageNumber(for location: RDEPUBLocation) -> Int? {
|
||||
if let textBook, let publication {
|
||||
// Anchor-based lookup (character-level precision)
|
||||
if let anchor = location.rangeAnchor?.start {
|
||||
if let page = textBook.indexTable.pageNumber(for: anchor, in: textBook) {
|
||||
return page + 1
|
||||
}
|
||||
}
|
||||
|
||||
let normalizedLocation = publication.resourceResolver.normalizedLocation(
|
||||
location,
|
||||
relativeToSpineIndex: nil,
|
||||
@@ -1779,13 +1849,14 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
|
||||
private func resolvedTextLocation(forPageNumber pageNumber: Int) -> RDEPUBLocation? {
|
||||
guard let textBook,
|
||||
let publication,
|
||||
let location = textBook.location(
|
||||
forPageNumber: pageNumber,
|
||||
bookIdentifier: currentBookIdentifier
|
||||
) else {
|
||||
let page = textBook.page(at: pageNumber) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let location = textBook.chapterData(for: page.href)?.location(forPage: page, bookIdentifier: currentBookIdentifier)
|
||||
?? textBook.location(forPageNumber: pageNumber, bookIdentifier: currentBookIdentifier)
|
||||
guard let location else { return nil }
|
||||
|
||||
return publication.resourceResolver.normalizedLocation(
|
||||
location,
|
||||
relativeToSpineIndex: nil,
|
||||
|
||||
Reference in New Issue
Block a user