├── 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 | 
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 |
428 |
--------------------------------------------------------------------------------