// RDEPUBReaderController+TableOfContents.swift // EPUB 阅读器目录功能 // 提供目录(Table of Contents)相关功能,包括解析当前页面对应的目录项 // 以及将嵌套目录树结构扁平化为线性列表。 import Foundation /// RDEPUBReaderController 目录扩展 /// /// 本文件提供目录(Table of Contents)相关功能,包括解析当前页面对应的目录项 /// 以及将嵌套目录树结构扁平化为线性列表。 extension RDEPUBReaderController { /// 解析当前阅读位置对应的目录项,按页码或 href 匹配 func resolvedCurrentTableOfContentsItem() -> RDEPUBReaderTableOfContentsItem? { let items = flattenedTableOfContents guard !items.isEmpty else { return nil } let currentPageNumber = max(readerView.currentPage + 1, 1) let pageAnchoredMatch = items.last { item in guard let pageNumber = item.pageNumber else { return false } return pageNumber <= currentPageNumber } if let pageAnchoredMatch { return pageAnchoredMatch } guard let publication, let currentLocation = currentVisibleLocation(), let normalizedCurrentHref = publication.resourceResolver.normalizedHref(currentLocation.href) else { return nil } if let textBook, let chapterData = textBook.chapterData( for: currentLocation, resolver: publication.resourceResolver, bookIdentifier: currentBookIdentifier ), let tocItem = chapterData.primaryTableOfContentsItem( from: publication.tableOfContents, normalizer: { publication.resourceResolver.normalizedHref($0) } ) { return items.last { $0.href == tocItem.href } ?? items.last { guard let normalizedItemHref = publication.resourceResolver.normalizedHref($0.href.components(separatedBy: "#").first ?? $0.href) else { return false } return normalizedItemHref == normalizedCurrentHref } } return items.last { item in guard let normalizedItemHref = publication.resourceResolver.normalizedHref(item.href.components(separatedBy: "#").first ?? item.href) else { return false } return normalizedItemHref == normalizedCurrentHref } } /// 将嵌套目录树递归扁平化为线性列表,并计算每项目标页码 func flattenedTableOfContentsItems( from items: [EPUBTableOfContentsItem], depth: Int = 0 ) -> [RDEPUBReaderTableOfContentsItem] { items.flatMap { item in let location = RDEPUBLocation(bookIdentifier: currentBookIdentifier, href: item.href, progression: 0) let pageNumber: Int? if let textBook, let publication, let chapterData = textBook.chapterData(for: location, resolver: publication.resourceResolver, bookIdentifier: currentBookIdentifier) { let normalizedLocation = publication.resourceResolver.normalizedLocation( location, bookIdentifier: currentBookIdentifier ) ?? location pageNumber = chapterData.pageNumber(for: normalizedLocation) ?? textBook.pageNumber( for: location, resolver: publication.resourceResolver, bookIdentifier: currentBookIdentifier ) } else if let readingSession { pageNumber = readingSession.pageIndex(for: location, bookIdentifier: currentBookIdentifier).map { $0 + 1 } } else { pageNumber = nil } let current = RDEPUBReaderTableOfContentsItem( title: item.title, href: item.href, depth: depth, pageNumber: pageNumber ) return [current] + flattenedTableOfContentsItems(from: item.children, depth: depth + 1) } } }