Compare commits

...

47 Commits

Author SHA1 Message Date
Curtis Hard 68393b296b Faster low level parsing for paths 2021-01-25 19:29:53 +00:00
Curtis Hard 1cfcb596d9 Much faster command parsing
tl;dr, stops converting between char*/unichar* and NSString*, the low level code for passing commands wants char* so no point of converting it to a higher level NSString* just to convert it back again, waste of time.
2021-01-24 22:18:07 +00:00
Curtis Hard 69077e49cf Faster parsing for very large SVGs 2021-01-24 19:16:10 +00:00
Curtis Hard 3bdf2151ca Rewrote how we parse transforms (its much faster)
Also much faster arc transform processsing
2021-01-23 18:58:39 +00:00
Curtis Hard f7e28a2962 Added originalType for future reference 2021-01-20 21:39:10 +00:00
Curtis Hard 2c07cfabdd Added more units and neatened up methods 2021-01-20 21:19:28 +00:00
Curtis Hard 3e356b3fdc Possible fix for CM and MM units 2021-01-20 21:00:15 +00:00
Curtis Hard 49f759edc0 Added extra header in 2021-01-10 16:55:28 +00:00
Curtis Hard 38e314eb99 Cant use description as its already a thing on NSObject. #oops 2020-11-22 18:50:09 +00:00
Curtis Hard d0eb015cf1 Added title and desc parsing into the IJSVGNode 2020-11-22 18:14:04 +00:00
Curtis Hard 080626a022 Removed warning 2020-11-14 16:44:13 +00:00
Curtis Hard 36c20bc55c Updated clang warning 2020-11-13 20:01:37 +00:00
Curtis Hard 4d89631e9a Brace in the wrong place! #oops 2020-09-21 20:16:53 +01:00
Curtis Hard 65f62007f3 Fixes exception being thrown and lower cased move command 2020-09-21 19:55:31 +01:00
Curtis Hard af5f16288d Updated opyright 2020-09-21 09:58:38 +01:00
Curtis Hard 08e0f9288d Adds support to remove default attributes from exported files 2020-09-16 18:40:05 +01:00
Curtis Hard 7650f6205c More improvements to exporting data 2020-09-13 20:36:29 +01:00
Curtis Hard 263768af5b Initial compression changes 2020-09-12 22:59:54 +01:00
Curtis Hard 4b1be17f9a Reduces exported data by a fair amount on larger SVGs by using NSXMLNodeCompactEmptyElement to self close tags
- also added option to get back an IJSVG object from the current export string
2020-09-08 18:57:20 +01:00
Curtis Hard 6b0d6b1452 This adds in floating point export options
- aswell as an option to round matrix values
2020-09-03 19:37:37 +01:00
Curtis Hard f80d4145bb Default rounds numbers, but not for transforms 2020-08-14 20:48:33 +01:00
Curtis Hard f68c83285b Fixes issuew here stroke-opacity was not exported 2020-08-14 20:13:53 +01:00
Curtis Hard 1f4bd989d8 Fixes RX and RY on rect 2020-08-13 12:10:49 +01:00
Curtis Hard 5b5d0b738b Added instruction passing for c -> s + t 2020-08-04 20:58:05 +01:00
Curtis Hard bd82c5d81b Only add the xlink namespace attribute on if required 2020-07-31 18:27:16 +01:00
Curtis Hard 145bbb17f8 Adds xml declaration removal to options 2020-07-29 20:13:01 +01:00
Curtis Hard d9f40551a4 Added ijsvg_isHexString for faster hex comparison 2020-07-27 12:06:03 +01:00
Curtis Hard 4448f6aeaf Small perf increase 2020-07-12 14:36:12 +01:00
Curtis Hard 6834abba19 Typo fix 2020-07-11 11:56:42 +01:00
Curtis Hard a86b4e80ba Refacator name 2020-07-09 16:32:20 +01:00
Curtis Hard 2e8d039599 Memory reductions 2020-06-27 19:00:38 +01:00
Curtis Hard 4ba6bb776d Xcode stuff 2020-06-27 12:28:35 +01:00
Curtis Hard 9c80412e88 Various image additions 2020-06-15 09:25:53 +01:00
Curtis Hard 509f0e0b0a Only allow removal of 0 if the float contains a floating point 2020-06-05 13:34:44 +01:00
Curtis Hard ce30877a26 Xcode stuff 2020-05-25 19:58:48 +01:00
Curtis Hard 122271bf36 Fixes for layer tree masking 2020-04-18 17:16:29 +01:00
Curtis Hard 28b4c6b85c Fixes issue with intrinsicSize size and dom being discarded 2020-03-01 18:19:38 +00:00
Curtis Hard 4b308d3a3e Wrong unit used 2020-02-27 08:33:16 +00:00
Curtis Hard d3ee05d8ac #oops 2020-02-25 08:29:59 +00:00
Curtis Hard 204b516e77 Fixes width and height not working correctly 2020-02-12 22:32:52 +00:00
Curtis Hard a832a986fd Swaps back to CATransaction 2020-01-16 20:37:48 +00:00
Curtis Hard f875714609 FOrmatting 2020-01-14 21:12:38 +00:00
Curtis Hard b8f166f4c1 Uses NSAnimationContext over CATransaction for safety 2020-01-14 21:12:33 +00:00
Curtis Hard 7901c9eafe Possible fixes for main thread 2020-01-14 20:41:59 +00:00
Curtis Hard 5de88cb1d7 #oopsie 2020-01-14 16:38:08 +00:00
Curtis Hard 6216b61c19 Fixes compat for 10.13 with CGPath block applying 2020-01-14 16:34:52 +00:00
Curtis Hard e05f5a0884 Fixes compatibility issues for 10.9 2020-01-14 15:51:15 +00:00
44 changed files with 2111 additions and 482 deletions
@@ -7,10 +7,18 @@
objects = {
/* Begin PBXBuildFile section */
5919E65723F47FF60051873A /* IJSVGUnitRect.h in Headers */ = {isa = PBXBuildFile; fileRef = 5919E65523F47FF60051873A /* IJSVGUnitRect.h */; settings = {ATTRIBUTES = (Public, ); }; };
5919E65823F47FF60051873A /* IJSVGUnitRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 5919E65623F47FF60051873A /* IJSVGUnitRect.m */; };
5919E65B23F480330051873A /* IJSVGUnitPoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 5919E65923F480330051873A /* IJSVGUnitPoint.h */; settings = {ATTRIBUTES = (Public, ); }; };
5919E65C23F480330051873A /* IJSVGUnitPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 5919E65A23F480330051873A /* IJSVGUnitPoint.m */; };
594A10DA248D7C90001A3181 /* NSImage+IJSVGAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 594A10D8248D7C90001A3181 /* NSImage+IJSVGAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
594A10DB248D7C90001A3181 /* NSImage+IJSVGAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 594A10D9248D7C90001A3181 /* NSImage+IJSVGAdditions.m */; };
594CF55F238FF462009B251B /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 594CF55E238FF462009B251B /* AppKit.framework */; };
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 */; };
59A24EBC23F480EA0090C374 /* IJSVGUnitSize.h in Headers */ = {isa = PBXBuildFile; fileRef = 59A24EBA23F480EA0090C374 /* IJSVGUnitSize.h */; settings = {ATTRIBUTES = (Public, ); }; };
59A24EBD23F480EA0090C374 /* IJSVGUnitSize.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A24EBB23F480EA0090C374 /* IJSVGUnitSize.m */; };
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, ); }; };
@@ -127,12 +135,20 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
5919E65523F47FF60051873A /* IJSVGUnitRect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitRect.h; sourceTree = "<group>"; };
5919E65623F47FF60051873A /* IJSVGUnitRect.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnitRect.m; sourceTree = "<group>"; };
5919E65923F480330051873A /* IJSVGUnitPoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitPoint.h; sourceTree = "<group>"; };
5919E65A23F480330051873A /* IJSVGUnitPoint.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnitPoint.m; sourceTree = "<group>"; };
594A10D8248D7C90001A3181 /* NSImage+IJSVGAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSImage+IJSVGAdditions.h"; sourceTree = "<group>"; };
594A10D9248D7C90001A3181 /* NSImage+IJSVGAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSImage+IJSVGAdditions.m"; sourceTree = "<group>"; };
594CF46F238FF38E009B251B /* IJSVG.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IJSVG.framework; sourceTree = BUILT_PRODUCTS_DIR; };
594CF473238FF38E009B251B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
594CF55E238FF462009B251B /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
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; };
59A24EBA23F480EA0090C374 /* IJSVGUnitSize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitSize.h; sourceTree = "<group>"; };
59A24EBB23F480EA0090C374 /* IJSVGUnitSize.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnitSize.m; sourceTree = "<group>"; };
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>"; };
@@ -396,6 +412,12 @@
59EB75BA23905F7000F5AE63 /* IJSVGUnitLength.m */,
59EB759423905F6D00F5AE63 /* IJSVGUtils.h */,
59EB758023905F6C00F5AE63 /* IJSVGUtils.m */,
5919E65523F47FF60051873A /* IJSVGUnitRect.h */,
5919E65623F47FF60051873A /* IJSVGUnitRect.m */,
5919E65923F480330051873A /* IJSVGUnitPoint.h */,
5919E65A23F480330051873A /* IJSVGUnitPoint.m */,
59A24EBA23F480EA0090C374 /* IJSVGUnitSize.h */,
59A24EBB23F480EA0090C374 /* IJSVGUnitSize.m */,
);
path = Utils;
sourceTree = "<group>";
@@ -431,6 +453,8 @@
59EB758E23905F6D00F5AE63 /* IJSVGBezierPathAdditions.m */,
59EB75B823905F7000F5AE63 /* IJSVGStringAdditions.h */,
59EB757823905F6C00F5AE63 /* IJSVGStringAdditions.m */,
594A10D8248D7C90001A3181 /* NSImage+IJSVGAdditions.h */,
594A10D9248D7C90001A3181 /* NSImage+IJSVGAdditions.m */,
);
path = Additions;
sourceTree = "<group>";
@@ -566,10 +590,14 @@
59EB761823905F7300F5AE63 /* IJSVGParser.h in Headers */,
59EB761E23905F7300F5AE63 /* IJSVGGroupLayer.h in Headers */,
59EB761D23905F7300F5AE63 /* IJSVGStyle.h in Headers */,
5919E65723F47FF60051873A /* IJSVGUnitRect.h in Headers */,
5919E65B23F480330051873A /* IJSVGUnitPoint.h in Headers */,
59A24EBC23F480EA0090C374 /* IJSVGUnitSize.h in Headers */,
59EB764523905F7300F5AE63 /* IJSVGExporter.h in Headers */,
59E7CFAF23B148600077D599 /* IJSVGCommandParser.h in Headers */,
59EB762823905F7300F5AE63 /* IJSVGCommandClose.h in Headers */,
59EB75E423905F7300F5AE63 /* IJSVGGradientUnitLength.h in Headers */,
594A10DA248D7C90001A3181 /* NSImage+IJSVGAdditions.h in Headers */,
59EB762D23905F7300F5AE63 /* IJSVGLayerTree.h in Headers */,
59EB760B23905F7300F5AE63 /* IJSVGStyleSheetSelector.h in Headers */,
);
@@ -602,7 +630,7 @@
594CF466238FF38E009B251B /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1100;
LastUpgradeCheck = 1220;
ORGANIZATIONNAME = "Curtis Hard";
TargetAttributes = {
594CF46E238FF38E009B251B = {
@@ -644,6 +672,7 @@
buildActionMask = 2147483647;
files = (
59EB763123905F7300F5AE63 /* IJSVGText.m in Sources */,
59A24EBD23F480EA0090C374 /* IJSVGUnitSize.m in Sources */,
59EB760223905F7300F5AE63 /* IJSVGCommandVerticalLine.m in Sources */,
59EB75FD23905F7300F5AE63 /* IJSVGPatternLayer.m in Sources */,
59EB763923905F7300F5AE63 /* IJSVGCommandSmoothQuadraticCurve.m in Sources */,
@@ -654,6 +683,7 @@
59EB761123905F7300F5AE63 /* IJSVGCommand.m in Sources */,
59EB760923905F7300F5AE63 /* IJSVGCommandCurve.m in Sources */,
59EB763323905F7300F5AE63 /* IJSVGLinearGradient.m in Sources */,
594A10DB248D7C90001A3181 /* NSImage+IJSVGAdditions.m in Sources */,
59EB761923905F7300F5AE63 /* IJSVGCommandSmoothCurve.m in Sources */,
59EB761323905F7300F5AE63 /* IJSVG.m in Sources */,
59EB763623905F7300F5AE63 /* IJSVGImageLayer.m in Sources */,
@@ -663,6 +693,7 @@
59EB763523905F7300F5AE63 /* IJSVGStyleSheetSelector.m in Sources */,
59E7CFB023B148600077D599 /* IJSVGCommandParser.m in Sources */,
59EB760723905F7300F5AE63 /* IJSVGNode.m in Sources */,
5919E65C23F480330051873A /* IJSVGUnitPoint.m in Sources */,
59EB75E923905F7300F5AE63 /* IJSVGStringAdditions.m in Sources */,
59EB761723905F7300F5AE63 /* IJSVGRadialGradient.m in Sources */,
59EB75E223905F7300F5AE63 /* IJSVGColorList.m in Sources */,
@@ -674,6 +705,7 @@
59EB75EB23905F7300F5AE63 /* IJSVGShapeLayer.m in Sources */,
59EB75F623905F7300F5AE63 /* IJSVGColor.m in Sources */,
59EB75F323905F7300F5AE63 /* IJSVGGroupLayer.m in Sources */,
5919E65823F47FF60051873A /* IJSVGUnitRect.m in Sources */,
59EB762523905F7300F5AE63 /* IJSVGTransform.m in Sources */,
59EB760023905F7300F5AE63 /* IJSVGGradientLayer.m in Sources */,
59EB762B23905F7300F5AE63 /* IJSVGUnitLength.m in Sources */,
@@ -731,6 +763,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -746,6 +779,7 @@
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREFIX_HEADER = "";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
@@ -792,6 +826,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -805,6 +840,7 @@
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREFIX_HEADER = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -15,7 +15,7 @@
<key>594CF46E238FF38E009B251B</key>
<dict>
<key>primary</key>
<true/>
<true />
</dict>
</dict>
</dict>
+1 -1
View File
@@ -19,6 +19,6 @@
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Curtis Hard. All rights reserved.</string>
<string>Copyright © 2020 Curtis Hard. All rights reserved.</string>
</dict>
</plist>
@@ -14,5 +14,6 @@
- (BOOL)ijsvg_isNumeric;
- (BOOL)ijsvg_containsAlpha;
- (NSArray*)ijsvg_componentsSplitByWhiteSpace;
- (BOOL)ijsvg_isHexString;
@end
@@ -49,9 +49,9 @@
- (BOOL)ijsvg_containsAlpha
{
const char* buffer = self.UTF8String;
unsigned long length = strlen(buffer);
for (int i = 0; i < length; i++) {
if (isalpha(buffer[i])) {
char currentChar;
while((currentChar = *buffer++) ) {
if (isalpha(currentChar)) {
return YES;
}
}
@@ -61,9 +61,9 @@
- (BOOL)ijsvg_isNumeric
{
const char* buffer = self.UTF8String;
unsigned long length = strlen(buffer);
for (int i = 0; i < length; i++) {
if (!isnumber(buffer[i])) {
char currentChar;
while((currentChar = *buffer++) ) {
if (!isnumber(currentChar)) {
return NO;
}
}
@@ -75,4 +75,20 @@
return [self ijsvg_componentsSeparatedByChars:"\t\n\r "];
}
- (BOOL)ijsvg_isHexString
{
const char* chars = self.UTF8String;
char c;
while((c = *chars++)) {
BOOL flag = ((c == '#') ||
(c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F'));
if(flag == NO) {
return NO;
}
}
return YES;
}
@end
@@ -0,0 +1,18 @@
//
// NSImage+IJSVGAdditions.h
// IJSVG
//
// Created by Curtis Hard on 07/06/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
IJSVG* IJSVGGetFromNSImage(NSImage* image);
@interface NSImage (IJSVGAdditions)
+ (NSImage*)SVGImageNamed:(NSString*)imageName;
@end
@@ -0,0 +1,53 @@
//
// NSImage+IJSVGAdditions.m
// IJSVG
//
// Created by Curtis Hard on 07/06/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGImageRep.h"
#import "NSImage+IJSVGAdditions.h"
IJSVG* IJSVGGetFromNSImage(NSImage* image)
{
for (NSImageRep* rep in image.representations) {
if ([rep isKindOfClass:IJSVGImageRep.class]) {
return ((IJSVGImageRep*)rep).SVG;
}
}
return nil;
}
@implementation NSImage (IJSVGAdditions)
+ (NSImage*)SVGImageNamed:(NSString*)imageName
{
// find the image
NSBundle* bundle = NSBundle.mainBundle;
NSString* str = nil;
NSString* ext = imageName.pathExtension;
if (ext == nil || ext.length == 0) {
ext = @"svg";
}
if ((str = [bundle pathForResource:imageName.stringByDeletingPathExtension
ofType:ext])
!= nil) {
// work out if we can get the data
NSData* data = [[[NSData alloc] initWithContentsOfFile:str] autorelease];
if (data == nil) {
return nil;
}
// grab the image rep
IJSVGImageRep* rep = [[[IJSVGImageRep alloc] initWithData:data] autorelease];
NSImage* image = [[[NSImage alloc] init] autorelease];
[image addRepresentation:rep];
return image;
}
return nil;
}
@end
@@ -8,6 +8,7 @@
#import "IJSVGColor.h"
#import "IJSVGUtils.h"
#import "IJSVGStringAdditions.h"
@implementation IJSVGColor
@@ -707,14 +708,7 @@ CGFloat* IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightnes
+ (BOOL)isHex:(NSString*)string
{
const char* validList = "0123456789ABCDEFabcdef#";
for (NSInteger i = 0; i < string.length; i++) {
char c = [string characterAtIndex:i];
if (strchr(validList, c) == NULL) {
return NO;
}
}
return YES;
return string.ijsvg_isHexString;
}
+ (unsigned long)lengthOfHEXInteger:(NSUInteger)hex
@@ -55,8 +55,8 @@ typedef NS_ENUM(NSInteger, IJSVGCommandType) {
intoArray:(NSMutableArray<IJSVGCommand*>*)commands
parentCommand:(IJSVGCommand*)parentCommand;
- (id)initWithCommandString:(NSString*)str
dataStream:(IJSVGPathDataStream*)dataStream;
- (id)initWithCommandStringBuffer:(const char*)str
dataStream:(IJSVGPathDataStream*)dataStream;
- (IJSVGCommand*)subcommandWithParameters:(CGFloat*)subParams
paramCount:(NSInteger)paramCount
previousCommand:(IJSVGCommand*)command;
@@ -111,18 +111,18 @@
[super dealloc];
}
- (id)initWithCommandString:(NSString*)str
dataStream:(IJSVGPathDataStream*)dataStream
- (id)initWithCommandStringBuffer:(const char*)str
dataStream:(IJSVGPathDataStream*)dataStream
{
if ((self = [super init]) != nil) {
// work out the basics
_currentIndex = 0;
command = [str characterAtIndex:0];
command = str[0];
type = [IJSVGUtils typeForCommandChar:command];
NSInteger sets = 0;
NSInteger paramCount = [self.class requiredParameterCount];
IJSVGPathDataSequence* sequence = [self.class pathDataSequence];
parameters = IJSVGParsePathDataStreamSequence(str.UTF8String, str.length,
parameters = IJSVGParsePathDataStreamSequence(str, strlen(str),
dataStream, sequence, [self.class requiredParameterCount], &sets);
if (sets <= 1) {
@@ -44,8 +44,8 @@ static IJSVGPathDataSequence* _sequence;
CGPoint arcEndPoint = CGPointZero;
CGPoint arcStartPoint = path.currentPoint;
CGFloat xAxisRotation = 0;
BOOL largeArcFlag = 0;
BOOL sweepFlag = 0;
BOOL largeArcFlag = NO;
BOOL sweepFlag = NO;
radii = [currentCommand readPoint];
xAxisRotation = [currentCommand readFloat];
@@ -91,15 +91,11 @@ static IJSVGPathDataSequence* _sequence;
CGPoint scale = (radii.x > radii.y) ? CGPointMake(1, radii.y / radii.x) : CGPointMake(radii.x / radii.y, 1);
NSAffineTransform* trans = NSAffineTransform.transform;
[trans translateXBy:-centerPoint.x yBy:-centerPoint.y];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans translateXBy:-centerPoint.x
yBy:-centerPoint.y];
[trans rotateByRadians:-xAxisRotation];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans scaleXBy:(1 / scale.x) yBy:(1 / scale.y)];
[trans scaleXBy:(1 / scale.x)
yBy:(1 / scale.y)];
[path.path transformUsingAffineTransform:trans];
[path.path appendBezierPathWithArcWithCenter:NSZeroPoint
@@ -109,15 +105,11 @@ static IJSVGPathDataSequence* _sequence;
clockwise:!sweepFlag];
trans = NSAffineTransform.transform;
[trans scaleXBy:scale.x yBy:scale.y];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans scaleXBy:scale.x
yBy:scale.y];
[trans rotateByRadians:xAxisRotation];
[path.path transformUsingAffineTransform:trans];
trans = NSAffineTransform.transform;
[trans translateXBy:centerPoint.x yBy:centerPoint.y];
[trans translateXBy:centerPoint.x
yBy:centerPoint.y];
[path.path transformUsingAffineTransform:trans];
}
@@ -35,17 +35,16 @@
return;
}
// actual move to command
if (type == kIJSVGCommandTypeAbsolute) {
// actual move to command - do a moveToPoint only
// if the type is absolute, or its possible the type is
// relative but there is no previous command which means
// there is no current point. Asking for current point on an empty
// path will result in an exception being thrown
if (type == kIJSVGCommandTypeAbsolute || command == nil) {
[path.path moveToPoint:NSMakePoint(params[0], params[1])];
return;
}
@try {
[path.path relativeMoveToPoint:NSMakePoint(params[0], params[1])];
}
@catch (NSException* exception) {
[path.path moveToPoint:NSMakePoint(params[0], params[1])];
}
[path.path relativeMoveToPoint:NSMakePoint(params[0], params[1])];
}
@end
+14 -2
View File
@@ -44,7 +44,6 @@
id<IJSVGDelegate> _delegate;
IJSVGLayer* _layerTree;
CGRect _viewBox;
CGSize _proposedViewSize;
CGFloat _backingScaleFactor;
CGFloat _lastProposedBackingScale;
IJSVGRenderQuality _lastProposedRenderQuality;
@@ -68,7 +67,10 @@
// fillColor, strokeColor, pattern and gradient fill
@property (nonatomic, assign) IJSVGRenderQuality renderQuality;
@property (nonatomic, assign) BOOL clipToViewport;
@property (nonatomic, retain) IJSVGRenderingStyle* style;
@property (nonatomic, retain) IJSVGRenderingStyle* renderingStyle;
@property (nonatomic, readonly) IJSVGUnitSize * intrinsicSize;
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* desc;
- (void)prepForDrawingInView:(NSView*)view;
- (BOOL)isFont;
@@ -80,6 +82,8 @@
- (IJSVGLayer*)layerWithTree:(IJSVGLayerTree*)tree;
- (NSArray<IJSVG*>*)subSVGs:(BOOL)recursive;
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options;
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (CGFloat)computeBackingScale:(CGFloat)scale;
- (void)discardDOM;
@@ -101,6 +105,10 @@
- (id)initWithSVGString:(NSString*)string
error:(NSError**)error;
- (id)initWithSVGData:(NSData*)data;
- (id)initWithSVGData:(NSData*)data
error:(NSError**)error;
- (id)initWithFile:(NSString*)file;
- (id)initWithFile:(NSString*)file
error:(NSError**)error;
@@ -122,6 +130,9 @@
error:(NSError**)error;
- (NSImage*)imageWithSize:(NSSize)aSize
flipped:(BOOL)flipped;
- (NSImage*)imageWithSize:(NSSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error;
- (NSImage*)imageByMaintainingAspectRatioWithSize:(NSSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error;
@@ -154,4 +165,5 @@
// colors
- (IJSVGColorList*)computedColorList:(BOOL*)hasPatternFills;
- (void)performBlock:(dispatch_block_t)block;
@end
+127 -35
View File
@@ -12,23 +12,33 @@
@implementation IJSVG
@synthesize title = _title;
@synthesize desc = _desc;
@synthesize renderingBackingScaleHelper;
@synthesize clipToViewport;
@synthesize renderQuality;
@synthesize style = _style;
@synthesize renderingStyle = _renderingStyle;
@synthesize intrinsicSize = _intrinsicSize;
- (void)dealloc
{
// this can all be called on the background thread to be released
BOOL hasTransaction = IJSVGBeginTransaction();
(void)([renderingBackingScaleHelper release]),
renderingBackingScaleHelper = nil;
(void)([_replacementColors release]), _replacementColors = nil;
(void)([_style release]), _style = nil;
(void)([_renderingStyle release]), _renderingStyle = nil;
(void)([_group release]), _group = nil;
(void)([_intrinsicSize release]), _intrinsicSize = nil;
(void)([_desc release]), _desc = nil;
(void)([_title release]), _title = nil;
// kill any memory that has been around
(void)([_layerTree release]), _layerTree = nil;
[super dealloc];
if (hasTransaction == YES) {
IJSVGEndTransaction();
}
}
+ (id)svgNamed:(NSString*)string
@@ -79,10 +89,14 @@
__block IJSVGImageLayer* imageLayer = nil;
// create the layers we require
BOOL hasTransaction = IJSVGBeginTransaction();
layer = [[[IJSVGGroupLayer alloc] init] autorelease];
imageLayer =
[[[IJSVGImageLayer alloc] initWithImage:image] autorelease];
[layer addSublayer:imageLayer];
if (hasTransaction == YES) {
IJSVGEndTransaction();
}
// return the initialized SVG
return [self initWithSVGLayer:layer
@@ -115,7 +129,7 @@
error:(NSError**)error
delegate:(id<IJSVGDelegate>)delegate
{
return [self initWithFilePathURL:[NSURL fileURLWithPath:file]
return [self initWithFilePathURL:[NSURL fileURLWithPath:file isDirectory:NO]
error:error
delegate:delegate];
}
@@ -192,6 +206,21 @@
return self;
}
- (id)initWithSVGData:(NSData*)data
{
return [self initWithSVGData:data
error:nil];
}
- (id)initWithSVGData:(NSData*)data
error:(NSError**)error
{
NSString* svgString = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
return [self initWithSVGString:svgString.autorelease
error:error];
}
- (id)initWithSVGString:(NSString*)string
{
return [self initWithSVGString:string
@@ -238,6 +267,15 @@
return self;
}
- (void)performBlock:(dispatch_block_t)block
{
BOOL hasTransaction = IJSVGBeginTransaction();
block();
if (hasTransaction == YES) {
IJSVGEndTransaction();
}
}
- (void)discardDOM
{
// if we discard, we can no longer create a tree, so lets create tree
@@ -250,24 +288,43 @@
- (void)_setupBasicInfoFromGroup
{
// store the viewbox
_viewBox = _group.viewBox;
_proposedViewSize = _group.proposedViewSize;
_intrinsicSize = _group.intrinsicSize.retain;
}
- (void)_setupBasicsFromAnyInitializer
{
self.style = [[[IJSVGRenderingStyle alloc] init] autorelease];
self.renderingStyle = [[[IJSVGRenderingStyle alloc] init] autorelease];
self.clipToViewport = YES;
self.renderQuality = kIJSVGRenderQualityFullResolution;
// setup low level backing scale
_lastProposedBackingScale = 0.f;
self.renderingBackingScaleHelper = ^CGFloat {
return 1.f;
return NSScreen.mainScreen.backingScaleFactor;
};
}
- (void)setTitle:(NSString*)title
{
_group.title = title;
}
- (NSString*)title
{
return _group.title;
}
- (void)setDesc:(NSString*)description
{
_group.desc = description;
}
- (NSString*)desc
{
return _group.desc;
}
- (NSString*)identifier
{
return _group.identifier;
@@ -310,10 +367,19 @@
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
{
IJSVGExporter* exporter =
[[[IJSVGExporter alloc] initWithSVG:self
size:self.viewBox.size
options:options] autorelease];
IJSVGExporter* exporter = [[[IJSVGExporter alloc] initWithSVG:self
size:self.viewBox.size
options:options] autorelease];
return [exporter SVGString];
}
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
IJSVGExporter* exporter = [[[IJSVGExporter alloc] initWithSVG:self
size:self.viewBox.size
options:options
floatingPointOptions:floatingPointOptions] autorelease];
return [exporter SVGString];
}
@@ -340,11 +406,19 @@
error:nil];
}
- (NSSize)computeSVGSizeWithRenderSize:(NSSize)size
{
IJSVGUnitSize* svgSize = _intrinsicSize;
return NSMakeSize([svgSize.width computeValue:size.width],
[svgSize.height computeValue:size.height]);
}
- (NSRect)computeOriginalDrawingFrameWithSize:(NSSize)aSize
{
[self _beginDraw:(NSRect){ .origin = CGPointZero, .size = aSize }];
return NSMakeRect(0.f, 0.f, _proposedViewSize.width * _clipScale,
_proposedViewSize.height * _clipScale);
NSSize propSize = [self computeSVGSizeWithRenderSize:aSize];
[self _beginDraw:(NSRect) { .origin = CGPointZero, .size = aSize }];
return NSMakeRect(0.f, 0.f, propSize.width * _clipScale,
propSize.height * _clipScale);
}
- (CGImageRef)newCGImageRefWithSize:(CGSize)size
@@ -353,7 +427,7 @@
{
// setup the drawing rect, this is used for both the intial drawing
// and the backing scale helper block
NSRect rect = (CGRect){
NSRect rect = (CGRect) {
.origin = CGPointZero,
.size = (CGSize)size
};
@@ -362,7 +436,7 @@
[self _beginDraw:rect];
// make sure we setup the scale based on the backing scale factor
CGFloat scale = [self backindScaleFactor:NULL];
CGFloat scale = [self backingScaleFactor:NULL];
// create the context and colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
@@ -422,7 +496,7 @@
- (NSData*)PDFData:(NSError**)error
{
return [self
PDFDataWithRect:(NSRect){ .origin = NSZeroPoint, .size = _viewBox.size }
PDFDataWithRect:(NSRect) { .origin = NSZeroPoint, .size = _viewBox.size }
error:error];
}
@@ -540,8 +614,14 @@
- (BOOL)drawInRect:(NSRect)rect
error:(NSError**)error
{
CGContextRef currentCGContext;
if (@available(macOS 10.10, *)) {
currentCGContext = NSGraphicsContext.currentContext.CGContext;
} else {
currentCGContext = NSGraphicsContext.currentContext.graphicsPort;
}
return [self _drawInRect:rect
context:[[NSGraphicsContext currentContext] CGContext]
context:currentCGContext
error:error];
}
@@ -557,11 +637,12 @@
// we also need to calculate the viewport so we can clip
// the drawing if needed
NSRect viewPort = NSZeroRect;
viewPort.origin.x = round((rect.size.width / 2 - (_proposedViewSize.width / 2) * _clipScale) + rect.origin.x);
NSSize propSize = [self computeSVGSizeWithRenderSize:rect.size];
viewPort.origin.x = round((rect.size.width / 2 - (propSize.width / 2) * _clipScale) + rect.origin.x);
viewPort.origin.y = round(
(rect.size.height / 2 - (_proposedViewSize.height / 2) * _clipScale) + rect.origin.y);
viewPort.size.width = _proposedViewSize.width * _clipScale;
viewPort.size.height = _proposedViewSize.height * _clipScale;
(rect.size.height / 2 - (propSize.height / 2) * _clipScale) + rect.origin.y);
viewPort.size.width = propSize.width * _clipScale;
viewPort.size.height = propSize.height * _clipScale;
// check the viewport
if (NSEqualRects(_viewBox, NSZeroRect) || _viewBox.size.width <= 0 || _viewBox.size.height <= 0 || NSEqualRects(NSZeroRect, viewPort) || CGRectIsEmpty(viewPort) || CGRectIsNull(viewPort) || viewPort.size.width <= 0 || viewPort.size.height <= 0) {
@@ -615,9 +696,7 @@
// do we need to update the backing scales on the
// layers?
if (self.renderingBackingScaleHelper != nil) {
[self backindScaleFactor:nil];
}
[self backingScaleFactor:nil];
CGInterpolationQuality quality;
switch (self.renderQuality) {
@@ -634,7 +713,11 @@
}
}
CGContextSetInterpolationQuality(ref, quality);
BOOL hasTransaction = IJSVGBeginTransaction();
[self.layer renderInContext:ref];
if (hasTransaction == YES) {
IJSVGEndTransaction();
}
}
} @catch (NSException* exception) {
// just catch and give back a drawing error to the caller
@@ -648,7 +731,7 @@
return (error == nil);
}
- (CGFloat)backindScaleFactor:(CGFloat* _Nullable)proposedBackingScale
- (CGFloat)backingScaleFactor:(CGFloat* _Nullable)proposedBackingScale
{
__block CGFloat scale = 1.f;
scale = (self.renderingBackingScaleHelper)();
@@ -684,19 +767,27 @@
};
// gogogo
BOOL hasTransaction = IJSVGBeginTransaction();
[IJSVGLayer recursivelyWalkLayer:self.layer withBlock:block];
if (hasTransaction == YES) {
IJSVGEndTransaction();
}
return _backingScaleFactor;
}
- (IJSVGLayer*)layerWithTree:(IJSVGLayerTree*)tree
{
// clear memory
BOOL hasTransaction = IJSVGBeginTransaction();
if (_layerTree != nil) {
(void)([_layerTree release]), _layerTree = nil;
}
// force rebuild of the tree
_layerTree = [[tree layerForNode:_group] retain];
if (hasTransaction == YES) {
IJSVGEndTransaction();
}
return _layerTree;
}
@@ -710,16 +801,16 @@
// from this SVG object
IJSVGLayerTree* renderer = [[[IJSVGLayerTree alloc] init] autorelease];
renderer.viewBox = self.viewBox;
renderer.style = self.style;
renderer.style = self.renderingStyle;
// return the rendered layer
return [self layerWithTree:renderer];
}
- (void)setStyle:(IJSVGRenderingStyle*)style
- (void)setRenderingStyle:(IJSVGRenderingStyle*)style
{
(void)([_style release]), _style = nil;
_style = style.retain;
(void)([_renderingStyle release]), _renderingStyle = nil;
_renderingStyle = style.retain;
}
- (void)observeValueForKeyPath:(NSString*)keyPath
@@ -728,7 +819,7 @@
context:(void*)context
{
// invalidate the tree if a style is set
if (object == _style) {
if (object == _renderingStyle) {
[self invalidateLayerTree];
}
}
@@ -800,12 +891,13 @@
// to transform the paths into our viewbox
NSSize dest = rect.size;
NSSize source = _viewBox.size;
_clipScale = MIN(dest.width / _proposedViewSize.width,
dest.height / _proposedViewSize.height);
NSSize propSize = [self computeSVGSizeWithRenderSize:rect.size];
_clipScale = MIN(dest.width / propSize.width,
dest.height / propSize.height);
// work out the actual scale based on the clip scale
CGFloat w = _proposedViewSize.width * _clipScale;
CGFloat h = _proposedViewSize.height * _clipScale;
CGFloat w = propSize.width * _clipScale;
CGFloat h = propSize.height * _clipScale;
_scale = MIN(w / source.width, h / source.height);
}
@@ -17,6 +17,9 @@
IJSVG* _svg;
}
- (instancetype)initWithData:(NSData*)data;
@property (nonatomic, readonly) CGRect viewBox;
@property (nonatomic, readonly) IJSVG* SVG;
@end
@@ -11,8 +11,6 @@
@implementation IJSVGImageRep
@synthesize viewBox = _viewBox;
+ (void)load
{
[NSBitmapImageRep registerImageRepClass:self];
@@ -25,12 +23,20 @@
+ (NSArray<NSString*>*)imageTypes
{
return @[ (NSString*)kUTTypeScalableVectorGraphics, @"svg" ];
if (@available(macOS 10.10, *)) {
return @[ (NSString*)kUTTypeScalableVectorGraphics, @"svg" ];
} else {
return @[ @"public.svg-image", @"svg" ];
}
}
+ (NSArray<NSString*>*)imageUnfilteredTypes
{
return @[ (NSString*)kUTTypeScalableVectorGraphics, @"svg" ];
if (@available(macOS 10.10, *)) {
return @[ (NSString*)kUTTypeScalableVectorGraphics, @"svg" ];
} else {
return @[ @"public.svg-image", @"svg" ];
}
}
+ (NSArray<NSImageRep*>*)imageRepsWithData:(NSData*)data
@@ -101,4 +107,9 @@
return _svg.viewBox;
}
- (IJSVG*)SVG
{
return _svg;
}
@end
@@ -42,7 +42,7 @@
if (imageName != nil) {
IJSVG* anSVG = [IJSVG svgNamed:imageName];
if (tintColor != nil) {
anSVG.style.fillColor = tintColor;
anSVG.renderingStyle.fillColor = tintColor;
}
self.SVG = anSVG;
}
@@ -7,6 +7,7 @@
//
#import <Foundation/Foundation.h>
#import "IJSVGUtils.h"
@class IJSVG;
@@ -33,12 +34,18 @@ typedef NS_OPTIONS(NSInteger, IJSVGExporterOptions) {
IJSVGExporterOptionColorAllowRRGGBBAA = 1 << 14,
IJSVGExporterOptionRemoveComments = 1 << 15,
IJSVGExporterOptionCenterWithinViewBox = 1 << 16,
IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef | IJSVGExporterOptionRemoveUselessGroups | IJSVGExporterOptionCreateUseForPaths | IJSVGExporterOptionMoveAttributesToGroup | IJSVGExporterOptionSortAttributes | IJSVGExporterOptionCollapseGroups | IJSVGExporterOptionCleanupPaths | IJSVGExporterOptionRemoveHiddenElements | IJSVGExporterOptionScaleToSizeIfNecessary | IJSVGExporterOptionCompressOutput | IJSVGExporterOptionCollapseGradients | IJSVGExporterOptionRemoveWidthHeightAttributes | IJSVGExporterOptionColorAllowRRGGBBAA | IJSVGExporterOptionRemoveComments | IJSVGExporterOptionCenterWithinViewBox
IJSVGExporterOptionRemoveXMLDeclaration = 1 << 17,
IJSVGExporterOptionConvertArcs = 1 << 18,
IJSVGExporterOptionConvertShapesToPaths = 1 << 19,
IJSVGExporterOptionRoundTransforms = 1 << 20,
IJSVGExporterOptionRemoveDefaultValues = 1 << 21,
IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef | IJSVGExporterOptionRemoveUselessGroups | IJSVGExporterOptionCreateUseForPaths | IJSVGExporterOptionMoveAttributesToGroup | IJSVGExporterOptionSortAttributes | IJSVGExporterOptionCollapseGroups | IJSVGExporterOptionCleanupPaths | IJSVGExporterOptionRemoveHiddenElements | IJSVGExporterOptionScaleToSizeIfNecessary | IJSVGExporterOptionCompressOutput | IJSVGExporterOptionCollapseGradients | IJSVGExporterOptionRemoveWidthHeightAttributes | IJSVGExporterOptionColorAllowRRGGBBAA | IJSVGExporterOptionRemoveComments | IJSVGExporterOptionCenterWithinViewBox | IJSVGExporterOptionRemoveXMLDeclaration | IJSVGExporterOptionConvertArcs | IJSVGExporterOptionConvertShapesToPaths | IJSVGExporterOptionRoundTransforms | IJSVGExporterOptionRemoveDefaultValues
};
BOOL IJSVGExporterHasOption(IJSVGExporterOptions options, NSInteger option);
void IJSVGEnumerateCGPathElements(CGPathRef path, IJSVGPathElementEnumerationBlock enumBlock);
const NSArray* IJSVGShortCharacterArray(void);
const NSArray<NSString*>* IJSVGShortCharacterArray(void);
const NSDictionary<NSString*, NSString*>* IJSVGDefaultAttributes(void);
@interface IJSVGExporter : NSObject {
@@ -50,15 +57,23 @@ const NSArray* IJSVGShortCharacterArray(void);
NSXMLElement* _defElement;
NSInteger _idCount;
NSInteger _shortIdCount;
BOOL _appliedXLink;
}
@property (nonatomic, assign) IJSVGFloatingPointOptions floatingPointOptions;
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* description;
- (id)initWithSVG:(IJSVG*)svg
size:(CGSize)size
options:(IJSVGExporterOptions)options;
- (id)initWithSVG:(IJSVG*)svg
size:(CGSize)size
options:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (NSString*)SVGString;
- (NSData*)SVGData;
- (IJSVG*)SVG:(NSError**)error;
@end
File diff suppressed because it is too large Load Diff
@@ -15,30 +15,53 @@ typedef struct {
NSArray<NSString*>* params;
} IJSVGExporterPathInstructionCommand;
typedef struct {
CGPoint center;
CGFloat radius;
} IJSVGExporterPathInstructionCircle;
@interface IJSVGExporterPathInstruction : NSObject {
@private
NSInteger _dataCount;
char _instruction;
CGFloat* _data;
CGFloat* _base;
CGFloat* _coords;
}
@property (nonatomic, assign) char instruction;
void IJSVGExporterPathInstructionRoundData(CGFloat* data, NSInteger length, IJSVGFloatingPointOptions options);
CGFloat IJSVGExporterPathFloatToFixed(CGFloat number, int precision);
IJSVGExporterPathInstructionCommand* IJSVGExporterPathInstructionCommandCopy(IJSVGExporterPathInstructionCommand command);
void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand* _Nullable command);
+ (NSArray<IJSVGExporterPathInstruction*>*)instructionsFromPath:(CGPathRef)path;
+ (NSArray<IJSVGExporterPathInstruction*>*)instructionsFromPath:(CGPathRef)path
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (id)initWithInstruction:(char)instruction
dataCount:(NSInteger)floatCount;
- (void)setInstruction:(char)newInstruction;
- (char)instruction;
- (CGFloat*)data;
- (NSInteger)dataLength;
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions;
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions;
+ (NSString*)pathStringWithInstructionSet:(NSArray<NSValue*>*)instructionSets;
+ (NSArray<IJSVGExporterPathInstruction*>*)convertInstructionsCurves:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (void)convertInstructionsToMixedAbsoluteRelative:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (void)convertInstructionsDataToRounded:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (NSString*)pathStringWithInstructionSet:(NSArray<NSValue*>*)instructionSets
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
@end
NS_ASSUME_NONNULL_END
static NSInteger const kIJSVGExporterPathInstructionFloatPrecision = 3;
static CGFloat const kIJSVGExporterPathInstructionErrorThreshold = 1e-2;
#define IJ_SVG_EXPORT_ROUND(value) IJSVGExporterPathFloatToFixed(value, kIJSVGExporterPathInstructionFloatPrecision)
@@ -9,13 +9,22 @@
#import "IJSVGExporter.h"
#import "IJSVGExporterPathInstruction.h"
#import "IJSVGUtils.h"
#import <math.h>
@implementation IJSVGExporterPathInstruction
@synthesize instruction = _instruction;
- (void)dealloc
{
if (_data != NULL) {
free(_data);
(void)free(_data), _data = NULL;
}
if (_base != NULL) {
(void)free(_base), _base = NULL;
}
if (_coords != NULL) {
(void)free(_coords), _coords = NULL;
}
[super dealloc];
}
@@ -28,8 +37,13 @@
// only allocate if not zero
if (floatCount != 0) {
_dataCount = floatCount;
_data = (CGFloat*)calloc(sizeof(CGFloat), floatCount);
}
// setup base and coords
_base = (CGFloat*)malloc(sizeof(CGFloat) * 2);
_coords = (CGFloat*)malloc(sizeof(CGFloat) * 2);
}
return self;
}
@@ -39,21 +53,21 @@
return _dataCount;
}
- (void)setInstruction:(char)newInstruction
{
_instruction = newInstruction;
}
- (char)instruction
{
return _instruction;
}
- (CGFloat*)data
{
return _data;
}
- (CGFloat*)base
{
return _base;
}
- (CGFloat*)coords
{
return _coords;
}
IJSVGExporterPathInstructionCommand* IJSVGExporterPathInstructionCommandCopy(IJSVGExporterPathInstructionCommand command)
{
IJSVGExporterPathInstructionCommand* copy = NULL;
@@ -71,10 +85,10 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
}
+ (NSString*)pathStringWithInstructionSet:(NSArray<NSValue*>*)instructionSets
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
IJSVGExporterPathInstructionCommand* lastCommand = NULL;
NSMutableString* string = [[[NSMutableString alloc] init] autorelease];
char* lastCommandChars = NULL;
for (NSValue* value in instructionSets) {
// read back the bytes
IJSVGExporterPathInstructionCommand command;
@@ -89,31 +103,9 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
[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;
}
// compresses the floats
NSString* compressedFloats = IJSVGCompressFloatParameterArray(command.params);
[string appendString:compressedFloats];
// store last command
IJSVGExporterPathInstructionCommandFree(lastCommand);
@@ -125,6 +117,7 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
}
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
NSMutableArray* pathInstructions = [[[NSMutableArray alloc] init] autorelease];
for (IJSVGExporterPathInstruction* instruction in instructions) {
@@ -132,11 +125,12 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
const char lowerInstruction = tolower(instruction.instruction);
NSArray<NSString*>* set = nil;
switch (lowerInstruction) {
case 't':
case 'm':
case 'l': {
set = @[
IJSVGShortFloatString(data[0]),
IJSVGShortFloatString(data[1])
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions)
];
break;
}
@@ -144,29 +138,43 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
case 'v':
case 'h': {
set = @[
IJSVGShortFloatString(data[0])
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions)
];
break;
}
case 'c': {
set = @[
IJSVGShortFloatString(data[0]),
IJSVGShortFloatString(data[1]),
IJSVGShortFloatString(data[2]),
IJSVGShortFloatString(data[3]),
IJSVGShortFloatString(data[4]),
IJSVGShortFloatString(data[5])
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[2], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[3], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[4], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[5], floatingPointOptions)
];
break;
}
case 's':
case 'q': {
set = @[
IJSVGShortFloatString(data[0]),
IJSVGShortFloatString(data[1]),
IJSVGShortFloatString(data[2]),
IJSVGShortFloatString(data[3])
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[2], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[3], floatingPointOptions)
];
break;
}
case 'a': {
set = @[
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[2], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[3], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[4], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[5], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[6], floatingPointOptions),
];
break;
}
@@ -187,13 +195,201 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
objCType:@encode(IJSVGExporterPathInstructionCommand)];
[pathInstructions addObject:value];
}
return [self pathStringWithInstructionSet:pathInstructions];
return [self pathStringWithInstructionSet:pathInstructions
floatingPointOptions:floatingPointOptions];
}
CGFloat IJSVGExporterPathFloatToFixed(CGFloat number, int precision)
{
return floorf(pow(10, precision) * number) / pow(10, precision);
}
void IJSVGExporterPathInstructionRoundData(CGFloat* data, NSInteger length,
IJSVGFloatingPointOptions options)
{
for (NSInteger i = length; i-- > 0;) {
CGFloat d = data[i];
CGFloat proposed = IJSVGExporterPathFloatToFixed(d, options.precision);
if (proposed != d) {
CGFloat rounded = +IJSVGExporterPathFloatToFixed(d, options.precision - 1);
data[i] = IJSVGExporterPathFloatToFixed(+fabs(rounded - d), options.precision + 1)
>= kIJSVGExporterPathInstructionErrorThreshold
? +IJSVGExporterPathFloatToFixed(d, options.precision)
: rounded;
}
}
}
+ (void)convertInstructionsToRoundRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
CGFloat relSubPoint[2] = { 0.f, 0.f };
for (IJSVGExporterPathInstruction* instruction in instructions) {
char instructionChar = instruction.instruction;
NSInteger length = instruction.dataLength;
CGFloat* data = instruction.data;
if (strchr("mltqsc", instructionChar) != NULL) {
for (NSInteger i = length; i--;) {
data[i] += instruction.base[i % 2] - relSubPoint[i % 2];
}
} else if (instructionChar == 'h') {
data[0] += instruction.base[0] - relSubPoint[0];
} else if (instructionChar == 'v') {
data[0] += instruction.base[1] - relSubPoint[1];
} else if (instructionChar == 'a') {
data[5] += instruction.base[0] - relSubPoint[0];
data[5] += instruction.base[1] - relSubPoint[1];
}
IJSVGExporterPathInstructionRoundData(data, length, floatingPointOptions);
if (instructionChar == 'h') {
relSubPoint[0] += data[0];
} else if (instructionChar == 'v') {
relSubPoint[1] += data[0];
} else {
relSubPoint[0] += data[length - 2];
relSubPoint[1] += data[length - 1];
}
IJSVGExporterPathInstructionRoundData(relSubPoint, 2, floatingPointOptions);
}
}
+ (void)convertInstructionsToMixedAbsoluteRelative:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
IJSVGExporterPathInstruction* prevInstruction = nil;
for (IJSVGExporterPathInstruction* instruction in instructions) {
if (prevInstruction == nil || instruction.dataLength == 0) {
prevInstruction = instruction;
continue;
}
char instructionChar = instruction.instruction;
CGFloat* data = instruction.data;
NSInteger length = instruction.dataLength;
CGFloat* adata = (CGFloat*)malloc(sizeof(CGFloat) * length);
memcpy(adata, data, sizeof(CGFloat) * length);
if (strchr("mltqsc", instructionChar) != NULL) {
for (NSInteger i = length; i--;) {
adata[i] += instruction.base[i % 2];
}
} else if (instructionChar == 'h') {
adata[0] += instruction.base[0];
} else if (instructionChar == 'v') {
adata[0] += instruction.base[1];
} else if (instructionChar == 'a') {
adata[5] += instruction.base[0];
adata[6] += instruction.base[1];
}
IJSVGExporterPathInstructionRoundData(adata, length, floatingPointOptions);
IJSVGExporterPathInstruction* ainstruction = nil;
ainstruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:instructionChar
dataCount:length] autorelease];
memcpy(ainstruction.data, adata, sizeof(CGFloat) * length);
// run these through our default string runner
// to compare the outputs
NSString* orig = [self pathStringFromInstructions:@[ instruction ]
floatingPointOptions:floatingPointOptions];
NSString* comp = [self pathStringFromInstructions:@[ ainstruction ]
floatingPointOptions:floatingPointOptions];
if (comp.length < orig.length && !(instructionChar == prevInstruction.instruction && prevInstruction.instruction > 96 && comp.length == orig.length - 1 && data[0] < 0.f && fmod(prevInstruction.data[prevInstruction.dataLength - 1], 1) != 0.f)) {
instruction.instruction = toupper(instructionChar);
memcpy(data, adata, sizeof(CGFloat) * length);
}
(void)free(adata), adata = NULL;
prevInstruction = instruction;
}
}
+ (void)convertInstructionsDataToRounded:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
for (IJSVGExporterPathInstruction* instruction in instructions) {
CGFloat* data = instruction.data;
IJSVGExporterPathInstructionRoundData(data, instruction.dataLength,
floatingPointOptions);
}
}
+ (NSArray<IJSVGExporterPathInstruction*>*)convertInstructionsCurves:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
NSMutableArray<IJSVGExporterPathInstruction*>* nInstructions = nil;
nInstructions = [[[NSMutableArray alloc] initWithCapacity:instructions.count] autorelease];
IJSVGExporterPathInstruction* lastInstruction = nil;
for (IJSVGExporterPathInstruction* instruction in instructions) {
lastInstruction = nInstructions.lastObject;
if (lastInstruction == nil) {
[nInstructions addObject:instruction];
continue;
}
if (instruction.instruction == 'c') {
if (lastInstruction.instruction == 'c' && instruction.data[0] == -(lastInstruction.data[2] - lastInstruction.data[4]) && instruction.data[1] == -(lastInstruction.data[3] - lastInstruction.data[5])) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'s'
dataCount:4] autorelease];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
nInstruction.data[2] = instruction.data[4];
nInstruction.data[3] = instruction.data[5];
[nInstructions addObject:nInstruction];
continue;
} else if (lastInstruction.instruction == 's' && instruction.data[0] == -(lastInstruction.data[0] - lastInstruction.data[2]) && instruction.data[1] == -(lastInstruction.data[1] - lastInstruction.data[3])) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'s'
dataCount:4] autorelease];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
nInstruction.data[2] = instruction.data[4];
nInstruction.data[3] = instruction.data[5];
[nInstructions addObject:nInstruction];
continue;
} else if (lastInstruction.instruction != 'c' && lastInstruction.instruction != 's' && instruction.data[0] == 0.f && instruction.data[1] == 0.f) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'s'
dataCount:4] autorelease];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
nInstruction.data[2] = instruction.data[4];
nInstruction.data[3] = instruction.data[5];
[nInstructions addObject:nInstruction];
continue;
}
} else if (instruction.instruction == 'q') {
if (lastInstruction.instruction == 'q' && instruction.data[0] == (lastInstruction.data[2] - lastInstruction.data[0]) && instruction.data[1] == (lastInstruction.data[3] - lastInstruction.data[1])) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'t'
dataCount:2] autorelease];
nInstruction.data[0] = instruction.data[3];
nInstruction.data[1] = instruction.data[4];
[nInstructions addObject:nInstruction];
continue;
} else if (lastInstruction.instruction == 't' && instruction.data[2] == lastInstruction.data[0] && instruction.data[3] == lastInstruction.data[1]) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'t'
dataCount:2] autorelease];
nInstruction.data[0] = instruction.data[3];
nInstruction.data[1] = instruction.data[4];
[nInstructions addObject:nInstruction];
continue;
}
}
[nInstructions addObject:instruction];
}
return nInstructions;
}
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
CGFloat point[2] = { 0, 0 };
CGFloat subpathPoint[2] = { 0, 0 };
IJSVGExporterPathInstruction* baseInstruction = nil;
IJSVGExporterPathInstruction* prevInstruction = nil;
NSInteger index = 0;
for (IJSVGExporterPathInstruction* anInstruction in instructions) {
@@ -212,6 +408,8 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
if (instruction == 'm') {
subpathPoint[0] = point[0];
subpathPoint[1] = point[1];
baseInstruction = anInstruction;
}
} else if (instruction == 'h') {
@@ -232,8 +430,8 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
subpathPoint[0] = point[0] += data[0];
subpathPoint[1] = point[1] += data[1];
baseInstruction = anInstruction;
} else if (instruction == 'L' || instruction == 'T') {
instruction = tolower(instruction);
data[0] -= point[0];
@@ -241,9 +439,7 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
point[0] += data[0];
point[1] += data[1];
} else if (instruction == 'C') {
instruction = 'c';
data[0] -= point[0];
@@ -255,9 +451,7 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
point[0] += data[4];
point[1] += data[5];
} else if (instruction == 'S' || instruction == 'Q') {
instruction = tolower(instruction);
data[0] -= point[0];
@@ -267,9 +461,7 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
point[0] += data[2];
point[1] += data[3];
} else if (instruction == 'A') {
instruction = 'a';
data[5] -= point[0];
@@ -277,38 +469,51 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
point[0] += data[5];
point[1] += data[6];
} else if (instruction == 'H') {
instruction = 'h';
data[0] -= point[0];
point[0] += data[0];
} else if (instruction == 'V') {
instruction = 'v';
data[0] -= point[1];
point[1] += data[0];
}
// reset the instruction
[anInstruction setInstruction:instruction];
anInstruction.instruction = instruction;
CGFloat* coords = anInstruction.coords;
coords[0] = point[0];
coords[1] = point[1];
} else if (instruction == 'Z' || instruction == 'z') {
if (baseInstruction != nil) {
CGFloat* coords = anInstruction.coords;
coords[0] = baseInstruction.coords[0];
coords[1] = baseInstruction.coords[1];
}
point[0] = subpathPoint[0];
point[1] = subpathPoint[1];
}
CGFloat* base = anInstruction.base;
if (prevInstruction != nil) {
base[0] = prevInstruction.coords[0];
base[1] = prevInstruction.coords[1];
} else {
base[0] = 0.f;
base[1] = 0.f;
}
// increment index
prevInstruction = anInstruction;
index++;
}
}
+ (NSArray<IJSVGExporterPathInstruction*>*)instructionsFromPath:(CGPathRef)path
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
// keep track of the current point
@@ -378,6 +583,7 @@ void IJSVGExporterPathInstructionCommandFree(IJSVGExporterPathInstructionCommand
CGPoint controlPoint1 = pathElement->points[0];
CGPoint controlPoint2 = pathElement->points[1];
CGPoint point = pathElement->points[2];
currentPoint = point;
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'C'
dataCount:6] autorelease];
@@ -25,7 +25,6 @@
- (void)dealloc
{
self.contents = nil;
(void)([_maskingLayer release]), _maskingLayer = nil;
[super dealloc];
}
@@ -26,7 +26,6 @@
- (void)dealloc
{
self.contents = nil;
(void)([_maskingLayer release]), _maskingLayer = nil;
[super dealloc];
}
@@ -43,6 +43,8 @@ typedef NS_ENUM(NSInteger, IJSVGNodeType) {
IJSVGNodeTypeTextSpan,
IJSVGNodeTypeStyle,
IJSVGNodeTypeSwitch,
IJSVGNodeTypeTitle,
IJSVGNodeTypeDesc,
IJSVGNodeTypeNotFound,
};
@@ -97,6 +99,8 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991;
@interface IJSVGNode : NSObject <NSCopying>
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* desc;
@property (nonatomic, assign) IJSVGNodeType type;
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* className;
@@ -12,6 +12,8 @@
@implementation IJSVGNode
@synthesize title;
@synthesize desc;
@synthesize shouldRender;
@synthesize type;
@synthesize name;
@@ -73,6 +75,8 @@
(void)([identifier release]), identifier = nil;
(void)([def release]), def = nil;
(void)([name release]), name = nil;
(void)([title release]), title = nil;
(void)([desc release]), desc = nil;
(void)([className release]), className = nil;
(void)([classNameList release]), classNameList = nil;
(void)([fillPattern release]), fillPattern = nil;
@@ -133,6 +137,12 @@
if ([string isEqualToString:@"tspan"] || kind == NSXMLTextKind) {
return IJSVGNodeTypeTextSpan;
}
if([string isEqualToString:@"title"]) {
return IJSVGNodeTypeTitle;
}
if([string isEqualToString:@"desc"]) {
return IJSVGNodeTypeDesc;
}
// are we commong HTML? - if so just treat as a group
if (IJSVGIsCommonHTMLElementName(string) == YES) {
@@ -152,6 +162,9 @@
- (void)applyPropertiesFromNode:(IJSVGNode*)node
{
self.title = node.title;
self.desc = node.desc;
self.name = node.name;
self.type = node.type;
self.unicode = node.unicode;
@@ -20,6 +20,7 @@
#import "IJSVGStyleSheet.h"
#import "IJSVGText.h"
#import "IJSVGTransform.h"
#import "IJSVGUnitRect.h"
#import "IJSVGUtils.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@@ -85,16 +86,15 @@ static NSString const* IJSVGAttributePoints = @"points";
@interface IJSVGParser : IJSVGGroup {
NSRect viewBox;
NSSize proposedViewSize;
IJSVGUnitSize* intrinsicSize;
@private
id<IJSVGParserDelegate> _delegate;
NSXMLDocument* _document;
NSMutableArray* _glyphs;
NSMutableArray<IJSVGNode*>* _glyphs;
IJSVGStyleSheet* _styleSheet;
NSMutableArray* _parsedNodes;
NSMutableDictionary* _defNodes;
NSMutableDictionary* _baseDefNodes;
NSMutableDictionary<NSString*, NSXMLElement*>* _defNodes;
NSMutableDictionary<NSString*, NSXMLElement*>* _baseDefNodes;
NSMutableArray<IJSVG*>* _svgs;
struct {
@@ -107,7 +107,7 @@ static NSString const* IJSVGAttributePoints = @"points";
}
@property (nonatomic, readonly) NSRect viewBox;
@property (nonatomic, readonly) NSSize proposedViewSize;
@property (nonatomic, readonly) IJSVGUnitSize* intrinsicSize;
+ (BOOL)isDataSVG:(NSData*)data;
@@ -12,7 +12,36 @@
@implementation IJSVGParser
@synthesize viewBox;
@synthesize proposedViewSize;
@synthesize intrinsicSize = _intrinsicSize;
static NSDictionary* _IJSVGAttributeDictionaryFloats = nil;
static NSDictionary* _IJSVGAttributeDictionaryNodes = nil;
static NSDictionary* _IJSVGAttributeDictionaryUnits = nil;
static NSDictionary* _IJSVGAttributeDictionaryTransforms = nil;
+ (void)load
{
_IJSVGAttributeDictionaryFloats = [@{
(NSString*)IJSVGAttributeX : @"x",
(NSString*)IJSVGAttributeY : @"y",
(NSString*)IJSVGAttributeWidth : @"width",
(NSString*)IJSVGAttributeHeight : @"height",
(NSString*)IJSVGAttributeOpacity : @"opacity",
(NSString*)IJSVGAttributeStrokeOpacity : @"strokeOpacity",
(NSString*)IJSVGAttributeStrokeWidth : @"strokeWidth",
(NSString*)IJSVGAttributeStrokeDashOffset : @"strokeDashOffset",
(NSString*)IJSVGAttributeFillOpacity : @"fillOpacity" } retain];
_IJSVGAttributeDictionaryNodes = [@{
(NSString*)IJSVGAttributeClipPath : @"clipPath",
(NSString*)IJSVGAttributeMask : @"mask" } retain];
_IJSVGAttributeDictionaryUnits = [@{
(NSString*)IJSVGAttributeGradientUnits : @"units",
(NSString*)IJSVGAttributeMaskUnits : @"units",
(NSString*)IJSVGAttributeMaskContentUnits : @"contentUnits"} retain];
_IJSVGAttributeDictionaryTransforms = [@{
(NSString*)IJSVGAttributeTransform : @"transforms",
(NSString*)IJSVGAttributeGradientTransform : @"transforms" } retain];
}
+ (IJSVGParser*)groupForFileURL:(NSURL*)aURL
{
@@ -42,10 +71,10 @@
{
(void)([_glyphs release]), _glyphs = nil;
(void)([_styleSheet release]), _styleSheet = nil;
(void)([_parsedNodes release]), _parsedNodes = nil;
(void)([_defNodes release]), _defNodes = nil;
(void)([_baseDefNodes release]), _baseDefNodes = nil;
(void)([_svgs release]), _svgs = nil;
(void)([_intrinsicSize release]), _intrinsicSize = nil;
if (_commandDataStream != NULL) {
(void)IJSVGPathDataStreamRelease(_commandDataStream), _commandDataStream = nil;
}
@@ -64,11 +93,7 @@
_respondsTo.handleSubSVG = [_delegate respondsToSelector:@selector(svgParser:foundSubSVG:withSVGString:)];
_commandDataStream = IJSVGPathDataStreamCreateDefault();
_glyphs = [[NSMutableArray alloc] init];
_parsedNodes = [[NSMutableArray alloc] init];
_defNodes = [[NSMutableDictionary alloc] init];
_baseDefNodes = [[NSMutableDictionary alloc] init];
_svgs = [[NSMutableArray alloc] init];
// load the document / file, assume its UTF8
@@ -202,8 +227,12 @@
free(box);
} else {
// there is no view box so find the width and height
CGFloat w = [svgElement attributeForName:(NSString*)IJSVGAttributeWidth].stringValue.floatValue;
CGFloat h = [svgElement attributeForName:(NSString*)IJSVGAttributeHeight].stringValue.floatValue;
NSString* wAtt = [svgElement attributeForName:(NSString*)IJSVGAttributeWidth].stringValue;
NSString* hAtt = [svgElement attributeForName:(NSString*)IJSVGAttributeHeight].stringValue;
IJSVGUnitLength* wLength = [IJSVGUnitLength unitWithString:wAtt];
IJSVGUnitLength* hLength = [IJSVGUnitLength unitWithString:hAtt];
CGFloat w = wLength.value;
CGFloat h = hLength.value;
if (h == 0.f && w != 0.f) {
h = w;
} else if (w == 0.f && h != 0.f) {
@@ -213,17 +242,22 @@
}
// parse the width and height....
CGFloat w = [svgElement attributeForName:(NSString*)IJSVGAttributeWidth].stringValue.floatValue;
CGFloat h = [svgElement attributeForName:(NSString*)IJSVGAttributeHeight].stringValue.floatValue;
if (w == 0.f && h == 0.f) {
w = viewBox.size.width;
h = viewBox.size.height;
} else if (w == 0 && h != 0.f) {
w = viewBox.size.width;
} else if (h == 0 && w != 0.f) {
h = viewBox.size.height;
NSString* w = [svgElement attributeForName:(NSString*)IJSVGAttributeWidth].stringValue;
NSString* h = [svgElement attributeForName:(NSString*)IJSVGAttributeHeight].stringValue;
// by default just the the width and height from the viewbox unless
// specified otherwise
IJSVGUnitLength* wl = [IJSVGUnitLength unitWithFloat:viewBox.size.width];
IJSVGUnitLength* hl = [IJSVGUnitLength unitWithFloat:viewBox.size.height];
if (w != nil) {
wl = [IJSVGUnitLength unitWithString:w];
}
proposedViewSize = NSMakeSize(w, h);
if (h != nil) {
hl = [IJSVGUnitLength unitWithString:h];
}
// store the width and height
_intrinsicSize = [IJSVGUnitSize sizeWithWidth:wl height:hl].retain;
// the root element is SVG, so iterate over its children
// recursively
@@ -232,16 +266,8 @@
intoGroup:self
def:NO];
// now everything has been done we need to compute the style tree
for (NSDictionary* dict in _parsedNodes) {
[self _postParseElementForCommonAttributes:dict[@"element"]
node:dict[@"node"]
ignoreAttributes:nil];
}
// dont need the style sheet or the parsed nodes as this point
(void)([_styleSheet release]), _styleSheet = nil;
(void)([_parsedNodes release]), _parsedNodes = nil;
(void)([_defNodes release]), _defNodes = nil;
(void)IJSVGPathDataStreamRelease(_commandDataStream), _commandDataStream = NULL;
}
@@ -317,22 +343,13 @@
}
// floats
atts(@{ (NSString*)IJSVGAttributeX : @"x",
(NSString*)IJSVGAttributeY : @"y",
(NSString*)IJSVGAttributeWidth : @"width",
(NSString*)IJSVGAttributeHeight : @"height",
(NSString*)IJSVGAttributeOpacity : @"opacity",
(NSString*)IJSVGAttributeStrokeOpacity : @"strokeOpacity",
(NSString*)IJSVGAttributeStrokeWidth : @"strokeWidth",
(NSString*)IJSVGAttributeStrokeDashOffset : @"strokeDashOffset",
(NSString*)IJSVGAttributeFillOpacity : @"fillOpacity" },
atts(_IJSVGAttributeDictionaryFloats,
^id(NSString* value) {
return [IJSVGUnitLength unitWithString:value];
});
// nodes
atts(@{ (NSString*)IJSVGAttributeClipPath : @"clipPath",
(NSString*)IJSVGAttributeMask : @"mask" },
atts(_IJSVGAttributeDictionaryNodes,
^id(NSString* value) {
NSString* url = [IJSVGUtils defURL:value];
if (url != nil) {
@@ -342,16 +359,13 @@
});
// units
atts(@{ (NSString*)IJSVGAttributeGradientUnits : @"units",
(NSString*)IJSVGAttributeMaskUnits : @"units",
(NSString*)IJSVGAttributeMaskContentUnits : @"contentUnits" },
atts(_IJSVGAttributeDictionaryUnits,
^id(NSString* value) {
return @([IJSVGUtils unitTypeForString:value]);
});
// transforms
atts(@{ (NSString*)IJSVGAttributeTransform : @"transforms",
(NSString*)IJSVGAttributeGradientTransform : @"transforms" },
atts(_IJSVGAttributeDictionaryTransforms,
^(NSString* value) {
NSMutableArray* tempTransforms = [[[NSMutableArray alloc] init] autorelease];
[tempTransforms addObjectsFromArray:[IJSVGTransform transformsForString:value]];
@@ -454,6 +468,24 @@
node.shouldRender = NO;
}
});
// is there a title or desc?
for(NSXMLElement* childElement in element.children) {
IJSVGNodeType type = [IJSVGNode typeForString:childElement.localName
kind:childElement.kind];
switch(type) {
case IJSVGNodeTypeTitle: {
node.title = childElement.stringValue;
break;
}
case IJSVGNodeTypeDesc: {
node.desc = childElement.stringValue;
break;
}
default: {
}
}
}
}
- (id)definedObjectForID:(NSString*)anID
@@ -483,25 +515,28 @@
- (BOOL)isFont
{
return [_glyphs count] != 0;
return _glyphs != nil && [_glyphs count] != 0;
}
- (NSArray*)glyphs
{
return _glyphs;
return _glyphs ?: @[];
}
- (void)addSubSVG:(IJSVG*)anSVG
{
if (_svgs == nil) {
_svgs = [[NSMutableArray alloc] init];
}
[_svgs addObject:anSVG];
}
- (NSArray<IJSVG*>*)subSVGs:(BOOL)recursive
{
if (recursive == NO) {
return _svgs;
return _svgs ?: @[];
}
NSMutableArray* svgs = [[[NSMutableArray alloc] init] autorelease];
NSMutableArray<IJSVG*>* svgs = [[[NSMutableArray alloc] init] autorelease];
for (IJSVG* anSVG in svgs) {
[svgs addObject:anSVG];
[svgs addObjectsFromArray:[anSVG subSVGs:recursive]];
@@ -511,6 +546,9 @@
- (void)addGlyph:(IJSVGNode*)glyph
{
if (_glyphs == nil) {
_glyphs = [[NSMutableArray alloc] init];
}
[_glyphs addObject:glyph];
}
@@ -581,6 +619,9 @@
default: {
// just a default def, continue on, as we are a def element,
// store these seperately to the default ID string ones
if (_baseDefNodes == nil) {
_baseDefNodes = [[NSMutableDictionary alloc] init];
}
NSString* defID = [childDef attributeForName:@"id"].stringValue;
if (defID != nil) {
_baseDefNodes[defID] = childDef;
@@ -891,7 +932,7 @@
// use
case IJSVGNodeTypeUse: {
NSString* xlink = [[element attributeForName:(NSString*)IJSVGAttributeXLink] stringValue];
NSString* xlink = [[self resolveXLinkAttributeForElement:element] stringValue];
NSString* xlinkID = [xlink substringFromIndex:1];
IJSVGNode* node = [self definedObjectForID:xlinkID];
@@ -938,7 +979,7 @@
// linear gradient
case IJSVGNodeTypeLinearGradient: {
NSString* xlink = [[element attributeForName:(NSString*)IJSVGAttributeXLink] stringValue];
NSString* xlink = [[self resolveXLinkAttributeForElement:element] stringValue];
NSString* xlinkID = [xlink substringFromIndex:1];
NSXMLElement* referenceElement;
IJSVGNode* node = [self definedObjectForID:xlinkID
@@ -975,7 +1016,7 @@
// radial gradient
case IJSVGNodeTypeRadialGradient: {
NSString* xlink = [[element attributeForName:(NSString*)IJSVGAttributeXLink] stringValue];
NSString* xlink = [[self resolveXLinkAttributeForElement:element] stringValue];
NSString* xlinkID = [xlink substringFromIndex:1];
NSXMLElement* referenceElement;
IJSVGNode* node = [self definedObjectForID:xlinkID
@@ -1064,8 +1105,8 @@
ignoreAttributes:nil];
// from base64
NSString* string = [element attributeForName:(NSString*)IJSVGAttributeXLink].stringValue;
[image loadFromBase64EncodedString:string];
NSXMLNode* attributeNode = [self resolveXLinkAttributeForElement:element];
[image loadFromBase64EncodedString:attributeNode.stringValue];
// add to parent
[parentGroup addChild:image];
@@ -1075,6 +1116,17 @@
}
}
- (NSXMLNode*)resolveXLinkAttributeForElement:(NSXMLElement*)element
{
NSString* const namespaceURI = @"http://www.w3.org/1999/xlink";
NSXMLNode* attributeNode = [element attributeForLocalName:@"href"
URI:namespaceURI];
if (attributeNode == nil) {
attributeNode = [element attributeForName:(NSString*)IJSVGAttributeXLink];
}
return attributeNode;
}
- (NSXMLElement*)mergedElement:(NSXMLElement*)element
withReferenceElement:(NSXMLElement*)reference
{
@@ -1104,22 +1156,14 @@
#pragma mark Parser stuff!
- (void)_parsePathCommandData:(NSString*)command
intoPath:(IJSVGPath*)path
- (void)_parsePathCommandDataBuffer:(const char*)buffer
intoPath:(IJSVGPath*)path
{
// invalid command
if (command == nil || command.length == 0) {
return;
}
// allocate memory for the string buffer for reading
NSUInteger len = command.length;
NSUInteger len = strlen(buffer);
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));
// make sure we plus 1 for the null byte
char* charBuffer = (char*)malloc(sizeof(char)*(len + 1));
NSInteger start = 0;
IJSVGCommand* _currentCommand = nil;
for (NSInteger i = 0; i < len; i++) {
@@ -1130,20 +1174,26 @@
// copy memory from current buffer
NSInteger index = ((i + 1) - start);
memcpy(&charBuffer[0], &buffer[start], sizeof(char) * index);
memcpy(&charBuffer[0], &buffer[start], sizeof(char)*index);
charBuffer[index] = '\0';
// create the command from the substring
NSString* commandString = [NSString stringWithUTF8String:charBuffer];
unsigned long length = index + 1;
size_t mlength = sizeof(char)*length;
char* commandString = (char*)malloc(mlength);
memcpy(commandString, &charBuffer[0], mlength);
// reset start position
start = (i + 1);
// previous command is actual subcommand
IJSVGCommand* previousCommand = _currentCommand.subCommands.lastObject;
IJSVGCommand* cCommand = [self _parseCommandString:commandString
previousCommand:previousCommand
intoPath:path];
IJSVGCommand* cCommand = [self _parseCommandStringBuffer:commandString
previousCommand:previousCommand
intoPath:path];
// free the memory as at this point, we are done with it
(void)free(commandString), commandString = NULL;
// retain the current one
if (cCommand != nil) {
@@ -1151,12 +1201,26 @@
}
}
}
free(charBuffer);
(void)free(charBuffer), charBuffer = NULL;
}
- (IJSVGCommand*)_parseCommandString:(NSString*)string
previousCommand:(IJSVGCommand*)previousCommand
intoPath:(IJSVGPath*)path
- (void)_parsePathCommandData:(NSString*)command
intoPath:(IJSVGPath*)path
{
// invalid command
if (command == nil || command.length == 0) {
return;
}
// allocate memory for the string buffer for reading
const char* buffer = command.UTF8String;
[self _parsePathCommandDataBuffer:buffer
intoPath:path];
}
- (IJSVGCommand*)_parseCommandStringBuffer:(const char*)buffer
previousCommand:(IJSVGCommand*)previousCommand
intoPath:(IJSVGPath*)path
{
// work out the last command - the reason this is so long is because the command
// could be a series of the same commands, so work it out by the number of parameters
@@ -1168,9 +1232,10 @@
// main commands
// Class commandClass = [IJSVGCommand classFor]
Class commandClass = [IJSVGCommand commandClassForCommandChar:[string characterAtIndex:0]];
IJSVGCommand* command = (IJSVGCommand*)[[[commandClass alloc] initWithCommandString:string
dataStream:_commandDataStream] autorelease];
Class commandClass = [IJSVGCommand commandClassForCommandChar:buffer[0]];
IJSVGCommand* command = nil;
command = (IJSVGCommand*)[[[commandClass alloc] initWithCommandStringBuffer:buffer
dataStream:_commandDataStream] autorelease];
for (IJSVGCommand* subCommand in command.subCommands) {
[command.class runWithParams:subCommand.parameters
paramCount:subCommand.parameterCount
@@ -1195,12 +1260,11 @@
CGFloat y2 = [element attributeForName:(NSString*)IJSVGAttributeY2].stringValue.floatValue;
// use sprintf as its quicker then stringWithFormat...
char buffer[50];
sprintf(buffer, "M%.2f %.2fL%.2f %.2f", x1, y1, x2, y2);
NSString* command = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
[self _parsePathCommandData:command
intoPath:path];
char* buffer;
asprintf(&buffer, "M%.2f %.2fL%.2f %.2f", x1, y1, x2, y2);
[self _parsePathCommandDataBuffer:buffer
intoPath:path];
(void)free(buffer);
}
- (void)_parseCircle:(NSXMLElement*)element
@@ -1258,23 +1322,46 @@
free(params);
return;
}
// construct a command
NSInteger capacity = count / 2;
if (closePath == YES) {
capacity += 1;
const int defBufferSize = 5;
char* buffer;
asprintf(&buffer, "M%f %f L", params[0], params[1]);
// compute a default buffer
size_t bSize = strlen(buffer);
size_t strLength = bSize;
// for every pair of coordinates
for(int i = 2; i < count; i+= 2) {
char* subbuf;
asprintf(&subbuf, "%f %f ", params[i], params[i + 1]);
size_t sSize = strlen(subbuf);
// if the new size of the string is large than the buffer
// increase the buffer up another def size
if((strLength + sSize + 1) > bSize) {
size_t nLength = MAX(sSize, defBufferSize) + 2;
buffer = realloc(buffer, sizeof(char)*(bSize+nLength));
bSize += nLength;
}
// append thr string onto the buffer, increment the
// string length and free the subbuffer memory
strcat(buffer, subbuf);
strLength += sSize;
(void)free(subbuf), subbuf = NULL;
}
NSMutableString* str = [[[NSMutableString alloc] initWithCapacity:capacity] autorelease];
[str appendFormat:@"M%f,%f L", params[0], params[1]];
for (NSInteger i = 2; i < count; i += 2) {
[str appendFormat:@"%f,%f ", params[i], params[i + 1]];
if(closePath == YES) {
strcat(buffer, "z");
}
if (closePath) {
[str appendString:@"z"];
}
[self _parsePathCommandData:str
intoPath:path];
free(params);
// actually perform the parse
[self _parsePathCommandDataBuffer:buffer
intoPath:path];
// free the params
(void)free(buffer), buffer = NULL;
(void)free(params), params = NULL;
}
- (void)_parseRect:(NSXMLElement*)element
@@ -623,8 +623,6 @@
if (node.clipPath.units == IJSVGUnitObjectBoundingBox) {
[self adjustLayer:clip
toParentLayerFrame:layer];
} else {
clip.affineTransform = [self absoluteTransform:node];
}
// add the layer
@@ -639,8 +637,6 @@
if (node.mask.units == IJSVGUnitObjectBoundingBox) {
[self adjustLayer:mask
toParentLayerFrame:layer];
} else {
mask.affineTransform = [self absoluteTransform:node];
}
// add the layer
@@ -648,8 +644,9 @@
}
// recursive colourize for each item
NSColor* color = [IJSVGColor computeColorSpace:NSColor.whiteColor];
[self _recursiveColorLayersFromLayer:maskLayer
withColor:[IJSVGColor computeColorSpace:NSColor.whiteColor].CGColor];
withColor:color.CGColor];
// add the mask
layer.mask = maskLayer;
@@ -7,6 +7,7 @@
//
#import "IJSVGMath.h"
#import "IJSVGCommandParser.h"
@implementation IJSVGMath
@@ -10,5 +10,5 @@
#import <QuartzCore/QuartzCore.h>
BOOL IJSVGIsMainThread(void);
void IJSVGBeginTransactionLock(void);
void IJSVGEndTransactionLock(void);
BOOL IJSVGBeginTransaction(void);
void IJSVGEndTransaction(void);
@@ -7,16 +7,23 @@
//
#import "IJSVGTransaction.h"
#import <AppKit/AppKit.h>
BOOL IJSVGIsMainThread(void) { return NSThread.isMainThread; };
void IJSVGBeginTransactionLock(void)
BOOL IJSVGBeginTransaction(void)
{
if(IJSVGIsMainThread() == YES) {
return NO;
}
// use nsanimationcontext as this sets a private flag of 0x4
// of the catransaction for background composites
[CATransaction begin];
[CATransaction setDisableActions:YES];
return YES;
};
void IJSVGEndTransactionLock(void)
void IJSVGEndTransaction(void)
{
[CATransaction commit];
};
@@ -43,10 +43,16 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
CGAffineTransform IJSVGConcatTransforms(NSArray<IJSVGTransform*>* transforms);
NSString* IJSVGTransformAttributeString(CGAffineTransform transform);
+ (NSArray<NSDictionary*>*)affineTransformToSVGTransformComponents:(CGAffineTransform)transform;
+ (NSString*)affineTransformToSVGTransformComponentString:(CGAffineTransform)transform
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (NSString*)affineTransformToSVGTransformComponentString:(CGAffineTransform)transform;
+ (NSArray<IJSVGTransform*>*)transformsFromAffineTransform:(CGAffineTransform)affineTransform;
+ (NSArray*)transformsForString:(NSString*)string;
+ (NSArray<IJSVGTransform*>*)transformsForString:(NSString*)string;
+ (NSBezierPath*)transformedPath:(IJSVGPath*)path;
+ (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)affineTransform;
+ (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)transform
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (CGAffineTransform)CGAffineTransform;
- (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier;
- (CGAffineTransform)stackIdentity:(CGAffineTransform)identity;
@@ -18,7 +18,7 @@
- (void)dealloc
{
free(parameters);
(void)free(parameters);
[super dealloc];
}
@@ -137,40 +137,76 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
return 10;
}
+ (NSArray*)transformsForString:(NSString*)string
+ (NSArray<IJSVGTransform*>*)transformsForString:(NSString*)string
{
static NSRegularExpression* _reg = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_reg = [[NSRegularExpression alloc] initWithPattern:@"([a-zA-Z]+)\\(([^\\)]+)\\)"
options:0
error:nil];
});
NSMutableArray* transforms = [[[NSMutableArray alloc] init] autorelease];
@autoreleasepool {
[_reg enumerateMatchesInString:string
options:0
range:NSMakeRange(0, string.length)
usingBlock:^(NSTextCheckingResult* result, NSMatchingFlags flags, BOOL* stop) {
NSString* command = [string substringWithRange:[result rangeAtIndex:1]];
IJSVGTransformCommand commandType = [self.class commandForCommandString:command];
if (commandType == IJSVGTransformCommandNotImplemented) {
return;
}
// create the transform
NSString* params = [string substringWithRange:[result rangeAtIndex:2]];
IJSVGTransform* transform = [[[self.class alloc] init] autorelease];
NSInteger count = 0;
transform.command = commandType;
transform.parameters = [IJSVGUtils commandParameters:params
count:&count];
transform.parameterCount = count;
transform.sort = [self.class sortForTransformCommand:commandType];
[transforms addObject:transform];
}];
NSMutableArray<IJSVGTransform*>* array = nil;
array = [[[NSMutableArray alloc] init] autorelease];
// setup the buffer for the string to be stored in
const char* charString = string.UTF8String;
unsigned long length = strlen(charString);
char* buffer = (char*)calloc(sizeof(char), length);
char* originBuffer = buffer;
int bufferIndex = 0;
// each command requires a name and parameters, store for later use
NSString* commandName = nil;
NSString* params = nil;
for(int i = 0; i < length; i++) {
char currentChar = *charString++;
// start of params - store the command name as its current in the buffer
if(currentChar == '(') {
// rest the pointer to beginning
buffer = originBuffer;
IJSVGTrimCharBuffer(buffer);
commandName = [NSString stringWithUTF8String:buffer];
// write null up until the limit we reached
memset(buffer, '\0', bufferIndex);
bufferIndex = 0;
continue;
}
// end of params - store the params into the buffer
if(currentChar == ')') {
// rest the pointer to beginning
buffer = originBuffer;
params = [NSString stringWithUTF8String:buffer];
// write null up until the limit we reached
memset(buffer, '\0', bufferIndex);
bufferIndex = 0;
// at this point we can just add the command
IJSVGTransformCommand commandType = [self.class commandForCommandString:commandName];
if(commandType == IJSVGTransformCommandNotImplemented) {
continue;
}
// create a new transform object and parse the parameters
NSInteger count = 0;
IJSVGTransform* transform = [[[self.class alloc] init] autorelease];
transform.command = commandType;
transform.sort = [self.class sortForTransformCommand:commandType];
transform.parameters = [IJSVGUtils commandParameters:params
count:&count];
transform.parameterCount = count;
// add to the list of transforms to return
[array addObject:transform];
// reset these values
commandName = nil;
params = nil;
continue;
}
// increment the buffer count
*buffer++ = currentChar;
bufferIndex++;
}
return transforms;
buffer = originBuffer;
(void)free(buffer), buffer = NULL;
return array;
}
+ (NSBezierPath*)transformedPath:(IJSVGPath*)path
@@ -184,7 +220,7 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
switch (transform.command) {
// matrix
case IJSVGTransformCommandMatrix: {
at.transformStruct = (NSAffineTransformStruct){
at.transformStruct = (NSAffineTransformStruct) {
.m11 = transform.parameters[0],
.m12 = transform.parameters[1],
.m21 = transform.parameters[2],
@@ -199,7 +235,7 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
case IJSVGTransformCommandSkewX: {
CGFloat degrees = transform.parameters[0];
CGFloat radians = degrees * M_PI / 180.f;
at.transformStruct = (NSAffineTransformStruct){
at.transformStruct = (NSAffineTransformStruct) {
.m11 = 1.f,
.m12 = 0.f,
.m21 = tan(radians),
@@ -214,7 +250,7 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
case IJSVGTransformCommandSkewY: {
CGFloat degrees = transform.parameters[0];
CGFloat radians = degrees * M_PI / 180.f;
at.transformStruct = (NSAffineTransformStruct){
at.transformStruct = (NSAffineTransformStruct) {
.m11 = 1.f,
.m12 = tan(radians),
.m21 = 0.f,
@@ -493,15 +529,199 @@ void IJSVGApplyTransform(NSArray<IJSVGTransform*>* transforms, IJSVGTransformApp
return [self transformsForString:matrix];
}
+ (NSString*)affineTransformToSVGTransformComponentString:(CGAffineTransform)transform
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
NSArray<NSDictionary*>* trans = [self affineTransformToSVGTransformComponents:transform];
trans = [self filterUselessAffineTransformComponents:trans];
NSMutableArray<NSString*>* strings = [[[NSMutableArray alloc] initWithCapacity:trans.count] autorelease];
for (NSDictionary* dict in trans) {
NSArray<NSNumber*>* data = dict[@"data"];
NSString* method = dict[@"name"];
NSMutableArray* dataStrings = [[[NSMutableArray alloc] initWithCapacity:data.count] autorelease];
for (NSNumber* number in data) {
[dataStrings addObject:IJSVGShortFloatStringWithOptions(number.floatValue,
floatingPointOptions)];
}
[strings addObject:[NSString stringWithFormat:@"%@(%@)", method,
IJSVGCompressFloatParameterArray(dataStrings)]];
}
NSString* componentsString = [strings componentsJoinedByString:@" "];
NSString* matrixString = [self affineTransformToSVGMatrixString:transform
floatingPointOptions:floatingPointOptions];
return componentsString.length < matrixString.length ? componentsString : matrixString;
}
+ (NSString*)affineTransformToSVGTransformComponentString:(CGAffineTransform)transform
{
NSArray<NSDictionary*>* trans = [self affineTransformToSVGTransformComponents:transform];
trans = [self filterUselessAffineTransformComponents:trans];
NSMutableArray<NSString*>* strings = [[[NSMutableArray alloc] initWithCapacity:trans.count] autorelease];
for (NSDictionary* dict in trans) {
NSArray<NSNumber*>* data = dict[@"data"];
NSString* method = dict[@"name"];
NSMutableArray* dataStrings = [[[NSMutableArray alloc] initWithCapacity:data.count] autorelease];
for (NSNumber* number in data) {
[dataStrings addObject:IJSVGShortFloatString(number.floatValue)];
}
[strings addObject:[NSString stringWithFormat:@"%@(%@)", method,
IJSVGCompressFloatParameterArray(dataStrings)]];
}
NSString* componentsString = [strings componentsJoinedByString:@" "];
NSString* matrixString = [self affineTransformToSVGMatrixString:transform];
return componentsString.length < matrixString.length ? componentsString : matrixString;
}
+ (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)transform
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
NSArray<NSString*>* numbers = @[
IJSVGShortFloatStringWithOptions(transform.a, floatingPointOptions),
IJSVGShortFloatStringWithOptions(transform.b, floatingPointOptions),
IJSVGShortFloatStringWithOptions(transform.c, floatingPointOptions),
IJSVGShortFloatStringWithOptions(transform.d, floatingPointOptions),
IJSVGShortFloatStringWithOptions(transform.tx, floatingPointOptions),
IJSVGShortFloatStringWithOptions(transform.ty, floatingPointOptions)
];
return [NSString stringWithFormat:@"matrix(%@)", IJSVGCompressFloatParameterArray(numbers)];
}
+ (NSString*)affineTransformToSVGMatrixString:(CGAffineTransform)transform
{
return [NSString stringWithFormat:@"matrix(%@ %@ %@ %@ %@ %@)",
IJSVGShortFloatString(transform.a),
IJSVGShortFloatString(transform.b),
IJSVGShortFloatString(transform.c),
IJSVGShortFloatString(transform.d),
IJSVGShortFloatString(transform.tx),
IJSVGShortFloatString(transform.ty)];
NSArray<NSString*>* numbers = @[
IJSVGShortFloatString(transform.a),
IJSVGShortFloatString(transform.b),
IJSVGShortFloatString(transform.c),
IJSVGShortFloatString(transform.d),
IJSVGShortFloatString(transform.tx),
IJSVGShortFloatString(transform.ty)
];
return [NSString stringWithFormat:@"matrix(%@)",
IJSVGCompressFloatParameterArray(numbers)];
}
+ (NSArray<NSDictionary*>*)filterUselessAffineTransformComponents:(NSArray<NSDictionary*>*)components
{
NSMutableArray* comps = [[[NSMutableArray alloc] initWithCapacity:components.count] autorelease];
NSArray<NSString*>* names = @[ @"translate", @"rotate", @"skewX", @"skewY" ];
for (NSDictionary* transform in components) {
NSString* name = transform[@"name"];
NSArray<NSNumber*>* data = transform[@"data"];
if ([names containsObject:name] && (data.count == 1 || [name isEqualToString:@"rotate"]) && data[0].floatValue == 0.f) {
continue;
} else if ([name isEqualToString:@"translate"] && data[0].floatValue == 0.f && data[1].floatValue == 0.f) {
continue;
} else if ([name isEqualToString:@"scale"] && data[0].floatValue == 1.f && (data.count < 2 || (data.count == 2 && data[1].floatValue == 1.f))) {
continue;
} else if ([name isEqualToString:@"matrix"] && data[0].floatValue == 1.f && data[3].floatValue == 1.f && !(data[1].floatValue != 0.f || data[2].floatValue != 0.f || data[4].floatValue != 0.f || data[5].floatValue != 0.f)) {
continue;
}
[comps addObject:transform];
}
return comps;
}
+ (NSArray<NSDictionary*>*)affineTransformToSVGTransformComponents:(CGAffineTransform)transform
{
const NSUInteger precision = 5;
CGFloat data[6] = {
IJSVGMathToFixed(transform.a, precision),
IJSVGMathToFixed(transform.b, precision),
IJSVGMathToFixed(transform.c, precision),
IJSVGMathToFixed(transform.d, precision),
IJSVGMathToFixed(transform.tx, precision),
IJSVGMathToFixed(transform.ty, precision)
};
CGFloat sx = IJSVGMathToFixed(hypotf(data[0], data[1]), precision);
CGFloat sy = IJSVGMathToFixed(((data[0] * data[3] - data[1] * data[2]) / sx), precision);
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* transforms = [[[NSMutableArray alloc] init] autorelease];
// tx, ty -> translate
if (data[4] != 0.f || data[5] != 0.f) {
[transforms addObject:@{
@"name" : @"translate",
@"data" : @[ @(data[4]), @(data[5]) ]
}];
}
// [sx, 0, tan(a).sy, sy, 0, 0] -> skewX(a).scale(sx,sy)
if (data[1] == 0.f && data[2] != 0.f) {
[transforms addObject:@{
@"name" : @"skewX",
@"data" : @[ @(IJSVGMathToFixed(IJSVGMathAtan(data[2] / sy), precision)) ]
}];
// [sx, sy.tan(a), 0, sy, 0, 0] -> skewX(a).scale(sx, sy)
} else if (data[1] != 0.f && data[2] == 0.f) {
[transforms addObject:@{
@"name" : @"skewY",
@"data" : @[ @(IJSVGMathToFixed(IJSVGMathAtan(data[1] / data[0]), precision)) ]
}];
sx = data[0];
sy = data[3];
} else if (colSum == 0.f || (sx == 1.f && sy == 1.f) || !scaleBefore) {
if (!scaleBefore) {
sx = (data[0] < 0.f ? -1.f : 1.f) * hypotf(data[0], data[2]);
sy = (data[3] < 0.f ? -1.f : 1.f) * hypotf(data[1], data[3]);
if (sx != 1.f || sy != 1.f) {
[transforms addObject:@{
@"name" : @"scale",
@"data" : (sx == sy) ? @[ @(sx) ] : @[ @(sx), @(sy) ]
}];
}
}
CGFloat angle = MIN(MAX(-1.f, data[0] / sx), 1.f);
NSMutableArray<NSNumber*>* rotate = [[[NSMutableArray alloc] initWithCapacity:3] autorelease];
[rotate addObject:@(IJSVGMathToFixed(IJSVGMathAcos(angle), precision) * ((scaleBefore ? 1.f : sy) * data[1] < 0.f ? -1.f : 1.f))];
if (rotate[0].floatValue != 0.f) {
[transforms addObject:@{
@"name" : @"rotate",
@"data" : rotate
}];
}
if (rowSum != 0.f && colSum != 0.f) {
[transforms addObject:@{
@"name" : @"skewX",
@"data" : @[ @(IJSVGMathToFixed(IJSVGMathAtan(colSum / (sx * sx)), precision)) ]
}];
}
// rotate can consume translate
if (rotate[0].floatValue != 0.f && (data[4] != 0.f || data[5] != 0.f)) {
[transforms 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));
[rotate addObject:@(((1.f - cos) * x - sin * y) / denom)];
[rotate addObject:@(((1.f - cos) * y + sin * x) / denom)];
}
} else if (data[1] != 0.f || data[2] != 0.f) {
NSDictionary* trans = @{
@"name" : @"matrix",
@"data" : @[ @(data[0]), @(data[1]), @(data[2]), @(data[3]), @(data[4]), @(data[5]) ]
};
return @[ trans ];
}
if (scaleBefore == YES && ((sx != 1.f || sy != 1.f) || transforms.count == 0)) {
NSDictionary* trans = @{
@"name" : @"scale",
@"data" : (sx == sy) ? @[ @(sx) ] : @[ @(sx), @(sy) ]
};
[transforms addObject:trans];
}
return transforms;
}
- (NSString*)description
@@ -8,9 +8,20 @@
#import <Foundation/Foundation.h>
typedef struct {
BOOL round;
int precision;
} IJSVGFloatingPointOptions;
typedef NS_ENUM(NSInteger, IJSVGUnitLengthType) {
IJSVGUnitLengthTypeNumber,
IJSVGUnitLengthTypePercentage
IJSVGUnitLengthTypePercentage,
IJSVGUnitLengthTypeCM,
IJSVGUnitLengthTypeMM,
IJSVGUnitLengthTypeIN,
IJSVGUnitLengthTypePT,
IJSVGUnitLengthTypePC,
IJSVGUnitLengthTypePX
};
typedef NS_ENUM(NSInteger, IJSVGUnitType) {
@@ -22,6 +33,7 @@ typedef NS_ENUM(NSInteger, IJSVGUnitType) {
@interface IJSVGUnitLength : NSObject
@property (nonatomic, assign) IJSVGUnitLengthType type;
@property (nonatomic, assign) IJSVGUnitLengthType originalType;
@property (nonatomic, assign) CGFloat value;
@property (nonatomic, assign) BOOL inherit;
@@ -38,5 +50,6 @@ typedef NS_ENUM(NSInteger, IJSVGUnitType) {
- (CGFloat)valueAsPercentage;
- (CGFloat)computeValue:(CGFloat)anotherValue;
- (NSString*)stringValue;
- (NSString*)stringValueWithFloatingPointOptions:(IJSVGFloatingPointOptions)options;
@end
@@ -15,6 +15,7 @@
@synthesize value;
@synthesize type;
@synthesize inherit;
@synthesize originalType;
+ (IJSVGUnitLength*)unitWithFloat:(CGFloat)number
{
@@ -55,6 +56,64 @@
return unit;
}
+ (IJSVGUnitLengthType)typeForString:(NSString*)string
{
if([string hasSuffix:@"%"] == YES) {
return IJSVGUnitLengthTypePercentage;
}
if([string hasSuffix:@"cm"] == YES) {
return IJSVGUnitLengthTypeCM;
}
if([string hasSuffix:@"mm"] == YES) {
return IJSVGUnitLengthTypeMM;
}
if([string hasSuffix:@"in"] == YES) {
return IJSVGUnitLengthTypeIN;
}
if([string hasSuffix:@"pt"] == YES) {
return IJSVGUnitLengthTypePT;
}
if([string hasSuffix:@"pc"] == YES) {
return IJSVGUnitLengthTypePC;
}
if([string hasSuffix:@"px"] == YES) {
return IJSVGUnitLengthTypePX;
}
return IJSVGUnitLengthTypeNumber;
}
+ (CGFloat)convertUnitValue:(CGFloat)unit
toBaseFromUnitLengthType:(IJSVGUnitLengthType)type
{
switch(type) {
case IJSVGUnitLengthTypeCM: {
return unit * (96.f / 2.54f);
}
case IJSVGUnitLengthTypeMM: {
return [self convertUnitValue:unit
toBaseFromUnitLengthType:IJSVGUnitLengthTypeCM] / 10.f;
}
case IJSVGUnitLengthTypePercentage: {
return unit / 100.f;
}
case IJSVGUnitLengthTypeIN: {
// 1in = 96px
return unit * 96.f;
}
case IJSVGUnitLengthTypePT: {
// 1pt = 1.333...px
return unit * 1.3333333f;
}
case IJSVGUnitLengthTypePC: {
// 1pc = 16px
return unit * 16.f;
}
default:
break;
}
return unit;
}
+ (IJSVGUnitLength*)unitWithString:(NSString*)string
{
// just return noting for inherit, node will deal
@@ -69,9 +128,20 @@
IJSVGUnitLength* unit = [[[self alloc] init] autorelease];
unit.value = string.floatValue;
unit.type = IJSVGUnitLengthTypeNumber;
if ([string hasSuffix:@"%"] == YES) {
unit.value /= 100.f;
unit.type = IJSVGUnitLengthTypePercentage;
IJSVGUnitLengthType type = [self typeForString:string];
unit.originalType = type;
switch(type) {
case IJSVGUnitLengthTypePercentage: {
unit.value = [self convertUnitValue:unit.value
toBaseFromUnitLengthType:type];
unit.type = IJSVGUnitLengthTypePercentage;
break;
}
default:
unit.value = [self convertUnitValue:unit.value
toBaseFromUnitLengthType:type];
break;
}
return unit;
}
@@ -92,11 +162,21 @@
- (NSString*)stringValue
{
if (self.type == IJSVGUnitLengthTypePercentage) {
return [NSString stringWithFormat:@"%@%%", IJSVGShortFloatString(self.value * 100.f)];
return [NSString stringWithFormat:@"%@%%",
IJSVGShortFloatString(self.value * 100.f)];
}
return IJSVGShortFloatString(self.value);
}
- (NSString*)stringValueWithFloatingPointOptions:(IJSVGFloatingPointOptions)options
{
if (self.type == IJSVGUnitLengthTypePercentage) {
return [NSString stringWithFormat:@"%@%%",
IJSVGShortFloatStringWithOptions(self.value * 100.f, options)];
}
return IJSVGShortFloatStringWithOptions(self.value, options);
}
- (NSString*)description
{
return [NSString stringWithFormat:@"%f%@",
@@ -0,0 +1,17 @@
//
// IJSVGUnitPoint.h
// IJSVG
//
// Created by Curtis Hard on 12/02/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGUnitLength.h"
#import <Foundation/Foundation.h>
@interface IJSVGUnitPoint : NSObject
@property (nonatomic, retain) IJSVGUnitLength* x;
@property (nonatomic, retain) IJSVGUnitLength* y;
@end
@@ -0,0 +1,32 @@
//
// IJSVGUnitPoint.m
// IJSVG
//
// Created by Curtis Hard on 12/02/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGUnitPoint.h"
@implementation IJSVGUnitPoint
@synthesize x = _x;
@synthesize y = _y;
- (void)dealloc
{
(void)[_x release], _x = nil;
(void)[_y release], _y = nil;
[super dealloc];
}
+ (IJSVGUnitPoint*)pointWithX:(IJSVGUnitLength*)x
y:(IJSVGUnitLength*)y
{
IJSVGUnitPoint* point = [[[self alloc] init] autorelease];
point.x = x;
point.y = y;
return point;
}
@end
@@ -0,0 +1,21 @@
//
// IJSVGUnitRect.h
// IJSVG
//
// Created by Curtis Hard on 12/02/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGUnitPoint.h"
#import "IJSVGUnitSize.h"
#import <Foundation/Foundation.h>
@interface IJSVGUnitRect : NSObject
@property (nonatomic, retain) IJSVGUnitSize* size;
@property (nonatomic, retain) IJSVGUnitPoint* origin;
+ (IJSVGUnitRect*)rectWithOrigin:(IJSVGUnitPoint*)origin
size:(IJSVGUnitSize*)size;
@end
@@ -0,0 +1,32 @@
//
// IJSVGUnitRect.m
// IJSVG
//
// Created by Curtis Hard on 12/02/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGUnitRect.h"
@implementation IJSVGUnitRect
@synthesize size = _size;
@synthesize origin = _origin;
- (void)dealloc
{
(void)[_size release], _size = nil;
(void)[_origin release], _origin = nil;
[super dealloc];
}
+ (IJSVGUnitRect*)rectWithOrigin:(IJSVGUnitPoint*)origin
size:(IJSVGUnitSize*)size
{
IJSVGUnitRect* rect = [[[self alloc] init] autorelease];
rect.origin = origin;
rect.size = size;
return rect;
}
@end
@@ -0,0 +1,20 @@
//
// IJSVGUnitSize.h
// IJSVG
//
// Created by Curtis Hard on 12/02/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGUnitLength.h"
#import <Foundation/Foundation.h>
@interface IJSVGUnitSize : NSObject
@property (nonatomic, retain) IJSVGUnitLength* width;
@property (nonatomic, retain) IJSVGUnitLength* height;
+ (IJSVGUnitSize*)sizeWithWidth:(IJSVGUnitLength*)width
height:(IJSVGUnitLength*)height;
@end
@@ -0,0 +1,32 @@
//
// IJSVGUnitSize.m
// IJSVG
//
// Created by Curtis Hard on 12/02/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import "IJSVGUnitSize.h"
@implementation IJSVGUnitSize
@synthesize width = _width;
@synthesize height = _height;
- (void)dealloc
{
(void)[_width release], _width = nil;
(void)[_height release], _height = nil;
[super dealloc];
}
+ (IJSVGUnitSize*)sizeWithWidth:(IJSVGUnitLength*)width
height:(IJSVGUnitLength*)height
{
IJSVGUnitSize* size = [[[self alloc] init] autorelease];
size.width = width;
size.height = height;
return size;
}
@end
@@ -21,9 +21,16 @@ CGFloat angle(CGPoint a, CGPoint b);
CGFloat radians_to_degrees(CGFloat radians);
CGFloat degrees_to_radians(CGFloat degrees);
void IJSVGTrimCharBuffer(char* buffer);
BOOL IJSVGIsCommonHTMLElementName(NSString* str);
NSArray* IJSVGCommonHTMLElementNames(void);
IJSVGFloatingPointOptions IJSVGFloatingPointOptionsDefault(void);
IJSVGFloatingPointOptions IJSVGFloatingPointOptionsMake(BOOL round, int precision);
NSString* IJSVGCompressFloatParameterArray(NSArray<NSString*>* stringToCompress);
NSString* IJSVGShortFloatStringWithOptions(CGFloat f, IJSVGFloatingPointOptions options);
NSString* IJSVGShortenFloatString(NSString* string);
NSString* IJSVGPointToCommandString(CGPoint point);
NSString* IJSVGShortFloatString(CGFloat f);
@@ -9,9 +9,24 @@
#import "IJSVGLayer.h"
#import "IJSVGShapeLayer.h"
#import "IJSVGUtils.h"
#import "IJSVGExporterPathInstruction.h"
@implementation IJSVGUtils
void IJSVGTrimCharBuffer(char* buffer)
{
char* ptr = buffer;
unsigned long length = strlen(ptr);
while(length-1 > 0 && isspace(ptr[length-1])) {
ptr[--length] = '\0';
}
while(*ptr && isspace(*ptr)) {
++ptr;
--length;
}
memmove(buffer, ptr, length+1);
}
BOOL IJSVGIsCommonHTMLElementName(NSString* str)
{
str = str.lowercaseString;
@@ -163,7 +178,7 @@ NSArray* IJSVGCommonHTMLElementNames(void)
NSString* IJSVGShortenFloatString(NSString* string)
{
const char* chars = string.UTF8String;
if (chars[0] == '-' && chars[1] == '0') {
if (chars[0] == '-' && chars[1] == '0' && strstr(chars, ".") != NULL) {
return [NSString stringWithFormat:@"-%@", [string substringFromIndex:2]];
} else if (chars[0] == '0' && chars[1] == '.') {
return [string substringFromIndex:1];
@@ -171,11 +186,64 @@ NSString* IJSVGShortenFloatString(NSString* string)
return string;
}
IJSVGFloatingPointOptions IJSVGFloatingPointOptionsDefault(void)
{
return IJSVGFloatingPointOptionsMake(NO, kIJSVGExporterPathInstructionFloatPrecision);
}
IJSVGFloatingPointOptions IJSVGFloatingPointOptionsMake(BOOL round, int precision)
{
return (IJSVGFloatingPointOptions) {
.round = round,
.precision = precision
};
}
NSString* IJSVGShortFloatStringWithOptions(CGFloat f, IJSVGFloatingPointOptions options)
{
if (options.round == YES) {
f = IJSVGExporterPathFloatToFixed(f, options.precision);
}
return IJSVGShortFloatString(f);
};
NSString* IJSVGShortFloatString(CGFloat f)
{
return IJSVGShortenFloatString([NSString stringWithFormat:@"%g", f]);
};
NSString* IJSVGCompressFloatParameterArray(NSArray<NSString*>* strings)
{
char* lastCommandChars = NULL;
NSInteger index = 0;
NSMutableString* string = [[[NSMutableString alloc] init] autorelease];
for (NSString* dataString in strings) {
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;
}
return string;
};
NSString* IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision)
{
NSString* format = [NSString stringWithFormat:@"%@.%ld%@", @"%", precision, @"f"];
@@ -188,7 +256,7 @@ NSString* IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision)
NSString* IJSVGPointToCommandString(CGPoint point)
{
return [NSString stringWithFormat:@"%@,%@",
return [NSString stringWithFormat:@"%@ %@",
IJSVGShortFloatString(point.x),
IJSVGShortFloatString(point.y)];
};
@@ -244,7 +312,7 @@ CGFloat degrees_to_radians(CGFloat degrees)
const char* characters = string.UTF8String;
unsigned long length = strlen(characters);
for (NSInteger i = 0; i < length; i++) {
char c = characters[i];
char c = *characters++;
if (c == '(') {
range.location = i + 1;
} else if (c == ')') {