mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-24 04:41:23 +08:00
feat: open-source 小浣熊阅读 for iOS
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import CoreText
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
private struct ReaderPositionPreferenceKey: PreferenceKey {
|
||||
static let defaultValue: [Int: CGFloat] = [:]
|
||||
static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) {
|
||||
value.merge(nextValue(), uniquingKeysWith: { _, new in new })
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderStylePreset: Identifiable {
|
||||
let id: String
|
||||
let name: String
|
||||
let background: UIColor
|
||||
let foreground: UIColor
|
||||
|
||||
static let legadoDefaults: [ReaderStylePreset] = [
|
||||
.init(id: "wechat", name: "微信读书", background: UIColor(hex: 0xC0EDC6), foreground: UIColor(hex: 0x0B0B0B)),
|
||||
.init(id: "preset1", name: "预设1", background: UIColor(hex: 0xFFFFFF), foreground: UIColor(hex: 0x000000)),
|
||||
.init(id: "preset2", name: "预设2", background: UIColor(hex: 0xDDC090), foreground: UIColor(hex: 0x3E3422)),
|
||||
.init(id: "preset3", name: "预设3", background: UIColor(hex: 0xC2D8AA), foreground: UIColor(hex: 0x596C44)),
|
||||
.init(id: "preset4", name: "预设4", background: UIColor(hex: 0xDBB8E2), foreground: UIColor(hex: 0x68516C)),
|
||||
.init(id: "preset5", name: "预设5", background: UIColor(hex: 0xABCEE0), foreground: UIColor(hex: 0x3D4C54)),
|
||||
.init(id: "night", name: "夜间", background: UIColor(hex: 0x000000), foreground: UIColor(hex: 0xADADAD))
|
||||
]
|
||||
static let eInk = ReaderStylePreset(
|
||||
id: "eink",
|
||||
name: "E-Ink",
|
||||
background: UIColor(hex: 0xFFFFFF),
|
||||
foreground: UIColor(hex: 0x000000)
|
||||
)
|
||||
|
||||
static func resolve(_ id: String) -> ReaderStylePreset {
|
||||
let migratedID = switch id {
|
||||
case "dark": "night"
|
||||
case "light": "preset1"
|
||||
case "paper": "preset2"
|
||||
default: id
|
||||
}
|
||||
return legadoDefaults.first(where: { $0.id == migratedID }) ?? legadoDefaults[0]
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIColor {
|
||||
convenience init(hex: UInt32) {
|
||||
self.init(
|
||||
red: CGFloat((hex >> 16) & 0xff) / 255,
|
||||
green: CGFloat((hex >> 8) & 0xff) / 255,
|
||||
blue: CGFloat(hex & 0xff) / 255,
|
||||
alpha: 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum ReaderBackgroundStore {
|
||||
static var url: URL {
|
||||
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("Legado/reader-background.jpg")
|
||||
}
|
||||
|
||||
static func load() -> UIImage? {
|
||||
guard let data = try? Data(contentsOf: url) else { return nil }
|
||||
return UIImage(data: data)
|
||||
}
|
||||
|
||||
static func save(_ data: Data) throws -> UIImage {
|
||||
guard let image = UIImage(data: data), let encoded = image.jpegData(compressionQuality: 0.9) else {
|
||||
throw CocoaError(.fileReadCorruptFile)
|
||||
}
|
||||
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try encoded.write(to: url, options: .atomic)
|
||||
return image
|
||||
}
|
||||
|
||||
static func remove() { try? FileManager.default.removeItem(at: url) }
|
||||
}
|
||||
|
||||
enum ReaderFontStore {
|
||||
static var directory: URL {
|
||||
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("Legado/fonts", isDirectory: true)
|
||||
}
|
||||
|
||||
static func install(from source: URL) throws -> String {
|
||||
let accessing = source.startAccessingSecurityScopedResource()
|
||||
defer { if accessing { source.stopAccessingSecurityScopedResource() } }
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
let target = directory.appendingPathComponent(source.lastPathComponent)
|
||||
if target != source {
|
||||
try? FileManager.default.removeItem(at: target)
|
||||
try FileManager.default.copyItem(at: source, to: target)
|
||||
}
|
||||
guard font(at: target.path, size: 20, weight: 0) != nil else {
|
||||
throw CocoaError(.fileReadCorruptFile)
|
||||
}
|
||||
return target.path
|
||||
}
|
||||
|
||||
static func font(at path: String, size: CGFloat, weight: Int) -> UIFont? {
|
||||
guard !path.isEmpty,
|
||||
let provider = CGDataProvider(url: URL(fileURLWithPath: path) as CFURL),
|
||||
let graphicsFont = CGFont(provider) else { return nil }
|
||||
CTFontManagerRegisterGraphicsFont(graphicsFont, nil)
|
||||
guard let postScriptName = graphicsFont.postScriptName else { return nil }
|
||||
let name = postScriptName as String
|
||||
guard let base = UIFont(name: name, size: size) else { return nil }
|
||||
if weight == 1,
|
||||
let descriptor = base.fontDescriptor.withSymbolicTraits(.traitBold) {
|
||||
return UIFont(descriptor: descriptor, size: size)
|
||||
}
|
||||
if weight == 2 {
|
||||
let descriptor = base.fontDescriptor.addingAttributes([
|
||||
.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.light.rawValue]
|
||||
])
|
||||
return UIFont(descriptor: descriptor, size: size)
|
||||
}
|
||||
return base
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user