feat: add in-reader search and restructure documentation
- Add RDEPUBReaderSearchBarView with animated show/hide, keyword navigation, and match counting integrated into the reader controller - Restructure docs: replace scattered design docs with consolidated BUSINESS_LOGIC.md and UML_CLASS_DIAGRAMS.md; update ARCHITECTURE.md - Add SearchTests and FanrenParseTimeTest; enhance LargeBookOnDemandTests - Add scripts/run_ui_regression.sh and summarize_ui_results.py for automated UI test execution and reporting Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d20196ee34
commit
0e7c0577e3
@@ -0,0 +1,687 @@
|
||||
# ReadViewSDK UML 类图
|
||||
|
||||
> 最后更新:2026-06-04
|
||||
> 图表格式: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 RDReaderView
|
||||
ReaderView[RDReaderView]
|
||||
FlowLayout[RDReaderFlowLayout]
|
||||
Preload[RDReaderPreloadController]
|
||||
Spread[RDReaderSpreadResolver]
|
||||
TapRegion[RDReaderTapRegionHandler]
|
||||
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 RDReaderView {
|
||||
+currentPage: Int
|
||||
+currentDisplayType: DisplayType
|
||||
+pageProvider: RDReaderPageProvider?
|
||||
+delegate: RDReaderDelegate?
|
||||
+landscapeDualPageEnabled: Bool
|
||||
+pageDirection: PageDirection
|
||||
+coverPageIndex: Int?
|
||||
+pagesPerScreen: Int
|
||||
+transitionToPage(pageNum:animated:)
|
||||
+reloadData()
|
||||
+switchReaderDisplayType(_:)
|
||||
}
|
||||
|
||||
class DisplayType {
|
||||
<<enum>>
|
||||
pageCurl
|
||||
horizontalScroll
|
||||
verticalScroll
|
||||
}
|
||||
|
||||
class RDReaderPageProvider {
|
||||
<<protocol>>
|
||||
+numberOfPages(in:) Int
|
||||
+readerView(_:viewForPageAt:reusableView:) UIView
|
||||
+pageIdentifier(in:index:) String?
|
||||
}
|
||||
|
||||
class RDReaderFlowLayout {
|
||||
+displayType: DisplayType
|
||||
+isLandscapeDualPage: Bool
|
||||
+coverPageIndex: Int?
|
||||
+pagesPerScreen: Int
|
||||
+currentPage: Int
|
||||
}
|
||||
|
||||
class RDReaderPreloadController {
|
||||
+radius: Int
|
||||
+pageViewForDisplay(pageNum:environment:contentViewProvider:) UIView?
|
||||
+takePreloadedView(for:) UIView?
|
||||
+prime(around:preferredForward:parentView:environment:contentViewProvider:)
|
||||
+invalidate(environment:)
|
||||
}
|
||||
|
||||
class RDReaderSpreadResolver {
|
||||
+isFullScreenPage(...) Bool
|
||||
+dualPagePair(for:totalPages:coverPageIndex:) (Int, Int?)
|
||||
+nextPage(from:totalPages:pagesPerScreen:coverPageIndex:forward:) Int?
|
||||
}
|
||||
|
||||
class RDReaderTapRegionHandler {
|
||||
+resolveTapEvent(point:viewFrame:isToolViewVisible:) TapEvent
|
||||
}
|
||||
|
||||
class TapEvent {
|
||||
<<enum>>
|
||||
none
|
||||
left
|
||||
center
|
||||
right
|
||||
}
|
||||
|
||||
class RDReaderPagingController {
|
||||
+isTransitioning: Bool
|
||||
+didBuildUI: Bool
|
||||
+pendingTransitionRequest: PageTransitionRequest?
|
||||
+shouldQueuePageTransition(_:currentDisplayType:) Bool
|
||||
+finishPageCurlTransition() PageTransitionRequest?
|
||||
}
|
||||
|
||||
class RDReaderContentCell {
|
||||
+containerView: UIView?
|
||||
}
|
||||
|
||||
class RDReaderPageChildViewController {
|
||||
+contentView: UIView?
|
||||
+pageNum: Int
|
||||
}
|
||||
|
||||
RDReaderView --> DisplayType : uses
|
||||
RDReaderView --> RDReaderPageProvider : delegates
|
||||
RDReaderView --> RDReaderFlowLayout : owns
|
||||
RDReaderView --> RDReaderPreloadController : owns
|
||||
RDReaderView --> RDReaderSpreadResolver : owns
|
||||
RDReaderView --> RDReaderTapRegionHandler : owns
|
||||
RDReaderView --> RDReaderPagingController : owns
|
||||
RDReaderView --> RDReaderContentCell : creates
|
||||
RDReaderView --> RDReaderPageChildViewController : creates
|
||||
RDReaderTapRegionHandler --> 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: RDReaderView
|
||||
+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
|
||||
+viewportMonitor: RDEPUBViewportMonitor
|
||||
+applyBookPageMap(_:restoreLocation:)
|
||||
+refreshBookPageMapInPlace(_:)
|
||||
}
|
||||
|
||||
class RDEPUBReaderPaginationCoordinator {
|
||||
-context: RDEPUBReaderContext
|
||||
+paginatePublication(restoreLocation:)
|
||||
+paginateMetadataOnly(token:restoreLocation:)
|
||||
+repaginatePreservingCurrentLocation()
|
||||
-restoreBookPageMapIfPossible(publication:) RDEPUBBookPageMap?
|
||||
-buildPageMap(from:summaries:) RDEPUBBookPageMap
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
+kind: RDEPUBAnnotationKind
|
||||
+location: RDEPUBLocation
|
||||
+text: String?
|
||||
+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: paginateMetadataOnly(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
|
||||
```
|
||||
Reference in New Issue
Block a user