damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 0e8c94b66877e9e233ca9d4e7b70516cb21c229a
parent e4beb872a50a89b581b60d4f2d8bdb40df5dd0fe
Author: OlegAba <mail@olegaba.com>
Date:   Fri, 27 Jan 2023 16:03:17 -0500

Replace SVGKit package with CoreSVG

Changelog-Fixed: Fixed crash on some SVG profile pictures
Closes: #416

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 31+++++--------------------------
Mdamus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 8--------
Mdamus/Models/KFImageModel.swift | 14++++++++++----
Adamus/Util/CoreSVG.swift | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 116 insertions(+), 38 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -177,9 +177,8 @@ 647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; }; 64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; }; 6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; }; - 7C45AE6D297352F90031D7BC /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7C45AE6C297352F90031D7BC /* SVGKit */; }; - 7C45AE6F297352F90031D7BC /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7C45AE6E297352F90031D7BC /* SVGKitSwift */; }; 7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C45AE70297353390031D7BC /* KFImageModel.swift */; }; + 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; }; 7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; }; 9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; }; BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; @@ -433,6 +432,7 @@ 647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; }; 64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; }; 7C45AE70297353390031D7BC /* KFImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFImageModel.swift; sourceTree = "<group>"; }; + 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; }; 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; }; 9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; }; @@ -449,8 +449,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7C45AE6F297352F90031D7BC /* SVGKitSwift in Frameworks */, - 7C45AE6D297352F90031D7BC /* SVGKit in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 6C7DE41F2955169800E66263 /* Vault in Frameworks */, 4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */, @@ -689,6 +687,7 @@ 4CB8838529656C8B00DC99E7 /* NIP05.swift */, 4CF0ABD72981980C00D66079 /* Lists.swift */, 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */, + 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */, ); path = Util; sourceTree = "<group>"; @@ -876,8 +875,6 @@ 4C649880286E0EE300EAE2B3 /* secp256k1 */, 4C06670328FC7EC500038D2A /* Kingfisher */, 6C7DE41E2955169800E66263 /* Vault */, - 7C45AE6C297352F90031D7BC /* SVGKit */, - 7C45AE6E297352F90031D7BC /* SVGKitSwift */, ); productName = damus; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; @@ -964,7 +961,6 @@ 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */, 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */, 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */, - 7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */, ); productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; projectDirPath = ""; @@ -1124,6 +1120,7 @@ 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */, 4CF0ABD82981980C00D66079 /* Lists.swift in Sources */, 4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */, + 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */, 6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */, 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */, 4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */, @@ -1391,7 +1388,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; - DEVELOPMENT_TEAM = XK7H4JAB3D; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = damus/Info.plist; @@ -1612,14 +1609,6 @@ minimumVersion = 1.0.0; }; }; - 7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SVGKit/SVGKit"; - requirement = { - kind = revision; - revision = e1f13e27b1e4c0ffe20e7d8d3984bf49c2a584d0; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1643,16 +1632,6 @@ package = 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */; productName = Vault; }; - 7C45AE6C297352F90031D7BC /* SVGKit */ = { - isa = XCSwiftPackageProductDependency; - package = 7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKit; - }; - 7C45AE6E297352F90031D7BC /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 7C45AE6B297352F90031D7BC /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */; diff --git a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,14 +27,6 @@ } }, { - "identity" : "svgkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SVGKit/SVGKit", - "state" : { - "revision" : "e1f13e27b1e4c0ffe20e7d8d3984bf49c2a584d0" - } - }, - { "identity" : "vault", "kind" : "remoteSourceControl", "location" : "https://github.com/SparrowTek/Vault", diff --git a/damus/Models/KFImageModel.swift b/damus/Models/KFImageModel.swift @@ -5,9 +5,8 @@ // Created by Oleg Abalonski on 1/11/23. // -import Foundation +import UIKit import Kingfisher -import SVGKit class KFImageModel: ObservableObject { @@ -80,8 +79,15 @@ struct CustomImageProcessor: ImageProcessor { } // Handle SVG image - if let svgImage = SVGKImage(data: data), let image = svgImage.uiImage { - return image.kf.scaled(to: options.scaleFactor) + if let dataString = String(data: data, encoding: .utf8), + let svg = SVG(dataString) { + + let render = UIGraphicsImageRenderer(size: svg.size) + let image = render.image { context in + svg.draw(in: context.cgContext) + } + + return image.kf.scaled(to: options.scaleFactor) } return DefaultImageProcessor.default.process(item: item, options: options) diff --git a/damus/Util/CoreSVG.swift b/damus/Util/CoreSVG.swift @@ -0,0 +1,101 @@ +// +// CoreSVG.swift +// damus +// +// Created by Oleg Abalonski on 1/27/23. +// Ref: https://gist.github.com/ollieatkinson/eb87a82fcb5500d5561fed8b0900a9f7 + +import Darwin +import Foundation +import UIKit + +@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) + } +}