-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathWrappedModel.swift
More file actions
112 lines (96 loc) · 3.76 KB
/
WrappedModel.swift
File metadata and controls
112 lines (96 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//
// WrappedData.swift
// PennMobile
//
// Created by Jonathan Melitski on 11/22/24.
// Copyright © 2024 PennLabs. All rights reserved.
//
import Foundation
import Lottie
import CoreText
public struct WrappedModel: Decodable {
let semester: String
// Designed to be optional for forwards compatability
// (making pages an optional field was a design discussion for disabling wrapped between semesters)
var pages: [WrappedUnit]
let fonts: [String: URL]
var fontProvider: WrappedFontProvider?
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.semester = try values.decode(String.self, forKey: .semester)
self.pages = try values.decodeIfPresent([WrappedUnit].self, forKey: .pages) ?? []
self.fonts = try values.decodeIfPresent([String: URL].self, forKey: .fonts) ?? [:]
}
public init(semester: String, pages: [WrappedUnit], fonts: [String: URL] = [:]) {
self.pages = pages
self.semester = semester
self.fonts = fonts
}
enum CodingKeys: String, CodingKey {
case pages, semester, fonts
}
mutating func loadModel() async {
let newPages = await withTaskGroup(of: WrappedUnit.self, returning: [WrappedUnit].self) { group in
for page in self.pages {
var newPage = page
group.addTask {
await newPage.loadAnimation()
return newPage
}
}
var results: [WrappedUnit] = []
for await result in group {
results.append(result)
}
return results
}
// Fail if duplicate ID (note: this silently fails)
// There should be exactly one of every ID
guard newPages.compactMap({ el in
newPages.count(where: { $0.id == el.id })
}).filter({ $0 != 1 }).isEmpty else {
print("Wrapped duplicate IDs detected. Check the model.")
self.pages = []
return
}
self.pages = newPages.filter({ $0.lottie != nil }).sorted(by: { $0.id < $1.id })
let fontFiles: [String: Data] = await withTaskGroup(of: (String, Data?).self, returning: [String: Data].self) { group in
for (name, downloadURL) in self.fonts {
group.addTask {
let request = URLRequest(url: downloadURL)
guard let (localURL, response) = try? await URLSession.shared.download(for: request) else {
return (name, nil)
}
return (name, try? Data(contentsOf: localURL))
}
}
var results: [String: Data] = [:]
for await result in group {
guard result.1 != nil else { continue }
results[result.0] = result.1
}
return results
}
self.fontProvider = WrappedFontProvider(from: fontFiles)
}
}
class WrappedFontProvider: AnimationFontProvider, Equatable {
static func == (lhs: WrappedFontProvider, rhs: WrappedFontProvider) -> Bool {
lhs === rhs
}
let fonts: [String: CGFont]
init(from files: [String: Data]) {
var fonts: [String: CGFont] = [:]
for (name, data) in files {
if let provider = CGDataProvider(data: data as CFData) {
fonts[name] = CGFont(provider)
}
}
self.fonts = fonts
}
func fontFor(family: String, size: CGFloat) -> CTFont? {
guard let font = fonts[family] else { return nil }
let ctFont = CTFontCreateWithGraphicsFont(font, size, nil, nil)
return ctFont
}
}