mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-24 21:01:22 +08:00
feat: 支持 EPUB 阅读并改善规则兼容与阅读稳定性
支持 EPUB 2/3 导入、目录、封面和章节插图,保留本地书管理与阅读进度。 修复 JSON 模板、动态替换与请求参数兼容,以及 RSS 翻页和连续滚动定位。 更新编辑器工具栏和回归测试,发布版本为 0.1.12(build 18)。
This commit is contained in:
@@ -129,7 +129,8 @@ struct ReaderScrollView: UIViewRepresentable {
|
||||
let layout = view.collectionViewLayout as! ReaderContinuousLayout
|
||||
let geometry = ReaderContinuousLayout.geometry(
|
||||
blocks: newBlocks, width: view.bounds.width,
|
||||
top: CGFloat(config.paddingTop), bottom: CGFloat(config.paddingBottom), viewportHeight: view.bounds.height
|
||||
top: CGFloat(config.paddingTop), bottom: CGFloat(config.paddingBottom),
|
||||
viewportHeight: view.bounds.height, scale: view.traitCollection.displayScale
|
||||
)
|
||||
let targetY = anchor.flatMap { offset(for: $0, blocks: newBlocks, frames: geometry.frames) }
|
||||
let correction = targetY.map { $0 - view.contentOffset.y } ?? 0
|
||||
@@ -277,10 +278,14 @@ struct ReaderScrollView: UIViewRepresentable {
|
||||
private func captureAnchor() -> Anchor? {
|
||||
guard let view, !blocks.isEmpty else { return nil }
|
||||
let layout = view.collectionViewLayout as! ReaderContinuousLayout
|
||||
let y = max(view.contentOffset.y, layout.frames.first?.minY ?? 0)
|
||||
guard let row = layout.row(at: y) else { return nil }
|
||||
// The reading position starts below the configured top padding.
|
||||
// Preserve that reference point when earlier chapters are inserted.
|
||||
let y = max(view.contentOffset.y + CGFloat(max(parent.configuration.paddingTop, 0)),
|
||||
layout.frames.first?.minY ?? 0)
|
||||
let scale = max(view.traitCollection.displayScale, 1)
|
||||
guard let row = layout.row(at: (y * scale).rounded() / scale) else { return nil }
|
||||
let block = blocks[row], chapter = block.chapter
|
||||
let localY = y - layout.frames[row].minY + block.top
|
||||
let localY = max(y - layout.frames[row].minY, 0) + block.top
|
||||
let glyph = chapter.manager.glyphIndex(for: CGPoint(x: 0, y: localY + 0.5), in: chapter.container)
|
||||
let safeGlyph = min(glyph, max(chapter.manager.numberOfGlyphs - 1, 0))
|
||||
let character = chapter.manager.characterIndexForGlyph(at: safeGlyph)
|
||||
@@ -385,17 +390,21 @@ final class ReaderContinuousLayout: UICollectionViewLayout {
|
||||
private var size = CGSize.zero
|
||||
override var collectionViewContentSize: CGSize { size }
|
||||
|
||||
static func geometry(blocks: [ReaderScrollBlock], width: CGFloat, top: CGFloat, bottom: CGFloat, viewportHeight: CGFloat) -> (frames: [CGRect], size: CGSize) {
|
||||
var y = top
|
||||
static func geometry(blocks: [ReaderScrollBlock], width: CGFloat, top: CGFloat, bottom: CGFloat, viewportHeight: CGFloat, scale: CGFloat) -> (frames: [CGRect], size: CGSize) {
|
||||
let scale = max(scale, 1)
|
||||
// Keep insertion offsets in whole physical pixels. UIKit rounds scroll
|
||||
// offsets, so fractional chapter heights otherwise accumulate drift.
|
||||
var pixels = (top * scale).rounded()
|
||||
let frames = blocks.map { block -> CGRect in
|
||||
let frame = CGRect(x: 0, y: y, width: width, height: block.height)
|
||||
y += block.height
|
||||
let height = ceil(block.height * scale)
|
||||
let frame = CGRect(x: 0, y: pixels / scale, width: width, height: height / scale)
|
||||
pixels += height
|
||||
return frame
|
||||
}
|
||||
let finalChapter = blocks.last?.chapter.chapter.id
|
||||
let finalStart = blocks.indices.first(where: { blocks[$0].chapter.chapter.id == finalChapter })
|
||||
.map { frames[$0].minY } ?? top
|
||||
return (frames, CGSize(width: width, height: max(y, finalStart + viewportHeight) + bottom))
|
||||
return (frames, CGSize(width: width, height: max(pixels / scale, finalStart + viewportHeight) + bottom))
|
||||
}
|
||||
|
||||
func install(frames: [CGRect], size: CGSize, correction: CGFloat) {
|
||||
@@ -412,9 +421,9 @@ final class ReaderContinuousLayout: UICollectionViewLayout {
|
||||
var low = 0, high = frames.count
|
||||
while low < high {
|
||||
let middle = (low + high) / 2
|
||||
if frames[middle].maxY <= y { low = middle + 1 } else { high = middle }
|
||||
if frames[middle].minY <= y { low = middle + 1 } else { high = middle }
|
||||
}
|
||||
return min(low, frames.count - 1)
|
||||
return max(low - 1, 0)
|
||||
}
|
||||
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
|
||||
Reference in New Issue
Block a user