Skip to content

Instantly share code, notes, and snippets.

@ollieatkinson
Last active October 5, 2024 16:51
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)
}
}
@ollieatkinson
Copy link
Author

ollieatkinson commented May 10, 2021

let data = """
<svg class="heart-loader" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 90 90" version="1.1">
<g class="heart-loader__group">
<path class="heart-loader__square" stroke-width="1" fill="none" d="M0,30 0,90 60,90 60,30z"/>
<path class="heart-loader__circle m--left" stroke-width="1" fill="none" d="M60,60 a30,30 0 0,1 -60,0 a30,30 0 0,1 60,0"/>
<path class="heart-loader__circle m--right" stroke-width="1" fill="none" d="M60,60 a30,30 0 0,1 -60,0 a30,30 0 0,1 60,0"/>
<path class="heart-loader__heartPath" stroke-width="2" d="M60,30 a30,30 0 0,1 0,60 L0,90 0,30 a30,30 0 0,1 60,0" />
</g>
</svg>
""".data(using: .utf8)!

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

@misteu
Copy link

misteu commented Sep 7, 2023

Hi there,
I am trying to use this but I get the following error:

Entity: line 1: parser error : Start tag expected, '<' not found
\211PNG
^
CoreSVG has logged an error. Set environment variabe "CORESVG_VERBOSE" to learn more.
Entity: line 1: parser error : Start tag expected, '<' not found
\211PNG
^

Do you have an idea what this means? I basically just passed Data I got from an URLSession inside of SVGs initializer.

An additional question is it possible to use this to (rasterize) and show SVG images in a SwiftUI Image ? Because that is what I am eventually trying to achieve

@IhorShevchuk
Copy link

Hi there, I am trying to use this but I get the following error:

Entity: line 1: parser error : Start tag expected, '<' not found
\211PNG
^
CoreSVG has logged an error. Set environment variabe "CORESVG_VERBOSE" to learn more.
Entity: line 1: parser error : Start tag expected, '<' not found
\211PNG
^

Do you have an idea what this means? I basically just passed Data I got from an URLSession inside of SVGs initializer.

An additional question is it possible to use this to (rasterize) and show SVG images in a SwiftUI Image ? Because that is what I am eventually trying to achieve

You can try to use UIImage's init(data:) method and then convert it to SwiftUI image like here. UIImage should support SVG natively:

You use image objects to represent image data of all kinds...

@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