feat: support custom fonts (#6)

+ remove legacy LoaderConfig properties
This commit is contained in:
pylixonly 2024-05-18 18:36:12 +08:00 • committed by GitHub
parent 4b3b7f10e8
commit 60be3dd5d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 115 additions and 21 deletions

View file

@ -2,5 +2,6 @@ globalThis.__PYON_LOADER__ = {
loaderName: "BunnyTweak",
loaderVersion: "0.2.0",
hasThemeSupport: true,
storedTheme: null
storedTheme: null,
fontPatch: 2
}

View file

@ -0,0 +1,95 @@
import PyoncordTweakC
import Orion
import UIKit
import os
import Foundation
// This is not the full implementation, we only take what we need in here
// For full definition, find it from the core codebase (JS side)
struct FontDefinition: Codable {
let name: String
let main: [String: String]?
}
var fontMap: [String: String] = [:]
class FontsHook: ClassHook<UIFont> {
class func fontWithName(_ name: String, size: CGFloat) -> UIFont {
if let replacementName = fontMap[name] {
return UIFont(name: replacementName, size: size)!
}
return orig.fontWithName(name, size: size)
}
class func fontWithDescriptor(_ descriptor: UIFontDescriptor, size: CGFloat) -> UIFont {
if let replacementName = fontMap[descriptor.postscriptName] {
let replacementDescriptor = UIFontDescriptor(name: replacementName, size: size)
.addingAttributes(descriptor.fontAttributes)
return orig.fontWithDescriptor(replacementDescriptor, size: size)
}
return orig.fontWithDescriptor(descriptor, size: size)
}
}
func patchFonts(_ main: [String: String], fontDefName: String) {
for (fontName, url) in main {
os_log("Replacing font %{public}@ with URL: %{public}@", log: pyoncordLog, type: .info, fontName, url)
let fontExtension = URL(string: url)!.pathExtension
let fontCachePath = pyoncordDirectory
.appendingPathComponent("downloads", isDirectory: true)
.appendingPathComponent("fonts", isDirectory: true)
.appendingPathComponent(fontDefName, isDirectory: true)
.appendingPathComponent("\(fontName).\(fontExtension)")
do {
os_log("Attempting to register font %{public}@ from %{public}@", log: pyoncordLog, type: .info, fontName, url)
let parent = fontCachePath.deletingLastPathComponent()
if !FileManager.default.fileExists(atPath: parent.path) {
os_log("Creating parent directory: %{public}@", log: pyoncordLog, type: .debug, parent.path)
try FileManager.default.createDirectory(
at: parent, withIntermediateDirectories: true, attributes: nil)
}
// JS side should download these already, but just in case...
if !FileManager.default.fileExists(atPath: fontCachePath.path) {
os_log("Downloading font %{public}@ from %{public}@", log: pyoncordLog, type: .debug, fontName, url)
if let data = try? Data(contentsOf: URL(string: url)!) {
os_log("Writing font data to: %{public}@", log: pyoncordLog, type: .debug, fontCachePath.path)
try? data.write(to: fontCachePath)
}
}
if let data = try? Data(contentsOf: fontCachePath) {
os_log("Registering font %{public}@ with provider", log: pyoncordLog, type: .debug, fontName)
let provider = CGDataProvider(data: data as CFData)
let font = CGFont(provider!)
var error: Unmanaged<CFError>?
// This does not work with system/app fonts unfortunately. Throws a CTFontManagerError.systemRequired
if let existingFont = CGFont(font!.postScriptName!) {
var unregisterError: Unmanaged<CFError>?
if !CTFontManagerUnregisterGraphicsFont(existingFont, &unregisterError) {
os_log("Failed to deregister font %{public}@: %{public}@", log: pyoncordLog, type: .error,
font!.postScriptName! as String,
String(describing: unregisterError!.takeUnretainedValue()))
}
}
if CTFontManagerRegisterGraphicsFont(font!, &error) {
fontMap[fontName] = font!.postScriptName! as String
os_log("Successfully registered font %{public}@ to %{public}@", log: pyoncordLog, type: .info, fontName, font!.postScriptName! as String)
} else {
os_log("Failed to register font %{public}@: %{public}@", log: pyoncordLog, type: .error, fontName, String(describing: error!.takeUnretainedValue()))
}
} else {
os_log("Failed to read font data from: %{public}@", log: pyoncordLog, type: .error, fontCachePath.path)
}
} catch {
os_log("Failed to register font %{public}@: %{public}@", log: pyoncordLog, type: .error, fontName, error.localizedDescription)
}
}
}

View file

@ -12,16 +12,13 @@ struct CustomLoadUrl: Codable {
struct LoaderConfig: Codable {
let customLoadUrl: CustomLoadUrl
let loadReactDevTools: Bool
var loadPyoncord: Bool? = nil
}
let defaultLoaderConfig = LoaderConfig(
customLoadUrl: CustomLoadUrl(
enabled: false,
url: URL(string: "http://localhost:4040/bunny.js")!
),
loadReactDevTools: false
)
)
let pyoncordDirectory = getPyoncordDirectory()

View file

@ -40,7 +40,7 @@ class LoadHook: ClassHook<RCTCxxBridge> {
let pyoncordDirectory = getPyoncordDirectory()
var bundle = try? Data(contentsOf: pyoncordDirectory.appendingPathComponent("bunny.js"))
var bundle = try? Data(contentsOf: pyoncordDirectory.appendingPathComponent("bundle.js"))
let group = DispatchGroup()
@ -52,13 +52,8 @@ class LoadHook: ClassHook<RCTCxxBridge> {
loaderConfig.customLoadUrl.url.absoluteString)
bundleUrl = loaderConfig.customLoadUrl.url
} else {
if loaderConfig.loadPyoncord == true {
bundleUrl = URL(
string: "https://raw.githubusercontent.com/pyoncord/pyoncord/builds/pyoncord.js")!
} else {
bundleUrl = URL(
string: "https://raw.githubusercontent.com/pyoncord/detta-builds/main/bunny.js")!
}
bundleUrl = URL(
string: "https://raw.githubusercontent.com/pyoncord/bunny-builds/main/bunny.js")!
}
os_log("Fetching JS bundle", log: pyoncordLog, type: .info)
@ -75,7 +70,7 @@ class LoadHook: ClassHook<RCTCxxBridge> {
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
os_log("Successfully fetched JS Bundle", log: pyoncordLog, type: .debug)
bundle = data
try? bundle?.write(to: pyoncordDirectory.appendingPathComponent("bunny.js"))
try? bundle?.write(to: pyoncordDirectory.appendingPathComponent("bundle.js"))
let etag = httpResponse.allHeaderFields["Etag"] as? String
try? etag?.write(
@ -131,12 +126,18 @@ class LoadHook: ClassHook<RCTCxxBridge> {
}
struct PyoncordTweak: Tweak {
func tweakDidActivate() {
if let themeData = try? Data(
contentsOf: pyoncordDirectory.appendingPathComponent("current-theme.json")) {
let theme = try? JSONDecoder().decode(Theme.self, from: themeData)
if let semanticColors = theme?.data.semanticColors { swizzleDCDThemeColor(semanticColors) }
if let rawColors = theme?.data.rawColors { swizzleUIColor(rawColors) }
}
func tweakDidActivate() {
if let themeData = try? Data(
contentsOf: pyoncordDirectory.appendingPathComponent("current-theme.json")) {
let theme = try? JSONDecoder().decode(Theme.self, from: themeData)
if let semanticColors = theme?.data.semanticColors { swizzleDCDThemeColor(semanticColors) }
if let rawColors = theme?.data.rawColors { swizzleUIColor(rawColors) }
}
if let fontData = try? Data(
contentsOf: pyoncordDirectory.appendingPathComponent("fonts.json")) {
let fonts = try? JSONDecoder().decode(FontDefinition.self, from: fontData)
if let main = fonts?.main { patchFonts(main, fontDefName: fonts!.name) }
}
}
}