Skip to content

Instantly share code, notes, and snippets.

@ollieatkinson
Last active June 24, 2024 11:37
Show Gist options
  • Save ollieatkinson/eb87a82fcb5500d5561fed8b0900a9f7 to your computer and use it in GitHub Desktop.
Save ollieatkinson/eb87a82fcb5500d5561fed8b0900a9f7 to your computer and use it in GitHub Desktop.
Utilise the private CoreSVG framework in Swift
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
Copy link

misteu commented Oct 23, 2023

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.

@ollieatkinson
Copy link
Author

ollieatkinson commented Jan 23, 2024

@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() 
    }
}

@ollieatkinson
Copy link
Author

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) }
}

@YisusLVP
Copy link

YisusLVP commented May 9, 2024

Can be used this class in UIKit? I'm trying to render a SVG downloaded from a remote server but I get this message:

Entity: line 1: parser error : Start tag expected, '<' not found
\377\330\377\340
^

This is my implementation:

let data = try Data(contentsOf: url)
if let svg = SVG(data) {
    let image = svg.image()
    let render = UIGraphicsImageRenderer(size: svg.size)
    let image2 = render.image { context in
        svg.draw(in: context.cgContext)
    }
    DispatchQueue.main.async {
        self.image = image2
    }
}

The app crashes inside of the draw() method, at this line:
CGContextDrawSVGDocument(context, document)

UPDATE: I found that the problem is with the image that I'm trying to download. Is there any problem with encoding types?
My original image is .utf8 (the one that cannot be assigned) and a one that worked for me (downloaded from internet) is isoLatin1.

@ollieatkinson
Copy link
Author

ollieatkinson commented May 22, 2024

Can be used this class in UIKit?

Yes.

This is my implementation:

I would recommend something like this:

final class SVGView: 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")
  }
  
  override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    svg.draw(in: context, size: rect.size)
  }
}

UPDATE: I found that the problem is with the image that I'm trying to download. Is there any problem with encoding types?
My original image is .utf8 (the one that cannot be assigned) and a one that worked for me (downloaded from internet) is isoLatin1.

That is strange - are you able to share the SVG which fails to decode?

@YisusLVP
Copy link

YisusLVP commented May 23, 2024

This is the image @ollieatkinson :
Restaurante-LPL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment