import Foundation /// Open-source license / attribution text bundled with PunktfunkKit (see `Resources/`). /// /// Exposed from the kit so the app shell can show an Acknowledgements screen. The text files are /// bundled as SwiftPM resources and read via `Bundle.module`, which works both for `swift build` /// and for the Xcode app (it links the PunktfunkKit product, so the resource bundle rides along). public enum Licenses { private static func resource(_ name: String) -> String { guard let url = Bundle.module.url(forResource: name, withExtension: "txt"), let text = try? String(contentsOf: url, encoding: .utf8) else { return "" } return text } /// Punktfunk's own license — MIT OR Apache-2.0, at your option. public static var appLicense: String { let mit = resource("LICENSE-MIT") let apache = resource("LICENSE-APACHE") if mit.isEmpty && apache.isEmpty { return "Punktfunk is licensed under MIT OR Apache-2.0, at your option." } return "Punktfunk is licensed under MIT OR Apache-2.0, at your option.\n\n" + "================================ MIT ================================\n\n" + mit + "\n\n============================== Apache-2.0 ==============================\n\n" + apache } /// The bundled brand typeface (Geist Sans + Geist Mono) — SIL Open Font License 1.1. The /// license file ships alongside the OTFs in `Resources/Fonts/`, satisfying the OFL's /// distribution requirement; this surfaces it in the Acknowledgements screen too. public static var fontLicense: String { guard let url = Bundle.module.url( forResource: "Geist-OFL", withExtension: "txt", subdirectory: "Fonts"), let text = try? String(contentsOf: url, encoding: .utf8) else { return "" } return text } /// Third-party software notices for the linked Rust crates (generated by /// `scripts/gen-third-party-notices.sh`). public static var thirdPartyNotices: String { let text = resource("THIRD-PARTY-NOTICES") return text.isEmpty ? "Third-party notices unavailable." : text } /// `thirdPartyNotices` pre-split into render-sized line chunks. The full notices are ~885 KB / /// 16k lines; a single SwiftUI `Text` that large overshoots CoreText/CoreAnimation's max /// renderable height — it lays out for ages and draws blank past the limit — so the /// Acknowledgements screen renders these chunks in a `LazyVStack` (only on-screen chunks lay /// out, and no chunk is tall enough to clip). Split at line boundaries and joined with "\n"; /// the inter-chunk break is the `LazyVStack` row boundary, so no text is lost. Computed once. public static let thirdPartyNoticesChunks: [String] = chunked(thirdPartyNotices) /// Lines per chunk: tvOS reads much smaller chunks — focus is how tvOS scrolls, and each /// chunk is one focus stop, so a 200-line chunk (~5 screens tall there) would skip most of /// itself per step; ~24 lines ≈ two thirds of a screen reads like a page turn. Elsewhere the /// only constraint is the text-render height limit, so chunks stay big. private static var chunkLines: Int { #if os(tvOS) 24 #else 200 #endif } /// `text` split at line boundaries into render/focus-sized chunks (joined with "\n"; the /// inter-chunk break is the caller's stack-row boundary, so no text is lost). tvOS pages /// focus through these — every license wall on the Acknowledgements screen renders this way. public static func chunked(_ text: String) -> [String] { let lines = text.split(separator: "\n", omittingEmptySubsequences: false) return stride(from: 0, to: lines.count, by: chunkLines).map { start in lines[start..