Last active
May 1, 2024 00:08
-
-
Save ollieatkinson/eb87a82fcb5500d5561fed8b0900a9f7 to your computer and use it in GitHub Desktop.
Utilise the private CoreSVG framework in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Darwin | |
import Foundation | |
import UIKit | |
// https://github.com/xybp888/iOS-SDKs/blob/master/iPhoneOS17.1.sdk/System/Library/PrivateFrameworks/CoreSVG.framework/CoreSVG.tbd | |
// https://developer.limneos.net/index.php?ios=17.1&framework=UIKitCore.framework&header=UIImage.h | |
@objc | |
class CGSVGDocument: NSObject { } | |
var CGSVGDocumentRetain: (@convention(c) (CGSVGDocument?) -> Unmanaged<CGSVGDocument>?) = load("CGSVGDocumentRetain") | |
var CGSVGDocumentRelease: (@convention(c) (CGSVGDocument?) -> Void) = load("CGSVGDocumentRelease") | |
var CGSVGDocumentCreateFromData: (@convention(c) (CFData?, CFDictionary?) -> Unmanaged<CGSVGDocument>?) = load("CGSVGDocumentCreateFromData") | |
var CGContextDrawSVGDocument: (@convention(c) (CGContext?, CGSVGDocument?) -> Void) = load("CGContextDrawSVGDocument") | |
var CGSVGDocumentGetCanvasSize: (@convention(c) (CGSVGDocument?) -> CGSize) = load("CGSVGDocumentGetCanvasSize") | |
typealias ImageWithCGSVGDocument = @convention(c) (AnyObject, Selector, CGSVGDocument) -> UIImage | |
var ImageWithCGSVGDocumentSEL: Selector = NSSelectorFromString("_imageWithCGSVGDocument:") | |
let CoreSVG = dlopen("/System/Library/PrivateFrameworks/CoreSVG.framework/CoreSVG", RTLD_NOW) | |
func load<T>(_ name: String) -> T { | |
unsafeBitCast(dlsym(CoreSVG, name), to: T.self) | |
} | |
public class SVG { | |
deinit { CGSVGDocumentRelease(document) } | |
let document: CGSVGDocument | |
public convenience init?(_ value: String) { | |
guard let data = value.data(using: .utf8) else { return nil } | |
self.init(data) | |
} | |
public init?(_ data: Data) { | |
guard let document = CGSVGDocumentCreateFromData(data as CFData, nil)?.takeUnretainedValue() else { return nil } | |
guard CGSVGDocumentGetCanvasSize(document) != .zero else { return nil } | |
self.document = document | |
} | |
public var size: CGSize { | |
CGSVGDocumentGetCanvasSize(document) | |
} | |
public func image() -> UIImage? { | |
let ImageWithCGSVGDocument = unsafeBitCast(UIImage.self.method(for: ImageWithCGSVGDocumentSEL), to: ImageWithCGSVGDocument.self) | |
let image = ImageWithCGSVGDocument(UIImage.self, ImageWithCGSVGDocumentSEL, document) | |
return image | |
} | |
public func draw(in context: CGContext) { | |
draw(in: context, size: size) | |
} | |
public func draw(in context: CGContext, size target: CGSize) { | |
var target = target | |
let ratio = ( | |
x: target.width / size.width, | |
y: target.height / size.height | |
) | |
let rect = ( | |
document: CGRect(origin: .zero, size: size), () | |
) | |
let scale: (x: CGFloat, y: CGFloat) | |
if target.width <= 0 { | |
scale = (ratio.y, ratio.y) | |
target.width = size.width * scale.x | |
} else if target.height <= 0 { | |
scale = (ratio.x, ratio.x) | |
target.width = size.width * scale.y | |
} else { | |
let min = min(ratio.x, ratio.y) | |
scale = (min, min) | |
target.width = size.width * scale.x | |
target.height = size.height * scale.y | |
} | |
let transform = ( | |
scale: CGAffineTransform(scaleX: scale.x, y: scale.y), | |
aspect: CGAffineTransform(translationX: (target.width / scale.x - rect.document.width) / 2, y: (target.height / scale.y - rect.document.height) / 2) | |
) | |
context.translateBy(x: 0, y: target.height) | |
context.scaleBy(x: 1, y: -1) | |
context.concatenate(transform.scale) | |
context.concatenate(transform.aspect) | |
CGContextDrawSVGDocument(context, document) | |
} | |
} | |
@misteu When using this in SwiftUI I used UI/NSViewRepresentable
and drew the SVG to the graphic context:
struct SVGView: UIViewRepresentable {
private final class View: UIView {
var svg: SVG
init(_ svg: SVG) {
self.svg = svg
super.init(frame: .init(origin: .zero, size: svg.size))
isOpaque = false
}
@available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
overrid func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
svg.draw(in: context, size: rect.size)
}
}
private let svg: SVG
init(_ svg: SVG) { self.svg = svg }
public func makeUIView(context: Context) -> UIView {
View(svg)
}
public func updateUIView(_ view: UIView, context: Context) {
view.setNeedsDisplay()
}
}
For those wanting to use this inside of production app, I've not been blocked with using this since the initial version in 2021, you just need to obfuscate the symbols
// private var CGSVGDocumentRetain: (@convention(c) (CGSVGDocument?) -> Unmanaged<CGSVGDocument>?) = load("CGSVGDocumentRetain")
private var CGSVGDocumentRetain: (@convention(c) (CGSVGDocument?) -> Unmanaged<CGSVGDocument>?) = load("==gbpFGdlJFduVWb1N2bEdkVTd0Q".deobfuscated)
// private var CGSVGDocumentRelease: (@convention(c) (CGSVGDocument?) -> Void) = load("CGSVGDocumentRelease")
private var CGSVGDocumentRelease: (@convention(c) (CGSVGDocument?) -> Void) = load("=U2chVGblJFduVWb1N2bEdkVTd0Q".deobfuscated)
// private var CGSVGDocumentCreateFromData: (@convention(c) (CFData?, CFDictionary?) -> Unmanaged<CGSVGDocument>?) = load("CGSVGDocumentCreateFromData")
private var CGSVGDocumentCreateFromData: (@convention(c) (CFData?, CFDictionary?) -> Unmanaged<CGSVGDocument>?) = load("hRXYE12byZUZ0FWZyNEduVWb1N2bEdkVTd0Q".deobfuscated)
// private var CGSVGDocumentWriteToData: (@convention(c) (CGSVGDocument?, CFData?, CFDictionary?) -> Void) = load("CGSVGDocumentWriteToData")
private var CGSVGDocumentWriteToData: (@convention(c) (CGSVGDocument?, CFData?, CFDictionary?) -> Void) = load("hRXYE9GVlRXaydFduVWb1N2bEdkVTd0Q".deobfuscated)
// private var CGContextDrawSVGDocument: (@convention(c) (CGContext?, CGSVGDocument?) -> Void) = load("CGContextDrawSVGDocument")
private var CGContextDrawSVGDocument: (@convention(c) (CGContext?, CGSVGDocument?) -> Void) = load("05WZtV3YvR0RWN1dhJHR0hXZ052bDd0Q".deobfuscated)
// private var CGSVGDocumentGetCanvasSize: (@convention(c) (CGSVGDocument?) -> CGSize) = load("CGSVGDocumentGetCanvasSize")
private var CGSVGDocumentGetCanvasSize: (@convention(c) (CGSVGDocument?) -> CGSize) = load("=UmepN1chZnbhNEdldEduVWb1N2bEdkVTd0Q".deobfuscated)
#if canImport(UIKit)
// private typealias ImageWithCGSVGDocument = @convention(c) (AnyObject, Selector, CGSVGDocument) -> UIImage
private typealias ImageWithCGSVGDocument = @convention(c) (AnyObject, Selector, CGSVGDocument) -> UIImage
#endif
// private var ImageWithCGSVGDocumentSEL: Selector = NSSelectorFromString("_imageWithCGSVGDocument:")
private var ImageWithCGSVGDocumentSEL: Selector = NSSelectorFromString("6Qnbl1Wdj9GRHZ1UHNEa0l2VldWYtl2X".deobfuscated)
// private let CoreSVG = dlopen("/System/Library/PrivateFrameworks/CoreSVG.framework/CoreSVG", RTLD_NOW)
private let CoreSVG = dlopen("=ckVTVmcvN0LrJ3b3VWbhJnZuckVTVmcvN0LztmcvdXZtFmcGVGdhZXayB1L5JXYyJWaM9SblR3c5N1L".deobfuscated, RTLD_NOW)
extension String {
fileprivate var deobfuscated: String { Data(base64Encoded: String(reversed()))!.string }
}
extension Data {
fileprivate var string: String { String(decoding: self, as: UTF8.self) }
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
yeah, that did not work for vector data though when I tried. Not sure why, but Apple's SVG support seems really limited and therefore a pain.
Loading SVG seems to work only when loading from the bundle.