diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj b/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj index 2b708ea..9319c8a 100644 --- a/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj +++ b/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj @@ -8,6 +8,12 @@ /* Begin PBXBuildFile section */ 343A19181FB2212C000652A2 /* IJSVGGradientUnitLength.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A19171FB2212C000652A2 /* IJSVGGradientUnitLength.m */; }; + 590C87EB201F9888004A1554 /* json.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87EA201F9888004A1554 /* json.svg */; }; + 590C87ED201FA08C004A1554 /* intertwingly.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87EC201FA08C004A1554 /* intertwingly.svg */; }; + 590C87EF201FA093004A1554 /* NewTux.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87EE201FA092004A1554 /* NewTux.svg */; }; + 590C87F1201FBF27004A1554 /* AJ_Digital_Camera.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87F0201FBF27004A1554 /* AJ_Digital_Camera.svg */; }; + 590C87F4201FC9E4004A1554 /* radialgradient2.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87F3201FC9E3004A1554 /* radialgradient2.svg */; }; + 590C87F6201FD0D4004A1554 /* car.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87F5201FD0D4004A1554 /* car.svg */; }; 591704CC1E2016E400012644 /* IJSVGExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 591704CB1E2016E400012644 /* IJSVGExporter.m */; }; 591A13E11E19838F001D1629 /* IJSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 591A13E01E19838F001D1629 /* IJSVGText.m */; }; 59265CE81C4F840400F333F0 /* css.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59265CE71C4F840400F333F0 /* css.svg */; }; @@ -33,6 +39,8 @@ 5986308A19BA106D00CF15EA /* SVGExampleView5.m in Sources */ = {isa = PBXBuildFile; fileRef = 5986308919BA106D00CF15EA /* SVGExampleView5.m */; }; 5986308C19BA180E00CF15EA /* dashed.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5986308B19BA180E00CF15EA /* dashed.svg */; }; 598759F61E242C850024CC3F /* IJSVGExporterPathInstruction.m in Sources */ = {isa = PBXBuildFile; fileRef = 598759F51E242C850024CC3F /* IJSVGExporterPathInstruction.m */; }; + 5991A2A9201E30E600913E3B /* gradients.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5991A2A8201E30E600913E3B /* gradients.svg */; }; + 5991A2AB201E310200913E3B /* heart.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5991A2AA201E310200913E3B /* heart.svg */; }; 599465DC1C4AA87200A2EEF3 /* IJSVGStyleSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 599465D51C4AA87200A2EEF3 /* IJSVGStyleSheet.m */; }; 599465DD1C4AA87200A2EEF3 /* IJSVGStyleSheetRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 599465D71C4AA87200A2EEF3 /* IJSVGStyleSheetRule.m */; }; 599465DE1C4AA87200A2EEF3 /* IJSVGStyleSheetSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = 599465D91C4AA87200A2EEF3 /* IJSVGStyleSheetSelector.m */; }; @@ -43,6 +51,8 @@ 59A3DA6D1E283022003E59A9 /* IJSVGTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A3DA6C1E283022003E59A9 /* IJSVGTransaction.m */; }; 59B93C6D19B7D1840063E823 /* paperplane.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59B93C6C19B7D1840063E823 /* paperplane.svg */; }; 59B93C6F19B7D32C0063E823 /* products.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59B93C6E19B7D32C0063E823 /* products.svg */; }; + 59D1E39E2022577500C54672 /* IJSVGQuartzRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59D1E39D2022577500C54672 /* IJSVGQuartzRenderer.m */; }; + 59D1E3A0202279CA00C54672 /* Group.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59D1E39F202279CA00C54672 /* Group.svg */; }; 59E0F5ED1E29964700F757F7 /* IJSVGUnitLength.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */; }; 59E2645119BA240D008A6FDB /* IJSVG.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2641C19BA240D008A6FDB /* IJSVG.m */; }; 59E2645219BA240D008A6FDB /* IJSVGBezierPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2641E19BA240D008A6FDB /* IJSVGBezierPathAdditions.m */; }; @@ -93,6 +103,12 @@ /* Begin PBXFileReference section */ 343A19161FB2212C000652A2 /* IJSVGGradientUnitLength.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGGradientUnitLength.h; sourceTree = ""; }; 343A19171FB2212C000652A2 /* IJSVGGradientUnitLength.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGGradientUnitLength.m; sourceTree = ""; }; + 590C87EA201F9888004A1554 /* json.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = json.svg; sourceTree = ""; }; + 590C87EC201FA08C004A1554 /* intertwingly.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = intertwingly.svg; sourceTree = ""; }; + 590C87EE201FA092004A1554 /* NewTux.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = NewTux.svg; sourceTree = ""; }; + 590C87F0201FBF27004A1554 /* AJ_Digital_Camera.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = AJ_Digital_Camera.svg; sourceTree = ""; }; + 590C87F3201FC9E3004A1554 /* radialgradient2.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = radialgradient2.svg; sourceTree = ""; }; + 590C87F5201FD0D4004A1554 /* car.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = car.svg; sourceTree = ""; }; 591704CA1E2016E400012644 /* IJSVGExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGExporter.h; sourceTree = ""; }; 591704CB1E2016E400012644 /* IJSVGExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGExporter.m; sourceTree = ""; }; 591A13DF1E19838F001D1629 /* IJSVGText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGText.h; sourceTree = ""; }; @@ -139,6 +155,8 @@ 5986308B19BA180E00CF15EA /* dashed.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dashed.svg; sourceTree = ""; }; 598759F41E242C850024CC3F /* IJSVGExporterPathInstruction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGExporterPathInstruction.h; sourceTree = ""; }; 598759F51E242C850024CC3F /* IJSVGExporterPathInstruction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGExporterPathInstruction.m; sourceTree = ""; }; + 5991A2A8201E30E600913E3B /* gradients.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = gradients.svg; sourceTree = ""; }; + 5991A2AA201E310200913E3B /* heart.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = heart.svg; sourceTree = ""; }; 599465D41C4AA87200A2EEF3 /* IJSVGStyleSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGStyleSheet.h; sourceTree = ""; }; 599465D51C4AA87200A2EEF3 /* IJSVGStyleSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGStyleSheet.m; sourceTree = ""; }; 599465D61C4AA87200A2EEF3 /* IJSVGStyleSheetRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGStyleSheetRule.h; sourceTree = ""; }; @@ -157,6 +175,9 @@ 59A3DA6C1E283022003E59A9 /* IJSVGTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGTransaction.m; sourceTree = ""; }; 59B93C6C19B7D1840063E823 /* paperplane.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = paperplane.svg; sourceTree = ""; }; 59B93C6E19B7D32C0063E823 /* products.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = products.svg; sourceTree = ""; }; + 59D1E39C2022577500C54672 /* IJSVGQuartzRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGQuartzRenderer.h; sourceTree = ""; }; + 59D1E39D2022577500C54672 /* IJSVGQuartzRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGQuartzRenderer.m; sourceTree = ""; }; + 59D1E39F202279CA00C54672 /* Group.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = Group.svg; sourceTree = ""; }; 59E0F5EB1E29964700F757F7 /* IJSVGUnitLength.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitLength.h; sourceTree = ""; }; 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnitLength.m; sourceTree = ""; }; 59E2641B19BA240D008A6FDB /* IJSVG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVG.h; sourceTree = ""; }; @@ -294,6 +315,15 @@ 5956657A19B62F4600D805FF /* Supporting Files */ = { isa = PBXGroup; children = ( + 59D1E39F202279CA00C54672 /* Group.svg */, + 590C87F5201FD0D4004A1554 /* car.svg */, + 590C87F3201FC9E3004A1554 /* radialgradient2.svg */, + 590C87F0201FBF27004A1554 /* AJ_Digital_Camera.svg */, + 590C87EC201FA08C004A1554 /* intertwingly.svg */, + 590C87EE201FA092004A1554 /* NewTux.svg */, + 590C87EA201F9888004A1554 /* json.svg */, + 5991A2AA201E310200913E3B /* heart.svg */, + 5991A2A8201E30E600913E3B /* gradients.svg */, 59265CE71C4F840400F333F0 /* css.svg */, 59459CEB19B906FE00CE493B /* clipped.svg */, 59F799E119B880CE00096CB7 /* htc_one.svg */, @@ -439,6 +469,8 @@ 597046A11E24352700A60138 /* IJSVGStrokeLayer.m */, 59E0F5EB1E29964700F757F7 /* IJSVGUnitLength.h */, 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */, + 59D1E39C2022577500C54672 /* IJSVGQuartzRenderer.h */, + 59D1E39D2022577500C54672 /* IJSVGQuartzRenderer.m */, ); name = source; path = ../../source; @@ -488,7 +520,7 @@ 5956656F19B62F4600D805FF /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0920; ORGANIZATIONNAME = "Curtis Hard"; TargetAttributes = { 5956657619B62F4600D805FF = { @@ -526,13 +558,22 @@ files = ( 59B93C6D19B7D1840063E823 /* paperplane.svg in Resources */, 5956658219B62F4600D805FF /* Images.xcassets in Resources */, + 590C87ED201FA08C004A1554 /* intertwingly.svg in Resources */, + 590C87F1201FBF27004A1554 /* AJ_Digital_Camera.svg in Resources */, + 590C87EF201FA093004A1554 /* NewTux.svg in Resources */, 59265CE81C4F840400F333F0 /* css.svg in Resources */, 595665DE19B6309C00D805FF /* test.svg in Resources */, + 590C87EB201F9888004A1554 /* json.svg in Resources */, + 59D1E3A0202279CA00C54672 /* Group.svg in Resources */, 59F799E219B880CE00096CB7 /* htc_one.svg in Resources */, 59B93C6F19B7D32C0063E823 /* products.svg in Resources */, + 5991A2A9201E30E600913E3B /* gradients.svg in Resources */, 5986308719BA104800CF15EA /* linecap.svg in Resources */, 5956658519B62F4600D805FF /* MainMenu.xib in Resources */, + 590C87F4201FC9E4004A1554 /* radialgradient2.svg in Resources */, 59459CEC19B906FE00CE493B /* clipped.svg in Resources */, + 590C87F6201FD0D4004A1554 /* car.svg in Resources */, + 5991A2AB201E310200913E3B /* heart.svg in Resources */, 5986308C19BA180E00CF15EA /* dashed.svg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -555,6 +596,7 @@ 59E2646919BA240D008A6FDB /* IJSVGStyle.m in Sources */, 59E2646419BA240D008A6FDB /* IJSVGLinearGradient.m in Sources */, 595665DC19B6302600D805FF /* README.md in Sources */, + 59D1E39E2022577500C54672 /* IJSVGQuartzRenderer.m in Sources */, 5941DAFD1CF9BC9B00B3A911 /* IJSVGImage.m in Sources */, 599465DE1C4AA87200A2EEF3 /* IJSVGStyleSheetSelector.m in Sources */, 5948DED61BB2BFE5004156FF /* IJSVGWriter.m in Sources */, diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate index e0a9c42..8039625 100644 Binary files a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate and b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/IJSVGExample/IJSVGExample.xcodeproj/xcuserdata/curtishard.xcuserdatad/xcschemes/IJSVGExample.xcscheme b/IJSVGExample/IJSVGExample.xcodeproj/xcuserdata/curtishard.xcuserdatad/xcschemes/IJSVGExample.xcscheme index e1a2b0f..0ad106b 100644 --- a/IJSVGExample/IJSVGExample.xcodeproj/xcuserdata/curtishard.xcuserdatad/xcschemes/IJSVGExample.xcscheme +++ b/IJSVGExample/IJSVGExample.xcodeproj/xcuserdata/curtishard.xcuserdatad/xcschemes/IJSVGExample.xcscheme @@ -1,6 +1,6 @@ + language = "" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,17 +63,22 @@ ReferencedContainer = "container:IJSVGExample.xcodeproj"> + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib b/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib index df8bf04..6876e68 100755 --- a/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib +++ b/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -671,19 +671,41 @@ - + - - + + - - + + + + - + diff --git a/IJSVGExample/IJSVGExample/Group.svg b/IJSVGExample/IJSVGExample/Group.svg new file mode 100644 index 0000000..435ffa4 --- /dev/null +++ b/IJSVGExample/IJSVGExample/Group.svg @@ -0,0 +1,13 @@ + + + + Group + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/IJSVGExample/IJSVGExample/NewTux.svg b/IJSVGExample/IJSVGExample/NewTux.svg new file mode 100644 index 0000000..291fc56 --- /dev/null +++ b/IJSVGExample/IJSVGExample/NewTux.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IJSVGExample/IJSVGExample/SVGExampleView1.m b/IJSVGExample/IJSVGExample/SVGExampleView1.m index 82a8b2d..6787b84 100644 --- a/IJSVGExample/IJSVGExample/SVGExampleView1.m +++ b/IJSVGExample/IJSVGExample/SVGExampleView1.m @@ -12,7 +12,7 @@ - (IJSVG *)svg { - return [[IJSVG svgNamed:@"paperplane"] retain]; + return [[IJSVG svgNamed:@"heart"] retain]; } @end diff --git a/IJSVGExample/IJSVGExample/SVGView.h b/IJSVGExample/IJSVGExample/SVGView.h index 0ef1238..735d755 100644 --- a/IJSVGExample/IJSVGExample/SVGView.h +++ b/IJSVGExample/IJSVGExample/SVGView.h @@ -15,6 +15,7 @@ } - +- (IBAction)switchToCoreGraphics:(id)sender; +- (IBAction)switchToCoreAnimation:(id)sender; @end diff --git a/IJSVGExample/IJSVGExample/SVGView.m b/IJSVGExample/IJSVGExample/SVGView.m index 8578532..4a2eec0 100644 --- a/IJSVGExample/IJSVGExample/SVGView.m +++ b/IJSVGExample/IJSVGExample/SVGView.m @@ -29,9 +29,21 @@ return self; } +- (void)switchToCoreGraphics:(id)sender +{ + svg.renderingEngine = IJSVGRenderingEngineQuartz; + [self setNeedsDisplay:YES]; +} + +- (void)switchToCoreAnimation:(id)sender +{ + svg.renderingEngine = IJSVGRenderingEngineLayered; + [self setNeedsDisplay:YES]; +} + - (IJSVG *)svg { - return [IJSVG svgNamed:@"test"]; + return [IJSVG svgNamed:@"clipped"]; } - (void)drawRect:(NSRect)dirtyRect diff --git a/IJSVGExample/IJSVGExample/car.svg b/IJSVGExample/IJSVGExample/car.svg new file mode 100644 index 0000000..00f5143 --- /dev/null +++ b/IJSVGExample/IJSVGExample/car.svg @@ -0,0 +1,7415 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/gradients.svg b/IJSVGExample/IJSVGExample/gradients.svg new file mode 100644 index 0000000..08efcd1 --- /dev/null +++ b/IJSVGExample/IJSVGExample/gradients.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IJSVGExample/IJSVGExample/heart.svg b/IJSVGExample/IJSVGExample/heart.svg new file mode 100644 index 0000000..4808edf --- /dev/null +++ b/IJSVGExample/IJSVGExample/heart.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/intertwingly.svg b/IJSVGExample/IJSVGExample/intertwingly.svg new file mode 100644 index 0000000..9495fa8 --- /dev/null +++ b/IJSVGExample/IJSVGExample/intertwingly.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/json.svg b/IJSVGExample/IJSVGExample/json.svg new file mode 100644 index 0000000..a1b7dd5 --- /dev/null +++ b/IJSVGExample/IJSVGExample/json.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/IJSVGExample/IJSVGExample/radialgradient2.svg b/IJSVGExample/IJSVGExample/radialgradient2.svg new file mode 100644 index 0000000..7a7e6da --- /dev/null +++ b/IJSVGExample/IJSVGExample/radialgradient2.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/test.svg b/IJSVGExample/IJSVGExample/test.svg index fd00464..da930e5 100755 --- a/IJSVGExample/IJSVGExample/test.svg +++ b/IJSVGExample/IJSVGExample/test.svg @@ -1,65 +1,7415 @@ - - - - icon - Created with Sketch. - - - - + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/IJSVG.h b/source/IJSVG.h index af602f4..debc619 100644 --- a/source/IJSVG.h +++ b/source/IJSVG.h @@ -13,6 +13,7 @@ #import "IJSVGGroupLayer.h" #import "IJSVGImageLayer.h" #import "IJSVGExporter.h" +#import "IJSVGQuartzRenderer.h" @class IJSVG; @@ -36,6 +37,11 @@ withSVGString:(NSString *)subSVGString; typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); +typedef NS_ENUM(NSInteger, IJSVGRenderingEngine) { + IJSVGRenderingEngineCoreGraphics, + IJSVGRenderingEngineCoreAnimation +}; + @interface IJSVG : NSObject { @private @@ -47,7 +53,9 @@ typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); CGRect _viewBox; CGSize _proposedViewSize; CGFloat _lastProposedBackingScale; + CGFloat _backingScale; NSMutableDictionary * _replacementColors; + IJSVGQuartzRenderer * _quartzRenderer; struct { unsigned int shouldHandleForeignObject: 1; @@ -70,6 +78,8 @@ typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); @property (nonatomic, assign) CGFloat strokeWidth; @property (nonatomic, assign) IJSVGLineCapStyle lineCapStyle; @property (nonatomic, assign) IJSVGLineJoinStyle lineJoinStyle; +@property (nonatomic, assign) IJSVGRenderingEngine renderingEngine; +@property (nonatomic, assign) BOOL clipToViewport; - (void)prepForDrawingInView:(NSView *)view; - (BOOL)isFont; diff --git a/source/IJSVG.m b/source/IJSVG.m index 15ad563..c6621ce 100644 --- a/source/IJSVG.m +++ b/source/IJSVG.m @@ -19,6 +19,8 @@ @synthesize lineCapStyle; @synthesize lineJoinStyle; @synthesize renderingBackingScaleHelper; +@synthesize renderingEngine; +@synthesize clipToViewport; - (void)dealloc { @@ -28,6 +30,7 @@ [_group release], _group = nil; [_layerTree release], _layerTree = nil; [_replacementColors release], _replacementColors = nil; + [_quartzRenderer release], _quartzRenderer = nil; [super dealloc]; } @@ -333,7 +336,14 @@ - (void)_setupBasicsFromAnyInitializer { - _lastProposedBackingScale = 1.f; + renderingEngine = IJSVGRenderingEngineCoreAnimation; + self.clipToViewport = YES; + + // setup low level backing scale + _lastProposedBackingScale = 0.f; + self.renderingBackingScaleHelper = ^CGFloat{ + return 1.f; + }; } - (NSString *)identifier @@ -557,6 +567,7 @@ - (CGFloat)computeBackingScale:(CGFloat)actualScale { + _backingScale = actualScale; return (CGFloat)(_scale + actualScale); } @@ -566,8 +577,8 @@ // 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); - viewPort.origin.y = round(rect.size.height/2-(_proposedViewSize.height/2)*_clipScale);; + viewPort.origin.x = round((rect.size.width/2-(_proposedViewSize.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; @@ -607,17 +618,11 @@ @try { [self _beginDraw:rect]; - - // scale the whole drawing context, but first, we need - // to translate the context so its centered - CGFloat tX = round(rect.size.width/2-(_viewBox.size.width/2)*_scale); - CGFloat tY = round(rect.size.height/2-(_viewBox.size.height/2)*_scale); // we also need to calculate the viewport so we can clip // the drawing if needed BOOL canDraw = NO; - NSRect viewPort = [self computeRectDrawingInRect:rect - isValid:&canDraw]; + NSRect viewPort = [self computeRectDrawingInRect:rect isValid:&canDraw]; // check the viewport if( !canDraw ) { if( error != NULL ) { @@ -630,12 +635,17 @@ } // clip to mask - CGContextClipToRect( ref, viewPort); + if(self.clipToViewport == YES) { + CGContextClipToRect( ref, viewPort); + } - tX -= (_viewBox.origin.x*_scale); - tY -= (_viewBox.origin.y*_scale); + // add the origin back onto the viewport + viewPort.origin.x -= round((_viewBox.origin.x)*_scale); + viewPort.origin.y -= round((_viewBox.origin.y)*_scale); + viewPort = CGRectIntegral(viewPort); - CGContextTranslateCTM( ref, tX, tY ); + // transforms + CGContextTranslateCTM( ref, viewPort.origin.x, viewPort.origin.y); CGContextScaleCTM( ref, _scale, _scale ); // render the layer, its really important we lock @@ -648,7 +658,27 @@ } // render the layers - [self.layer renderInContext:ref]; + switch(self.renderingEngine) { + // CoreGraphics / Quartz + case IJSVGRenderingEngineCoreGraphics: { + if(_quartzRenderer == nil) { + // init the renderer if its not already defined + _quartzRenderer = [[IJSVGQuartzRenderer alloc] init]; + } + _quartzRenderer.scale = _scale; + _quartzRenderer.backingScale = _backingScale; + _quartzRenderer.viewPort = viewPort; + + // render it + [_quartzRenderer renderLayer:self.layer + inContext:ref]; + break; + } + // CALayer tree + case IJSVGRenderingEngineCoreAnimation: { + [self.layer renderInContext:ref]; + } + } IJSVGEndTransactionLock(); } @catch (NSException *exception) { @@ -681,8 +711,9 @@ // walk the tree void (^block)(CALayer * layer, BOOL isMask) = ^void (CALayer * layer, BOOL isMask) { - if(((IJSVGLayer *)layer).requiresBackingScaleHelp == YES) { - ((IJSVGLayer *)layer).backingScaleFactor = scale; + IJSVGLayer * propLayer = ((IJSVGLayer *)layer); + if(propLayer.requiresBackingScaleHelp == YES) { + propLayer.backingScaleFactor = scale; } }; @@ -738,6 +769,7 @@ // force rebuild of the tree IJSVGBeginTransactionLock(); _layerTree = [[tree layerForNode:_group] retain]; + IJSVGEndTransactionLock(); return _layerTree; } diff --git a/source/IJSVGColor.h b/source/IJSVGColor.h index 4b8bd7a..6aa9580 100644 --- a/source/IJSVGColor.h +++ b/source/IJSVGColor.h @@ -160,11 +160,14 @@ typedef NS_ENUM( NSInteger, IJSVGPredefinedColor ) { @interface IJSVGColor : NSObject +CGFloat * IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness); + + (NSColor *)computeColorSpace:(NSColor *)color; + (NSColorSpace *)defaultColorSpace; + (BOOL)isColor:(NSString *)string; + (NSString *)colorStringFromColor:(NSColor *)color - forceHex:(BOOL)forceHex; + forceHex:(BOOL)forceHex + allowShorthand:(BOOL)allowShorthand; + (NSString *)colorStringFromColor:(NSColor *)color; + (NSColor *)computeColor:(id)colour; + (NSColor *)colorFromString:(NSString *)string; diff --git a/source/IJSVGColor.m b/source/IJSVGColor.m index 39d80f0..67d58fa 100644 --- a/source/IJSVGColor.m +++ b/source/IJSVGColor.m @@ -11,8 +11,27 @@ @implementation IJSVGColor -static NSMutableDictionary * _colorTree = nil; +static NSDictionary * _colorTree = nil; +CGFloat * IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness) +{ + hue *= (1.f/360.f); + hue = (hue - floorf(hue)); + saturation *= 0.01; + lightness *= 0.01; + lightness *= 2.f; + + CGFloat s = saturation * ((lightness < 1.f) ? lightness : (2.f - lightness)); + CGFloat brightness = (lightness + s) * .5f; + if(s != 0.f) { + s = (2.f * s) / (lightness + s); + } + CGFloat * floats = (CGFloat *)malloc(3*sizeof(CGFloat)); + floats[0] = hue; + floats[1] = s; + floats[2] = brightness; + return floats; +}; + (void)load { @@ -37,157 +56,156 @@ static NSMutableDictionary * _colorTree = nil; { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - - _colorTree = [[NSMutableDictionary alloc] init]; - // add the colours in - [_colorTree setObject:@"f0f8ff" forKey:@"aliceblue"]; - [_colorTree setObject:@"faebd7" forKey:@"antiquewhite"]; - [_colorTree setObject:@"00ffff" forKey:@"aqua"]; - [_colorTree setObject:@"7fffd4" forKey:@"aquamarine"]; - [_colorTree setObject:@"f0ffff" forKey:@"azure"]; - [_colorTree setObject:@"f5f5dc" forKey:@"beige"]; - [_colorTree setObject:@"ffe4c4" forKey:@"bisque"]; - [_colorTree setObject:@"000000" forKey:@"black"]; - [_colorTree setObject:@"ffebcd" forKey:@"blanchedalmond"]; - [_colorTree setObject:@"0000ff" forKey:@"blue"]; - [_colorTree setObject:@"8a2be2" forKey:@"blueviolet"]; - [_colorTree setObject:@"a52a2a" forKey:@"brown"]; - [_colorTree setObject:@"deb887" forKey:@"burlywood"]; - [_colorTree setObject:@"5f9ea0" forKey:@"cadetblue"]; - [_colorTree setObject:@"7fff00" forKey:@"chartreuse"]; - [_colorTree setObject:@"d2691e" forKey:@"chocolate"]; - [_colorTree setObject:@"ff7f50" forKey:@"coral"]; - [_colorTree setObject:@"6495ed" forKey:@"cornflowerblue"]; - [_colorTree setObject:@"fff8dc" forKey:@"cornsilk"]; - [_colorTree setObject:@"dc143c" forKey:@"crimson"]; - [_colorTree setObject:@"000000" forKey:@"currentcolor"]; - [_colorTree setObject:@"00ffff" forKey:@"cyan"]; - [_colorTree setObject:@"00008b" forKey:@"darkblue"]; - [_colorTree setObject:@"008b8b" forKey:@"darkcyan"]; - [_colorTree setObject:@"b8860b" forKey:@"darkgoldenrod"]; - [_colorTree setObject:@"a9a9a9" forKey:@"darkgray"]; - [_colorTree setObject:@"006400" forKey:@"darkgreen"]; - [_colorTree setObject:@"a9a9a9" forKey:@"darkgrey"]; - [_colorTree setObject:@"bdb76b" forKey:@"darkkhaki"]; - [_colorTree setObject:@"8b008b" forKey:@"darkmagenta"]; - [_colorTree setObject:@"556b2f" forKey:@"darkolivegreen"]; - [_colorTree setObject:@"ff8c00" forKey:@"darkorange"]; - [_colorTree setObject:@"9932cc" forKey:@"darkorchid"]; - [_colorTree setObject:@"8b0000" forKey:@"darkred"]; - [_colorTree setObject:@"e9967a" forKey:@"darksalmon"]; - [_colorTree setObject:@"8fbc8f" forKey:@"darkseagreen"]; - [_colorTree setObject:@"483d8b" forKey:@"darkslateblue"]; - [_colorTree setObject:@"2f4f4f" forKey:@"darkslategray"]; - [_colorTree setObject:@"2f4f4f" forKey:@"darkslategrey"]; - [_colorTree setObject:@"00ced1" forKey:@"darkturquoise"]; - [_colorTree setObject:@"9400d3" forKey:@"darkviolet"]; - [_colorTree setObject:@"ff1493" forKey:@"deeppink"]; - [_colorTree setObject:@"00bfff" forKey:@"deepskyblue"]; - [_colorTree setObject:@"696969" forKey:@"dimgray"]; - [_colorTree setObject:@"696969" forKey:@"dimgrey"]; - [_colorTree setObject:@"1e90ff" forKey:@"dodgerblue"]; - [_colorTree setObject:@"b22222" forKey:@"firebrick"]; - [_colorTree setObject:@"fffaf0" forKey:@"floralwhite"]; - [_colorTree setObject:@"228b22" forKey:@"forestgreen"]; - [_colorTree setObject:@"ff00ff" forKey:@"fuchsia"]; - [_colorTree setObject:@"dcdcdc" forKey:@"gainsboro"]; - [_colorTree setObject:@"f8f8ff" forKey:@"ghostwhite"]; - [_colorTree setObject:@"ffd700" forKey:@"gold"]; - [_colorTree setObject:@"daa520" forKey:@"goldenrod"]; - [_colorTree setObject:@"808080" forKey:@"gray"]; - [_colorTree setObject:@"008000" forKey:@"green"]; - [_colorTree setObject:@"adff2f" forKey:@"greenyellow"]; - [_colorTree setObject:@"808080" forKey:@"grey"]; - [_colorTree setObject:@"f0fff0" forKey:@"honeydew"]; - [_colorTree setObject:@"ff69b4" forKey:@"hotpink"]; - [_colorTree setObject:@"cd5c5c" forKey:@"indianred"]; - [_colorTree setObject:@"4b0082" forKey:@"indigo"]; - [_colorTree setObject:@"fffff0" forKey:@"ivory"]; - [_colorTree setObject:@"f0e68c" forKey:@"khaki"]; - [_colorTree setObject:@"e6e6fa" forKey:@"lavender"]; - [_colorTree setObject:@"fff0f5" forKey:@"lavenderblush"]; - [_colorTree setObject:@"7cfc00" forKey:@"lawngreen"]; - [_colorTree setObject:@"fffacd" forKey:@"lemonchiffon"]; - [_colorTree setObject:@"add8e6" forKey:@"lightblue"]; - [_colorTree setObject:@"f08080" forKey:@"lightcoral"]; - [_colorTree setObject:@"e0ffff" forKey:@"lightcyan"]; - [_colorTree setObject:@"fafad2" forKey:@"lightgoldenrodyellow"]; - [_colorTree setObject:@"d3d3d3" forKey:@"lightgray"]; - [_colorTree setObject:@"90ee90" forKey:@"lightgreen"]; - [_colorTree setObject:@"d3d3d3" forKey:@"lightgrey"]; - [_colorTree setObject:@"ffb6c1" forKey:@"lightpink"]; - [_colorTree setObject:@"ffa07a" forKey:@"lightsalmon"]; - [_colorTree setObject:@"20b2aa" forKey:@"lightseagreen"]; - [_colorTree setObject:@"87cefa" forKey:@"lightskyblue"]; - [_colorTree setObject:@"778899" forKey:@"lightslategray"]; - [_colorTree setObject:@"778899" forKey:@"lightslategrey"]; - [_colorTree setObject:@"b0c4de" forKey:@"lightsteelblue"]; - [_colorTree setObject:@"ffffe0" forKey:@"lightyellow"]; - [_colorTree setObject:@"00ff00" forKey:@"lime"]; - [_colorTree setObject:@"32cd32" forKey:@"limegreen"]; - [_colorTree setObject:@"faf0e6" forKey:@"linen"]; - [_colorTree setObject:@"ff00ff" forKey:@"magenta"]; - [_colorTree setObject:@"800000" forKey:@"maroon"]; - [_colorTree setObject:@"66cdaa" forKey:@"mediumaquamarine"]; - [_colorTree setObject:@"0000cd" forKey:@"mediumblue"]; - [_colorTree setObject:@"ba55d3" forKey:@"mediumorchid"]; - [_colorTree setObject:@"9370db" forKey:@"mediumpurple"]; - [_colorTree setObject:@"3cb371" forKey:@"mediumseagreen"]; - [_colorTree setObject:@"7b68ee" forKey:@"mediumslateblue"]; - [_colorTree setObject:@"00fa9a" forKey:@"mediumspringgreen"]; - [_colorTree setObject:@"48d1cc" forKey:@"mediumturquoise"]; - [_colorTree setObject:@"c71585" forKey:@"mediumvioletred"]; - [_colorTree setObject:@"191970" forKey:@"midnightblue"]; - [_colorTree setObject:@"f5fffa" forKey:@"mintcream"]; - [_colorTree setObject:@"ffe4e1" forKey:@"mistyrose"]; - [_colorTree setObject:@"ffe4b5" forKey:@"moccasin"]; - [_colorTree setObject:@"ffdead" forKey:@"navajowhite"]; - [_colorTree setObject:@"000080" forKey:@"navy"]; - [_colorTree setObject:@"fdf5e6" forKey:@"oldlace"]; - [_colorTree setObject:@"808000" forKey:@"olive"]; - [_colorTree setObject:@"6b8e23" forKey:@"olivedrab"]; - [_colorTree setObject:@"ffa500" forKey:@"orange"]; - [_colorTree setObject:@"ff4500" forKey:@"orangered"]; - [_colorTree setObject:@"da70d6" forKey:@"orchid"]; - [_colorTree setObject:@"eee8aa" forKey:@"palegoldenrod"]; - [_colorTree setObject:@"98fb98" forKey:@"palegreen"]; - [_colorTree setObject:@"afeeee" forKey:@"paleturquoise"]; - [_colorTree setObject:@"db7093" forKey:@"palevioletred"]; - [_colorTree setObject:@"ffefd5" forKey:@"papayawhip"]; - [_colorTree setObject:@"ffdab9" forKey:@"peachpuff"]; - [_colorTree setObject:@"cd853f" forKey:@"peru"]; - [_colorTree setObject:@"ffc0cb" forKey:@"pink"]; - [_colorTree setObject:@"dda0dd" forKey:@"plum"]; - [_colorTree setObject:@"b0e0e6" forKey:@"powderblue"]; - [_colorTree setObject:@"800080" forKey:@"purple"]; - [_colorTree setObject:@"ff0000" forKey:@"red"]; - [_colorTree setObject:@"bc8f8f" forKey:@"rosybrown"]; - [_colorTree setObject:@"4169e1" forKey:@"royalblue"]; - [_colorTree setObject:@"8b4513" forKey:@"saddlebrown"]; - [_colorTree setObject:@"fa8072" forKey:@"salmon"]; - [_colorTree setObject:@"f4a460" forKey:@"sandybrown"]; - [_colorTree setObject:@"2e8b57" forKey:@"seagreen"]; - [_colorTree setObject:@"fff5ee" forKey:@"seashell"]; - [_colorTree setObject:@"a0522d" forKey:@"sienna"]; - [_colorTree setObject:@"c0c0c0" forKey:@"silver"]; - [_colorTree setObject:@"87ceeb" forKey:@"skyblue"]; - [_colorTree setObject:@"6a5acd" forKey:@"slateblue"]; - [_colorTree setObject:@"708090" forKey:@"slategray"]; - [_colorTree setObject:@"708090" forKey:@"slategrey"]; - [_colorTree setObject:@"fffafa" forKey:@"snow"]; - [_colorTree setObject:@"00ff7f" forKey:@"springgreen"]; - [_colorTree setObject:@"4682b4" forKey:@"steelblue"]; - [_colorTree setObject:@"d2b48c" forKey:@"tan"]; - [_colorTree setObject:@"008080" forKey:@"teal"]; - [_colorTree setObject:@"d8bfd8" forKey:@"thistle"]; - [_colorTree setObject:@"ff6347" forKey:@"tomato"]; - [_colorTree setObject:@"40e0d0" forKey:@"turquoise"]; - [_colorTree setObject:@"ee82ee" forKey:@"violet"]; - [_colorTree setObject:@"f5deb3" forKey:@"wheat"]; - [_colorTree setObject:@"ffffff" forKey:@"white"]; - [_colorTree setObject:@"f5f5f5" forKey:@"whitesmoke"]; - [_colorTree setObject:@"ffff00" forKey:@"yellow"]; - [_colorTree setObject:@"9acd32" forKey:@"yellowgreen"]; + _colorTree = [@{ + @"aliceblue":@"f0f8ff", + @"antiquewhite":@"faebd7", + @"aqua":@"00ffff", + @"aquamarine":@"7fffd4", + @"azure":@"f0ffff", + @"beige":@"f5f5dc", + @"bisque":@"ffe4c4", + @"black":@"000000", + @"blanchedalmond":@"ffebcd", + @"blue":@"0000ff", + @"blueviolet":@"8a2be2", + @"brown":@"a52a2a", + @"burlywood":@"deb887", + @"cadetblue":@"5f9ea0", + @"chartreuse":@"7fff00", + @"chocolate":@"d2691e", + @"coral":@"ff7f50", + @"cornflowerblue":@"6495ed", + @"cornsilk":@"fff8dc", + @"crimson":@"dc143c", + @"currentcolor":@"000000", + @"cyan":@"00ffff", + @"darkblue":@"00008b", + @"darkcyan":@"008b8b", + @"darkgoldenrod":@"b8860b", + @"darkgray":@"a9a9a9", + @"darkgreen":@"006400", + @"darkgrey":@"a9a9a9", + @"darkkhaki":@"bdb76b", + @"darkmagenta":@"8b008b", + @"darkolivegreen":@"556b2f", + @"darkorange":@"ff8c00", + @"darkorchid":@"9932cc", + @"darkred":@"8b0000", + @"darksalmon":@"e9967a", + @"darkseagreen":@"8fbc8f", + @"darkslateblue":@"483d8b", + @"darkslategray":@"2f4f4f", + @"darkslategrey":@"2f4f4f", + @"darkturquoise":@"00ced1", + @"darkviolet":@"9400d3", + @"deeppink":@"ff1493", + @"deepskyblue":@"00bfff", + @"dimgray":@"696969", + @"dimgrey":@"696969", + @"dodgerblue":@"1e90ff", + @"firebrick":@"b22222", + @"floralwhite":@"fffaf0", + @"forestgreen":@"228b22", + @"fuchsia":@"ff00ff", + @"gainsboro":@"dcdcdc", + @"ghostwhite":@"f8f8ff", + @"gold":@"ffd700", + @"goldenrod":@"daa520", + @"gray":@"808080", + @"green":@"008000", + @"greenyellow":@"adff2f", + @"grey":@"808080", + @"honeydew":@"f0fff0", + @"hotpink":@"ff69b4", + @"indianred":@"cd5c5c", + @"indigo":@"4b0082", + @"ivory":@"fffff0", + @"khaki":@"f0e68c", + @"lavender":@"e6e6fa", + @"lavenderblush":@"fff0f5", + @"lawngreen":@"7cfc00", + @"lemonchiffon":@"fffacd", + @"lightblue":@"add8e6", + @"lightcoral":@"f08080", + @"lightcyan":@"e0ffff", + @"lightgoldenrodyellow":@"fafad2", + @"lightgray":@"d3d3d3", + @"lightgreen":@"90ee90", + @"lightgrey":@"d3d3d3", + @"lightpink":@"ffb6c1", + @"lightsalmon":@"ffa07a", + @"lightseagreen":@"20b2aa", + @"lightskyblue":@"87cefa", + @"lightslategray":@"778899", + @"lightslategrey":@"778899", + @"lightsteelblue":@"b0c4de", + @"lightyellow":@"ffffe0", + @"lime":@"00ff00", + @"limegreen":@"32cd32", + @"linen":@"faf0e6", + @"magenta":@"ff00ff", + @"maroon":@"800000", + @"mediumaquamarine":@"66cdaa", + @"mediumblue":@"0000cd", + @"mediumorchid":@"ba55d3", + @"mediumpurple":@"9370db", + @"mediumseagreen":@"3cb371", + @"mediumslateblue":@"7b68ee", + @"mediumspringgreen":@"00fa9a", + @"mediumturquoise":@"48d1cc", + @"mediumvioletred":@"c71585", + @"midnightblue":@"191970", + @"mintcream":@"f5fffa", + @"mistyrose":@"ffe4e1", + @"moccasin":@"ffe4b5", + @"navajowhite":@"ffdead", + @"navy":@"000080", + @"oldlace":@"fdf5e6", + @"olive":@"808000", + @"olivedrab":@"6b8e23", + @"orange":@"ffa500", + @"orangered":@"ff4500", + @"orchid":@"da70d6", + @"palegoldenrod":@"eee8aa", + @"palegreen":@"98fb98", + @"paleturquoise":@"afeeee", + @"palevioletred":@"db7093", + @"papayawhip":@"ffefd5", + @"peachpuff":@"ffdab9", + @"peru":@"cd853f", + @"pink":@"ffc0cb", + @"plum":@"dda0dd", + @"powderblue":@"b0e0e6", + @"purple":@"800080", + @"red":@"ff0000", + @"rosybrown":@"bc8f8f", + @"royalblue":@"4169e1", + @"saddlebrown":@"8b4513", + @"salmon":@"fa8072", + @"sandybrown":@"f4a460", + @"seagreen":@"2e8b57", + @"seashell":@"fff5ee", + @"sienna":@"a0522d", + @"silver":@"c0c0c0", + @"skyblue":@"87ceeb", + @"slateblue":@"6a5acd", + @"slategray":@"708090", + @"slategrey":@"708090", + @"snow":@"fffafa", + @"springgreen":@"00ff7f", + @"steelblue":@"4682b4", + @"tan":@"d2b48c", + @"teal":@"008080", + @"thistle":@"d8bfd8", + @"tomato":@"ff6347", + @"turquoise":@"40e0d0", + @"violet":@"ee82ee", + @"wheat":@"f5deb3", + @"white":@"ffffff", + @"whitesmoke":@"f5f5f5", + @"yellow":@"ffff00", + @"yellowgreen":@"9acd32" + } retain]; }); } @@ -226,14 +244,37 @@ static NSMutableDictionary * _colorTree = nil; if( count == 4 ) { alpha = params[3]; } - color = [NSColor colorWithDeviceRed:params[0]/255 - green:params[1]/255 - blue:params[2]/255 + color = [NSColor colorWithDeviceRed:params[0]/255.f + green:params[1]/255.f + blue:params[2]/255.f alpha:alpha]; free(params); return color; } + // is it HSL? + if([[string substringToIndex:3] isEqualToString:@"hsl"]) { + NSInteger count = 0; + CGFloat * params = [IJSVGUtils commandParameters:string + count:&count]; + CGFloat alpha = 1; + if(count == 4) { + alpha = params[3]; + } + + // convert HSL to HSB + CGFloat * hsb = IJSVGColorCSSHSLToHSB(params[0], params[1], params[2]); + color = [NSColor colorWithDeviceHue:hsb[0] + saturation:hsb[1] + brightness:hsb[2] + alpha:alpha]; + + // memory clean! + free(hsb); + free(params); + return color; + } + color = [[self class] colorFromHEXString:string alpha:1.f]; return color; @@ -252,11 +293,13 @@ static NSMutableDictionary * _colorTree = nil; + (NSString *)colorStringFromColor:(NSColor *)color { return [self colorStringFromColor:color - forceHex:NO]; + forceHex:NO + allowShorthand:YES]; } + (NSString *)colorStringFromColor:(NSColor *)color forceHex:(BOOL)forceHex + allowShorthand:(BOOL)allowShorthand { // convert to RGB color = [self computeColorSpace:color]; @@ -275,12 +318,22 @@ static NSMutableDictionary * _colorTree = nil; if(forceHex || alpha == 100 || (red == 0 && green == 0 && blue == 0 && alpha == 0) || (red == 255 && green == 255 && blue == 255 && alpha == 100)) { - // just return hex + if(allowShorthand == YES) { + NSString * r = [NSString stringWithFormat:@"%02X",red]; + NSString * g = [NSString stringWithFormat:@"%02X",green]; + NSString * b = [NSString stringWithFormat:@"%02X",blue]; + if([r characterAtIndex:0] == [r characterAtIndex:1] && + [g characterAtIndex:0] == [g characterAtIndex:1] && + [b characterAtIndex:0] == [b characterAtIndex:1]) { + return [NSString stringWithFormat:@"#%c%c%c",[r characterAtIndex:0], + [g characterAtIndex:0],[b characterAtIndex:0]]; + } + } return [NSString stringWithFormat:@"#%02X%02X%02X",red,green,blue]; } // note the %g, CSS alpha is 0 to 1, not 0 - 100, my bad! - return [NSString stringWithFormat:@"rgba(%d, %d, %d, %g)",red, green, blue, + return [NSString stringWithFormat:@"rgba(%d,%d,%d,%g)",red, green, blue, ((float)alpha/100.f)]; } diff --git a/source/IJSVGCommand.h b/source/IJSVGCommand.h index 98611fa..4bca42c 100644 --- a/source/IJSVGCommand.h +++ b/source/IJSVGCommand.h @@ -41,6 +41,7 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { IJSVGCommand * previousCommand; NSInteger _currentIndex; Class commandClass; + BOOL isSubCommand; } @property ( nonatomic, copy ) NSString * commandString; @@ -52,6 +53,7 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { @property ( nonatomic, retain ) NSMutableArray * subCommands; @property ( nonatomic, assign ) Class commandClass; @property ( nonatomic, assign ) IJSVGCommand * previousCommand; +@property ( nonatomic, assign ) BOOL isSubCommand; - (id)initWithCommandString:(NSString *)commandString; diff --git a/source/IJSVGCommand.m b/source/IJSVGCommand.m index 80411e4..603f1ed 100644 --- a/source/IJSVGCommand.m +++ b/source/IJSVGCommand.m @@ -20,6 +20,7 @@ @synthesize requiredParameters; @synthesize type; @synthesize previousCommand; +@synthesize isSubCommand; static NSMutableDictionary * _classes = nil; @@ -71,8 +72,9 @@ static NSMutableDictionary * _classes = nil; c.parameters = subParams; c.type = self.type; c.command = self.command; - c.previousCommand = [self.subCommands lastObject]; + c.previousCommand = self.subCommands.lastObject; c.commandClass = self.commandClass; + c.isSubCommand = i == 0 ? NO : YES; // add it to our tree [self.subCommands addObject:c]; diff --git a/source/IJSVGCommandArc.m b/source/IJSVGCommandArc.m index 394e3a1..e578c24 100644 --- a/source/IJSVGCommandArc.m +++ b/source/IJSVGCommandArc.m @@ -29,10 +29,6 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { - - // command was taken from: https://github.com/jmenter/JAMSVGImage/blob/89b375c0c3203355a0c693e3b805458415bf4e29/Classes/JAMSVGImage/Path%20and%20Parser/JAMStyledBezierPathFactory.m - // and modified to purpose inside this to be converted into degrees and not radians - CGPoint radii = CGPointZero; CGPoint arcEndPoint = CGPointZero; CGPoint arcStartPoint = path.currentPoint; @@ -46,7 +42,7 @@ sweepFlag = [currentCommand readBOOL]; arcEndPoint = [currentCommand readPoint]; - if ( type == IJSVGCommandTypeRelative ) { + if (type == IJSVGCommandTypeRelative) { arcEndPoint.x += path.currentPoint.x; arcEndPoint.y += path.currentPoint.y; } @@ -82,13 +78,19 @@ } CGFloat radius = MAX(radii.x, radii.y); - CGPoint scale = (radii.x > radii.y) ? CGPointMake(1, radii.y / radii.x) : CGPointMake(radii.x / radii.y, 1); + 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]; - [trans rotateByRadians:-xAxisRotation]; - [trans scaleXBy:(1/scale.x) yBy:(1/scale.y)]; + [path.currentSubpath transformUsingAffineTransform:trans]; + trans = [NSAffineTransform transform]; + [trans rotateByRadians:-xAxisRotation]; + [path.currentSubpath transformUsingAffineTransform:trans]; + + trans = [NSAffineTransform transform]; + [trans scaleXBy:(1/scale.x) yBy:(1/scale.y)]; [path.currentSubpath transformUsingAffineTransform:trans]; [path.currentSubpath appendBezierPathWithArcWithCenter:NSZeroPoint @@ -98,12 +100,16 @@ clockwise:!sweepFlag]; trans = [NSAffineTransform transform]; - [trans translateXBy:centerPoint.x yBy:centerPoint.y]; - [trans rotateByRadians:xAxisRotation]; [trans scaleXBy:scale.x yBy:scale.y]; [path.currentSubpath transformUsingAffineTransform:trans]; + trans = [NSAffineTransform transform]; + [trans rotateByRadians:xAxisRotation]; + [path.currentSubpath transformUsingAffineTransform:trans]; + trans = [NSAffineTransform transform]; + [trans translateXBy:centerPoint.x yBy:centerPoint.y]; + [path.currentSubpath transformUsingAffineTransform:trans]; } @end diff --git a/source/IJSVGCommandCurve.m b/source/IJSVGCommandCurve.m index faf840d..24d8ca9 100644 --- a/source/IJSVGCommandCurve.m +++ b/source/IJSVGCommandCurve.m @@ -28,17 +28,15 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { - if( type == IJSVGCommandTypeAbsolute ) - { + if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] curveToPoint:NSMakePoint( params[4], params[5]) controlPoint1:NSMakePoint( params[0], params[1]) controlPoint2:NSMakePoint( params[2], params[3])]; return; } - NSPoint point = [[path currentSubpath] currentPoint]; - [[path currentSubpath] curveToPoint:NSMakePoint( point.x + params[4], point.y + params[5]) - controlPoint1:NSMakePoint( point.x + params[0], point.y + params[1]) - controlPoint2:NSMakePoint( point.x + params[2], point.y + params[3])]; + [[path currentSubpath] relativeCurveToPoint:NSMakePoint( params[4], params[5]) + controlPoint1:NSMakePoint( params[0], params[1]) + controlPoint2:NSMakePoint( params[2], params[3])]; } diff --git a/source/IJSVGCommandLineTo.m b/source/IJSVGCommandLineTo.m index f15bb06..4228344 100644 --- a/source/IJSVGCommandLineTo.m +++ b/source/IJSVGCommandLineTo.m @@ -28,12 +28,11 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { - if( type == IJSVGCommandTypeAbsolute ) - { + if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] lineToPoint:NSMakePoint( params[0], params[1])]; return; } - NSPoint point = NSMakePoint( [path currentSubpath].currentPoint.x + params[0], + NSPoint point = NSMakePoint([path currentSubpath].currentPoint.x + params[0], [path currentSubpath].currentPoint.y + params[1]); [[path currentSubpath] lineToPoint:point]; } diff --git a/source/IJSVGCommandMove.m b/source/IJSVGCommandMove.m index 4970fac..4ef78ab 100644 --- a/source/IJSVGCommandMove.m +++ b/source/IJSVGCommandMove.m @@ -32,8 +32,7 @@ { // move to's allow more then one move to, but if there are more then one, // we need to run the line to instead...who knew! - if( command.commandClass == [self class]) - { + if( command.commandClass == [self class] && currentCommand.isSubCommand == YES) { [IJSVGCommandLineTo runWithParams:params paramCount:count command:currentCommand @@ -44,8 +43,7 @@ } // actual move to command - if( type == IJSVGCommandTypeAbsolute ) - { + if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] moveToPoint:NSMakePoint( params[0], params[1])]; return; } diff --git a/source/IJSVGExporter.h b/source/IJSVGExporter.h index 6e95d5c..7a8db19 100644 --- a/source/IJSVGExporter.h +++ b/source/IJSVGExporter.h @@ -26,6 +26,8 @@ typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { IJSVGExporterOptionRemoveHiddenElements = 1 << 8, IJSVGExporterOptionScaleToSizeIfNecessary = 1 << 9, IJSVGExporterOptionCompressOutput = 1 << 10, + IJSVGExporterOptionCollapseGradients = 1 << 11, + IJSVGExporterOptionCreateClasses = 1 << 12, IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef| IJSVGExporterOptionRemoveUselessGroups| IJSVGExporterOptionCreateUseForPaths| @@ -35,7 +37,8 @@ typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { IJSVGExporterOptionCleanupPaths| IJSVGExporterOptionRemoveHiddenElements| IJSVGExporterOptionScaleToSizeIfNecessary| - IJSVGExporterOptionCompressOutput + IJSVGExporterOptionCompressOutput| + IJSVGExporterOptionCollapseGradients }; @interface IJSVGExporter : NSObject { @@ -46,13 +49,9 @@ typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { IJSVGExporterOptions _options; NSXMLDocument * _dom; NSXMLElement * _defElement; - NSInteger _gradCount; - NSInteger _patternCount; - NSInteger _imageCount; - NSInteger _maskCount; - NSInteger _pathCount; NSXMLElement * _scaledRootNode; - + NSInteger _idCount; + NSInteger _shortIdCount; } @property (nonatomic, copy) NSString * title; diff --git a/source/IJSVGExporter.m b/source/IJSVGExporter.m index ed791b0..7768e41 100644 --- a/source/IJSVGExporter.m +++ b/source/IJSVGExporter.m @@ -31,56 +31,69 @@ @synthesize title; @synthesize description; +const NSArray * IJSVGShortCharacterArray() +{ + static NSArray * _array; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _array = [@[@"a",@"b",@"c",@"d",@"e",@"f",@"g",@"h",@"i",@"j",@"k",@"l", + @"m",@"n",@"o",@"p",@"q",@"r",@"s",@"t",@"u",@"v",@"w",@"x",@"y",@"z", + @"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L", + @"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z"] retain]; + }); + return _array; +} + const NSArray * IJSVGInheritableAttributes() { static NSArray * _attributes; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _attributes = [@[ - @"clip-rule", - @"color", - @"color-interpolation", - @"color-interpolation-filters", - @"color-profile", - @"color-rendering", - @"cursor", - @"direction", - @"fill", - @"fill-opacity", - @"fill-rule", - @"font", - @"font-family", - @"font-size", - @"font-size-adjust", - @"font-stretch", - @"font-style", - @"font-variant", - @"font-weight", - @"glyph-orientation-horizontal", - @"glyph-orientation-vertical", - @"image-rendering", - @"kerning", - @"letter-spacing", - @"marker", - @"marker-end", - @"marker-mid", - @"marker-start", - @"pointer-events", - @"shape-rendering", - @"stroke", - @"stroke-dasharray", - @"stroke-dashoffset", - @"stroke-linecap", - @"stroke-linejoin", - @"stroke-miterlimit", - @"stroke-opacity", - @"stroke-width", - @"text-anchor", - @"text-rendering", - @"visibility", - @"white-space", - @"word-spacing", - @"writing-mode"] retain]; + @"clip-rule", + @"color", + @"color-interpolation", + @"color-interpolation-filters", + @"color-profile", + @"color-rendering", + @"cursor", + @"direction", + @"fill", + @"fill-opacity", + @"fill-rule", + @"font", + @"font-family", + @"font-size", + @"font-size-adjust", + @"font-stretch", + @"font-style", + @"font-variant", + @"font-weight", + @"glyph-orientation-horizontal", + @"glyph-orientation-vertical", + @"image-rendering", + @"kerning", + @"letter-spacing", + @"marker", + @"marker-end", + @"marker-mid", + @"marker-start", + @"pointer-events", + @"shape-rendering", + @"stroke", + @"stroke-dasharray", + @"stroke-dashoffset", + @"stroke-linecap", + @"stroke-linejoin", + @"stroke-miterlimit", + @"stroke-opacity", + @"stroke-width", + @"text-anchor", + @"text-rendering", + @"visibility", + @"white-space", + @"word-spacing", + @"writing-mode"] retain]; }); return _attributes; } @@ -97,11 +110,6 @@ NSDictionary * IJSVGElementAttributeDictionary(NSXMLElement * element) { return dict; }; -NSString * IJSVGShortFloatString(CGFloat f) -{ - return [NSString stringWithFormat:@"%g",f]; -}; - NSString * IJSVGHashURL(NSString * key) { return [NSString stringWithFormat:@"url(#%@)",key]; }; @@ -165,11 +173,11 @@ NSString * IJSVGHash(NSString * key) { // sort out viewbox NSRect viewBox = _svg.viewBox; NSDictionary * attributes = @{ - @"viewBox":[self viewBoxWithRect:viewBox], - @"version": [NSString stringWithFormat:@"%g",XML_DOC_VERSION], - @"xmlns": XML_DOC_NS, - @"xmlns:xlink": XML_DOC_NSXLINK - }; + @"viewBox":[self viewBoxWithRect:viewBox], + @"version": [NSString stringWithFormat:@"%g",XML_DOC_VERSION], + @"xmlns": XML_DOC_NS, + @"xmlns:xlink": XML_DOC_NSXLINK + }; // was there a size set? if(CGSizeEqualToSize(CGSizeZero, _size) == NO && @@ -219,6 +227,19 @@ NSString * IJSVGHash(NSString * key) { return root; } +- (NSString *)generateID +{ + const NSArray * chars = IJSVGShortCharacterArray(); + if(_idCount < chars.count) { + return chars[_idCount++]; + } + + if((_idCount % chars.count) == 0) { + _shortIdCount++; + } + return [NSString stringWithFormat:@"%@%ld",chars[(_idCount++ % chars.count)],_shortIdCount]; +} + - (void)_prepare { // create the stand alone DOM @@ -226,18 +247,28 @@ NSString * IJSVGHash(NSString * key) { _dom.version = XML_DOCTYPE_VERSION; _dom.characterEncoding = XML_DOC_CHARSET; - // add defs in - [_dom.rootElement addChild:[self defElement]]; - // sort out header // sort out stuff, so here we go... [self _recursiveParseFromLayer:_svg.layer intoElement:(_scaledRootNode?:_dom.rootElement)]; + // this needs to be added incase it needs to be cleaned + NSXMLElement * defNode = [self defElement]; + if(defNode.childCount != 0) { + [_dom.rootElement insertChild:[self defElement] + atIndex:0]; + } + // cleanup [self _cleanup]; + // could had been removed during cleaning process needs to be added back in! + if(defNode.childCount != 0 && defNode.parent == nil) { + [_dom.rootElement insertChild:[self defElement] + atIndex:0]; + } + // add generator NSXMLNode * generatorNode = [[[NSXMLNode alloc] initWithKind:NSXMLCommentKind] autorelease]; generatorNode.stringValue = XML_DOC_GENERATOR; @@ -262,11 +293,6 @@ NSString * IJSVGHash(NSString * key) { [self _cleanDef]; } - // move any possible attributes to its parent group - if((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) { - [self _moveAttributesToGroup]; - } - // collapse groups if((_options & IJSVGExporterOptionCollapseGroups) != 0) { [self _collapseGroups]; @@ -286,6 +312,65 @@ NSString * IJSVGHash(NSString * key) { if((_options & IJSVGExporterOptionCollapseGroups) != 0) { [self _compressGroups]; } + + // collapse gradients? + if((_options & IJSVGExporterOptionCollapseGradients) != 0) { + [self _collapseGradients]; + } + + // create classes? + if((_options & IJSVGExporterOptionCreateClasses) != 0) { + [self _createClasses]; + } + + // move attributes to group + if((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) { + [self _moveAttributesToGroupWithElement:_dom.rootElement]; + } +} + +- (void)_createClasses +{ + const NSArray * inhert = IJSVGInheritableAttributes(); + NSArray * elements = [_dom nodesForXPath:@"//*" + error:nil]; + NSMutableDictionary * rules = [[[NSMutableDictionary alloc] init] autorelease]; + for(NSXMLElement * element in elements) { + NSDictionary * inhertEl = [self intersectableAttributes:IJSVGElementAttributeDictionary(element) + inheritableAttributes:inhert]; + NSString * styles = [self styleSheetRulesFromDictionary:inhertEl]; + NSString * className = nil; + if((className = [rules objectForKey:styles]) == nil) { + className = [NSString stringWithFormat:@"%@",[self generateID]]; + rules[styles] = className; + } + + for(NSString * attributeName in inhertEl) { + [element removeAttributeForName:attributeName]; + } + IJSVGApplyAttributesToElement(@{@"class":className},element); + } + + // add styles to dom + NSXMLElement * styles = [[[NSXMLElement alloc] initWithName:@"style"] autorelease]; + NSXMLNode * node = [[[NSXMLNode alloc] initWithKind:NSXMLTextKind] autorelease]; + + NSMutableArray * classes = [[[NSMutableArray alloc] initWithCapacity:rules.count] autorelease]; + for(NSString * r in rules) { + [classes addObject:[NSString stringWithFormat:@".%@%@",rules[r],r]]; + } + node.stringValue = [classes componentsJoinedByString:@""]; + [styles addChild:node]; + [_dom.rootElement insertChild:styles atIndex:0]; +} + +- (NSString *)styleSheetRulesFromDictionary:(NSDictionary *)dict +{ + NSMutableArray * array = [[[NSMutableArray alloc] initWithCapacity:dict.count] autorelease]; + for(NSString * key in dict.allKeys) { + [array addObject:[NSString stringWithFormat:@"%@: %@;",key,dict[key]]]; + } + return [NSString stringWithFormat:@"{%@}",[array componentsJoinedByString:@" "]]; } - (void)_sortAttributesOnElement:(NSXMLElement *)element @@ -304,85 +389,116 @@ NSString * IJSVGHash(NSString * key) { { // find any elements where they have a style, but the element itself // must not be in the defs - NSArray * elements = [_dom nodesForXPath:@"//*[@style][not(ancestor::defs)]" + NSArray * elements = [_dom nodesForXPath:@"//*[@display='none']" error:nil]; for(NSXMLElement * element in elements) { - // parse the style - NSString * styleString = [element attributeForName:@"style"].stringValue; - IJSVGStyle * style = [IJSVGStyle parseStyleString:styleString]; - if([[style property:@"display"] isEqualToString:@"none"]) { - // this can be removed! - NSXMLElement * parent = (NSXMLElement *)element.parent; - [parent removeChildAtIndex:element.index]; + NSXMLElement * parent = (NSXMLElement *)element.parent; + [parent removeChildAtIndex:element.index]; + } +} + +- (void)_collapseGradients +{ + NSString * xPath = @"//defs/*[self::linearGradient or self::radialGradient]"; + NSArray * gradients = [_dom nodesForXPath:xPath error:nil]; + for(NSInteger i = 0; i < gradients.count; i++) { + if(i != 0) { + NSXMLElement * gradientA = gradients[i]; + NSXMLElement * gradientB = nil; + for(NSInteger s = (i - 1); s >= 0; s--) { + gradientB = gradients[s]; + if([self compareElementChildren:gradientA toElement:gradientB] == YES) { + NSString * idString = [gradientB attributeForName:@"id"].stringValue; + if(idString == nil || idString.length == 0) { + idString = [self generateID]; + IJSVGApplyAttributesToElement(@{@"id":idString}, gradientB); + } + NSDictionary * atts = @{@"xlink:href":IJSVGHash(idString)}; + IJSVGApplyAttributesToElement(atts, gradientA); + [gradientA setChildren:nil]; + break; + } + } } } } -- (void)_moveAttributesToGroup +- (BOOL)compareElementChildren:(NSXMLElement *)element + toElement:(NSXMLElement *)toElement { - @autoreleasepool { + NSArray * childrenA = element.children; + NSArray * childrenB = toElement.children; + if(childrenA.count != childrenB.count) { + return NO; + } + for(NSInteger i = 0; i < childrenA.count; i++) { + NSXMLElement * childA = childrenA[i]; + NSXMLElement * childB = childrenB[i]; + if([self compareElement:childA withElement:childB] == NO) { + return NO; + } + } + return YES; +} + +- (void)_moveAttributesToGroupWithElement:(NSXMLElement *)parentElement +{ + + const NSArray * excludedNodes = @[@"script",@"style",@"defs"]; + if([excludedNodes containsObject:parentElement.name] == YES) { + return; + } + + const NSArray * inheritableAttributes = IJSVGInheritableAttributes(); + + NSDictionary * intersection = @{}; + NSMutableArray * grouped = [[[NSMutableArray alloc] init] autorelease]; + + NSInteger counter = 0; + NSInteger size = parentElement.childCount - 1; + for(NSXMLElement * element in parentElement.children) { - NSArray * groups = [_dom nodesForXPath:@"//g" - error:nil]; + NSDictionary * attributes = [self intersectableAttributes:IJSVGElementAttributeDictionary(element) + inheritableAttributes:inheritableAttributes]; - const NSArray * inheritableAttributes = IJSVGInheritableAttributes(); + if(intersection.count == 0) { + intersection = attributes; + } - for(NSXMLElement * group in groups) { - @autoreleasepool { - NSDictionary * intersection = @{}; - - // does it have a mask/clip? - BOOL hasClip = [group attributeForName:@"clip-path"] != nil || - [group attributeForName:@"mask"] != nil; - - NSMutableArray * intersected = [[[NSMutableArray alloc] init] autorelease]; - - // loop around each child - for(NSXMLElement * child in group.children) { - if(child.attributes.count == 0) { - continue; - } - - // straight add on - NSDictionary * atts = [self intersectableAttributes:IJSVGElementAttributeDictionary(child) - inheritableAttributes:inheritableAttributes]; - if(intersection.count == 0) { - intersection = atts; - } else { - - // work out the intersection - NSDictionary * dict = nil; - dict = [self intersectionInheritableAttributes:intersection - currentAttributes:atts - inheritableAttributes:inheritableAttributes]; - - // break if nothing - if(dict == nil) { - continue; - } - - // add them - intersection = dict; - } - // add the child to array - [intersected addObject:child]; + NSDictionary * dict = [self intersectionInheritableAttributes:intersection + currentAttributes:attributes + inheritableAttributes:inheritableAttributes]; + + for(NSString * attributeToRemove in dict.allKeys) { + [element removeAttributeForName:attributeToRemove]; + } + if(dict != nil) { + [grouped addObject:element]; + } + + if(dict == nil || counter == size) { + if(grouped.count > 1) { + NSXMLElement * groupElement = [[[NSXMLElement alloc] initWithName:@"g"] autorelease]; + NSXMLElement * lastElement = (NSXMLElement *)grouped.lastObject; + NSInteger index = lastElement.index; + [parentElement replaceChildAtIndex:index withNode:groupElement]; + for(NSXMLElement * elementToGroup in grouped) { + [elementToGroup detach]; + [groupElement addChild:elementToGroup]; } - - if(intersected.count != 0 && hasClip == NO) { - for(NSXMLElement * child in intersected) { - for(NSString * attributeName in intersection.allKeys) { - // remove attribute - [child removeAttributeForName:attributeName]; - // add the - NSDictionary * atts = nil; - atts = @{attributeName:intersection[attributeName]}; - IJSVGApplyAttributesToElement(atts, group); - } - } + IJSVGApplyAttributesToElement(intersection, groupElement); + } else { + if(grouped.count == 1) { + NSXMLElement * onlyElement = (NSXMLElement *)grouped.lastObject; + IJSVGApplyAttributesToElement(intersection, onlyElement); } } + intersection = @{}; + [grouped removeAllObjects]; } + + counter++; } } @@ -406,6 +522,10 @@ NSString * IJSVGHash(NSString * key) { for(NSString * key in newAttributes.allKeys) { // make sure they are the same and // they are inheritable + if([currentAttributes objectForKey:key] == nil) { + return nil; + } + if([currentAttributes objectForKey:key] != nil && [inheritableAtts containsObject:key] && [newAttributes[key] isEqualToString:currentAttributes[key]]) { @@ -478,7 +598,7 @@ NSString * IJSVGHash(NSString * key) { [(NSXMLElement *)n.parent removeChildAtIndex:n.index]; } } - + } - (void)_collapseGroups @@ -486,11 +606,10 @@ NSString * IJSVGHash(NSString * key) { NSArray * groups = [_dom nodesForXPath:@"//g" error:nil]; const NSArray * inheritable = IJSVGInheritableAttributes(); for(NSXMLElement * group in groups) { - + // dont do anything due to it being referenced - if([group attributeForName:@"id"] != nil || - [group attributeForName:@"transform"] != nil) { - continue; + if([group attributeForName:@"id"] != nil) { + return; } if(group.attributes.count != 0 && group.children.count == 1) { @@ -501,31 +620,35 @@ NSString * IJSVGHash(NSString * key) { continue; } - for(NSXMLNode * gAttribute in group.attributes) { - - // if it just doesnt have the attriute, just add it - if([child attributeForName:gAttribute.name] == NO) { - // remove first, or throws a wobbly - [group removeAttributeForName:gAttribute.name]; - [child addAttribute:gAttribute]; - - } else if([gAttribute.name isEqualToString:@"transform"]) { - - // transform requires concatination - NSXMLNode * childTransform = [child attributeForName:@"transform"]; - childTransform.stringValue = [NSString stringWithFormat:@"%@ %@", - gAttribute.stringValue, childTransform.stringValue]; - - } else if([inheritable containsObject:gAttribute.name] == NO) { - - // if its not inheritable, only remove it if its not equal - NSXMLNode * aAtt = [child attributeForName:gAttribute.name]; - if(aAtt == nil || (aAtt != nil && [aAtt.stringValue isEqualToString:gAttribute.stringValue] == NO)) { - continue; - } - } - [group removeAttributeForName:gAttribute.name]; - } + for(NSXMLNode * gAttribute in group.attributes) { + + // if it just doesnt have the attriute, just add it + if([child attributeForName:gAttribute.name] == NO) { + // remove first, or throws a wobbly + [group removeAttributeForName:gAttribute.name]; + [child addAttribute:gAttribute]; + } else if([gAttribute.name isEqualToString:@"transform"]) { + // transform requires concatination + NSXMLNode * childTransform = [child attributeForName:@"transform"]; + childTransform.stringValue = [NSString stringWithFormat:@"%@ %@", + gAttribute.stringValue, childTransform.stringValue]; + + } else if([inheritable containsObject:gAttribute.name] == NO) { + // if its not inheritable, only remove it if its not equal + NSXMLNode * aAtt = [child attributeForName:gAttribute.name]; + if(aAtt == nil || (aAtt != nil && [aAtt.stringValue isEqualToString:gAttribute.stringValue] == NO)) { + continue; + } + } + [group removeAttributeForName:gAttribute.name]; + } + + // remove the group as its useless! + if(group.attributes.count == 0) { + [child detach]; + [(NSXMLElement *)group.parent replaceChildAtIndex:group.index + withNode:child]; + } } } } @@ -577,7 +700,7 @@ NSString * IJSVGHash(NSString * key) { element.name = @"path"; NSDictionary * atts = @{@"d":data, - @"id":[NSString stringWithFormat:@"path-%ld",(++_pathCount)]}; + @"id":[self generateID]}; IJSVGApplyAttributesToElement(atts, element); // store it against the def @@ -648,39 +771,13 @@ NSString * IJSVGHash(NSString * key) { - (void)applyTransformToElement:(NSXMLElement *)element fromLayer:(IJSVGLayer *)layer { - // dont do anything, they are the same - CGFloat x = layer.frame.origin.x; - CGFloat y = layer.frame.origin.y; - - // check for x or y..they must be included - if(CGAffineTransformEqualToTransform(layer.affineTransform, CGAffineTransformIdentity) && - x == 0.f && y == 0.f) { + CGAffineTransform transform = layer.affineTransform; + if(CGAffineTransformEqualToTransform(transform, CGAffineTransformIdentity) == YES) { return; } - // construct the matrix - CGAffineTransform transform = layer.affineTransform; - - // was there already x and y transform? - BOOL hasXTransform = transform.tx != 0.f; - BOOL hasYTransform = transform.ty != 0.f; - - // move the x and y position - if(x != 0.f || y != 0.f) { - transform = CGAffineTransformConcat( transform, CGAffineTransformMakeTranslation( x, y )); - } - - // x and y were not given so just transform - if(hasXTransform) { - transform.tx /= 2.f; - } - if(hasYTransform) { - transform.ty /= 2.f; - } - // append the string - NSArray * transformArray = [IJSVGTransform affineTransformToSVGTransformAttributeString:transform]; - NSString * transformStr = [transformArray componentsJoinedByString:@" "]; + NSString * transformStr = [IJSVGTransform affineTransformToSVGMatrixString:transform]; // apply it to the node IJSVGApplyAttributesToElement(@{@"transform":transformStr},element); @@ -708,6 +805,9 @@ NSString * IJSVGHash(NSString * key) { - (NSString *)base64EncodedStringFromCGImage:(CGImageRef)image { + if(image == nil) { + return nil; + } // convert the CGImage into an NSImage NSBitmapImageRep * rep = [[[NSBitmapImageRep alloc] initWithCGImage:image] autorelease]; @@ -733,7 +833,7 @@ NSString * IJSVGHash(NSString * key) { patternElement.name = @"pattern"; NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; - dict[@"id"] = [NSString stringWithFormat:@"pattern-%ld",(++_patternCount)]; + dict[@"id"] = [self generateID]; dict[@"width"] = IJSVGShortFloatString(layer.patternNode.width.value); dict[@"height"] = IJSVGShortFloatString(layer.patternNode.height.value); @@ -780,12 +880,12 @@ NSString * IJSVGHash(NSString * key) { toElement:(NSXMLElement *)element { IJSVGGradient * gradient = layer.gradient; - NSString * gradKey = [NSString stringWithFormat:@"gradient-%ld",(++_gradCount)]; + NSString * gradKey = [self generateID]; NSXMLElement * gradientElement = [[[NSXMLElement alloc] init] autorelease]; // work out linear gradient if([gradient isKindOfClass:[IJSVGLinearGradient class]]) { - + IJSVGLinearGradient * lGradient = (IJSVGLinearGradient *)gradient; gradientElement.name = @"linearGradient"; NSDictionary * dict = @{@"id":gradKey, @@ -838,12 +938,21 @@ NSString * IJSVGHash(NSString * key) { atts[@"offset"] = [NSString stringWithFormat:@"%g%%",(location*100)]; // add the color - atts[@"stop-color"] = [IJSVGColor colorStringFromColor:aColor - forceHex:YES]; + NSString * stopColor = [IJSVGColor colorStringFromColor:aColor + forceHex:YES + allowShorthand:YES]; + if([stopColor isEqualToString:@"#000000"] == NO) { + atts[@"stop-color"] = stopColor; + } // we need to work out the color at this point, annoyingly... CGFloat opacity = aColor.alphaComponent; - atts[@"stop-opacity"] = [NSString stringWithFormat:@"%g",opacity]; + + // is opacity is equal to 1, no need to add it as spec + // defaults opacity to 1 anyway :) + if(opacity != 1.f) { + atts[@"stop-opacity"] = IJSVGShortFloatStringWithPrecision(opacity, 2); + } // att the attributes @@ -859,24 +968,9 @@ NSString * IJSVGHash(NSString * key) { // work out the transform NSArray * transforms = layer.gradient.transforms; if(transforms.count != 0.f) { - CGAffineTransform transform = [self affineTransformFromTransforms:transforms]; - - // work out if there is x and y translate - BOOL hasXTransform = transform.tx != 0.f; - BOOL hasYTransform = transform.ty != 0.f; - - // x and y were not given so just transform - if(hasXTransform) { - transform.tx /= 2.f; - } - if(hasYTransform) { - transform.ty /= 2.f; - } - - // apply the attributes - NSArray * transforms = [IJSVGTransform affineTransformToSVGTransformAttributeString:transform]; - NSString * tString = [transforms componentsJoinedByString:@" "]; - IJSVGApplyAttributesToElement(@{@"gradientTransform":tString}, gradientElement); + CGAffineTransform transform = IJSVGConcatTransforms(transforms); + NSString * transformString = [IJSVGTransform affineTransformToSVGMatrixString:transform]; + IJSVGApplyAttributesToElement(@{@"gradientTransform":transformString}, gradientElement); } // add it to the element passed in @@ -885,7 +979,7 @@ NSString * IJSVGHash(NSString * key) { // fill opacity if(layer.opacity != 1.f) { - IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatString(layer.opacity)}, element); + IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatStringWithPrecision(layer.opacity,2)}, element); } } else { IJSVGApplyAttributesToElement(@{@"stroke":IJSVGHashURL(gradKey)}, element); @@ -905,13 +999,16 @@ NSString * IJSVGHash(NSString * key) { fromParent:(NSXMLElement *)parent { NSString * base64String = [self base64EncodedStringFromCGImage:(CGImageRef)layer.contents]; + if(base64String == nil || layer.contents == nil) { + return nil; + } // image element for the SVG NSXMLElement * imageElement = [[[NSXMLElement alloc] init] autorelease]; imageElement.name = @"image"; NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; - dict[@"id"] = [NSString stringWithFormat:@"image-%ld",(++_imageCount)]; + dict[@"id"] = [self generateID]; dict[@"width"] = IJSVGShortFloatString(layer.frame.size.width); dict[@"height"] = IJSVGShortFloatString(layer.frame.size.height); dict[@"xlink:href"] = base64String; @@ -936,10 +1033,17 @@ NSString * IJSVGHash(NSString * key) { e.name = @"path"; CGPathRef path = layer.path; + // copy the path as we want to translate + CGAffineTransform trans = CGAffineTransformMakeTranslation(layer.originalPathOrigin.x, + layer.originalPathOrigin.y); + CGPathRef transformPath = CGPathCreateCopyByTransformingPath(path, &trans); + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; // path - dict[@"d"] = [self pathFromCGPath:path]; + dict[@"d"] = [self pathFromCGPath:transformPath]; + + CGPathRelease(transformPath); // work out even odd rule if([layer.fillRule isEqualToString:kCAFillRuleNonZero] == NO) { @@ -996,7 +1100,7 @@ NSString * IJSVGHash(NSString * key) { parentLayer:(IJSVGPatternLayer *)layer stroke:YES toElement:e]; - + } else if(strokeLayer.strokeColor != nil) { NSColor * strokeColor = [NSColor colorWithCGColor:strokeLayer.strokeColor]; NSString * strokeColorString = [IJSVGColor colorStringFromColor:strokeColor]; @@ -1004,6 +1108,10 @@ NSString * IJSVGHash(NSString * key) { // could be none if(strokeColorString != nil) { dict[@"stroke"] = strokeColorString; + if([strokeColorString isEqualToString:@"none"] == YES) { + // remove the stroke width as its completely useless + [dict removeObjectForKey:@"stroke-width"]; + } } } @@ -1045,7 +1153,7 @@ NSString * IJSVGHash(NSString * key) { } } - + // apply the attributes IJSVGApplyAttributesToElement(dict, e); @@ -1062,7 +1170,7 @@ NSString * IJSVGHash(NSString * key) { // opacity if(layer.opacity != 1.f) { - dict[@"opacity"] = IJSVGShortFloatString(layer.opacity); + dict[@"opacity"] = IJSVGShortFloatStringWithPrecision(layer.opacity,2); } // blendmode - we only every apply a stylesheet blend mode @@ -1111,7 +1219,7 @@ NSString * IJSVGHash(NSString * key) { mask.name = @"mask"; // create the key - NSString * maskKey = [NSString stringWithFormat:@"mask-%ld",(++_maskCount)]; + NSString * maskKey = [self generateID]; NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; dict[@"id"] = maskKey; dict[@"maskContentUnits"] = @"userSpaceOnUse"; @@ -1229,3 +1337,4 @@ void IJSVGExporterPathCaller(void * info, const CGPathElement * pathElement) { } @end + diff --git a/source/IJSVGGradient.h b/source/IJSVGGradient.h index 40854e1..3da49d4 100644 --- a/source/IJSVGGradient.h +++ b/source/IJSVGGradient.h @@ -13,9 +13,6 @@ @interface IJSVGGradient : IJSVGNode @property ( nonatomic, retain ) NSGradient * gradient; -@property ( nonatomic, assign ) CGFloat angle; -@property ( nonatomic, assign ) CGPoint startPoint; -@property ( nonatomic, assign ) CGPoint endPoint; @property ( nonatomic, assign ) CGGradientRef CGGradient; @property ( nonatomic, retain ) IJSVGUnitLength * x1; @property ( nonatomic, retain ) IJSVGUnitLength * x2; @@ -26,6 +23,8 @@ colors:(NSArray **)someColors; - (CGGradientRef)CGGradient; - (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect; + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox; @end diff --git a/source/IJSVGGradient.m b/source/IJSVGGradient.m index 2b7f4c4..6012d9b 100644 --- a/source/IJSVGGradient.m +++ b/source/IJSVGGradient.m @@ -11,7 +11,6 @@ @implementation IJSVGGradient @synthesize gradient, CGGradient; -@synthesize angle, startPoint, endPoint; @synthesize x1, x2, y1, y2; - (void)dealloc @@ -31,8 +30,6 @@ { IJSVGGradient * clone = [super copyWithZone:zone]; clone.gradient = [[self.gradient copy] autorelease]; - clone.startPoint = self.startPoint; - clone.endPoint = self.endPoint; return clone; } @@ -40,14 +37,11 @@ colors:(NSArray **)someColors { // find each stop element - NSArray * stops = [element nodesForXPath:@"stop" - error:nil]; - + NSArray * stops = [element children]; NSMutableArray * colors = [[[NSMutableArray alloc] initWithCapacity:stops.count] autorelease]; CGFloat * stopsParams = (CGFloat *)malloc(stops.count*sizeof(CGFloat)); NSInteger i = 0; - for( NSXMLElement * stop in stops ) - { + for( NSXMLElement * stop in stops ) { // find the offset CGFloat offset = [stop attributeForName:@"offset"].stringValue.floatValue; if( offset > 1.f ) { @@ -134,7 +128,9 @@ } - (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox { } diff --git a/source/IJSVGGradientLayer.h b/source/IJSVGGradientLayer.h index f8bab11..32d5acc 100644 --- a/source/IJSVGGradientLayer.h +++ b/source/IJSVGGradientLayer.h @@ -15,6 +15,10 @@ } +@property (nonatomic, assign) CGRect viewBox; @property (nonatomic, retain) IJSVGGradient * gradient; +@property (nonatomic, assign) CGAffineTransform absoluteTransform; +@property (nonatomic, assign) CGRect objectRect; @end + diff --git a/source/IJSVGGradientLayer.m b/source/IJSVGGradientLayer.m index 4c09924..c28c0df 100644 --- a/source/IJSVGGradientLayer.m +++ b/source/IJSVGGradientLayer.m @@ -10,7 +10,10 @@ @implementation IJSVGGradientLayer +@synthesize viewBox; @synthesize gradient; +@synthesize absoluteTransform; +@synthesize objectRect; - (void)dealloc { @@ -36,7 +39,14 @@ } // draw the gradient - [self.gradient drawInContextRef:ctx rect:self.bounds]; + CGAffineTransform trans = CGAffineTransformMakeTranslation(-CGRectGetMinX(objectRect), + -CGRectGetMinY(objectRect)); + CGAffineTransform transform = CGAffineTransformConcat(absoluteTransform,trans); + + [self.gradient drawInContextRef:ctx + objectRect:objectRect + absoluteTransform:transform + viewPort:self.viewBox]; } @end diff --git a/source/IJSVGImage.m b/source/IJSVGImage.m index 0b7d488..6b7cb3a 100644 --- a/source/IJSVGImage.m +++ b/source/IJSVGImage.m @@ -22,10 +22,16 @@ - (void)loadFromBase64EncodedString:(NSString *)encodedString { + if([encodedString hasPrefix:@"data:"]) { + encodedString = [encodedString stringByReplacingOccurrencesOfString:@"\\s+" + withString:@"" + options:NSRegularExpressionSearch + range:NSMakeRange(0, encodedString.length)]; + } NSURL * URL = [NSURL URLWithString:encodedString]; NSData * data = [NSData dataWithContentsOfURL:URL]; - // no data, jsut ignore...invalid probably + // no data, just ignore...invalid probably if(data == nil) { return; } diff --git a/source/IJSVGImageLayer.m b/source/IJSVGImageLayer.m index 83fff81..e20c3ad 100644 --- a/source/IJSVGImageLayer.m +++ b/source/IJSVGImageLayer.m @@ -34,11 +34,21 @@ // set the frame, simple stuff self.frame = (CGRect){ .origin = CGPointZero, - .size = CGSizeMake( CGImageGetWidth(imageRef), - CGImageGetHeight(imageRef)) + .size = CGSizeMake(CGImageGetWidth(imageRef), + CGImageGetHeight(imageRef)) }; } return self; } +- (void)setNeedsDisplay +{ + // swap the content around on call + // because set needs display discards previous + // content - yolo! + id oldContent = self.contents; + [super setNeedsDisplay]; + self.contents = oldContent; +} + @end diff --git a/source/IJSVGLayer.m b/source/IJSVGLayer.m index 4ae5dbb..8faee8b 100644 --- a/source/IJSVGLayer.m +++ b/source/IJSVGLayer.m @@ -32,7 +32,6 @@ IJSVGEndTransactionLock(); } - + (NSArray *)deepestSublayersOfLayer:(CALayer *)layer { NSMutableArray * arr = [[[NSMutableArray alloc] init] autorelease]; @@ -80,14 +79,14 @@ - (void)setBackingScaleFactor:(CGFloat)newFactor { - if(self.backingScaleFactor == newFactor) { - return; - } - backingScaleFactor = newFactor; - self.contentsScale = newFactor; - self.rasterizationScale = newFactor; - [self setNeedsDisplay]; -} + if(self.backingScaleFactor == newFactor) { + return; + } + backingScaleFactor = newFactor; + self.contentsScale = newFactor; + self.rasterizationScale = newFactor; + [self setNeedsDisplay]; +} - (void)_customRenderInContext:(CGContextRef)ctx { @@ -176,18 +175,6 @@ return; } [self _customRenderInContext:ctx]; -} - -- (CGPoint)absoluteOrigin -{ - CGPoint point = CGPointZero; - CALayer * pLayer = self; - while(pLayer != nil) { - point.x += pLayer.frame.origin.x; - point.y += pLayer.frame.origin.y; - pLayer = pLayer.superlayer; - } - return point; } @end diff --git a/source/IJSVGLayerTree.m b/source/IJSVGLayerTree.m index 04837a8..6ee8a8d 100644 --- a/source/IJSVGLayerTree.m +++ b/source/IJSVGLayerTree.m @@ -67,44 +67,50 @@ layer = [self layerForImage:(IJSVGImage *)node]; } - // apply any basic defaults - [self applyDefaultsToLayer:layer - fromNode:node]; + [self applyDefaultsToLayer:layer fromNode:node]; - return [self proposedLayerAfterApplyingTransforms:layer - transforms:node.transforms]; + // create the new layer + layer = [self applyTransforms:node.transforms + toLayer:layer]; + + return layer; } -- (IJSVGLayer *)proposedLayerAfterApplyingTransforms:(IJSVGLayer *)layer - transforms:(NSArray *)transforms +- (IJSVGLayer *)applyTransforms:(NSArray *)transforms + toLayer:(IJSVGLayer *)layer + { + + // do some magic transform + if(transforms.count == 0) { + return layer; + } + // add any transforms - if(transforms.count != 0) { - IJSVGLayer * topLayer = nil; - IJSVGLayer * parentLayer = nil; + IJSVGLayer * topLayer = nil; + IJSVGLayer * parentLayer = nil; + + for(IJSVGTransform * transform in transforms) { + // make sure we apply the transform to the parent + // so they stack + IJSVGGroupLayer * childLayer = [[[IJSVGGroupLayer alloc] init] autorelease]; + childLayer.affineTransform = transform.CGAffineTransform; - for(IJSVGTransform * transform in transforms) { - // make sure we apply the transform to the parent - // so they stack - IJSVGGroupLayer * childLayer = [[[IJSVGGroupLayer alloc] init] autorelease]; - childLayer.affineTransform = transform.CGAffineTransform; - - // add it to the parent layer - if(parentLayer != nil) { - [parentLayer addSublayer:childLayer]; - } else { - // make sure we keep track of the top most layer - topLayer = childLayer; - } - - // reset parent layer to the new child - parentLayer = childLayer; + // add it to the parent layer + if(parentLayer != nil) { + [parentLayer addSublayer:childLayer]; + } else { + // make sure we keep track of the top most layer + topLayer = childLayer; } - // swap the layer around - [parentLayer addSublayer:layer]; - layer = topLayer; + // reset parent layer to the new child + parentLayer = childLayer; } + + // swap the layer around + [parentLayer addSublayer:layer]; + layer = topLayer; return layer; } @@ -127,14 +133,25 @@ layer.hidden = YES; } + CGRect frame = layer.frame; + frame.origin.x += [node.x computeValue:frame.size.width]; + frame.origin.y += [node.y computeValue:frame.size.height]; + layer.frame = frame; } - (IJSVGLayer *)layerForImage:(IJSVGImage *)image { IJSVGImageLayer * layer = [[[IJSVGImageLayer alloc] initWithCGImage:image.CGImage] autorelease]; - layer.frame = CGRectMake(image.x.value, image.y.value, image.width.value, image.height.value); layer.affineTransform = CGAffineTransformConcat(layer.affineTransform, CGAffineTransformMakeScale( 1.f, -1.f)); + + // make sure we set the width and height correctly, + // as this may not be exactly the same as the size of the + // given image + CGRect frame = layer.frame; + frame.size.width = image.width.value; + frame.size.height = image.height.value; + layer.frame = frame; return layer; } @@ -145,10 +162,7 @@ [groupLayer addSublayer:[self layerForNode:node]]; } groupLayer.frame = (CGRect){ - .origin = (CGPoint){ - .x = group.x.value, - .y = group.y.value - }, + .origin = CGPointZero, .size = (CGSize){ .width = group.width.value, .height = group.height.value @@ -180,16 +194,21 @@ } - (IJSVGShapeLayer *)basicLayerForPath:(IJSVGPath *)path + originalBoundingBox:(CGRect *)originalBoundingBox { // setup path and layer IJSVGShapeLayer * layer = [[[IJSVGShapeLayer alloc] init] autorelease]; CGPathRef introPath = [path newPathRefByAutoClosingPath:NO]; - CGRect bounds = [self correctedBounds:CGPathGetBoundingBox(introPath)]; + *originalBoundingBox = CGRectIntegral(CGPathGetBoundingBox(introPath)); + layer.originalPathOrigin = (*originalBoundingBox).origin; + + CGRect bounds = [self correctedBounds:*originalBoundingBox]; // zero back the path CGAffineTransform trans = CGAffineTransformMakeTranslation(-bounds.origin.x, -bounds.origin.y); + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(introPath, &trans); layer.path = transformedPath; @@ -198,9 +217,7 @@ CGPathRelease(introPath); // set the bounds - bounds.origin.x += path.x.value; - bounds.origin.y += path.y.value; - layer.frame = bounds; + layer.frame = CGRectIntegral(bounds); // basic fill color and rule layer.fillColor = nil; @@ -217,6 +234,26 @@ return mask; } +- (CGAffineTransform)absoluteTransform:(IJSVGNode *)node +{ + CGAffineTransform parentAbsoluteTransform = CGAffineTransformIdentity; + IJSVGNode * intermediateNode = node.intermediateParentNode; + node = node.parentNode; + while(node != nil) { + // intermediateParent should be skipped as these are elements + // created by use statements and are technically fake elements, but the + // spec says use them for the transforms, yolo! + if(node == intermediateNode) { + node = node.parentNode; + continue; + } + CGAffineTransform trans = IJSVGConcatTransforms(node.transforms); + parentAbsoluteTransform = CGAffineTransformConcat(trans,parentAbsoluteTransform); + node = node.parentNode; + } + return parentAbsoluteTransform; +} + - (IJSVGLayer *)layerForPath:(IJSVGPath *)path { // is there a sub SVG? @@ -226,7 +263,13 @@ } // garb the basic shape layer - IJSVGShapeLayer * layer = [self basicLayerForPath:path]; + CGRect originalShapeBounds; + IJSVGShapeLayer * layer = [self basicLayerForPath:path + originalBoundingBox:&originalShapeBounds]; + + BOOL hasStroke = (path.strokeColor != nil || + path.strokePattern != nil || + path.strokeGradient != nil); // any gradient? if(self.fillColor == nil && path.fillGradient != nil) { @@ -234,15 +277,12 @@ // create the gradient IJSVGGradientLayer * gradLayer = [self gradientLayerForLayer:layer gradient:path.fillGradient - fromNode:path]; + fromNode:path + objectRect:originalShapeBounds + shouldMask:YES]; // add the gradient and set it against the layer [layer addSublayer:gradLayer]; - - // apply offsets - [self applyOffsetsToLayer:gradLayer - fromNode:path.fillGradient]; - layer.gradientFillLayer = gradLayer; } else if(self.fillColor == nil && path.fillPattern != nil) { @@ -264,6 +304,9 @@ // only use the global if its set and the current colors // alpha channel is not 0.f, otherwise its a blank clear color, // aka, not filled in + + + NSColor * fColor = path.fillColor; BOOL hasColor = (fColor.alphaComponent == 0.f || fColor == nil) == NO; BOOL hasFill = path.fillPattern != nil || path.fillGradient != nil; @@ -292,9 +335,7 @@ } // stroke it - if(path.strokeColor != nil || - path.strokePattern != nil || - path.strokeGradient != nil) { + if(hasStroke == YES) { // load the stroke layer IJSVGStrokeLayer * strokeLayer = [self strokeLayer:layer @@ -311,7 +352,8 @@ // create the gradient IJSVGGradientLayer * gradLayer = [self gradientStrokeLayerForLayer:layer gradient:path.strokeGradient - fromNode:path]; + fromNode:path + objectRect:originalShapeBounds]; moveStrokeLayer = YES; gradLayer.mask = strokeLayer; @@ -402,33 +444,19 @@ - (IJSVGGradientLayer *)gradientStrokeLayerForLayer:(IJSVGShapeLayer *)layer gradient:(IJSVGGradient *)gradient fromNode:(IJSVGNode *)path + objectRect:(CGRect)objectRect { // the gradient drawing layer - IJSVGGradientLayer * gradLayer = [[[IJSVGGradientLayer alloc] init] autorelease]; - gradLayer.gradient = gradient; - - // is there a fill opacity? - if(path.fillOpacity.value != 0.f) { - gradLayer.opacity = path.fillOpacity.value; - } + IJSVGGradientLayer * gradLayer = [self gradientLayerForLayer:layer + gradient:gradient + fromNode:path + objectRect:objectRect + shouldMask:NO]; // set the bounds CGRect bounds = CGPathGetBoundingBox(layer.path); bounds = [self correctBounds:bounds forStrokedPath:path]; gradLayer.frame = bounds; - - // display it - [gradLayer setNeedsDisplay]; - - if(path.fillGradient.units == IJSVGUnitUserSpaceOnUse) { - // move back if needed - gradLayer.frame = (CGRect){ - .size = gradLayer.frame.size, - .origin = CGPointMake(-fabs(gradLayer.frame.origin.x), - -fabs(gradLayer.frame.origin.y)) - }; - } - return gradLayer; } @@ -436,34 +464,28 @@ - (IJSVGGradientLayer *)gradientLayerForLayer:(IJSVGShapeLayer *)layer gradient:(IJSVGGradient *)gradient fromNode:(IJSVGNode *)path + objectRect:(CGRect)objectRect + shouldMask:(BOOL)shouldMask { - // add the mask - IJSVGShapeLayer * mask = [self layerMaskFromLayer:layer - fromNode:path]; - // the gradient drawing layer IJSVGGradientLayer * gradLayer = [[[IJSVGGradientLayer alloc] init] autorelease]; - gradLayer.frame = CGPathGetBoundingBox(((IJSVGShapeLayer *)layer).path); + gradLayer.viewBox = self.viewBox; + gradLayer.frame = layer.bounds; gradLayer.gradient = gradient; - gradLayer.mask = mask; + gradLayer.absoluteTransform = [self absoluteTransform:path]; + gradLayer.objectRect = CGRectApplyAffineTransform(objectRect, gradLayer.absoluteTransform); + + if(shouldMask == YES) { + // add the mask + IJSVGShapeLayer * mask = [self layerMaskFromLayer:layer + fromNode:path]; + gradLayer.mask = mask; + } // is there a fill opacity? if(path.fillOpacity.value != 0.f) { gradLayer.opacity = path.fillOpacity.value; } - - // display it - [gradLayer setNeedsDisplay]; - - if(path.fillGradient.units == IJSVGUnitUserSpaceOnUse) { - // move back if needed - gradLayer.frame = (CGRect){ - .size = gradLayer.frame.size, - .origin = CGPointMake(-fabs(gradLayer.frame.origin.x), - -fabs(gradLayer.frame.origin.y)) - }; - } - return gradLayer; } @@ -551,7 +573,7 @@ } - (IJSVGStrokeLayer *)strokeLayer:(IJSVGShapeLayer *)layer - fromNode:(IJSVGNode *)path + fromNode:(IJSVGPath *)path { // same as fill, dont use global if the alpha is 0.f, but do use it // if there is a pattern or gradient @@ -742,4 +764,17 @@ } } ++ (void)log:(IJSVGLayer *)layer + depth:(NSInteger)depth { + NSLog(@"%@%@: %@, Transforms: %@",[@"" stringByPaddingToLength:depth + withString:@"\t" + startingAtIndex:0],layer, + NSStringFromRect(layer.frame), + [IJSVGTransform affineTransformToSVGTransformAttributeString:layer.affineTransform]); + for(IJSVGLayer * sublayer in layer.sublayers) { + [self log:(IJSVGLayer *)sublayer + depth:depth++]; + } +} + @end diff --git a/source/IJSVGLinearGradient.h b/source/IJSVGLinearGradient.h index 57c8d7d..5601bd0 100644 --- a/source/IJSVGLinearGradient.h +++ b/source/IJSVGLinearGradient.h @@ -12,8 +12,6 @@ @interface IJSVGLinearGradient : IJSVGGradient + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGLinearGradient *)aGradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint; + gradient:(IJSVGLinearGradient *)aGradient; @end diff --git a/source/IJSVGLinearGradient.m b/source/IJSVGLinearGradient.m index f8ab845..76007c2 100644 --- a/source/IJSVGLinearGradient.m +++ b/source/IJSVGLinearGradient.m @@ -13,8 +13,6 @@ + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGLinearGradient *)aGradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint { CGFloat px1 = [[element attributeForName:@"x1"] stringValue].floatValue; @@ -25,10 +23,8 @@ // work out each coord, and work out if its a % or not // annoyingly we need to check them all against each other -_- BOOL isPercent = NO; - if(px1 <= 1.f && px2 <= 1.f && py1 <= 1.f && py2 <= 1.f) { - isPercent = YES; - } else if((px1 >= 0.f && px1 <= 1.f) && (px2 >= 0.f && px2 <= 1.f) && - (py1 >= 0.f && py1 <= 1.f) && (py2 >= 0.f && py2 <= 1.f)) { + if((px1 >= 0.f && px1 <= 1.f) && (px2 >= 0.f && px2 <= 1.f) && + (py1 >= 0.f && py1 <= 1.f) && (py2 >= 0.f && py2 <= 1.f)) { isPercent = YES; } @@ -62,32 +58,53 @@ } - (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox { - // grab the start and end point - CGPoint aStartPoint = (CGPoint){ - .x = [self.x1 computeValue:rect.size.width], - .y = [self.y1 computeValue:rect.size.height] + BOOL inUserSpace = self.units == IJSVGUnitUserSpaceOnUse; + + CGPoint gradientStartPoint = CGPointZero; + CGPoint gradientEndPoint = CGPointZero; + CGAffineTransform absTransform = absoluteTransform; + CGAffineTransform selfTransform = IJSVGConcatTransforms(self.transforms); + +#pragma mark User Space On Use + CGContextSaveGState(ctx); + { + if(inUserSpace == YES) { + CGFloat width = CGRectGetWidth(viewBox); + CGFloat height = CGRectGetHeight(viewBox); + gradientStartPoint = CGPointMake([self.x1 computeValue:width], + [self.y1 computeValue:height]); + + gradientEndPoint = CGPointMake([self.x2 computeValue:width], + [self.y2 computeValue:height]); + + // transform absolute - due to user space + CGContextConcatCTM(ctx, absTransform); + } else { +#pragma mark Object Bounding Box + CGFloat width = CGRectGetWidth(objectRect); + CGFloat height = CGRectGetHeight(objectRect); + gradientStartPoint = CGPointMake([self.x1 computeValue:width], + [self.y1 computeValue:height]); + + gradientEndPoint = CGPointMake([self.x2 computeValue:width], + [self.y2 computeValue:height]); + } + + // transform the context + CGContextConcatCTM(ctx, selfTransform); + + // draw the gradient + CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation| + kCGGradientDrawsAfterEndLocation; + + CGContextDrawLinearGradient(ctx, self.CGGradient, gradientStartPoint, + gradientEndPoint, options); }; - - CGPoint aEndPoint = (CGPoint){ - .x = [self.x2 computeValue:rect.size.width], - .y = [self.y2 computeValue:rect.size.height] - }; - - // convert the nsgradient to a CGGradient - CGGradientRef gRef = [self CGGradient]; - - // apply transform for each point - for( IJSVGTransform * transform in self.transforms ) { - CGAffineTransform trans = transform.CGAffineTransform; - aStartPoint = CGPointApplyAffineTransform(aStartPoint, trans); - aEndPoint = CGPointApplyAffineTransform(aEndPoint, trans); - } - - // draw the gradient - CGGradientDrawingOptions opt = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; - CGContextDrawLinearGradient(ctx, gRef, aStartPoint, aEndPoint, opt); + CGContextRestoreGState(ctx); } @end diff --git a/source/IJSVGNode.h b/source/IJSVGNode.h index 0adf80f..b3f3527 100644 --- a/source/IJSVGNode.h +++ b/source/IJSVGNode.h @@ -16,6 +16,7 @@ @class IJSVGGradient; @class IJSVGGroup; @class IJSVGPattern; +@class IJSVGTransform; typedef NS_ENUM( NSInteger, IJSVGNodeType ) { IJSVGNodeTypeGroup, @@ -120,12 +121,13 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; @property ( nonatomic, retain ) NSColor * strokeColor; @property ( nonatomic, copy ) NSString * identifier; @property ( nonatomic, assign ) IJSVGNode * parentNode; +@property ( nonatomic, assign ) IJSVGNode * intermediateParentNode; @property ( nonatomic, retain ) IJSVGGroup * clipPath; @property ( nonatomic, retain ) IJSVGGroup * mask; @property ( nonatomic, assign ) IJSVGWindingRule windingRule; @property ( nonatomic, assign ) IJSVGLineCapStyle lineCapStyle; @property ( nonatomic, assign ) IJSVGLineJoinStyle lineJoinStyle; -@property ( nonatomic, retain ) NSArray * transforms; +@property ( nonatomic, retain ) NSArray * transforms; @property ( nonatomic, retain ) IJSVGDef * def; @property ( nonatomic, retain ) IJSVGGradient * fillGradient; @property ( nonatomic, retain ) IJSVGPattern * fillPattern; diff --git a/source/IJSVGNode.m b/source/IJSVGNode.m index 8a03b5a..04a33d4 100644 --- a/source/IJSVGNode.m +++ b/source/IJSVGNode.m @@ -8,6 +8,7 @@ #import "IJSVGNode.h" #import "IJSVGDef.h" +#import "IJSVGUtils.h" @implementation IJSVGNode @@ -29,6 +30,7 @@ @synthesize opacity; @synthesize identifier; @synthesize parentNode; +@synthesize intermediateParentNode; @synthesize transforms; @synthesize windingRule; @synthesize def; @@ -131,6 +133,12 @@ if([string isEqualToString:@"tspan"] || kind == NSXMLTextKind) { return IJSVGNodeTypeTextSpan; } + + // are we commong HTML? - if so just treat as a group + if(IJSVGIsCommonHTMLElementName(string) == YES) { + return IJSVGNodeTypeGroup; + } + return IJSVGNodeTypeNotFound; } @@ -233,10 +241,12 @@ - (IJSVGDef *)defForID:(NSString *)anID { IJSVGDef * aDef = nil; - if( (aDef = [def defForID:anID]) != nil ) + if( (aDef = [def defForID:anID]) != nil ) { return aDef; - if( parentNode != nil ) + } + if( parentNode != nil ) { return [parentNode defForID:anID]; + } return nil; } @@ -248,7 +258,7 @@ // winding rule can inherit.. - (IJSVGWindingRule)windingRule { - if( windingRule == IJSVGWindingRuleInherit && parentNode != nil ) { + if(windingRule == IJSVGWindingRuleInherit && parentNode != nil) { return parentNode.windingRule; } return windingRule; @@ -256,20 +266,20 @@ - (IJSVGLineCapStyle)lineCapStyle { - if( lineCapStyle == IJSVGLineCapStyleInherit ) - { - if( parentNode != nil ) + if( lineCapStyle == IJSVGLineCapStyleInherit ) { + if( parentNode != nil ) { return parentNode.lineCapStyle; + } } return lineCapStyle; } - (IJSVGLineJoinStyle)lineJoinStyle { - if( lineJoinStyle == IJSVGLineJoinStyleInherit ) - { - if( parentNode != nil ) + if( lineJoinStyle == IJSVGLineJoinStyleInherit ) { + if( parentNode != nil ) { return parentNode.lineJoinStyle; + } } return lineJoinStyle; } @@ -327,8 +337,9 @@ // must be on the path, it can also be on the - (NSColor *)fillColor { - if( fillColor == nil && parentNode != nil ) + if( fillColor == nil && parentNode != nil ) { return parentNode.fillColor; + } return fillColor; } diff --git a/source/IJSVGParser.h b/source/IJSVGParser.h index 8365d39..5d6b68a 100644 --- a/source/IJSVGParser.h +++ b/source/IJSVGParser.h @@ -95,6 +95,7 @@ handleForeignObject:(IJSVGForeignObject *)foreignObject NSMutableDictionary * _defNodes; NSMutableDictionary * _baseDefNodes; NSMutableArray * _svgs; + NSMutableArray * _definedGroups; struct { unsigned int shouldHandleForeignObject: 1; diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index 907ed37..11d961e 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -45,6 +45,7 @@ [_parsedNodes release], _parsedNodes = nil; [_defNodes release], _defNodes = nil; [_baseDefNodes release], _baseDefNodes = nil; + [_definedGroups release], _definedGroups = nil; [_svgs release], _svgs = nil; [super dealloc]; } @@ -178,7 +179,8 @@ // parse common attributes on the SVG element [self _parseElementForCommonAttributes:svgElement - node:self]; + node:self + ignoreAttributes:nil]; // find the sizebox! NSXMLNode * attribute = nil; @@ -224,7 +226,8 @@ // now everything has been done we need to compute the style tree for(NSDictionary * dict in _parsedNodes) { [self _postParseElementForCommonAttributes:dict[@"element"] - node:dict[@"node"]]; + node:dict[@"node"] + ignoreAttributes:nil]; } // dont need the style sheet or the parsed nodes as this point @@ -235,6 +238,7 @@ - (void)_postParseElementForCommonAttributes:(NSXMLElement *)element node:(IJSVGNode *)node + ignoreAttributes:(NSArray *)ignoredAttributes { // first of all, compute a style sheet @@ -244,6 +248,9 @@ // attribute helpers typedef void (^cp)(NSString *); void (^attr)(const NSString *, cp) = ^(NSString * key, cp block) { + if([ignoredAttributes containsObject:key]) { + return; + } NSString * v = [element attributeForName:key].stringValue ?: [style property:key]; if(v != nil && v.length != 0) { @@ -397,12 +404,15 @@ node.fillPattern = (IJSVGPattern *)obj; } } else { - // its a color node.fillColor = [IJSVGColor colorFromString:value]; - if(node.fillOpacity.value != 1.f) { - node.fillColor = [IJSVGColor changeAlphaOnColor:node.fillColor - to:node.fillOpacity.value]; - } + } + }); + + // fill opacity + attr(IJSVGAttributeFillOpacity, ^(NSString * value) { + if(node.fillOpacity.value != 1.f) { + node.fillColor = [IJSVGColor changeAlphaOnColor:node.fillColor + to:node.fillOpacity.value]; } }); @@ -425,22 +435,34 @@ } - (id)definedObjectForID:(NSString *)anID + xmlElement:(NSXMLElement **)element { // check base def nodes first, then check rest of document NSXMLElement * parseElement = _baseDefNodes[anID] ?: _defNodes[anID]; if(parseElement != nil) { - // parse the element - IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; - // parse the block + if(element != nil && element != NULL) { + *element = parseElement; + } + IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; [self _parseBaseBlock:parseElement intoGroup:group def:NO]; + if(_definedGroups == nil) { + _definedGroups = [[NSMutableArray alloc] init]; + } + [_definedGroups addObject:group]; return [group defForID:anID]; } return nil; } +- (id)definedObjectForID:(NSString *)anID +{ + return [self definedObjectForID:anID + xmlElement:nil]; +} + - (BOOL)isFont { return [_glyphs count] != 0; @@ -476,9 +498,11 @@ - (void)_parseElementForCommonAttributes:(NSXMLElement *)element node:(IJSVGNode *)node + ignoreAttributes:(NSArray *)ignoredAttributes { [self _postParseElementForCommonAttributes:element - node:node]; + node:node + ignoreAttributes:ignoredAttributes]; } - (void)_setupDefaultsForNode:(IJSVGNode *)node @@ -511,7 +535,7 @@ for(NSXMLElement * element in anElement.children) { // not a def - if([IJSVGNode typeForString:element.name + if([IJSVGNode typeForString:element.localName kind:element.kind] != IJSVGNodeTypeDef) { continue; } @@ -519,7 +543,7 @@ // store each object for(NSXMLElement * childDef in element.children) { // is there any stylesheets within this? - IJSVGNodeType childType = [IJSVGNode typeForString:childDef.name + IJSVGNodeType childType = [IJSVGNode typeForString:childDef.localName kind:element.kind]; switch(childType) { @@ -552,7 +576,7 @@ intoGroup:(IJSVGGroup *)parentGroup def:(BOOL)flag { - NSString * subName = element.name; + NSString * subName = element.localName; NSXMLNodeKind nodeKind = element.kind; IJSVGNodeType aType = [IJSVGNode typeForString:subName kind:nodeKind]; @@ -587,7 +611,8 @@ // grab common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; // work out the SVG NSError * error = nil; @@ -632,7 +657,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; // pass the commands for it [self _parsePathCommandData:[[element attributeForName:(NSString *)IJSVGAttributeD] stringValue] @@ -673,7 +699,8 @@ // find common attributes [self _setupDefaultsForNode:group]; [self _parseElementForCommonAttributes:element - node:group]; + node:group + ignoreAttributes:nil]; // recursively parse blocks [self _parseBlock:element @@ -699,7 +726,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parsePathCommandData:[[element attributeForName:(NSString *)IJSVGAttributeD] stringValue] intoPath:path]; @@ -721,7 +749,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parsePolygon:element intoPath:path]; [parentGroup addDef:path]; @@ -743,7 +772,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parsePolyline:element intoPath:path]; [parentGroup addDef:path]; @@ -767,7 +797,8 @@ [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:@[@"x",@"y"]]; [parentGroup addDef:path]; break; } @@ -785,7 +816,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parseLine:element intoPath:path]; [parentGroup addDef:path]; @@ -807,7 +839,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parseCircle:element intoPath:path]; [parentGroup addDef:path]; @@ -828,7 +861,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parseEllipse:element intoPath:path]; [parentGroup addDef:path]; @@ -847,13 +881,30 @@ node.identifier = nil; } - node.parentNode = parentGroup; + // at this point, we need to create another group! + IJSVGGroup * subGroup = [[[IJSVGGroup alloc] init] autorelease]; + subGroup.parentNode = parentGroup; + [subGroup addChild:node]; + node.parentNode = subGroup; + node.intermediateParentNode = subGroup; + + // is there a width and height? + CGFloat x = [element attributeForName:(NSString *)IJSVGAttributeX].stringValue.floatValue; + CGFloat y = [element attributeForName:(NSString *)IJSVGAttributeY].stringValue.floatValue; + + // we need to add a transform to the subgroup + subGroup.transforms = @[[IJSVGTransform transformByTranslatingX:x y:y]]; + if(!flag) { - [parentGroup addChild:node]; + [parentGroup addChild:subGroup]; } + // parse attributes from element onto group - but spec + // says ignore x, y, width, height and xlink:href... [self _parseElementForCommonAttributes:element - node:node]; + node:node + ignoreAttributes:@[@"x",@"y",@"width", + @"height",@"xlink:href"]]; [parentGroup addDef:node]; break; @@ -864,43 +915,36 @@ NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; - IJSVGNode * node = [self definedObjectForID:xlinkID]; + NSXMLElement * referenceElement; + IJSVGNode * node = [self definedObjectForID:xlinkID + xmlElement:&referenceElement]; if( node != nil ) { // we are a clone + NSXMLElement * elementCopy = [self mergedElement:element + withReferenceElement:referenceElement]; + IJSVGLinearGradient * grad = [[[IJSVGLinearGradient alloc] init] autorelease]; grad.type = aType; - [grad applyPropertiesFromNode:node]; - - grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; - CGPoint startPoint, endPoint; - [IJSVGLinearGradient parseGradient:element - gradient:grad - startPoint:&startPoint - endPoint:&endPoint]; + grad.gradient = [IJSVGLinearGradient parseGradient:elementCopy + gradient:grad]; [self _setupDefaultsForNode:grad]; - [self _parseElementForCommonAttributes:element - node:grad]; - grad.startPoint = startPoint; - grad.endPoint = endPoint; + [self _parseElementForCommonAttributes:elementCopy + node:grad + ignoreAttributes:nil]; [parentGroup addDef:grad]; break; } IJSVGLinearGradient * gradient = [[[IJSVGLinearGradient alloc] init] autorelease]; gradient.type = aType; - - CGPoint startPoint, endPoint; gradient.gradient = [IJSVGLinearGradient parseGradient:element - gradient:gradient - startPoint:&startPoint - endPoint:&endPoint]; + gradient:gradient]; [self _setupDefaultsForNode:gradient]; [self _parseElementForCommonAttributes:element - node:gradient]; - gradient.startPoint = startPoint; - gradient.endPoint = endPoint; + node:gradient + ignoreAttributes:nil]; [parentGroup addDef:gradient]; break; } @@ -910,44 +954,37 @@ NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; - IJSVGNode * node = [self definedObjectForID:xlinkID]; - if( node != nil ) - { + NSXMLElement * referenceElement; + IJSVGNode * node = [self definedObjectForID:xlinkID + xmlElement:&referenceElement]; + if( node != nil ) { // we are a clone IJSVGRadialGradient * grad = [[[IJSVGRadialGradient alloc] init] autorelease]; grad.type = aType; - [grad applyPropertiesFromNode:node]; - grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; - CGPoint startPoint, endPoint; - [IJSVGRadialGradient parseGradient:element - gradient:grad - startPoint:&startPoint - endPoint:&endPoint]; + NSXMLElement * elementCopy = [self mergedElement:element + withReferenceElement:referenceElement]; + + grad.gradient = [IJSVGRadialGradient parseGradient:elementCopy + gradient:grad]; [self _setupDefaultsForNode:grad]; - [self _parseElementForCommonAttributes:element - node:grad]; - grad.startPoint = startPoint; - grad.endPoint = endPoint; + [self _parseElementForCommonAttributes:elementCopy + node:grad + ignoreAttributes:nil]; [parentGroup addDef:grad]; break; } IJSVGRadialGradient * gradient = [[[IJSVGRadialGradient alloc] init] autorelease]; gradient.type = aType; - - CGPoint startPoint, endPoint; gradient.gradient = [IJSVGRadialGradient parseGradient:element - gradient:gradient - startPoint:&startPoint - endPoint:&endPoint]; - gradient.startPoint = startPoint; - gradient.endPoint = endPoint; + gradient:gradient]; [self _setupDefaultsForNode:gradient]; [self _parseElementForCommonAttributes:element - node:gradient]; + node:gradient + ignoreAttributes:nil]; [parentGroup addDef:gradient]; break; } @@ -964,7 +1001,8 @@ // find common attributes [self _parseElementForCommonAttributes:element - node:group]; + node:group + ignoreAttributes:nil]; // recursively parse blocks [self _parseBlock:element @@ -982,7 +1020,8 @@ // find common attributes [self _parseElementForCommonAttributes:element - node:pattern]; + node:pattern + ignoreAttributes:nil]; // pattern has children [self _parseBlock:element @@ -1001,7 +1040,8 @@ // find common attributes [self _parseElementForCommonAttributes:element - node:image]; + node:image + ignoreAttributes:nil]; // from base64 [image loadFromBase64EncodedString:[[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]]; @@ -1015,6 +1055,18 @@ } } +- (NSXMLElement *)mergedElement:(NSXMLElement *)element + withReferenceElement:(NSXMLElement *)reference +{ + NSXMLElement * copy = [[reference copy] autorelease]; + for(NSXMLNode * attribute in element.attributes) { + [copy removeAttributeForName:attribute.name]; + attribute = [[attribute copy] autorelease]; + [copy addAttribute:attribute]; + } + return copy; +} + - (void)_parseBlock:(NSXMLElement *)anElement intoGroup:(IJSVGGroup*)parentGroup def:(BOOL)flag @@ -1188,8 +1240,7 @@ NSInteger count = 0; CGFloat * params = [IJSVGUtils commandParameters:points count:&count]; - if( (count % 2) != 0 ) - { + if( (count % 2) != 0 ) { // error occured, free the params free(params); return; @@ -1198,12 +1249,12 @@ // construct a command NSMutableString * str = [[[NSMutableString alloc] init] autorelease]; [str appendFormat:@"M%f,%f L",params[0],params[1]]; - for( NSInteger i = 2; i < count; i+=2 ) - { + for( NSInteger i = 2; i < count; i+=2 ) { [str appendFormat:@"%f,%f ",params[i],params[i+1]]; } - if( closePath ) + if( closePath ) { [str appendString:@"z"]; + } [self _parsePathCommandData:str intoPath:path]; free(params); @@ -1213,86 +1264,33 @@ - (void)_parseRect:(NSXMLElement *)element intoPath:(IJSVGPath *)path { - CGFloat aX, aY, aWidth, aHeight; - if([self namespacedAttribute:(NSString *)IJSVGAttributeX - element:element] != nil) { - - // already namespaced, find them - aX = [[self namespacedAttribute:(NSString *)IJSVGAttributeX - element:element] floatValue]; - aY = [[self namespacedAttribute:(NSString *)IJSVGAttributeY - element:element] floatValue]; - aWidth = [IJSVGUtils floatValue:[self namespacedAttribute:(NSString *)IJSVGAttributeWidth - element:element] - fallBackForPercent:self.viewBox.size.width]; - aHeight = [IJSVGUtils floatValue:[self namespacedAttribute:(NSString *)IJSVGAttributeHeight - element:element] - fallBackForPercent:self.viewBox.size.height]; - } else { - - // reassign X - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeX - value:[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] - element:element]; - aX = [[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] floatValue]; - - // reassign Y - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeY - value:[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] - element:element]; - aY = [[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] floatValue]; - - // reassign width - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeWidth - value:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] - element:element]; - aWidth = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] - fallBackForPercent:self.viewBox.size.width]; - - // reassign height - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeHeight - value:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] - element:element]; - aHeight = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] - fallBackForPercent:self.viewBox.size.height]; - - // set the namespaced versions as we need to remove the attributes - [element removeAttributeForName:(NSString *)IJSVGAttributeX]; - [element removeAttributeForName:(NSString *)IJSVGAttributeY]; - [element removeAttributeForName:(NSString *)IJSVGAttributeWidth]; - [element removeAttributeForName:(NSString *)IJSVGAttributeHeight]; - } - CGFloat rX = [[[element attributeForName:(NSString *)IJSVGAttributeRX] stringValue] floatValue]; - CGFloat rY = [[[element attributeForName:(NSString *)IJSVGAttributeRY] stringValue] floatValue]; - if( [element attributeForName:(NSString *)IJSVGAttributeRY] == nil ) { + // width and height + CGFloat width = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] + fallBackForPercent:self.viewBox.size.width]; + + + CGFloat height = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] + fallBackForPercent:self.viewBox.size.height]; + + + // rect uses x and y as start of path, not move path object -_- + CGFloat x = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] + fallBackForPercent:self.viewBox.size.width]; + CGFloat y = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] + fallBackForPercent:self.viewBox.size.height]; + + // radius + CGFloat rX = [element attributeForName:(NSString *)IJSVGAttributeRX].stringValue.floatValue; + CGFloat rY = [element attributeForName:(NSString *)IJSVGAttributeRY].stringValue.floatValue; + if([element attributeForName:(NSString *)IJSVGAttributeRY] == nil) { rY = rX; } - [path overwritePath:[NSBezierPath bezierPathWithRoundedRect:NSMakeRect( aX, aY, aWidth, aHeight) - xRadius:rX - yRadius:rY]]; -} - -- (NSString *)namespacedAttribute:(NSString *)key - element:(NSXMLElement *)element -{ - key = [NSString stringWithFormat:@"ij-svg:%@",key]; - if([element attributeForName:key] != nil) { - return [[element attributeForName:key] stringValue]; - } - return nil; -} - -- (void)applyNamespacedAttribute:(NSString *)key - value:(NSString *)value - element:(NSXMLElement *)element -{ - key = [NSString stringWithFormat:@"ij-svg:%@",key]; - NSXMLNode * node = [[[NSXMLNode alloc] initWithKind:NSXMLAttributeKind] autorelease]; - node.name = key; - node.stringValue= value; - [element addAttribute:node]; + NSBezierPath * newPath = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect( x, y, width, height) + xRadius:rX + yRadius:rY]; + [path overwritePath:newPath]; } @end diff --git a/source/IJSVGQuartzRenderer.h b/source/IJSVGQuartzRenderer.h new file mode 100644 index 0000000..d313535 --- /dev/null +++ b/source/IJSVGQuartzRenderer.h @@ -0,0 +1,24 @@ +// +// IJSVGRenderer.h +// IJSVGExample +// +// Created by Curtis Hard on 31/01/2018. +// Copyright © 2018 Curtis Hard. All rights reserved. +// + +#import + +@class IJSVGLayer; + +@interface IJSVGQuartzRenderer : NSObject { + +} + +@property (nonatomic, assign) CGFloat scale; +@property (nonatomic, assign) CGFloat backingScale; +@property (nonatomic, assign) CGRect viewPort; + +- (void)renderLayer:(IJSVGLayer *)layer + inContext:(CGContextRef)context; + +@end diff --git a/source/IJSVGQuartzRenderer.m b/source/IJSVGQuartzRenderer.m new file mode 100644 index 0000000..eb6052c --- /dev/null +++ b/source/IJSVGQuartzRenderer.m @@ -0,0 +1,247 @@ +// +// IJSVGRenderer.m +// IJSVGExample +// +// Created by Curtis Hard on 31/01/2018. +// Copyright © 2018 Curtis Hard. All rights reserved. +// + +#import "IJSVGQuartzRenderer.h" +#import "IJSVG.h" +#import "IJSVGLayerTree.h" +#import "IJSVGGradientLayer.h" +#import "IJSVGStrokeLayer.h" + +@implementation IJSVGQuartzRenderer + +@synthesize scale, viewPort, backingScale; + + +- (void)renderLayer:(IJSVGLayer *)layer + inContext:(CGContextRef)ctx +{ + // if layer is hidden, no need to continue + // trying to render it, its useless! + if(layer.hidden == YES) { + return; + } + + // check which layers can actually be rendered + if(layer.class == IJSVGStrokeLayer.class || + layer.class == IJSVGGradientLayer.class) { + return; + } + + CGContextSaveGState(ctx); + + // apply any transfroms so children also transform too + CGContextConcatCTM(ctx, layer.affineTransform); + CGContextSetAlpha(ctx, layer.opacity); + + // is there a mask?! + if(layer.mask != nil) { + IJSVGLayer * maskLayer = (IJSVGLayer *)layer.mask; + CGRect rect; + CGImageRef maskImage = [self newMaskedImageForLayer:maskLayer + proposedRect:&rect]; + CGRect maskRect = CGRectMake(0, 0, rect.size.width, rect.size.height); + CGContextClipToMask(ctx, maskRect, maskImage); + CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y); + CGImageRelease(maskImage); + } + + // render itself + [self _renderLayer:layer + inContext:ctx]; + + // render the children recursively + for(IJSVGLayer * sublayer in layer.sublayers) { + [self renderLayer:sublayer + inContext:ctx]; + } + CGContextRestoreGState(ctx); +} + +- (void)_renderLayer:(IJSVGLayer *)layer + inContext:(CGContextRef)ctx +{ + // is a shape layer + if(layer.class == IJSVGShapeLayer.class) { + // move the path + CGContextTranslateCTM(ctx, layer.frame.origin.x, + layer.frame.origin.y); + + // find the shape + IJSVGShapeLayer * shape = (IJSVGShapeLayer *)layer; + CGPathRef path = shape.path; + + // has just a plain fill color + CGContextSaveGState(ctx); { + + CGContextAddPath(ctx, path); + if([shape.fillRule isEqualToString:kCAFillRuleEvenOdd]) { + CGContextEOClip(ctx); + } else { + CGContextClip(ctx); + } + + CGContextAddPath(ctx, path); + if(shape.gradientFillLayer == nil) { + if(shape.fillColor != nil) { + CGContextSetFillColorWithColor(ctx, shape.fillColor); + CGContextFillPath(ctx); + } + // has a gradient fill + } else if(shape.gradientFillLayer != nil) { + CGContextSaveGState(ctx); { + CGContextSetAlpha(ctx, shape.gradientFillLayer.opacity); + [shape.gradientFillLayer drawInContext:ctx]; + } CGContextRestoreGState(ctx); + } + } CGContextRestoreGState(ctx); + + // stroke must be done outside of the fill due to + // clipping path will cause it to render incorrectly + CGContextSaveGState(ctx); { + + // any stroke? + if(shape.strokeLayer != nil) { + IJSVGStrokeLayer * strokeLayer = (IJSVGStrokeLayer *)shape.strokeLayer; + if(strokeLayer.strokeColor != nil) { + CGContextSaveGState(ctx); { + + // set opacity + CGContextSetAlpha(ctx, strokeLayer.opacity); + + CGContextSetLineCap(ctx, [self.class lineCapFromLayer:strokeLayer]); + CGContextSetLineJoin(ctx, [self.class lineJoinFromLayer:strokeLayer]); + + // are there any line dashes? + NSArray * dash = strokeLayer.lineDashPattern; + CGFloat * lengths = (CGFloat *)malloc(sizeof(CGFloat)*dash.count); + NSInteger i = 0; + for(NSNumber * number in dash) { + lengths[i++] = number.floatValue; + } + CGContextSetLineDash(ctx, strokeLayer.lineDashPhase, + lengths, dash.count); + free(lengths); + + // get bounding box of the current path + CGContextAddPath(ctx, strokeLayer.path); + CGContextSetLineWidth(ctx, strokeLayer.lineWidth); + CGContextSetStrokeColorWithColor(ctx, strokeLayer.strokeColor); + CGContextStrokePath(ctx); + + } CGContextRestoreGState(ctx); + } + } else { + CGContextSetLineWidth(ctx, 0.f); + CGContextStrokePath(ctx); + } + + } CGContextRestoreGState(ctx); + + } +} + ++ (CGLineJoin)lineJoinFromLayer:(IJSVGShapeLayer *)layer +{ + if([layer.lineJoin isEqualToString:kCALineJoinBevel]) { + return kCGLineJoinBevel; + } else if([layer.lineJoin isEqualToString:kCALineJoinMiter]) { + return kCGLineJoinMiter; + } + return kCGLineJoinRound; +} + ++ (CGLineCap)lineCapFromLayer:(IJSVGShapeLayer *)layer +{ + if([layer.lineCap isEqualToString:kCALineCapButt]) { + return kCGLineCapButt; + } else if([layer.lineCap isEqualToString:kCALineCapRound]) { + return kCGLineCapRound; + } + return kCGLineCapSquare; +} + ++ (CGRect)findFrameForLayer:(IJSVGLayer *)layer +{ + CGRect rect = layer.frame; + return [self _recursivelyFindFrameForLayer:layer + rect:rect]; +} + ++ (CGRect)_recursivelyFindFrameForLayer:(IJSVGLayer *)layer + rect:(CGRect)rect +{ + CGRect frame = layer.frame; + if(CGRectGetMinX(frame) < CGRectGetMinX(rect)) { + rect.origin.x = CGRectGetMinX(frame); + } + if(CGRectGetMinY(frame) < CGRectGetMinY(rect)) { + rect.origin.y = CGRectGetMinY(frame); + } + if(CGRectGetMaxX(frame) > CGRectGetMaxX(rect)) { + rect.size.width = CGRectGetMaxX(frame); + } + if(CGRectGetMaxY(frame) > CGRectGetMaxY(rect)) { + rect.size.height = CGRectGetMaxY(frame); + } + for(IJSVGLayer * sublayer in layer.sublayers) { + rect = [self _recursivelyFindFrameForLayer:sublayer + rect:rect]; + } + return rect; +} + +- (CGRect)convertLayerFrame:(CGRect)rect +{ + rect.size.width = fabs(rect.origin.x - rect.size.width); + rect.size.height = fabs(rect.origin.y - rect.size.height); + rect.origin.x = rect.origin.y = 0.f; + return rect; +} + +- (CGImageRef)newImageForLayer:(IJSVGLayer *)layer + colorSpace:(CGColorSpaceRef)colorSpace + proposedRect:(CGRect *)proposedRect +{ + // create color space and new context + NSRect cRect = [self.class findFrameForLayer:layer]; + NSRect convertedSize = [self convertLayerFrame:cRect]; + CGSize size = cRect.size; + *proposedRect = cRect; + CGFloat actualScale = self.scale * self.backingScale; + CGContextRef ctx = CGBitmapContextCreate(NULL, size.width * actualScale, + size.height * actualScale, + 8, 0, colorSpace, + kCGImageAlphaPremultipliedLast); + + CGContextScaleCTM(ctx, actualScale, actualScale); + + // render the layer tree into this context + [self renderLayer:layer + inContext:ctx]; + + // grab image from it + CGImageRef image = CGBitmapContextCreateImage(ctx); + + // clean memory + CGContextRelease(ctx); + return image; +} + +- (CGImageRef)newMaskedImageForLayer:(IJSVGLayer *)layer + proposedRect:(CGRect *)proposedRect +{ + // create color space and new context + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + CGImageRef image = [self newImageForLayer:layer + colorSpace:colorSpace + proposedRect:proposedRect]; + CGColorSpaceRelease(colorSpace); + return image; +} + +@end diff --git a/source/IJSVGRadialGradient.h b/source/IJSVGRadialGradient.h index 0c1d64c..7885bec 100644 --- a/source/IJSVGRadialGradient.h +++ b/source/IJSVGRadialGradient.h @@ -18,8 +18,6 @@ @property ( nonatomic, retain ) IJSVGUnitLength * radius; + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGRadialGradient *)gradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint; + gradient:(IJSVGRadialGradient *)gradient; @end diff --git a/source/IJSVGRadialGradient.m b/source/IJSVGRadialGradient.m index 872c909..e65f574 100644 --- a/source/IJSVGRadialGradient.m +++ b/source/IJSVGRadialGradient.m @@ -34,48 +34,55 @@ grad.cx = self.cx; grad.cy = self.cy; grad.radius = self.radius; - grad.startPoint = self.startPoint; - grad.endPoint = self.endPoint; return grad; } + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGRadialGradient *)gradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint { // cx defaults to 50% if not specified NSDictionary * kv = @{@"cx":@"cx", @"cy":@"cy", - @"r":@"radius", - @"fx":@"fx"}; + @"r":@"radius"}; for(NSString * key in kv.allKeys) { NSString * str = [element attributeForName:key].stringValue; IJSVGUnitLength * unit = nil; if(str != nil) { - unit = [IJSVGUnitLength unitWithPercentageString:str]; + unit = [IJSVGUnitLength unitWithString:str]; } else { - unit = [IJSVGUnitLength unitWithPercentageFloat:50]; + unit = [IJSVGUnitLength unitWithPercentageFloat:.5f]; } [gradient setValue:unit forKey:kv[key]]; } - // fy defaults to cy if not specified - NSString * fy = [element attributeForName:@"fy"].stringValue; - if(fy != nil) { - gradient.fy = [IJSVGUnitLength unitWithPercentageString:fy]; - } else { - gradient.fy = gradient.cy; + gradient.fx = gradient.cx; + gradient.fy = gradient.cy; + + // needs fixing + NSString * fx = [element attributeForName:@"fx"].stringValue; + if(fx != nil) { + if(fx.floatValue < 1.f) { + gradient.fx = [IJSVGUnitLength unitWithPercentageString:fx]; + } else { + gradient.fx = [IJSVGUnitLength unitWithString:fx]; + } } - if( gradient.gradient != nil ) + NSString * fy = [element attributeForName:@"fy"].stringValue; + if(fx != nil) { + if(fx.floatValue < 1.f) { + gradient.fy = [IJSVGUnitLength unitWithPercentageString:fy]; + } else { + gradient.fy = [IJSVGUnitLength unitWithString:fy]; + } + } + + if( gradient.gradient != nil ) { return nil; - - *startPoint = CGPointMake(gradient.cx.valueAsPercentage, gradient.cy.valueAsPercentage); - *endPoint = CGPointMake(gradient.fx.valueAsPercentage, gradient.fy.valueAsPercentage); + } NSArray * colors = nil; CGFloat * colorStops = [[self class] computeColorStopsFromString:element colors:&colors]; @@ -86,71 +93,79 @@ return ret; } -- (CGFloat)_handleTransform:(IJSVGTransform *)transform - bounds:(CGRect)bounds - index:(NSInteger)index - value:(CGFloat)value +- (void)drawInContextRef:(CGContextRef)ctx + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox { - // rotate transform, assume its based on percentages - // if lower then 0 is specified for 1 or 2 - CGFloat max = bounds.size.width>bounds.size.height?bounds.size.width:bounds.size.height; - if( transform.command == IJSVGTransformCommandRotate ) { - switch(index) { - case 1: - case 2: { - if(value<1.f) { - return (max*value); + BOOL inUserSpace = self.units == IJSVGUnitUserSpaceOnUse; + CGFloat radius = self.radius.value; + CGPoint startPoint = CGPointZero; + CGPoint gradientStartPoint = CGPointZero; + CGPoint gradientEndPoint = CGPointZero; + + // transforms + CGAffineTransform selfTransform = IJSVGConcatTransforms(self.transforms); + + CGContextSaveGState(ctx); + { +#pragma mark User Space On Use + if(inUserSpace == YES) { + CGFloat rad = radius*2.f; + startPoint = CGPointMake(self.cx.value, self.cy.value); + + // work out the new radius + CGRect rect = CGRectMake(startPoint.x, startPoint.y, rad, rad); + rect = CGRectApplyAffineTransform(rect, selfTransform); + rect = CGRectApplyAffineTransform(rect, absoluteTransform); + radius = CGRectGetHeight(rect)/2.f; + + gradientStartPoint = startPoint; + gradientEndPoint = CGPointMake(self.fx.value, self.fy.value); + + // apply the absolute position + CGContextConcatCTM(ctx, absoluteTransform); + } else { +#pragma mark Object Bounding Box + // compute size based on percentages + CGFloat x = [self.cx computeValue:CGRectGetWidth(objectRect)]; + CGFloat y = [self.cy computeValue:CGRectGetHeight(objectRect)]; + startPoint = CGPointMake(x, y); + CGFloat val = MIN(CGRectGetWidth(objectRect), CGRectGetHeight(objectRect)); + radius = [self.radius computeValue:val]; + + CGFloat ex = [self.fx computeValue:CGRectGetWidth(objectRect)]; + CGFloat ey = [self.fy computeValue:CGRectGetHeight(objectRect)]; + + gradientEndPoint = CGPointMake(ex, ey); + gradientStartPoint = startPoint; + + // transform if width or height is not equal + if(CGRectGetWidth(objectRect) != CGRectGetHeight(objectRect)) { + CGAffineTransform tr = CGAffineTransformMakeTranslation(gradientStartPoint.x, + gradientStartPoint.y); + if(CGRectGetWidth(objectRect) > CGRectGetHeight(objectRect)) { + tr = CGAffineTransformScale(tr, CGRectGetWidth(objectRect)/CGRectGetHeight(objectRect), 1); + } else { + tr = CGAffineTransformScale(tr, 1.f, CGRectGetHeight(objectRect)/CGRectGetWidth(objectRect)); } - break; + tr = CGAffineTransformTranslate(tr, -gradientStartPoint.x, -gradientStartPoint.y); + selfTransform = CGAffineTransformConcat(tr, selfTransform); } } - } - return value; -} - -- (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect -{ - CGRect bounds = rect; - for( IJSVGTransform * transform in self.transforms ) { - IJSVGTransformParameterModifier modifier = ^CGFloat(NSInteger index, CGFloat value) { - return [self _handleTransform:transform - bounds:bounds - index:index - value:value]; - }; - CGContextConcatCTM(ctx, [transform CGAffineTransformWithModifier:modifier]); - } +#pragma mark Default drawing + // transform the context + CGContextConcatCTM(ctx, selfTransform); - CGPoint sp = self.startPoint; - CGPoint ep = self.endPoint; - - if( self.startPoint.x == .5f ) { - sp.x = bounds.size.width*self.startPoint.x; - } - - if(self.startPoint.y == .5f) { - sp.y = bounds.size.height*self.startPoint.y; - } - - if(self.endPoint.x == .5f) { - ep.x = bounds.size.width*self.endPoint.x; - } - - if(self.endPoint.y == .5f) { - ep.y = bounds.size.height*self.endPoint.y; - } - - CGFloat r = self.radius.value; - if(r == .5f) { - r = (sp.x>sp.y?sp.x:sp.y); - } - - // actually perform the draw - CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; - CGGradientRef grad = self.CGGradient; - CGContextDrawRadialGradient(ctx, grad, sp, 0.f, ep, r, options); + // draw the gradient + CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation| + kCGGradientDrawsAfterEndLocation; + CGContextDrawRadialGradient(ctx, self.CGGradient, + gradientEndPoint, 0, gradientStartPoint, + radius, options); + }; + CGContextRestoreGState(ctx); } @end diff --git a/source/IJSVGShapeLayer.h b/source/IJSVGShapeLayer.h index 1671899..19207db 100644 --- a/source/IJSVGShapeLayer.h +++ b/source/IJSVGShapeLayer.h @@ -25,6 +25,7 @@ @property (nonatomic, assign) CGFloat backingScaleFactor; @property (nonatomic, assign) CGBlendMode blendingMode; @property (nonatomic, assign) CGPoint absoluteOrigin; +@property (nonatomic, assign) CGPoint originalPathOrigin; @property (nonatomic, assign) BOOL convertMasksToPaths; - (void)applySublayerMaskToContext:(CGContextRef)context diff --git a/source/IJSVGShapeLayer.m b/source/IJSVGShapeLayer.m index 0de6555..6ef05d0 100644 --- a/source/IJSVGShapeLayer.m +++ b/source/IJSVGShapeLayer.m @@ -20,6 +20,7 @@ @synthesize backingScaleFactor; @synthesize blendingMode; @synthesize convertMasksToPaths; +@synthesize originalPathOrigin; - (void)dealloc { diff --git a/source/IJSVGTransform.h b/source/IJSVGTransform.h index 36af4b4..12d9ef1 100644 --- a/source/IJSVGTransform.h +++ b/source/IJSVGTransform.h @@ -9,7 +9,10 @@ #import #import "IJSVGUtils.h" +@class IJSVGTransform; + typedef CGFloat (^IJSVGTransformParameterModifier)(NSInteger index, CGFloat value); +typedef void (^IJSVGTransformApplyBlock)(IJSVGTransform * transform); typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { IJSVGTransformCommandMatrix, @@ -35,12 +38,21 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { @property ( nonatomic, assign ) NSInteger parameterCount; @property ( nonatomic, assign ) NSInteger sort; +NSString * IJSVGDebugAffineTransform(CGAffineTransform transform); +NSString * IJSVGDebugTransforms(NSArray * transforms); +void IJSVGApplyTransform(NSArray * transforms, IJSVGTransformApplyBlock block); +CGAffineTransform IJSVGConcatTransforms(NSArray * transforms); + ++ (NSArray *)transformsFromAffineTransform:(CGAffineTransform)affineTransform; + (NSArray *)transformsForString:(NSString *)string; + (NSBezierPath *)transformedPath:(IJSVGPath *)path; + (NSArray *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform; ++ (NSString *)affineTransformToSVGMatrixString:(CGAffineTransform)affineTransform; - (CGAffineTransform)CGAffineTransform; - (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier; - (CGAffineTransform)stackIdentity:(CGAffineTransform)identity; - (void)recalculateWithBounds:(CGRect)bounds; ++ (IJSVGTransform *)transformByTranslatingX:(CGFloat)x + y:(CGFloat)y; @end diff --git a/source/IJSVGTransform.m b/source/IJSVGTransform.m index 1ec65fb..9eaf9dd 100644 --- a/source/IJSVGTransform.m +++ b/source/IJSVGTransform.m @@ -33,6 +33,51 @@ return trans; } +NSString * IJSVGDebugAffineTransform(CGAffineTransform transform) +{ + NSMutableArray * strings = [[[NSMutableArray alloc] init] autorelease]; + [strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform]]; + return [strings componentsJoinedByString:@" "]; +} + +NSString * IJSVGDebugTransforms(NSArray * transforms) +{ + NSMutableArray * strings = [[[NSMutableArray alloc] init] autorelease]; + IJSVGApplyTransform(transforms, ^(IJSVGTransform *transform) { + [strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform.CGAffineTransform]]; + }); + return [strings componentsJoinedByString:@" "]; +} + +CGAffineTransform IJSVGConcatTransforms(NSArray * transforms) +{ + __block CGAffineTransform trans = CGAffineTransformIdentity; + IJSVGApplyTransform(transforms, ^(IJSVGTransform *transform) { + trans = CGAffineTransformConcat(trans, transform.CGAffineTransform); + }); + return trans; +} + +void IJSVGApplyTransform(NSArray * transforms, IJSVGTransformApplyBlock block) +{ + for(IJSVGTransform * transform in transforms) { + block(transform); + } +}; + ++ (IJSVGTransform *)transformByTranslatingX:(CGFloat)x + y:(CGFloat)y +{ + IJSVGTransform * transform = [[[self alloc] init] autorelease]; + transform.command = IJSVGTransformCommandTranslate; + transform.parameterCount = 2; + CGFloat * params = (CGFloat *)malloc(sizeof(CGFloat)*2); + params[0] = x; + params[1] = y; + transform.parameters = params; + return transform; +} + - (void)recalculateWithBounds:(CGRect)bounds { CGFloat max = bounds.size.width>bounds.size.height?bounds.size.width:bounds.size.height; @@ -101,8 +146,9 @@ { NSString * command = [string substringWithRange:[result rangeAtIndex:1]]; IJSVGTransformCommand commandType = [[self class] commandForCommandString:command]; - if( commandType == IJSVGTransformCommandNotImplemented ) + if( commandType == IJSVGTransformCommandNotImplemented ) { return; + } // create the transform NSString * params = [string substringWithRange:[result rangeAtIndex:2]]; @@ -400,6 +446,19 @@ return CGAffineTransformIdentity; } ++ (NSArray *)transformsFromAffineTransform:(CGAffineTransform)affineTransform +{ + NSArray * strings = [self affineTransformToSVGTransformAttributeString:affineTransform]; + return [self transformsForString:[strings componentsJoinedByString:@" "]]; +} + ++ (NSString *)affineTransformToSVGMatrixString:(CGAffineTransform)transform +{ + return [NSString stringWithFormat:@"matrix(%g,%g,%g,%g,%g,%g)", + transform.a, transform.b, transform.c, transform.d, + transform.tx, transform.ty]; +} + // this is an Object-C version of the matrixToTransform method from SVGO + (NSArray *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform { @@ -500,5 +559,11 @@ return trans; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ %@",[super description], + [self.class affineTransformToSVGTransformAttributeString:self.CGAffineTransform]]; +} + @end diff --git a/source/IJSVGUtils.h b/source/IJSVGUtils.h index 84b54bb..49d7759 100644 --- a/source/IJSVGUtils.h +++ b/source/IJSVGUtils.h @@ -18,6 +18,12 @@ CGFloat angle( CGPoint a, CGPoint b ); CGFloat radians_to_degrees( CGFloat radians); CGFloat degrees_to_radians( CGFloat degrees ); +BOOL IJSVGIsCommonHTMLElementName(NSString * str); +NSArray * IJSVGCommonHTMLElementNames(); + +NSString * IJSVGShortFloatString(CGFloat f); +NSString * IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision); + BOOL IJSVGIsLegalCommandCharacter(unichar aChar); BOOL IJSVGIsSVGLayer(CALayer * layer); + (IJSVGCommandType)typeForCommandString:(NSString *)string; diff --git a/source/IJSVGUtils.m b/source/IJSVGUtils.m index e018bcf..fc14ad2 100644 --- a/source/IJSVGUtils.m +++ b/source/IJSVGUtils.m @@ -12,6 +12,170 @@ @implementation IJSVGUtils +BOOL IJSVGIsCommonHTMLElementName(NSString * str) +{ + str = str.lowercaseString; + return [IJSVGCommonHTMLElementNames() containsObject:str]; +}; + +NSArray * IJSVGCommonHTMLElementNames() +{ + static NSArray * names = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + names = [@[@"a", + @"abbr", + @"acronym", + @"abbr", + @"address", + @"applet", + @"embed", + @"object", + @"area", + @"article", + @"aside", + @"audio", + @"b", + @"base", + @"basefont", + @"bdi", + @"bdo", + @"big", + @"blockquote", + @"body", + @"br", + @"button", + @"canvas", + @"caption", + @"center", + @"cite", + @"code", + @"col", + @"colgroup", + @"colgroup", + @"datalist", + @"dd", + @"del", + @"details", + @"dfn", + @"dialog", + @"dir", + @"ul", + @"div", + @"dl", + @"dt", + @"em", + @"embed", + @"fieldset", + @"figcaption", + @"figure", + @"figure", + @"font", + @"footer", + @"form", + @"frame", + @"frameset", + @"h1", + @"h6", + @"head", + @"header", + @"hr", + @"html", + @"i", + @"iframe", + @"img", + @"input", + @"ins", + @"kbd", + @"label", + @"input", + @"legend", + @"fieldset", + @"li", + @"link", + @"main", + @"map", + @"mark", + @"menu", + @"menuitem", + @"meta", + @"meter", + @"nav", + @"noframes", + @"noscript", + @"object", + @"ol", + @"optgroup", + @"option", + @"output", + @"p", + @"param", + @"picture", + @"pre", + @"progress", + @"q", + @"rp", + @"rt", + @"ruby", + @"s", + @"samp", + @"script", + @"section", + @"select", + @"small", + @"source", + @"video", + @"audio", + @"span", + @"strike", + @"del", + @"s", + @"strong", + @"style", + @"sub", + @"summary", + @"details", + @"sup", + @"table", + @"tbody", + @"td", + @"template", + @"textarea", + @"tfoot", + @"th", + @"thead", + @"time", + @"title", + @"tr", + @"track", + @"video", + @"audio", + @"tt", + @"u", + @"ul", + @"var", + @"video", + @"wbr"] retain]; + }); + return names; +}; + +NSString * IJSVGShortFloatString(CGFloat f) +{ + return [NSString stringWithFormat:@"%g",f]; +}; + +NSString * IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision) +{ + NSString * format = [NSString stringWithFormat:@"%@.%ld%@",@"%",precision,@"f"]; + NSString * ret = [NSString stringWithFormat:format,f]; + // can it be reduced even more? + if(ret.floatValue == (float)ret.integerValue) { + ret = [NSString stringWithFormat:@"%ld",ret.integerValue]; + } + return ret; +}; + BOOL IJSVGIsLegalCommandCharacter(unichar aChar) { char * validChars = "MmZzLlHhVvCcSsQqTtAa"; @@ -253,7 +417,6 @@ CGFloat degrees_to_radians( CGFloat degrees ) // sizes for the string buffer NSInteger defSize = 50; NSInteger size = defSize; - NSInteger sLength = string.length; // default memory size for the floats NSInteger defFloatSize = 100; @@ -265,6 +428,8 @@ CGFloat degrees_to_radians( CGFloat degrees ) const char * cString = [string cStringUsingEncoding:NSUTF8StringEncoding]; const char * validChars = "0123456789eE+-."; + NSInteger sLength = strlen(cString); + // buffer for the returned floats CGFloat * floats = (CGFloat *)malloc(sizeof(CGFloat)*defFloatSize);