- PaginationCoordinator: 新增 textReflowable 快速进入路径,支持增量章节构建 与 staged 合并策略,避免整书一次性构建的主线程阻塞 - TextBookBuilder: 重构为支持单章构建与增量合并模式 - ChapterPageCounter: 优化分页计数逻辑,支持章节级分页缓存 - PageFrameFactory: 新增 CoreText 页面帧工厂,提升排版性能 - ReaderContext/ReaderRuntime: 适配增量分页状态管理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
57 lines
2.5 KiB
Swift
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
|
||
}
|
||
}
|