feat: optimize reader caching and document formats

This commit is contained in:
shenlei
2026-07-20 17:22:06 +09:00
parent 52f803e8db
commit 68d9363f0a
35 changed files with 3771 additions and 2229 deletions
+2 -2
View File
@@ -45,8 +45,8 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
DTCoreText: 11b7fe2104f476f82e75a4e3dbdde74d7186cecb
DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0
RDEpubReaderView: 6bab24a23236a223b29a02f3d00b6ac7829a4b97
RDPDFReaderView: ea8c225655c8fdc1f34d75026e05fa1e50744b36
RDEpubReaderView: 5a09a7856f04b6f2f8610aa6fd965d35cadb8e17
RDPDFReaderView: f7c2d0ea7c129aa4ab15ae7f9daad2974573d749
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
@@ -2,7 +2,7 @@
"name": "RDEpubReaderView",
"module_name": "RDEpubReaderView",
"version": "0.0.2",
"summary": "A reader view for EPUB and plain-text books",
"summary": "A reader view for EPUB, TXT, MOBI, and CBZ books",
"platforms": {
"ios": "15.0"
},
@@ -2,7 +2,7 @@
"name": "RDPDFReaderView",
"module_name": "RDPDFReaderView",
"version": "0.0.1",
"summary": "Independent UIKit PDF reader interaction engine",
"summary": "UIKit PDF reader supporting host images and direct PDFKit parsing",
"platforms": {
"ios": "15.0"
},
@@ -25,7 +25,8 @@
},
"frameworks": [
"Vision",
"CoreImage"
"CoreImage",
"PDFKit"
],
"requires_arc": true,
"swift_version": "5.10"
+2 -2
View File
@@ -45,8 +45,8 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
DTCoreText: 11b7fe2104f476f82e75a4e3dbdde74d7186cecb
DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0
RDEpubReaderView: 6bab24a23236a223b29a02f3d00b6ac7829a4b97
RDPDFReaderView: ea8c225655c8fdc1f34d75026e05fa1e50744b36
RDEpubReaderView: 5a09a7856f04b6f2f8610aa6fd965d35cadb8e17
RDPDFReaderView: f7c2d0ea7c129aa4ab15ae7f9daad2974573d749
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
File diff suppressed because it is too large Load Diff
@@ -5,7 +5,7 @@ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText/DTCoreText.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation/DTFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDEpubReaderView/RDEpubReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDPDFReaderView/RDPDFReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.framework/Headers" "$(SDKROOT)/usr/include/libxml2"
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -l"xml2" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "RDEpubReaderView" -framework "RDPDFReaderView" -framework "SnapKit" -framework "Vision" -framework "ZIPFoundation"
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -l"xml2" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "PDFKit" -framework "QuartzCore" -framework "RDEpubReaderView" -framework "RDPDFReaderView" -framework "SnapKit" -framework "Vision" -framework "ZIPFoundation"
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "-F${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDEpubReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDPDFReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "-F${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
@@ -5,7 +5,7 @@ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText/DTCoreText.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation/DTFoundation.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDEpubReaderView/RDEpubReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/RDPDFReaderView/RDPDFReaderView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation/ZIPFoundation.framework/Headers" "$(SDKROOT)/usr/include/libxml2"
LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -l"xml2" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "RDEpubReaderView" -framework "RDPDFReaderView" -framework "SnapKit" -framework "Vision" -framework "ZIPFoundation"
OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -l"xml2" -framework "CoreGraphics" -framework "CoreImage" -framework "CoreText" -framework "DTCoreText" -framework "DTFoundation" -framework "ImageIO" -framework "MediaPlayer" -framework "PDFKit" -framework "QuartzCore" -framework "RDEpubReaderView" -framework "RDPDFReaderView" -framework "SnapKit" -framework "Vision" -framework "ZIPFoundation"
OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/DTCoreText" "-F${PODS_CONFIGURATION_BUILD_DIR}/DTFoundation" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDEpubReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/RDPDFReaderView" "-F${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "-F${PODS_CONFIGURATION_BUILD_DIR}/ZIPFoundation"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
@@ -3,7 +3,7 @@ CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RDPDFReaderView
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_LDFLAGS = $(inherited) -framework "CoreImage" -framework "SnapKit" -framework "Vision"
OTHER_LDFLAGS = $(inherited) -framework "CoreImage" -framework "PDFKit" -framework "SnapKit" -framework "Vision"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
@@ -3,7 +3,7 @@ CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/RDPDFReaderView
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
OTHER_LDFLAGS = $(inherited) -framework "CoreImage" -framework "SnapKit" -framework "Vision"
OTHER_LDFLAGS = $(inherited) -framework "CoreImage" -framework "PDFKit" -framework "SnapKit" -framework "Vision"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
@@ -20,6 +20,7 @@
1A2B3C4D00000015AABBCC01 /* AsyncChapterLoadingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000016AABBCC01 /* AsyncChapterLoadingTests.swift */; };
1A2B3C4D00000021AABBCC01 /* MemoryFootprintTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000020AABBCC01 /* MemoryFootprintTests.swift */; };
1A2B3C4D00000030AABBCC01 /* FixedLayoutRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000031AABBCC01 /* FixedLayoutRotationTests.swift */; };
1A2B3C4D00000033AABBCC01 /* PDFDrawingModeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B3C4D00000032AABBCC01 /* PDFDrawingModeTests.swift */; };
23BB1155EA379786DAA10A89 /* DisplayTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB49AFCCBC2BE04C82B8F286 /* DisplayTypeTests.swift */; };
369C9658D870DCAFC17EB7F7 /* SearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE01DBDCB8D790832A4DE3F /* SearchTests.swift */; };
3BC5C96D7A0ACF35F2192CC7 /* XCUIApplication+Launch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B4C44287820D68ED6570F8 /* XCUIApplication+Launch.swift */; };
@@ -31,6 +32,18 @@
C08FF8D030048DC5147729E9 /* ReaderOpenCloseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDF066BA12974E6CFBE519F /* ReaderOpenCloseTests.swift */; };
DE1437A969DA1C5F0CBB047D /* Pods_ReadViewDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 792DF85CE4A3DD80D67843C7 /* Pods_ReadViewDemo.framework */; };
FEDB5937CEB858CB06E38E2D /* SettingsPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 201C2B482287866487EFAE66 /* SettingsPanelTests.swift */; };
2B3C4D5E00000001AABBCC01 /* BookmarkChromeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000001AABBCC02 /* BookmarkChromeStateTests.swift */; };
2B3C4D5E00000002AABBCC01 /* BookmarkManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000002AABBCC02 /* BookmarkManagementTests.swift */; };
2B3C4D5E00000003AABBCC01 /* ErrorAndEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000003AABBCC02 /* ErrorAndEdgeCaseTests.swift */; };
2B3C4D5E00000004AABBCC01 /* HighlightsManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000004AABBCC02 /* HighlightsManagementTests.swift */; };
2B3C4D5E00000005AABBCC01 /* LargeBookOnDemandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000005AABBCC02 /* LargeBookOnDemandTests.swift */; };
2B3C4D5E00000006AABBCC01 /* LocationPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000006AABBCC02 /* LocationPersistenceTests.swift */; };
2B3C4D5E00000007AABBCC01 /* NavigationBackwardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000007AABBCC02 /* NavigationBackwardTests.swift */; };
2B3C4D5E00000008AABBCC01 /* SelectionAnnotateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000008AABBCC02 /* SelectionAnnotateTests.swift */; };
2B3C4D5E00000009AABBCC01 /* SelectionMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E00000009AABBCC02 /* SelectionMenuTests.swift */; };
2B3C4D5E0000000AAABBCC01 /* SettingsEffectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E0000000AAABBCC02 /* SettingsEffectTests.swift */; };
2B3C4D5E0000000BAABBCC01 /* TOCInteractionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E0000000BAABBCC02 /* TOCInteractionTests.swift */; };
2B3C4D5E0000000CAABBCC01 /* ToolbarStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3C4D5E0000000CAABBCC02 /* ToolbarStateTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -58,6 +71,7 @@
1A2B3C4D00000016AABBCC01 /* AsyncChapterLoadingTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AsyncChapterLoadingTests.swift; sourceTree = "<group>"; };
1A2B3C4D00000020AABBCC01 /* MemoryFootprintTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MemoryFootprintTests.swift; sourceTree = "<group>"; };
1A2B3C4D00000031AABBCC01 /* FixedLayoutRotationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FixedLayoutRotationTests.swift; sourceTree = "<group>"; };
1A2B3C4D00000032AABBCC01 /* PDFDrawingModeTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PDFDrawingModeTests.swift; sourceTree = "<group>"; };
201C2B482287866487EFAE66 /* SettingsPanelTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsPanelTests.swift; sourceTree = "<group>"; };
20BD15E5D8F04E7E9A239E14 /* ReaderAnnotationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReaderAnnotationTests.swift; sourceTree = "<group>"; };
3A43AED288BFCA3ADBA97DD7 /* Pods-ReadViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReadViewDemo.release.xcconfig"; path = "Target Support Files/Pods-ReadViewDemo/Pods-ReadViewDemo.release.xcconfig"; sourceTree = "<group>"; };
@@ -72,6 +86,18 @@
CFE01DBDCB8D790832A4DE3F /* SearchTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SearchTests.swift; sourceTree = "<group>"; };
DE070C1D2FBF0CC900ED065F /* ReadViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReadViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
FB49AFCCBC2BE04C82B8F286 /* DisplayTypeTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DisplayTypeTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000001AABBCC02 /* BookmarkChromeStateTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BookmarkChromeStateTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000002AABBCC02 /* BookmarkManagementTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BookmarkManagementTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000003AABBCC02 /* ErrorAndEdgeCaseTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ErrorAndEdgeCaseTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000004AABBCC02 /* HighlightsManagementTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HighlightsManagementTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000005AABBCC02 /* LargeBookOnDemandTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LargeBookOnDemandTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000006AABBCC02 /* LocationPersistenceTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LocationPersistenceTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000007AABBCC02 /* NavigationBackwardTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NavigationBackwardTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000008AABBCC02 /* SelectionAnnotateTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectionAnnotateTests.swift; sourceTree = "<group>"; };
2B3C4D5E00000009AABBCC02 /* SelectionMenuTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectionMenuTests.swift; sourceTree = "<group>"; };
2B3C4D5E0000000AAABBCC02 /* SettingsEffectTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsEffectTests.swift; sourceTree = "<group>"; };
2B3C4D5E0000000BAABBCC02 /* TOCInteractionTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TOCInteractionTests.swift; sourceTree = "<group>"; };
2B3C4D5E0000000CAABBCC02 /* ToolbarStateTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ToolbarStateTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -187,6 +213,18 @@
FB49AFCCBC2BE04C82B8F286 /* DisplayTypeTests.swift */,
1A2B3C4D00000008AABBCC01 /* PageNavigationTests.swift */,
A1C5E34F6B7192D400A1B235 /* PDFAnnotationTests.swift */,
2B3C4D5E00000001AABBCC02 /* BookmarkChromeStateTests.swift */,
2B3C4D5E00000002AABBCC02 /* BookmarkManagementTests.swift */,
2B3C4D5E00000003AABBCC02 /* ErrorAndEdgeCaseTests.swift */,
2B3C4D5E00000004AABBCC02 /* HighlightsManagementTests.swift */,
2B3C4D5E00000005AABBCC02 /* LargeBookOnDemandTests.swift */,
2B3C4D5E00000006AABBCC02 /* LocationPersistenceTests.swift */,
2B3C4D5E00000007AABBCC02 /* NavigationBackwardTests.swift */,
2B3C4D5E00000008AABBCC02 /* SelectionAnnotateTests.swift */,
2B3C4D5E00000009AABBCC02 /* SelectionMenuTests.swift */,
2B3C4D5E0000000AAABBCC02 /* SettingsEffectTests.swift */,
2B3C4D5E0000000BAABBCC02 /* TOCInteractionTests.swift */,
2B3C4D5E0000000CAABBCC02 /* ToolbarStateTests.swift */,
20BD15E5D8F04E7E9A239E14 /* ReaderAnnotationTests.swift */,
1A2B3C4D0000000AAABBCC01 /* ReaderAnnotationExtendedTests.swift */,
9EDF066BA12974E6CFBE519F /* ReaderOpenCloseTests.swift */,
@@ -202,6 +240,7 @@
1A2B3C4D00000020AABBCC01 /* MemoryFootprintTests.swift */,
1A2B3C4D00000016AABBCC01 /* AsyncChapterLoadingTests.swift */,
1A2B3C4D00000031AABBCC01 /* FixedLayoutRotationTests.swift */,
1A2B3C4D00000032AABBCC01 /* PDFDrawingModeTests.swift */,
);
path = ReaderUITests;
sourceTree = "<group>";
@@ -355,6 +394,18 @@
23BB1155EA379786DAA10A89 /* DisplayTypeTests.swift in Sources */,
1A2B3C4D00000007AABBCC01 /* PageNavigationTests.swift in Sources */,
A1C5E34F6B7192D400A1B234 /* PDFAnnotationTests.swift in Sources */,
2B3C4D5E00000001AABBCC01 /* BookmarkChromeStateTests.swift in Sources */,
2B3C4D5E00000002AABBCC01 /* BookmarkManagementTests.swift in Sources */,
2B3C4D5E00000003AABBCC01 /* ErrorAndEdgeCaseTests.swift in Sources */,
2B3C4D5E00000004AABBCC01 /* HighlightsManagementTests.swift in Sources */,
2B3C4D5E00000005AABBCC01 /* LargeBookOnDemandTests.swift in Sources */,
2B3C4D5E00000006AABBCC01 /* LocationPersistenceTests.swift in Sources */,
2B3C4D5E00000007AABBCC01 /* NavigationBackwardTests.swift in Sources */,
2B3C4D5E00000008AABBCC01 /* SelectionAnnotateTests.swift in Sources */,
2B3C4D5E00000009AABBCC01 /* SelectionMenuTests.swift in Sources */,
2B3C4D5E0000000AAABBCC01 /* SettingsEffectTests.swift in Sources */,
2B3C4D5E0000000BAABBCC01 /* TOCInteractionTests.swift in Sources */,
2B3C4D5E0000000CAABBCC01 /* ToolbarStateTests.swift in Sources */,
52A6A0C9941A4B5F9B88C6B0 /* ReaderAnnotationTests.swift in Sources */,
1A2B3C4D00000009AABBCC01 /* ReaderAnnotationExtendedTests.swift in Sources */,
C08FF8D030048DC5147729E9 /* ReaderOpenCloseTests.swift in Sources */,
@@ -370,6 +421,7 @@
1A2B3C4D00000021AABBCC01 /* MemoryFootprintTests.swift in Sources */,
1A2B3C4D00000015AABBCC01 /* AsyncChapterLoadingTests.swift in Sources */,
1A2B3C4D00000030AABBCC01 /* FixedLayoutRotationTests.swift in Sources */,
1A2B3C4D00000033AABBCC01 /* PDFDrawingModeTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -10,19 +10,23 @@ enum PDFDemoImageTextSource: String {
case none
}
/// PDFKit 宿 PDF SDK
private final class PDFDemoPageProvider: RDPDFReaderPageProvider, RDPDFReaderOutlineProviding {
/// 宿 PDF
private final class PDFDemoPageProvider: RDPDFReaderPageProvider, RDPDFReaderOutlineProviding, RDPDFReaderAsyncOutlineProviding {
private let document: PDFDocument
private let identifier: String
private let title: String
private let textSource: PDFDemoImageTextSource
private var images: [Int: UIImage] = [:]
private let renderQueue = DispatchQueue(label: "com.readviewsdk.demo-pdf-render", qos: .userInitiated)
private let images = NSCache<NSNumber, UIImage>()
private var outlineItemsCache: [RDPDFReaderOutlineItem]?
init(document: PDFDocument, identifier: String, title: String, textSource: PDFDemoImageTextSource) {
self.document = document
self.identifier = identifier
self.title = title
self.textSource = textSource
images.countLimit = 6
images.totalCostLimit = 96 * 1024 * 1024
}
func readerBookDescriptor() -> RDPDFReaderBookDescriptor {
@@ -35,19 +39,46 @@ private final class PDFDemoPageProvider: RDPDFReaderPageProvider, RDPDFReaderOut
case .hostFixture: textRuns = fixtureTextRuns(for: index)
case .vision, .none: textRuns = nil
}
completion(.init(index: index, image: image(at: index), textRuns: textRuns))
renderQueue.async { [weak self] in
guard let self else { return }
let image = self.image(at: index)
DispatchQueue.main.async { completion(.init(index: index, image: image, textRuns: textRuns)) }
}
}
func readerThumbnail(at index: Int, targetSize: CGSize, completion: @escaping (UIImage?) -> Void) {
guard let page = document.page(at: index) else { completion(nil); return }
let box = page.bounds(for: .mediaBox)
let scale = min(targetSize.width / box.width, targetSize.height / box.height) * UIScreen.main.scale
let size = CGSize(width: box.width * scale, height: box.height * scale)
completion(render(page, size: size, scale: scale))
renderQueue.async { [weak self] in
guard let self, let page = self.document.page(at: index) else {
DispatchQueue.main.async { completion(nil) }
return
}
let box = page.bounds(for: .mediaBox)
let scale = min(targetSize.width / box.width, targetSize.height / box.height) * UIScreen.main.scale
let size = CGSize(width: box.width * scale, height: box.height * scale)
let image = self.render(page, size: size, scale: scale)
DispatchQueue.main.async { completion(image) }
}
}
func readerOutlineItems() -> [RDPDFReaderOutlineItem] {
guard let root = document.outlineRoot else { return pageOutlineItems() }
renderQueue.sync { outlineItemsLocked() }
}
func readerOutlineItems(completion: @escaping ([RDPDFReaderOutlineItem]) -> Void) {
renderQueue.async { [weak self] in
guard let self else { return }
let items = self.outlineItemsLocked()
DispatchQueue.main.async { completion(items) }
}
}
private func outlineItemsLocked() -> [RDPDFReaderOutlineItem] {
if let outlineItemsCache { return outlineItemsCache }
guard let root = document.outlineRoot else {
let items = pageOutlineItems()
outlineItemsCache = items
return items
}
var items: [RDPDFReaderOutlineItem] = []
func append(_ outline: PDFOutline, level: Int) {
for index in 0..<outline.numberOfChildren {
@@ -60,7 +91,9 @@ private final class PDFDemoPageProvider: RDPDFReaderPageProvider, RDPDFReaderOut
}
}
append(root, level: 0)
return items.isEmpty ? pageOutlineItems() : items
let resolved = items.isEmpty ? pageOutlineItems() : items
outlineItemsCache = resolved
return resolved
}
func transformed(_ image: UIImage, for theme: RDPDFReaderThemeOption) -> UIImage {
@@ -81,12 +114,14 @@ private final class PDFDemoPageProvider: RDPDFReaderPageProvider, RDPDFReaderOut
}
private func image(at index: Int) -> UIImage? {
if let image = images[index] { return image }
if let image = images.object(forKey: NSNumber(value: index)) { return image }
guard let page = document.page(at: index) else { return nil }
let box = page.bounds(for: .mediaBox)
let scale = max(2, UIScreen.main.scale)
let image = render(page, size: CGSize(width: box.width * scale, height: box.height * scale), scale: scale)
images[index] = image
let cost = image.cgImage.map { $0.bytesPerRow * $0.height }
?? Int(image.size.width * image.size.height * image.scale * image.scale * 4)
images.setObject(image, forKey: NSNumber(value: index), cost: cost)
return image
}
@@ -118,6 +153,8 @@ final class PDFDemoReaderViewController: UIViewController, RDPDFReaderViewContro
private let reader: RDPDFReaderViewController
private var navigationBarHiddenBeforeReader: Bool?
private var display = "pagecurl"
private var lastPageIndex = 0
private var lastCopiedText: String?
private let stateLabel: UILabel = {
let label = UILabel()
label.accessibilityIdentifier = "demo.reader.state"
@@ -127,19 +164,44 @@ final class PDFDemoReaderViewController: UIViewController, RDPDFReaderViewContro
}()
init(url: URL, bookIdentifier: String? = nil, textSource: PDFDemoImageTextSource = .vision) {
guard let document = PDFDocument(url: url) else { fatalError("无法打开 PDF") }
let identifier = bookIdentifier ?? url.lastPathComponent
let provider = PDFDemoPageProvider(document: document, identifier: identifier, title: url.deletingPathExtension().lastPathComponent, textSource: textSource)
let store = RDPDFReaderPersistenceStore(rootURL: Self.annotationStorageURL(for: identifier, legacyBookURL: url))
var configuration = RDPDFReaderViewController.Configuration()
configuration.enablesOCR = textSource == .vision
configuration.missingTextSource = .region
configuration.recognitionLanguages = ["zh-Hans", "en-US"]
configuration.pageImageTransform = { [weak provider] image, theme in provider?.transformed(image, for: theme) ?? image }
reader = RDPDFReaderViewController(pageProvider: provider, annotationPersistence: store, configuration: configuration)
if textSource != .vision {
guard let document = PDFDocument(url: url) else { fatalError("无法打开 PDF") }
let provider = PDFDemoPageProvider(
document: document,
identifier: identifier,
title: url.deletingPathExtension().lastPathComponent,
textSource: textSource
)
configuration.pageImageTransform = { [weak provider] image, theme in
provider?.transformed(image, for: theme) ?? image
}
reader = RDPDFReaderViewController(
pageProvider: provider,
annotationPersistence: store,
configuration: configuration
)
} else {
do {
reader = try RDPDFReaderViewController(
pdfURL: url,
bookIdentifier: identifier,
annotationPersistence: store,
configuration: configuration
)
} catch {
fatalError("无法打开 PDF\(error.localizedDescription)")
}
}
super.init(nibName: nil, bundle: nil)
reader.delegate = self
title = provider.readerBookDescriptor().title
title = url.deletingPathExtension().lastPathComponent
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
@@ -182,6 +244,11 @@ final class PDFDemoReaderViewController: UIViewController, RDPDFReaderViewContro
func pdfReaderViewController(_ controller: RDPDFReaderViewController, didChangePage pageIndex: Int) { updateState(page: pageIndex) }
func pdfReaderViewController(_ controller: RDPDFReaderViewController, didCopyText text: String) {
lastCopiedText = text
updateState(page: lastPageIndex)
}
func pdfReaderViewController(_ controller: RDPDFReaderViewController, didFailAnnotationPersistence error: Error) {
stateLabel.text = "reader=opened mode=pdf annotationStorage=error"
guard presentedViewController == nil else { return }
@@ -190,7 +257,16 @@ final class PDFDemoReaderViewController: UIViewController, RDPDFReaderViewContro
present(alert, animated: true)
}
private func updateState(page: Int) { stateLabel.text = "reader=opened mode=pdf page=\(page + 1) display=\(display)" }
private func updateState(page: Int) {
lastPageIndex = page
var state = "reader=opened mode=pdf page=\(page + 1) display=\(display)"
if let lastCopiedText {
// UI
let encoded = lastCopiedText.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? ""
state += " copy=success copiedText=\(encoded)"
}
stateLabel.text = state
}
private static func annotationStorageURL(for bookIdentifier: String, legacyBookURL: URL) -> URL {
let root = annotationStorageRoot()
@@ -112,7 +112,7 @@ final class ViewController: UIViewController {
label.textAlignment = .center
label.textColor = .secondaryLabel
label.font = .systemFont(ofSize: 15)
label.text = "book 目录下暂时没有可阅读的 txt 或 epub 文件"
label.text = "book 目录下暂时没有可阅读的 EPUB、TXT、MOBI、CBZ 或 PDF 文件"
label.accessibilityIdentifier = "demo.books.empty"
return label
}()
@@ -165,7 +165,7 @@ final class ViewController: UIViewController {
private func updateChrome() {
if books.isEmpty {
statusLabel.text = "ReadViewSDK Demo\nbook 目录下未找到 txt 或 epub 文件"
statusLabel.text = "ReadViewSDK Demo\nbook 目录下未找到 EPUB、TXT、MOBI、CBZ 或 PDF 文件"
tableView.backgroundView = emptyStateLabel
} else {
statusLabel.text = "ReadViewSDK Demo\n已发现 \(books.count) 本本地图书,点击即可进入阅读器"
@@ -191,7 +191,7 @@ final class ViewController: UIViewController {
return fileURLs
.filter { url in
let ext = url.pathExtension.lowercased()
return ext == "epub" || ext == "txt" || ext == "pdf"
return RDReaderDocumentFormat.supports(url) || ext == "pdf"
}
.reduce(into: [URL]()) { result, url in
if !result.contains(url) {
@@ -44,9 +44,9 @@ enum IDs {
/// The text layer is the precise, image-coordinate based interaction surface.
static let pdfTextLayer = "pdf.reader.text.layer"
static let pdfOCRStatus = "pdf.reader.ocr.status"
/// PDF EPUB 使//
/// PDF EPUB 使/线/
static let pdfSelectionMenuCopy = "复制"
static let pdfSelectionMenuHighlight = "高亮"
static let pdfSelectionMenuHighlight = "划线"
static let pdfSelectionMenuAnnotate = "注释"
static let pdfCopyStatus = "pdf.reader.copy.status"
@@ -119,7 +119,9 @@ final class HighlightsManagementTests: XCTestCase {
let deleteControl = highlightsTable.buttons["删除"].firstMatch
XCTAssertTrue(deleteControl.waitForExistence(timeout: 3), "编辑状态应显示删除控件")
deleteControl.tap()
app.buttons["删除"].lastMatch.tap()
//
let deleteButtons = app.buttons.matching(identifier: "删除")
deleteButtons.element(boundBy: max(0, deleteButtons.count - 1)).tap()
XCTAssertTrue(app.staticTexts[IDs.readerHighlightsEmptyLabel].waitForExistence(timeout: 5), "删除后应显示空状态")
}
@@ -153,7 +153,7 @@ final class LocationPersistenceTests: XCTestCase {
}
func testHighlightNavigationSurvivesFontChoiceChange() throws {
app.launchAndOpenSampleBook(resetsReaderState: true, pageNumber: 3)
app.launchAndOpenSampleBook(pageNumber: 3, resetsReaderState: true)
app.waitForReader(timeout: 12)
let initialState = app.waitForDemoReaderState(timeout: 8, description: "初始位于第 3 页") { state in
@@ -58,6 +58,46 @@ final class PDFDrawingModeTests: XCTestCase {
)
}
///
func testZoomedPageDragsOnlyWhenDrawingToolIsDeselected() {
app.launchAndOpenSampleBook(bookTitleQuery: "英汉大词典", displayType: "pagecurl")
app.waitForReader()
let content = app.otherElements[IDs.readerContent].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 8), "PDF 阅读页面未出现")
app.showReaderChromeIfNeeded(requiredButtonIDs: [IDs.pdfDrawing])
let drawing = app.buttons[IDs.pdfDrawing]
XCTAssertTrue(drawing.waitForExistence(timeout: 5), "画笔入口未出现")
drawing.tap()
let pen = app.buttons[IDs.pdfDrawingPen]
XCTAssertTrue(pen.waitForExistence(timeout: 5), "画笔工具未出现")
//
content.pinch(withScale: 2.5, velocity: 1)
RunLoop.current.run(until: Date().addingTimeInterval(1.0))
let zoomedFrame = content.frame
XCTAssertGreaterThan(zoomedFrame.width, app.windows.firstMatch.frame.width * 1.2, "画笔面板打开时无法双指放大页面")
//
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.3, dy: 0.3))
start.press(forDuration: 0.05, thenDragTo: end)
RunLoop.current.run(until: Date().addingTimeInterval(0.8))
let draggedFrame = content.frame
XCTAssertNotEqual(draggedFrame.origin, zoomedFrame.origin, "未选中工具时,放大页面无法单指拖动")
//
pen.tap()
let frameBeforeStroke = content.frame
let strokeStart = content.coordinate(withNormalizedOffset: CGVector(dx: 0.45, dy: 0.45))
let strokeEnd = content.coordinate(withNormalizedOffset: CGVector(dx: 0.6, dy: 0.6))
strokeStart.press(forDuration: 0.05, thenDragTo: strokeEnd)
RunLoop.current.run(until: Date().addingTimeInterval(0.8))
XCTAssertEqual(content.frame.origin, frameBeforeStroke.origin, "选中画笔后,单指仍拖动了放大页面而不是绘制")
}
private func waitForLandscapeLayout() -> Bool {
let deadline = Date().addingTimeInterval(8)
while Date() < deadline {
@@ -69,8 +109,12 @@ final class PDFDrawingModeTests: XCTestCase {
}
private func scrollToAnotherPage(_ content: XCUIElement, from page: Int) -> Bool {
let window = app.windows.firstMatch
for _ in 0..<5 {
content.swipeUp()
//
let start = window.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.35))
let end = window.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
start.press(forDuration: 0.05, thenDragTo: end, withVelocity: .fast, thenHoldForDuration: 0)
let deadline = Date().addingTimeInterval(1.5)
while Date() < deadline {
if let current = app.currentDemoReaderState()?.page, current != page { return true }