Files
ReadViewSDK/Sources/RDReaderView/EPUBCore/RDEPUBParser+ReadingProfile.swift
T
shenleiandClaude Opus 4.7 c76beed03c feat(reader): 实现大书快速进入与增量分页路径
- PaginationCoordinator: 新增 textReflowable 快速进入路径,支持增量章节构建
  与 staged 合并策略,避免整书一次性构建的主线程阻塞
- TextBookBuilder: 重构为支持单章构建与增量合并模式
- ChapterPageCounter: 优化分页计数逻辑,支持章节级分页缓存
- PageFrameFactory: 新增 CoreText 页面帧工厂,提升排版性能
- ReaderContext/ReaderRuntime: 适配增量分页状态管理

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 21:16:57 +08:00

57 lines
2.5 KiB
Swift

// RDEPUBParser+ReadingProfile.swift
// EPUB 阅读配置文件判断
// 根据 metadata 的 layout 属性和 manifest/spine 内容,
// 将出版物分类为 webFixedLayout(固定版式)、webInteractive(含脚本/多媒体)或 textReflowable(纯文本重排)。
import Foundation
extension RDEPUBParser {
/// 判断出版物的阅读配置文件
/// 优先判断固定版式,其次检测交互内容(脚本/iframe/视频等),默认为文本重排
public func readingProfile() -> RDEPUBReadingProfile {
if metadata.layout == .fixed {
return .webFixedLayout
}
return hasInteractiveContent() ? .webInteractive : .textReflowable
}
/// 检测出版物是否包含交互内容
/// 通过 manifest 中的脚本媒体类型和 "scripted" 属性,
/// 以及扫描 HTML 中的 script/iframe/video/audio/canvas/svg/form 等标签
public func hasInteractiveContent() -> Bool {
let interactiveMediaTypes = [
"application/javascript",
"text/javascript",
"application/ecmascript"
]
if manifest.values.contains(where: { item in
interactiveMediaTypes.contains(item.mediaType.lowercased()) || item.properties.contains("scripted")
}) {
return true
}
for item in spine where item.linear && (item.mediaType.contains("html") || item.mediaType.contains("xhtml")) {
guard let html = htmlString(forRelativePath: item.href) else {
continue
}
if containsInteractiveMarkup(html) {
return true
}
}
return false
}
/// 检测 HTML 是否包含需要走 Web 交互渲染的特征。
/// 注意:普通小说 EPUB 常用静态 SVG 包封面图,不能仅因 `<svg>` 就误判为 interactive。
private func containsInteractiveMarkup(_ html: String) -> Bool {
let interactivePattern = #"<(script|iframe|video|audio|canvas|object|embed)\b|\bon(load|click|touchstart|touchend|mouseover|submit|change|input)=|hype_generated_script|swiper|webview"#
if html.range(of: interactivePattern, options: [.regularExpression, .caseInsensitive]) != nil {
return true
}
let dynamicSVGPattern = #"<svg\b[^>]*>[\s\S]*?(<(script|foreignObject|animate|animateMotion|animateTransform|set)\b|\bon(load|click|touchstart|touchend|mouseover)=)"#
return html.range(of: dynamicSVGPattern, options: [.regularExpression, .caseInsensitive]) != nil
}
}