Compare commits

..

34 Commits

Author SHA1 Message Date
Curtis Hard 849807d3c5 Updated build number 2020-01-05 17:11:41 +00:00
Curtis Hard eeb03bd89c Fixes exporter being wrong if the viewbox is not zero 2020-01-02 14:16:17 +00:00
Curtis Hard fdc0859e3f Faster parsing still 2020-01-01 22:06:29 +00:00
Curtis Hard e2a7f51507 Fixes 2020-01-01 17:48:15 +00:00
Curtis Hard ab69ed94dc Formatting 2019-12-29 20:50:44 +00:00
Curtis Hard c5be83f57f Code cleanup 2019-12-28 15:48:43 +00:00
Curtis Hard ed00aca06e Refactor 2019-12-27 16:48:12 +00:00
Curtis Hard f51fda5f1e Removed cache
- also fixes center if no scale was applied
2019-12-27 15:01:20 +00:00
Curtis Hard dd655053ca Finally fixes memory leak
- due to this being fixed, we can also get rid of the transaction lock
2019-12-26 20:10:47 +00:00
Curtis Hard 8c89627f74 Just use matrix, attributes are a pain in the butt 2019-12-26 16:08:14 +00:00
Curtis Hard eb1d27decd Much better path compression and float rewriting for exporting 2019-12-26 13:49:41 +00:00
Curtis Hard e6141d7a0f Fixes a long time ago bug with transforms with matrix converting to attribute strings 2019-12-25 21:05:56 +00:00
Curtis Hard 3b7ad40794 Vey strange... 2019-12-25 18:40:50 +00:00
Curtis Hard 674d17863f Fixes memory overflow issues
- also added centering for exported SVG’s as an option
2019-12-25 18:19:28 +00:00
Curtis Hard a59072fa1d Refactor 2019-12-25 13:53:42 +00:00
Curtis Hard 910b90cd4e More perf stuff 2019-12-25 13:45:13 +00:00
Curtis Hard bb3b9c5378 More improvements 2019-12-25 11:58:10 +00:00
Curtis Hard 6d10b172e7 Increases again 2019-12-24 14:47:47 +00:00
Curtis Hard c6e59a9280 More perf increases 2019-12-24 09:27:06 +00:00
Curtis Hard df5b3219ca Refactor of naming 2019-12-23 21:59:19 +00:00
Curtis Hard 2c1ae8d0f3 Credit where its due 2019-12-23 21:40:02 +00:00
Curtis Hard 7ba939aabf This is insanely fast 2019-12-23 20:48:12 +00:00
Curtis Hard 24e097fedf Faster again! 2019-12-23 20:30:05 +00:00
Curtis Hard 025ac84958 Refactor of command parsing 2019-12-23 19:37:56 +00:00
Curtis Hard ac9ccdda25 Refactor 2019-12-23 16:17:47 +00:00
Curtis Hard 14641ddd60 More goodies 2019-12-23 15:40:41 +00:00
Curtis Hard 5f32d03744 Intiial commit 2019-12-23 14:24:49 +00:00
Curtis Hard af086622b1 Faster implementation of transaction 2019-12-22 22:19:20 +00:00
Curtis Hard 79187326bc Added CGImageRef method 2019-12-21 13:23:52 +00:00
Curtis Hard ee570eb77f Fixes scale being incorrect 2019-12-20 08:36:12 +00:00
Curtis Hard d8e1ce8d70 This is much more performant then using nsimage lockFocus 2019-12-19 22:32:55 +00:00
Curtis Hard a900e2dc50 Resorted this list as its actually faster, basically stick the commonly used commands first 2019-12-17 21:48:52 +00:00
Curtis Hard 21f97babec Refactor of contsant names 2019-12-12 11:02:49 +00:00
Curtis Hard 0273a491de Refactor of path - uses less memory and makes more sense 2019-12-12 10:51:20 +00:00
47 changed files with 1052 additions and 1056 deletions
+12 -10
View File
@@ -11,6 +11,8 @@
594CF561238FF46C009B251B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 594CF560238FF46C009B251B /* Foundation.framework */; };
594CF563238FF473009B251B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 594CF562238FF473009B251B /* QuartzCore.framework */; };
599EB4D3238FF570004CB6BC /* libobjc.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 599EB4D2238FF535004CB6BC /* libobjc.tbd */; };
59E7CFAF23B148600077D599 /* IJSVGCommandParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 59E7CFAD23B148600077D599 /* IJSVGCommandParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
59E7CFB023B148600077D599 /* IJSVGCommandParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E7CFAE23B148600077D599 /* IJSVGCommandParser.m */; };
59EB75D623905F7300F5AE63 /* IJSVGLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB756523905F6B00F5AE63 /* IJSVGLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB75D723905F7300F5AE63 /* IJSVGGradientLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB756623905F6B00F5AE63 /* IJSVGGradientLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB75D823905F7300F5AE63 /* IJSVGStyleSheetRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB756723905F6B00F5AE63 /* IJSVGStyleSheetRule.m */; };
@@ -57,7 +59,6 @@
59EB760123905F7300F5AE63 /* IJSVGLayerTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB759023905F6D00F5AE63 /* IJSVGLayerTree.m */; };
59EB760223905F7300F5AE63 /* IJSVGCommandVerticalLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB759123905F6D00F5AE63 /* IJSVGCommandVerticalLine.m */; };
59EB760323905F7300F5AE63 /* IJSVGNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB759223905F6D00F5AE63 /* IJSVGNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB760423905F7300F5AE63 /* IJSVGCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB759323905F6D00F5AE63 /* IJSVGCache.m */; };
59EB760523905F7300F5AE63 /* IJSVGUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB759423905F6D00F5AE63 /* IJSVGUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB760623905F7300F5AE63 /* IJSVGCommandHorizontalLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB759523905F6D00F5AE63 /* IJSVGCommandHorizontalLine.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB760723905F7300F5AE63 /* IJSVGNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB759623905F6D00F5AE63 /* IJSVGNode.m */; };
@@ -100,7 +101,6 @@
59EB762C23905F7300F5AE63 /* IJSVGPattern.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB75BB23905F7000F5AE63 /* IJSVGPattern.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB762D23905F7300F5AE63 /* IJSVGLayerTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB75BC23905F7000F5AE63 /* IJSVGLayerTree.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB762E23905F7300F5AE63 /* IJSVGExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB75BD23905F7000F5AE63 /* IJSVGExporter.m */; };
59EB762F23905F7300F5AE63 /* IJSVGCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB75BE23905F7000F5AE63 /* IJSVGCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB763023905F7300F5AE63 /* IJSVGPatternLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 59EB75BF23905F7000F5AE63 /* IJSVGPatternLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
59EB763123905F7300F5AE63 /* IJSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB75C023905F7100F5AE63 /* IJSVGText.m */; };
59EB763223905F7300F5AE63 /* IJSVGView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59EB75C123905F7100F5AE63 /* IJSVGView.m */; };
@@ -133,6 +133,8 @@
594CF560238FF46C009B251B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
594CF562238FF473009B251B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
599EB4D2238FF535004CB6BC /* libobjc.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libobjc.tbd; path = usr/lib/libobjc.tbd; sourceTree = SDKROOT; };
59E7CFAD23B148600077D599 /* IJSVGCommandParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = IJSVGCommandParser.h; path = IJSVG/Source/Parsing/IJSVGCommandParser.h; sourceTree = SOURCE_ROOT; };
59E7CFAE23B148600077D599 /* IJSVGCommandParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = IJSVGCommandParser.m; path = IJSVG/Source/Parsing/IJSVGCommandParser.m; sourceTree = SOURCE_ROOT; };
59EB756523905F6B00F5AE63 /* IJSVGLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGLayer.h; sourceTree = "<group>"; };
59EB756623905F6B00F5AE63 /* IJSVGGradientLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGGradientLayer.h; sourceTree = "<group>"; };
59EB756723905F6B00F5AE63 /* IJSVGStyleSheetRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGStyleSheetRule.m; sourceTree = "<group>"; };
@@ -179,7 +181,6 @@
59EB759023905F6D00F5AE63 /* IJSVGLayerTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGLayerTree.m; sourceTree = "<group>"; };
59EB759123905F6D00F5AE63 /* IJSVGCommandVerticalLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGCommandVerticalLine.m; sourceTree = "<group>"; };
59EB759223905F6D00F5AE63 /* IJSVGNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGNode.h; sourceTree = "<group>"; };
59EB759323905F6D00F5AE63 /* IJSVGCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGCache.m; sourceTree = "<group>"; };
59EB759423905F6D00F5AE63 /* IJSVGUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGUtils.h; sourceTree = "<group>"; };
59EB759523905F6D00F5AE63 /* IJSVGCommandHorizontalLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGCommandHorizontalLine.h; sourceTree = "<group>"; };
59EB759623905F6D00F5AE63 /* IJSVGNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGNode.m; sourceTree = "<group>"; };
@@ -222,7 +223,6 @@
59EB75BB23905F7000F5AE63 /* IJSVGPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGPattern.h; sourceTree = "<group>"; };
59EB75BC23905F7000F5AE63 /* IJSVGLayerTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGLayerTree.h; sourceTree = "<group>"; };
59EB75BD23905F7000F5AE63 /* IJSVGExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGExporter.m; sourceTree = "<group>"; };
59EB75BE23905F7000F5AE63 /* IJSVGCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGCache.h; sourceTree = "<group>"; };
59EB75BF23905F7000F5AE63 /* IJSVGPatternLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGPatternLayer.h; sourceTree = "<group>"; };
59EB75C023905F7100F5AE63 /* IJSVGText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGText.m; sourceTree = "<group>"; };
59EB75C123905F7100F5AE63 /* IJSVGView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGView.m; sourceTree = "<group>"; };
@@ -346,6 +346,8 @@
592ABBE62397A00C00F44380 /* Parsing */ = {
isa = PBXGroup;
children = (
59E7CFAD23B148600077D599 /* IJSVGCommandParser.h */,
59E7CFAE23B148600077D599 /* IJSVGCommandParser.m */,
59EB75A723905F6F00F5AE63 /* IJSVGParser.h */,
59EB75A523905F6E00F5AE63 /* IJSVGParser.m */,
);
@@ -475,8 +477,6 @@
children = (
59EB756A23905F6B00F5AE63 /* IJSVG.h */,
59EB75A223905F6E00F5AE63 /* IJSVG.m */,
59EB75BE23905F7000F5AE63 /* IJSVGCache.h */,
59EB759323905F6D00F5AE63 /* IJSVGCache.m */,
59EB758423905F6C00F5AE63 /* IJSVGError.h */,
59EB758823905F6D00F5AE63 /* IJSVGFontConverter.h */,
59EB757C23905F6C00F5AE63 /* IJSVGFontConverter.m */,
@@ -545,7 +545,6 @@
59EB75E823905F7300F5AE63 /* IJSVGUnitLength.h in Headers */,
59EB762723905F7300F5AE63 /* IJSVGMath.h in Headers */,
59EB761523905F7300F5AE63 /* IJSVGStrokeLayer.h in Headers */,
59EB762F23905F7300F5AE63 /* IJSVGCache.h in Headers */,
59EB75DE23905F7300F5AE63 /* IJSVGCommandVerticalLine.h in Headers */,
59EB764123905F7300F5AE63 /* IJSVGImageLayer.h in Headers */,
59EB75E023905F7300F5AE63 /* IJSVGStyleSheet.h in Headers */,
@@ -568,6 +567,7 @@
59EB761E23905F7300F5AE63 /* IJSVGGroupLayer.h in Headers */,
59EB761D23905F7300F5AE63 /* IJSVGStyle.h in Headers */,
59EB764523905F7300F5AE63 /* IJSVGExporter.h in Headers */,
59E7CFAF23B148600077D599 /* IJSVGCommandParser.h in Headers */,
59EB762823905F7300F5AE63 /* IJSVGCommandClose.h in Headers */,
59EB75E423905F7300F5AE63 /* IJSVGGradientUnitLength.h in Headers */,
59EB762D23905F7300F5AE63 /* IJSVGLayerTree.h in Headers */,
@@ -661,10 +661,10 @@
59EB75E523905F7300F5AE63 /* IJSVGStrokeLayer.m in Sources */,
59EB763223905F7300F5AE63 /* IJSVGView.m in Sources */,
59EB763523905F7300F5AE63 /* IJSVGStyleSheetSelector.m in Sources */,
59E7CFB023B148600077D599 /* IJSVGCommandParser.m in Sources */,
59EB760723905F7300F5AE63 /* IJSVGNode.m in Sources */,
59EB75E923905F7300F5AE63 /* IJSVGStringAdditions.m in Sources */,
59EB761723905F7300F5AE63 /* IJSVGRadialGradient.m in Sources */,
59EB760423905F7300F5AE63 /* IJSVGCache.m in Sources */,
59EB75E223905F7300F5AE63 /* IJSVGColorList.m in Sources */,
59EB75ED23905F7300F5AE63 /* IJSVGFontConverter.m in Sources */,
59EB763C23905F7300F5AE63 /* IJSVGCommandEllipticalArc.m in Sources */,
@@ -828,6 +828,7 @@
CLANG_WARN_STRICT_PROTOTYPES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = 9KTR4W9XX6;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -840,7 +841,7 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.1;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.iconjar.ijsvg;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -855,6 +856,7 @@
CLANG_WARN_STRICT_PROTOTYPES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = 9KTR4W9XX6;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -867,7 +869,7 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.1;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.iconjar.ijsvg;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,4 +14,6 @@
- (void)addQuadCurveToPoint:(NSPoint)aPoint
controlPoint:(NSPoint)cp;
- (CGPathRef)newCGPathRef:(BOOL)autoClose;
@end
@@ -23,4 +23,65 @@
controlPoint2:CP2];
}
- (CGPathRef)newCGPathRef:(BOOL)autoClose
{
NSInteger i = 0;
NSInteger numElements = self.elementCount;
NSBezierPath* bezPath = self;
// nothing to return
if (numElements == 0) {
return NULL;
}
CGMutablePathRef aPath = CGPathCreateMutable();
NSPoint points[3];
BOOL didClosePath = YES;
for (i = 0; i < numElements; i++) {
switch ([bezPath elementAtIndex:i associatedPoints:points]) {
// move
case NSMoveToBezierPathElement: {
CGPathMoveToPoint(aPath, NULL, points[0].x, points[0].y);
break;
}
// line
case NSLineToBezierPathElement: {
CGPathAddLineToPoint(aPath, NULL, points[0].x, points[0].y);
didClosePath = NO;
break;
}
// curve
case NSCurveToBezierPathElement: {
CGPathAddCurveToPoint(aPath, NULL, points[0].x, points[0].y,
points[1].x, points[1].y,
points[2].x, points[2].y);
didClosePath = NO;
break;
}
// close
case NSClosePathBezierPathElement: {
CGPathCloseSubpath(aPath);
didClosePath = YES;
break;
}
}
}
if (!didClosePath && autoClose) {
CGPathCloseSubpath(aPath);
}
// create immutable and release
CGPathRef pathToReturn = CGPathCreateCopy(aPath);
CGPathRelease(aPath);
return pathToReturn;
}
@end
@@ -10,7 +10,7 @@
@interface NSString (IJSVGAdditions)
- (NSArray<NSString *> *)ijsvg_componentsSeparatedByChars:(char *)aChar;
- (NSArray<NSString*>*)ijsvg_componentsSeparatedByChars:(const char*)aChar;
- (BOOL)ijsvg_isNumeric;
- (BOOL)ijsvg_containsAlpha;
- (NSArray*)ijsvg_componentsSplitByWhiteSpace;
@@ -10,7 +10,7 @@
@implementation NSString (IJSVGAdditions)
- (NSArray<NSString*>*)ijsvg_componentsSeparatedByChars:(char*)aChar
- (NSArray<NSString*>*)ijsvg_componentsSeparatedByChars:(const char*)aChar
{
NSMutableArray* comp = [[[NSMutableArray alloc] init] autorelease];
NSInteger length = self.length;
@@ -18,25 +18,13 @@
NSInteger ind = 0;
BOOL startedString = NO;
// block for easy comparison
NSUInteger aLength = strlen(aChar);
BOOL (^charsContainsChar)(char anotherChar) = ^(char anotherChar) {
for (NSInteger i = 0; i < aLength; i++) {
if (aChar[i] == anotherChar) {
return YES;
}
}
return NO;
};
const char* buffer = self.UTF8String;
for (NSInteger i = 0; i < length; i++) {
// the char
unichar theChar = [self characterAtIndex:i];
unichar theChar = buffer[i];
// start the buffer
BOOL isEqualToChar = charsContainsChar(theChar);
BOOL isEqualToChar = strchr(aChar, theChar) != NULL;
if (isEqualToChar == NO) {
startedString = YES;
chars[ind++] = theChar;
@@ -48,11 +36,10 @@
// append the comp
[comp addObject:[NSString stringWithCharacters:chars length:ind]];
free(chars);
// restart and realloc the memory
ind = 0;
chars = (unichar*)calloc(sizeof(unichar), self.length);
chars = memset(chars, '\0', sizeof(unichar) * ind);
}
}
free(chars);
@@ -385,8 +385,8 @@ CGFloat* IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightnes
}
// note the %g, CSS alpha is 0 to 1, not 0 - 100, my bad!
return [NSString stringWithFormat:@"rgba(%d,%d,%d,%g)", red, green, blue,
((float)alpha / 100.f)];
return [NSString stringWithFormat:@"rgba(%d,%d,%d,%@)", red, green, blue,
IJSVGShortFloatString((float)alpha / 100.f)];
}
+ (NSString*)colorNameFromPredefinedColor:(IJSVGPredefinedColor)color
@@ -6,6 +6,7 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import "IJSVGCommandParser.h"
#import "IJSVGPath.h"
#import <Foundation/Foundation.h>
@@ -16,14 +17,9 @@ typedef NS_ENUM(NSInteger, IJSVGCommandType) {
kIJSVGCommandTypeRelative
};
typedef NS_ENUM(NSUInteger, IJSVGPathDataSequence) {
kIJSVGPathDataSequenceTypeFloat,
kIJSVGPathDataSequenceTypeFlag
};
@interface IJSVGCommand : NSObject {
NSString* commandString;
NSString* command;
char command;
CGFloat* parameters;
NSInteger parameterCount;
NSArray<IJSVGCommand*>* subCommands;
@@ -35,10 +31,9 @@ typedef NS_ENUM(NSUInteger, IJSVGPathDataSequence) {
}
@property (nonatomic, copy) NSString* commandString;
@property (nonatomic, copy) NSString* command;
@property (nonatomic, assign) char command;
@property (nonatomic, assign) CGFloat* parameters;
@property (nonatomic, assign) NSInteger parameterCount;
@property (nonatomic, assign) NSInteger requiredParameters;
@property (nonatomic, assign) IJSVGCommandType type;
@property (nonatomic, retain) NSArray<IJSVGCommand*>* subCommands;
@property (nonatomic, assign) IJSVGCommand* previousCommand;
@@ -60,8 +55,10 @@ typedef NS_ENUM(NSUInteger, IJSVGPathDataSequence) {
intoArray:(NSMutableArray<IJSVGCommand*>*)commands
parentCommand:(IJSVGCommand*)parentCommand;
- (id)initWithCommandString:(NSString*)commandString;
- (id)initWithCommandString:(NSString*)str
dataStream:(IJSVGPathDataStream*)dataStream;
- (IJSVGCommand*)subcommandWithParameters:(CGFloat*)subParams
paramCount:(NSInteger)paramCount
previousCommand:(IJSVGCommand*)command;
- (CGFloat)readFloat;
@@ -27,7 +27,6 @@
@synthesize parameterCount;
@synthesize parameters;
@synthesize subCommands;
@synthesize requiredParameters;
@synthesize type;
@synthesize previousCommand;
@synthesize isSubCommand;
@@ -105,7 +104,6 @@
- (void)dealloc
{
(void)([commandString release]), commandString = nil;
(void)([command release]), command = nil;
(void)([subCommands release]), subCommands = nil;
if (parameters) {
(void)(free(parameters)), parameters = nil;
@@ -114,24 +112,26 @@
}
- (id)initWithCommandString:(NSString*)str
dataStream:(IJSVGPathDataStream*)dataStream
{
if ((self = [super init]) != nil) {
// work out the basics
_currentIndex = 0;
command = [[str substringToIndex:1] copy];
type = [IJSVGUtils typeForCommandString:self.command];
requiredParameters = [self.class requiredParameterCount];
command = [str characterAtIndex:0];
type = [IJSVGUtils typeForCommandChar:command];
NSInteger sets = 0;
NSInteger paramCount = [self.class requiredParameterCount];
IJSVGPathDataSequence* sequence = [self.class pathDataSequence];
parameters = IJSVGParsePathDataSequence(str, sequence, requiredParameters, &sets);
parameters = IJSVGParsePathDataStreamSequence(str.UTF8String, str.length,
dataStream, sequence, [self.class requiredParameterCount], &sets);
if (sets <= 1) {
CGFloat* subParams = [self parametersFromIndexOffset:0];
IJSVGCommand* command = [self subcommandWithParameters:subParams
paramCount:paramCount
previousCommand:nil];
subCommands = @[ command ].retain;
} else {
NSMutableArray<IJSVGCommand*>* subCommandArray = nil;
subCommandArray = [[NSMutableArray alloc] initWithCapacity:sets].autorelease;
@@ -143,6 +143,7 @@
// generate the subcommand
IJSVGCommand* command = [self subcommandWithParameters:subParams
paramCount:paramCount
previousCommand:lastCommand];
// make sure we assign the last command or hell breaks
@@ -162,7 +163,7 @@
- (CGFloat*)parametersFromIndexOffset:(NSInteger)index
{
CGFloat* subParams = 0;
NSInteger req = self.requiredParameters;
NSInteger req = [self.class requiredParameterCount];
if (req != 0) {
subParams = (CGFloat*)malloc(req * sizeof(CGFloat));
memcpy(subParams, &self.parameters[index * req], sizeof(CGFloat) * req);
@@ -171,11 +172,12 @@
}
- (IJSVGCommand*)subcommandWithParameters:(CGFloat*)subParams
paramCount:(NSInteger)paramCount
previousCommand:(IJSVGCommand*)aPreviousCommand
{
// create a subcommand per set
IJSVGCommand* c = [[[self.class alloc] init] autorelease];
c.parameterCount = self.requiredParameters;
c.parameterCount = paramCount;
c.parameters = subParams;
c.type = self.type;
c.command = self.command;
@@ -23,14 +23,14 @@
path:(IJSVGPath*)path
{
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] curveToPoint:NSMakePoint(params[4], params[5])
controlPoint1:NSMakePoint(params[0], params[1])
controlPoint2:NSMakePoint(params[2], params[3])];
[path.path curveToPoint:NSMakePoint(params[4], params[5])
controlPoint1:NSMakePoint(params[0], params[1])
controlPoint2:NSMakePoint(params[2], params[3])];
return;
}
[[path currentSubpath] relativeCurveToPoint:NSMakePoint(params[4], params[5])
controlPoint1:NSMakePoint(params[0], params[1])
controlPoint2:NSMakePoint(params[2], params[3])];
[path.path relativeCurveToPoint:NSMakePoint(params[4], params[5])
controlPoint1:NSMakePoint(params[0], params[1])
controlPoint2:NSMakePoint(params[2], params[3])];
}
@end
@@ -92,33 +92,33 @@ static IJSVGPathDataSequence* _sequence;
NSAffineTransform* trans = NSAffineTransform.transform;
[trans translateXBy:-centerPoint.x yBy:-centerPoint.y];
[path.currentSubpath transformUsingAffineTransform:trans];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans rotateByRadians:-xAxisRotation];
[path.currentSubpath transformUsingAffineTransform:trans];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans scaleXBy:(1 / scale.x) yBy:(1 / scale.y)];
[path.currentSubpath transformUsingAffineTransform:trans];
[path.path transformUsingAffineTransform:trans];
[path.currentSubpath appendBezierPathWithArcWithCenter:NSZeroPoint
radius:radius
startAngle:radians_to_degrees(startAngle)
endAngle:radians_to_degrees(startAngle + angleDelta)
clockwise:!sweepFlag];
[path.path appendBezierPathWithArcWithCenter:NSZeroPoint
radius:radius
startAngle:radians_to_degrees(startAngle)
endAngle:radians_to_degrees(startAngle + angleDelta)
clockwise:!sweepFlag];
trans = NSAffineTransform.transform;
[trans scaleXBy:scale.x yBy:scale.y];
[path.currentSubpath transformUsingAffineTransform:trans];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans rotateByRadians:xAxisRotation];
[path.currentSubpath transformUsingAffineTransform:trans];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans translateXBy:centerPoint.x yBy:centerPoint.y];
[path.currentSubpath transformUsingAffineTransform:trans];
[path.path transformUsingAffineTransform:trans];
}
@end
@@ -23,10 +23,10 @@
path:(IJSVGPath*)path
{
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] lineToPoint:NSMakePoint(params[0], [path currentSubpath].currentPoint.y)];
[path.path lineToPoint:NSMakePoint(params[0], path.currentPoint.y)];
return;
}
[[path currentSubpath] relativeLineToPoint:NSMakePoint(params[0], 0.f)];
[path.path relativeLineToPoint:NSMakePoint(params[0], 0.f)];
}
@end
@@ -23,12 +23,12 @@
path:(IJSVGPath*)path
{
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] lineToPoint:NSMakePoint(params[0], params[1])];
[path.path lineToPoint:NSMakePoint(params[0], params[1])];
return;
}
NSPoint point = NSMakePoint([path currentSubpath].currentPoint.x + params[0],
[path currentSubpath].currentPoint.y + params[1]);
[[path currentSubpath] lineToPoint:point];
NSPoint point = NSMakePoint(path.currentPoint.x + params[0],
path.currentPoint.y + params[1]);
[path.path lineToPoint:point];
}
@end
@@ -37,14 +37,14 @@
// actual move to command
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] moveToPoint:NSMakePoint(params[0], params[1])];
[path.path moveToPoint:NSMakePoint(params[0], params[1])];
return;
}
@try {
[[path currentSubpath] relativeMoveToPoint:NSMakePoint(params[0], params[1])];
[path.path relativeMoveToPoint:NSMakePoint(params[0], params[1])];
}
@catch (NSException* exception) {
[[path currentSubpath] moveToPoint:NSMakePoint(params[0], params[1])];
[path.path moveToPoint:NSMakePoint(params[0], params[1])];
}
}
@@ -24,12 +24,12 @@
path:(IJSVGPath*)path
{
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] addQuadCurveToPoint:NSMakePoint(params[2], params[3])
controlPoint:NSMakePoint(params[0], params[1])];
[path.path addQuadCurveToPoint:NSMakePoint(params[2], params[3])
controlPoint:NSMakePoint(params[0], params[1])];
return;
}
[[path currentSubpath] addQuadCurveToPoint:NSMakePoint([path currentSubpath].currentPoint.x + params[2], [path currentSubpath].currentPoint.y + params[3])
controlPoint:NSMakePoint([path currentSubpath].currentPoint.x + params[0], [path currentSubpath].currentPoint.y + params[1])];
[path.path addQuadCurveToPoint:NSMakePoint(path.currentPoint.x + params[2], path.currentPoint.y + params[3])
controlPoint:NSMakePoint(path.currentPoint.x + params[0], path.currentPoint.y + params[1])];
}
@end
@@ -24,41 +24,41 @@
type:(IJSVGCommandType)type
path:(IJSVGPath*)path
{
NSPoint firstControl = NSMakePoint([path currentSubpath].currentPoint.x, [path currentSubpath].currentPoint.y);
NSPoint firstControl = NSMakePoint(path.currentPoint.x, path.currentPoint.y);
if (command != nil) {
if (command.class == [IJSVGCommandCurve class] || command.class == self.class) {
if (command.class == [IJSVGCommandCurve class]) {
if (command.type == kIJSVGCommandTypeAbsolute) {
firstControl = NSMakePoint(-1 * command.parameters[2] + 2 * [path currentSubpath].currentPoint.x,
-1 * command.parameters[3] + 2 * [path currentSubpath].currentPoint.y);
firstControl = NSMakePoint(-1 * command.parameters[2] + 2 * path.currentPoint.x,
-1 * command.parameters[3] + 2 * path.currentPoint.y);
} else {
NSPoint oldPoint = NSMakePoint([path currentSubpath].currentPoint.x - command.parameters[4],
[path currentSubpath].currentPoint.y - command.parameters[5]);
firstControl = NSMakePoint(-1 * (command.parameters[2] + oldPoint.x) + 2 * [path currentSubpath].currentPoint.x,
-1 * (command.parameters[3] + oldPoint.y) + 2 * [path currentSubpath].currentPoint.y);
NSPoint oldPoint = NSMakePoint(path.currentPoint.x - command.parameters[4],
path.currentPoint.y - command.parameters[5]);
firstControl = NSMakePoint(-1 * (command.parameters[2] + oldPoint.x) + 2 * path.currentPoint.x,
-1 * (command.parameters[3] + oldPoint.y) + 2 * path.currentPoint.y);
}
} else {
if (command.type == kIJSVGCommandTypeAbsolute) {
firstControl = NSMakePoint(-1 * command.parameters[0] + 2 * [path currentSubpath].currentPoint.x,
-1 * command.parameters[1] + 2 * [path currentSubpath].currentPoint.y);
firstControl = NSMakePoint(-1 * command.parameters[0] + 2 * path.currentPoint.x,
-1 * command.parameters[1] + 2 * path.currentPoint.y);
} else {
NSPoint oldPoint = NSMakePoint([path currentSubpath].currentPoint.x - command.parameters[2],
[path currentSubpath].currentPoint.y - command.parameters[3]);
firstControl = NSMakePoint(-1 * (command.parameters[0] + oldPoint.x) + 2 * [path currentSubpath].currentPoint.x,
-1 * (command.parameters[1] + oldPoint.y) + 2 * [path currentSubpath].currentPoint.y);
NSPoint oldPoint = NSMakePoint(path.currentPoint.x - command.parameters[2],
path.currentPoint.y - command.parameters[3]);
firstControl = NSMakePoint(-1 * (command.parameters[0] + oldPoint.x) + 2 * path.currentPoint.x,
-1 * (command.parameters[1] + oldPoint.y) + 2 * path.currentPoint.y);
}
}
}
}
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] curveToPoint:NSMakePoint(params[2], params[3])
controlPoint1:NSMakePoint(firstControl.x, firstControl.y)
controlPoint2:NSMakePoint(params[0], params[1])];
[path.path curveToPoint:NSMakePoint(params[2], params[3])
controlPoint1:NSMakePoint(firstControl.x, firstControl.y)
controlPoint2:NSMakePoint(params[0], params[1])];
return;
}
[[path currentSubpath] curveToPoint:NSMakePoint([path currentSubpath].currentPoint.x + params[2], [path currentSubpath].currentPoint.y + params[3])
controlPoint1:NSMakePoint(firstControl.x, firstControl.y)
controlPoint2:NSMakePoint([path currentSubpath].currentPoint.x + params[0], [path currentSubpath].currentPoint.y + params[1])];
[path.path curveToPoint:NSMakePoint(path.currentPoint.x + params[2], path.currentPoint.y + params[3])
controlPoint1:NSMakePoint(firstControl.x, firstControl.y)
controlPoint2:NSMakePoint(path.currentPoint.x + params[0], path.currentPoint.y + params[1])];
}
@end
@@ -24,33 +24,33 @@
type:(IJSVGCommandType)type
path:(IJSVGPath*)path
{
NSPoint commandPoint = NSMakePoint([path currentSubpath].currentPoint.x, [path currentSubpath].currentPoint.y);
NSPoint commandPoint = NSMakePoint(path.currentPoint.x, path.currentPoint.y);
if (command != nil) {
if (command.class == IJSVGCommandQuadraticCurve.class) {
// quadratic curve
if (command.type == kIJSVGCommandTypeAbsolute) {
commandPoint = NSMakePoint(-1 * command.parameters[0] + 2 * [path currentSubpath].currentPoint.x,
-1 * command.parameters[1] + 2 * [path currentSubpath].currentPoint.y);
commandPoint = NSMakePoint(-1 * command.parameters[0] + 2 * path.currentPoint.x,
-1 * command.parameters[1] + 2 * path.currentPoint.y);
} else {
NSPoint oldPoint = CGPointMake([path currentSubpath].currentPoint.x - command.parameters[2],
[path currentSubpath].currentPoint.y - command.parameters[3]);
commandPoint = CGPointMake(-1 * (command.parameters[0] + oldPoint.x) + 2 * ([path currentSubpath].currentPoint.x),
-1 * (command.parameters[1] + oldPoint.y) + 2 * [path currentSubpath].currentPoint.y);
NSPoint oldPoint = CGPointMake(path.currentPoint.x - command.parameters[2],
path.currentPoint.y - command.parameters[3]);
commandPoint = CGPointMake(-1 * (command.parameters[0] + oldPoint.x) + 2 * (path.currentPoint.x),
-1 * (command.parameters[1] + oldPoint.y) + 2 * path.currentPoint.y);
}
} else if (command.class == self.class) {
// smooth quadratic curve
commandPoint = CGPointMake(-1 * (path.lastControlPoint.x) + 2 * ([path currentSubpath].currentPoint.x),
-1 * (path.lastControlPoint.y) + 2 * [path currentSubpath].currentPoint.y);
commandPoint = CGPointMake(-1 * (path.lastControlPoint.x) + 2 * (path.currentPoint.x),
-1 * (path.lastControlPoint.y) + 2 * path.currentPoint.y);
}
}
path.lastControlPoint = commandPoint;
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] addQuadCurveToPoint:NSMakePoint(params[0], params[1])
controlPoint:commandPoint];
[path.path addQuadCurveToPoint:NSMakePoint(params[0], params[1])
controlPoint:commandPoint];
return;
}
[[path currentSubpath] addQuadCurveToPoint:NSMakePoint([path currentSubpath].currentPoint.x + params[0], [path currentSubpath].currentPoint.y + params[1])
controlPoint:commandPoint];
[path.path addQuadCurveToPoint:NSMakePoint(path.currentPoint.x + params[0], path.currentPoint.y + params[1])
controlPoint:commandPoint];
}
@end
@@ -23,10 +23,10 @@
path:(IJSVGPath*)path
{
if (type == kIJSVGCommandTypeAbsolute) {
[[path currentSubpath] lineToPoint:NSMakePoint([path currentSubpath].currentPoint.x, params[0])];
[path.path lineToPoint:NSMakePoint(path.currentPoint.x, params[0])];
return;
}
[[path currentSubpath] relativeLineToPoint:NSMakePoint(0.f, params[0])];
[path.path relativeLineToPoint:NSMakePoint(0.f, params[0])];
}
@end
+5 -12
View File
@@ -45,6 +45,7 @@
IJSVGLayer* _layerTree;
CGRect _viewBox;
CGSize _proposedViewSize;
CGFloat _backingScaleFactor;
CGFloat _lastProposedBackingScale;
IJSVGRenderQuality _lastProposedRenderQuality;
CGFloat _backingScale;
@@ -86,9 +87,6 @@
+ (id)svgNamed:(NSString*)string;
+ (id)svgNamed:(NSString*)string
delegate:(id<IJSVGDelegate>)delegate;
+ (id)svgNamed:(NSString*)string
useCache:(BOOL)useCache
delegate:(id<IJSVGDelegate>)delegate;
- (id)initWithImage:(NSImage*)image;
@@ -103,8 +101,6 @@
- (id)initWithSVGString:(NSString*)string
error:(NSError**)error;
- (id)initWithFile:(NSString*)file
useCache:(BOOL)useCache;
- (id)initWithFile:(NSString*)file;
- (id)initWithFile:(NSString*)file
error:(NSError**)error;
@@ -113,19 +109,12 @@
- (id)initWithFile:(NSString*)file
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate;
- (id)initWithFile:(NSString*)file
useCache:(BOOL)useCache
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate;
- (id)initWithFilePathURL:(NSURL*)aURL;
- (id)initWithFilePathURL:(NSURL*)aURL
useCache:(BOOL)useCache;
- (id)initWithFilePathURL:(NSURL*)aURL
error:(NSError**)error;
- (id)initWithFilePathURL:(NSURL*)aURL
delegate:(id<IJSVGDelegate>)delegate;
- (id)initWithFilePathURL:(NSURL*)aURL
useCache:(BOOL)useCache
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate;
- (NSImage*)imageWithSize:(NSSize)aSize;
@@ -136,6 +125,10 @@
- (NSImage*)imageByMaintainingAspectRatioWithSize:(NSSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error;
- (CGImageRef)newCGImageRefWithSize:(CGSize)size
flipped:(BOOL)flipped
error:(NSError**)error;
- (BOOL)drawAtPoint:(NSPoint)point
size:(NSSize)size;
- (BOOL)drawAtPoint:(NSPoint)point
+168 -145
View File
@@ -7,7 +7,6 @@
//
#import "IJSVG.h"
#import "IJSVGCache.h"
#import "IJSVGExporter.h"
#import "IJSVGTransaction.h"
@@ -20,51 +19,41 @@
- (void)dealloc
{
IJSVGBeginTransactionLock();
// this can all be called on the background thread to be released
(void)([renderingBackingScaleHelper release]),
renderingBackingScaleHelper = nil;
(void)([_group release]), _group = nil;
(void)([_layerTree release]), _layerTree = nil;
(void)([_replacementColors release]), _replacementColors = nil;
(void)([_style release]), _style = nil;
IJSVGEndTransactionLock();
(void)([_group release]), _group = nil;
// kill any memory that has been around
(void)([_layerTree release]), _layerTree = nil;
[super dealloc];
}
+ (id)svgNamed:(NSString*)string error:(NSError**)error
{
return [self.class svgNamed:string error:error delegate:nil];
}
+ (id)svgNamed:(NSString*)string
{
return [self.class svgNamed:string error:nil];
}
+ (id)svgNamed:(NSString*)string
useCache:(BOOL)useCache
delegate:(id<IJSVGDelegate>)delegate
{
return [self.class svgNamed:string
useCache:useCache
error:nil
delegate:delegate];
}
+ (id)svgNamed:(NSString*)string delegate:(id<IJSVGDelegate>)delegate
{
return [self.class svgNamed:string error:nil delegate:delegate];
}
+ (id)svgNamed:(NSString*)string
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate
{
return [self svgNamed:string useCache:YES error:error delegate:delegate];
return [self.class svgNamed:string
error:error
delegate:nil];
}
+ (id)svgNamed:(NSString*)string
{
return [self.class svgNamed:string
error:nil];
}
+ (id)svgNamed:(NSString*)string
delegate:(id<IJSVGDelegate>)delegate
{
return [self.class svgNamed:string
error:nil
delegate:delegate];
}
+ (id)svgNamed:(NSString*)string
useCache:(BOOL)useCache
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate
{
@@ -78,7 +67,6 @@
ofType:ext])
!= nil) {
return [[[self alloc] initWithFile:str
useCache:useCache
error:error
delegate:delegate] autorelease];
}
@@ -90,20 +78,19 @@
__block IJSVGGroupLayer* layer = nil;
__block IJSVGImageLayer* imageLayer = nil;
// make sure we obtain a lock, with whatever we do with layers!
IJSVGBeginTransactionLock();
// create the layers we require
layer = [[[IJSVGGroupLayer alloc] init] autorelease];
imageLayer =
[[[IJSVGImageLayer alloc] initWithImage:image] autorelease];
[layer addSublayer:imageLayer];
IJSVGEndTransactionLock();
// return the initialized SVG
return [self initWithSVGLayer:layer viewBox:imageLayer.frame];
return [self initWithSVGLayer:layer
viewBox:imageLayer.frame];
}
- (id)initWithSVGLayer:(IJSVGGroupLayer*)group viewBox:(NSRect)viewBox
- (id)initWithSVGLayer:(IJSVGGroupLayer*)group
viewBox:(NSRect)viewBox
{
// this completely bypasses passing of files
if ((self = [super init]) != nil) {
@@ -119,33 +106,9 @@
- (id)initWithFile:(NSString*)file
{
return [self initWithFile:file delegate:nil];
}
- (id)initWithFile:(NSString*)file useCache:(BOOL)useCache
{
return [self initWithFile:file useCache:useCache error:nil delegate:nil];
}
- (id)initWithFile:(NSString*)file
useCache:(BOOL)useCache
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate
{
return [self initWithFilePathURL:[NSURL fileURLWithPath:file]
useCache:useCache
error:error
delegate:delegate];
}
- (id)initWithFile:(NSString*)file error:(NSError**)error
{
return [self initWithFile:file error:error delegate:nil];
}
- (id)initWithFile:(NSString*)file delegate:(id<IJSVGDelegate>)delegate
{
return [self initWithFile:file error:nil delegate:delegate];
return [self initWithFile:file
error:nil
delegate:nil];
}
- (id)initWithFile:(NSString*)file
@@ -153,57 +116,53 @@
delegate:(id<IJSVGDelegate>)delegate
{
return [self initWithFilePathURL:[NSURL fileURLWithPath:file]
useCache:YES
error:error
delegate:delegate];
}
- (id)initWithFile:(NSString*)file
error:(NSError**)error
{
return [self initWithFile:file
error:error
delegate:nil];
}
- (id)initWithFile:(NSString*)file
delegate:(id<IJSVGDelegate>)delegate
{
return [self initWithFile:file
error:nil
delegate:delegate];
}
- (id)initWithFilePathURL:(NSURL*)aURL
{
return [self initWithFilePathURL:aURL useCache:YES error:nil delegate:nil];
}
- (id)initWithFilePathURL:(NSURL*)aURL error:(NSError**)error
{
return [self initWithFilePathURL:aURL
useCache:YES
error:error
delegate:nil];
}
- (id)initWithFilePathURL:(NSURL*)aURL useCache:(BOOL)useCache
{
return [self initWithFilePathURL:aURL
useCache:useCache
error:nil
delegate:nil];
}
- (id)initWithFilePathURL:(NSURL*)aURL delegate:(id<IJSVGDelegate>)delegate
- (id)initWithFilePathURL:(NSURL*)aURL
error:(NSError**)error
{
return [self initWithFilePathURL:aURL
error:error
delegate:nil];
}
- (id)initWithFilePathURL:(NSURL*)aURL
delegate:(id<IJSVGDelegate>)delegate
{
return [self initWithFilePathURL:aURL
useCache:YES
error:nil
delegate:delegate];
}
- (id)initWithFilePathURL:(NSURL*)aURL
useCache:(BOOL)useCache
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate
{
#ifndef __clang_analyzer__
// check the cache first
if (useCache && [IJSVGCache enabled]) {
IJSVG* svg = nil;
if ((svg = [IJSVGCache cachedSVGForFileURL:aURL]) != nil) {
// have to release, as this was called from an alloc..!
[self release];
return [svg retain];
}
}
// create the object
if ((self = [super init]) != nil) {
NSError* anError = nil;
@@ -214,8 +173,9 @@
[self _checkDelegate];
// create the group
_group = [[IJSVGParser groupForFileURL:aURL error:&anError delegate:self]
retain];
_group = [[IJSVGParser groupForFileURL:aURL
error:&anError
delegate:self] retain];
[self _setupBasicInfoFromGroup];
[self _setupBasicsFromAnyInitializer];
@@ -228,24 +188,23 @@
(void)([self release]), self = nil;
return nil;
}
// cache the file
if (useCache && [IJSVGCache enabled]) {
[IJSVGCache cacheSVG:self fileURL:aURL];
}
}
#endif
return self;
}
- (id)initWithSVGString:(NSString*)string
{
return [self initWithSVGString:string error:nil delegate:nil];
return [self initWithSVGString:string
error:nil
delegate:nil];
}
- (id)initWithSVGString:(NSString*)string error:(NSError**)error
- (id)initWithSVGString:(NSString*)string
error:(NSError**)error
{
return [self initWithSVGString:string error:error delegate:nil];
return [self initWithSVGString:string
error:error
delegate:nil];
}
- (id)initWithSVGString:(NSString*)string
@@ -360,17 +319,25 @@
- (NSImage*)imageWithSize:(NSSize)aSize
{
return [self imageWithSize:aSize flipped:NO error:nil];
return [self imageWithSize:aSize
flipped:NO
error:nil];
}
- (NSImage*)imageWithSize:(NSSize)aSize error:(NSError**)error;
- (NSImage*)imageWithSize:(NSSize)aSize
error:(NSError**)error;
{
return [self imageWithSize:aSize flipped:NO error:error];
return [self imageWithSize:aSize
flipped:NO
error:error];
}
- (NSImage*)imageWithSize:(NSSize)aSize flipped:(BOOL)flipped
- (NSImage*)imageWithSize:(NSSize)aSize
flipped:(BOOL)flipped
{
return [self imageWithSize:aSize flipped:flipped error:nil];
return [self imageWithSize:aSize
flipped:flipped
error:nil];
}
- (NSRect)computeOriginalDrawingFrameWithSize:(NSSize)aSize
@@ -380,22 +347,63 @@
_proposedViewSize.height * _clipScale);
}
- (CGImageRef)newCGImageRefWithSize:(CGSize)size
flipped:(BOOL)flipped
error:(NSError**)error
{
// setup the drawing rect, this is used for both the intial drawing
// and the backing scale helper block
NSRect rect = (CGRect){
.origin = CGPointZero,
.size = (CGSize)size
};
// this is highly important this is setup
[self _beginDraw:rect];
// make sure we setup the scale based on the backing scale factor
CGFloat scale = [self backindScaleFactor:NULL];
// create the context and colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ref = CGBitmapContextCreate(NULL, (int)size.width * scale,
(int)size.height * scale, 8, 0, colorSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
// scale the context
CGContextScaleCTM(ref, scale, scale);
if (flipped == YES) {
CGContextTranslateCTM(ref, 0.f, size.height);
CGContextScaleCTM(ref, 1.f, -1.f);
}
// draw the SVG into the context
[self _drawInRect:rect
context:ref
error:error];
// create the image from the context
CGImageRef imageRef = CGBitmapContextCreateImage(ref);
// release all things!
CGColorSpaceRelease(colorSpace);
CGContextRelease(ref);
return imageRef;
}
- (NSImage*)imageWithSize:(NSSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error
{
NSImage* im = [[[NSImage alloc] initWithSize:aSize] autorelease];
[im lockFocus];
CGContextRef ref = [[NSGraphicsContext currentContext] CGContext];
CGContextSaveGState(ref);
if (flipped) {
CGContextTranslateCTM(ref, 0.f, aSize.height);
CGContextScaleCTM(ref, 1.f, -1.f);
}
[self drawAtPoint:NSMakePoint(0.f, 0.f) size:aSize error:error];
CGContextRestoreGState(ref);
[im unlockFocus];
return im;
CGImageRef ref = [self newCGImageRefWithSize:aSize
flipped:flipped
error:error];
NSImage* image = [[NSImage alloc] initWithCGImage:ref
size:aSize];
CGImageRelease(ref);
return image.autorelease;
}
- (NSImage*)imageByMaintainingAspectRatioWithSize:(NSSize)aSize
@@ -423,7 +431,8 @@
return [self PDFDataWithRect:rect error:nil];
}
- (NSData*)PDFDataWithRect:(NSRect)rect error:(NSError**)error
- (NSData*)PDFDataWithRect:(NSRect)rect
error:(NSError**)error
{
// create the data for the PDF
NSMutableData* data = [[[NSMutableData alloc] init] autorelease];
@@ -505,15 +514,21 @@
};
}
- (BOOL)drawAtPoint:(NSPoint)point size:(NSSize)aSize
- (BOOL)drawAtPoint:(NSPoint)point
size:(NSSize)aSize
{
return [self drawAtPoint:point size:aSize error:nil];
return [self drawAtPoint:point
size:aSize
error:nil];
}
- (BOOL)drawAtPoint:(NSPoint)point size:(NSSize)aSize error:(NSError**)error
- (BOOL)drawAtPoint:(NSPoint)point
size:(NSSize)aSize
error:(NSError**)error
{
return
[self drawInRect:NSMakeRect(point.x, point.y, aSize.width, aSize.height)
[self drawInRect:NSMakeRect(point.x, point.y,
aSize.width, aSize.height)
error:error];
}
@@ -522,7 +537,8 @@
return [self drawInRect:rect error:nil];
}
- (BOOL)drawInRect:(NSRect)rect error:(NSError**)error
- (BOOL)drawInRect:(NSRect)rect
error:(NSError**)error
{
return [self _drawInRect:rect
context:[[NSGraphicsContext currentContext] CGContext]
@@ -535,7 +551,8 @@
return (CGFloat)(_scale + actualScale);
}
- (NSRect)computeRectDrawingInRect:(NSRect)rect isValid:(BOOL*)valid
- (NSRect)computeRectDrawingInRect:(NSRect)rect
isValid:(BOOL*)valid
{
// we also need to calculate the viewport so we can clip
// the drawing if needed
@@ -556,7 +573,8 @@
return viewPort;
}
- (void)drawInRect:(NSRect)rect context:(CGContextRef)context
- (void)drawInRect:(NSRect)rect
context:(CGContextRef)context
{
[self _drawInRect:rect context:context error:nil];
}
@@ -595,13 +613,10 @@
CGContextTranslateCTM(ref, viewPort.origin.x, viewPort.origin.y);
CGContextScaleCTM(ref, _scale, _scale);
// render the layer, its really important we lock
// the transaction when drawing
IJSVGBeginTransactionLock();
// do we need to update the backing scales on the
// layers?
if (self.renderingBackingScaleHelper != nil) {
[self _askHelperForBackingScale];
[self backindScaleFactor:nil];
}
CGInterpolationQuality quality;
@@ -620,7 +635,6 @@
}
CGContextSetInterpolationQuality(ref, quality);
[self.layer renderInContext:ref];
IJSVGEndTransactionLock();
}
} @catch (NSException* exception) {
// just catch and give back a drawing error to the caller
@@ -634,12 +648,14 @@
return (error == nil);
}
- (void)_askHelperForBackingScale
- (CGFloat)backindScaleFactor:(CGFloat* _Nullable)proposedBackingScale
{
CGFloat scale = (self.renderingBackingScaleHelper)();
__block CGFloat scale = 1.f;
scale = (self.renderingBackingScaleHelper)();
if (scale < 1.f) {
scale = 1.f;
}
_backingScaleFactor = scale;
// make sure we multiple the scale by the scale of the rendered clip
// or it will be blurry for gradients and other bitmap drawing
@@ -648,12 +664,15 @@
// dont do anything, nothing has changed, no point of iterating over
// every layer for no reason!
if (scale == _lastProposedBackingScale && renderQuality == _lastProposedRenderQuality) {
return;
return _backingScaleFactor;
}
IJSVGRenderQuality quality = self.renderQuality;
_lastProposedBackingScale = scale;
_lastProposedRenderQuality = quality;
if (proposedBackingScale != nil && proposedBackingScale != NULL) {
*proposedBackingScale = scale;
}
// walk the tree
void (^block)(CALayer* layer, BOOL isMask) = ^void(CALayer* layer, BOOL isMask) {
@@ -666,6 +685,7 @@
// gogogo
[IJSVGLayer recursivelyWalkLayer:self.layer withBlock:block];
return _backingScaleFactor;
}
- (IJSVGLayer*)layerWithTree:(IJSVGLayerTree*)tree
@@ -676,9 +696,7 @@
}
// force rebuild of the tree
IJSVGBeginTransactionLock();
_layerTree = [[tree layerForNode:_group] retain];
IJSVGEndTransactionLock();
return _layerTree;
}
@@ -813,7 +831,9 @@
withSVGString:(NSString*)string
{
if (_delegate != nil && _respondsTo.shouldHandleSubSVG == 1) {
[_delegate svg:self foundSubSVG:subSVG withSVGString:string];
[_delegate svg:self
foundSubSVG:subSVG
withSVGString:string];
}
}
@@ -821,7 +841,8 @@
shouldHandleForeignObject:(IJSVGForeignObject*)foreignObject
{
if (_delegate != nil && _respondsTo.shouldHandleForeignObject == 1) {
return [_delegate svg:self shouldHandleForeignObject:foreignObject];
return [_delegate svg:self
shouldHandleForeignObject:foreignObject];
}
return NO;
}
@@ -831,7 +852,9 @@
document:(NSXMLDocument*)document
{
if (_delegate != nil && _respondsTo.handleForeignObject == 1) {
[_delegate svg:self handleForeignObject:foreignObject document:document];
[_delegate svg:self
handleForeignObject:foreignObject
document:document];
}
}
@@ -1,24 +0,0 @@
//
// IJSVGCache.h
// IconJar
//
// Created by Curtis Hard on 02/09/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import "IJSVG.h"
#import <Foundation/Foundation.h>
#import <sys/stat.h>
@interface IJSVGCache : NSObject {
}
+ (IJSVG*)cachedSVGForFileURL:(NSURL*)aURL;
+ (void)cacheSVG:(IJSVG*)svg fileURL:(NSURL*)aURL;
+ (void)flushCache;
+ (BOOL)enabled;
+ (void)setEnabled:(BOOL)flag;
+ (void)purgeCachedSVGForFileURL:(NSURL*)aURL;
+ (void)setEvictItemsAfter:(NSInteger)count;
@end
@@ -1,77 +0,0 @@
//
// IJSVGCache.m
// IconJar
//
// Created by Curtis Hard on 02/09/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import "IJSVGCache.h"
#import <malloc/malloc.h>
@implementation IJSVGCache
static NSInteger _maxCacheItems = 20;
static NSCache* _cache = nil;
static BOOL _enabled = YES;
+ (void)load
{
[self setEnabled:_enabled];
}
+ (void)setEvictItemsAfter:(NSInteger)count
{
_maxCacheItems = count;
[_cache setTotalCostLimit:_maxCacheItems];
}
+ (IJSVG*)cachedSVGForFileURL:(NSURL*)aURL
{
if (![self.class enabled] || _cache == nil)
return nil;
IJSVG* svg = nil;
if ((svg = [_cache objectForKey:aURL]) == nil)
return nil;
return svg;
}
+ (void)purgeCachedSVGForFileURL:(NSURL*)aURL
{
[_cache removeObjectForKey:aURL];
}
+ (void)cacheSVG:(IJSVG*)svg
fileURL:(NSURL*)aURL
{
[_cache setObject:svg
forKey:aURL
cost:1];
}
+ (void)setEnabled:(BOOL)flag
{
_enabled = flag;
if (!flag) {
[self.class flushCache];
return;
}
// create a new cache if allowed
if (_cache == nil) {
_cache = [[NSCache alloc] init];
[_cache setTotalCostLimit:_maxCacheItems];
}
}
+ (BOOL)enabled
{
return _enabled;
}
+ (void)flushCache
{
[_cache removeAllObjects];
}
@end
@@ -104,7 +104,7 @@
+ (IJSVG*)convertIJSVGPathToSVG:(IJSVGPath*)path
{
CGPathRef cgPath = [IJSVGUtils newCGPathFromBezierPath:path.path];
CGPathRef cgPath = [path.path newCGPathRef:NO];
CGPathRef flippedPath = [IJSVGUtils newFlippedCGPath:cgPath];
IJSVG* svg = [self convertPathToSVG:flippedPath];
CGPathRelease(flippedPath);
@@ -115,7 +115,6 @@
+ (IJSVG*)convertPathToSVG:(CGPathRef)path
{
__block IJSVG* svg = nil;
IJSVGBeginTransactionLock();
IJSVGGroupLayer* layer = [[[IJSVGGroupLayer alloc] init] autorelease];
IJSVGShapeLayer* shape = [[[IJSVGShapeLayer alloc] init] autorelease];
[layer addSublayer:shape];
@@ -123,7 +122,6 @@
CGRect box = CGPathGetPathBoundingBox(path);
svg = [[IJSVG alloc] initWithSVGLayer:layer
viewBox:box];
IJSVGEndTransactionLock();
return [svg autorelease];
}
@@ -32,11 +32,13 @@ typedef NS_OPTIONS(NSInteger, IJSVGExporterOptions) {
IJSVGExporterOptionRemoveWidthHeightAttributes = 1 << 13,
IJSVGExporterOptionColorAllowRRGGBBAA = 1 << 14,
IJSVGExporterOptionRemoveComments = 1 << 15,
IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef | IJSVGExporterOptionRemoveUselessGroups | IJSVGExporterOptionCreateUseForPaths | IJSVGExporterOptionMoveAttributesToGroup | IJSVGExporterOptionSortAttributes | IJSVGExporterOptionCollapseGroups | IJSVGExporterOptionCleanupPaths | IJSVGExporterOptionRemoveHiddenElements | IJSVGExporterOptionScaleToSizeIfNecessary | IJSVGExporterOptionCompressOutput | IJSVGExporterOptionCollapseGradients | IJSVGExporterOptionRemoveWidthHeightAttributes | IJSVGExporterOptionColorAllowRRGGBBAA | IJSVGExporterOptionRemoveComments
IJSVGExporterOptionCenterWithinViewBox = 1 << 16,
IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef | IJSVGExporterOptionRemoveUselessGroups | IJSVGExporterOptionCreateUseForPaths | IJSVGExporterOptionMoveAttributesToGroup | IJSVGExporterOptionSortAttributes | IJSVGExporterOptionCollapseGroups | IJSVGExporterOptionCleanupPaths | IJSVGExporterOptionRemoveHiddenElements | IJSVGExporterOptionScaleToSizeIfNecessary | IJSVGExporterOptionCompressOutput | IJSVGExporterOptionCollapseGradients | IJSVGExporterOptionRemoveWidthHeightAttributes | IJSVGExporterOptionColorAllowRRGGBBAA | IJSVGExporterOptionRemoveComments | IJSVGExporterOptionCenterWithinViewBox
};
BOOL IJSVGExporterHasOption(IJSVGExporterOptions options, NSInteger option);
void IJSVGEnumerateCGPathElements(CGPathRef path, IJSVGPathElementEnumerationBlock enumBlock);
const NSArray* IJSVGShortCharacterArray(void);
@interface IJSVGExporter : NSObject {
@@ -46,7 +48,6 @@ void IJSVGEnumerateCGPathElements(CGPathRef path, IJSVGPathElementEnumerationBlo
IJSVGExporterOptions _options;
NSXMLDocument* _dom;
NSXMLElement* _defElement;
NSXMLElement* _scaledRootNode;
NSInteger _idCount;
NSInteger _shortIdCount;
}
@@ -36,7 +36,7 @@ BOOL IJSVGExporterHasOption(IJSVGExporterOptions options, NSInteger option)
return (options & option) != 0;
};
const NSArray* IJSVGShortCharacterArray()
const NSArray* IJSVGShortCharacterArray(void)
{
static NSArray* _array;
static dispatch_once_t onceToken;
@@ -130,7 +130,6 @@ NSString* IJSVGHash(NSString* key)
- (void)dealloc
{
(void)([_scaledRootNode release]), _scaledRootNode = nil;
(void)([_svg release]), _svg = nil;
(void)([_dom release]), _dom = nil;
(void)([_defElement release]), _defElement = nil;
@@ -175,7 +174,7 @@ NSString* IJSVGHash(NSString* key)
return viewBox;
}
- (NSXMLElement*)rootNode
- (NSXMLElement*)rootNode:(NSXMLElement**)nestedRoot
{
// generates the root document
NSXMLElement* root = [[[NSXMLElement alloc] initWithName:@"svg"] autorelease];
@@ -190,7 +189,7 @@ NSString* IJSVGHash(NSString* key)
};
// add on width and height unless specified otherwise
if ((_options & IJSVGExporterOptionRemoveWidthHeightAttributes) == 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionRemoveWidthHeightAttributes) == NO) {
NSMutableDictionary* attDict = [[attributes mutableCopy] autorelease];
attDict[@"width"] = IJSVGShortFloatString(_size.width);
attDict[@"height"] = IJSVGShortFloatString(_size.height);
@@ -198,39 +197,44 @@ NSString* IJSVGHash(NSString* key)
}
// was there a size set?
CGFloat scale = 1.f;
NSMutableArray<IJSVGTransform*>* transforms = [[[NSMutableArray alloc] initWithCapacity:2] autorelease];
if (CGSizeEqualToSize(CGSizeZero, _size) == NO && (_size.width != viewBox.size.width && _size.height != viewBox.size.height)) {
// copy the attributes
NSMutableDictionary* att = [[attributes mutableCopy] autorelease];
att[@"width"] = IJSVGShortFloatString(_size.width);
att[@"height"] = IJSVGShortFloatString(_size.height);
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionRemoveWidthHeightAttributes) == NO) {
att[@"width"] = IJSVGShortFloatString(_size.width);
att[@"height"] = IJSVGShortFloatString(_size.height);
}
// scale the whole SVG to fit the specified size
if ((_options & IJSVGExporterOptionScaleToSizeIfNecessary) != 0) {
// work out the scale
CGFloat scale = MIN(_size.width / viewBox.size.width,
_size.height / viewBox.size.height);
// work out the scale
const CGFloat _proposedScale = MIN(_size.width / viewBox.size.width,
_size.height / viewBox.size.height);
// actually do the scale
if (scale != 1.f) {
NSString* scaleString = [NSString stringWithFormat:@"scale(%g)", scale];
NSDictionary* transform = @{ @"transform" : scaleString };
// actually do the scale
if (_proposedScale != 1.f) {
// compute x and y, don't multiply 0
const CGFloat x = viewBox.origin.x == 0.f ? 0.f : (viewBox.origin.x * _proposedScale);
const CGFloat y = viewBox.origin.y == 0.f ? 0.f : (viewBox.origin.y * _proposedScale);
// create the main group and apply transform
_scaledRootNode = [[NSXMLElement alloc] initWithName:@"g"];
IJSVGApplyAttributesToElement(transform, _scaledRootNode);
// reset the viewbox for the exported SVG
NSRect newViewBox = (NSRect){
.origin = NSMakePoint(x, y),
.size = NSMakeSize(_size.width,
_size.height)
};
att[@"viewBox"] = [self viewBoxWithRect:newViewBox];
// add it back onto root
[root addChild:_scaledRootNode];
// do we need to scale?
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionScaleToSizeIfNecessary) == YES) {
IJSVGTransform* transform = nil;
transform = [IJSVGTransform transformByScaleX:_proposedScale
y:_proposedScale];
[transforms addObject:transform];
// compute x and y, dont multiply 0
const CGFloat x = viewBox.origin.x == 0.f ? 0.f : (viewBox.origin.x * scale);
const CGFloat y = viewBox.origin.y == 0.f ? 0.f : (viewBox.origin.y * scale);
// reset the viewbox for the exported SVG
att[@"viewBox"] = [self viewBoxWithRect:(NSRect){
.origin = NSMakePoint(x, y),
.size = NSMakeSize(_size.width, _size.height) }];
// reset the scale
scale = _proposedScale;
}
}
@@ -238,8 +242,39 @@ NSString* IJSVGHash(NSString* key)
attributes = [[att copy] autorelease];
}
// do we need to center the svg within the box?
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCenterWithinViewBox) == YES) {
CGPoint transformPoint = CGPointMake(_size.width / 2.f - ((viewBox.size.width * scale) / 2.f),
_size.height / 2.f - ((viewBox.size.height * scale) / 2.f));
// work out what transform point we need to do, if any
if (CGPointEqualToPoint(transformPoint, CGPointZero) == NO) {
IJSVGTransform* transform = nil;
transform = [IJSVGTransform transformByTranslatingX:transformPoint.x
y:transformPoint.y];
[transforms addObject:transform];
}
}
// any transform for the root node?
if (transforms.count != 0) {
// concat the transform
CGAffineTransform afTransform = IJSVGConcatTransforms(transforms);
NSXMLElement* transformedElement = [[[NSXMLElement alloc] initWithName:@"g"] autorelease];
NSString* transString = nil;
transString = IJSVGTransformAttributeString(afTransform);
IJSVGApplyAttributesToElement(
@{ @"transform" : transString },
transformedElement);
*nestedRoot = transformedElement;
[root addChild:transformedElement];
}
// apply the attributes
IJSVGApplyAttributesToElement(attributes, root);
if (*nestedRoot == nil) {
*nestedRoot = root;
}
return root;
}
@@ -259,15 +294,15 @@ NSString* IJSVGHash(NSString* key)
- (void)_prepare
{
// create the stand alone DOM
_dom = [[NSXMLDocument alloc] initWithRootElement:[self rootNode]];
NSXMLElement* nestedRoot = nil;
NSXMLElement* rootNode = [self rootNode:&nestedRoot];
_dom = [[NSXMLDocument alloc] initWithRootElement:rootNode];
_dom.version = XML_DOCTYPE_VERSION;
_dom.characterEncoding = XML_DOC_CHARSET;
// sort out header
// sort out stuff, so here we go...
[self _recursiveParseFromLayer:_svg.layer
intoElement:(_scaledRootNode ?: _dom.rootElement)];
intoElement:nestedRoot];
// this needs to be added incase it needs to be cleaned
NSXMLElement* defNode = [self defElement];
@@ -286,7 +321,7 @@ NSString* IJSVGHash(NSString* key)
}
// add generator
if ((_options & IJSVGExporterOptionRemoveComments) == 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionRemoveComments) == NO) {
NSXMLNode* generatorNode = [[[NSXMLNode alloc] initWithKind:NSXMLCommentKind] autorelease];
generatorNode.stringValue = XML_DOC_GENERATOR;
[_dom.rootElement insertChild:generatorNode
@@ -297,52 +332,52 @@ NSString* IJSVGHash(NSString* key)
- (void)_cleanup
{
// remove hidden elements
if ((_options & IJSVGExporterOptionRemoveHiddenElements) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionRemoveHiddenElements) == YES) {
[self _removeHiddenElements];
}
// convert any duplicate paths into use
if ((_options & IJSVGExporterOptionCreateUseForPaths) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCreateUseForPaths) == YES) {
[self _convertUseElements];
}
// cleanup def
if ((_options & IJSVGExporterOptionRemoveUselessDef) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionRemoveUselessDef) == YES) {
[self _cleanDef];
}
// collapse groups
if ((_options & IJSVGExporterOptionCollapseGroups) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCollapseGroups) == YES) {
[self _collapseGroups];
}
// clean any blank groups
if ((_options & IJSVGExporterOptionRemoveUselessGroups) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionRemoveUselessGroups) == YES) {
[self _cleanEmptyGroups];
}
// sort attributes
if ((_options & IJSVGExporterOptionSortAttributes) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionSortAttributes) == YES) {
[self _sortAttributesOnElement:_dom.rootElement];
}
// compress groups together
if ((_options & IJSVGExporterOptionCollapseGroups) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCollapseGroups) == YES) {
[self _compressGroups];
}
// collapse gradients?
if ((_options & IJSVGExporterOptionCollapseGradients) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCollapseGradients) == YES) {
[self _collapseGradients];
}
// create classes?
if ((_options & IJSVGExporterOptionCreateClasses) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCreateClasses) == YES) {
[self _createClasses];
}
// move attributes to group
if ((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionMoveAttributesToGroup) == YES) {
[self _moveAttributesToGroupWithElement:_dom.rootElement];
}
}
@@ -793,7 +828,7 @@ NSString* IJSVGHash(NSString* key)
}
// append the string
NSString* transformStr = [IJSVGTransform affineTransformToSVGMatrixString:transform];
NSString* transformStr = IJSVGTransformAttributeString(transform);
// apply it to the node
IJSVGApplyAttributesToElement(@{ @"transform" : transformStr }, element);
@@ -961,7 +996,9 @@ NSString* IJSVGHash(NSString* key)
IJSVGColorStringOptions options = IJSVGColorStringOptionForceHEX | IJSVGColorStringOptionAllowShortHand;
NSString* stopColor = [IJSVGColor colorStringFromColor:aColor
options:options];
if ([stopColor isEqualToString:@"#000000"] == NO) {
// dont bother adding default
if ([stopColor isEqualToString:@"#000"] == NO) {
atts[@"stop-color"] = stopColor;
}
@@ -975,7 +1012,6 @@ NSString* IJSVGHash(NSString* key)
}
// att the attributes
IJSVGApplyAttributesToElement(atts, stop);
// append the stop the gradient
@@ -989,7 +1025,7 @@ NSString* IJSVGHash(NSString* key)
NSArray* transforms = layer.gradient.transforms;
if (transforms.count != 0.f) {
CGAffineTransform transform = IJSVGConcatTransforms(transforms);
NSString* transformString = [IJSVGTransform affineTransformToSVGMatrixString:transform];
NSString* transformString = IJSVGTransformAttributeString(transform);
IJSVGApplyAttributesToElement(@{ @"gradientTransform" : transformString }, gradientElement);
}
@@ -1006,15 +1042,6 @@ NSString* IJSVGHash(NSString* key)
}
}
- (CGAffineTransform)affineTransformFromTransforms:(NSArray<IJSVGTransform*>*)transforms
{
CGAffineTransform t = CGAffineTransformIdentity;
for (IJSVGTransform* transform in transforms) {
t = CGAffineTransformConcat(t, [transform CGAffineTransform]);
}
return t;
}
- (NSXMLElement*)elementForImage:(IJSVGImageLayer*)layer
fromParent:(NSXMLElement*)parent
{
@@ -1049,7 +1076,7 @@ NSString* IJSVGHash(NSString* key)
- (IJSVGColorStringOptions)colorOptions
{
IJSVGColorStringOptions options = IJSVGColorStringOptionDefault;
if ((_options & IJSVGExporterOptionColorAllowRRGGBBAA) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionColorAllowRRGGBBAA) == YES) {
options |= IJSVGColorStringOptionAllowRRGGBBAA;
}
return options;
@@ -1058,19 +1085,19 @@ NSString* IJSVGHash(NSString* key)
- (NSString*)elementNameForPrimitiveType:(IJSVGPrimitivePathType)primitiveType
{
switch (primitiveType) {
case IJSVGPrimitivePathTypeRect:
case kIJSVGPrimitivePathTypeRect:
return @"rect";
case IJSVGPrimitivePathTypePolyLine:
case kIJSVGPrimitivePathTypePolyLine:
return @"polyline";
case IJSVGPrimitivePathTypeEllipse:
case kIJSVGPrimitivePathTypeEllipse:
return @"ellipse";
case IJSVGPrimitivePathTypeCircle:
case kIJSVGPrimitivePathTypeCircle:
return @"circle";
case IJSVGPrimitivePathTypeLine:
case kIJSVGPrimitivePathTypeLine:
return @"line";
case IJSVGPrimitivePathTypePolygon:
case kIJSVGPrimitivePathTypePolygon:
return @"polygon";
case IJSVGPrimitivePathTypePath:
case kIJSVGPrimitivePathTypePath:
default:
return @"path";
}
@@ -1092,7 +1119,7 @@ NSString* IJSVGHash(NSString* key)
// path
switch (layer.primitiveType) {
case IJSVGPrimitivePathTypeRect: {
case kIJSVGPrimitivePathTypeRect: {
__block BOOL radiusSet = NO;
IJSVGEnumerateCGPathElements(transformPath, ^(const CGPathElement* pathElement, CGPoint currentPoint) {
if (radiusSet == NO && pathElement->type == kCGPathElementAddCurveToPoint) {
@@ -1116,7 +1143,7 @@ NSString* IJSVGHash(NSString* key)
dict[@"height"] = IJSVGShortFloatString(boundingBox.size.height);
break;
}
case IJSVGPrimitivePathTypeLine: {
case kIJSVGPrimitivePathTypeLine: {
IJSVGEnumerateCGPathElements(transformPath, ^(const CGPathElement* pathElement, CGPoint currentPoint) {
switch (pathElement->type) {
case kCGPathElementMoveToPoint: {
@@ -1135,8 +1162,8 @@ NSString* IJSVGHash(NSString* key)
});
break;
}
case IJSVGPrimitivePathTypePolygon:
case IJSVGPrimitivePathTypePolyLine: {
case kIJSVGPrimitivePathTypePolygon:
case kIJSVGPrimitivePathTypePolyLine: {
NSMutableArray<NSString*>* points = [[[NSMutableArray alloc] init] autorelease];
IJSVGEnumerateCGPathElements(transformPath, ^(const CGPathElement* pathElement, CGPoint currentPoint) {
switch (pathElement->type) {
@@ -1153,13 +1180,13 @@ NSString* IJSVGHash(NSString* key)
}
});
// polygon does not need the move to command
if (layer.primitiveType == IJSVGPrimitivePathTypePolygon) {
if (layer.primitiveType == kIJSVGPrimitivePathTypePolygon) {
[points removeLastObject];
}
dict[@"points"] = [points componentsJoinedByString:@" "];
break;
}
case IJSVGPrimitivePathTypeEllipse: {
case kIJSVGPrimitivePathTypeEllipse: {
CGRect boundingBox = CGPathGetPathBoundingBox(transformPath);
dict[@"cx"] = IJSVGShortFloatString(boundingBox.origin.x + boundingBox.size.width / 2.f);
dict[@"cy"] = IJSVGShortFloatString(boundingBox.origin.y + boundingBox.size.height / 2.f);
@@ -1167,7 +1194,7 @@ NSString* IJSVGHash(NSString* key)
dict[@"ry"] = IJSVGShortFloatString(boundingBox.size.height / 2.f);
break;
}
case IJSVGPrimitivePathTypeCircle: {
case kIJSVGPrimitivePathTypeCircle: {
// IJSVGCGPathHandler callback = ^(const CGPathElement * pathElement) {
CGRect boundingBox = CGPathGetPathBoundingBox(transformPath);
dict[@"cx"] = IJSVGShortFloatString(boundingBox.origin.x + boundingBox.size.width / 2.f);
@@ -1175,7 +1202,7 @@ NSString* IJSVGHash(NSString* key)
dict[@"r"] = IJSVGShortFloatString(boundingBox.size.width / 2.f);
break;
}
case IJSVGPrimitivePathTypePath:
case kIJSVGPrimitivePathTypePath:
default:
dict[@"d"] = [self pathFromCGPath:transformPath];
}
@@ -1385,7 +1412,7 @@ NSString* IJSVGHash(NSString* key)
- (NSString*)SVGString
{
NSXMLNodeOptions options = NSXMLNodePrettyPrint;
if ((_options & IJSVGExporterOptionCompressOutput) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCompressOutput) == YES) {
options = NSXMLNodeOptionsNone;
}
return [_dom XMLStringWithOptions:options];
@@ -1404,7 +1431,7 @@ NSString* IJSVGHash(NSString* key)
NSArray* instructions = [IJSVGExporterPathInstruction instructionsFromPath:path];
// work out what to do...
if ((_options & IJSVGExporterOptionCleanupPaths) != 0) {
if (IJSVGExporterHasOption(_options, IJSVGExporterOptionCleanupPaths) == YES) {
[IJSVGExporterPathInstruction convertInstructionsToRelativeCoordinates:instructions];
}
return [IJSVGExporterPathInstruction pathStringFromInstructions:instructions];
@@ -8,6 +8,13 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef struct {
char instruction;
NSArray<NSString*>* params;
} IJSVGExporterPathInstructionCommand;
@interface IJSVGExporterPathInstruction : NSObject {
@private
@@ -16,6 +23,9 @@
CGFloat* _data;
}
IJSVGExporterPathInstructionCommand* IJSVGExporterPathInstructionCommandCopy(IJSVGExporterPathInstructionCommand command);
void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand* _Nullable command);
+ (NSArray<IJSVGExporterPathInstruction*>*)instructionsFromPath:(CGPathRef)path;
- (id)initWithInstruction:(char)instruction
@@ -28,5 +38,7 @@
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions;
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions;
+ (NSString*)pathStringWithInstructionSet:(NSArray<NSValue*>*)instructionSets;
@end
NS_ASSUME_NONNULL_END
@@ -8,6 +8,7 @@
#import "IJSVGExporter.h"
#import "IJSVGExporterPathInstruction.h"
#import "IJSVGUtils.h"
@implementation IJSVGExporterPathInstruction
@@ -53,87 +54,140 @@
return _data;
}
IJSVGExporterPathInstructionCommand* IJSVGExporterPathInstructionCommandCopy(IJSVGExporterPathInstructionCommand command)
{
IJSVGExporterPathInstructionCommand* copy = NULL;
copy = (IJSVGExporterPathInstructionCommand*)malloc(sizeof(IJSVGExporterPathInstructionCommand));
copy->instruction = command.instruction;
copy->params = command.params;
return copy;
}
void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand* _Nullable command)
{
if (command != NULL) {
free(command);
}
}
+ (NSString*)pathStringWithInstructionSet:(NSArray<NSValue*>*)instructionSets
{
IJSVGExporterPathInstructionCommand* lastCommand = NULL;
NSMutableString* string = [[[NSMutableString alloc] init] autorelease];
char* lastCommandChars = NULL;
for (NSValue* value in instructionSets) {
// read back the bytes
IJSVGExporterPathInstructionCommand command;
[value getValue:&command];
// add on the instruction character only if there is no current command
// or the last command is not the same as the current command
// if they both are the same, we still need to seperate them via a space
if (lastCommand == nil || (lastCommand != nil && lastCommand->instruction != command.instruction)) {
[string appendFormat:@"%c", command.instruction];
} else {
[string appendString:@" "];
}
NSInteger index = 0;
for (NSString* dataString in command.params) {
const char* chars = dataString.UTF8String;
// work out if the command is signed and or decimal
BOOL isSigned = chars[0] == '-';
BOOL isDecimal = (isSigned == NO && chars[0] == '.') || (isSigned == YES && chars[1] == '.');
// we also need to know if the previous command was a decimal or not
BOOL lastWasDecimal = NO;
if (lastCommandChars != NULL) {
lastWasDecimal = strchr(lastCommandChars, '.') != NULL;
}
// we only need a space if the current command is not signed
// a decimal and the previous command was decimal too
if (index++ == 0 || isSigned || (isDecimal == YES && lastWasDecimal == YES)) {
[string appendString:dataString];
} else {
[string appendFormat:@" %@", dataString];
}
// store last command chars
lastCommandChars = (char*)chars;
}
// store last command
IJSVGExporterPathInstructionCommandFree(lastCommand);
lastCommand = IJSVGExporterPathInstructionCommandCopy(command);
}
IJSVGExporterPathInstructionCommandFree(lastCommand);
return string;
}
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions
{
NSMutableArray* pathData = [[[NSMutableArray alloc] init] autorelease];
NSMutableArray* pathInstructions = [[[NSMutableArray alloc] init] autorelease];
for (IJSVGExporterPathInstruction* instruction in instructions) {
CGFloat* data = instruction.data;
NSString* str = nil;
switch (instruction.instruction) {
// move
case 'M':
case 'm': {
char* buffer;
asprintf(&buffer, "%c%g,%g", instruction.instruction, data[0], data[1]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// vertical and horizonal line
case 'V':
case 'v':
case 'H':
case 'h': {
char* buffer;
asprintf(&buffer, "%c%g", instruction.instruction, data[0]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// line
case 'L':
const char lowerInstruction = tolower(instruction.instruction);
NSArray<NSString*>* set = nil;
switch (lowerInstruction) {
case 'm':
case 'l': {
char* buffer;
asprintf(&buffer, "%c%g,%g", instruction.instruction, data[0], data[1]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
set = @[
IJSVGShortFloatString(data[0]),
IJSVGShortFloatString(data[1])
];
break;
}
case 'v':
case 'h': {
set = @[
IJSVGShortFloatString(data[0])
];
break;
}
// curve
case 'C':
case 'c': {
char* buffer;
asprintf(&buffer, "%c%g,%g %g,%g %g,%g", instruction.instruction,
data[0], data[1], data[2], data[3], data[4], data[5]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
set = @[
IJSVGShortFloatString(data[0]),
IJSVGShortFloatString(data[1]),
IJSVGShortFloatString(data[2]),
IJSVGShortFloatString(data[3]),
IJSVGShortFloatString(data[4]),
IJSVGShortFloatString(data[5])
];
break;
}
// quadratic curve
case 'Q':
case 'q': {
char* buffer;
asprintf(&buffer, "%c%g,%g %g,%g", instruction.instruction,
data[0], data[1], data[2], data[3]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
set = @[
IJSVGShortFloatString(data[0]),
IJSVGShortFloatString(data[1]),
IJSVGShortFloatString(data[2]),
IJSVGShortFloatString(data[3])
];
break;
}
// close path
case 'Z':
case 'z': {
str = [NSString stringWithFormat:@"%c", instruction.instruction];
[pathData addObject:str];
set = @[];
}
}
// wrap into the command and give to the array
IJSVGExporterPathInstructionCommand wrapper;
wrapper.instruction = instruction.instruction;
wrapper.params = set ?: @[];
// encode and store
NSValue* value = [NSValue valueWithBytes:&wrapper
objCType:@encode(IJSVGExporterPathInstructionCommand)];
[pathInstructions addObject:value];
}
return [pathData componentsJoinedByString:@""];
return [self pathStringWithInstructionSet:pathInstructions];
}
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
@@ -362,7 +416,7 @@
}
}
}
return instructions;
}
@@ -25,6 +25,7 @@
- (void)dealloc
{
self.contents = nil;
(void)([_maskingLayer release]), _maskingLayer = nil;
[super dealloc];
}
@@ -26,6 +26,7 @@
- (void)dealloc
{
self.contents = nil;
(void)([_maskingLayer release]), _maskingLayer = nil;
[super dealloc];
}
@@ -9,12 +9,13 @@
#import "IJSVGNode.h"
#import <Foundation/Foundation.h>
@interface IJSVGDef : IJSVGNode {
@interface IJSVGDef : NSObject {
@private
NSMutableDictionary* _dict;
}
- (void)addDef:(IJSVGNode*)aDef;
- (IJSVGDef*)defForID:(NSString*)anID;
@end
@@ -18,7 +18,7 @@
- (id)init
{
if ((self = [super initWithDef:NO]) != nil) {
if ((self = [super init]) != nil) {
_dict = [[NSMutableDictionary alloc] init];
}
return self;
@@ -29,13 +29,12 @@
if (aDef.identifier == nil) {
return;
}
[_dict setObject:aDef
forKey:aDef.identifier];
_dict[aDef.identifier] = aDef;
}
- (IJSVGDef*)defForID:(NSString*)anID
{
return [_dict objectForKey:anID];
return _dict[anID];
}
@end
+8 -13
View File
@@ -13,32 +13,27 @@
@class IJSVGGroup;
typedef NS_ENUM(NSInteger, IJSVGPrimitivePathType) {
IJSVGPrimitivePathTypePath,
IJSVGPrimitivePathTypeRect,
IJSVGPrimitivePathTypePolygon,
IJSVGPrimitivePathTypePolyLine,
IJSVGPrimitivePathTypeCircle,
IJSVGPrimitivePathTypeEllipse,
IJSVGPrimitivePathTypeLine
kIJSVGPrimitivePathTypePath,
kIJSVGPrimitivePathTypeRect,
kIJSVGPrimitivePathTypePolygon,
kIJSVGPrimitivePathTypePolyLine,
kIJSVGPrimitivePathTypeCircle,
kIJSVGPrimitivePathTypeEllipse,
kIJSVGPrimitivePathTypeLine
};
@interface IJSVGPath : IJSVGNode {
NSBezierPath* path;
NSBezierPath* subpath;
CGPoint lastControlPoint;
}
@property (nonatomic, assign) IJSVGPrimitivePathType primitiveType;
@property (nonatomic, readonly) NSBezierPath* path;
@property (nonatomic, readonly) NSBezierPath* subpath;
@property (nonatomic, retain) NSBezierPath* path;
@property (nonatomic, assign) CGPoint lastControlPoint;
@property (nonatomic, readonly) CGPathRef CGPath;
- (NSBezierPath*)currentSubpath;
- (void)close;
- (NSPoint)currentPoint;
- (void)overwritePath:(NSBezierPath*)aPath;
- (CGPathRef)newPathRefByAutoClosingPath:(BOOL)autoClose;
@end
+18 -87
View File
@@ -11,30 +11,26 @@
@implementation IJSVGPath
@synthesize path;
@synthesize subpath;
@synthesize path = _path;
@synthesize lastControlPoint;
@synthesize CGPath = _CGPath;
@synthesize primitiveType = _primitiveType;
- (void)dealloc
{
if(_CGPath != nil) {
if (_CGPath != nil) {
CGPathRelease(_CGPath);
_CGPath = nil;
}
if (subpath != nil) {
(void)([subpath release]), subpath = nil;
}
((void)[_path release]), _path = nil;
[super dealloc];
}
- (id)init
{
if ((self = [super init]) != nil) {
_primitiveType = IJSVGPrimitivePathTypePath;
subpath = NSBezierPath.bezierPath.retain;
path = subpath; // for legacy use
_primitiveType = kIJSVGPrimitivePathTypePath;
_path = NSBezierPath.bezierPath.retain;
}
return self;
}
@@ -42,99 +38,34 @@
- (id)copyWithZone:(NSZone*)zone
{
IJSVGPath* node = [super copyWithZone:zone];
[node overwritePath:self.path];
node.path = [self.path.copy autorelease];
return node;
}
- (NSPoint)currentPoint
{
return [subpath currentPoint];
}
- (NSBezierPath*)currentSubpath
{
return subpath;
return _path.currentPoint;
}
- (void)close
{
[subpath closePath];
[_path closePath];
}
- (void)invlidateCGPath
{
if (_CGPath != nil) {
CGPathRelease(_CGPath);
}
_CGPath = nil;
}
- (CGPathRef)CGPath
{
if(_CGPath == nil) {
_CGPath = [self newPathRefByAutoClosingPath:NO];
if (_CGPath == nil) {
_CGPath = [_path newCGPathRef:NO];
}
return _CGPath;
}
- (void)overwritePath:(NSBezierPath*)aPath
{
(void)([subpath release]), subpath = nil;
subpath = [aPath retain];
path = subpath;
}
- (CGPathRef)newPathRefByAutoClosingPath:(BOOL)autoClose
{
NSInteger i = 0;
NSInteger numElements = self.path.elementCount;
NSBezierPath* bezPath = self.path;
// nothing to return
if (numElements == 0) {
return NULL;
}
CGMutablePathRef aPath = CGPathCreateMutable();
NSPoint points[3];
BOOL didClosePath = YES;
for (i = 0; i < numElements; i++) {
switch ([bezPath elementAtIndex:i associatedPoints:points]) {
// move
case NSMoveToBezierPathElement: {
CGPathMoveToPoint(aPath, NULL, points[0].x, points[0].y);
break;
}
// line
case NSLineToBezierPathElement: {
CGPathAddLineToPoint(aPath, NULL, points[0].x, points[0].y);
didClosePath = NO;
break;
}
// curve
case NSCurveToBezierPathElement: {
CGPathAddCurveToPoint(aPath, NULL, points[0].x, points[0].y,
points[1].x, points[1].y,
points[2].x, points[2].y);
didClosePath = NO;
break;
}
// close
case NSClosePathBezierPathElement: {
CGPathCloseSubpath(aPath);
didClosePath = YES;
break;
}
}
}
if (!didClosePath && autoClose) {
CGPathCloseSubpath(aPath);
}
// create immutable and release
CGPathRef pathToReturn = CGPathCreateCopy(aPath);
CGPathRelease(aPath);
return pathToReturn;
}
@end
@@ -0,0 +1,42 @@
//
// IJSVGCommandParser.h
// IJSVG
//
// Created by Curtis Hard on 23/12/2019.
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <xlocale.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, IJSVGPathDataSequence) {
kIJSVGPathDataSequenceTypeFloat,
kIJSVGPathDataSequenceTypeFlag
};
static NSUInteger const IJSVG_STREAM_FLOAT_BLOCK_SIZE = 50;
static NSUInteger const IJSVG_STREAM_CHAR_BLOCK_SIZE = 20;
typedef struct {
CGFloat* floatBuffer;
NSInteger floatCount;
char* charBuffer;
NSInteger charCount;
} IJSVGPathDataStream;
@interface IJSVGCommandParser : NSObject
IJSVGPathDataStream* IJSVGPathDataStreamCreateDefault(void);
IJSVGPathDataStream* IJSVGPathDataStreamCreate(NSUInteger floatCount, NSUInteger charCount);
void IJSVGPathDataStreamRelease(IJSVGPathDataStream* buffer);
IJSVGPathDataSequence* IJSVGPathDataSequenceCreateWithType(IJSVGPathDataSequence type, NSInteger length);
CGFloat* _Nullable IJSVGParsePathDataStreamSequence(const char* commandChars, NSInteger commandCharLength,
IJSVGPathDataStream* dataStream, IJSVGPathDataSequence* _Nullable sequence,
NSInteger commandLength, NSInteger* _Nullable commandsFound);
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,249 @@
//
// IJSVGCommandParser.m
// IJSVG
//
// Created by Curtis Hard on 23/12/2019.
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import "IJSVGCommandParser.h"
@implementation IJSVGCommandParser
#define VALID_DIGIT(c) ((c ^ '0') <= 9)
IJSVGPathDataSequence* IJSVGPathDataSequenceCreateWithType(IJSVGPathDataSequence type, NSInteger length)
{
size_t size = sizeof(IJSVGPathDataSequence) * length;
IJSVGPathDataSequence* sequence = (IJSVGPathDataSequence*)malloc(size);
memset(sequence, (int)type, size);
return sequence;
};
// Datastreams work by setting up one stream of bits/memory per SVG
// so that each SVG has a reusable memory block to read and parse paths into.
// As its all linear and one SVG per thread, this saves alot of memory allocation
// calls as we simple can just reuse the buffer that already exists - this also
// allows us to specify the default allocation size, so when parsing viewBox we
// can simply allocate (4*sizeof(CGFloat)) instead of the default 50 slots
IJSVGPathDataStream* IJSVGPathDataStreamCreateDefault(void)
{
return IJSVGPathDataStreamCreate(IJSVG_STREAM_FLOAT_BLOCK_SIZE,
IJSVG_STREAM_CHAR_BLOCK_SIZE);
}
IJSVGPathDataStream* IJSVGPathDataStreamCreate(NSUInteger floatCount, NSUInteger charCount)
{
floatCount = floatCount ?: IJSVG_STREAM_FLOAT_BLOCK_SIZE;
charCount = charCount ?: IJSVG_STREAM_CHAR_BLOCK_SIZE;
IJSVGPathDataStream* buffer = (IJSVGPathDataStream*)malloc(sizeof(IJSVGPathDataStream));
buffer->floatBuffer = (CGFloat*)malloc(sizeof(CGFloat) * floatCount);
buffer->floatCount = floatCount;
buffer->charBuffer = (char*)calloc(sizeof(char), charCount);
buffer->charCount = charCount;
return buffer;
}
void IJSVGPathDataStreamRelease(IJSVGPathDataStream* buffer)
{
free(buffer->charBuffer);
free(buffer->floatBuffer);
free(buffer);
};
CGFloat* _Nullable IJSVGParsePathDataStreamSequence(const char* commandChars, NSInteger commandCharLength,
IJSVGPathDataStream* dataStream, IJSVGPathDataSequence* _Nullable sequence,
NSInteger commandLength, NSInteger* _Nullable commandsFound)
{
// if no command length, its completely pointless function,
// so just return null and set commandsFound to 0, if we dont
// we get a arithmetic error later on due to zero
if (commandLength == 0) {
*commandsFound = 0;
return NULL;
}
// default memory size for the float
NSInteger i = 0;
NSInteger counter = 0;
const char* cString = commandChars;
const char* validChars = "+-.";
// this is much faster then doing strlen as it doesnt need
// to compute the length
NSInteger sLength = commandCharLength;
NSInteger sLengthMinusOne = sLength - 1;
bool isDecimal = false;
int bufferCount = 0;
while (i < sLength) {
char currentChar = *cString++;
// work out next char
char nextChar = (char)0;
if (i < sLengthMinusOne) {
nextChar = *cString++;
cString--;
}
// check for validator
bool isE = (currentChar | ('E' ^ 'e')) == 'e';
bool isValid = VALID_DIGIT(currentChar) || isE || strchr(validChars, currentChar) != NULL;
// in order to work out the split, its either because the next char is
// a hyphen or a plus, or next char is a decimal and the current number is a decimal
bool nIsSign = nextChar == '-' || nextChar == '+';
bool wantsEnd = nIsSign || (nextChar == '.' && isDecimal);
// work our what the sequence is...
IJSVGPathDataSequence seq = kIJSVGPathDataSequenceTypeFloat;
if (sequence != NULL) {
seq = sequence[counter % commandLength];
}
// is a flag, consists of one value
// if its invalid, make sure we free the memory
// and return null - or hell breaks lose
if (isValid == YES && seq == kIJSVGPathDataSequenceTypeFlag) {
if (bufferCount != 0 || (currentChar != '0' && currentChar != '1')) {
return NULL;
}
wantsEnd = YES;
}
// could be a float like 5.334e-5 so dont break on the hypen
if (wantsEnd && isE && nIsSign) {
wantsEnd = false;
}
// make sure its a valid string
if (isValid == YES) {
// alloc the buffer if needed
if ((bufferCount + 1) == dataStream->charCount) {
// realloc the buffer, incase the string is overflowing the
// allocated memory
dataStream->charCount += IJSVG_STREAM_CHAR_BLOCK_SIZE;
dataStream->charBuffer = (char*)realloc(dataStream->charBuffer,
sizeof(char) * dataStream->charCount);
}
// set the actual char against it
if (currentChar == '.') {
isDecimal = true;
}
dataStream->charBuffer[bufferCount++] = currentChar;
} else {
// if its an invalid char, just stop it
wantsEnd = true;
}
// is at end of string, or wants to be stopped
// buffer has to actually exist or its completly
// useless and will cause a crash
if (bufferCount != 0 && (wantsEnd || i == sLengthMinusOne)) {
// make sure there is enough room in the float pool
if ((counter + 1) == dataStream->floatCount) {
dataStream->floatCount += IJSVG_STREAM_FLOAT_BLOCK_SIZE;
dataStream->floatBuffer = (CGFloat*)realloc(dataStream->floatBuffer,
sizeof(CGFloat) * dataStream->floatCount);
}
// add the float - for performance reasons, we can simply set the
// null value of the end of the string instead of nulling out
// with memset \0 - huzzah!
dataStream->charBuffer[bufferCount] = '\0';
dataStream->floatBuffer[counter++] = IJSVGParseFloat(dataStream->charBuffer);
// reset
isDecimal = false;
bufferCount = 0;
}
i++;
}
// set commands found - only if there is one
if (commandsFound != NULL) {
*commandsFound = (NSInteger)round(counter / commandLength);
}
// allocate the new buffer from memory
CGFloat* floats = (CGFloat*)malloc(sizeof(CGFloat) * counter);
memcpy(floats, dataStream->floatBuffer, counter * sizeof(CGFloat));
// return the floats just set into the memory
return floats;
}
// this method is finely tuned to just handle the buffer
// that IJSVGParsePathDataSequence produces for each float
// it does not look or skip white space as the previous method
// handles this for us
// inspired and modified from http://www.leapsecond.com/tools/fast_atof.c
CGFloat IJSVGParseFloat(char* buffer)
{
int fraction;
double sign, value, scale;
// work out a sign, if any, might not be, who knows
sign = 1.f;
if (*buffer == '-') {
sign = -1.f;
buffer += 1;
} else if (*buffer == '+') {
buffer += 1;
}
// get numbers before decimal point or exponent
for (value = 0.f; VALID_DIGIT(*buffer); buffer += 1) {
value = value * 10.f + (*buffer - '0');
}
// get digits after decimal point
if (*buffer == '.') {
double pow10 = 10.f;
buffer += 1;
while (VALID_DIGIT(*buffer)) {
value += (*buffer - '0') / pow10;
pow10 *= 10.f;
buffer += 1;
}
}
// handle exponent
fraction = 0;
scale = 1.f;
if ((*buffer | ('E' ^ 'e')) == 'e') {
unsigned int exponent;
buffer += 1;
if (*buffer == '-') {
fraction = 1;
buffer += 1;
} else if (*buffer == '+') {
buffer += 1;
}
for (exponent = 0; VALID_DIGIT(*buffer); buffer += 1) {
exponent = exponent * 10 + (*buffer - '0');
}
if (exponent > 308) {
exponent = 308;
}
while (exponent >= 50) {
scale *= 1E50;
exponent -= 50;
}
while (exponent >= 8) {
scale *= 1E8;
exponent -= 8;
}
while (exponent > 0) {
scale *= 10.f;
exponent -= 1;
}
}
// make sure we cast this to a CGFloat before return
return (CGFloat)(sign * (fraction ? (value / scale) : (value * scale)));
}
@end
@@ -96,13 +96,14 @@ static NSString const* IJSVGAttributePoints = @"points";
NSMutableDictionary* _defNodes;
NSMutableDictionary* _baseDefNodes;
NSMutableArray<IJSVG*>* _svgs;
NSMutableArray* _definedGroups;
struct {
unsigned int shouldHandleForeignObject : 1;
unsigned int handleForeignObject : 1;
unsigned int handleSubSVG : 1;
} _respondsTo;
IJSVGPathDataStream* _commandDataStream;
}
@property (nonatomic, readonly) NSRect viewBox;
@@ -45,8 +45,10 @@
(void)([_parsedNodes release]), _parsedNodes = nil;
(void)([_defNodes release]), _defNodes = nil;
(void)([_baseDefNodes release]), _baseDefNodes = nil;
(void)([_definedGroups release]), _definedGroups = nil;
(void)([_svgs release]), _svgs = nil;
if (_commandDataStream != NULL) {
(void)IJSVGPathDataStreamRelease(_commandDataStream), _commandDataStream = nil;
}
[super dealloc];
}
@@ -61,6 +63,7 @@
_respondsTo.shouldHandleForeignObject = [_delegate respondsToSelector:@selector(svgParser:shouldHandleForeignObject:)];
_respondsTo.handleSubSVG = [_delegate respondsToSelector:@selector(svgParser:foundSubSVG:withSVGString:)];
_commandDataStream = IJSVGPathDataStreamCreateDefault();
_glyphs = [[NSMutableArray alloc] init];
_parsedNodes = [[NSMutableArray alloc] init];
_defNodes = [[NSMutableDictionary alloc] init];
@@ -240,6 +243,7 @@
(void)([_styleSheet release]), _styleSheet = nil;
(void)([_parsedNodes release]), _parsedNodes = nil;
(void)([_defNodes release]), _defNodes = nil;
(void)IJSVGPathDataStreamRelease(_commandDataStream), _commandDataStream = NULL;
}
- (void)_postParseElementForCommonAttributes:(NSXMLElement*)element
@@ -466,10 +470,6 @@
[self _parseBaseBlock:parseElement
intoGroup:group
def:NO];
if (_definedGroups == nil) {
_definedGroups = [[NSMutableArray alloc] init];
}
[_definedGroups addObject:group];
return [group defForID:anID];
}
return nil;
@@ -1108,43 +1108,39 @@
intoPath:(IJSVGPath*)path
{
// invalid command
if (command == nil || command.length == 0) {
return;
}
NSUInteger len = [command length];
// allocate memory for the string buffer for reading
const char* buffer = [command cStringUsingEncoding:NSUTF8StringEncoding];
int defaultBufferSize = 200;
int currentBufferSize = 0;
int currentSize = defaultBufferSize;
unichar* commandBuffer = NULL;
if (len != 0) {
commandBuffer = (unichar*)calloc(defaultBufferSize, sizeof(unichar));
}
NSUInteger len = command.length;
NSUInteger lastIndex = len - 1;
const char* buffer = command.UTF8String;
// make sure we plus 1 for the null byte
char* charBuffer = (char*)malloc(sizeof(char) * (len + 1));
NSInteger start = 0;
IJSVGCommand* _currentCommand = nil;
for (int i = 0; i < len; i++) {
unichar currentChar = buffer[i];
unichar nextChar = buffer[i + 1];
BOOL atEnd = i == len - 1;
for (NSInteger i = 0; i < len; i++) {
char nextChar = buffer[i + 1];
BOOL atEnd = i == lastIndex;
BOOL isStartCommand = IJSVGIsLegalCommandCharacter(nextChar);
if ((currentBufferSize + 1) == currentSize) {
currentSize += defaultBufferSize;
commandBuffer = (unichar*)realloc(commandBuffer, sizeof(unichar) * currentSize);
}
commandBuffer[currentBufferSize++] = currentChar;
if (isStartCommand == YES || atEnd == YES) {
NSString* commandString = [NSString stringWithCharacters:commandBuffer
length:currentBufferSize];
// copy memory from current buffer
NSInteger index = ((i + 1) - start);
memcpy(&charBuffer[0], &buffer[start], sizeof(char) * index);
charBuffer[index] = '\0';
// create the command from the substring
NSString* commandString = [NSString stringWithUTF8String:charBuffer];
// reset start position
start = (i + 1);
// previous command is actual subcommand
IJSVGCommand* previousCommand = [_currentCommand subCommands].lastObject;
IJSVGCommand* previousCommand = _currentCommand.subCommands.lastObject;
IJSVGCommand* cCommand = [self _parseCommandString:commandString
previousCommand:previousCommand
intoPath:path];
@@ -1153,16 +1149,9 @@
if (cCommand != nil) {
_currentCommand = cCommand;
}
if (atEnd == NO) {
currentBufferSize = 0;
memset(commandBuffer, '\0', sizeof(unichar) * currentSize);
}
}
}
// free the buffer
free(commandBuffer);
free(charBuffer);
}
- (IJSVGCommand*)_parseCommandString:(NSString*)string
@@ -1180,8 +1169,9 @@
// main commands
// Class commandClass = [IJSVGCommand classFor]
Class commandClass = [IJSVGCommand commandClassForCommandChar:[string characterAtIndex:0]];
IJSVGCommand* command = (IJSVGCommand*)[[[commandClass alloc] initWithCommandString:string] autorelease];
for (IJSVGCommand* subCommand in [command subCommands]) {
IJSVGCommand* command = (IJSVGCommand*)[[[commandClass alloc] initWithCommandString:string
dataStream:_commandDataStream] autorelease];
for (IJSVGCommand* subCommand in command.subCommands) {
[command.class runWithParams:subCommand.parameters
paramCount:subCommand.parameterCount
command:subCommand
@@ -1198,7 +1188,7 @@
{
// convert a line into a command,
// basically MX1 Y1LX2 Y2
path.primitiveType = IJSVGPrimitivePathTypeLine;
path.primitiveType = kIJSVGPrimitivePathTypeLine;
CGFloat x1 = [element attributeForName:(NSString*)IJSVGAttributeX1].stringValue.floatValue;
CGFloat y1 = [element attributeForName:(NSString*)IJSVGAttributeY1].stringValue.floatValue;
CGFloat x2 = [element attributeForName:(NSString*)IJSVGAttributeX2].stringValue.floatValue;
@@ -1216,30 +1206,30 @@
- (void)_parseCircle:(NSXMLElement*)element
intoPath:(IJSVGPath*)path
{
path.primitiveType = IJSVGPrimitivePathTypeCircle;
path.primitiveType = kIJSVGPrimitivePathTypeCircle;
CGFloat cX = [element attributeForName:(NSString*)IJSVGAttributeCX].stringValue.floatValue;
CGFloat cY = [element attributeForName:(NSString*)IJSVGAttributeCY].stringValue.floatValue;
CGFloat r = [element attributeForName:(NSString*)IJSVGAttributeR].stringValue.floatValue;
NSRect rect = NSMakeRect(cX - r, cY - r, r * 2, r * 2);
[path overwritePath:[NSBezierPath bezierPathWithOvalInRect:rect]];
path.path = [NSBezierPath bezierPathWithOvalInRect:rect];
}
- (void)_parseEllipse:(NSXMLElement*)element
intoPath:(IJSVGPath*)path
{
path.primitiveType = IJSVGPrimitivePathTypeEllipse;
path.primitiveType = kIJSVGPrimitivePathTypeEllipse;
CGFloat cX = [element attributeForName:(NSString*)IJSVGAttributeCX].stringValue.floatValue;
CGFloat cY = [element attributeForName:(NSString*)IJSVGAttributeCY].stringValue.floatValue;
CGFloat rX = [element attributeForName:(NSString*)IJSVGAttributeRX].stringValue.floatValue;
CGFloat rY = [element attributeForName:(NSString*)IJSVGAttributeRY].stringValue.floatValue;
NSRect rect = NSMakeRect(cX - rX, cY - rY, rX * 2, rY * 2);
[path overwritePath:[NSBezierPath bezierPathWithOvalInRect:rect]];
path.path = [NSBezierPath bezierPathWithOvalInRect:rect];
}
- (void)_parsePolyline:(NSXMLElement*)element
intoPath:(IJSVGPath*)path
{
path.primitiveType = IJSVGPrimitivePathTypePolyLine;
path.primitiveType = kIJSVGPrimitivePathTypePolyLine;
[self _parsePoly:element
intoPath:path
closePath:NO];
@@ -1248,7 +1238,7 @@
- (void)_parsePolygon:(NSXMLElement*)element
intoPath:(IJSVGPath*)path
{
path.primitiveType = IJSVGPrimitivePathTypePolygon;
path.primitiveType = kIJSVGPrimitivePathTypePolygon;
[self _parsePoly:element
intoPath:path
closePath:YES];
@@ -1290,7 +1280,7 @@
- (void)_parseRect:(NSXMLElement*)element
intoPath:(IJSVGPath*)path
{
path.primitiveType = IJSVGPrimitivePathTypeRect;
path.primitiveType = kIJSVGPrimitivePathTypeRect;
// width and height
CGFloat width = [IJSVGUtils floatValue:[element attributeForName:(NSString*)IJSVGAttributeWidth].stringValue
fallBackForPercent:self.viewBox.size.width];
@@ -1310,11 +1300,9 @@
if ([element attributeForName:(NSString*)IJSVGAttributeRY] == nil) {
rY = rX;
}
NSBezierPath* newPath = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(x, y, width, height)
xRadius:rX
yRadius:rY];
[path overwritePath:newPath];
path.path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(x, y, width, height)
xRadius:rX
yRadius:rY];
}
@end
@@ -743,19 +743,4 @@
}
}
+ (void)log:(IJSVGLayer*)layer
depth:(NSInteger)depth
{
NSLog(@"%@%@: %@, Transforms: %@", [@"" stringByPaddingToLength:depth
withString:@"\t"
startingAtIndex:0],
layer,
NSStringFromRect(layer.frame),
[IJSVGTransform affineTransformToSVGTransformAttributeString:layer.affineTransform]);
for (IJSVGLayer* sublayer in layer.sublayers) {
[self log:(IJSVGLayer*)sublayer
depth:depth++];
}
}
@end
@@ -17,5 +17,6 @@ CGFloat IJSVGMathSin(CGFloat val);
CGFloat IJSVGMathAsin(CGFloat val);
CGFloat IJSVGMathTan(CGFloat val);
CGFloat IJSVGMathAtan(CGFloat val);
CGFloat IJSVGMathToFixed(CGFloat val, NSInteger decimalPlaces);
@end
@@ -20,6 +20,12 @@ CGFloat IJSVGMathDeg(CGFloat val)
return val * 180.f / M_PI;
};
CGFloat IJSVGMathToFixed(CGFloat val, NSInteger decimalPlaces)
{
int p = pow(10, decimalPlaces);
return (CGFloat)floor(p * val) / p;
}
CGFloat IJSVGMathAcos(CGFloat val)
{
return IJSVGMathDeg(acosf(val));
@@ -12,17 +12,11 @@ BOOL IJSVGIsMainThread(void) { return NSThread.isMainThread; };
void IJSVGBeginTransactionLock(void)
{
if (IJSVGIsMainThread()) {
return;
}
[CATransaction begin];
[CATransaction setDisableActions:YES];
};
void IJSVGEndTransactionLock(void)
{
if (IJSVGIsMainThread()) {
return;
}
[CATransaction commit];
};
@@ -6,13 +6,13 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IJSVGUtils.h"
#import <Foundation/Foundation.h>
@class IJSVGTransform;
typedef CGFloat (^IJSVGTransformParameterModifier)(NSInteger index, CGFloat value);
typedef void (^IJSVGTransformApplyBlock)(IJSVGTransform * transform);
typedef void (^IJSVGTransformApplyBlock)(IJSVGTransform* transform);
typedef NS_OPTIONS(NSInteger, IJSVGTransformCommand) {
IJSVGTransformCommandMatrix,
@@ -27,34 +27,33 @@ typedef NS_OPTIONS(NSInteger, IJSVGTransformCommand) {
};
@interface IJSVGTransform : NSObject {
IJSVGTransformCommand command;
CGFloat * parameters;
CGFloat* parameters;
NSInteger parameterCount;
NSInteger sort;
}
@property ( nonatomic, assign ) IJSVGTransformCommand command;
@property ( nonatomic, assign ) CGFloat * parameters;
@property ( nonatomic, assign ) NSInteger parameterCount;
@property ( nonatomic, assign ) NSInteger sort;
@property (nonatomic, assign) IJSVGTransformCommand command;
@property (nonatomic, assign) CGFloat* parameters;
@property (nonatomic, assign) NSInteger parameterCount;
@property (nonatomic, assign) NSInteger sort;
NSString * IJSVGDebugAffineTransform(CGAffineTransform transform);
NSString * IJSVGDebugTransforms(NSArray<IJSVGTransform *> * transforms);
void IJSVGApplyTransform(NSArray<IJSVGTransform *> * transforms, IJSVGTransformApplyBlock block);
CGAffineTransform IJSVGConcatTransforms(NSArray<IJSVGTransform *> * transforms);
void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApplyBlock block);
CGAffineTransform IJSVGConcatTransforms(NSArray<IJSVGTransform*>* transforms);
NSString* IJSVGTransformAttributeString(CGAffineTransform transform);
+ (NSArray<IJSVGTransform *> *)transformsFromAffineTransform:(CGAffineTransform)affineTransform;
+ (NSArray *)transformsForString:(NSString *)string;
+ (NSBezierPath *)transformedPath:(IJSVGPath *)path;
+ (NSArray<NSString *> *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform;
+ (NSString *)affineTransformToSVGMatrixString:(CGAffineTransform)affineTransform;
+ (NSArray<IJSVGTransform*>*)transformsFromAffineTransform:(CGAffineTransform)affineTransform;
+ (NSArray*)transformsForString:(NSString*)string;
+ (NSBezierPath*)transformedPath:(IJSVGPath*)path;
+ (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)affineTransform;
- (CGAffineTransform)CGAffineTransform;
- (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier;
- (CGAffineTransform)stackIdentity:(CGAffineTransform)identity;
- (void)recalculateWithBounds:(CGRect)bounds;
+ (IJSVGTransform *)transformByTranslatingX:(CGFloat)x
y:(CGFloat)y;
+ (IJSVGTransform*)transformByTranslatingX:(CGFloat)x
y:(CGFloat)y;
+ (IJSVGTransform*)transformByScaleX:(CGFloat)x
y:(CGFloat)y;
@end
@@ -33,22 +33,6 @@
return trans;
}
NSString* IJSVGDebugAffineTransform(CGAffineTransform transform)
{
NSMutableArray* strings = [[[NSMutableArray alloc] init] autorelease];
[strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform]];
return [strings componentsJoinedByString:@" "];
}
NSString* IJSVGDebugTransforms(NSArray<IJSVGTransform*>* transforms)
{
NSMutableArray* strings = [[[NSMutableArray alloc] init] autorelease];
IJSVGApplyTransform(transforms, ^(IJSVGTransform* transform) {
[strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform.CGAffineTransform]];
});
return [strings componentsJoinedByString:@" "];
}
CGAffineTransform IJSVGConcatTransforms(NSArray<IJSVGTransform*>* transforms)
{
__block CGAffineTransform trans = CGAffineTransformIdentity;
@@ -58,6 +42,11 @@ CGAffineTransform IJSVGConcatTransforms(NSArray<IJSVGTransform*>* transforms)
return trans;
}
NSString* IJSVGTransformAttributeString(CGAffineTransform transform)
{
return [IJSVGTransform affineTransformToSVGMatrixString:transform];
}
void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApplyBlock block)
{
for (IJSVGTransform* transform in transforms) {
@@ -78,13 +67,27 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
return transform;
}
+ (IJSVGTransform*)transformByScaleX:(CGFloat)x
y:(CGFloat)y
{
IJSVGTransform* transform = [[[self alloc] init] autorelease];
transform.command = IJSVGTransformCommandScale;
transform.parameterCount = 2;
CGFloat* params = (CGFloat*)malloc(sizeof(CGFloat) * 2);
params[0] = x;
params[1] = y;
transform.parameters = params;
return transform;
}
- (void)recalculateWithBounds:(CGRect)bounds
{
CGFloat max = bounds.size.width > bounds.size.height ? bounds.size.width : bounds.size.height;
switch (self.command) {
case IJSVGTransformCommandRotate: {
if (self.parameterCount == 1)
if (self.parameterCount == 1) {
return;
}
self.parameters[1] = self.parameters[1] * max;
self.parameters[2] = self.parameters[2] * max;
}
@@ -172,8 +175,9 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
+ (NSBezierPath*)transformedPath:(IJSVGPath*)path
{
if (path.transforms.count == 0)
if (path.transforms.count == 0) {
return path.path;
}
NSBezierPath* cop = [[path.path copy] autorelease];
for (IJSVGTransform* transform in path.transforms) {
NSAffineTransform* at = NSAffineTransform.transform;
@@ -325,10 +329,10 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
// scale
case IJSVGTransformCommandScale: {
CGFloat p0 = self.parameters[0];
CGFloat p1 = self.parameters[1];
if (self.parameterCount == 1) {
return CGAffineTransformScale(identity, p0, p0);
}
CGFloat p1 = self.parameters[1];
return CGAffineTransformScale(identity, p0, p1);
}
@@ -389,13 +393,14 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
// translate
case IJSVGTransformCommandTranslate: {
CGFloat p0 = self.parameters[0];
if (self.parameterCount == 1) {
return CGAffineTransformMakeTranslation(p0, 0);
}
CGFloat p1 = self.parameters[1];
if (modifier != nil) {
p0 = modifier(0, p0);
p1 = modifier(1, p1);
}
if (self.parameterCount == 1)
return CGAffineTransformMakeTranslation(p0, 0);
return CGAffineTransformMakeTranslation(p0, p1);
}
@@ -420,13 +425,14 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
// scale
case IJSVGTransformCommandScale: {
CGFloat p0 = self.parameters[0];
if (self.parameterCount == 1) {
return CGAffineTransformMakeScale(p0, p0);
}
CGFloat p1 = self.parameters[1];
if (modifier != nil) {
p0 = modifier(0, p0);
p1 = modifier(1, p1);
}
if (self.parameterCount == 1)
return CGAffineTransformMakeScale(p0, p0);
return CGAffineTransformMakeScale(p0, p1);
}
@@ -452,9 +458,9 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
// rotate
case IJSVGTransformCommandRotate: {
if (self.parameterCount == 1)
if (self.parameterCount == 1) {
return CGAffineTransformMakeRotation((self.parameters[0] / 180) * M_PI);
else {
} else {
CGFloat p0 = self.parameters[0];
CGFloat p1 = self.parameters[1];
CGFloat p2 = self.parameters[2];
@@ -483,121 +489,25 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
+ (NSArray<IJSVGTransform*>*)transformsFromAffineTransform:(CGAffineTransform)affineTransform
{
NSArray* strings = [self affineTransformToSVGTransformAttributeString:affineTransform];
return [self transformsForString:[strings componentsJoinedByString:@" "]];
NSString* matrix = [self affineTransformToSVGMatrixString:affineTransform];
return [self transformsForString:matrix];
}
+ (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)transform
{
return [NSString stringWithFormat:@"matrix(%g,%g,%g,%g,%g,%g)",
transform.a, transform.b, transform.c, transform.d,
transform.tx, transform.ty];
}
// this is an Object-C version of the matrixToTransform method from SVGO
+ (NSArray<NSString*>*)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform
{
const CGFloat data[6] = {
affineTransform.a,
affineTransform.b,
affineTransform.c,
affineTransform.d,
affineTransform.tx,
affineTransform.ty
};
CGFloat sx = sqrtf(data[0] * data[0] + data[1] * data[1]);
CGFloat sy = (data[0] * data[3] - data[1] * data[2]) / sx;
CGFloat colSum = data[0] * data[2] + data[1] * data[3];
CGFloat rowSum = data[0] * data[1] + data[2] * data[3];
BOOL scaleBefore = rowSum != 0.f || (sx == sy);
NSMutableArray* trans = [[[NSMutableArray alloc] init] autorelease];
// translate
if (data[4] != 0.f || data[5] != 0.f) {
NSString* str = [NSString stringWithFormat:@"translate(%g, %g)", data[4], data[5]];
[trans addObject:str];
}
// skewX
if (data[1] == 0.f && data[2] != 0.f) {
NSString* str = [NSString stringWithFormat:@"skewX(%g)", IJSVGMathAtan(data[2] / sy)];
[trans addObject:str];
// skewY
} else if (data[1] != 0.f && data[2] == 0.f) {
NSString* str = [NSString stringWithFormat:@"skewY(%g)", IJSVGMathAtan(data[1] / data[0])];
[trans addObject:str];
sx = data[0];
sy = data[3];
} else if (colSum == 0.f || (sx == 1.f && sy == 1.f) || scaleBefore == NO) {
if (scaleBefore == NO) {
sx = (data[0] < 0.f ? -1.f : 1.f) * sqrtf(data[0] * data[0] + data[2] * data[2]);
sy = (data[3] < 0.f ? -1.f : 1.f) * sqrtf(data[1] * data[1] + data[3] * data[3]);
NSString* str = nil;
if (sx == sy) {
str = [NSString stringWithFormat:@"scale(%g)", sx];
} else {
str = [NSString stringWithFormat:@"scale(%g, %g)", sx, sy];
}
[trans addObject:str];
}
// rotate
CGFloat rotate = IJSVGMathAcos(data[0] / sx) * (data[1] * sy < 0.f ? -1.f : 1.f);
NSString* rotateString = nil;
if (rotate != 0.f) {
rotateString = [NSString stringWithFormat:@"rotate(%g)", rotate];
}
// skewX
if (rowSum != 0.f && colSum != 0.f) {
NSString* str = [NSString stringWithFormat:@"skewX(%g)", IJSVGMathAtan(colSum / (sx * sx))];
[trans addObject:str];
}
// rotate around center
if (rotate != 0.f && (data[4] != 0.f || data[5] != 0.f)) {
[trans removeObjectAtIndex:0];
CGFloat cos = data[0] / sx;
CGFloat sin = data[1] / (scaleBefore ? sx : sy);
CGFloat x = data[4] * (scaleBefore ? 1.f : sy);
CGFloat y = data[5] * (scaleBefore ? 1.f : sx);
CGFloat denom = (powf(1.f - cos, 2.f) + powf(sin, 2.f)) * (scaleBefore ? 1.f : sx * sy);
CGFloat r1 = rotate;
CGFloat r2 = ((1.f - cos) * x - sin * y) / denom;
CGFloat r3 = ((1.f - cos) * y + sin * x) / denom;
rotateString = [NSString stringWithFormat:@"rotate(%g, %g, %g)", r1, r2, r3];
}
if (rotateString != nil) {
[trans addObject:rotateString];
}
}
// scale
if ((scaleBefore && (sx != 1.f || sy != 1.f)) || trans.count == 0.f) {
NSString* str = nil;
if (sx == sy) {
str = [NSString stringWithFormat:@"scale(%g)", sx];
} else {
str = [NSString stringWithFormat:@"scale(%g, %g)", sx, sy];
}
[trans addObject:str];
}
return trans;
return [NSString stringWithFormat:@"matrix(%@ %@ %@ %@ %@ %@)",
IJSVGShortFloatString(transform.a),
IJSVGShortFloatString(transform.b),
IJSVGShortFloatString(transform.c),
IJSVGShortFloatString(transform.d),
IJSVGShortFloatString(transform.tx),
IJSVGShortFloatString(transform.ty)];
}
- (NSString*)description
{
return [NSString stringWithFormat:@"%@ %@", [super description],
[self.class affineTransformToSVGTransformAttributeString:self.CGAffineTransform]];
[self.class affineTransformToSVGMatrixString:self.CGAffineTransform]];
}
@end
@@ -8,6 +8,7 @@
#import "IJSVGNode.h"
#import "IJSVGUnitLength.h"
#import "IJSVGUtils.h"
@implementation IJSVGUnitLength
@@ -91,14 +92,15 @@
- (NSString*)stringValue
{
if (self.type == IJSVGUnitLengthTypePercentage) {
return [NSString stringWithFormat:@"%g%%", (self.value * 100.f)];
return [NSString stringWithFormat:@"%@%%", IJSVGShortFloatString(self.value * 100.f)];
}
return [NSString stringWithFormat:@"%g", self.value];
return IJSVGShortFloatString(self.value);
}
- (NSString*)description
{
return [NSString stringWithFormat:@"%f%@", self.value, (self.type == IJSVGUnitLengthTypePercentage ? @"%" : @"")];
return [NSString stringWithFormat:@"%f%@",
self.value, (self.type == IJSVGUnitLengthTypePercentage ? @"%" : @"")];
}
@end
@@ -10,7 +10,8 @@
#import "IJSVGGradientUnitLength.h"
#import "IJSVGStringAdditions.h"
#import <Foundation/Foundation.h>
#include <xlocale.h>
NS_ASSUME_NONNULL_BEGIN
@interface IJSVGUtils : NSObject
@@ -23,17 +24,14 @@ CGFloat degrees_to_radians(CGFloat degrees);
BOOL IJSVGIsCommonHTMLElementName(NSString* str);
NSArray* IJSVGCommonHTMLElementNames(void);
NSString* IJSVGShortenFloatString(NSString* string);
NSString* IJSVGPointToCommandString(CGPoint point);
NSString* IJSVGShortFloatString(CGFloat f);
NSString* IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision);
IJSVGPathDataSequence* IJSVGPathDataSequenceCreateWithType(IJSVGPathDataSequence type, NSInteger length);
CGFloat* IJSVGParsePathDataSequence(NSString* string, IJSVGPathDataSequence* sequence,
NSInteger commandLength, NSInteger* commandsFound);
BOOL IJSVGIsLegalCommandCharacter(unichar aChar);
BOOL IJSVGIsSVGLayer(CALayer* layer);
+ (IJSVGCommandType)typeForCommandString:(NSString*)string;
+ (IJSVGCommandType)typeForCommandChar:(char)commandChar;
+ (CGFloat*)commandParameters:(NSString*)command
count:(NSInteger*)count;
+ (CGFloat*)parseViewBox:(NSString*)string;
@@ -42,7 +40,7 @@ BOOL IJSVGIsSVGLayer(CALayer* layer);
+ (IJSVGLineCapStyle)lineCapStyleForString:(NSString*)string;
+ (IJSVGUnitType)unitTypeForString:(NSString*)string;
+ (IJSVGBlendMode)blendModeForString:(NSString*)string;
+ (NSString*)mixBlendingModeForBlendMode:(IJSVGBlendMode)blendMode;
+ (NSString* _Nullable)mixBlendingModeForBlendMode:(IJSVGBlendMode)blendMode;
+ (NSRange)rangeOfParentheses:(NSString*)string;
+ (void)logParameters:(CGFloat*)param
@@ -50,7 +48,7 @@ BOOL IJSVGIsSVGLayer(CALayer* layer);
+ (CGFloat)floatValue:(NSString*)string;
+ (CGFloat)angleBetweenPointA:(NSPoint)point
pointb:(NSPoint)point;
+ (NSString*)defURL:(NSString*)string;
+ (NSString* _Nullable)defURL:(NSString*)string;
+ (CGFloat)floatValue:(NSString*)string
fallBackForPercent:(CGFloat)viewBox;
+ (CGFloat*)scanFloatsFromString:(NSString*)string
@@ -60,5 +58,5 @@ BOOL IJSVGIsSVGLayer(CALayer* layer);
weight:(CGFloat*)weight;
+ (CGPathRef)newFlippedCGPath:(CGPathRef)path;
+ (CGPathRef)newCGPathFromBezierPath:(NSBezierPath*)bezPath;
@end
NS_ASSUME_NONNULL_END
+35 -200
View File
@@ -160,166 +160,45 @@ NSArray* IJSVGCommonHTMLElementNames(void)
return names;
};
NSString* IJSVGShortenFloatString(NSString* string)
{
const char* chars = string.UTF8String;
if (chars[0] == '-' && chars[1] == '0') {
return [NSString stringWithFormat:@"-%@", [string substringFromIndex:2]];
} else if (chars[0] == '0' && chars[1] == '.') {
return [string substringFromIndex:1];
}
return string;
}
NSString* IJSVGShortFloatString(CGFloat f)
{
return [NSString stringWithFormat:@"%g", f];
return IJSVGShortenFloatString([NSString stringWithFormat:@"%g", f]);
};
NSString* IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision)
{
NSString* format = [NSString stringWithFormat:@"%@.%ld%@", @"%", precision, @"f"];
NSString* ret = [NSString stringWithFormat:format, f];
// can it be reduced even more?
if (ret.floatValue == (float)ret.integerValue) {
ret = [NSString stringWithFormat:@"%ld", ret.integerValue];
}
return ret;
return IJSVGShortenFloatString(ret);
};
IJSVGPathDataSequence* IJSVGPathDataSequenceCreateWithType(IJSVGPathDataSequence type, NSInteger length)
{
size_t size = sizeof(IJSVGPathDataSequence) * length;
IJSVGPathDataSequence* sequence = (IJSVGPathDataSequence*)malloc(size);
memset(sequence, type, size);
return sequence;
};
CGFloat* _Nullable IJSVGParsePathDataSequence(NSString* string, IJSVGPathDataSequence* _Nullable sequence,
NSInteger commandLength, NSInteger* commandsFound)
{
// if no command length, its completely pointless function,
// so just return null and set commandsFound to 0, if we dont
// we get a arithmetic error later on due to zero
if (commandLength == 0) {
*commandsFound = 0;
return NULL;
}
// default sizes and memory
// sizes for the string buffer
const NSInteger defFloatSize = 20;
const NSInteger defSize = 10;
// default memory size for the float
NSInteger size = defSize;
NSInteger floatSize = defFloatSize;
NSInteger i = 0;
NSInteger counter = 0;
const char* cString = string.UTF8String;
const char* validChars = "eE+-.";
// this is much faster then doing strlen as it doesnt need
// to compute the length
NSInteger sLength = string.length;
// buffer for the returned floats
CGFloat* floats = (CGFloat*)malloc(sizeof(CGFloat) * defFloatSize);
char* buffer = NULL;
bool isDecimal = false;
int bufferCount = 0;
while (i < sLength) {
char currentChar = cString[i];
// work out next char
char nextChar = (char)0;
if (i < (sLength - 1)) {
nextChar = cString[i + 1];
}
// check for validator
bool isValid = (currentChar >= '0' && currentChar <= '9') || strchr(validChars, currentChar) != NULL;
// in order to work out the split, its either because the next char is
// a hyphen or a plus, or next char is a decimal and the current number is a decimal
bool isE = currentChar == 'e' || currentChar == 'E';
bool wantsEnd = nextChar == '-' || nextChar == '+' || (nextChar == '.' && isDecimal);
// work our what the sequence is...
IJSVGPathDataSequence seq = kIJSVGPathDataSequenceTypeFloat;
if (sequence != NULL) {
seq = sequence[counter % commandLength];
}
// is a flag, consists of one value
// if its invalid, make sure we free the memory
// and return null - or hell breaks lose
if (isValid == YES && seq == kIJSVGPathDataSequenceTypeFlag) {
if (bufferCount != 0 || (currentChar != '0' && currentChar != '1')) {
if (buffer) {
(void)free(buffer), buffer = nil;
}
(void)free(floats), floats = nil;
return NULL;
}
wantsEnd = YES;
}
// could be a float like 5.334e-5 so dont break on the hypen
if (wantsEnd && isE && (nextChar == '-' || nextChar == '+')) {
wantsEnd = false;
}
// make sure its a valid string
if (isValid) {
// alloc the buffer if needed
if (buffer == NULL) {
buffer = (char*)calloc(sizeof(char), size);
} else if ((bufferCount + 1) == size) {
// realloc the buffer, incase the string is overflowing the
// allocated memory
size += defSize;
buffer = (char*)realloc(buffer, sizeof(char) * size);
}
// set the actual char against it
if (currentChar == '.') {
isDecimal = true;
}
buffer[bufferCount++] = currentChar;
} else {
// if its an invalid char, just stop it
wantsEnd = true;
}
// is at end of string, or wants to be stopped
// buffer has to actually exist or its completly
// useless and will cause a crash
if ((buffer != NULL && bufferCount != 0) && (wantsEnd || i == sLength - 1)) {
// make sure there is enough room in the float pool
if ((counter + 1) == floatSize) {
floatSize += defFloatSize;
floats = (CGFloat*)realloc(floats, sizeof(CGFloat) * floatSize);
}
// add the float
floats[counter++] = strtod_l(buffer, NULL, NULL);
// memory clean and counter resets
memset(buffer, '\0', sizeof(*buffer) * size);
isDecimal = false;
bufferCount = 0;
}
i++;
}
if (buffer != NULL) {
free(buffer);
}
*commandsFound = (NSInteger)round(counter / commandLength);
return floats;
}
NSString* IJSVGPointToCommandString(CGPoint point)
{
return [NSString stringWithFormat:@"%@,%@", IJSVGShortFloatString(point.x), IJSVGShortFloatString(point.y)];
return [NSString stringWithFormat:@"%@,%@",
IJSVGShortFloatString(point.x),
IJSVGShortFloatString(point.y)];
};
BOOL IJSVGIsLegalCommandCharacter(unichar aChar)
{
const char* validChars = "MmZzLlHhVvCcSsQqTtAa";
return strchr(validChars, aChar) != NULL;
if ((aChar | ('M' ^ 'm')) == 'm' || (aChar | ('Z' ^ 'z')) == 'z' || (aChar | ('C' ^ 'c')) == 'c' || (aChar | ('L' ^ 'l')) == 'l' || (aChar | ('S' ^ 's')) == 's' || (aChar | ('Q' ^ 'q')) == 'q' || (aChar | ('H' ^ 'h')) == 'h' || (aChar | ('V' ^ 'v')) == 'v' || (aChar | ('T' ^ 't')) == 't' || (aChar | ('A' ^ 'a')) == 'a') {
return YES;
}
return NO;
}
BOOL IJSVGIsSVGLayer(CALayer* layer)
@@ -354,9 +233,9 @@ CGFloat degrees_to_radians(CGFloat degrees)
return ((degrees) / 180.0 * M_PI);
}
+ (IJSVGCommandType)typeForCommandString:(NSString*)string
+ (IJSVGCommandType)typeForCommandChar:(char)commandChar
{
return isupper([string characterAtIndex:0]) ? kIJSVGCommandTypeAbsolute : kIJSVGCommandTypeRelative;
return isupper(commandChar) ? kIJSVGCommandTypeAbsolute : kIJSVGCommandTypeRelative;
}
+ (NSRange)rangeOfParentheses:(NSString*)string
@@ -375,7 +254,7 @@ CGFloat degrees_to_radians(CGFloat degrees)
return range;
}
+ (NSString*)defURL:(NSString*)string
+ (NSString* _Nullable)defURL:(NSString*)string
{
// insta check for URL
NSCharacterSet* set = NSCharacterSet.whitespaceCharacterSet;
@@ -505,7 +384,7 @@ CGFloat degrees_to_radians(CGFloat degrees)
return IJSVGBlendModeNormal;
}
+ (NSString*)mixBlendingModeForBlendMode:(IJSVGBlendMode)blendMode
+ (NSString* _Nullable)mixBlendingModeForBlendMode:(IJSVGBlendMode)blendMode
{
switch (blendMode) {
case IJSVGBlendModeMultiply: {
@@ -570,14 +449,21 @@ CGFloat degrees_to_radians(CGFloat degrees)
+ (CGFloat*)scanFloatsFromString:(NSString*)string
size:(NSInteger*)length
{
return IJSVGParsePathDataSequence(string, NULL, 1, length);
IJSVGPathDataStream* stream = IJSVGPathDataStreamCreateDefault();
CGFloat* floats = IJSVGParsePathDataStreamSequence(string.UTF8String, string.length,
stream, NULL, 1, length);
IJSVGPathDataStreamRelease(stream);
return floats;
}
+ (CGFloat*)parseViewBox:(NSString*)string
{
NSInteger size = 0;
return [self.class scanFloatsFromString:string
size:&size];
IJSVGPathDataStream* stream = IJSVGPathDataStreamCreate(4,
IJSVG_STREAM_CHAR_BLOCK_SIZE);
CGFloat* floats = IJSVGParsePathDataStreamSequence(string.UTF8String,
string.length, stream, NULL, 1, NULL);
IJSVGPathDataStreamRelease(stream);
return floats;
}
+ (CGFloat)floatValue:(NSString*)string
@@ -621,55 +507,4 @@ CGFloat degrees_to_radians(CGFloat degrees)
return transformPath;
}
+ (CGPathRef)newCGPathFromBezierPath:(NSBezierPath*)bezPath
{
CGPathRef immutablePath = NULL;
// Then draw the path elements.
NSInteger numElements = bezPath.elementCount;
if (numElements > 0) {
CGMutablePathRef path = CGPathCreateMutable();
NSPoint points[3];
BOOL didClosePath = YES;
for (NSInteger i = 0; i < numElements; i++) {
switch ([bezPath elementAtIndex:i associatedPoints:points]) {
case NSMoveToBezierPathElement: {
CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
break;
}
case NSLineToBezierPathElement: {
CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
didClosePath = NO;
break;
}
case NSCurveToBezierPathElement: {
CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
points[1].x, points[1].y,
points[2].x, points[2].y);
didClosePath = NO;
break;
}
case NSClosePathBezierPathElement: {
CGPathCloseSubpath(path);
didClosePath = YES;
break;
}
}
}
// Be sure the path is closed or Quartz may not do valid hit detection.
if (didClosePath == NO) {
CGPathCloseSubpath(path);
}
// memory clean
immutablePath = CGPathCreateCopy(path);
CGPathRelease(path);
}
return immutablePath;
}
@end