ReadViewSDK/Doc/UML_CLASS_DIAGRAMS.md
shenlei d7fcda345d refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- 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
2026-07-10 19:44:53 +09:00

735 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```