- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
735 lines
22 KiB
Markdown
735 lines
22 KiB
Markdown
# ReadViewSDK UML 类图
|
|
|
|
> 最后更新:2026-06-09
|
|
> 图表格式:Mermaid(可在 GitHub / VS Code / Mermaid Live Editor 中渲染)
|
|
|
|
---
|
|
|
|
## 1. 整体模块关系
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph EPUBCore
|
|
Parser[RDEPUBParser]
|
|
Pub[RDEPUBPublication]
|
|
Resolver[RDEPUBResourceResolver]
|
|
Session[RDEPUBReadingSession]
|
|
WebView[RDEPUBWebView]
|
|
Paginator[RDEPUBPaginator]
|
|
SearchEngine[RDEPUBHTMLSearchEngine]
|
|
end
|
|
|
|
subgraph EPUBTextRendering
|
|
Builder[RDEPUBTextBookBuilder]
|
|
Renderer[RDEPUBDTCoreTextRenderer]
|
|
Pipeline[RDEPUBTextTypesetterPipeline]
|
|
Counter[RDEPUBChapterPageCounter]
|
|
FrameFactory[RDEPUBCoreTextPageFrameFactory]
|
|
BreakPolicy[RDEPUBPageBreakPolicy]
|
|
BookCache[RDEPUBTextBookCache]
|
|
ChapterData[RDEPUBChapterData]
|
|
IndexTable[RDEPUBTextIndexTable]
|
|
end
|
|
|
|
subgraph RDEpubReaderView
|
|
ReaderView[RDEpubReaderView]
|
|
FlowLayout[RDEpubReaderFlowLayout]
|
|
Preload[RDEpubReaderPreloadController]
|
|
Spread[RDEpubReaderSpreadResolver]
|
|
TapRegion[RDEpubReaderTapRegionHandler]
|
|
end
|
|
|
|
subgraph EPUBUI
|
|
Controller[RDEPUBReaderController]
|
|
Context[RDEPUBReaderContext]
|
|
Runtime[RDEPUBReaderRuntime]
|
|
PaginationCoord[RDEPUBReaderPaginationCoordinator]
|
|
Loader[RDEPUBChapterLoader]
|
|
RuntimeStore[RDEPUBChapterRuntimeStore]
|
|
DiskCache[RDEPUBChapterSummaryDiskCache]
|
|
PageMap[RDEPUBBookPageMap]
|
|
Config[RDEPUBReaderConfiguration]
|
|
end
|
|
|
|
Parser --> Pub
|
|
Pub --> Resolver
|
|
Session --> Pub
|
|
WebView --> Pub
|
|
WebView --> Resolver
|
|
Paginator --> Parser
|
|
|
|
Builder --> Renderer
|
|
Builder --> Pipeline
|
|
Builder --> Counter
|
|
Counter --> FrameFactory
|
|
FrameFactory --> BreakPolicy
|
|
Builder --> BookCache
|
|
ChapterData --> IndexTable
|
|
|
|
Controller --> Context
|
|
Context --> Runtime
|
|
Runtime --> Loader
|
|
Runtime --> RuntimeStore
|
|
Runtime --> DiskCache
|
|
PaginationCoord --> Context
|
|
PaginationCoord --> PageMap
|
|
Loader --> RuntimeStore
|
|
Loader --> DiskCache
|
|
|
|
Controller --> ReaderView
|
|
Controller --> Session
|
|
Controller --> Builder
|
|
ReaderView --> FlowLayout
|
|
ReaderView --> Preload
|
|
ReaderView --> Spread
|
|
ReaderView --> TapRegion
|
|
```
|
|
|
|
---
|
|
|
|
## 2. EPUBCore 核心类图
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class RDEPUBParser {
|
|
+metadata: RDEPUBMetadata
|
|
+manifest: [String: RDEPUBManifestItem]
|
|
+spine: [RDEPUBSpineItem]
|
|
+tableOfContents: [EPUBTableOfContentsItem]
|
|
+extractionRootURL: URL
|
|
+opfURL: URL
|
|
+opfDirectoryURL: URL
|
|
+parse(epubURL: URL) throws
|
|
+makePublication() RDEPUBPublication
|
|
+htmlString(forRelativePath:) String?
|
|
+htmlString(forSpineIndex:) String?
|
|
+coverImage() UIImage?
|
|
+resourceURL(forRelativePath:) URL
|
|
+fileURL(forRelativePath:) URL?
|
|
}
|
|
|
|
class RDEPUBPublication {
|
|
+parser: RDEPUBParser
|
|
+resourceResolver: RDEPUBResourceResolver
|
|
+metadata: RDEPUBMetadata
|
|
+manifest: [RDEPUBManifestItem]
|
|
+spine: [RDEPUBSpineItem]
|
|
+tableOfContents: [EPUBTableOfContentsItem]
|
|
+layout: RDEPUBLayout
|
|
+readingProfile: RDEPUBReadingProfile
|
|
+readingProgression: RDEPUBReadingProgression
|
|
+bookIdentifier: String?
|
|
+fixedLayoutSpreadEnabled(preferences:viewportSize:) Bool
|
|
+makeFixedSpreads(preferences:viewportSize:) [EPUBFixedSpread]
|
|
}
|
|
|
|
class RDEPUBResourceResolver {
|
|
+parser: RDEPUBParser
|
|
+fileURL(forRelativePath:) URL?
|
|
+resourceURL(forRelativePath:) URL
|
|
+normalizedHref(_:relativeToSpineIndex:) String?
|
|
+normalizedLocation(_:relativeToSpineIndex:bookIdentifier:) RDEPUBLocation?
|
|
+spineIndex(forNormalizedHref:) Int?
|
|
+spineIndex(for:) Int?
|
|
}
|
|
|
|
class RDEPUBReadingSession {
|
|
+publication: RDEPUBPublication
|
|
+navigatorState: RDEPUBNavigatorState
|
|
+activePages: [EPUBPage]
|
|
+activeChapters: [EPUBChapterInfo]
|
|
+currentViewport: RDEPUBViewport?
|
|
+currentReadingContext: RDEPUBReadingContext?
|
|
+transition(to: RDEPUBNavigatorState)
|
|
+setActiveSnapshot(_:)
|
|
+queueNavigation(to:relativeToSpineIndex:bookIdentifier:) Int
|
|
+updateReadingContext(...)
|
|
+makePaginationSnapshot(pageCounts:preferences:layoutContext:)
|
|
}
|
|
|
|
class RDEPUBWebView {
|
|
+publication: RDEPUBPublication?
|
|
+currentRenderRequest: RDEPUBRenderRequest?
|
|
+webView: WKWebView
|
|
+delegate: RDEPUBWebViewDelegate?
|
|
+load(publication:request:)
|
|
+reset()
|
|
}
|
|
|
|
class RDEPUBNavigatorState {
|
|
<<enum>>
|
|
initializing
|
|
loading
|
|
idle
|
|
jumping
|
|
moving
|
|
repaginating
|
|
+isStableForSnapshotApplication: Bool
|
|
}
|
|
|
|
RDEPUBParser --> RDEPUBPublication : creates
|
|
RDEPUBPublication --> RDEPUBResourceResolver : owns
|
|
RDEPUBReadingSession --> RDEPUBPublication : wraps
|
|
RDEPUBReadingSession --> RDEPUBNavigatorState : manages
|
|
RDEPUBWebView --> RDEPUBPublication : uses
|
|
RDEPUBWebView --> RDEPUBResourceResolver : uses
|
|
```
|
|
|
|
---
|
|
|
|
## 3. EPUBTextRendering 类图
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class RDEPUBTextBookBuilder {
|
|
+renderer: RDEPUBTextRenderer
|
|
+cache: RDEPUBTextBookCache?
|
|
+layoutConfig: RDEPUBTextLayoutConfig
|
|
+renderPipeline: RDEPUBChapterRenderPipeline
|
|
+paginationPipeline: RDEPUBChapterPaginationPipeline
|
|
+tailNormalizer: RDEPUBChapterTailNormalizer
|
|
+build(parser:publication:pageSize:style:) throws RDEPUBTextBook
|
|
+buildChapter(parser:publication:spineIndex:pageSize:style:) throws RDEPUBTextChapterBuildResult?
|
|
}
|
|
|
|
class RDEPUBTextRenderer {
|
|
<<protocol>>
|
|
+renderChapter(request:) throws RDEPUBRenderedChapterContent
|
|
}
|
|
|
|
class RDEPUBDTCoreTextRenderer {
|
|
+renderChapter(request:) throws RDEPUBRenderedChapterContent
|
|
+renderChapter(html:baseURL:style:) throws RDEPUBRenderedChapterContent
|
|
}
|
|
|
|
class RDEPUBTextTypesetterPipeline {
|
|
+makeRequest(from: RDEPUBTypesettingInput) RDEPUBTypesettingOutput
|
|
}
|
|
|
|
class RDEPUBChapterPageCounter {
|
|
+factory: RDEPUBCoreTextPageFrameFactory
|
|
+layoutFrames(fragmentOffsets:) [RDEPUBTextLayoutFrame]
|
|
}
|
|
|
|
class RDEPUBCoreTextPageFrameFactory {
|
|
+attributedString: NSAttributedString
|
|
+pageSize: CGSize
|
|
+config: RDEPUBTextLayoutConfig
|
|
+makeFrames(...) [RDEPUBTextLayoutFrame]
|
|
}
|
|
|
|
class RDEPUBPageBreakPolicy {
|
|
+lineIsInAvoidPageBreakInsideBlock(_:) Bool
|
|
+lineIsInKeepWithNextBlock(_:) Bool
|
|
+adjustedRange(from:totalLength:lineRanges:factory:) RDEPUBPageBreakDecision
|
|
}
|
|
|
|
class RDEPUBTextBook {
|
|
+chapters: [RDEPUBTextChapter]
|
|
+pages: [RDEPUBTextPage]
|
|
+indexTable: RDEPUBTextIndexTable
|
|
+positionConverter: RDEPUBTextPositionConverter
|
|
+chapterData(for href:) RDEPUBChapterData?
|
|
+chapterData(forPageNumber:) RDEPUBChapterData?
|
|
}
|
|
|
|
class RDEPUBTextChapter {
|
|
+chapterIndex: Int
|
|
+spineIndex: Int
|
|
+href: String
|
|
+title: String
|
|
+attributedContent: NSAttributedString
|
|
+fragmentOffsets: [String: Int]
|
|
+pages: [RDEPUBTextPage]
|
|
}
|
|
|
|
class RDEPUBTextPage {
|
|
+absolutePageIndex: Int
|
|
+chapterIndex: Int
|
|
+spineIndex: Int
|
|
+contentRange: NSRange
|
|
+metadata: RDEPUBTextPageMetadata
|
|
}
|
|
|
|
class RDEPUBTextBookCache {
|
|
+schemaVersion: Int
|
|
+load(key:) [String: RDEPUBTextChapterPaginationCache]?
|
|
+save(_:key:)
|
|
+invalidateAll()
|
|
}
|
|
|
|
class RDEPUBChapterData {
|
|
+chapterIndex: Int
|
|
+spineIndex: Int
|
|
+href: String
|
|
+title: String
|
|
+pageCount: Int
|
|
+page(containing:) RDEPUBTextPage?
|
|
+pageNumber(containing:) Int?
|
|
+absoluteRange(for location:) ClosedRange<Int>?
|
|
}
|
|
|
|
class RDEPUBTextIndexTable {
|
|
+chapterStartOffsets: [Int]
|
|
+totalCharacterCount: Int
|
|
+anchor(forAbsoluteIndex:in:) RDEPUBTextAnchor
|
|
+pageNumber(for:in:) Int
|
|
+globalIndex(for:) Int
|
|
}
|
|
|
|
RDEPUBTextBookBuilder --> RDEPUBTextRenderer : uses
|
|
RDEPUBDTCoreTextRenderer ..|> RDEPUBTextRenderer
|
|
RDEPUBTextBookBuilder --> RDEPUBTextTypesetterPipeline : uses
|
|
RDEPUBTextBookBuilder --> RDEPUBChapterPageCounter : uses
|
|
RDEPUBChapterPageCounter --> RDEPUBCoreTextPageFrameFactory : uses
|
|
RDEPUBCoreTextPageFrameFactory --> RDEPUBPageBreakPolicy : uses
|
|
RDEPUBTextBookBuilder --> RDEPUBTextBookCache : uses
|
|
RDEPUBTextBook --> RDEPUBTextChapter : contains
|
|
RDEPUBTextChapter --> RDEPUBTextPage : contains
|
|
RDEPUBTextBook --> RDEPUBTextIndexTable : computes
|
|
RDEPUBChapterData --> RDEPUBTextChapter : wraps
|
|
RDEPUBChapterData --> RDEPUBTextIndexTable : uses
|
|
```
|
|
|
|
---
|
|
|
|
## 4. ReaderView 类图
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class RDEpubReaderView {
|
|
+currentPage: Int
|
|
+currentDisplayType: DisplayType
|
|
+pageProvider: RDEpubReaderPageProvider?
|
|
+delegate: RDEpubReaderDelegate?
|
|
+landscapeDualPageEnabled: Bool
|
|
+pageDirection: PageDirection
|
|
+coverPageIndex: Int?
|
|
+pagesPerScreen: Int
|
|
+transitionToPage(pageNum:animated:)
|
|
+reloadData()
|
|
+switchReaderDisplayType(_:)
|
|
}
|
|
|
|
class DisplayType {
|
|
<<enum>>
|
|
pageCurl
|
|
horizontalScroll
|
|
verticalScroll
|
|
}
|
|
|
|
class RDEpubReaderPageProvider {
|
|
<<protocol>>
|
|
+numberOfPages(in:) Int
|
|
+readerView(_:viewForPageAt:reusableView:) UIView
|
|
+pageIdentifier(in:index:) String?
|
|
}
|
|
|
|
class RDEpubReaderFlowLayout {
|
|
+displayType: DisplayType
|
|
+isLandscapeDualPage: Bool
|
|
+coverPageIndex: Int?
|
|
+pagesPerScreen: Int
|
|
+currentPage: Int
|
|
}
|
|
|
|
class RDEpubReaderPreloadController {
|
|
+radius: Int
|
|
+pageViewForDisplay(pageNum:environment:contentViewProvider:) UIView?
|
|
+takePreloadedView(for:) UIView?
|
|
+prime(around:preferredForward:parentView:environment:contentViewProvider:)
|
|
+invalidate(environment:)
|
|
}
|
|
|
|
class RDEpubReaderSpreadResolver {
|
|
+isFullScreenPage(...) Bool
|
|
+dualPagePair(for:totalPages:coverPageIndex:) (Int, Int?)
|
|
+nextPage(from:totalPages:pagesPerScreen:coverPageIndex:forward:) Int?
|
|
}
|
|
|
|
class RDEpubReaderTapRegionHandler {
|
|
+resolveTapEvent(point:viewFrame:isToolViewVisible:) TapEvent
|
|
}
|
|
|
|
class TapEvent {
|
|
<<enum>>
|
|
none
|
|
left
|
|
center
|
|
right
|
|
}
|
|
|
|
class RDEpubReaderPagingController {
|
|
+isTransitioning: Bool
|
|
+didBuildUI: Bool
|
|
+pendingTransitionRequest: PageTransitionRequest?
|
|
+shouldQueuePageTransition(_:currentDisplayType:) Bool
|
|
+finishPageCurlTransition() PageTransitionRequest?
|
|
}
|
|
|
|
class RDEpubReaderContentCell {
|
|
+containerView: UIView?
|
|
}
|
|
|
|
class RDEpubReaderPageChildViewController {
|
|
+contentView: UIView?
|
|
+pageNum: Int
|
|
}
|
|
|
|
RDEpubReaderView --> DisplayType : uses
|
|
RDEpubReaderView --> RDEpubReaderPageProvider : delegates
|
|
RDEpubReaderView --> RDEpubReaderFlowLayout : owns
|
|
RDEpubReaderView --> RDEpubReaderPreloadController : owns
|
|
RDEpubReaderView --> RDEpubReaderSpreadResolver : owns
|
|
RDEpubReaderView --> RDEpubReaderTapRegionHandler : owns
|
|
RDEpubReaderView --> RDEpubReaderPagingController : owns
|
|
RDEpubReaderView --> RDEpubReaderContentCell : creates
|
|
RDEpubReaderView --> RDEpubReaderPageChildViewController : creates
|
|
RDEpubReaderTapRegionHandler --> TapEvent : produces
|
|
```
|
|
|
|
---
|
|
|
|
## 5. EPUBUI 控制器与运行时类图
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class RDEPUBReaderController {
|
|
+delegate: RDEPUBReaderDelegate?
|
|
+configuration: RDEPUBReaderConfiguration
|
|
+currentLocation: RDEPUBLocation?
|
|
+currentPageNumber: Int?
|
|
+currentSelection: RDEPUBSelection?
|
|
+highlights: [RDEPUBHighlight]
|
|
+bookmarks: [RDEPUBBookmark]
|
|
+tableOfContents: [EPUBTableOfContentsItem]
|
|
+readerView: RDEpubReaderView
|
|
+readerContext: RDEPUBReaderContext
|
|
+runtime: RDEPUBReaderRuntime?
|
|
}
|
|
|
|
class RDEPUBReaderContext {
|
|
+parser: RDEPUBParser?
|
|
+publication: RDEPUBPublication?
|
|
+readingSession: RDEPUBReadingSession?
|
|
+textBook: RDEPUBTextBook?
|
|
+bookPageMap: RDEPUBBookPageMap?
|
|
+configuration: RDEPUBReaderConfiguration
|
|
+persistence: RDEPUBReaderPersistence?
|
|
+currentRenderSignature() String
|
|
+chapterCacheKey(forSpineIndex:) RDEPUBChapterCacheKey
|
|
+chapterCacheKey(forSpineIndex:precomputedContentHash:renderSignature:) RDEPUBChapterCacheKey
|
|
+makeTextBookBuilder(layoutConfig:) RDEPUBTextBookBuilder
|
|
}
|
|
|
|
class RDEPUBReaderRuntime {
|
|
+chapterLoader: RDEPUBChapterLoader
|
|
+chapterRuntimeStore: RDEPUBChapterRuntimeStore
|
|
+summaryDiskCache: RDEPUBChapterSummaryDiskCache
|
|
+pageResolver: RDEPUBPageResolver
|
|
+loadCoordinator: RDEPUBReaderLoadCoordinator
|
|
+paginationCoordinator: RDEPUBReaderPaginationCoordinator
|
|
+locationCoordinator: RDEPUBReaderLocationCoordinator
|
|
+searchCoordinator: RDEPUBReaderSearchCoordinator
|
|
+chromeCoordinator: RDEPUBReaderChromeCoordinator
|
|
+annotationCoordinator: RDEPUBReaderAnnotationCoordinator
|
|
+viewportMonitor: RDEPUBReaderViewportMonitor
|
|
+applyBookPageMap(_:restoreLocation:)
|
|
+refreshBookPageMapInPlace(_:)
|
|
}
|
|
|
|
class RDEPUBReaderPaginationCoordinator {
|
|
-context: RDEPUBReaderContext
|
|
+paginatePublication(restoreLocation:)
|
|
+repaginatePreservingCurrentLocation()
|
|
}
|
|
|
|
class RDEPUBMetadataParseWorker {
|
|
-context: RDEPUBReaderContext
|
|
-cancellationController: RDEPUBMetadataParseCancellationController
|
|
+start(token:restoreLocation:)
|
|
}
|
|
|
|
class RDEPUBMetadataParseCancellationController {
|
|
+token: UUID
|
|
+attach(queue:)
|
|
+cancel()
|
|
+isCancelled: Bool
|
|
}
|
|
|
|
class RDEPUBReaderLoadCoordinator {
|
|
-context: RDEPUBReaderContext
|
|
+startInitialLoadIfNeeded()
|
|
-loadPublication()
|
|
-applyParsedPublication()
|
|
}
|
|
|
|
class RDEPUBReaderSearchCoordinator {
|
|
-context: RDEPUBReaderContext
|
|
+search(keyword:)
|
|
+goToNextMatch()
|
|
+goToPreviousMatch()
|
|
+clearSearch()
|
|
}
|
|
|
|
class RDEPUBReaderAnnotationCoordinator {
|
|
-context: RDEPUBReaderContext
|
|
+addHighlight(from:)
|
|
+removeHighlight(id:)
|
|
+toggleBookmark(at:)
|
|
+handleSelectionMenuAction(_:)
|
|
}
|
|
|
|
class RDEPUBChapterLoader {
|
|
-context: RDEPUBReaderContext
|
|
+loadChapter(spineIndex:store:) async throws RDEPUBRuntimeChapter
|
|
+loadChapterSynchronouslyForMigration(spineIndex:store:) throws RDEPUBRuntimeChapter
|
|
}
|
|
|
|
class RDEPUBChapterRuntimeStore {
|
|
+chapterDataCache: [Int: RDEPUBRuntimeChapter]
|
|
+pageCountCache: RDEPUBPageCountCache
|
|
+imageCache: NSCache
|
|
+setCurrentChapter(spineIndex:totalSpineCount:windowRadius:)
|
|
+chapterData(for:) RDEPUBRuntimeChapter?
|
|
+pageCount(for:) RDEPUBRuntimePageCount?
|
|
}
|
|
|
|
class RDEPUBChapterSummaryDiskCache {
|
|
+cacheDirectory: URL
|
|
+read(for:) RDEPUBChapterSummary?
|
|
+readAll(keys:) ([Int: RDEPUBChapterSummary], RDEPUBBookPageMap.Builder)
|
|
+write(summary:for:)
|
|
+flushPendingWrites()
|
|
+isCacheComplete(keys:) Bool
|
|
}
|
|
|
|
class RDEPUBBookPageMap {
|
|
+totalChapters: Int
|
|
+totalPages: Int
|
|
+chapters: [EPUBChapterInfo]
|
|
+pages: [EPUBPage]
|
|
}
|
|
|
|
class RDEPUBChapterCacheKey {
|
|
+bookID: String
|
|
+spineIndex: Int
|
|
+renderSignature: String
|
|
+chapterContentHash: String
|
|
}
|
|
|
|
class RDEPUBReaderConfiguration {
|
|
+fontSize: CGFloat
|
|
+fontChoice: FontChoice
|
|
+lineHeightMultiple: CGFloat
|
|
+numberOfColumns: Int
|
|
+theme: ReaderTheme
|
|
+displayType: DisplayType
|
|
+onDemandChapterWindowSize: Int
|
|
+metadataParsingConcurrency: Int
|
|
}
|
|
|
|
RDEPUBReaderController --> RDEPUBReaderContext : owns
|
|
RDEPUBReaderController --> RDEPUBReaderRuntime : owns
|
|
RDEPUBReaderController --> RDEPUBReaderPaginationCoordinator : owns
|
|
RDEPUBReaderContext --> RDEPUBReaderConfiguration : holds
|
|
RDEPUBReaderRuntime --> RDEPUBChapterLoader : owns
|
|
RDEPUBReaderRuntime --> RDEPUBChapterRuntimeStore : owns
|
|
RDEPUBReaderRuntime --> RDEPUBChapterSummaryDiskCache : owns
|
|
RDEPUBReaderRuntime --> RDEPUBReaderLoadCoordinator : owns
|
|
RDEPUBReaderRuntime --> RDEPUBReaderSearchCoordinator : owns
|
|
RDEPUBReaderRuntime --> RDEPUBReaderAnnotationCoordinator : owns
|
|
RDEPUBReaderPaginationCoordinator --> RDEPUBReaderContext : uses
|
|
RDEPUBChapterLoader --> RDEPUBReaderContext : uses
|
|
RDEPUBChapterLoader --> RDEPUBChapterRuntimeStore : uses
|
|
RDEPUBChapterLoader --> RDEPUBChapterSummaryDiskCache : uses
|
|
RDEPUBChapterSummaryDiskCache --> RDEPUBChapterCacheKey : uses
|
|
RDEPUBBookPageMap --> EPUBChapterInfo : contains
|
|
RDEPUBBookPageMap --> EPUBPage : contains
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 数据模型关系图
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class RDEPUBLocation {
|
|
+bookIdentifier: String?
|
|
+href: String
|
|
+progression: Double
|
|
+lastProgression: Double?
|
|
+fragment: String?
|
|
+rangeAnchor: RDEPUBTextRangeAnchor?
|
|
}
|
|
|
|
class RDEPUBTextAnchor {
|
|
+fileIndex: Int
|
|
+row: Int
|
|
+column: Int
|
|
+chapterOffset: Int
|
|
+fragmentID: String?
|
|
+spineIndex: Int
|
|
}
|
|
|
|
class RDEPUBTextRangeAnchor {
|
|
+start: RDEPUBTextAnchor
|
|
+end: RDEPUBTextAnchor
|
|
+nsRange: NSRange
|
|
}
|
|
|
|
class RDEPUBSelection {
|
|
+bookIdentifier: String?
|
|
+location: RDEPUBLocation
|
|
+text: String
|
|
+rangeInfo: String?
|
|
+createdAt: Date
|
|
}
|
|
|
|
class RDEPUBHighlight {
|
|
+id: String
|
|
+bookIdentifier: String?
|
|
+location: RDEPUBLocation
|
|
+text: String
|
|
+style: RDEPUBHighlightStyle
|
|
+color: String
|
|
+note: String?
|
|
+createdAt: Date
|
|
+uiColor: UIColor
|
|
}
|
|
|
|
class RDEPUBBookmark {
|
|
+id: String
|
|
+bookIdentifier: String?
|
|
+location: RDEPUBLocation
|
|
+chapterTitle: String?
|
|
+note: String?
|
|
+createdAt: Date
|
|
}
|
|
|
|
class RDEPUBAnnotation {
|
|
+id: String
|
|
+bookIdentifier: String?
|
|
+kind: RDEPUBAnnotationKind
|
|
+location: RDEPUBLocation
|
|
+text: String?
|
|
+rangeInfo: String?
|
|
+chapterTitle: String?
|
|
+createdAt: Date
|
|
+bookmark: RDEPUBBookmark?
|
|
+highlight: RDEPUBHighlight?
|
|
}
|
|
|
|
class EPUBPage {
|
|
+spineIndex: Int
|
|
+chapterIndex: Int
|
|
+pageIndexInChapter: Int
|
|
+totalPagesInChapter: Int
|
|
+chapterTitle: String
|
|
+fixedSpread: EPUBFixedSpread?
|
|
}
|
|
|
|
class EPUBChapterInfo {
|
|
+spineIndex: Int
|
|
+title: String
|
|
+pageCount: Int
|
|
}
|
|
|
|
class RDEPUBViewport {
|
|
+resources: [RDEPUBViewportResource]
|
|
+visiblePageNumber: Int
|
|
+chapterIndex: Int?
|
|
+isFixedLayout: Bool
|
|
}
|
|
|
|
class RDEPUBReadingContext {
|
|
+location: RDEPUBLocation
|
|
+viewport: RDEPUBViewport
|
|
+pageNumber: Int
|
|
+chapterIndex: Int?
|
|
}
|
|
|
|
RDEPUBLocation --> RDEPUBTextRangeAnchor : optional
|
|
RDEPUBTextRangeAnchor --> RDEPUBTextAnchor : start/end
|
|
RDEPUBSelection --> RDEPUBLocation : references
|
|
RDEPUBHighlight --> RDEPUBLocation : references
|
|
RDEPUBBookmark --> RDEPUBLocation : references
|
|
RDEPUBAnnotation --> RDEPUBLocation : references
|
|
RDEPUBReadingContext --> RDEPUBLocation : holds
|
|
RDEPUBReadingContext --> RDEPUBViewport : holds
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 后台解析并发模型
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Main as Main Thread
|
|
participant BG as Background Queue
|
|
participant OQ as OperationQueue (N workers)
|
|
participant Disk as Disk Cache
|
|
|
|
Main->>BG: RDEPUBMetadataParseWorker.start(token)
|
|
BG->>BG: 预计算 contentHash (串行)
|
|
BG->>Disk: readAll(catalog) 批量读取缓存
|
|
BG->>Main: refreshBookPageMapInPlace (缓存部分)
|
|
BG->>BG: waitForReadingInteractionToSettle (0.8s)
|
|
|
|
loop 每个未缓存章节
|
|
BG->>OQ: addOperation(spineIndex)
|
|
OQ->>OQ: buildChapter (渲染+分页)
|
|
OQ->>OQ: chapterCacheKey (复用预计算hash)
|
|
OQ->>Disk: write(summary) 异步写盘
|
|
OQ->>OQ: resultLock: 累加结果
|
|
alt 每32章或最后一章
|
|
OQ->>Main: refreshBookPageMapInPlace
|
|
end
|
|
end
|
|
|
|
OQ->>BG: waitUntilAllOperationsAreFinished
|
|
BG->>Disk: flushPendingWrites
|
|
BG->>Main: refreshBookPageMapInPlace (最终)
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 缓存键与失效策略
|
|
|
|
```mermaid
|
|
graph LR
|
|
subgraph CacheKey["RDEPUBChapterCacheKey"]
|
|
A[bookID]
|
|
B[spineIndex]
|
|
C[renderSignature]
|
|
D[chapterContentHash]
|
|
end
|
|
|
|
subgraph RenderSignature["renderSignature 组成"]
|
|
E[fontName]
|
|
F[fontSize]
|
|
G[lineHeightMultiple]
|
|
H[lineSpacing]
|
|
I[layoutConfig.cacheSignature]
|
|
J[schemaVersion]
|
|
end
|
|
|
|
subgraph CacheSignature["layoutConfig.cacheSignature"]
|
|
K[frameWidth]
|
|
L[frameHeight]
|
|
M[edgeInsets]
|
|
N[numberOfColumns]
|
|
O[columnGap]
|
|
P[avoidOrphans/Widows]
|
|
Q[hyphenation]
|
|
R[imageMaxHeightRatio]
|
|
end
|
|
|
|
C --> E & F & G & H & I & J
|
|
I --> K & L & M & N & O & P & Q & R
|
|
|
|
style CacheKey fill:#e1f5fe
|
|
style RenderSignature fill:#fff3e0
|
|
style CacheSignature fill:#e8f5e9
|
|
```
|