├── SwiftVG.xcodeproj ├── xcuserdata │ └── austin.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── SwiftVG.xcscheme ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── SwiftVG.xccheckout └── project.pbxproj ├── SwiftVG ├── SwiftVG-Bridging-Header.h ├── ExampleViewController.swift ├── SwiftSVG │ ├── Protocols │ │ ├── SVGFillable.swift │ │ ├── SVGDrawable.swift │ │ └── SVGGradient.swift │ ├── Dependencies │ │ ├── SKUBezierPath+SVG.h │ │ ├── SVGBezierBuilder.swift │ │ └── SKUBezierPath+SVG.m │ ├── UIKit Categories │ │ └── UIColor+Hexadecimal.swift │ ├── Views │ │ ├── SVGVectorImage Categories │ │ │ └── SVGVectorImage+ColorReplacement.swift │ │ ├── SVGView.swift │ │ └── SVGVectorImage.swift │ └── Parser │ │ ├── SVGText.swift │ │ ├── SVGGroup.swift │ │ ├── SVGPath.swift │ │ ├── SVGRadialGradient.swift │ │ ├── SVGLinearGradient.swift │ │ └── SVGParser.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── AppDelegate.swift ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.xib └── example.svg ├── SwiftVGTests ├── Info.plist └── SwiftVGTests.swift ├── LICENSE └── README.md /SwiftVG.xcodeproj/xcuserdata/austin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftVG.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftVG/SwiftVG-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftVG-Bridging-Header.h 3 | // SwiftVG 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 austinfitzpatrick. All rights reserved. 7 | // 8 | 9 | #import "SKUBezierPath+SVG.h" -------------------------------------------------------------------------------- /SwiftVG/ExampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftVG 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 austinfitzpatrick. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ExampleViewController: UIViewController { 12 | 13 | @IBOutlet private var svgView:SVGView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | } 19 | 20 | override func didReceiveMemoryWarning() { 21 | super.didReceiveMemoryWarning() 22 | 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Protocols/SVGFillable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fillable.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Protocol shared by objects capable of acting as a fill for an SVGPath 12 | protocol SVGFillable: class { 13 | func asColor() -> UIColor? 14 | func asGradient() -> SVGGradient? 15 | } 16 | 17 | /// Extend UIColor to conform to SVGFillable 18 | extension UIColor: SVGFillable{ 19 | func asColor() -> UIColor? { return self } 20 | func asGradient() -> SVGGradient? { return nil} 21 | } 22 | -------------------------------------------------------------------------------- /SwiftVG.xcodeproj/xcuserdata/austin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftVG.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | D944A0001ABB43DF0060A72A 16 | 17 | primary 18 | 19 | 20 | D944A0151ABB43DF0060A72A 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Protocols/SVGDrawable.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // File.swift 4 | // SVGPlayground 5 | // 6 | // Created by Austin Fitzpatrick on 3/19/15. 7 | // Copyright (c) 2015 Seedling. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | // An SVGDrawable can be drawn to the screen. To conform a type must implement one method, draw() 13 | protocol SVGDrawable { 14 | var identifier:String? { get set }//The ID of the drawable for targetting 15 | func draw() //Draw the SVGDrawable to the screen 16 | var group:SVGGroup? { get set } //The parent group of this SVGDrawable 17 | var clippingPath:UIBezierPath? { get set } //The clipping path for this drawable - if any 18 | var onWillDraw:(()->())? { get set } 19 | var onDidDraw:(()->())? { get set } 20 | } 21 | -------------------------------------------------------------------------------- /SwiftVGTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.austinfitzpatrick.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Dependencies/SKUBezierPath+SVG.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+SVG.h 3 | // svg_test 4 | // 5 | // Created by Arthur Evstifeev on 5/29/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | // Modified by Michael Redig 9/28/14 9 | 10 | #define SKUBezierPath UIBezierPath 11 | #define addLineToPointSKU addLineToPoint 12 | #define addCurveToPointSKU addCurveToPoint 13 | #import 14 | 15 | 16 | 17 | @interface SKUBezierPath (SVG) 18 | 19 | - (void)addPathFromSVGString:(NSString *)svgString factoryIdentifier:(NSString*) identifier; 20 | + (SKUBezierPath *)bezierPathWithSVGString:(NSString *)svgString factoryIdentifier:(NSString*) identifier; 21 | 22 | @end 23 | 24 | #if TARGET_OS_IPHONE 25 | #else 26 | @interface NSBezierPath (AddQuads) 27 | 28 | -(void)addQuadCurveToPoint:(CGPoint)point controlPoint:(CGPoint)controlPoint; 29 | 30 | @end 31 | #endif -------------------------------------------------------------------------------- /SwiftVGTests/SwiftVGTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftVGTests.swift 3 | // SwiftVGTests 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 austinfitzpatrick. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SwiftVGTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Austin Fitzpatrick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Protocols/SVGGradient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGGradient.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// the SVGGradient protocol extends SVGFillable to specify that the conforming type should 12 | /// be able to supply an CGGradient and modify it with a given opacity when asked 13 | protocol SVGGradient: SVGFillable { 14 | 15 | var id:String { get } //an ID for gradient lookup 16 | var stops:[GradientStop] { get } //a list of GradientStops 17 | func addStop(offset:CGFloat, color:UIColor, opacity:CGFloat) //Add a gradient stop with an offset and color 18 | func removeStop(stop:GradientStop) 19 | func drawGradientWithOpacity(opacity:CGFloat) //Should draw the gradient - call this after clipping with a bezier path 20 | } 21 | 22 | /// Structure defining a gradient stop - contains an offset and a color 23 | struct GradientStop: Equatable{ 24 | var offset:CGFloat 25 | var color:UIColor 26 | var opacity:CGFloat = 1.0 27 | } 28 | 29 | func ==(lhs:GradientStop, rhs:GradientStop) -> Bool{ 30 | return lhs.offset == rhs.offset && lhs.color == rhs.color 31 | } -------------------------------------------------------------------------------- /SwiftVG/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/UIKit Categories/UIColor+Hexadecimal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Hexadecimal.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Extension for UIColor allowing it to parse a "#ABCDEF" style hex string 12 | extension UIColor { 13 | 14 | /// Initializes a UIColor with a hex string 15 | /// :param: hexString the string to parse for a hex color 16 | /// :returns: the UIColor or nil if parsing fails 17 | convenience init?(hexString:String) { 18 | if hexString == "#FFFFFF" { 19 | self.init(white: 1, alpha: 1) 20 | return 21 | } 22 | if hexString == "#000000" { 23 | self.init(white: 0, alpha: 1) 24 | return 25 | } 26 | let charset = NSCharacterSet(charactersInString: "#0123456789ABCDEF") 27 | var rgbValue:UInt32 = 0 28 | let scanner = NSScanner(string: hexString) 29 | scanner.scanLocation = 1 30 | scanner.scanHexInt(&rgbValue) 31 | self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16)/255.0, green:CGFloat((rgbValue & 0xFF00) >> 8)/255.0, blue:CGFloat(rgbValue & 0xFF)/255.0, alpha:1.0) 32 | if let range = hexString.rangeOfCharacterFromSet(charset.invertedSet, options: .allZeros, range: nil){ 33 | return nil 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SwiftVG.xcodeproj/project.xcworkspace/xcshareddata/SwiftVG.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | FDDF7593-6850-49C4-ABC7-6522089FB0E0 9 | IDESourceControlProjectName 10 | SwiftVG 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 789FB9C0CA14EE58AC6E326086BAE59C9C60A4F0 14 | github.com:austinfitzpatrick/SwiftVG.git 15 | 16 | IDESourceControlProjectPath 17 | SwiftVG.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 789FB9C0CA14EE58AC6E326086BAE59C9C60A4F0 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:austinfitzpatrick/SwiftVG.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 789FB9C0CA14EE58AC6E326086BAE59C9C60A4F0 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 789FB9C0CA14EE58AC6E326086BAE59C9C60A4F0 36 | IDESourceControlWCCName 37 | SwiftVG 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Views/SVGVectorImage Categories/SVGVectorImage+ColorReplacement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGVectorImage+ColorReplacement.swift 3 | // SwiftVG 4 | // 5 | // Created by Austin Fitzpatrick on 3/20/15. 6 | // Copyright (c) 2015 austinfitzpatrick. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | protocol SVGColorReplaceable { 13 | func replaceColor(color: UIColor, withColor replacement:UIColor, includeGradients:Bool) 14 | } 15 | 16 | extension SVGGroup: SVGColorReplaceable { 17 | func replaceColor(color: UIColor, withColor replacement: UIColor, includeGradients:Bool) { 18 | for drawable in drawables { 19 | if let group = drawable as? SVGGroup { group.replaceColor(color, withColor: replacement, includeGradients:includeGradients) } 20 | else if let path = drawable as? SVGPath { path.replaceColor(color, withColor: replacement, includeGradients:includeGradients) } 21 | } 22 | } 23 | } 24 | 25 | extension SVGPath: SVGColorReplaceable { 26 | func replaceColor(color: UIColor, withColor replacement: UIColor, includeGradients:Bool) { 27 | if let fillColor = self.fill?.asColor(){ 28 | if fillColor == color { 29 | self.fill = replacement 30 | } 31 | } else if let fillGradient = self.fill?.asGradient() { 32 | if includeGradients { 33 | for stop in fillGradient.stops { 34 | if stop.color == color { 35 | fillGradient.removeStop(stop) 36 | fillGradient.addStop(stop.offset, color: replacement, opacity: 1.0) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SwiftVG/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.austinfitzpatrick.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Parser/SVGText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGText.swift 3 | // Seedling Comic 4 | // 5 | // Created by Austin Fitzpatrick on 3/23/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SVGText: SVGDrawable { 12 | 13 | var group:SVGGroup? /// The group that this Text belongs to 14 | var clippingPath:UIBezierPath? /// The clipping path to apply to the text 15 | 16 | var text:String? /// The string to draw 17 | var transform:CGAffineTransform? /// The transform to apply to the text 18 | var fill:SVGFillable? /// The fill to apply to the text 19 | var font:UIFont? /// The font to use to render the text 20 | var viewBox:CGRect? 21 | var identifier:String? 22 | 23 | var onWillDraw:(()->())? 24 | var onDidDraw:(()->())? 25 | 26 | init(){ 27 | 28 | } 29 | 30 | /// Draws the text to the current context 31 | func draw() { 32 | onWillDraw?() 33 | let color = fill?.asColor() ?? UIColor.whiteColor() 34 | let attributes:[NSString:AnyObject] = [NSFontAttributeName: font ?? UIFont.systemFontOfSize(24), NSForegroundColorAttributeName: color] 35 | let line = CTLineCreateWithAttributedString((NSAttributedString(string:text!, attributes: attributes))) 36 | var ascent = CGFloat(0) 37 | CTLineGetTypographicBounds(line, &ascent, nil, nil) 38 | let offsetToConvertSVGOriginToAppleOrigin = -ascent 39 | 40 | let context = UIGraphicsGetCurrentContext() 41 | CGContextSaveGState(context) 42 | CGContextConcatCTM(context, CGAffineTransformMakeTranslation(-viewBox!.origin.x, -viewBox!.origin.y)) 43 | CGContextConcatCTM(context, transform!) 44 | 45 | let size = (text! as NSString).sizeWithAttributes(attributes) 46 | let p = CGPointMake(0, offsetToConvertSVGOriginToAppleOrigin) 47 | (text! as NSString).drawInRect(CGRectMake(p.x, p.y, size.width, size.height), withAttributes: attributes) 48 | CGContextRestoreGState(context) 49 | onDidDraw?() 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /SwiftVG/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftVG 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 austinfitzpatrick. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Parser/SVGGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGGroup.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// an SVGGroup contains a set of SVGDrawable objects, which could be SVGPaths or SVGGroups. 12 | class SVGGroup: SVGDrawable, Printable { 13 | 14 | var group:SVGGroup? //The parent of this group, if any 15 | var clippingPath:UIBezierPath? //the clipping path for this group, if any 16 | var identifier:String? 17 | 18 | var onWillDraw:(()->())? 19 | var onDidDraw:(()->())? 20 | 21 | /// Initialies and empty SVGGroup 22 | /// 23 | /// :returns: an SVGGroup with no drawables 24 | init(){ 25 | self.drawables = [] 26 | } 27 | 28 | /// Initializes an SVGGroup pre-populated with drawables 29 | /// 30 | /// :param: drawables the drawables to populate the group with 31 | /// :returns: an SVGGroup pre-populated with drawables 32 | init(drawables:[SVGDrawable]) { 33 | self.drawables = drawables 34 | } 35 | 36 | /// Draws the SVGGroup to the screen by iterating through its contained SVGDrawables 37 | func draw() { 38 | onWillDraw?() 39 | CGContextSaveGState(UIGraphicsGetCurrentContext()) 40 | if let clippingPath = clippingPath { 41 | clippingPath.addClip() 42 | } 43 | for drawable in drawables { 44 | drawable.draw() 45 | } 46 | CGContextRestoreGState(UIGraphicsGetCurrentContext()) 47 | onDidDraw?() 48 | } 49 | 50 | /// Adds an SVGDrawable (SVGDrawable) to the group - and sets that SVGDrawable's group property 51 | /// to point at this SVGGroup 52 | /// 53 | /// :param: drawable an SVGDrawable/SVGDrawable to add to this group 54 | func addToGroup(drawable:SVGDrawable) { 55 | var groupable = drawable 56 | drawables.append(groupable) 57 | groupable.group = self 58 | } 59 | 60 | /// Prints the contents of the group 61 | var description:String { 62 | return "{Group<\(drawables.count)> (\(clippingPath != nil)): \(drawables)}" 63 | } 64 | 65 | //MARK: Private variables and functions 66 | 67 | internal var drawables:[SVGDrawable] //The list of drawables (which are themselves groupable) in this group 68 | 69 | } 70 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Views/SVGView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGView.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An SVGView provides a way to display SVGVectorImages to the screen respecting the contentMode property. 12 | @IBDesignable class SVGView: UIView { 13 | 14 | @IBInspectable var svgName:String? // The name of the SVG - mostly for interface builder 15 | { didSet { svgNameChanged() } } 16 | var vectorImage:SVGVectorImage? // The vector image to draw to the screen 17 | { didSet { setNeedsDisplay() } } 18 | 19 | convenience init(vectorImage:SVGVectorImage?){ 20 | self.init(frame:CGRect(x: 0, y: 0, width: vectorImage?.size.width ?? 0, height: vectorImage?.size.height ?? 0)) 21 | self.vectorImage = vectorImage 22 | } 23 | 24 | 25 | //MARK: Lifecycle 26 | override func awakeFromNib() { 27 | super.awakeFromNib() 28 | if let svgName = svgName { svgNameChanged() } 29 | } 30 | 31 | /// When the SVG's name changes we'll reparse the new file 32 | func svgNameChanged() { 33 | #if !TARGET_INTERFACE_BUILDER 34 | let bundle = NSBundle.mainBundle() 35 | #else 36 | let bundle = NSBundle(forClass: self.dynamicType) 37 | #endif 38 | if let path = bundle.pathForResource(svgName, ofType: "svg") { 39 | let parser = SVGParser(path: path) 40 | vectorImage = parser.parse() 41 | } else { 42 | vectorImage = nil 43 | } 44 | 45 | } 46 | 47 | /// Draw the SVGVectorImage to the screen - respecting the contentMode property 48 | override func drawRect(rect: CGRect) { 49 | super.drawRect(rect) 50 | if let svg = self.vectorImage { 51 | let context = UIGraphicsGetCurrentContext() 52 | let translation = svg.translationWithTargetSize(rect.size, contentMode: contentMode) 53 | let scale = svg.scaleWithTargetSize(rect.size, contentMode: contentMode) 54 | CGContextScaleCTM(context, scale.width, scale.height) 55 | CGContextTranslateCTM(context, translation.x / scale.width, translation.y / scale.height) 56 | svg.draw() 57 | } 58 | } 59 | 60 | /// Interface builder drawing code 61 | override func prepareForInterfaceBuilder() { 62 | svgNameChanged() 63 | setNeedsDisplay() 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Parser/SVGPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGPath.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An SVGPath is the most common SVGDrawable element in an SVGVectorImage 12 | /// it contains a bezier path and instructions for drawing it to the canvas 13 | /// with its draw() method 14 | class SVGPath: SVGDrawable, Printable { 15 | 16 | var identifier:String? //The ID of the path for targetting 17 | var bezierPath:UIBezierPath // The bezier path to draw to the canvas 18 | var fill:SVGFillable? // The fill for the bezier path - commonly 19 | // a UIColor or SVGGradient 20 | 21 | var opacity:CGFloat // The opacity to draw the path at 22 | var group:SVGGroup? // The group that this path belongs to - if any 23 | var clippingPath:UIBezierPath? //the clipping path for this path, if any 24 | 25 | var onWillDraw:(()->())? 26 | var onDidDraw:(()->())? 27 | 28 | var bounds:CGRect //the path's bounds 29 | { return bezierPath.bounds } 30 | 31 | /// Initializes a SVGPath 32 | /// 33 | /// :param: bezierPath The UIBezierPath to use for drawing to the canvas 34 | /// :param: fill An Object conforming to Fillable to use as the fill. UIColor and SVGGradient are common choices 35 | /// :param: opacity The opacity to draw the path at 36 | /// :returns: an SVGPath ready for drawing with draw() 37 | init(bezierPath:UIBezierPath, fill:SVGFillable?, opacity:CGFloat = 1.0, clippingPath:UIBezierPath? = nil){ 38 | self.bezierPath = bezierPath 39 | self.fill = fill ?? UIColor.blackColor() 40 | self.opacity = opacity 41 | self.clippingPath = clippingPath 42 | } 43 | 44 | /// Draws the SVGPath to the canvas 45 | func draw(){ 46 | onWillDraw?() 47 | CGContextSaveGState(UIGraphicsGetCurrentContext()) 48 | clippingPath?.addClip() 49 | if let color = fill?.asColor() { 50 | if opacity != 1 { 51 | color.colorWithAlphaComponent(opacity).setFill() 52 | } else { 53 | color.setFill() 54 | } 55 | bezierPath.fill() 56 | } else if let gradient = fill?.asGradient() { 57 | let context = UIGraphicsGetCurrentContext() 58 | CGContextSaveGState(context) 59 | bezierPath.addClip() 60 | gradient.drawGradientWithOpacity(opacity) 61 | CGContextRestoreGState(context) 62 | } 63 | CGContextRestoreGState(UIGraphicsGetCurrentContext()) 64 | onDidDraw?() 65 | } 66 | 67 | var description:String{ 68 | return "Path" 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftVG 2 | ## Purpose 3 | If you need to display SVG files in your iOS application you don't have a ton of options. The native support just isn't there and [PaintCode](http://www.paintcodeapp.com/) is wonderful but has limitations: 4 | 5 | + Complex SVGs generate a lot of code - and that much code can slow XCode way down. 6 | + The code generated by PaintCode has to be included in your app at compile time - no over the air SVGs. 7 | 8 | Looking around the web I was able to find a couple great projects but nothing that did exactly what I wanted. [SVGKit](https://github.com/SVGKit/SVGKit) was heavy and didn't let me draw directly to the context. SKUBezierPath+SVG does a great job translating SVG paths to UIBezierPaths but doesn't help with parsing a large SVG file. 9 | 10 | I built SwiftVG to make displaying SVG files in your iOS app easier - whether you bundle them with the application or deliver them over the air. 11 | 12 | ## Installation 13 | 14 | I'll get to work on a cocoapod soon. In the meantime just download the files and add them to your project. You'll need an objective-c bridging header for [SKUBezierPath+SVG](https://github.com/ap4y/UIBezierPath-SVG) (included) which handles the path parsing. I'm working on a Swift port inspired by SKUBezierPath+SVG to get rid of this dependency. 15 | 16 | ## Usage 17 | 18 | Use `SVGVectorImage` where you would normally use `UIImage` and use `SVGView` where you would normally use `UIImageView`. 19 | 20 | ### Programmatically 21 | ```swift 22 | let svgView = SVGView(vectorImage:SVGVectorImage(named: "example")) 23 | view.addSubview(svgView) 24 | ``` 25 | 26 | ### In A Storyboard 27 | 28 | + Drag a UIView out into the storyboard and change it's class to SVGView in the Identity Inspector 29 | + Optionally set an SVG Name in the Attributes Inspector - this is a file name in your main bundle (excluding the .svg) 30 | + The storyboard will live render your SVG, just like an UIImageView would! 31 | 32 | ![Alt Text](http://i.imgur.com/vKx4ux4.png) 33 | 34 | ### What if my SVG isn't in the bundle? 35 | 36 | Instantiate your SVGVectorImage from NSData or a file path that could point anywhere. 37 | ```swift 38 | //load from data 39 | let data:NSData = /* data from somewhere - maybe the internet? */ 40 | let svgVectorImage = SVGVectorImage(data: data) 41 | 42 | //load from a path 43 | let path = /* some path - maybe documents or tmp directory? */ 44 | let svgVectorImage = SVGVectorImage(path: path) 45 | 46 | //however you end up with an SVGVectorImage you can add it to a SVGView to display it 47 | let svgView = SVGView() //could be on a storyboard - or created earlier 48 | svgView.vectorImage = svgVectorImage 49 | ``` 50 | 51 | ### What's supported - What isn't? 52 | 53 | SVGView supports the following contentMode values: 54 | + `.ScaleAspectFill` 55 | + `.ScaleAspectFit` 56 | + `.ScaleToFill` 57 | + `.Center` 58 | 59 | I haven't tested every SVG out there so I'm sure there are things that I'm not supporting. I currently support `groups`, `paths`, `polygons`, `rects`, `linearGradients`, and `radialGradients`. This tends to work well for me on SVGs exported from Adobe Illustrator as per my workflow - but if you have a problematic SVG file let me know about it - or even better fix it and submit a pull request :) 60 | -------------------------------------------------------------------------------- /SwiftVG/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /SwiftVG/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SwiftVG.xcodeproj/xcuserdata/austin.xcuserdatad/xcschemes/SwiftVG.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Parser/SVGRadialGradient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGGradient.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Defines a radial gradient for filling paths. Create the gradient and then add stops to prepare it for filling. 12 | class SVGRadialGradient: SVGGradient { 13 | var id:String //The id of the gradient for lookup 14 | var center:CGPoint //The center of the gradient 15 | var radius:CGFloat //The radius of the gradient 16 | var transform:CGAffineTransform //The transform to apply to the gradient 17 | var gradientUnits:String //The units of the gradient TODO 18 | var stops:[GradientStop] //Stops define the colors and percentages along the gradient 19 | 20 | /// Initializes an SVGRadialGradient to use as a fill 21 | /// 22 | /// :param: id The id of the gradient 23 | /// :param: center The center point of the gradient - before transform is applied 24 | /// :param: radius The radius of the gradient 25 | /// :param: gradientUnits the units of the gradient TODO 26 | /// :param: viewBox the viewBox - used to transform the center point 27 | /// :returns: an SVGRadialGradient with no stops. Stops will need to be added with addStop(offset, color) 28 | init(id:String, center:CGPoint, radius:CGFloat, gradientTransform:String?, gradientUnits:String, viewBox:CGRect){ 29 | self.id = id 30 | self.center = center 31 | self.radius = radius 32 | if let gradientTransformString = gradientTransform { 33 | transform = SVGParser.transformFromString(gradientTransformString) 34 | } else { 35 | transform = CGAffineTransformIdentity 36 | } 37 | self.radius = self.radius * transform.a 38 | self.center = CGPointApplyAffineTransform(self.center, transform) 39 | self.center = CGPointMake(self.center.x - viewBox.origin.x, self.center.y - viewBox.origin.y) 40 | self.gradientUnits = gradientUnits 41 | stops = [] 42 | } 43 | 44 | /// Initializes an SVGRadialGradient to use as a fill - convenience for creating it directly from the attributeDict in the XML 45 | /// 46 | /// :param: attributeDict the attributeDict directly from the NSXMLParser 47 | /// :returns: an SVGRadialGradient with no stops. Stops will need to be added with addStop(offset, color) 48 | convenience init(attributeDict:[NSObject:AnyObject], viewBox:CGRect){ 49 | let id = attributeDict["id"] as String 50 | var center = CGPoint(x: CGFloat((attributeDict["cx"] as NSString).floatValue), y: CGFloat((attributeDict["cy"] as NSString).floatValue)) 51 | let radius = CGFloat((attributeDict["r"] as NSString).floatValue) 52 | let gradientTransform = attributeDict["gradientTransform"] as? String 53 | let gradientUnits = attributeDict["gradientUnits"] as String 54 | self.init(id:id, center:center, radius:radius, gradientTransform:gradientTransform, gradientUnits:gradientUnits, viewBox:viewBox) 55 | } 56 | 57 | /// Draws the gradient to the current context 58 | /// 59 | /// :param: opacity modify the colors by adjusting opacity 60 | func drawGradientWithOpacity(opacity:CGFloat) { 61 | let context = UIGraphicsGetCurrentContext() 62 | 63 | CGContextDrawRadialGradient(context, CGGradientWithOpacity(opacity), 64 | center, 0, 65 | center, radius, 66 | UInt32(kCGGradientDrawsBeforeStartLocation) | UInt32(kCGGradientDrawsAfterEndLocation)) 67 | } 68 | 69 | /// Adds a Stop to the gradient - a Gradient is made up of several stops 70 | /// 71 | /// :param: offset the offset location of the stop 72 | /// :color: the color to blend from/towards at this stop 73 | func addStop(offset:CGFloat, color:UIColor, opacity:CGFloat){ 74 | stops.append(GradientStop(offset: offset, color: color, opacity:opacity)) 75 | } 76 | 77 | //MARK: Private Variables and Functions 78 | 79 | /// Returns a CGGradientRef for drawing to the canvas - after modifing the colors if necsssary with given opacity 80 | /// 81 | /// :param: opacity The opacity at which to draw the gradient 82 | /// :returns: A CGGradientRef ready for drawing to a canvas 83 | private func CGGradientWithOpacity(opacity:CGFloat) -> CGGradientRef { 84 | return CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), stops.map{$0.color.colorWithAlphaComponent(opacity * $0.opacity).CGColor}, stops.map{$0.offset}) 85 | } 86 | 87 | /// Removes a stop previously added to the gradient. 88 | /// 89 | /// :param: stop The stop to remove 90 | internal func removeStop(stop: GradientStop) { 91 | if let index = find(stops, stop){ 92 | stops.removeAtIndex(index) 93 | } 94 | } 95 | 96 | //MARK: SVGFillable 97 | 98 | /// Returns a fillable as a gradient optional 99 | /// :returns: self, if self is an SVGGradient, or nil 100 | func asGradient() -> SVGGradient? { 101 | return self 102 | } 103 | 104 | /// Returns nil 105 | /// :returns: self, if self is a UIColor, or nil 106 | func asColor() -> UIColor? { 107 | return nil 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Parser/SVGLinearGradient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGGradient.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Defines a linear gradient for filling paths. Create the gradient and then add stops to prepare it for filling. 12 | class SVGLinearGradient: SVGGradient { 13 | var id:String //The id of the gradient for lookup 14 | var startPoint:CGPoint //The starting point of the gradient 15 | var endPoint:CGPoint //The ending point of the gradient 16 | var transform:CGAffineTransform //The transform to apply to the gradient 17 | var gradientUnits:String //The units of the gradient TODO 18 | var stops:[GradientStop] //Stops define the colors and percentages along the gradient 19 | 20 | /// Initializes an SVGLinearGradient to use as a fill 21 | /// 22 | /// :param: id The id of the gradient 23 | /// :param: startPoint the starting point of the gradient 24 | /// :param: endPoint the ending point of the gradient 25 | /// :param: gradientUnits the units of the gradient TODO 26 | /// :param: viewBox the viewBox - used to transform the center point 27 | /// :returns: an SVGLinearGradient with no stops. Stops will need to be added with addStop(offset, color) 28 | init(id:String, startPoint:CGPoint, endPoint:CGPoint, gradientTransform:String?, gradientUnits:String, viewBox:CGRect){ 29 | self.id = id 30 | self.startPoint = startPoint 31 | self.endPoint = endPoint 32 | if let gradientTransformString = gradientTransform { 33 | transform = SVGParser.transformFromString(gradientTransformString)//CGAffineTransformMake(CGFloat(a), CGFloat(b), CGFloat(c), CGFloat(d), CGFloat(tx), CGFloat(ty)) 34 | } else { 35 | transform = CGAffineTransformIdentity 36 | } 37 | self.startPoint = CGPointApplyAffineTransform(self.startPoint, transform) 38 | self.endPoint = CGPointApplyAffineTransform(self.endPoint, transform) 39 | self.startPoint = CGPointMake(self.startPoint.x - viewBox.origin.x, self.startPoint.y - viewBox.origin.y) 40 | self.endPoint = CGPointMake(self.endPoint.x - viewBox.origin.x, self.endPoint.y - viewBox.origin.y) 41 | self.gradientUnits = gradientUnits 42 | stops = [] 43 | } 44 | 45 | /// Initializes an SVGLinearGradient to use as a fill - convenience for creating it directly from the attributeDict in the XML 46 | /// 47 | /// :param: attributeDict the attributeDict directly from the NSXMLParser 48 | /// :returns: an SVGLinearGradient with no stops. Stops will need to be added with addStop(offset, color) 49 | convenience init(attributeDict:[NSObject:AnyObject], viewBox:CGRect){ 50 | let id = attributeDict["id"] as String 51 | var startPoint = CGPoint(x: CGFloat((attributeDict["x1"] as NSString).floatValue), y: CGFloat((attributeDict["y1"] as NSString).floatValue)) 52 | var endPoint = CGPoint(x: CGFloat((attributeDict["x2"] as NSString).floatValue), y: CGFloat((attributeDict["y2"] as NSString).floatValue)) 53 | let gradientTransform = attributeDict["gradientTransform"] as? String 54 | let gradientUnits = attributeDict["gradientUnits"] as String 55 | self.init(id:id, startPoint:startPoint, endPoint:endPoint, gradientTransform:gradientTransform, gradientUnits:gradientUnits, viewBox:viewBox) 56 | } 57 | 58 | /// Adds a Stop to the gradient - a Gradient is made up of several stops 59 | /// 60 | /// :param: offset the offset location of the stop 61 | /// :color: the color to blend from/towards at this stop 62 | func addStop(offset:CGFloat, color:UIColor, opacity:CGFloat){ 63 | stops.append(GradientStop(offset: offset, color: color, opacity:opacity)) 64 | } 65 | 66 | 67 | /// Draws the gradient to the current context 68 | /// 69 | /// :param: opacity modify the colors by adjusting opacity 70 | func drawGradientWithOpacity(opacity:CGFloat) { 71 | let context = UIGraphicsGetCurrentContext() 72 | CGContextDrawLinearGradient(context, CGGradientWithOpacity(opacity), startPoint, endPoint, UInt32(kCGGradientDrawsBeforeStartLocation) | UInt32(kCGGradientDrawsAfterEndLocation)) 73 | } 74 | 75 | /// Returns a CGGradientRef for drawing to the canvas - after modifing the colors if necsssary with given opacity 76 | /// 77 | /// :param: opacity The opacity at which to draw the gradient 78 | /// :returns: A CGGradientRef ready for drawing to a canvas 79 | private func CGGradientWithOpacity(opacity:CGFloat) -> CGGradientRef { 80 | return CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), stops.map{$0.color.colorWithAlphaComponent(opacity * $0.opacity).CGColor}, stops.map{$0.offset}) 81 | } 82 | 83 | /// Removes a stop previously added to the gradient. 84 | /// 85 | /// :param: stop The stop to remove 86 | internal func removeStop(stop: GradientStop) { 87 | if let index = find(stops, stop){ 88 | stops.removeAtIndex(index) 89 | } 90 | } 91 | 92 | //MARK: SVGFillable 93 | 94 | /// Returns a fillable as a gradient optional 95 | /// :returns: self, if self is an SVGGradient, or nil 96 | func asGradient() -> SVGGradient? { 97 | return self 98 | } 99 | 100 | /// Returns nil 101 | /// :returns: self, if self is a UIColor, or nil 102 | func asColor() -> UIColor? { 103 | return nil 104 | } 105 | 106 | } -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Views/SVGVectorImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGDrawable.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An SVGVectorImage is used by a SVGView to display an SVG to the screen. 12 | class SVGVectorImage: SVGGroup { 13 | 14 | private(set) var size:CGSize //the size of the SVG's at "100%" 15 | 16 | /// initializes an SVGVectorImage with a list of drawables and a size 17 | /// 18 | /// :param: drawables The list of drawables at the root level - can be nested 19 | /// :param: size The size of the vector image at "100%" 20 | /// :returns: An SVGVectorImage ready for display in an SVGView 21 | init(drawables:[SVGDrawable], size:CGSize){ 22 | self.size = size 23 | super.init(drawables:drawables) 24 | } 25 | 26 | /// initializes an SVGVectorImage with the contents of another SVGVectorImage 27 | /// 28 | /// :param: vectorImage another vector image to take the contents of 29 | /// :returns: an SVGVectorImage ready for display in an SVGView 30 | init(vectorImage: SVGVectorImage){ 31 | self.size = vectorImage.size 32 | super.init(drawables:vectorImage.drawables) 33 | } 34 | 35 | /// Initializes an SVGVectorImage with the contents of the file at the 36 | /// given path 37 | /// 38 | /// :param: path A file path to the SVG file 39 | /// :returns: an SVGVectorImage ready for display in an SVGView 40 | convenience init(path: String) { 41 | let (drawables, size) = SVGParser(path: path).coreParse() 42 | self.init(drawables: drawables, size: size) 43 | } 44 | 45 | /// Initializes an SVGVectorImage with the data provided (should be an XML String) 46 | /// 47 | /// :param: path A file path to the SVG file 48 | /// :returns: an SVGVectorImage ready for display in an SVGView 49 | convenience init(data:NSData) { 50 | let (drawables, size) = SVGParser(data: data).coreParse() 51 | self.init(drawables: drawables, size: size) 52 | } 53 | 54 | /// Optionally initialies an SVGVectorImage with the given name in the main bundle 55 | /// 56 | /// :param: name The name of the vector image file (without the .svg extension) 57 | /// :returns: an SVGVectorImage ready for display in an SVGView or nil if no svg exists 58 | /// at the given path 59 | convenience init?(named name: String){ 60 | if let path = NSBundle.mainBundle().pathForResource(name, ofType: "svg"){ 61 | let vector = SVGParser(path: path).parse() 62 | self.init(vectorImage: vector) 63 | } else { 64 | self.init(drawables:[], size:CGSizeZero) 65 | return nil 66 | } 67 | } 68 | 69 | //MARK: Rendering to images 70 | 71 | /// Renders the vector image to a raster UIImage 72 | /// 73 | /// :param: size the size of the UIImage to be returned 74 | /// :param: contentMode the contentMode to use for rendering, some values may effect the output size 75 | /// :returns: a UIImage containing a raster representation of the SVGVectorImage 76 | func renderToImage(#size:CGSize, contentMode:UIViewContentMode = .ScaleToFill) -> UIImage{ 77 | 78 | let targetSize = sizeWithTargetSize(size, contentMode: contentMode) 79 | let scale = scaleWithTargetSize(size, contentMode: contentMode) 80 | UIGraphicsBeginImageContext(targetSize) 81 | let context = UIGraphicsGetCurrentContext() 82 | CGContextScaleCTM(context, scale.width, scale.height) 83 | self.draw() 84 | let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage() 85 | UIGraphicsEndImageContext() 86 | return image 87 | } 88 | 89 | //MARK: Private Functions and Variables 90 | 91 | /// Returns the size of the vector image when scaled to fit in the size parameter using 92 | /// the given content mode 93 | /// 94 | /// :param: size The size to render at 95 | /// :param: contentMode the contentMode to use for rendering 96 | /// :returns: the size to render at 97 | internal func sizeWithTargetSize(size:CGSize, contentMode:UIViewContentMode) -> CGSize { 98 | let targetSize = self.size 99 | let bounds = size 100 | switch contentMode { 101 | case .ScaleAspectFit: 102 | let scaleFactor = min(bounds.width / targetSize.width, bounds.height / targetSize.height) 103 | let scale = CGSizeMake(scaleFactor, scaleFactor) 104 | return CGSize(width: targetSize.width * scale.width, height: targetSize.height * scale.height) 105 | case .ScaleAspectFill: 106 | let scaleFactor = max(bounds.width / targetSize.width, bounds.height / targetSize.height) 107 | let scale = CGSizeMake(scaleFactor, scaleFactor) 108 | return CGSize(width: targetSize.width * scale.width, height: targetSize.height * scale.height) 109 | case .ScaleToFill: 110 | return size 111 | case .Center: 112 | return size 113 | default: 114 | return size 115 | } 116 | } 117 | 118 | /// Returns the size of the translation to apply when rendering the SVG at the given size with the given contentMode 119 | /// 120 | /// :param: size The size to render at 121 | /// :param: contentMode the contentMode to use for rendering 122 | /// :returns: the translation to apply when rendering 123 | internal func translationWithTargetSize(size:CGSize, contentMode:UIViewContentMode) -> CGPoint { 124 | let targetSize = self.size 125 | let bounds = size 126 | var newSize:CGSize 127 | switch contentMode { 128 | case .ScaleAspectFit: 129 | let scaleFactor = min(bounds.width / targetSize.width, bounds.height / targetSize.height) 130 | let scale = CGSizeMake(scaleFactor, scaleFactor) 131 | newSize = CGSize(width: targetSize.width * scale.width, height: targetSize.height * scale.height) 132 | let xTranslation = (bounds.width - newSize.width) / 2.0 133 | let yTranslation = (bounds.height - newSize.height) / 2.0 134 | return CGPoint(x:xTranslation, y:yTranslation) 135 | case .ScaleAspectFill: 136 | let scaleFactor = max(bounds.width / targetSize.width, bounds.height / targetSize.height) 137 | let scale = CGSizeMake(scaleFactor, scaleFactor) 138 | newSize = CGSize(width: targetSize.width * scale.width, height: targetSize.height * scale.height) 139 | let xTranslation = (bounds.width - newSize.width) / 2.0 140 | let yTranslation = (bounds.height - newSize.height) / 2.0 141 | return CGPoint(x:xTranslation, y:yTranslation) 142 | case .ScaleToFill: 143 | newSize = size 144 | let scaleFactor = CGSize(width:bounds.width / targetSize.width, height: bounds.height / targetSize.height) 145 | return CGPointZero 146 | case .Center: 147 | newSize = targetSize 148 | let xTranslation = (bounds.width - newSize.width) / 2.0 149 | let yTranslation = (bounds.height - newSize.height) / 2.0 150 | return CGPoint(x:xTranslation, y:yTranslation) 151 | default: 152 | return CGPointZero 153 | } 154 | } 155 | 156 | /// Returns the scale of the translation to apply when rendering the SVG at the given size with the given contentMode 157 | /// 158 | /// :param: size The size to render at 159 | /// :param: contentMode the contentMode to use for rendering 160 | /// :returns: the scale to apply to the context when rendering 161 | internal func scaleWithTargetSize(size:CGSize, contentMode:UIViewContentMode) -> CGSize { 162 | 163 | let targetSize = self.size 164 | let bounds = size 165 | var newSize:CGSize 166 | switch contentMode { 167 | case .ScaleAspectFit: 168 | let scaleFactor = min(bounds.width / targetSize.width, bounds.height / targetSize.height) 169 | return CGSizeMake(scaleFactor, scaleFactor) 170 | case .ScaleAspectFill: 171 | let scaleFactor = max(bounds.width / targetSize.width, bounds.height / targetSize.height) 172 | return CGSizeMake(scaleFactor, scaleFactor) 173 | case .ScaleToFill: 174 | return CGSize(width:bounds.width / targetSize.width, height: bounds.height / targetSize.height) 175 | case .Center: 176 | return CGSize(width: 1, height: 1) 177 | default: 178 | return CGSize(width: 1, height: 1) 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Dependencies/SVGBezierBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGBezierBuilder.swift 3 | // SwiftVG 4 | // 5 | // Created by Austin Fitzpatrick on 3/19/15. 6 | // Copyright (c) 2015 austinfitzpatrick. All rights reserved. 7 | //// 8 | // 9 | //import Foundation 10 | // 11 | //class SVGBezierBuilder: NSObject { 12 | // 13 | // class func bezierPathFromSVGString(d:String){ 14 | // 15 | // } 16 | // 17 | // enum CommandType { 18 | // case Absolute 19 | // case Relatve 20 | // } 21 | // 22 | //} 23 | // 24 | //protocol SVGCommand { 25 | // func processCommandString(commandString:String, withPrevoiusCommand prevCommand:NSString, forPath path:UIBezierPath) 26 | //} 27 | // 28 | //class SVGCommandImplementation: SVGCommand { 29 | // 30 | // struct Static { 31 | // static let ParamRegex:NSRegularExpression = NSRegularExpression(pattern: "[-+]?[0-9]*\\.?[0-9]+", options: .allZeros, error: nil)! 32 | // } 33 | // 34 | // var previousCommand:String? 35 | // 36 | // func getCommandParameters(commandString:String) -> CGFloat { 37 | // let regex = Static.ParamRegex 38 | // let matches = regex.matchesInString(commandString, options: .allZeros, range: NSMakeRange(0, commandString.utf16Count)) 39 | // 40 | // } 41 | // 42 | // func processCommandString(commandString: String, withPrevoiusCommand prevCommand: NSString, forPath path: UIBezierPath) { 43 | // 44 | // } 45 | // 46 | // 47 | //} 48 | // 49 | // - (CGFloat *)getCommandParameters:(NSString *)commandString { 50 | // NSRegularExpression *regex = [SVGCommandImpl paramRegex]; 51 | // NSArray *matches = [regex matchesInString:commandString 52 | // options:0 53 | // range:NSMakeRange(0, [commandString length])]; 54 | // CGFloat *result = (CGFloat *)malloc(matches.count * sizeof(CGFloat)); 55 | // 56 | // for (int i = 0; i < matches.count; i++) { 57 | // NSTextCheckingResult *match = [matches objectAtIndex:i]; 58 | // NSString *paramString = [commandString substringWithRange:match.range]; 59 | // CGFloat param = (CGFloat)[paramString floatValue]; 60 | // result[i] = param; 61 | // } 62 | // 63 | // return result; 64 | // } 65 | // 66 | // - (BOOL)isAbsoluteCommand:(NSString *)commandLetter { 67 | // return [commandLetter isEqualToString:[commandLetter uppercaseString]]; 68 | // } 69 | // 70 | // - (void)processCommandString:(NSString *)commandString 71 | //withPrevCommand:(NSString *)prevCommand 72 | //forPath:(SKUBezierPath *)path { 73 | // self.prevCommand = prevCommand; 74 | // NSString *commandLetter = [commandString substringToIndex:1]; 75 | // CGFloat *params = [self getCommandParameters:commandString]; 76 | // [self performWithParams:params 77 | // commandType:[self isAbsoluteCommand:commandLetter] ? Absolute : Relative 78 | // forPath:path]; 79 | // free(params); 80 | // } 81 | // 82 | // - (void)performWithParams:(CGFloat *)params 83 | //commandType:(CommandType)type 84 | //forPath:(SKUBezierPath *)path { 85 | // @throw [NSException exceptionWithName:NSInternalInconsistencyException 86 | // reason:[NSString stringWithFormat:@"You must override %@ in a subclass", 87 | // NSStringFromSelector(_cmd)] 88 | // userInfo:nil]; 89 | //} 90 | // 91 | //@end 92 | // 93 | //#pragma mark ----------SVGMoveCommand---------- 94 | //@interface SVGMoveCommand : SVGCommandImpl @end 95 | // 96 | //@implementation SVGMoveCommand 97 | // 98 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 99 | // if (type == Absolute) { 100 | // [path moveToPoint:CGPointMake(params[0], params[1])]; 101 | // } else { 102 | // [path moveToPoint:CGPointMake(path.currentPoint.x + params[0], 103 | // path.currentPoint.y + params[1])]; 104 | // 105 | // 106 | // 107 | // } 108 | //} 109 | // 110 | //@end 111 | // 112 | //#pragma mark ----------SVGLineToCommand---------- 113 | //@interface SVGLineToCommand : SVGCommandImpl @end 114 | // 115 | //@implementation SVGLineToCommand 116 | // 117 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 118 | // if (type == Absolute) { 119 | // [path addLineToPointSKU:CGPointMake(params[0], params[1])]; 120 | // 121 | // } else { 122 | // [path addLineToPointSKU:CGPointMake(path.currentPoint.x + params[0], 123 | // path.currentPoint.y + params[1])]; 124 | // 125 | // } 126 | //} 127 | // 128 | //@end 129 | // 130 | //#pragma mark ----------SVGHorizontalLineToCommand---------- 131 | //@interface SVGHorizontalLineToCommand : SVGCommandImpl @end 132 | // 133 | //@implementation SVGHorizontalLineToCommand 134 | // 135 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 136 | // if (type == Absolute) { 137 | // [path addLineToPointSKU:CGPointMake(params[0], path.currentPoint.y)]; 138 | // 139 | // } else { 140 | // [path addLineToPointSKU:CGPointMake(path.currentPoint.x + params[0], 141 | // path.currentPoint.y)]; 142 | // 143 | // } 144 | //} 145 | // 146 | //@end 147 | // 148 | //#pragma mark ----------SVGVerticalLineToCommand---------- 149 | //@interface SVGVerticalLineToCommand : SVGCommandImpl @end 150 | // 151 | //@implementation SVGVerticalLineToCommand 152 | // 153 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 154 | // if (type == Absolute) { 155 | // [path addLineToPointSKU:CGPointMake(path.currentPoint.x, params[0])]; 156 | // 157 | // } else { 158 | // [path addLineToPointSKU:CGPointMake(path.currentPoint.x, 159 | // path.currentPoint.y + params[0])]; 160 | // 161 | // } 162 | //} 163 | // 164 | //@end 165 | // 166 | //#pragma mark ----------SVGCurveToCommand---------- 167 | //@interface SVGCurveToCommand : SVGCommandImpl @end 168 | // 169 | //@implementation SVGCurveToCommand 170 | // 171 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 172 | // if (type == Absolute) { 173 | // [path addCurveToPointSKU:CGPointMake(params[4], params[5]) 174 | // controlPoint1:CGPointMake(params[0], params[1]) 175 | // controlPoint2:CGPointMake(params[2], params[3])]; 176 | // 177 | // 178 | // } else { 179 | // [path addCurveToPointSKU:CGPointMake(path.currentPoint.x + params[4], path.currentPoint.y + params[5]) 180 | // controlPoint1:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1]) 181 | // controlPoint2:CGPointMake(path.currentPoint.x + params[2], path.currentPoint.y + params[3])]; 182 | // 183 | // } 184 | //} 185 | // 186 | //@end 187 | // 188 | //#pragma mark ----------SVGSmoothCurveToCommand---------- 189 | //@interface SVGSmoothCurveToCommand : SVGCommandImpl @end 190 | // 191 | //@implementation SVGSmoothCurveToCommand 192 | // 193 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 194 | // 195 | // CGPoint firstControlPoint = CGPointMake(path.currentPoint.x, path.currentPoint.y); 196 | // 197 | // if (self.prevCommand && self.prevCommand.length > 0) { 198 | // NSString *prevCommandType = [self.prevCommand substringToIndex:1]; 199 | // NSString *prevCommandTypeLowercase = [prevCommandType lowercaseString]; 200 | // BOOL isAbsolute = ![prevCommandType isEqualToString:prevCommandTypeLowercase]; 201 | // 202 | // if ([prevCommandTypeLowercase isEqualToString:@"c"] || 203 | // [prevCommandTypeLowercase isEqualToString:@"s"]) { 204 | // 205 | // CGFloat *prevParams = [self getCommandParameters:self.prevCommand]; 206 | // if ([prevCommandTypeLowercase isEqualToString:@"c"]) { 207 | // 208 | // if (isAbsolute) { 209 | // firstControlPoint = CGPointMake(-1*prevParams[2] + 2*path.currentPoint.x, 210 | // -1*prevParams[3] + 2*path.currentPoint.y); 211 | // } else { 212 | // CGPoint oldCurrentPoint = CGPointMake(path.currentPoint.x - prevParams[4], 213 | // path.currentPoint.y - prevParams[5]); 214 | // firstControlPoint = CGPointMake(-1*(prevParams[2] + oldCurrentPoint.x) + 2*path.currentPoint.x, 215 | // -1*(prevParams[3] + oldCurrentPoint.y) + 2*path.currentPoint.y); 216 | // } 217 | // } else { 218 | // if (isAbsolute) { 219 | // firstControlPoint = CGPointMake(-1*prevParams[0] + 2*path.currentPoint.x, 220 | // -1*prevParams[1] + 2*path.currentPoint.y); 221 | // } else { 222 | // CGPoint oldCurrentPoint = CGPointMake(path.currentPoint.x - prevParams[2], 223 | // path.currentPoint.y - prevParams[3]); 224 | // firstControlPoint = CGPointMake(-1*(prevParams[0] + oldCurrentPoint.x) + 2*path.currentPoint.x, 225 | // -1*(prevParams[1] + oldCurrentPoint.y) + 2*path.currentPoint.y); 226 | // } 227 | // } 228 | // free(prevParams); 229 | // } 230 | // } 231 | // 232 | // if (type == Absolute) { 233 | // [path addCurveToPointSKU:CGPointMake(params[2], params[3]) 234 | // controlPoint1:CGPointMake(firstControlPoint.x, firstControlPoint.y) 235 | // controlPoint2:CGPointMake(params[0], params[1])]; 236 | // 237 | // } else { 238 | // [path addCurveToPointSKU:CGPointMake(path.currentPoint.x + params[2], path.currentPoint.y + params[3]) 239 | // controlPoint1:CGPointMake(firstControlPoint.x, firstControlPoint.y) 240 | // controlPoint2:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1])]; 241 | // 242 | // } 243 | // 244 | // 245 | //} 246 | // 247 | //@end 248 | // 249 | //#pragma mark ----------SVGQuadraticCurveToCommand---------- 250 | //@interface SVGQuadraticCurveToCommand : SVGCommandImpl @end 251 | // 252 | //@implementation SVGQuadraticCurveToCommand 253 | // 254 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 255 | // 256 | // if (type == Absolute) { 257 | // [path addQuadCurveToPoint:CGPointMake(params[2], params[3]) 258 | // controlPoint:CGPointMake(params[0], params[1])]; 259 | // 260 | // } else { 261 | // [path addQuadCurveToPoint:CGPointMake(path.currentPoint.x + params[2], path.currentPoint.y + params[3]) 262 | // controlPoint:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1])]; 263 | // } 264 | // 265 | //} 266 | // 267 | //@end 268 | // 269 | //#pragma mark ----------SVGSmoothQuadraticCurveToCommand---------- 270 | //@interface SVGSmoothQuadraticCurveToCommand : SVGCommandImpl @end 271 | // 272 | //@implementation SVGSmoothQuadraticCurveToCommand 273 | // 274 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 275 | // CGPoint firstControlPoint = CGPointMake(path.currentPoint.x, path.currentPoint.y); 276 | // 277 | // if (self.prevCommand && self.prevCommand.length > 0) { 278 | // NSString *prevCommandType = [self.prevCommand substringToIndex:1]; 279 | // NSString *prevCommandTypeLowercase = [prevCommandType lowercaseString]; 280 | // BOOL isAbsolute = ![prevCommandType isEqualToString:prevCommandTypeLowercase]; 281 | // 282 | // if ([prevCommandTypeLowercase isEqualToString:@"q"]) { 283 | // 284 | // CGFloat *prevParams = [self getCommandParameters:self.prevCommand]; 285 | // 286 | // if (isAbsolute) { 287 | // firstControlPoint = CGPointMake(-1*prevParams[0] + 2*path.currentPoint.x, 288 | // -1*prevParams[1] + 2*path.currentPoint.y); 289 | // } else { 290 | // CGPoint oldCurrentPoint = CGPointMake(path.currentPoint.x - prevParams[2], 291 | // path.currentPoint.y - prevParams[3]); 292 | // firstControlPoint = CGPointMake(-1*(prevParams[0] + oldCurrentPoint.x) + 2*path.currentPoint.x, 293 | // -1*(prevParams[1] + oldCurrentPoint.y) + 2*path.currentPoint.y); 294 | // } 295 | // free(prevParams); 296 | // } 297 | // } 298 | // if (type == Absolute) { 299 | // [path addQuadCurveToPoint:CGPointMake(params[0], params[1]) 300 | // controlPoint:CGPointMake(firstControlPoint.x, firstControlPoint.y)]; 301 | // } else { 302 | // [path addQuadCurveToPoint:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1]) 303 | // controlPoint:CGPointMake(firstControlPoint.x, firstControlPoint.y)]; 304 | // } 305 | // 306 | //} 307 | // 308 | //@end 309 | // 310 | //#pragma mark ----------SVGClosePathCommand---------- 311 | //@interface SVGClosePathCommand : SVGCommandImpl @end 312 | // 313 | //@implementation SVGClosePathCommand 314 | // 315 | //- (void)performWithParams:(CGFloat *)params commandType:(CommandType)type forPath:(SKUBezierPath *)path { 316 | // [path closePath]; 317 | //} 318 | // 319 | //@end 320 | // 321 | //#pragma mark ----------SVGCommandFactory---------- 322 | // 323 | //@interface SVGCommandFactory : NSObject { 324 | // NSDictionary *commands; 325 | // } 326 | // + (SVGCommandFactory *)defaultFactory; 327 | //- (id)commandForCommandLetter:(NSString *)commandLetter; 328 | //@end 329 | // 330 | //@implementation SVGCommandFactory 331 | // 332 | //+ (SVGCommandFactory *)defaultFactory { 333 | // static SVGCommandFactory *instance = nil; 334 | // static dispatch_once_t onceToken; 335 | // dispatch_once(&onceToken, ^{ 336 | // instance = [[SVGCommandFactory alloc] init]; 337 | // }); 338 | // 339 | // return instance; 340 | // } 341 | // 342 | // - (id)init { 343 | // self = [super init]; 344 | // if (self) { 345 | // SVGMoveCommand *move = [[SVGMoveCommand alloc] init]; 346 | // SVGLineToCommand *lineTo = [[SVGLineToCommand alloc] init]; 347 | // SVGHorizontalLineToCommand *horizontalLineTo = [[SVGHorizontalLineToCommand alloc] init]; 348 | // SVGVerticalLineToCommand *verticalLineTo = [[SVGVerticalLineToCommand alloc] init]; 349 | // SVGCurveToCommand *curveTo = [[SVGCurveToCommand alloc] init]; 350 | // SVGSmoothCurveToCommand *smoothCurveTo = [[SVGSmoothCurveToCommand alloc] init]; 351 | // SVGQuadraticCurveToCommand *quadraticCurveTo = [[SVGQuadraticCurveToCommand alloc] init]; 352 | // SVGSmoothQuadraticCurveToCommand *smoothQuadraticCurveTo = [[SVGSmoothQuadraticCurveToCommand alloc] init]; 353 | // SVGClosePathCommand *closePath = [[SVGClosePathCommand alloc] init]; 354 | // 355 | // commands = [[NSDictionary alloc] initWithObjectsAndKeys: 356 | // move, @"m", 357 | // lineTo, @"l", 358 | // horizontalLineTo, @"h", 359 | // verticalLineTo, @"v", 360 | // curveTo, @"c", 361 | // smoothCurveTo, @"s", 362 | // quadraticCurveTo, @"q", 363 | // smoothQuadraticCurveTo, @"t", 364 | // closePath, @"z", 365 | // nil]; 366 | // 367 | // } 368 | // return self; 369 | //} 370 | // 371 | //- (id)commandForCommandLetter:(NSString *)commandLetter { 372 | // return [commands objectForKey:[commandLetter lowercaseString]]; 373 | //} 374 | // 375 | //@end 376 | // 377 | //#pragma mark ----------SKUBezierPath (SVG)---------- 378 | // 379 | //@implementation SKUBezierPath (SVG) 380 | // 381 | //+ (void)processCommandString:(NSString *)commandString 382 | //withPrevCommandString:(NSString *)prevCommand 383 | //forPath:(SKUBezierPath*)path { 384 | // 385 | // if (!commandString || commandString.length <= 0) { 386 | // @throw [NSException exceptionWithName:NSInvalidArgumentException 387 | // reason:[NSString stringWithFormat:@"Invalid command %@", commandString] 388 | // userInfo:nil]; 389 | // } 390 | // 391 | // NSString *commandLetter = [commandString substringToIndex:1]; 392 | // id command = [[SVGCommandFactory defaultFactory] commandForCommandLetter:commandLetter]; 393 | // 394 | // if (command) { 395 | // [command processCommandString:commandString withPrevCommand:prevCommand forPath:path]; 396 | // } else { 397 | // @throw [NSException exceptionWithName:NSInvalidArgumentException 398 | // reason:[NSString stringWithFormat:@"Unknown command %@", commandLetter] 399 | // userInfo:nil]; 400 | // } 401 | // } 402 | // 403 | // + (NSRegularExpression *)commandRegex { 404 | // static NSRegularExpression *_commandRegex; 405 | // static dispatch_once_t onceToken; 406 | // dispatch_once(&onceToken, ^{ 407 | // _commandRegex = [[NSRegularExpression alloc] initWithPattern:@"[A-Za-z]" 408 | // options:0 409 | // error:nil]; 410 | // }); 411 | // return _commandRegex; 412 | // } 413 | // 414 | // + (SKUBezierPath *)addPathWithSVGString:(NSString *)svgString toPath:(SKUBezierPath *)aPath { 415 | // if (aPath && svgString && svgString.length > 0) { 416 | // NSRegularExpression *regex = [self commandRegex]; 417 | // __block NSTextCheckingResult *prevMatch = nil; 418 | // __block NSString *prevCommand = @""; 419 | // [regex enumerateMatchesInString:svgString 420 | // options:0 421 | // range:NSMakeRange(0, [svgString length]) 422 | // usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { 423 | // @autoreleasepool { 424 | // if (prevMatch) { 425 | // NSUInteger length = match.range.location - prevMatch.range.location; 426 | // NSString *commandString = [svgString substringWithRange:NSMakeRange(prevMatch.range.location, 427 | // length)]; 428 | // 429 | // 430 | // [self processCommandString:commandString withPrevCommandString:prevCommand forPath:aPath]; 431 | // prevCommand = nil; 432 | // prevMatch = nil; 433 | // prevCommand = commandString; 434 | // } 435 | // prevMatch = match; 436 | // } 437 | // 438 | // }]; 439 | // 440 | // 441 | // NSString *result = [svgString substringWithRange:NSMakeRange(prevMatch.range.location, svgString.length - prevMatch.range.location)]; 442 | // 443 | // [self processCommandString:result withPrevCommandString:prevCommand forPath:aPath]; 444 | // } 445 | // return aPath; 446 | // } 447 | // 448 | // - (void)addPathFromSVGString:(NSString *)svgString { 449 | // [SKUBezierPath addPathWithSVGString:svgString toPath:self]; 450 | // } 451 | // 452 | // + (SKUBezierPath *)bezierPathWithSVGString:(NSString *)svgString { 453 | // return [self addPathWithSVGString:svgString toPath:[SKUBezierPath bezierPath]]; 454 | // } 455 | // 456 | //@end 457 | // 458 | //#if TARGET_OS_IPHONE 459 | //#else 460 | //@implementation NSBezierPath (AddQuads) 461 | // 462 | //-(void)addQuadCurveToPoint:(CGPoint)point controlPoint:(CGPoint)controlPoint { 463 | // 464 | //CGPoint qp0, qp1, qp2, cp0, cp1, cp2, cp3; 465 | //CGFloat twoThree = 0.6666666666666666; 466 | // 467 | //qp0 = [self currentPoint]; 468 | //qp1 = controlPoint; 469 | //qp2 = point; 470 | // 471 | // 472 | //cp0 = qp0; 473 | //cp1 = CGPointMake((qp0.x + twoThree * (qp1.x - qp0.x)), (qp0.y + twoThree * (qp1.y - qp0.y))); 474 | //cp2 = CGPointMake((qp2.x + twoThree * (qp1.x - qp2.x)), (qp2.y + twoThree * (qp1.y - qp2.y))); 475 | // 476 | //cp3 = qp2; 477 | // 478 | //[self curveToPoint:cp3 controlPoint1:cp1 controlPoint2:cp2]; 479 | // 480 | // 481 | //} 482 | // 483 | // 484 | //@end 485 | // 486 | //#endif -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Dependencies/SKUBezierPath+SVG.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+SVG.m 3 | // svg_test 4 | // 5 | // Created by Arthur Evstifeev on 5/29/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | // Modified by Michael Redig 9/28/14 9 | 10 | #import "SKUBezierPath+SVG.h" 11 | 12 | #pragma mark ----------Common---------- 13 | typedef enum : NSInteger { 14 | Absolute, 15 | Relative 16 | } CommandType; 17 | 18 | @protocol SVGCommand 19 | - (void)processCommandString:(NSString *)commandString 20 | withPrevCommand:(NSString *)prevCommand 21 | forPath:(SKUBezierPath *)path 22 | factoryIdentifier:(NSString*) identifier; 23 | @end 24 | 25 | #pragma mark ----------SVGCommandImpl---------- 26 | @interface SVGCommandImpl : NSObject 27 | @property (strong, nonatomic) NSString *prevCommand; 28 | 29 | - (void)performWithParams:(CGFloat *)params 30 | commandType:(CommandType)type 31 | forPath:(SKUBezierPath *)path 32 | factoryIdentifier:(NSString*) identifier; 33 | @end 34 | 35 | @implementation SVGCommandImpl 36 | 37 | + (NSRegularExpression *)paramRegex { 38 | static NSRegularExpression *_paramRegex; 39 | static dispatch_once_t onceToken; 40 | dispatch_once(&onceToken, ^{ 41 | _paramRegex = [[NSRegularExpression alloc] initWithPattern:@"[-+]?[0-9]*\\.?[0-9]+" 42 | options:0 43 | error:nil]; 44 | }); 45 | return _paramRegex; 46 | } 47 | 48 | - (CGFloat *)getCommandParameters:(NSString *)commandString { 49 | NSRegularExpression *regex = [SVGCommandImpl paramRegex]; 50 | NSArray *matches = [regex matchesInString:commandString 51 | options:0 52 | range:NSMakeRange(0, [commandString length])]; 53 | CGFloat *result = (CGFloat *)malloc(matches.count * sizeof(CGFloat)); 54 | 55 | for (int i = 0; i < matches.count; i++) { 56 | NSTextCheckingResult *match = [matches objectAtIndex:i]; 57 | NSString *paramString = [commandString substringWithRange:match.range]; 58 | CGFloat param = (CGFloat)[paramString floatValue]; 59 | result[i] = param; 60 | } 61 | 62 | return result; 63 | } 64 | 65 | - (BOOL)isAbsoluteCommand:(NSString *)commandLetter { 66 | return [commandLetter isEqualToString:[commandLetter uppercaseString]]; 67 | } 68 | 69 | - (void)processCommandString:(NSString *)commandString 70 | withPrevCommand:(NSString *)prevCommand 71 | forPath:(SKUBezierPath *)path 72 | factoryIdentifier:(NSString *)identifier{ 73 | self.prevCommand = prevCommand; 74 | NSString *commandLetter = [commandString substringToIndex:1]; 75 | CGFloat *params = [self getCommandParameters:commandString]; 76 | [self performWithParams:params 77 | commandType:[self isAbsoluteCommand:commandLetter] ? Absolute : Relative 78 | forPath:path factoryIdentifier:identifier]; 79 | free(params); 80 | } 81 | 82 | - (void)performWithParams:(CGFloat *)params 83 | commandType:(CommandType)type 84 | forPath:(SKUBezierPath *)path 85 | factoryIdentifier:(NSString *)identifier{ 86 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 87 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", 88 | NSStringFromSelector(_cmd)] 89 | userInfo:nil]; 90 | } 91 | 92 | @end 93 | 94 | #pragma mark ----------SVGMoveCommand---------- 95 | @interface SVGMoveCommand : SVGCommandImpl @end 96 | 97 | @implementation SVGMoveCommand 98 | 99 | - (void)performWithParams:(CGFloat *)params 100 | commandType:(CommandType)type 101 | forPath:(SKUBezierPath *)path 102 | factoryIdentifier:(NSString *)identifier { 103 | if (type == Absolute) { 104 | [path moveToPoint:CGPointMake(params[0], params[1])]; 105 | } else { 106 | [path moveToPoint:CGPointMake(path.currentPoint.x + params[0], 107 | path.currentPoint.y + params[1])]; 108 | 109 | 110 | 111 | } 112 | } 113 | 114 | @end 115 | 116 | #pragma mark ----------SVGLineToCommand---------- 117 | @interface SVGLineToCommand : SVGCommandImpl @end 118 | 119 | @implementation SVGLineToCommand 120 | 121 | - (void)performWithParams:(CGFloat *)params 122 | commandType:(CommandType)type 123 | forPath:(SKUBezierPath *)path 124 | factoryIdentifier:(NSString *)identifier { 125 | if (type == Absolute) { 126 | [path addLineToPointSKU:CGPointMake(params[0], params[1])]; 127 | 128 | } else { 129 | [path addLineToPointSKU:CGPointMake(path.currentPoint.x + params[0], 130 | path.currentPoint.y + params[1])]; 131 | 132 | } 133 | } 134 | 135 | @end 136 | 137 | #pragma mark ----------SVGHorizontalLineToCommand---------- 138 | @interface SVGHorizontalLineToCommand : SVGCommandImpl @end 139 | 140 | @implementation SVGHorizontalLineToCommand 141 | 142 | - (void)performWithParams:(CGFloat *)params 143 | commandType:(CommandType)type 144 | forPath:(SKUBezierPath *)path 145 | factoryIdentifier:(NSString *)identifier { 146 | if (type == Absolute) { 147 | [path addLineToPointSKU:CGPointMake(params[0], path.currentPoint.y)]; 148 | 149 | } else { 150 | [path addLineToPointSKU:CGPointMake(path.currentPoint.x + params[0], 151 | path.currentPoint.y)]; 152 | 153 | } 154 | } 155 | 156 | @end 157 | 158 | #pragma mark ----------SVGVerticalLineToCommand---------- 159 | @interface SVGVerticalLineToCommand : SVGCommandImpl @end 160 | 161 | @implementation SVGVerticalLineToCommand 162 | 163 | - (void)performWithParams:(CGFloat *)params 164 | commandType:(CommandType)type 165 | forPath:(SKUBezierPath *)path 166 | factoryIdentifier:(NSString *)identifier{ 167 | if (type == Absolute) { 168 | [path addLineToPointSKU:CGPointMake(path.currentPoint.x, params[0])]; 169 | 170 | } else { 171 | [path addLineToPointSKU:CGPointMake(path.currentPoint.x, 172 | path.currentPoint.y + params[0])]; 173 | 174 | } 175 | } 176 | 177 | @end 178 | 179 | #pragma mark ----------SVGCurveToCommand---------- 180 | @interface SVGCurveToCommand : SVGCommandImpl @end 181 | 182 | @implementation SVGCurveToCommand 183 | 184 | - (void)performWithParams:(CGFloat *)params 185 | commandType:(CommandType)type 186 | forPath:(SKUBezierPath *)path 187 | factoryIdentifier:(NSString *)identifier { 188 | if (type == Absolute) { 189 | [path addCurveToPointSKU:CGPointMake(params[4], params[5]) 190 | controlPoint1:CGPointMake(params[0], params[1]) 191 | controlPoint2:CGPointMake(params[2], params[3])]; 192 | 193 | 194 | } else { 195 | [path addCurveToPointSKU:CGPointMake(path.currentPoint.x + params[4], path.currentPoint.y + params[5]) 196 | controlPoint1:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1]) 197 | controlPoint2:CGPointMake(path.currentPoint.x + params[2], path.currentPoint.y + params[3])]; 198 | 199 | } 200 | } 201 | 202 | @end 203 | 204 | #pragma mark ----------SVGSmoothCurveToCommand---------- 205 | @interface SVGSmoothCurveToCommand : SVGCommandImpl @end 206 | 207 | @implementation SVGSmoothCurveToCommand 208 | 209 | - (void)performWithParams:(CGFloat *)params 210 | commandType:(CommandType)type 211 | forPath:(SKUBezierPath *)path 212 | factoryIdentifier:(NSString *)identifier { 213 | 214 | CGPoint firstControlPoint = CGPointMake(path.currentPoint.x, path.currentPoint.y); 215 | 216 | if (self.prevCommand && self.prevCommand.length > 0) { 217 | NSString *prevCommandType = [self.prevCommand substringToIndex:1]; 218 | NSString *prevCommandTypeLowercase = [prevCommandType lowercaseString]; 219 | BOOL isAbsolute = ![prevCommandType isEqualToString:prevCommandTypeLowercase]; 220 | 221 | if ([prevCommandTypeLowercase isEqualToString:@"c"] || 222 | [prevCommandTypeLowercase isEqualToString:@"s"]) { 223 | 224 | CGFloat *prevParams = [self getCommandParameters:self.prevCommand]; 225 | if ([prevCommandTypeLowercase isEqualToString:@"c"]) { 226 | 227 | if (isAbsolute) { 228 | firstControlPoint = CGPointMake(-1*prevParams[2] + 2*path.currentPoint.x, 229 | -1*prevParams[3] + 2*path.currentPoint.y); 230 | } else { 231 | CGPoint oldCurrentPoint = CGPointMake(path.currentPoint.x - prevParams[4], 232 | path.currentPoint.y - prevParams[5]); 233 | firstControlPoint = CGPointMake(-1*(prevParams[2] + oldCurrentPoint.x) + 2*path.currentPoint.x, 234 | -1*(prevParams[3] + oldCurrentPoint.y) + 2*path.currentPoint.y); 235 | } 236 | } else { 237 | if (isAbsolute) { 238 | firstControlPoint = CGPointMake(-1*prevParams[0] + 2*path.currentPoint.x, 239 | -1*prevParams[1] + 2*path.currentPoint.y); 240 | } else { 241 | CGPoint oldCurrentPoint = CGPointMake(path.currentPoint.x - prevParams[2], 242 | path.currentPoint.y - prevParams[3]); 243 | firstControlPoint = CGPointMake(-1*(prevParams[0] + oldCurrentPoint.x) + 2*path.currentPoint.x, 244 | -1*(prevParams[1] + oldCurrentPoint.y) + 2*path.currentPoint.y); 245 | } 246 | } 247 | free(prevParams); 248 | } 249 | } 250 | 251 | if (type == Absolute) { 252 | [path addCurveToPointSKU:CGPointMake(params[2], params[3]) 253 | controlPoint1:CGPointMake(firstControlPoint.x, firstControlPoint.y) 254 | controlPoint2:CGPointMake(params[0], params[1])]; 255 | 256 | } else { 257 | [path addCurveToPointSKU:CGPointMake(path.currentPoint.x + params[2], path.currentPoint.y + params[3]) 258 | controlPoint1:CGPointMake(firstControlPoint.x, firstControlPoint.y) 259 | controlPoint2:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1])]; 260 | 261 | } 262 | 263 | 264 | } 265 | 266 | @end 267 | 268 | #pragma mark ----------SVGQuadraticCurveToCommand---------- 269 | @interface SVGQuadraticCurveToCommand : SVGCommandImpl @end 270 | 271 | @implementation SVGQuadraticCurveToCommand 272 | 273 | - (void)performWithParams:(CGFloat *)params 274 | commandType:(CommandType)type 275 | forPath:(SKUBezierPath *)path 276 | factoryIdentifier:(NSString *)identifier { 277 | 278 | if (type == Absolute) { 279 | [path addQuadCurveToPoint:CGPointMake(params[2], params[3]) 280 | controlPoint:CGPointMake(params[0], params[1])]; 281 | 282 | } else { 283 | [path addQuadCurveToPoint:CGPointMake(path.currentPoint.x + params[2], path.currentPoint.y + params[3]) 284 | controlPoint:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1])]; 285 | } 286 | 287 | } 288 | 289 | @end 290 | 291 | #pragma mark ----------SVGSmoothQuadraticCurveToCommand---------- 292 | @interface SVGSmoothQuadraticCurveToCommand : SVGCommandImpl @end 293 | 294 | @implementation SVGSmoothQuadraticCurveToCommand 295 | 296 | - (void)performWithParams:(CGFloat *)params 297 | commandType:(CommandType)type 298 | forPath:(SKUBezierPath *)path 299 | factoryIdentifier:(NSString *)identifier { 300 | CGPoint firstControlPoint = CGPointMake(path.currentPoint.x, path.currentPoint.y); 301 | 302 | if (self.prevCommand && self.prevCommand.length > 0) { 303 | NSString *prevCommandType = [self.prevCommand substringToIndex:1]; 304 | NSString *prevCommandTypeLowercase = [prevCommandType lowercaseString]; 305 | BOOL isAbsolute = ![prevCommandType isEqualToString:prevCommandTypeLowercase]; 306 | 307 | if ([prevCommandTypeLowercase isEqualToString:@"q"]) { 308 | 309 | CGFloat *prevParams = [self getCommandParameters:self.prevCommand]; 310 | 311 | if (isAbsolute) { 312 | firstControlPoint = CGPointMake(-1*prevParams[0] + 2*path.currentPoint.x, 313 | -1*prevParams[1] + 2*path.currentPoint.y); 314 | } else { 315 | CGPoint oldCurrentPoint = CGPointMake(path.currentPoint.x - prevParams[2], 316 | path.currentPoint.y - prevParams[3]); 317 | firstControlPoint = CGPointMake(-1*(prevParams[0] + oldCurrentPoint.x) + 2*path.currentPoint.x, 318 | -1*(prevParams[1] + oldCurrentPoint.y) + 2*path.currentPoint.y); 319 | } 320 | free(prevParams); 321 | } 322 | } 323 | if (type == Absolute) { 324 | [path addQuadCurveToPoint:CGPointMake(params[0], params[1]) 325 | controlPoint:CGPointMake(firstControlPoint.x, firstControlPoint.y)]; 326 | } else { 327 | [path addQuadCurveToPoint:CGPointMake(path.currentPoint.x + params[0], path.currentPoint.y + params[1]) 328 | controlPoint:CGPointMake(firstControlPoint.x, firstControlPoint.y)]; 329 | } 330 | 331 | } 332 | 333 | @end 334 | 335 | #pragma mark ----------SVGClosePathCommand---------- 336 | @interface SVGClosePathCommand : SVGCommandImpl @end 337 | 338 | @implementation SVGClosePathCommand 339 | 340 | - (void)performWithParams:(CGFloat *)params 341 | commandType:(CommandType)type 342 | forPath:(SKUBezierPath *)path 343 | factoryIdentifier:(NSString *)identifier{ 344 | [path closePath]; 345 | } 346 | 347 | @end 348 | 349 | #pragma mark ----------SVGCommandFactory---------- 350 | 351 | @interface SVGCommandFactory : NSObject { 352 | NSDictionary *commands; 353 | } 354 | + (SVGCommandFactory *)defaultFactory; 355 | + (SVGCommandFactory *)factoryWithIdentifier:(NSString*) string; 356 | - (id)commandForCommandLetter:(NSString *)commandLetter; 357 | @end 358 | 359 | @implementation SVGCommandFactory 360 | 361 | static NSMutableDictionary * factories; 362 | 363 | + (SVGCommandFactory *)factoryWithIdentifier:(NSString*) string { 364 | if (factories == nil) { 365 | factories = [[NSMutableDictionary alloc] init]; 366 | } 367 | 368 | if (factories[string] != nil ) { 369 | return factories[string]; 370 | } else { 371 | factories[string] = [[SVGCommandFactory alloc] init]; 372 | return factories[string]; 373 | } 374 | 375 | } 376 | 377 | + (SVGCommandFactory *)defaultFactory { 378 | static SVGCommandFactory *instance = nil; 379 | static dispatch_once_t onceToken; 380 | dispatch_once(&onceToken, ^{ 381 | instance = [[SVGCommandFactory alloc] init]; 382 | }); 383 | 384 | return instance; 385 | } 386 | 387 | - (id)init { 388 | self = [super init]; 389 | if (self) { 390 | SVGMoveCommand *move = [[SVGMoveCommand alloc] init]; 391 | SVGLineToCommand *lineTo = [[SVGLineToCommand alloc] init]; 392 | SVGHorizontalLineToCommand *horizontalLineTo = [[SVGHorizontalLineToCommand alloc] init]; 393 | SVGVerticalLineToCommand *verticalLineTo = [[SVGVerticalLineToCommand alloc] init]; 394 | SVGCurveToCommand *curveTo = [[SVGCurveToCommand alloc] init]; 395 | SVGSmoothCurveToCommand *smoothCurveTo = [[SVGSmoothCurveToCommand alloc] init]; 396 | SVGQuadraticCurveToCommand *quadraticCurveTo = [[SVGQuadraticCurveToCommand alloc] init]; 397 | SVGSmoothQuadraticCurveToCommand *smoothQuadraticCurveTo = [[SVGSmoothQuadraticCurveToCommand alloc] init]; 398 | SVGClosePathCommand *closePath = [[SVGClosePathCommand alloc] init]; 399 | 400 | commands = [[NSDictionary alloc] initWithObjectsAndKeys: 401 | move, @"m", 402 | lineTo, @"l", 403 | horizontalLineTo, @"h", 404 | verticalLineTo, @"v", 405 | curveTo, @"c", 406 | smoothCurveTo, @"s", 407 | quadraticCurveTo, @"q", 408 | smoothQuadraticCurveTo, @"t", 409 | closePath, @"z", 410 | nil]; 411 | 412 | } 413 | return self; 414 | } 415 | 416 | - (id)commandForCommandLetter:(NSString *)commandLetter { 417 | return [commands objectForKey:[commandLetter lowercaseString]]; 418 | } 419 | 420 | @end 421 | 422 | #pragma mark ----------SKUBezierPath (SVG)---------- 423 | 424 | @implementation SKUBezierPath (SVG) 425 | 426 | + (void)processCommandString:(NSString *)commandString 427 | withPrevCommandString:(NSString *)prevCommand 428 | forPath:(SKUBezierPath*)path 429 | withFactoryIdentifier:(NSString*) identifier{ 430 | 431 | if (!commandString || commandString.length <= 0) { 432 | @throw [NSException exceptionWithName:NSInvalidArgumentException 433 | reason:[NSString stringWithFormat:@"Invalid command %@", commandString] 434 | userInfo:nil]; 435 | } 436 | 437 | NSString *commandLetter = [commandString substringToIndex:1]; 438 | id command = [[SVGCommandFactory factoryWithIdentifier:identifier] commandForCommandLetter:commandLetter]; 439 | 440 | if (command) { 441 | [command processCommandString:commandString withPrevCommand:prevCommand forPath:path factoryIdentifier:identifier]; 442 | } else { 443 | @throw [NSException exceptionWithName:NSInvalidArgumentException 444 | reason:[NSString stringWithFormat:@"Unknown command %@", commandLetter] 445 | userInfo:nil]; 446 | } 447 | } 448 | 449 | + (NSRegularExpression *)commandRegex { 450 | static NSRegularExpression *_commandRegex; 451 | static dispatch_once_t onceToken; 452 | dispatch_once(&onceToken, ^{ 453 | _commandRegex = [[NSRegularExpression alloc] initWithPattern:@"[A-Za-z]" 454 | options:0 455 | error:nil]; 456 | }); 457 | return _commandRegex; 458 | } 459 | 460 | + (SKUBezierPath *)addPathWithSVGString:(NSString *)svgString toPath:(SKUBezierPath *)aPath factoryIdentifier: (NSString*) identifier { 461 | if (aPath && svgString && svgString.length > 0) { 462 | NSRegularExpression *regex = [self commandRegex]; 463 | __block NSTextCheckingResult *prevMatch = nil; 464 | __block NSString *prevCommand = @""; 465 | [regex enumerateMatchesInString:svgString 466 | options:0 467 | range:NSMakeRange(0, [svgString length]) 468 | usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { 469 | @autoreleasepool { 470 | if (prevMatch) { 471 | NSUInteger length = match.range.location - prevMatch.range.location; 472 | NSString *commandString = [svgString substringWithRange:NSMakeRange(prevMatch.range.location, 473 | length)]; 474 | 475 | 476 | [self processCommandString:commandString withPrevCommandString:prevCommand forPath:aPath withFactoryIdentifier:identifier]; 477 | prevCommand = nil; 478 | prevMatch = nil; 479 | prevCommand = commandString; 480 | } 481 | prevMatch = match; 482 | } 483 | 484 | }]; 485 | 486 | 487 | NSString *result = [svgString substringWithRange:NSMakeRange(prevMatch.range.location, svgString.length - prevMatch.range.location)]; 488 | 489 | [self processCommandString:result withPrevCommandString:prevCommand forPath:aPath withFactoryIdentifier:identifier]; 490 | } 491 | return aPath; 492 | } 493 | 494 | - (void)addPathFromSVGString:(NSString *)svgString factoryIdentifier:(NSString*) identifier{ 495 | [SKUBezierPath addPathWithSVGString:svgString toPath:self factoryIdentifier:identifier]; 496 | } 497 | 498 | + (SKUBezierPath *)bezierPathWithSVGString:(NSString *)svgString factoryIdentifier:(NSString*) identifier{ 499 | return [self addPathWithSVGString:svgString toPath:[SKUBezierPath bezierPath] factoryIdentifier:identifier]; 500 | } 501 | 502 | @end 503 | 504 | #if TARGET_OS_IPHONE 505 | #else 506 | @implementation NSBezierPath (AddQuads) 507 | 508 | -(void)addQuadCurveToPoint:(CGPoint)point controlPoint:(CGPoint)controlPoint { 509 | 510 | CGPoint qp0, qp1, qp2, cp0, cp1, cp2, cp3; 511 | CGFloat twoThree = 0.6666666666666666; 512 | 513 | qp0 = [self currentPoint]; 514 | qp1 = controlPoint; 515 | qp2 = point; 516 | 517 | 518 | cp0 = qp0; 519 | cp1 = CGPointMake((qp0.x + twoThree * (qp1.x - qp0.x)), (qp0.y + twoThree * (qp1.y - qp0.y))); 520 | cp2 = CGPointMake((qp2.x + twoThree * (qp1.x - qp2.x)), (qp2.y + twoThree * (qp1.y - qp2.y))); 521 | 522 | cp3 = qp2; 523 | 524 | [self curveToPoint:cp3 controlPoint1:cp1 controlPoint2:cp2]; 525 | 526 | 527 | } 528 | 529 | 530 | @end 531 | 532 | #endif -------------------------------------------------------------------------------- /SwiftVG/SwiftSVG/Parser/SVGParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVGParser.swift 3 | // SVGPlayground 4 | // 5 | // Created by Austin Fitzpatrick on 3/18/15. 6 | // Copyright (c) 2015 Seedling. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A Parser which takes in a .svg file and spits out an SVGVectorImage for display 12 | /// Begin your interaction with the parser by initializing it with a path and calling 13 | /// parse() to retrieve an SVGVectorImage. Safe to call on a background thread. 14 | class SVGParser: NSObject, NSXMLParserDelegate { 15 | 16 | let parserId:String = NSUUID().UUIDString /// Each parser has its own parser ID to protect against concurrency issues 17 | 18 | /// Initializes an SVGParser for the file at the given path 19 | /// 20 | /// :param: path The path to the SVG file 21 | /// :returns: An SVGParser ready to parse() 22 | init(path:String) { 23 | let url = NSURL(fileURLWithPath: path) 24 | if let parser = NSXMLParser(contentsOfURL: url) { 25 | self.parser = parser 26 | } else { 27 | fatalError("SVGParser could not find an SVG at the given path: \(path)") 28 | } 29 | } 30 | 31 | init(data:NSData) { 32 | self.parser = NSXMLParser(data: data) 33 | } 34 | 35 | /// Parse the supplied SVG file and return an SVGVectorImage 36 | /// 37 | /// :returns: an SVGImageVector ready for display 38 | func parse() -> SVGVectorImage { 39 | let (drawables, size) = coreParse() 40 | return SVGVectorImage(drawables: drawables, size:size) 41 | } 42 | 43 | /// Parse the supplied SVG file and return the components of an SVGVectorImage 44 | /// 45 | /// :returns: a tuple containing the SVGDrawable array and the size of the SVGVectorImage 46 | func coreParse() -> ([SVGDrawable], CGSize) { 47 | parser?.delegate = self 48 | parser?.parse() 49 | return (drawables, svgViewBox.size) 50 | } 51 | 52 | /// Parse a transform matrix string "matrix(a,b,c,d,tx,ty)" and return a CGAffineTransform 53 | /// 54 | /// :param: string The transformation String 55 | /// :returns: A CGAffineTransform 56 | class func transformFromString(transformString:String?) -> CGAffineTransform { 57 | if let string = transformString { 58 | let scanner = NSScanner(string: string) 59 | scanner.scanString("matrix(", intoString: nil) 60 | var a:Float = 0, b:Float = 0, c:Float = 0, d:Float = 0, tx:Float = 0, ty:Float = 0 61 | scanner.scanFloat(&a) 62 | scanner.scanFloat(&b) 63 | scanner.scanFloat(&c) 64 | scanner.scanFloat(&d) 65 | scanner.scanFloat(&tx) 66 | scanner.scanFloat(&ty) 67 | return CGAffineTransform(a: CGFloat(a), b: CGFloat(b), c: CGFloat(c), d: CGFloat(d), tx: CGFloat(tx), ty: CGFloat(ty)) 68 | } else { 69 | return CGAffineTransformIdentity 70 | } 71 | } 72 | 73 | //MARK: Private variables and functions 74 | 75 | private var parser:NSXMLParser? //an NSXMLParser to do the actual XML parsing. 76 | 77 | private var svgViewBox:CGRect = CGRectZero //viewbox defines the bounds and size of the vector image 78 | 79 | private var drawables:[SVGDrawable] = [] //A list of drawables at the root level - can include paths and groups 80 | private var colors:[String:UIColor] = [:] //A list of colors found in the document - cached for re-use 81 | private var gradients:[String:SVGGradient] = [:] //A list of gradients referenced by the document 82 | private var namedPaths:[String:UIBezierPath] = [:] //A list of paths given names by the document 83 | private var clippingPaths:[String:UIBezierPath] = [:] //A list of clipping paths 84 | 85 | private var lastGradient:SVGGradient? //the currently open gradient 86 | private var lastGroup:SVGGroup? //the currently open group 87 | private var lastClippingPath:String? //the ID of the last clipping path 88 | private var definingDefs:Bool = false //whether or not we're defining for rects, etc 89 | private var lastText:SVGText? //the last text we started parsing 90 | 91 | /// Takes a string containing a hex value and converts it to a UIColor. Caches the UIColor for later use. 92 | /// 93 | /// :param: potentialHexString the string potentially containing a hex value to parse into a UIColor 94 | /// :returns: UIColor representation of the hex string - or nil if no hex string is found 95 | func addColor(potentialHexString:String?) -> UIColor?{ 96 | if potentialHexString == "none" { return UIColor.clearColor() } 97 | if let potentialHexStringUnwrapped = potentialHexString { 98 | if let hexRange = potentialHexStringUnwrapped.rangeOfString("#", options: .allZeros, range: nil, locale: nil){ 99 | let hexString = potentialHexStringUnwrapped.stringByReplacingCharactersInRange(Range(start:potentialHexStringUnwrapped.startIndex, end: hexRange.startIndex), withString: "") 100 | colors[hexString] = colors[hexString] ?? UIColor(hexString: hexString) 101 | return colors[hexString] 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | /// Parses a viewBox string and sets the view box of the SVG 108 | /// 109 | /// :param: attributeDict the attribute dictionary from the SVG element form which to extract a view box 110 | func setViewBox(attributeDict:[NSObject:AnyObject]){ 111 | if let viewBox = attributeDict["viewBox"] as? NSString { 112 | let floats:[CGFloat] = viewBox.componentsSeparatedByString(" ").map { CGFloat(($0 as NSString).floatValue) } 113 | if floats.count < 4 { svgViewBox = CGRectZero; println("An error has occured - the view box is zero") } 114 | svgViewBox = CGRect(x: floats[0], y: floats[1], width: floats[2], height: floats[3]) 115 | } else { 116 | let width = (attributeDict["width"] as NSString).floatValue 117 | let height = (attributeDict["height"] as NSString).floatValue 118 | svgViewBox = CGRect(x:0, y:0, width:CGFloat(width), height:CGFloat(height)) 119 | } 120 | } 121 | 122 | /// Returns either a gradient ID or hex string for a color from a string (strips "url(" from gradient references) 123 | /// 124 | /// :param: string The string to parse for #hexcolors or url(#gradientId) 125 | /// :returns: A string fit for using as a key to lookup gradients and colors - or nil of the param was nil 126 | private func itemIDOrHexFromAttribute(string:String?) -> String? { 127 | let newString = string?.stringByReplacingOccurrencesOfString("url(#", withString: "", options: .allZeros, range: nil) 128 | return newString?.stringByReplacingOccurrencesOfString(")", withString: "", options: .allZeros, range: nil) 129 | } 130 | 131 | /// Returns an array of points parsed out of a string in the format "x1,y1 x2,y2 x3,y3" 132 | /// 133 | /// :params: string the string to parse points out of 134 | /// :returns: an array of CGPoint structs representing the points in the string 135 | private func pointsFromPointString(string:String?) -> [CGPoint] { 136 | if string == nil { return [] } 137 | let pairs = string!.componentsSeparatedByString(" ") as [String] 138 | return pairs.filter{ ($0.utf16Count > 0) }.map { 139 | let numbers = $0.componentsSeparatedByString(",") 140 | let x = (numbers[0] as NSString).floatValue 141 | let y = (numbers[1] as NSString).floatValue 142 | return CGPoint(x:CGFloat(x) - self.svgViewBox.origin.x, y:CGFloat(y) - self.svgViewBox.origin.y) 143 | } 144 | } 145 | 146 | /// Processes a rectangle found during parsing. If we're defining the for the document we'll just save the rect 147 | /// for later use. If we're not currently defining then we'll interpret it as a rectangular path. 148 | /// 149 | /// :param: attributeDict The attributes from the XML element - currently "x", "y", "width", "height", "id", "opacity", "fill" are supported. 150 | func addRect(attributeDict: [NSObject : AnyObject]){ 151 | let id = attributeDict["id"] as? String 152 | let originX = CGFloat((attributeDict["x"] as NSString).floatValue) 153 | let originY = CGFloat((attributeDict["y"] as NSString).floatValue) 154 | let width = CGFloat((attributeDict["width"] as NSString).floatValue) 155 | let height = CGFloat((attributeDict["height"] as NSString).floatValue) 156 | let rect = CGRectOffset(CGRect(x: originX, y: originY, width: width, height: height), -svgViewBox.origin.x, -svgViewBox.origin.y) 157 | if definingDefs { 158 | if let id = id { 159 | namedPaths[id] = UIBezierPath(rect:rect) 160 | } else{ 161 | println("Defining defs, but didn't find id for rect") 162 | } 163 | } else { 164 | let bezierPath = UIBezierPath(rect: rect) 165 | bezierPath.applyTransform(SVGParser.transformFromString(attributeDict["transform"] as? String)) 166 | createSVGPath(bezierPath, attributeDict: attributeDict) 167 | } 168 | } 169 | 170 | /// Adds a path defined by attributeDict to either the last group or the root element, if no group exists 171 | /// 172 | /// :param: attributeDict The attributes from the XML element - currently "fill", "opacity" and "d" are supported. 173 | private func addPath(attributeDict: [NSObject:AnyObject]){ 174 | let id = attributeDict["id"] as? String 175 | let d = attributeDict["d"] as String 176 | let bezierPath = UIBezierPath(SVGString: d, factoryIdentifier:parserId) 177 | bezierPath.applyTransform(CGAffineTransformMakeTranslation(-svgViewBox.origin.x, -svgViewBox.origin.y)) 178 | bezierPath.miterLimit = 4 179 | if definingDefs { 180 | if let id = id { 181 | namedPaths[id] = bezierPath 182 | } else{ 183 | println("Defining defs, but didn't find id for rect") 184 | } 185 | } else { 186 | createSVGPath(bezierPath, attributeDict: attributeDict) 187 | } 188 | } 189 | 190 | /// Adds a path in the shape of the polygon defined by attributeDict 191 | /// 192 | /// :param: attributeDict the attributes from the XML - currently "points", "fill", and "opacity" 193 | private func addPolygon(attributeDict:[NSObject : AnyObject]) { 194 | let id = attributeDict["id"] as? String 195 | let points = pointsFromPointString(attributeDict["points"] as? String) 196 | 197 | var bezierPath = UIBezierPath() 198 | bezierPath.moveToPoint(points[0]) 199 | for i in 1.. 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 40 | 41 | 42 | 44 | 46 | 48 | 52 | 53 | 55 | 59 | 61 | 66 | 69 | 74 | 75 | 77 | 79 | 81 | 83 | 85 | 89 | 91 | 94 | 97 | 103 | 108 | 111 | 117 | 123 | 127 | 130 | 131 | 137 | 139 | 141 | 143 | 145 | 147 | 150 | 152 | 154 | 157 | 159 | 162 | 165 | 168 | 169 | 170 | 171 | 172 | 317 | 318 | 330 | 335 | 337 | 339 | 341 | 344 | 346 | 348 | 349 | 351 | 352 | 354 | 355 | 357 | 359 | 362 | 364 | 365 | 367 | 368 | 369 | 371 | 373 | 375 | 377 | 379 | 380 | 382 | 383 | 385 | 387 | 388 | 390 | 391 | 393 | 394 | 396 | 398 | 400 | 402 | 404 | 406 | 408 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | --------------------------------------------------------------------------------