- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
58 lines
2.2 KiB
Swift
58 lines
2.2 KiB
Swift
import UIKit
|
|
|
|
#if canImport(DTCoreText)
|
|
import DTCoreText
|
|
|
|
/// Redistributes the leftover space at the bottom of a page into the gaps
|
|
/// between lines so the last line sits flush with the content bottom edge
|
|
/// (vertical justification), keeping page character ranges untouched.
|
|
///
|
|
/// Mutating each line's `baselineOrigin` is sufficient: DTCoreText derives
|
|
/// line frames, glyph-run frames and attachment positions from it lazily,
|
|
/// so drawing, selection, highlights and hit-testing all stay consistent.
|
|
enum RDEPUBTextPageVerticalJustifier {
|
|
|
|
/// Leftover larger than this many typical line advances is kept as
|
|
/// whitespace instead of being stretched: it usually comes from a whole
|
|
/// block (image, table) pushed to the next page, and stretching would
|
|
/// make the line spacing visibly sparse.
|
|
static let maxStretchLineAdvanceRatio: CGFloat = 1.5
|
|
|
|
static func justify(
|
|
_ layoutFrame: DTCoreTextLayoutFrame,
|
|
contentHeight: CGFloat,
|
|
isChapterLastPage: Bool,
|
|
pixelScale: CGFloat
|
|
) {
|
|
guard !isChapterLastPage,
|
|
contentHeight > 0,
|
|
let lines = layoutFrame.lines as? [DTCoreTextLayoutLine],
|
|
lines.count >= 2,
|
|
let firstLine = lines.first,
|
|
let lastLine = lines.last else {
|
|
return
|
|
}
|
|
|
|
let leftover = contentHeight - lastLine.frame.maxY
|
|
guard leftover > 0.5 else { return }
|
|
|
|
let gapCount = CGFloat(lines.count - 1)
|
|
let typicalAdvance = (lastLine.baselineOrigin.y - firstLine.baselineOrigin.y) / gapCount
|
|
guard typicalAdvance > 0,
|
|
leftover <= typicalAdvance * maxStretchLineAdvanceRatio else {
|
|
return
|
|
}
|
|
|
|
let scale = max(pixelScale, 1)
|
|
for (index, line) in lines.enumerated() where index > 0 {
|
|
// Round each cumulative shift down to the pixel grid so glyphs
|
|
// stay sharp and the last line never overshoots the bottom edge.
|
|
let shift = floor(leftover * CGFloat(index) / gapCount * scale) / scale
|
|
var origin = line.baselineOrigin
|
|
origin.y += shift
|
|
line.baselineOrigin = origin
|
|
}
|
|
}
|
|
}
|
|
#endif
|