diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj b/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj index 1f47d42..907477b 100644 --- a/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj +++ b/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj @@ -7,8 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 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 */; }; 59265CEB1C4F843E00F333F0 /* SVGExampleView6.m in Sources */ = {isa = PBXBuildFile; fileRef = 59265CEA1C4F843E00F333F0 /* SVGExampleView6.m */; }; + 592840B11E153F3D002F9AF0 /* IJSVGLayerTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 592840B01E153F3D002F9AF0 /* IJSVGLayerTree.m */; }; + 592840B41E155308002F9AF0 /* IJSVGGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 592840B31E155308002F9AF0 /* IJSVGGradientLayer.m */; }; 5941DAFD1CF9BC9B00B3A911 /* IJSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 5941DAFA1CF9BC9B00B3A911 /* IJSVGImage.m */; }; 5941DAFE1CF9BC9B00B3A911 /* IJSVGPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 5941DAFC1CF9BC9B00B3A911 /* IJSVGPattern.m */; }; 59459CEC19B906FE00CE493B /* clipped.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59459CEB19B906FE00CE493B /* clipped.svg */; }; @@ -23,9 +27,11 @@ 595665DC19B6302600D805FF /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 595665C619B6302600D805FF /* README.md */; }; 595665DE19B6309C00D805FF /* test.svg in Resources */ = {isa = PBXBuildFile; fileRef = 595665DD19B6309C00D805FF /* test.svg */; }; 595665E119B630B600D805FF /* SVGView.m in Sources */ = {isa = PBXBuildFile; fileRef = 595665E019B630B600D805FF /* SVGView.m */; }; + 597046A21E24352700A60138 /* IJSVGStrokeLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 597046A11E24352700A60138 /* IJSVGStrokeLayer.m */; }; 5986308719BA104800CF15EA /* linecap.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5986308619BA104800CF15EA /* linecap.svg */; }; 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 */; }; 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 */; }; @@ -33,9 +39,10 @@ 59A11E6C19B89CEA00E44498 /* SVGExampleView1.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A11E6B19B89CEA00E44498 /* SVGExampleView1.m */; }; 59A11E6F19B89D2000E44498 /* SVGExampleView2.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A11E6E19B89D2000E44498 /* SVGExampleView2.m */; }; 59A11E7219B89D6600E44498 /* SVGExampleView3.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A11E7119B89D6600E44498 /* SVGExampleView3.m */; }; - 59B80A231CF8C2E20024923B /* IJSVGUnits.m in Sources */ = {isa = PBXBuildFile; fileRef = 59B80A221CF8C2E20024923B /* IJSVGUnits.m */; }; + 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 */; }; + 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 */; }; 59E2645319BA240D008A6FDB /* IJSVGCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2642019BA240D008A6FDB /* IJSVGCache.m */; }; @@ -63,6 +70,12 @@ 59E2646919BA240D008A6FDB /* IJSVGStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2644C19BA240D008A6FDB /* IJSVGStyle.m */; }; 59E2646A19BA240D008A6FDB /* IJSVGTransform.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2644E19BA240D008A6FDB /* IJSVGTransform.m */; }; 59E2646B19BA240D008A6FDB /* IJSVGUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2645019BA240D008A6FDB /* IJSVGUtils.m */; }; + 59E8ABEA1E2102A30032A80C /* IJSVGPatternLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E8ABE91E2102A30032A80C /* IJSVGPatternLayer.m */; }; + 59E8ABED1E211EBE0032A80C /* IJSVGLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E8ABEC1E211EBE0032A80C /* IJSVGLayer.m */; }; + 59E8ABF01E211EDB0032A80C /* IJSVGShapeLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E8ABEF1E211EDB0032A80C /* IJSVGShapeLayer.m */; }; + 59E8ABF31E2167D90032A80C /* IJSVGImageLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E8ABF21E2167D90032A80C /* IJSVGImageLayer.m */; }; + 59E8ABF61E2176340032A80C /* IJSVGGroupLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E8ABF51E2176340032A80C /* IJSVGGroupLayer.m */; }; + 59E8ABF91E219C860032A80C /* IJSVGMath.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E8ABF81E219C860032A80C /* IJSVGMath.m */; }; 59F799E219B880CE00096CB7 /* htc_one.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59F799E119B880CE00096CB7 /* htc_one.svg */; }; /* End PBXBuildFile section */ @@ -77,9 +90,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 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 = ""; }; + 591A13E01E19838F001D1629 /* IJSVGText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGText.m; sourceTree = ""; }; 59265CE71C4F840400F333F0 /* css.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = css.svg; sourceTree = ""; }; 59265CE91C4F843E00F333F0 /* SVGExampleView6.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVGExampleView6.h; sourceTree = ""; }; 59265CEA1C4F843E00F333F0 /* SVGExampleView6.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVGExampleView6.m; sourceTree = ""; }; + 592840AF1E153F3D002F9AF0 /* IJSVGLayerTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGLayerTree.h; sourceTree = ""; }; + 592840B01E153F3D002F9AF0 /* IJSVGLayerTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGLayerTree.m; sourceTree = ""; }; + 592840B21E155308002F9AF0 /* IJSVGGradientLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGGradientLayer.h; sourceTree = ""; }; + 592840B31E155308002F9AF0 /* IJSVGGradientLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGGradientLayer.m; sourceTree = ""; }; 5941DAF91CF9BC9B00B3A911 /* IJSVGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGImage.h; sourceTree = ""; }; 5941DAFA1CF9BC9B00B3A911 /* IJSVGImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGImage.m; sourceTree = ""; }; 5941DAFB1CF9BC9B00B3A911 /* IJSVGPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGPattern.h; sourceTree = ""; }; @@ -107,10 +128,14 @@ 595665DD19B6309C00D805FF /* test.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = test.svg; sourceTree = ""; }; 595665DF19B630B600D805FF /* SVGView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVGView.h; sourceTree = ""; }; 595665E019B630B600D805FF /* SVGView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVGView.m; sourceTree = ""; }; + 597046A01E24352700A60138 /* IJSVGStrokeLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGStrokeLayer.h; sourceTree = ""; }; + 597046A11E24352700A60138 /* IJSVGStrokeLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGStrokeLayer.m; sourceTree = ""; }; 5986308619BA104800CF15EA /* linecap.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = linecap.svg; sourceTree = ""; }; 5986308819BA106D00CF15EA /* SVGExampleView5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVGExampleView5.h; sourceTree = ""; }; 5986308919BA106D00CF15EA /* SVGExampleView5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVGExampleView5.m; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -125,10 +150,12 @@ 59A11E6E19B89D2000E44498 /* SVGExampleView2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVGExampleView2.m; sourceTree = ""; }; 59A11E7019B89D6600E44498 /* SVGExampleView3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVGExampleView3.h; sourceTree = ""; }; 59A11E7119B89D6600E44498 /* SVGExampleView3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVGExampleView3.m; sourceTree = ""; }; - 59B80A211CF8C2E20024923B /* IJSVGUnits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGUnits.h; sourceTree = ""; }; - 59B80A221CF8C2E20024923B /* IJSVGUnits.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnits.m; sourceTree = ""; }; + 59A3DA6B1E283022003E59A9 /* IJSVGTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGTransaction.h; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; 59E2641C19BA240D008A6FDB /* IJSVG.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVG.m; sourceTree = ""; }; 59E2641D19BA240D008A6FDB /* IJSVGBezierPathAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGBezierPathAdditions.h; sourceTree = ""; }; @@ -183,6 +210,18 @@ 59E2644E19BA240D008A6FDB /* IJSVGTransform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGTransform.m; sourceTree = ""; }; 59E2644F19BA240D008A6FDB /* IJSVGUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGUtils.h; sourceTree = ""; }; 59E2645019BA240D008A6FDB /* IJSVGUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGUtils.m; sourceTree = ""; }; + 59E8ABE81E2102A30032A80C /* IJSVGPatternLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGPatternLayer.h; sourceTree = ""; }; + 59E8ABE91E2102A30032A80C /* IJSVGPatternLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGPatternLayer.m; sourceTree = ""; }; + 59E8ABEB1E211EBE0032A80C /* IJSVGLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGLayer.h; sourceTree = ""; }; + 59E8ABEC1E211EBE0032A80C /* IJSVGLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGLayer.m; sourceTree = ""; }; + 59E8ABEE1E211EDB0032A80C /* IJSVGShapeLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGShapeLayer.h; sourceTree = ""; }; + 59E8ABEF1E211EDB0032A80C /* IJSVGShapeLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGShapeLayer.m; sourceTree = ""; }; + 59E8ABF11E2167D90032A80C /* IJSVGImageLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGImageLayer.h; sourceTree = ""; }; + 59E8ABF21E2167D90032A80C /* IJSVGImageLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGImageLayer.m; sourceTree = ""; }; + 59E8ABF41E2176340032A80C /* IJSVGGroupLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGGroupLayer.h; sourceTree = ""; }; + 59E8ABF51E2176340032A80C /* IJSVGGroupLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGGroupLayer.m; sourceTree = ""; }; + 59E8ABF71E219C860032A80C /* IJSVGMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGMath.h; sourceTree = ""; }; + 59E8ABF81E219C860032A80C /* IJSVGMath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGMath.m; sourceTree = ""; }; 59F799E119B880CE00096CB7 /* htc_one.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = htc_one.svg; sourceTree = ""; }; /* End PBXFileReference section */ @@ -296,12 +335,14 @@ 59E2641A19BA240D008A6FDB /* source */ = { isa = PBXGroup; children = ( + 59A3DA6B1E283022003E59A9 /* IJSVGTransaction.h */, + 59A3DA6C1E283022003E59A9 /* IJSVGTransaction.m */, + 598759F41E242C850024CC3F /* IJSVGExporterPathInstruction.h */, + 598759F51E242C850024CC3F /* IJSVGExporterPathInstruction.m */, 5941DAF91CF9BC9B00B3A911 /* IJSVGImage.h */, 5941DAFA1CF9BC9B00B3A911 /* IJSVGImage.m */, 5941DAFB1CF9BC9B00B3A911 /* IJSVGPattern.h */, 5941DAFC1CF9BC9B00B3A911 /* IJSVGPattern.m */, - 59B80A211CF8C2E20024923B /* IJSVGUnits.h */, - 59B80A221CF8C2E20024923B /* IJSVGUnits.m */, 599465D41C4AA87200A2EEF3 /* IJSVGStyleSheet.h */, 599465D51C4AA87200A2EEF3 /* IJSVGStyleSheet.m */, 599465D61C4AA87200A2EEF3 /* IJSVGStyleSheetRule.h */, @@ -369,6 +410,30 @@ 59E2644E19BA240D008A6FDB /* IJSVGTransform.m */, 59E2644F19BA240D008A6FDB /* IJSVGUtils.h */, 59E2645019BA240D008A6FDB /* IJSVGUtils.m */, + 592840AF1E153F3D002F9AF0 /* IJSVGLayerTree.h */, + 592840B01E153F3D002F9AF0 /* IJSVGLayerTree.m */, + 592840B21E155308002F9AF0 /* IJSVGGradientLayer.h */, + 592840B31E155308002F9AF0 /* IJSVGGradientLayer.m */, + 591A13DF1E19838F001D1629 /* IJSVGText.h */, + 591A13E01E19838F001D1629 /* IJSVGText.m */, + 591704CA1E2016E400012644 /* IJSVGExporter.h */, + 591704CB1E2016E400012644 /* IJSVGExporter.m */, + 59E8ABE81E2102A30032A80C /* IJSVGPatternLayer.h */, + 59E8ABE91E2102A30032A80C /* IJSVGPatternLayer.m */, + 59E8ABEB1E211EBE0032A80C /* IJSVGLayer.h */, + 59E8ABEC1E211EBE0032A80C /* IJSVGLayer.m */, + 59E8ABEE1E211EDB0032A80C /* IJSVGShapeLayer.h */, + 59E8ABEF1E211EDB0032A80C /* IJSVGShapeLayer.m */, + 59E8ABF11E2167D90032A80C /* IJSVGImageLayer.h */, + 59E8ABF21E2167D90032A80C /* IJSVGImageLayer.m */, + 59E8ABF41E2176340032A80C /* IJSVGGroupLayer.h */, + 59E8ABF51E2176340032A80C /* IJSVGGroupLayer.m */, + 59E8ABF71E219C860032A80C /* IJSVGMath.h */, + 59E8ABF81E219C860032A80C /* IJSVGMath.m */, + 597046A01E24352700A60138 /* IJSVGStrokeLayer.h */, + 597046A11E24352700A60138 /* IJSVGStrokeLayer.m */, + 59E0F5EB1E29964700F757F7 /* IJSVGUnitLength.h */, + 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */, ); name = source; path = ../../source; @@ -491,42 +556,55 @@ 599465DD1C4AA87200A2EEF3 /* IJSVGStyleSheetRule.m in Sources */, 5986308A19BA106D00CF15EA /* SVGExampleView5.m in Sources */, 59E2645619BA240D008A6FDB /* IJSVGCommandArc.m in Sources */, - 59B80A231CF8C2E20024923B /* IJSVGUnits.m in Sources */, + 59E8ABF91E219C860032A80C /* IJSVGMath.m in Sources */, 59265CEB1C4F843E00F333F0 /* SVGExampleView6.m in Sources */, 5941DAFE1CF9BC9B00B3A911 /* IJSVGPattern.m in Sources */, + 59E8ABF01E211EDB0032A80C /* IJSVGShapeLayer.m in Sources */, 599465DC1C4AA87200A2EEF3 /* IJSVGStyleSheet.m in Sources */, 59E2645319BA240D008A6FDB /* IJSVGCache.m in Sources */, 599465DF1C4AA87200A2EEF3 /* IJSVGStyleSheetSelectorRaw.m in Sources */, + 59E8ABEA1E2102A30032A80C /* IJSVGPatternLayer.m in Sources */, 59E2646619BA240D008A6FDB /* IJSVGParser.m in Sources */, 595665E119B630B600D805FF /* SVGView.m in Sources */, 59E2645719BA240D008A6FDB /* IJSVGCommandClose.m in Sources */, 59E2645A19BA240D008A6FDB /* IJSVGCommandHorizontalLine.m in Sources */, 59E2645B19BA240D008A6FDB /* IJSVGCommandLineTo.m in Sources */, 59E2645F19BA240D008A6FDB /* IJSVGCommandVerticalLine.m in Sources */, + 59E8ABF61E2176340032A80C /* IJSVGGroupLayer.m in Sources */, 59E2646819BA240D008A6FDB /* IJSVGRadialGradient.m in Sources */, + 59E8ABF31E2167D90032A80C /* IJSVGImageLayer.m in Sources */, 59E2645919BA240D008A6FDB /* IJSVGCommandCurve.m in Sources */, 5956658019B62F4600D805FF /* AppDelegate.m in Sources */, 59E2646519BA240D008A6FDB /* IJSVGNode.m in Sources */, + 591A13E11E19838F001D1629 /* IJSVGText.m in Sources */, 59A11E6C19B89CEA00E44498 /* SVGExampleView1.m in Sources */, + 59A3DA6D1E283022003E59A9 /* IJSVGTransaction.m in Sources */, 59E2646A19BA240D008A6FDB /* IJSVGTransform.m in Sources */, 59E2646B19BA240D008A6FDB /* IJSVGUtils.m in Sources */, + 592840B41E155308002F9AF0 /* IJSVGGradientLayer.m in Sources */, 59E2646019BA240D008A6FDB /* IJSVGDef.m in Sources */, 59E2645C19BA240D008A6FDB /* IJSVGCommandMove.m in Sources */, 59E2646219BA240D008A6FDB /* IJSVGGradient.m in Sources */, + 598759F61E242C850024CC3F /* IJSVGExporterPathInstruction.m in Sources */, 59E2645E19BA240D008A6FDB /* IJSVGCommandSmoothCurve.m in Sources */, 59E2645819BA240D008A6FDB /* IJSVGCommandCommandSmoothQuadraticCurve.m in Sources */, 59E2645D19BA240D008A6FDB /* IJSVGCommandQuadraticCurve.m in Sources */, 59E2646719BA240D008A6FDB /* IJSVGPath.m in Sources */, + 59E8ABED1E211EBE0032A80C /* IJSVGLayer.m in Sources */, + 591704CC1E2016E400012644 /* IJSVGExporter.m in Sources */, 59E2645519BA240D008A6FDB /* IJSVGCommand.m in Sources */, 59A11E7219B89D6600E44498 /* SVGExampleView3.m in Sources */, + 59E0F5ED1E29964700F757F7 /* IJSVGUnitLength.m in Sources */, 5956657D19B62F4600D805FF /* main.m in Sources */, 59A11E6F19B89D2000E44498 /* SVGExampleView2.m in Sources */, 5948DED51BB2BFE5004156FF /* IJSVGFontConverter.m in Sources */, 59E2646319BA240D008A6FDB /* IJSVGGroup.m in Sources */, 59459CEF19B9074F00CE493B /* SVGExampleView4.m in Sources */, + 597046A21E24352700A60138 /* IJSVGStrokeLayer.m in Sources */, 59E2646119BA240D008A6FDB /* IJSVGForeignObject.m in Sources */, 59E2645219BA240D008A6FDB /* IJSVGBezierPathAdditions.m in Sources */, 59E2645119BA240D008A6FDB /* IJSVG.m in Sources */, + 592840B11E153F3D002F9AF0 /* IJSVGLayerTree.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -598,7 +676,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.7; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx10.11; + SDKROOT = macosx10.12; }; name = Debug; }; @@ -634,7 +712,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.7; MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx10.11; + SDKROOT = macosx10.12; }; name = Release; }; 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 b9098d1..e193a61 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/Base.lproj/MainMenu.xib b/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib index 0af5efa..9b987b6 100755 --- a/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib +++ b/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - - + + - + @@ -676,33 +676,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/IJSVGExample/IJSVGExample/SVGExampleView2.m b/IJSVGExample/IJSVGExample/SVGExampleView2.m index f193a63..07d2eda 100644 --- a/IJSVGExample/IJSVGExample/SVGExampleView2.m +++ b/IJSVGExample/IJSVGExample/SVGExampleView2.m @@ -12,7 +12,7 @@ - (IJSVG *)svg { - return [[IJSVG svgNamed:@"htc_one"] retain]; + return [[IJSVG svgNamed:@"linecap"] retain]; } @end diff --git a/IJSVGExample/IJSVGExample/SVGView.m b/IJSVGExample/IJSVGExample/SVGView.m index 4603d8c..c95d0ac 100644 --- a/IJSVGExample/IJSVGExample/SVGView.m +++ b/IJSVGExample/IJSVGExample/SVGView.m @@ -7,6 +7,7 @@ // #import "SVGView.h" +#import "IJSVGExporter.h" @implementation SVGView @@ -21,6 +22,9 @@ if( ( self = [super initWithFrame:frameRect] ) != nil ) { svg = [self svg]; + svg.renderingBackingScaleHelper = ^{ + return [svg computeBackingScale:self.window.backingScaleFactor]; + }; } return self; } diff --git a/source/IJSVG.h b/source/IJSVG.h index ccd12f2..ae619b2 100644 --- a/source/IJSVG.h +++ b/source/IJSVG.h @@ -9,9 +9,16 @@ #import #import "IJSVGParser.h" #import "IJSVGBezierPathAdditions.h" +#import "IJSVGLayerTree.h" +#import "IJSVGGroupLayer.h" +#import "IJSVGImageLayer.h" @class IJSVG; +void IJSVGBeginTransactionLock(); +void IJSVGEndTransactionLock(); +void IJSVGObtainTransactionLock(dispatch_block_t block, BOOL renderOnMainThread); + @protocol IJSVGDelegate @optional @@ -26,14 +33,19 @@ withSVGString:(NSString *)subSVGString; @end +typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); + @interface IJSVG : NSObject { @private IJSVGParser * _group; CGFloat _scale; CGFloat _clipScale; - NSMutableArray * _colors; id _delegate; + IJSVGLayer * _layerTree; + CGRect _viewBox; + CGSize _proposedViewSize; + CGFloat _lastProposedBackingScale; struct { unsigned int shouldHandleForeignObject: 1; @@ -43,16 +55,39 @@ withSVGString:(NSString *)subSVGString; } -+ (NSColor *)baseColor; +// set this to be called when the layer is about to draw, it will call this +// and ask for the scale of the backing store where its going to be drawn +// and apply the scale to each layer that has custom drawing against it, mainly +// pattern and gradient layers +@property (nonatomic, copy) IJSVGRenderingBackingScaleFactorHelper renderingBackingScaleHelper; + +// global overwriting rules for when rendering an SVG, this will overide any +// fillColor, strokeColor, pattern and gradient fill +@property (nonatomic, retain) NSColor * fillColor; +@property (nonatomic, retain) NSColor * strokeColor; + - (BOOL)isFont; - (NSRect)viewBox; - (NSArray *)glyphs; - (NSString *)identifier; +- (IJSVGLayer *)layer; +- (IJSVGLayer *)layerWithTree:(IJSVGLayerTree *)tree; - (NSArray *)subSVGs:(BOOL)recursive; -+ (void)setBaseColor:(NSColor *)color; + +- (CGFloat)computeBackingScale:(CGFloat)scale; +- (void)discardDOM; + + (id)svgNamed:(NSString *)string; + (id)svgNamed:(NSString *)string delegate:(id)delegate; ++ (id)svgNamed:(NSString *)string + useCache:(BOOL)useCache + delegate:(id)delegate; + +- (id)initWithImage:(NSImage *)image; + +- (id)initWithSVGLayer:(IJSVGGroupLayer *)group + viewBox:(NSRect)viewBox; - (id)initWithSVGString:(NSString *)string error:(NSError **)error @@ -62,6 +97,8 @@ withSVGString:(NSString *)subSVGString; - (id)initWithSVGString:(NSString *)string error:(NSError **)error; +- (id)initWithFile:(NSString *)file + useCache:(BOOL)useCache; - (id)initWithFile:(NSString *)file; - (id)initWithFile:(NSString *)file error:(NSError **)error; @@ -70,12 +107,19 @@ withSVGString:(NSString *)subSVGString; - (id)initWithFile:(NSString *)file error:(NSError **)error delegate:(id)delegate; +- (id)initWithFile:(NSString *)file + useCache:(BOOL)useCache + error:(NSError **)error + delegate:(id)delegate; - (id)initWithFilePathURL:(NSURL *)aURL; +- (id)initWithFilePathURL:(NSURL *)aURL + useCache:(BOOL)useCache; - (id)initWithFilePathURL:(NSURL *)aURL error:(NSError **)error; - (id)initWithFilePathURL:(NSURL *)aURL delegate:(id)delegate; - (id)initWithFilePathURL:(NSURL *)aURL + useCache:(BOOL)useCache error:(NSError **)error delegate:(id)delegate; - (NSImage *)imageWithSize:(NSSize)aSize; @@ -91,7 +135,7 @@ withSVGString:(NSString *)subSVGString; - (BOOL)drawInRect:(NSRect)rect; - (BOOL)drawInRect:(NSRect)rect error:(NSError **)error; -- (NSArray *)colors; + - (NSData *)PDFData; - (NSData *)PDFData:(NSError **)error; - (NSData *)PDFDataWithRect:(NSRect)rect; diff --git a/source/IJSVG.m b/source/IJSVG.m index 2f20c55..e958fba 100644 --- a/source/IJSVG.m +++ b/source/IJSVG.m @@ -8,33 +8,24 @@ #import "IJSVG.h" #import "IJSVGCache.h" +#import "IJSVGTransaction.h" @implementation IJSVG +@synthesize fillColor; +@synthesize strokeColor; +@synthesize renderingBackingScaleHelper; + - (void)dealloc { + [renderingBackingScaleHelper release], renderingBackingScaleHelper = nil; + [fillColor release], fillColor = nil; + [strokeColor release], strokeColor = nil; [_group release], _group = nil; - [_colors release], _colors = nil; + [_layerTree release], _layerTree = nil; [super dealloc]; } -static NSColor * _baseColor = nil; - -+ (void)setBaseColor:(NSColor *)color -{ - if( _baseColor != nil ) - [_baseColor release], _baseColor = nil; - if( color == nil ) - _baseColor = nil; - else - _baseColor = [color retain]; -} - -+ (NSColor *)baseColor -{ - return [[_baseColor copy] autorelease]; -} - + (id)svgNamed:(NSString *)string error:(NSError **)error { @@ -49,6 +40,16 @@ static NSColor * _baseColor = nil; error:nil]; } ++ (id)svgNamed:(NSString *)string + useCache:(BOOL)useCache + delegate:(id)delegate +{ + return [[self class] svgNamed:string + useCache:useCache + error:nil + delegate:delegate]; +} + + (id)svgNamed:(NSString *)string delegate:(id)delegate { @@ -60,26 +61,93 @@ static NSColor * _baseColor = nil; + (id)svgNamed:(NSString *)string error:(NSError **)error delegate:(id)delegate +{ + return [self svgNamed:string + useCache:YES + error:error + delegate:delegate]; +} + ++ (id)svgNamed:(NSString *)string + useCache:(BOOL)useCache + error:(NSError **)error + delegate:(id)delegate { NSBundle * bundle = [NSBundle mainBundle]; NSString * str = nil; NSString * ext = [string pathExtension]; - if( ext == nil || ext.length == 0 ) + if( ext == nil || ext.length == 0 ) { ext = @"svg"; - if( ( str = [bundle pathForResource:[string stringByDeletingPathExtension] - ofType:ext] ) != nil ) + } + if( ( str = [bundle pathForResource:[string stringByDeletingPathExtension] ofType:ext] ) != nil ) { return [[[self alloc] initWithFile:str + useCache:useCache error:error delegate:delegate] autorelease]; + } return nil; } +- (id)initWithImage:(NSImage *)image +{ + __block IJSVGGroupLayer * layer = nil; + __block IJSVGImageLayer * imageLayer = nil; + + // make sure we obtain a lock, with whatever we do with layers! + IJSVGObtainTransactionLock(^{ + // create the layers we require + layer = [[[IJSVGGroupLayer alloc] init] autorelease]; + imageLayer = [[[IJSVGImageLayer alloc] initWithImage:image] autorelease]; + [layer addSublayer:imageLayer]; + }, NO); + + // return the initialized SVG + return [self initWithSVGLayer:layer + viewBox:imageLayer.frame]; +} + +- (id)initWithSVGLayer:(IJSVGGroupLayer *)group + viewBox:(NSRect)viewBox +{ + // this completely bypasses passing of files + if((self = [super init]) != nil) { + // keep the layer tree + _layerTree = [group retain]; + _viewBox = viewBox; + + // any setups + [self _setupBasicsFromAnyInitializer]; + } + return self; +} + + - (id)initWithFile:(NSString *)file { return [self initWithFile:file delegate:nil]; } +- (id)initWithFile:(NSString *)file + useCache:(BOOL)useCache +{ + return [self initWithFile:file + useCache:useCache + error:nil + delegate:nil]; +} + +- (id)initWithFile:(NSString *)file + useCache:(BOOL)useCache + error:(NSError **)error + delegate:(id)delegate +{ + return [self initWithFilePathURL:[NSURL fileURLWithPath:file] + useCache:useCache + error:error + delegate:delegate]; +} + - (id)initWithFile:(NSString *)file error:(NSError **)error { @@ -101,6 +169,7 @@ static NSColor * _baseColor = nil; delegate:(id)delegate { return [self initWithFilePathURL:[NSURL fileURLWithPath:file] + useCache:YES error:error delegate:delegate]; } @@ -108,6 +177,7 @@ static NSColor * _baseColor = nil; - (id)initWithFilePathURL:(NSURL *)aURL { return [self initWithFilePathURL:aURL + useCache:YES error:nil delegate:nil]; } @@ -116,55 +186,77 @@ static NSColor * _baseColor = nil; error:(NSError **)error { return [self initWithFilePathURL:aURL + useCache:YES error:error delegate:nil]; } +- (id)initWithFilePathURL:(NSURL *)aURL + useCache:(BOOL)useCache +{ + return [self initWithFilePathURL:aURL + useCache:useCache + error:nil + delegate:nil]; +} + - (id)initWithFilePathURL:(NSURL *)aURL delegate:(id)delegate { return [self initWithFilePathURL:aURL + useCache:YES error:nil delegate:delegate]; } - (id)initWithFilePathURL:(NSURL *)aURL + useCache:(BOOL)useCache error:(NSError **)error delegate:(id)delegate { #ifndef __clang_analyzer__ - if( [IJSVGCache enabled] ) - { + + // check the cache first + if( useCache && [IJSVGCache enabled] ) { IJSVG * svg = nil; - if( ( svg = [IJSVGCache cachedSVGForFileURL:aURL] ) != nil ) - { + if( ( svg = [IJSVGCache cachedSVGForFileURL:aURL] ) != nil ) { // have to release, as this was called from an alloc..! [self release]; return [svg retain]; } } + // create the object if( ( self = [super init] ) != nil ) { NSError * anError = nil; _delegate = delegate; + + // this is a really quick check against the delegate + // for methods that exist [self _checkDelegate]; + // create the group _group = [[IJSVGParser groupForFileURL:aURL error:&anError delegate:self] retain]; + [self _setupBasicInfoFromGroup]; + [self _setupBasicsFromAnyInitializer]; + // something went wrong... - if( _group == nil ) - { + if( _group == nil ) { if( error != NULL ) { *error = anError; } [self release], self = nil; return nil; } - if( [IJSVGCache enabled] ) + + // cache the file + if( useCache && [IJSVGCache enabled] ) { [IJSVGCache cacheSVG:self fileURL:aURL]; + } } #endif return self; @@ -201,6 +293,9 @@ static NSColor * _baseColor = nil; error:&anError delegate:self]; + [self _setupBasicInfoFromGroup]; + [self _setupBasicsFromAnyInitializer]; + // something went wrong :( if(_group == nil) { if(error != NULL) { @@ -213,6 +308,28 @@ static NSColor * _baseColor = nil; return self; } +- (void)discardDOM +{ + // if we discard, we can no longer create a tree, so lets create tree + // upfront before we kill anything + [self layer]; + + // now clear memory + [_group release], _group = nil; +} + +- (void)_setupBasicInfoFromGroup +{ + // store the viewbox + _viewBox = _group.viewBox; + _proposedViewSize = _group.proposedViewSize; +} + +- (void)_setupBasicsFromAnyInitializer +{ + _lastProposedBackingScale = 1.f; +} + - (NSString *)identifier { return _group.identifier; @@ -227,7 +344,7 @@ static NSColor * _baseColor = nil; - (NSRect)viewBox { - return _group.viewBox; + return _viewBox; } - (BOOL)isFont @@ -297,8 +414,8 @@ static NSColor * _baseColor = nil; { return [self PDFDataWithRect:(NSRect){ .origin=NSZeroPoint, - .size=_group.size} - error:error]; + .size=_viewBox.size + } error:error]; } - (NSData *)PDFDataWithRect:(NSRect)rect @@ -310,27 +427,17 @@ static NSColor * _baseColor = nil; - (NSData *)PDFDataWithRect:(NSRect)rect error:(NSError **)error { - NSColor * oldBaseColour = [[self class] baseColor]; - [[self class] setBaseColor:nil]; - // store the old context - NSGraphicsContext * oldGraphicsContext = [NSGraphicsContext currentContext]; - // create the data for the PDF NSMutableData * data = [[[NSMutableData alloc] init] autorelease]; // assign the data to the consumer CGDataConsumerRef dataConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)data); - const CGRect box = CGRectMake( rect.origin.x, rect.origin.y, rect.size.width, rect.size.height ); + const CGRect box = CGRectMake( rect.origin.x, rect.origin.y, + rect.size.width, rect.size.height ); // create the context CGContextRef context = CGPDFContextCreate( dataConsumer, &box, NULL ); - NSGraphicsContext * newContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context - flipped:NO]; - CGContextSaveGState(context); - - // set it as the current - [NSGraphicsContext setCurrentContext:newContext]; CGContextBeginPage( context, &box ); // the context is currently upside down, doh! flip it... @@ -341,31 +448,16 @@ static NSColor * _baseColor = nil; [self _drawInRect:(NSRect)box context:context error:error]; + CGContextEndPage(context); //clean up CGPDFContextClose(context); CGContextRelease(context); CGDataConsumerRelease(dataConsumer); - - CGContextRestoreGState(context); - - // set the graphics context back to its original - [NSGraphicsContext setCurrentContext:oldGraphicsContext]; - [[self class] setBaseColor:oldBaseColour]; return data; } -- (NSArray *)colors -{ - if( _colors == nil ) - { - _colors = [[NSMutableArray alloc] init]; - [self _recursiveColors:_group]; - } - return [[_colors copy] autorelease]; -} - - (BOOL)drawAtPoint:(NSPoint)point size:(NSSize)aSize { @@ -396,21 +488,26 @@ static NSColor * _baseColor = nil; error:error]; } +- (CGFloat)computeBackingScale:(CGFloat)actualScale +{ + return (CGFloat)(_scale + actualScale); +} + - (NSRect)computeRectDrawingInRect:(NSRect)rect isValid:(BOOL *)valid { // 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-(_group.proposedViewSize.width/2)*_clipScale); - viewPort.origin.y = round(rect.size.height/2-(_group.proposedViewSize.height/2)*_clipScale);; - viewPort.size.width = _group.proposedViewSize.width*_clipScale; - viewPort.size.height = _group.proposedViewSize.height*_clipScale; + 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.size.width = _proposedViewSize.width*_clipScale; + viewPort.size.height = _proposedViewSize.height*_clipScale; // check the viewport - if( NSEqualRects( _group.viewBox, NSZeroRect ) - || _group.viewBox.size.width <= 0 - || _group.viewBox.size.height <= 0 + if( NSEqualRects( _viewBox, NSZeroRect ) + || _viewBox.size.width <= 0 + || _viewBox.size.height <= 0 || NSEqualRects( NSZeroRect, viewPort) || CGRectIsEmpty(viewPort) || CGRectIsNull(viewPort) @@ -438,8 +535,8 @@ static NSColor * _baseColor = nil; // scale the whole drawing context, but first, we need // to translate the context so its centered - CGFloat tX = round(rect.size.width/2-(_group.size.width/2)*_scale); - CGFloat tY = round(rect.size.height/2-(_group.size.height/2)*_scale); + 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 @@ -447,34 +544,38 @@ static NSColor * _baseColor = nil; NSRect viewPort = [self computeRectDrawingInRect:rect isValid:&canDraw]; // check the viewport - if( !canDraw ) - { - if( error != NULL ) + if( !canDraw ) { + if( error != NULL ) { *error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain code:IJSVGErrorDrawing userInfo:nil] autorelease]; + } CGContextRestoreGState(ref); return NO; } - // clip any drawing to the view port - [[NSBezierPath bezierPathWithRect:viewPort] addClip]; + // clip to mask + CGContextClipToRect( ref, viewPort); - tX -= _group.viewBox.origin.x*_scale; - tY -= _group.viewBox.origin.y*_scale; + tX -= (_viewBox.origin.x*_scale); + tY -= (_viewBox.origin.y*_scale); CGContextTranslateCTM( ref, tX, tY ); CGContextScaleCTM( ref, _scale, _scale ); - // apply standard defaults - [self _applyDefaults:ref - node:_group]; - - // begin draw - [self _drawGroup:_group - rect:rect - context:ref]; + // render the layer, its really important we lock + // the transaction when drawing + __block IJSVG * weakSelf = self; + IJSVGBeginTransactionLock(); + // do we need to update the backing scales on the + // layers? + if(weakSelf.renderingBackingScaleHelper != nil) { + [weakSelf _askHelperForBackingScale]; + } + // render the layers + [weakSelf.layer renderInContext:ref]; + IJSVGEndTransactionLock(); } @catch (NSException *exception) { // just catch and give back a drawing error to the caller @@ -490,31 +591,84 @@ static NSColor * _baseColor = nil; return (error == nil); } -- (void)_recursiveColors:(IJSVGGroup *)group +- (void)_askHelperForBackingScale { - if( group.fillColor != nil && !group.usesDefaultFillColor ) - [self _addColor:group.fillColor]; - if( group.strokeColor != nil ) - [self _addColor:group.strokeColor]; - for( id node in [group children] ) - { - if( [node isKindOfClass:[IJSVGGroup class]] ) - [self _recursiveColors:node]; - else { - IJSVGPath * p = (IJSVGPath*)node; - if( p.fillColor != nil ) - [self _addColor:p.fillColor]; - if( p.strokeColor != nil ) - [self _addColor:p.strokeColor]; - } + CGFloat scale = (self.renderingBackingScaleHelper)(); + if(scale < 1.f) { + scale = 1.f; + } + + // dont do anything, nothing has changed, no point of iterating over + // every layer for no reason! + if(scale == _lastProposedBackingScale) { + return; + } + _lastProposedBackingScale = scale; + [self _recursivelySetScale:scale + forLayer:self.layer]; +} + +- (void)_recursivelySetScale:(CGFloat)scale + forLayer:(IJSVGLayer *)layer +{ + // update the backing layer scale + if(layer.requiresBackingScaleHelp == YES) { + layer.backingScaleFactor = scale; + } + + // find the next! + for(IJSVGLayer * aLayer in layer.sublayers) { + [self _recursivelySetScale:scale + forLayer:aLayer]; } } -- (void)_addColor:(NSColor *)color +- (void)setFillColor:(NSColor *)aColor { - if( [_colors containsObject:color] || color == [NSColor clearColor] ) - return; - [_colors addObject:color]; + if(fillColor != nil) { + [fillColor release], fillColor = nil; + } + fillColor = [aColor retain]; + [_layerTree release], _layerTree = nil; +} + +- (void)setStrokeColor:(NSColor *)aColor +{ + if(strokeColor != nil) { + [strokeColor release], strokeColor = nil; + } + strokeColor = [aColor retain]; + [_layerTree release], _layerTree = nil; +} + +- (IJSVGLayer *)layerWithTree:(IJSVGLayerTree *)tree +{ + // clear memory + if(_layerTree != nil) { + [_layerTree release], _layerTree = nil; + } + + // force rebuild of the tree + IJSVGBeginTransactionLock(); + _layerTree = [[tree layerForNode:_group] retain]; + IJSVGEndTransactionLock(); + return _layerTree; +} + +- (IJSVGLayer *)layer +{ + if(_layerTree != nil) { + return _layerTree; + } + + // create the renderer and assign default values + // from this SVG object + IJSVGLayerTree * renderer = [[[IJSVGLayerTree alloc] init] autorelease]; + renderer.fillColor = self.fillColor; + renderer.strokeColor = self.strokeColor; + + // return the rendered layer + return [self layerWithTree:renderer]; } - (void)_beginDraw:(NSRect)rect @@ -523,280 +677,16 @@ static NSColor * _baseColor = nil; // rect, we need to work out the ratio scale in order // to transform the paths into our viewbox NSSize dest = rect.size; - NSSize source = _group.viewBox.size; - _clipScale = MIN(dest.width/_group.proposedViewSize.width,dest.height/_group.proposedViewSize.height); + NSSize source = _viewBox.size; + _clipScale = MIN(dest.width/_proposedViewSize.width, + dest.height/_proposedViewSize.height); // work out the actual scale based on the clip scale - CGFloat w = _group.proposedViewSize.width*_clipScale; - CGFloat h = _group.proposedViewSize.height*_clipScale; + CGFloat w = _proposedViewSize.width*_clipScale; + CGFloat h = _proposedViewSize.height*_clipScale; _scale = MIN(w/source.width,h/source.height); } -- (void)_prepClip:(IJSVGNode *)node - context:(CGContextRef)context - drawBlock:(dispatch_block_t)block - rect:(NSRect)rect -{ - if( node.clipPath != nil ) - { - for( id clip in node.clipPath.children ) - { - // save the context - CGContextSaveGState(context); - { - - if( [clip isKindOfClass:[IJSVGGroup class]] ) - { - [self _drawGroup:clip - rect:rect - context:context]; - } else { - - // add the clip and draw - IJSVGPath * path = (IJSVGPath *)clip; - [[IJSVGTransform transformedPath:path] addClip]; - block(); - } - - // restore the context - } - CGContextRestoreGState(context); - } - return; - } - - // just draw - block(); -} - -- (void)_drawGroup:(IJSVGGroup *)group - rect:(NSRect)rect - context:(CGContextRef)context -{ - - if( !group.shouldRender ) - return; - - CGContextSaveGState( context ); - { - - // perform any transforms - [self _applyDefaults:context - node:group]; - - dispatch_block_t drawBlock = ^(void) - { - // it could be a group or a path - for( id child in [group children] ) - { - if( [child isKindOfClass:[IJSVGPath class]] ) { - dispatch_block_t block = ^(void) { - IJSVGPath * p = (IJSVGPath *)child; - if( p.shouldRender ) - [self _drawPath:p - rect:rect - context:context]; - }; - - // draw the clip - [self _prepClip:child - context:context - drawBlock:block - rect:rect]; - - } else if( [child isKindOfClass:[IJSVGGroup class]] ) { - - // if its a group, we recursively call this method - // to generate the paths required - [self _drawGroup:child - rect:rect - context:context]; - } else if([child isKindOfClass:[IJSVGImage class]]) { - - // draw the image - dispatch_block_t block = ^(void) { - IJSVGImage * image = (IJSVGImage *)child; - if(image.shouldRender) { - [self _drawImage:image - rect:rect - context:context]; - } - }; - - [self _prepClip:child - context:context - drawBlock:block - rect:rect]; - - } - } - - }; - - // main group clipping - [self _prepClip:group - context:context - drawBlock:drawBlock - rect:rect]; - - } - // restore the context - CGContextRestoreGState(context); -} - -- (void)_applyDefaults:(CGContextRef)context - node:(IJSVGNode *)node -{ - // the opacity, if its 0, assume its broken - // so set it to 1.f - CGFloat opacity = node.opacity; - if( opacity == 0.f ) { - opacity = 1.f; - } - - // scale it - CGContextSetAlpha( context, opacity ); - CGContextTranslateCTM( context, node.x, node.y); - - // perform any transforms - for( IJSVGTransform * transform in node.transforms ) - { - [IJSVGTransform performTransform:transform - inContext:context]; - } - -} - -- (void)_drawImage:(IJSVGImage *)image - rect:(NSRect)rect - context:(CGContextRef)ref -{ - CGContextSaveGState(ref); - { - // apply any transforms or defaults - [self _applyDefaults:ref - node:image]; - - // draw the actual image - [image drawInContextRef:ref - path:nil]; - }; - - // retsore the graphics state - CGContextRestoreGState(ref); -} - -- (void)_drawWithinNativeViewBox -{ - [self drawInRect:_group.viewBox]; -} - -- (void)_drawPath:(IJSVGPath *)path - rect:(NSRect)rect - context:(CGContextRef)ref -{ - // there should be a colour on it... - // defaults to black if not existant - CGContextSaveGState(ref); - { - // there could be transforms per path - [self _applyDefaults:ref - node:path]; - - // sub SVG - if(path.svg != nil) { - CGContextSaveGState(ref); - { - // draw the sub SVG in context - [path.svg _drawInRect:path.svg.viewBox - context:ref - error:nil]; - } - CGContextRestoreGState(ref); - } - - // fill the path - if( path.fillGradient != nil ) - { - CGContextSaveGState(ref); - { - // for this to work, we need to add the clip so when - // drawing occurs, it doesnt go outside the path bounds - [path.path addClip]; - [path.fillGradient drawInContextRef:ref - path:path]; - } - CGContextRestoreGState(ref); - } else if(path.fillPattern != nil) { - CGContextSaveGState(ref); - { - // for this to work, we need to add the clip so when - // drawing occurs, it doesnt go outside the path bounds - [path.path addClip]; - [path.fillPattern drawInContextRef:ref - path:path]; - } - CGContextRestoreGState(ref); - } else { - // no gradient specified - // just use the color instead - if( path.windingRule != IJSVGWindingRuleInherit ) - [path.path setWindingRule:(NSWindingRule)path.windingRule]; - - if( path.fillColor != nil ) - { - [path.fillColor set]; - [path.path fill]; - } else if( _baseColor != nil ) { - - // is there a base color? - // this is basically used whenever no color - // is set, its also set via [IJSVG setBaseColor], - // this must be defined! - - [_baseColor set]; - [path.path fill]; - } else { - [path.path fill]; - } - } - - // any stroke? - if( path.strokeColor != nil ) - { - // default line width is 1 - // if its defined elsewhere, then - // use that one instead - CGFloat lineWidth = 1.f; - if( path.strokeWidth > 0.f ) - lineWidth = path.strokeWidth; - - if( path.lineCapStyle != IJSVGLineCapStyleInherit ) - [path.path setLineCapStyle:(NSLineCapStyle)path.lineCapStyle]; - else - [path.path setLineCapStyle:NSButtLineCapStyle]; - - if( path.lineJoinStyle != IJSVGLineJoinStyleInherit ) - [path.path setLineJoinStyle:(NSLineJoinStyle)path.lineJoinStyle]; - - [path.strokeColor setStroke]; - [path.path setLineWidth:lineWidth]; - - // any dashed array? - if( path.strokeDashArrayCount != 0 ) - [path.path setLineDash:path.strokeDashArray - count:path.strokeDashArrayCount - phase:path.strokeDashOffset]; - - [path.path stroke]; - } - - } - // restore the graphics state - CGContextRestoreGState(ref); - -} - #pragma mark NSPasteboard - (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard diff --git a/source/IJSVGCache.h b/source/IJSVGCache.h index e2d7005..a1e211f 100644 --- a/source/IJSVGCache.h +++ b/source/IJSVGCache.h @@ -20,5 +20,6 @@ + (BOOL)enabled; + (void)setEnabled:(BOOL)flag; + (void)purgeCachedSVGForFileURL:(NSURL *)aURL; ++ (void)setEvictItemsAfter:(NSInteger)count; @end diff --git a/source/IJSVGCache.m b/source/IJSVGCache.m index 7666c03..e1af60c 100644 --- a/source/IJSVGCache.m +++ b/source/IJSVGCache.m @@ -11,9 +11,7 @@ @implementation IJSVGCache -// 5MB -#define MAX_CACHE_SIZE 5000000 - +static NSInteger _maxCacheItems = 20; static NSCache * _cache = nil; static BOOL _enabled = YES; @@ -22,6 +20,12 @@ static BOOL _enabled = YES; [self setEnabled:_enabled]; } ++ (void)setEvictItemsAfter:(NSInteger)count +{ + _maxCacheItems = count; + [_cache setTotalCostLimit:_maxCacheItems]; +} + + (IJSVG *)cachedSVGForFileURL:(NSURL *)aURL { if( ![[self class] enabled] || _cache == nil ) @@ -40,33 +44,23 @@ static BOOL _enabled = YES; + (void)cacheSVG:(IJSVG *)svg fileURL:(NSURL *)aURL { - // use the malloc size for the object size, - // actually bad idea, use the file size - // is this correct? - struct stat st; - long cost = 0; - if( lstat( [[aURL path] cStringUsingEncoding:NSUTF8StringEncoding], &st ) != -1 ) - cost = st.st_size; - [_cache setObject:svg forKey:aURL - cost:cost]; + cost:1]; } + (void)setEnabled:(BOOL)flag { _enabled = flag; - if( !flag ) - { + if( !flag ) { [[self class] flushCache]; return; } // create a new cache if allowed - if( _cache == nil ) - { + if( _cache == nil ) { _cache = [[NSCache alloc] init]; - [_cache setTotalCostLimit:MAX_CACHE_SIZE]; + [_cache setTotalCostLimit:_maxCacheItems]; } } diff --git a/source/IJSVGColor.h b/source/IJSVGColor.h index 071d872..8c9052a 100644 --- a/source/IJSVGColor.h +++ b/source/IJSVGColor.h @@ -161,6 +161,9 @@ typedef NS_ENUM( NSInteger, IJSVGPredefinedColor ) { @interface IJSVGColor : NSObject + (BOOL)isColor:(NSString *)string; ++ (NSString *)colorStringFromColor:(NSColor *)color + forceHex:(BOOL)forceHex; ++ (NSString *)colorStringFromColor:(NSColor *)color; + (NSColor *)computeColor:(id)colour; + (NSColor *)colorFromString:(NSString *)string; + (NSColor *)colorFromHEXString:(NSString *)string diff --git a/source/IJSVGColor.m b/source/IJSVGColor.m index a10c84c..2983647 100644 --- a/source/IJSVGColor.m +++ b/source/IJSVGColor.m @@ -205,10 +205,10 @@ static NSMutableDictionary * _colorTree = nil; CGFloat alpha = 1; if( count == 4 ) alpha = params[3]; - color = [NSColor colorWithCalibratedRed:params[0]/255 - green:params[1]/255 - blue:params[2]/255 - alpha:alpha]; + color = [NSColor colorWithDeviceRed:params[0]/255 + green:params[1]/255 + blue:params[2]/255 + alpha:alpha]; free(params); return color; } @@ -228,6 +228,38 @@ static NSMutableDictionary * _colorTree = nil; alpha:1.f]; } ++ (NSString *)colorStringFromColor:(NSColor *)color +{ + return [self colorStringFromColor:color + forceHex:NO]; +} + ++ (NSString *)colorStringFromColor:(NSColor *)color + forceHex:(BOOL)forceHex +{ + // convert to RGB + color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + + int red = color.redComponent * 0xFF; + int green = color.greenComponent * 0xFF; + int blue = color.blueComponent * 0xFF; + int alpha = (int)(color.alphaComponent*100); + + // jsut return none + if(alpha == 0 && forceHex == NO) { + return @"none"; + } + + // always return hex unless criteria is met + if(forceHex || alpha == 100 || + (red == 0 && green == 0 && blue == 0 && alpha == 0) || + (red == 255 && green == 255 && blue == 255 && alpha == 100)) { + // just return hex + return [NSString stringWithFormat:@"#%02X%02X%02X",red,green,blue]; + } + return [NSString stringWithFormat:@"rgba(%d, %d, %d, %d)",red, green, blue, alpha]; +} + + (NSString *)colorNameFromPredefinedColor:(IJSVGPredefinedColor)color { switch(color) @@ -534,10 +566,10 @@ static NSMutableDictionary * _colorTree = nil; to:(CGFloat)alphaValue { color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; - return [NSColor colorWithCalibratedRed:[color redComponent] - green:[color greenComponent] - blue:[color blueComponent] - alpha:alphaValue]; + return [NSColor colorWithDeviceRed:[color redComponent] + green:[color greenComponent] + blue:[color blueComponent] + alpha:alphaValue]; } + (BOOL)isColor:(NSString *)string @@ -580,10 +612,10 @@ static NSMutableDictionary * _colorTree = nil; NSInteger r = (hex>>16) & 0xFF; NSInteger g = (hex>>8) & 0xFF; NSInteger b = (hex) & 0xFF; - return [NSColor colorWithCalibratedRed:r/255.f - green:g/255.f - blue:b/255.f - alpha:alpha]; + return [NSColor colorWithDeviceRed:r/255.f + green:g/255.f + blue:b/255.f + alpha:alpha]; } return nil; } diff --git a/source/IJSVGDef.m b/source/IJSVGDef.m index 6e137cb..bab5fca 100644 --- a/source/IJSVGDef.m +++ b/source/IJSVGDef.m @@ -18,8 +18,7 @@ - (id)init { - if( ( self = [super initWithDef:NO] ) != nil ) - { + if( ( self = [super initWithDef:NO] ) != nil ) { _dict = [[NSMutableDictionary alloc] init]; } return self; @@ -27,8 +26,9 @@ - (void)addDef:(IJSVGNode *)aDef { - if( aDef.identifier == nil ) + if( aDef.identifier == nil ) { return; + } [_dict setObject:aDef forKey:aDef.identifier]; } diff --git a/source/IJSVGExporter.h b/source/IJSVGExporter.h new file mode 100644 index 0000000..0f1ba52 --- /dev/null +++ b/source/IJSVGExporter.h @@ -0,0 +1,57 @@ +// +// IJSVGExporter.h +// IJSVGExample +// +// Created by Curtis Hard on 06/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import + +@class IJSVG; + +typedef void (^IJSVGCGPathHandler)(const CGPathElement * pathElement); + +void IJSVGExporterPathCaller(void * info, const CGPathElement * pathElement); + +typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { + IJSVGExporterOptionRemoveUselessGroups = 1 << 1, + IJSVGExporterOptionRemoveUselessDef = 1 << 2, + IJSVGExporterOptionMoveAttributesToGroup = 1 << 3, + IJSVGExporterOptionCreateUseForPaths = 1 << 4, + IJSVGExporterOptionSortAttributes = 1 << 5, + IJSVGExporterOptionCollapseGroups = 1 << 6, + IJSVGExporteroptionCleanupPaths = 1 << 7, + IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef| + IJSVGExporterOptionRemoveUselessGroups| + IJSVGExporterOptionCreateUseForPaths| + IJSVGExporterOptionMoveAttributesToGroup| + IJSVGExporterOptionSortAttributes| + IJSVGExporterOptionCollapseGroups| + IJSVGExporteroptionCleanupPaths +}; + +@interface IJSVGExporter : NSObject { + +@private + IJSVG * _svg; + IJSVGExporterOptions _options; + NSXMLDocument * _dom; + NSXMLElement * _defElement; + NSInteger _gradCount; + NSInteger _patternCount; + NSInteger _imageCount; + NSInteger _maskCount; + NSInteger _pathCount; + +} + +@property (nonatomic, copy) NSString * title; +@property (nonatomic, copy) NSString * description; + +- (id)initWithSVG:(IJSVG *)svg + options:(IJSVGExporterOptions)options; +- (NSString *)SVGString; +- (NSData *)SVGData; + +@end diff --git a/source/IJSVGExporter.m b/source/IJSVGExporter.m new file mode 100644 index 0000000..67572c5 --- /dev/null +++ b/source/IJSVGExporter.m @@ -0,0 +1,1086 @@ +// +// IJSVGExporter.m +// IJSVGExample +// +// Created by Curtis Hard on 06/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGExporter.h" +#import "IJSVG.h" +#import "IJSVGGradientLayer.h" +#import "IJSVGRadialGradient.h" +#import "IJSVGLinearGradient.h" +#import "IJSVGPatternLayer.h" +#import "IJSVGImageLayer.h" +#import "IJSVGShapeLayer.h" +#import "IJSVGGroupLayer.h" +#import "IJSVGStrokeLayer.h" +#import "IJSVGMath.h" +#import "IJSVGExporterPathInstruction.h" + +@implementation IJSVGExporter + +#define XML_DOC_VERSION 1.1f +#define XML_DOC_NS @"http://www.w3.org/2000/svg" +#define XML_DOC_NSXLINK @"http://www.w3.org/1999/xlink" +#define XML_DOCTYPE_VERSION @"1.0" +#define XML_DOC_CHARSET @"UTF-8" +#define XML_DOC_GENERATOR @"Generated by IJSVG (https://github.com/curthard89/IJSVG)" + +@synthesize title; +@synthesize description; + +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]; + }); + return _attributes; +} + +void IJSVGApplyAttributesToElement(NSDictionary *attributes, NSXMLElement *element) { + [element setAttributesAsDictionary:attributes]; +}; + +NSDictionary * IJSVGElementAttributeDictionary(NSXMLElement * element) { + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; + for(NSXMLNode * attribute in element.attributes) { + dict[attribute.name] = attribute.stringValue; + } + return dict; +}; + +NSString * IJSVGShortFloatString(CGFloat f) +{ + return [NSString stringWithFormat:@"%g",f]; +}; + +NSString * IJSVGHashURL(NSString * key) { + return [NSString stringWithFormat:@"url(#%@)",key]; +}; + +NSString * IJSVGHash(NSString * key) { + return [@"#" stringByAppendingString:key]; +} + +- (void)dealloc +{ + [_svg release], _svg = nil; + [_dom release], _dom = nil; + [title release], title = nil; + [description release], description = nil; + [super dealloc]; +} + +- (id)initWithSVG:(IJSVG *)svg + options:(IJSVGExporterOptions)options +{ + if((self = [super init]) != nil) { + _options = options; + _svg = [svg retain]; + + // clear memory as soon as possible + @autoreleasepool { + [self _prepare]; + } + } + return self; +} + +- (NSXMLElement *)defElement +{ + if(_defElement != nil) { + return _defElement; + } + return _defElement = [[NSXMLElement alloc] initWithName:@"defs"]; +} + +- (NSXMLElement *)rootNode +{ + // generates the root document + NSXMLElement * root = [[[NSXMLElement alloc] initWithName:@"svg"] autorelease]; + + // sort out viewbox + NSRect viewBox = _svg.viewBox; + NSDictionary * attributes = @{ + @"viewBox":[NSString stringWithFormat:@"%g %g %g %g", + viewBox.origin.x, viewBox.origin.y, + viewBox.size.width, viewBox.size.height], + @"version": [NSString stringWithFormat:@"%g",XML_DOC_VERSION], + @"xmlns": XML_DOC_NS, + @"xmlns:xlink": XML_DOC_NSXLINK + }; + + // apply the attributes + IJSVGApplyAttributesToElement(attributes, root); + return root; +} + +- (void)_prepare +{ + // create the stand alone DOM + _dom = [[NSXMLDocument alloc] initWithRootElement:[self rootNode]]; + _dom.version = XML_DOCTYPE_VERSION; + _dom.characterEncoding = XML_DOC_CHARSET; + + // add generator + NSXMLNode * generatorNode = [[[NSXMLNode alloc] initWithKind:NSXMLCommentKind] autorelease]; + generatorNode.stringValue = XML_DOC_GENERATOR; + [_dom.rootElement addChild:generatorNode]; + + // add defs in + [_dom.rootElement addChild:[self defElement]]; + + // sort out header + + // sort out stuff, so here we go... + [self _recursiveParseFromLayer:_svg.layer + intoElement:_dom.rootElement]; + + // cleanup + [self _cleanup]; +} + +- (void)_cleanup +{ + + // convert any duplicate paths into use + if((_options & IJSVGExporterOptionCreateUseForPaths) != 0) { + [self _convertUseElements]; + } + + // cleanup def + if((_options & IJSVGExporterOptionRemoveUselessDef) != 0) { + [self _cleanDef]; + } + + // move any possible attributes to its parent group + if((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) { + [self _moveAttributesToGroup]; + } + + // collapse groups + if((_options & IJSVGExporterOptionCollapseGroups) != 0) { + [self _collapseGroups]; + } + + // clean any blank groups + if((_options & IJSVGExporterOptionRemoveUselessGroups) != 0) { + [self _cleanEmptyGroups]; + } + + // sort attributes + if((_options & IJSVGExporterOptionSortAttributes) != 0) { + [self _sortAttributesOnElement:_dom.rootElement]; + } + + // compress groups together + if((_options & IJSVGExporterOptionCollapseGroups) != 0) { + [self _compressGroups]; + } +} + +- (void)_sortAttributesOnElement:(NSXMLElement *)element +{ + // only apply to XML elements, not XMLNodes + if([element isKindOfClass:[NSXMLElement class]] == NO) { + return; + } + [self sortAttributesOnElement:element]; + for(NSXMLElement * child in element.children) { + [self _sortAttributesOnElement:child]; + } +} + +- (void)_moveAttributesToGroup +{ + @autoreleasepool { + + NSArray * groups = [_dom nodesForXPath:@"//g" + error:nil]; + + const NSArray * inheritableAttributes = IJSVGInheritableAttributes(); + + 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]; + } + + 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); + } + } + } + } + } + } +} + +- (NSDictionary *)intersectableAttributes:(NSDictionary *)atts + inheritableAttributes:(const NSArray *)inheritable +{ + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; + for(NSString * key in atts.allKeys) { + if([inheritable containsObject:key]) { + dict[key] = atts[key]; + } + } + return dict; +} + +- (NSDictionary *)intersectionInheritableAttributes:(NSDictionary *)newAttributes + currentAttributes:(NSDictionary *)currentAttributes + inheritableAttributes:(const NSArray *)inheritableAtts +{ + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; + for(NSString * key in newAttributes.allKeys) { + // make sure they are the same and + // they are inheritable + if([currentAttributes objectForKey:key] != nil && + [inheritableAtts containsObject:key] && + [newAttributes[key] isEqualToString:currentAttributes[key]]) { + dict[key] = currentAttributes[key]; + } + } + + // nothing to return, kill it + if(dict.count == 0) { + return nil; + } + return dict; +} + +- (void)_cleanDef +{ + NSXMLElement * defNode = [self defElement]; + if(defNode.children == 0) { + NSXMLElement * parent = (NSXMLElement *)defNode.parent; + [parent removeChildAtIndex:defNode.index]; + } +} + +- (void)_cleanEmptyGroups +{ + @autoreleasepool { + // cleanup any groups that are completely useless + NSArray * groups = [_dom nodesForXPath:@"//g" error:nil]; + for(NSXMLElement * element in groups) { + NSXMLElement * parent = (NSXMLElement *)element.parent; + if(element.childCount == 0) { + // empty group + [(NSXMLElement *)element.parent removeChildAtIndex:element.index]; + } else if(element.attributes.count == 0) { + // no useful data on the group + NSInteger index = element.index; + for(NSXMLElement * child in element.children) { + [(NSXMLElement *)child.parent removeChildAtIndex:child.index]; + [parent insertChild:child + atIndex:index++]; + } + [parent removeChildAtIndex:element.index]; + } + } + } +} + +- (void)_compressGroups +{ + NSArray * groups = [_dom nodesForXPath:@"//g" error:nil]; + for(NSXMLElement * group in groups) { + + // whats the next group? + if(group.parent == nil) { + continue; + } + + // compare each group with its next sibling + NSXMLElement * nextGroup = (NSXMLElement *)group.nextSibling; + while([self compareElement:group withElement:nextGroup]) { + // move each child into the older group + for(NSXMLElement * child in nextGroup.children) { + [nextGroup removeChildAtIndex:child.index]; + [group addChild:child]; + } + + // remove the newer + NSXMLElement * n = nextGroup; + nextGroup = (NSXMLElement *)nextGroup.nextSibling; + [(NSXMLElement *)n.parent removeChildAtIndex:n.index]; + } + } + +} + +- (void)_collapseGroups +{ + 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) { + continue; + } + + if(group.attributes.count != 0 && group.children.count == 1) { + + // grab the first child as its a loner + NSXMLElement * child = (NSXMLElement *)group.children[0]; + + 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]; + } + } + } +} + +- (BOOL)compareElement:(NSXMLElement *)element + withElement:(NSXMLElement *)anotherElement +{ + // not a matching element + if([element.name isEqualToString:anotherElement.name] == NO || + element.attributes.count != anotherElement.attributes.count) { + return NO; + } + + // compare attributes + for(NSXMLNode * attribute in element.attributes) { + NSString * compareString = [anotherElement attributeForName:attribute.name].stringValue; + if([attribute.stringValue isEqualToString:compareString] == NO) { + return NO; + } + } + return YES; +} + +- (void)_convertUseElements +{ + @autoreleasepool { + NSArray * paths = [_dom nodesForXPath:@"//path" + error:nil]; + + NSCountedSet * set = [[[NSCountedSet alloc] init] autorelease]; + for(NSXMLElement * element in paths) { + [set addObject:[element attributeForName:@"d"].stringValue]; + } + + NSMutableDictionary * defs = [[[NSMutableDictionary alloc] init] autorelease]; + + // now actually compute them + for(NSXMLElement * element in paths) { + NSString * data = [element attributeForName:@"d"].stringValue; + if([set countForObject:data] == 1) { + continue; + } + + // at this point, we know the path is being used more then once + NSXMLElement * defParentElement = nil; + if((defParentElement = [defs objectForKey:data]) == nil) { + // create the def + NSXMLElement * element = [[[NSXMLElement alloc] init] autorelease]; + element.name = @"path"; + + NSDictionary * atts = @{@"d":data, + @"id":[NSString stringWithFormat:@"path-%ld",(++_pathCount)]}; + IJSVGApplyAttributesToElement(atts, element); + + // store it against the def + defs[data] = element; + defParentElement = element; + } + + // we know at this point, we need to swap out the path to a use + NSXMLElement * use = [[[NSXMLElement alloc] init] autorelease]; + use.name = @"use"; + + // grab the id + NSString * pathId = [defParentElement attributeForName:@"id"].stringValue; + + NSXMLNode * useAttribute = [[[NSXMLNode alloc] initWithKind:NSXMLAttributeKind] autorelease]; + useAttribute.name = @"xlink:href"; + useAttribute.stringValue = IJSVGHash(pathId); + [use addAttribute:useAttribute]; + + // remove the d attribute + for(NSXMLNode * attribute in element.attributes) { + if([attribute.name isEqualToString:@"d"]) { + continue; + } + [element removeAttributeForName:attribute.name]; + [use addAttribute:attribute]; + } + + // swap it out + [(NSXMLElement *)element.parent replaceChildAtIndex:element.index + withNode:use]; + } + + // add the defs back in + NSXMLElement * def = [self defElement]; + for(NSXMLElement * defElement in defs.allValues) { + [def addChild:defElement]; + } + } +} + +- (void)_recursiveParseFromLayer:(IJSVGLayer *)layer + intoElement:(NSXMLElement *)element +{ + // is a shape + if([layer class] == [IJSVGShapeLayer class]) { + NSXMLElement * child = [self elementForShape:(IJSVGShapeLayer *)layer + fromParent:element]; + if(child != nil) { + [element addChild:child]; + } + } else if([layer isKindOfClass:[IJSVGImageLayer class]]) { + NSXMLElement * child = [self elementForImage:(IJSVGImageLayer *)layer + fromParent:element]; + if(child != nil) { + [element addChild:child]; + } + } else if([layer isKindOfClass:[IJSVGGroupLayer class]]) { + // assume its probably a group? + NSXMLElement * child = [self elementForGroup:layer + fromParent:element]; + if(child != nil) { + [element addChild:child]; + } + } +} + +- (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) { + 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:@" "]; + + // apply it to the node + IJSVGApplyAttributesToElement(@{@"transform":transformStr},element); +} + +- (NSXMLElement *)elementForGroup:(IJSVGLayer *)layer + fromParent:(NSXMLElement *)parent +{ + // create the element + NSXMLElement * e = [[[NSXMLElement alloc] init] autorelease]; + e.name = @"g"; + + // stick defaults + [self applyDefaultsToElement:e + fromLayer:layer]; + + // add group children + for(IJSVGLayer * childLayer in layer.sublayers) { + [self _recursiveParseFromLayer:childLayer + intoElement:e]; + } + + return e; +} + +- (NSString *)base64EncodedStringFromCGImage:(CGImageRef)image +{ + + // convert the CGImage into an NSImage + NSBitmapImageRep * rep = [[[NSBitmapImageRep alloc] initWithCGImage:image] autorelease]; + + // work out the data + NSData * data = [rep representationUsingType:NSBitmapImageFileTypePNG + properties:@{}]; + + NSString * base64String = [data base64EncodedStringWithOptions:0]; + return [@"data:image/png;base64," stringByAppendingString:base64String]; +} + +- (void)applyPatternFromLayer:(IJSVGPatternLayer *)layer + parentLayer:(IJSVGLayer *)parentLayer + toElement:(NSXMLElement *)element +{ + // now we need the pattern + IJSVGGroupLayer * patternLayer = (IJSVGGroupLayer *)layer.pattern; + + NSXMLElement * patternElement = [self elementForGroup:patternLayer + fromParent:nil]; + patternElement.name = @"pattern"; + + NSDictionary * dict = @{@"id":[NSString stringWithFormat:@"pattern-%ld",(++_patternCount)], + @"width":IJSVGShortFloatString(layer.patternNode.width.value), + @"height":IJSVGShortFloatString(layer.patternNode.height.value)}; + + IJSVGApplyAttributesToElement(dict, patternElement); + + [[self defElement] addChild:patternElement]; + + // now the use statement + NSXMLElement * useElement = [[[NSXMLElement alloc] init] autorelease]; + useElement.name = @"use"; + + // now add the fill + dict = @{@"fill":IJSVGHashURL([patternElement attributeForName:@"id"].stringValue)}; + IJSVGApplyAttributesToElement(dict, element); + + // fill opacity + if(patternLayer.opacity != 1.f) { + IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatString(patternLayer.opacity)}, element); + } +} + +- (void)applyGradientFromLayer:(IJSVGGradientLayer *)layer + parentLayer:(IJSVGLayer *)parentLayer + toElement:(NSXMLElement *)element +{ + IJSVGGradient * gradient = layer.gradient; + NSString * gradKey = [NSString stringWithFormat:@"gradient-%ld",(++_gradCount)]; + 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, + @"x1":lGradient.x1.stringValue, + @"y1":lGradient.y1.stringValue, + @"x2":lGradient.x2.stringValue, + @"y2":lGradient.y2.stringValue}; + + // give it the attibutes + IJSVGApplyAttributesToElement(dict, gradientElement); + } else { + + // assume radial + IJSVGRadialGradient * rGradient = (IJSVGRadialGradient *)gradient; + gradientElement.name = @"radialGradient"; + NSDictionary * dict = @{@"id":gradKey, + @"cx":rGradient.cx.stringValue, + @"cy":rGradient.cy.stringValue, + @"fx":rGradient.fx.stringValue, + @"fy":rGradient.fy.stringValue, + @"r":rGradient.radius.stringValue}; + + // give it the attributes + IJSVGApplyAttributesToElement(dict, gradientElement); + } + + // add the stops + NSGradient * grad = layer.gradient.gradient; + NSInteger noStops = grad.numberOfColorStops; + for(NSInteger i = 0; i < noStops; i++) { + + // grab each color from the gradient + NSColor * aColor = nil; + CGFloat location; + [grad getColor:&aColor + location:&location + atIndex:i]; + + // create the stop element + NSXMLElement * stop = [[[NSXMLElement alloc] init] autorelease]; + stop.name = @"stop"; + + NSMutableDictionary * atts = [[[NSMutableDictionary alloc] init] autorelease]; + atts[@"offset"] = [NSString stringWithFormat:@"%g%%",(location*100)]; + + // add the color + atts[@"stop-color"] = [IJSVGColor colorStringFromColor:aColor + forceHex:YES]; + + // we need to work out the color at this point, annoyingly... + CGFloat opacity = aColor.alphaComponent; + atts[@"stop-opacity"] = [NSString stringWithFormat:@"%g",opacity]; + + // att the attributes + + IJSVGApplyAttributesToElement(atts, stop); + + // append the stop the gradient + [gradientElement addChild:stop]; + } + + // append it to the defs + [[self defElement] addChild:gradientElement]; + + // 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); + } + + // add it to the element passed in + IJSVGApplyAttributesToElement(@{@"fill":IJSVGHashURL(gradKey)}, element); + + // fill opacity + if(layer.opacity != 1.f) { + IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatString(layer.opacity)}, element); + } +} + +- (CGAffineTransform)affineTransformFromTransforms:(NSArray *)transforms +{ + CGAffineTransform t = CGAffineTransformIdentity; + for(IJSVGTransform * transform in transforms) { + t = CGAffineTransformConcat( t, [transform CGAffineTransform]); + } + return t; +} + +- (NSXMLElement *)elementForImage:(IJSVGImageLayer *)layer + fromParent:(NSXMLElement *)parent +{ + NSString * base64String = [self base64EncodedStringFromCGImage:(CGImageRef)layer.contents]; + + // 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[@"width"] = IJSVGShortFloatString(layer.frame.size.width); + dict[@"height"] = IJSVGShortFloatString(layer.frame.size.height); + dict[@"xlink:href"] = base64String; + + // work out any position + if(layer.frame.origin.x != 0.f) { + dict[@"x"] = IJSVGShortFloatString(layer.frame.origin.x); + } + if(layer.frame.origin.y != 0.f) { + dict[@"y"] = IJSVGShortFloatString(layer.frame.origin.y); + } + + // add the attributes + IJSVGApplyAttributesToElement(dict, imageElement); + return imageElement; +} + +- (NSXMLElement *)elementForShape:(IJSVGShapeLayer *)layer + fromParent:(NSXMLElement *)parent +{ + NSXMLElement * e = [[[NSXMLElement alloc] init] autorelease]; + e.name = @"path"; + CGPathRef path = layer.path; + + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; + + // path + dict[@"d"] = [self pathFromCGPath:path]; + + // work out even odd rule + if([layer.fillRule isEqualToString:kCAFillRuleNonZero] == NO) { + dict[@"fill-rule"] = @"evenodd"; + } + + // fill color + if(layer.fillColor != nil) { + NSColor * fillColor = [NSColor colorWithCGColor:layer.fillColor]; + NSString * colorString = [IJSVGColor colorStringFromColor:fillColor]; + + // could be none + if(colorString != nil) { + dict[@"fill"] = colorString; + } + } + + // is there a gradient fill? + if(layer.gradientFillLayer != nil) { + [self applyGradientFromLayer:layer.gradientFillLayer + parentLayer:(IJSVGLayer *)layer + toElement:e]; + } + + // is there a pattern? + if(layer.patternFillLayer != nil) { + [self applyPatternFromLayer:layer.patternFillLayer + parentLayer:(IJSVGLayer *)layer + toElement:e]; + } + + // is there a stroke layer? + if(layer.strokeLayer != nil) { + // check the type + IJSVGStrokeLayer * strokeLayer = layer.strokeLayer; + if([strokeLayer isKindOfClass:[IJSVGShapeLayer class]]) { + // stroke + if(strokeLayer.lineWidth != 0.f) { + dict[@"stroke-width"] = IJSVGShortFloatString(strokeLayer.lineWidth); + } + + // stroke color + if(strokeLayer.strokeColor != nil) { + NSColor * strokeColor = [NSColor colorWithCGColor:strokeLayer.strokeColor]; + NSString * strokeColorString = [IJSVGColor colorStringFromColor:strokeColor]; + + // could be none + if(strokeColorString != nil) { + dict[@"stroke"] = strokeColorString; + } + } + + // work out line cap + if([strokeLayer.lineCap isEqualToString:kCALineCapButt] == NO) { + NSString * capStyle = nil; + if([strokeLayer.lineCap isEqualToString:kCALineCapRound]) { + capStyle = @"round"; + } else if([strokeLayer.lineCap isEqualToString:kCALineCapSquare]) { + capStyle = @"square"; + } + if(capStyle != nil) { + dict[@"stroke-linecap"] = capStyle; + } + } + + // work out line join + if([strokeLayer.lineJoin isEqualToString:kCALineJoinMiter] == NO) { + NSString * joinStyle = nil; + if([strokeLayer.lineJoin isEqualToString:kCALineJoinBevel]) { + joinStyle = @"bevel"; + } else if([strokeLayer.lineJoin isEqualToString:kCALineJoinRound]) { + joinStyle = @"round"; + } + if(joinStyle != nil) { + dict[@"stroke-linejoin"] = joinStyle; + } + } + + // work out dash offset... + if(strokeLayer.lineDashPhase != 0.f) { + dict[@"stroke-dashoffset"] = IJSVGShortFloatString(strokeLayer.lineDashPhase); + } + + // work out dash array + if(strokeLayer.lineDashPattern.count != 0) { + dict[@"stroke-dasharray"] = [strokeLayer.lineDashPattern componentsJoinedByString:@" "]; + } + + } + } + + + +// // stroke is done by a child layer +// if(layer.sublayers.count != 0) { +// +// // add the pattern sublayer +// if([sublayer isKindOfClass:[IJSVGPatternLayer class]]) { +// [self applyPatternFromLayer:(IJSVGPatternLayer *)sublayer +// parentLayer:(IJSVGLayer *)layer +// toElement:e]; +// break; +// } +// } + + // apply the attributes + IJSVGApplyAttributesToElement(dict, e); + + // apple defaults + [self applyDefaultsToElement:e + fromLayer:(IJSVGLayer *)layer]; + return e; +} + +- (void)applyDefaultsToElement:(NSXMLElement *)element + fromLayer:(IJSVGLayer *)layer +{ + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; + + // opacity + if(layer.opacity != 1.f) { + dict[@"opacity"] = IJSVGShortFloatString(layer.opacity); + } + + // add atttributes + IJSVGApplyAttributesToElement(dict, element); + + // apply transforms + [self applyTransformToElement:element + fromLayer:layer]; + + // add any masks... + if(layer.mask != nil) { + [self applyMaskToElement:element + fromLayer:layer]; + } +} + +- (void)applyMaskToElement:(NSXMLElement *)element + fromLayer:(IJSVGLayer *)layer +{ + // create the element + NSXMLElement * mask = [[[NSXMLElement alloc] init] autorelease]; + mask.name = @"mask"; + + // create the key + NSString * maskKey = [NSString stringWithFormat:@"mask-%ld",(++_maskCount)]; + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; + dict[@"id"] = maskKey; + dict[@"maskContentUnits"] = @"userSpaceOnUse"; + dict[@"maskUnits"] = @"objectBoundingBox"; + + if(layer.mask.frame.origin.x != 0.f) { + dict[@"x"] = IJSVGShortFloatString(layer.mask.frame.origin.x); + } + if(layer.mask.frame.origin.y != 0.f) { + dict[@"y"] = IJSVGShortFloatString(layer.mask.frame.origin.y); + } + + IJSVGApplyAttributesToElement(dict, mask); + + // add the cool stuff + [self _recursiveParseFromLayer:(IJSVGLayer *)layer.mask + intoElement:mask]; + + // add mask id to element + IJSVGApplyAttributesToElement(@{@"mask":IJSVGHashURL(maskKey)}, element); + + // add it defs + [[self defElement] addChild:mask]; +} + +- (NSString *)SVGString +{ + return [_dom XMLStringWithOptions:NSXMLNodePrettyPrint]; +} + +- (NSData *)SVGData +{ + return [[self SVGString] dataUsingEncoding:NSUTF8StringEncoding]; +} + +#pragma mark CGPath stuff + +- (NSString *)pathFromCGPath:(CGPathRef)path +{ + // string to store the path in + NSArray * instructions = [IJSVGExporterPathInstruction instructionsFromPath:path]; + + // work out what to do... + if((_options & IJSVGExporteroptionCleanupPaths) != 0) { + [IJSVGExporterPathInstruction convertInstructionsToRelativeCoordinates:instructions]; + } + return [IJSVGExporterPathInstruction pathStringFromInstructions:instructions]; +} + +void IJSVGExporterPathCaller(void * info, const CGPathElement * pathElement) { + IJSVGCGPathHandler handler = (IJSVGCGPathHandler)info; + handler(pathElement); +}; + +- (void)sortAttributesOnElement:(NSXMLElement *)element +{ + const NSArray * order = @[@"id",@"width",@"height",@"x",@"x1",@"x2", + @"y",@"y1",@"y2",@"cx",@"cy",@"r",@"fill", + @"stroke",@"marker",@"d",@"points",@"transform", + @"gradientTransform", @"xlink:href"]; + + // grab the attributes + NSArray* attributes = element.attributes; + NSInteger count = attributes.count; + + // sort the attributes using a custom sort + NSArray * sorted = [attributes sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { + // tell compiler we are nodes + NSXMLNode * attribute1 = (NSXMLNode *)obj1; + NSXMLNode * attribute2 = (NSXMLNode *)obj2; + + // base index + float aIndex = count; + float bIndex = count; + + // loop around each order string + for(NSInteger i = 0; i < order.count; i++) { + if([attribute1.name isEqualToString:order[i]]) { + aIndex = i; + } else if([attribute1.name rangeOfString:[order[i] stringByAppendingString:@"-"]].location == 0) { + aIndex = i + .5; + } + if([attribute2.name isEqualToString:order[i]]) { + bIndex = i; + } else if([attribute2.name rangeOfString:[order[i] stringByAppendingString:@"-"]].location == 0) { + bIndex = i + .5; + } + } + + // return the comparison set + if(aIndex != bIndex) { + if(aIndex > bIndex) { + return NSOrderedDescending; + } else { + return NSOrderedAscending; + } + } + return [attribute1.name compare:attribute2.name]; + }]; + + // remove all attributes + for(NSXMLNode * node in attributes) { + [element removeAttributeForName:node.name]; + } + + // add them back on in order + for(NSXMLNode * attribute in sorted) { + [element addAttribute:attribute]; + } + +} + +@end diff --git a/source/IJSVGExporterPathInstruction.h b/source/IJSVGExporterPathInstruction.h new file mode 100644 index 0000000..e196571 --- /dev/null +++ b/source/IJSVGExporterPathInstruction.h @@ -0,0 +1,32 @@ +// +// IJSVGExporterPathInstruction.h +// IconJar +// +// Created by Curtis Hard on 08/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import + +@interface IJSVGExporterPathInstruction : NSObject { + +@private + NSInteger _dataCount; + char _instruction; + CGFloat * _data; +} + ++ (NSArray *)instructionsFromPath:(CGPathRef)path; + +- (id)initWithInstruction:(char)instruction + dataCount:(NSInteger)floatCount; + +- (void)setInstruction:(char)newInstruction; +- (char)instruction; +- (CGFloat *)data; +- (NSInteger)dataLength; + ++ (void)convertInstructionsToRelativeCoordinates:(NSArray *)instructions; ++ (NSString *)pathStringFromInstructions:(NSArray *)instructions; + +@end diff --git a/source/IJSVGExporterPathInstruction.m b/source/IJSVGExporterPathInstruction.m new file mode 100644 index 0000000..e4cc8cc --- /dev/null +++ b/source/IJSVGExporterPathInstruction.m @@ -0,0 +1,359 @@ +// +// IJSVGExporterPathInstruction.m +// IconJar +// +// Created by Curtis Hard on 08/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGExporterPathInstruction.h" +#import "IJSVGExporter.h" + +@implementation IJSVGExporterPathInstruction + +- (void)dealloc +{ + if(_data != NULL) { + free(_data); + } + [super dealloc]; +} + +- (id)initWithInstruction:(char)instruction + dataCount:(NSInteger)floatCount +{ + if((self = [super init]) != nil) { + _instruction = instruction; + + // only allocate if not zero + if(floatCount != 0) { + _data = (CGFloat *)calloc(sizeof(CGFloat), floatCount); + } + } + return self; +} + +- (NSInteger)dataLength +{ + return _dataCount; +} + +- (void)setInstruction:(char)newInstruction +{ + _instruction = newInstruction; +} + +- (char)instruction +{ + return _instruction; +} + +- (CGFloat *)data +{ + return _data; +} + ++ (NSString *)pathStringFromInstructions:(NSArray *)instructions +{ + NSMutableArray * pathData = [[[NSMutableArray alloc] init] autorelease]; + for(IJSVGExporterPathInstruction * instruction in instructions) { + CGFloat * data = instruction.data; + NSString * str = nil; + switch(instruction.instruction) { + + // move + case 'M': + case 'm': { + str = [NSString stringWithFormat:@"%c%g,%g", + instruction.instruction,data[0],data[1]]; + [pathData addObject:str]; + break; + } + + // vertical and horizonal line + case 'V': + case 'v': + case 'H': + case 'h': { + str = [NSString stringWithFormat:@"%c%g", + instruction.instruction,data[0]]; + [pathData addObject:str]; + break; + } + + // line + case 'L': + case 'l': { + str = [NSString stringWithFormat:@"%c%g,%g", + instruction.instruction, data[0], data[1]]; + [pathData addObject:str]; + break; + } + + // curve + case 'C': + case 'c': { + str = [NSString stringWithFormat:@"%c%g,%g %g,%g %g,%g", + instruction.instruction, data[0], data[1], + data[2], data[3], data[4], data[5]]; + [pathData addObject:str]; + break; + } + + // quadratic curve + case 'Q': + case 'q': { + str = [NSString stringWithFormat:@"%c%g,%g %g,%g", + instruction.instruction, data[0], data[1], + data[2], data[3]]; + [pathData addObject:str]; + break; + } + + // close path + case 'Z': + case 'z': { + str = [NSString stringWithFormat:@"%c",instruction.instruction]; + [pathData addObject:str]; + } + } + } + return [pathData componentsJoinedByString:@""]; +} + ++ (void)convertInstructionsToRelativeCoordinates:(NSArray *)instructions +{ + CGFloat point[2] = {0,0}; + CGFloat subpathPoint[2] = {0,0}; + + NSInteger index = 0; + for(IJSVGExporterPathInstruction * anInstruction in instructions) { + char instruction = anInstruction.instruction; + CGFloat * data = anInstruction.data; + NSInteger length = anInstruction.dataLength; + + if(data != NULL) { + + // already relative + if(instruction == 'm' || instruction == 'c' || instruction == 's' || + instruction == 'l' || instruction == 'q' || instruction == 't' || + instruction == 'a') { + + point[0] += data[length-2]; + point[1] += data[length-1]; + + if(instruction == 'm') { + subpathPoint[0] = point[0]; + subpathPoint[1] = point[1]; + } + + } else if(instruction == 'h') { + point[0] += data[0]; + } else if(instruction == 'v') { + point[1] += data[0]; + } + + // convert absolute to relative + if(instruction == 'M') { + if(index > 0) { + instruction = 'm'; + } + + data[0] -= point[0]; + data[1] -= point[1]; + + subpathPoint[0] = point[0] += data[0]; + subpathPoint[1] = point[1] += data[1]; + + } else if(instruction == 'L' || instruction == 'T') { + + instruction = tolower(instruction); + + data[0] -= point[0]; + data[1] -= point[1]; + + point[0] += data[0]; + point[1] += data[1]; + + } else if(instruction == 'C') { + + instruction = 'c'; + + data[0] -= point[0]; + data[1] -= point[1]; + data[2] -= point[0]; + data[3] -= point[1]; + data[4] -= point[0]; + data[5] -= point[1]; + + point[0] += data[4]; + point[1] += data[5]; + + } else if(instruction == 'S' || instruction == 'Q') { + + instruction = tolower(instruction); + + data[0] -= point[0]; + data[1] -= point[1]; + data[2] -= point[0]; + data[3] -= point[1]; + + point[0] += data[2]; + point[1] += data[3]; + + } else if(instruction == 'A') { + + instruction = 'a'; + + data[5] -= point[0]; + data[6] -= point[1]; + + point[0] += data[5]; + point[1] += data[6]; + + } else if(instruction == 'H') { + + instruction = 'h'; + + data[0] -= point[0]; + + point[0] += data[0]; + + } else if(instruction == 'V') { + + instruction = 'v'; + + data[0] -= point[1]; + + point[1] += data[0]; + + } + + // reset the instruction + [anInstruction setInstruction:instruction]; + + } else if(instruction == 'Z' || instruction == 'z') { + point[0] = subpathPoint[0]; + point[1] = subpathPoint[1]; + } + + // increment index + index++; + } +} + ++ (NSArray *)instructionsFromPath:(CGPathRef)path +{ + + // keep track of the current point + __block CGPoint currentPoint = CGPointZero; + NSMutableArray * instructions = [[[NSMutableArray alloc] init] autorelease]; + + // create the path callback + IJSVGCGPathHandler callback = ^(const CGPathElement * pathElement) { + IJSVGExporterPathInstruction * instruction = nil; + // work out what to do + switch(pathElement->type) { + + case kCGPathElementMoveToPoint: { + // move to command + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'M' + dataCount:2] autorelease]; + CGPoint point = pathElement->points[0]; + instruction.data[0] = point.x; + instruction.data[1] = point.y; + currentPoint = point; + + [instructions addObject:instruction]; + break; + } + + case kCGPathElementAddLineToPoint: { + // line to command + CGPoint point = pathElement->points[0]; + if(point.x == currentPoint.x) { + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'V' + dataCount:1] autorelease]; + instruction.data[0] = point.y; + } else if(point.y == currentPoint.y) { + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'H' + dataCount:1] autorelease]; + instruction.data[0] = point.x; + } else { + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'L' + dataCount:2] autorelease]; + instruction.data[0] = point.x; + instruction.data[1] = point.y; + } + currentPoint = point; + + [instructions addObject:instruction]; + break; + } + + case kCGPathElementAddQuadCurveToPoint: { + // quad curve to command + CGPoint controlPoint = pathElement->points[0]; + CGPoint point = pathElement->points[1]; + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'Q' + dataCount:4] autorelease]; + instruction.data[0] = controlPoint.x; + instruction.data[1] = controlPoint.y; + instruction.data[2] = point.x; + instruction.data[3] = point.y; + currentPoint = point; + + [instructions addObject:instruction]; + break; + } + + case kCGPathElementAddCurveToPoint: { + // curve to command + CGPoint controlPoint1 = pathElement->points[0]; + CGPoint controlPoint2 = pathElement->points[1]; + CGPoint point = pathElement->points[2]; + currentPoint = point; + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'C' + dataCount:6] autorelease]; + instruction.data[0] = controlPoint1.x; + instruction.data[1] = controlPoint1.y; + instruction.data[2] = controlPoint2.x; + instruction.data[3] = controlPoint2.y; + instruction.data[4] = point.x; + instruction.data[5] = point.y; + + [instructions addObject:instruction]; + break; + } + + case kCGPathElementCloseSubpath: { + // close command + instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'Z' + dataCount:0] autorelease]; + [instructions addObject:instruction]; + break; + } + } + }; + + // apply the + CGPathApply(path, (__bridge void*)callback, IJSVGExporterPathCaller); + + // remove last instruction if it was Z -> M + IJSVGExporterPathInstruction * lastInstruction = instructions.lastObject; + if(lastInstruction.instruction == 'M' || + lastInstruction.instruction == 'm') { + if(instructions.count >= 2) { + NSInteger index = [instructions indexOfObject:lastInstruction]-1; + IJSVGExporterPathInstruction * prevInstruction = instructions[index]; + if(prevInstruction.instruction == 'z' || + prevInstruction.instruction == 'Z') { + [instructions removeLastObject]; + } + } + } + + return instructions; +} + +@end diff --git a/source/IJSVGGradient.h b/source/IJSVGGradient.h index ae393d8..40854e1 100644 --- a/source/IJSVGGradient.h +++ b/source/IJSVGGradient.h @@ -10,35 +10,22 @@ #import "IJSVGDef.h" #import "IJSVGTransform.h" -@interface IJSVGGradient : IJSVGDef { - - NSGradient * gradient; - CGGradientRef CGGradient; - CGFloat angle; - CGPoint startPoint; - CGPoint endPoint; - - IJSVGUnit x1; - IJSVGUnit x2; - IJSVGUnit y1; - IJSVGUnit y2; - -} +@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, assign ) IJSVGUnit x1; -@property ( nonatomic, assign ) IJSVGUnit x2; -@property ( nonatomic, assign ) IJSVGUnit y1; -@property ( nonatomic, assign ) IJSVGUnit y2; +@property ( nonatomic, retain ) IJSVGUnitLength * x1; +@property ( nonatomic, retain ) IJSVGUnitLength * x2; +@property ( nonatomic, retain ) IJSVGUnitLength * y1; +@property ( nonatomic, retain ) IJSVGUnitLength * y2; + (CGFloat *)computeColorStopsFromString:(NSXMLElement *)element colors:(NSArray **)someColors; - (CGGradientRef)CGGradient; - (void)drawInContextRef:(CGContextRef)ctx - path:(IJSVGPath *)path; + rect:(NSRect)rect; @end diff --git a/source/IJSVGGradient.m b/source/IJSVGGradient.m index 7a17a3a..5693cf6 100644 --- a/source/IJSVGGradient.m +++ b/source/IJSVGGradient.m @@ -16,9 +16,14 @@ - (void)dealloc { + [x1 release], x1 = nil; + [x2 release], x2 = nil; + [y1 release], y1 = nil; + [y2 release], y2 = nil; [gradient release], gradient = nil; - if( CGGradient != nil ) + if( CGGradient != nil ) { CGGradientRelease(CGGradient); + } [super dealloc]; } @@ -45,16 +50,18 @@ { // find the offset CGFloat offset = [[[stop attributeForName:@"offset"] stringValue] floatValue]; - if( offset > 1 ) + if( offset > 1.f ) { offset /= 100.f; + } stopsParams[i++] = offset; // find the stop opacity CGFloat stopOpacity = 1.f; NSXMLNode * stopOpacityAttribute = [stop attributeForName:@"stop-opacity"]; - if( stopOpacityAttribute != nil ) + if( stopOpacityAttribute != nil ) { stopOpacity = [[stopOpacityAttribute stringValue] floatValue]; + } // find the stop color NSColor * stopColor = [IJSVGColor colorFromHEXString:[[stop attributeForName:@"stop-color"] stringValue] @@ -64,32 +71,34 @@ if( stopColor == nil ) { stopColor = [IJSVGColor colorFromPredefinedColorName:[[stop attributeForName:@"stop-color"] stringValue]]; - if( stopColor != nil && stopOpacity != 1.f ) + if( stopColor != nil && stopOpacity != 1.f ) { stopColor = [IJSVGColor changeAlphaOnColor:stopColor to:stopOpacity]; + } } // add it into the array - if( stopColor != nil ) + if( stopColor != nil ) { [(NSMutableArray *)colors addObject:stopColor]; + } NSXMLNode * styleAttribute = [stop attributeForName:@"style"]; if( styleAttribute != nil ) { IJSVGStyle * style = [IJSVGStyle parseStyleString:[styleAttribute stringValue]]; - NSColor * color = [style property:@"stop-color"]; + NSColor * color = [IJSVGColor colorFromString:[style property:@"stop-color"]]; // we have a color! - if( color != nil ) - { + if( color != nil ) { // is there a stop opacity? NSNumber * number = nil; - if( (number = [style property:@"stop-opacity"] ) != nil ) + if( (number = [style property:@"stop-opacity"] ) != nil ) { color = [IJSVGColor changeAlphaOnColor:color to:[number floatValue]]; - else + } else { color = [IJSVGColor changeAlphaOnColor:color to:stopOpacity]; + } [(NSMutableArray *)colors addObject:color]; } } @@ -101,15 +110,15 @@ - (CGGradientRef)CGGradient { // store it in the cache - if(CGGradient != nil) + if(CGGradient != nil) { return CGGradient; + } // actually create the gradient NSInteger num = self.gradient.numberOfColorStops; CGFloat * locations = malloc(sizeof(CGFloat)*num); CFMutableArrayRef colors = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)num, &kCFTypeArrayCallBacks); - for( NSInteger i = 0; i < num; i++ ) - { + for( NSInteger i = 0; i < num; i++ ) { NSColor * color; [self.gradient getColor:&color location:&locations[i] @@ -123,7 +132,7 @@ } - (void)drawInContextRef:(CGContextRef)ctx - path:(IJSVGPath *)path + rect:(NSRect)rect { } diff --git a/source/IJSVGGradientLayer.h b/source/IJSVGGradientLayer.h new file mode 100644 index 0000000..f8bab11 --- /dev/null +++ b/source/IJSVGGradientLayer.h @@ -0,0 +1,20 @@ +// +// IJSVGGradientLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 29/12/2016. +// Copyright © 2016 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGGradient.h" +#import "IJSVGPath.h" +#import "IJSVGLayer.h" + +@interface IJSVGGradientLayer : IJSVGLayer { + +} + +@property (nonatomic, retain) IJSVGGradient * gradient; + +@end diff --git a/source/IJSVGGradientLayer.m b/source/IJSVGGradientLayer.m new file mode 100644 index 0000000..4c09924 --- /dev/null +++ b/source/IJSVGGradientLayer.m @@ -0,0 +1,42 @@ +// +// IJSVGGradientLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 29/12/2016. +// Copyright © 2016 Curtis Hard. All rights reserved. +// + +#import "IJSVGGradientLayer.h" + +@implementation IJSVGGradientLayer + +@synthesize gradient; + +- (void)dealloc +{ + [gradient release], gradient = nil; + [super dealloc]; +} + +- (id)init +{ + if((self = [super init]) != nil) { + self.requiresBackingScaleHelp = YES; + } + return self; +} + +- (void)drawInContext:(CGContextRef)ctx +{ + [super drawInContext:ctx]; + + // nothing to do :( + if(self.gradient == nil) { + return; + } + + // draw the gradient + [self.gradient drawInContextRef:ctx rect:self.bounds]; +} + +@end diff --git a/source/IJSVGGroupLayer.h b/source/IJSVGGroupLayer.h new file mode 100644 index 0000000..bfd2350 --- /dev/null +++ b/source/IJSVGGroupLayer.h @@ -0,0 +1,15 @@ +// +// IJSVGGroupLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGLayer.h" +#import "IJSVGShapeLayer.h" + +@interface IJSVGGroupLayer : IJSVGLayer + +@end diff --git a/source/IJSVGGroupLayer.m b/source/IJSVGGroupLayer.m new file mode 100644 index 0000000..3daed89 --- /dev/null +++ b/source/IJSVGGroupLayer.m @@ -0,0 +1,13 @@ +// +// IJSVGGroupLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGGroupLayer.h" + +@implementation IJSVGGroupLayer + +@end diff --git a/source/IJSVGImage.h b/source/IJSVGImage.h index 3435b8f..ea8e529 100644 --- a/source/IJSVGImage.h +++ b/source/IJSVGImage.h @@ -19,6 +19,7 @@ } +- (CGImageRef)CGImage; - (void)drawInContextRef:(CGContextRef)context path:(IJSVGPath *)path; - (void)loadFromBase64EncodedString:(NSString *)encodedString; diff --git a/source/IJSVGImage.m b/source/IJSVGImage.m index 4d4d300..0b7d488 100644 --- a/source/IJSVGImage.m +++ b/source/IJSVGImage.m @@ -40,7 +40,7 @@ if(imagePath == nil) { // lazy load the path as it might not be needed imagePath = [[IJSVGPath alloc] init]; - [imagePath.path appendBezierPathWithRect:NSMakeRect(0.f, 0.f, self.width, self.height)]; + [imagePath.path appendBezierPathWithRect:NSMakeRect(0.f, 0.f, self.width.value, self.height.value)]; [imagePath close]; } return imagePath; @@ -58,7 +58,7 @@ CGImage = nil; } - NSRect rect = NSMakeRect( 0.f, 0.f, self.width, self.height); + NSRect rect = NSMakeRect( 0.f, 0.f, self.width.value, self.height.value); CGImage = [image CGImageForProposedRect:&rect context:nil hints:nil]; @@ -67,12 +67,17 @@ CGImageRetain(CGImage); } +- (CGImageRef)CGImage +{ + return CGImage; +} + - (void)drawInContextRef:(CGContextRef)context path:(IJSVGPath *)path { // run the transforms // draw the image - if(self.width == 0.f || self.height == 0.f) { + if(self.width.value == 0.f || self.height.value == 0.f) { return; } diff --git a/source/IJSVGImageLayer.h b/source/IJSVGImageLayer.h new file mode 100644 index 0000000..b60294f --- /dev/null +++ b/source/IJSVGImageLayer.h @@ -0,0 +1,19 @@ +// +// IJSVGImageLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGLayer.h" + +@interface IJSVGImageLayer : IJSVGLayer { + +} + +- (id)initWithImage:(NSImage *)image; +- (id)initWithCGImage:(CGImageRef)imageRef; + +@end diff --git a/source/IJSVGImageLayer.m b/source/IJSVGImageLayer.m new file mode 100644 index 0000000..9f27da6 --- /dev/null +++ b/source/IJSVGImageLayer.m @@ -0,0 +1,41 @@ +// +// IJSVGImageLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGImageLayer.h" + +@implementation IJSVGImageLayer + +- (id)initWithImage:(NSImage *)image +{ + NSRect rect = (NSRect){ + .origin = NSZeroPoint, + .size = image.size + }; + CGImageRef ref = [image CGImageForProposedRect:&rect + context:nil + hints:nil]; + return [self initWithCGImage:ref]; +} + +- (id)initWithCGImage:(CGImageRef)imageRef +{ + if((self = [super init]) != nil) { + // set the contents + self.contents = (id)imageRef; + + // set the frame, simple stuff + self.frame = (CGRect){ + .origin = CGPointZero, + .size = CGSizeMake( CGImageGetWidth(imageRef), + CGImageGetHeight(imageRef)) + }; + } + return self; +} + +@end diff --git a/source/IJSVGLayer.h b/source/IJSVGLayer.h new file mode 100644 index 0000000..0453359 --- /dev/null +++ b/source/IJSVGLayer.h @@ -0,0 +1,67 @@ +// +// IJSVGLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGTransaction.h" + +@class IJSVGShapeLayer; +@class IJSVGGradientLayer; +@class IJSVGPatternLayer; +@class IJSVGStrokeLayer; + +#define IJSVG_LAYER_ADD_SUBVIEW_DEFAULT_IMPLEMENTATION \ +- (void)addSublayer:(CALayer *)layer { \ + if([layer isKindOfClass:[IJSVGLayer class]] == NO && \ + [layer isKindOfClass:[IJSVGShapeLayer class]] == NO) { \ + NSString * r = [NSString stringWithFormat:@"The layer must be an instance of IJSVGLayer, %@ given.", \ + [layer class]]; \ + NSException * exception = [NSException exceptionWithName:@"IJSVGInvalidSublayerException"\ + reason:r \ + userInfo:nil];\ + @throw exception; \ + }\ + [super addSublayer:layer];\ +} \ +\ +- (void)setBackingScaleFactor:(CGFloat)newFactor \ +{ \ + if(self.backingScaleFactor == newFactor) { \ + return; \ + } \ + backingScaleFactor = newFactor; \ + self.contentsScale = newFactor; \ + self.rasterizationScale = newFactor; \ + [self setNeedsDisplay]; \ +}; + +#define IJSVG_LAYER_DEFAULT_PROPERTIES \ +@property (nonatomic, assign) IJSVGGradientLayer * gradientFillLayer; \ +@property (nonatomic, assign) IJSVGPatternLayer * patternFillLayer; \ +@property (nonatomic, assign) IJSVGStrokeLayer * strokeLayer; \ +@property (nonatomic, assign) BOOL requiresBackingScaleHelp; \ +@property (nonatomic, assign) CGFloat backingScaleFactor; + +#define IJSVG_LAYER_DEFAULT_SYNTHESIZE \ +@synthesize gradientFillLayer; \ +@synthesize patternFillLayer; \ +@synthesize strokeLayer; \ +@synthesize requiresBackingScaleHelp; \ +@synthesize backingScaleFactor; + +#define IJSVG_LAYER_DEFAULT_DEALLOC_INSTRUCTIONS \ +IJSVGBeginTransactionLock(); \ + [super dealloc]; \ +IJSVGEndTransactionLock(); + +@interface IJSVGLayer : CALayer { + +} + +IJSVG_LAYER_DEFAULT_PROPERTIES + +@end diff --git a/source/IJSVGLayer.m b/source/IJSVGLayer.m new file mode 100644 index 0000000..d794599 --- /dev/null +++ b/source/IJSVGLayer.m @@ -0,0 +1,26 @@ +// +// IJSVGLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGLayer.h" +#import "IJSVGShapeLayer.h" + + +@implementation IJSVGLayer + +IJSVG_LAYER_DEFAULT_SYNTHESIZE + +#ifndef __clang_analyzer__ +- (void)dealloc +{ + IJSVG_LAYER_DEFAULT_DEALLOC_INSTRUCTIONS +} +#endif + +IJSVG_LAYER_ADD_SUBVIEW_DEFAULT_IMPLEMENTATION + +@end diff --git a/source/IJSVGLayerTree.h b/source/IJSVGLayerTree.h new file mode 100644 index 0000000..db61987 --- /dev/null +++ b/source/IJSVGLayerTree.h @@ -0,0 +1,23 @@ +// +// IJSVGLayerTree.h +// IJSVGExample +// +// Created by Curtis Hard on 29/12/2016. +// Copyright © 2016 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGNode.h" + +@class IJSVGLayer; + +@interface IJSVGLayerTree : NSObject { + +} + +@property (nonatomic, retain) NSColor * fillColor; +@property (nonatomic, retain) NSColor * strokeColor; + +- (IJSVGLayer *)layerForNode:(IJSVGNode *)node; + +@end diff --git a/source/IJSVGLayerTree.m b/source/IJSVGLayerTree.m new file mode 100644 index 0000000..caf8643 --- /dev/null +++ b/source/IJSVGLayerTree.m @@ -0,0 +1,451 @@ +// +// IJSVGLayerTree.m +// IJSVGExample +// +// Created by Curtis Hard on 29/12/2016. +// Copyright © 2016 Curtis Hard. All rights reserved. +// + +#import "IJSVGLayerTree.h" +#import "IJSVGGroup.h" +#import "IJSVGTransform.h" +#import "IJSVGUtils.h" +#import "IJSVGPath.h" +#import "IJSVGImage.h" +#import "IJSVGGradient.h" +#import "IJSVGGradientLayer.h" +#import "IJSVGPatternLayer.h" +#import "IJSVGPattern.h" +#import "IJSVG.h" +#import "IJSVGText.h" +#import "IJSVGLayer.h" +#import "IJSVGShapeLayer.h" +#import "IJSVGImageLayer.h" +#import "IJSVGGroupLayer.h" +#import "IJSVGStrokeLayer.h" + +@implementation IJSVGLayerTree + +#define DEFAULT_SHAPE_FILL_COLOR [NSColor blackColor].CGColor + +@synthesize fillColor; +@synthesize strokeColor; + +- (void)dealloc +{ + [fillColor release], fillColor = nil; + [strokeColor release], strokeColor = nil; + [super dealloc]; +} + +- (IJSVGLayer *)layerForNode:(IJSVGNode *)node +{ + IJSVGLayer * layer = nil; + + // is there a sub SVG? + if([node isKindOfClass:[IJSVGPath class]]) { + // path + layer = [self layerForPath:(IJSVGPath *)node]; + } else if([node isKindOfClass:[IJSVGGroup class]]) { + // group + layer = [self layerForGroup:(IJSVGGroup *)node]; + } else if([node isKindOfClass:[IJSVGImage class]]) { + // image + layer = [self layerForImage:(IJSVGImage *)node]; + } + + // apply any basic defaults + [self applyDefaultsToLayer:layer + fromNode:node]; + + return [self proposedLayerAfterApplyingTransforms:layer + transforms:node.transforms]; +} + +- (IJSVGLayer *)proposedLayerAfterApplyingTransforms:(IJSVGLayer *)layer + transforms:(NSArray *)transforms +{ + // add any transforms + if(transforms.count != 0) { + 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; + + // 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; + } + + // swap the layer around + [parentLayer addSublayer:layer]; + layer = topLayer; + } + return layer; +} + +- (void)applyDefaultsToLayer:(IJSVGLayer *)layer + fromNode:(IJSVGNode *)node +{ + CGFloat opacity = node.opacity.value; + if(opacity == 0.f) { + opacity = 1.f; + } + layer.opacity = opacity; +} + +- (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)); + return layer; +} + +- (IJSVGLayer *)layerForGroup:(IJSVGGroup *)group +{ + IJSVGGroupLayer * groupLayer = [[[IJSVGGroupLayer alloc] init] autorelease]; + for(IJSVGNode * node in group.children) { + [groupLayer addSublayer:[self layerForNode:node]]; + } + groupLayer.frame = (CGRect){ + .origin = (CGPoint){ + .x = group.x.value, + .y = group.y.value + }, + .size = (CGSize){ + .width = group.width.value, + .height = group.height.value + } + }; + return groupLayer; +} + +- (CGRect)correctedBounds:(CGRect)bounds +{ + if(bounds.origin.x >= INFINITY) { + bounds.origin.x = 0.f; + } + if(bounds.origin.y >= INFINITY) { + bounds.origin.y = 0.f; + } + if(bounds.size.width >= INFINITY) { + bounds.size.width = 0.f; + } + if(bounds.size.height >= INFINITY) { + bounds.size.height = 0.f; + } + return bounds; +} + +- (IJSVGShapeLayer *)basicLayerForPath:(IJSVGPath *)path +{ + // setup path and layer + IJSVGShapeLayer * layer = [[[IJSVGShapeLayer alloc] init] autorelease]; + CGPathRef introPath = [path newPathRefByAutoClosingPath:NO]; + + CGRect bounds = [self correctedBounds:CGPathGetBoundingBox(introPath)]; + + // zero back the path + CGAffineTransform trans = CGAffineTransformMakeTranslation(-bounds.origin.x, + -bounds.origin.y); + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(introPath, &trans); + layer.path = transformedPath; + + // clean up path memory + CGPathRelease(transformedPath); + CGPathRelease(introPath); + + // set the bounds + layer.frame = bounds; + + // basic fill color and rule + layer.fillColor = nil; + layer.fillRule = [self fillRule:path.windingRule]; + return layer; +} + +- (IJSVGShapeLayer *)layerMaskFromLayer:(CAShapeLayer *)layer + fromNode:(IJSVGNode *)node +{ + IJSVGShapeLayer * mask = [[[IJSVGShapeLayer alloc] init] autorelease]; + mask.fillColor = [NSColor blackColor].CGColor; + mask.path = layer.path; + return mask; +} + +- (IJSVGLayer *)layerForPath:(IJSVGPath *)path +{ + // is there a sub SVG? + if(path.svg != nil) { + // grab the sub layer tree from the SVG + return [path.svg layerWithTree:self]; + } + + // garb the basic shape layer + IJSVGShapeLayer * layer = [self basicLayerForPath:path]; + CGRect pathBoundingBox = CGPathGetBoundingBox(layer.path); + + // any gradient? + if(self.fillColor == nil && path.fillGradient != nil) { + + // add the mask + IJSVGShapeLayer * mask = [self layerMaskFromLayer:layer + fromNode:path]; + + // the gradient drawing layer + IJSVGGradientLayer * gradLayer = [[[IJSVGGradientLayer alloc] init] autorelease]; + gradLayer.frame = pathBoundingBox; + gradLayer.gradient = path.fillGradient; + gradLayer.mask = mask; + + // is there a fill opacity? + if(path.fillOpacity.value != 0.f) { + gradLayer.opacity = path.fillOpacity.value; + } + + // display it + [gradLayer setNeedsDisplay]; + + // add the gradient + [layer addSublayer:gradLayer]; + + // assign it + layer.gradientFillLayer = gradLayer; + + if(path.fillGradient.units == IJSVGUnitUserSpaceOnUse) { + // move back if needed + gradLayer.frame = (CGRect){ + .size = gradLayer.frame.size, + .origin = CGPointMake(-(gradLayer.frame.origin.x), + -(gradLayer.frame.origin.y)) + }; + } + + } else if(self.fillColor == nil && path.fillPattern != nil) { + + // create the pattern, this is actually not as easy as it may seem + IJSVGPatternLayer * patternLayer = [[[IJSVGPatternLayer alloc] init] autorelease]; + patternLayer.patternNode = path.fillPattern; + patternLayer.pattern = [self layerForNode:path.fillPattern]; + patternLayer.frame = pathBoundingBox; + + // add the mask + patternLayer.mask = [self layerMaskFromLayer:layer + fromNode:path]; + + // display it + [patternLayer setNeedsDisplay]; + + // add it + [layer addSublayer:patternLayer]; + + // assign it + layer.patternFillLayer = patternLayer; + + } else { + // 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; + if(self.fillColor && (hasFill || hasColor || fColor == nil)) { + fColor = self.fillColor; + } + + // just set the color + layer.fillColor = fColor.CGColor ?: DEFAULT_SHAPE_FILL_COLOR; + } + + // stroke it + if(path.strokeColor != nil) { + + // same as fill, dont use global if the alpha is 0.f + NSColor * sColor = path.strokeColor; + if(self.strokeColor != nil && (sColor != nil && sColor.alphaComponent != 0.f)) { + sColor = self.strokeColor; + } + + // stroke layer + IJSVGStrokeLayer * strokeLayer = [[[IJSVGStrokeLayer alloc] init] autorelease]; + strokeLayer.path = layer.path; + strokeLayer.fillColor = nil; + strokeLayer.strokeColor = sColor.CGColor; + + CGFloat lineWidth = 1.f; + if(path.strokeWidth.value > 0.f) { + lineWidth = path.strokeWidth.value; + } + + // line styles + strokeLayer.lineWidth = lineWidth; + strokeLayer.lineCap = [self lineCap:path.lineCapStyle]; + strokeLayer.lineJoin = [self lineJoin:path.lineJoinStyle]; + strokeLayer.miterLimit = lineWidth; + + CGFloat strokeOpacity = 1.f; + if(path.strokeOpacity.value != 0.f) { + strokeOpacity = path.strokeOpacity.value; + } + strokeLayer.opacity = strokeOpacity; + + // dashing + strokeLayer.lineDashPhase = path.strokeDashOffset.value; + strokeLayer.lineDashPattern = [self lineDashPattern:path]; + + // add the stroke layer + [layer addSublayer:strokeLayer]; + + // add it + layer.strokeLayer = strokeLayer; + } + + // apply masking + [self maskLayer:(IJSVGLayer *)layer + fromNode:path]; + return (IJSVGLayer *)layer; +} + +- (void)maskLayer:(IJSVGLayer *)layer + fromNode:(IJSVGNode *)node +{ + // any clippath? + if(node.clipPath != nil || node.mask != nil) { + IJSVGGroupLayer * maskLayer = [[[IJSVGGroupLayer alloc] init] autorelease]; + + // add clip mask + if(node.clipPath != nil) { + IJSVGLayer * clip = [self layerForNode:node.clipPath]; + + // adjust the frame + [self adjustLayer:clip + toParentLayerFrame:layer]; + + // add the layer + [maskLayer addSublayer:clip]; + } + + // add the actual mask + if(node.mask != nil) { + IJSVGLayer * mask = [self layerForNode:node.mask]; + + // only move if bounding box + if(node.mask.units == IJSVGUnitObjectBoundingBox) { + [self adjustLayer:mask + toParentLayerFrame:layer]; + } + + // add the layer + [maskLayer addSublayer:mask]; + } + + // recursive colourize for each item + [self _recursiveColorLayersFromLayer:maskLayer + withColor:[NSColor whiteColor].CGColor]; + + // add the mask + layer.mask = maskLayer; + } +} + +- (void)_recursiveColorLayersFromLayer:(IJSVGLayer *)layer + withColor:(CGColorRef)color +{ + if([layer isKindOfClass:[IJSVGShapeLayer class]]) { + // has a proper fill method + for(IJSVGLayer * c in layer.sublayers) { + if([c isKindOfClass:[IJSVGGradientLayer class]] || + [c isKindOfClass:[IJSVGPatternLayer class]]) { + return; + } + } + + // set the fill + IJSVGShapeLayer * l = (IJSVGShapeLayer *)layer; + l.fillColor = color; + } else if([layer isKindOfClass:[IJSVGGroupLayer class]]) { + // go through its children, assume its a group + for(IJSVGLayer * child in layer.sublayers) { + [self _recursiveColorLayersFromLayer:child + withColor:color]; + } + } +} + +- (void)adjustLayer:(IJSVGLayer *)childLayer + toParentLayerFrame:(IJSVGLayer *)parent +{ + childLayer.frame = (CGRect){ + .size = childLayer.frame.size, + .origin = CGPointMake((childLayer.frame.origin.x - parent.frame.origin.x), + (childLayer.frame.origin.y - parent.frame.origin.y)) + }; +} + +- (NSArray *)lineDashPattern:(IJSVGNode *)node +{ + NSMutableArray * arr = [[[NSMutableArray alloc] init] autorelease]; + for(NSInteger i = 0; i < node.strokeDashArrayCount; i++) { + [arr addObject:@((CGFloat)node.strokeDashArray[i])]; + } + return [[arr copy] autorelease]; +} + +- (NSString *)lineJoin:(IJSVGLineJoinStyle)joinStyle +{ + switch (joinStyle) { + default: + case IJSVGLineJoinStyleMiter: { + return kCALineJoinMiter; + } + case IJSVGLineJoinStyleBevel: { + return kCALineJoinBevel; + } + case IJSVGLineJoinStyleRound: { + return kCALineJoinRound; + } + } +} + +- (NSString *)lineCap:(IJSVGLineCapStyle)capStyle +{ + switch (capStyle) { + default: + case IJSVGLineCapStyleButt: { + return kCALineCapButt; + } + case IJSVGLineCapStyleRound: { + return kCALineCapRound; + } + case IJSVGLineCapStyleSquare: { + return kCALineCapSquare; + } + } +} + +- (NSString *)fillRule:(IJSVGWindingRule)rule +{ + switch (rule) { + case IJSVGWindingRuleEvenOdd: { + return kCAFillRuleEvenOdd; + } + default: { + return kCAFillRuleNonZero; + } + } +} + +@end diff --git a/source/IJSVGLinearGradient.m b/source/IJSVGLinearGradient.m index cb79f74..83628b1 100644 --- a/source/IJSVGLinearGradient.m +++ b/source/IJSVGLinearGradient.m @@ -17,28 +17,36 @@ endPoint:(CGPoint *)endPoint { - // assume its a vertical / horizonal - aGradient.x1 = IJSVGUnitFromString([[element attributeForName:@"x1"] stringValue]); - aGradient.x2 = IJSVGUnitFromString([[element attributeForName:@"x2"] stringValue]); - aGradient.y1 = IJSVGUnitFromString([[element attributeForName:@"y1"] stringValue]); - aGradient.y2 = IJSVGUnitFromString([[element attributeForName:@"y2"] stringValue]); -// -// *startPoint = CGPointMake(x1, y1); -// *endPoint = CGPointMake(x2, y2); -// -// // horizontal -// if( y1 == y2 && x1 != x2 ) -// aGradient.angle = 0.f; -// -// // vertical -// else if( x1 == x2 && y1 != y2 ) -// aGradient.angle = 270.f; -// -// // angles -// else if( x1 != x2 && y1 != y2 ) -// aGradient.angle = [IJSVGUtils angleBetweenPointA:NSMakePoint( x1, y1 ) -// pointb:NSMakePoint( x2, y2 )]; + CGFloat px1 = [[element attributeForName:@"x1"] stringValue].floatValue; + CGFloat px2 = [[element attributeForName:@"x2"] stringValue].floatValue; + CGFloat py1 = [[element attributeForName:@"y1"] stringValue].floatValue; + CGFloat py2 = [[element attributeForName:@"y2"] stringValue].floatValue; + // 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)) { + isPercent = YES; + } + + // assume its a vertical / horizonal + if(isPercent == NO) { + // just ask unit for the value + aGradient.x1 = [IJSVGUnitLength unitWithString:[[element attributeForName:@"x1"] stringValue] ?: @"0"]; + aGradient.x2 = [IJSVGUnitLength unitWithString:[[element attributeForName:@"x2"] stringValue] ?: @"100"]; + aGradient.y1 = [IJSVGUnitLength unitWithString:[[element attributeForName:@"y1"] stringValue] ?: @"0"]; + aGradient.y2 = [IJSVGUnitLength unitWithString:[[element attributeForName:@"y2"] stringValue] ?: @"0"]; + } else { + // make sure its a percent! + aGradient.x1 = [IJSVGUnitLength unitWithPercentageString:[[element attributeForName:@"x1"] stringValue] ?: @"0"]; + aGradient.x2 = [IJSVGUnitLength unitWithPercentageString:[[element attributeForName:@"x2"] stringValue] ?: @"100"]; + aGradient.y1 = [IJSVGUnitLength unitWithPercentageString:[[element attributeForName:@"y1"] stringValue] ?: @"0"]; + aGradient.y2 = [IJSVGUnitLength unitWithPercentageString:[[element attributeForName:@"y2"] stringValue] ?: @"0"]; + } + // compute the color stops and colours NSArray * colors = nil; CGFloat * stopsParams = [[self class] computeColorStopsFromString:element @@ -54,11 +62,18 @@ } - (void)drawInContextRef:(CGContextRef)ctx - path:(IJSVGPath *)path + rect:(NSRect)rect { // grab the start and end point - CGPoint aStartPoint = CGPointMake(IJSVGFloatFromUnit(x1, path, YES), IJSVGFloatFromUnit(y1, path, NO)); - CGPoint aEndPoint = CGPointMake(IJSVGFloatFromUnit(x2, path, YES), IJSVGFloatFromUnit(y2, path, NO)); + CGPoint aStartPoint = (CGPoint){ + .x = [self.x1 computeValue:rect.size.width], + .y = [self.y1 computeValue:rect.size.height] + }; + + 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]; @@ -70,16 +85,9 @@ aEndPoint = CGPointApplyAffineTransform(aEndPoint, trans); } - // we need to move the context into the path coordinate space - so save the state! - CGContextSaveGState(ctx); - CGContextTranslateCTM(ctx, path.path.bounds.origin.x, path.path.bounds.origin.y); - // draw the gradient CGGradientDrawingOptions opt = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; CGContextDrawLinearGradient(ctx, gRef, aStartPoint, aEndPoint, opt); - - // restore the state - CGContextRestoreGState(ctx); } @end diff --git a/source/IJSVGMath.h b/source/IJSVGMath.h new file mode 100644 index 0000000..b1a5209 --- /dev/null +++ b/source/IJSVGMath.h @@ -0,0 +1,21 @@ +// +// IJSVGMath.h +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import + +@interface IJSVGMath : NSObject + +CGFloat IJSVGMathRad(CGFloat val); +CGFloat IJSVGMathDeg(CGFloat val); +CGFloat IJSVGMathAcos(CGFloat val); +CGFloat IJSVGMathSin(CGFloat val); +CGFloat IJSVGMathAsin(CGFloat val); +CGFloat IJSVGMathTan(CGFloat val); +CGFloat IJSVGMathAtan(CGFloat val); + +@end diff --git a/source/IJSVGMath.m b/source/IJSVGMath.m new file mode 100644 index 0000000..7f4a24c --- /dev/null +++ b/source/IJSVGMath.m @@ -0,0 +1,41 @@ +// +// IJSVGMath.m +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGMath.h" + +@implementation IJSVGMath + +CGFloat IJSVGMathRad(CGFloat val) { + return val * M_PI / 180; +}; + +CGFloat IJSVGMathDeg(CGFloat val) { + return val * 180.f / M_PI; +}; + +CGFloat IJSVGMathAcos(CGFloat val) { + return IJSVGMathDeg(acosf(val)); +}; + +CGFloat IJSVGMathSin(CGFloat val) { + return sinf(IJSVGMathRad(val)); +}; + +CGFloat IJSVGMathAsin(CGFloat val) { + return IJSVGMathDeg(asinf(val)); +}; + +CGFloat IJSVGMathTan(CGFloat val) { + return tanf(IJSVGMathRad(val)); +}; + +CGFloat IJSVGMathAtan(CGFloat val) { + return IJSVGMathDeg(atanf(val)); +}; + +@end diff --git a/source/IJSVGNode.h b/source/IJSVGNode.h index 9203e93..aff2c5f 100644 --- a/source/IJSVGNode.h +++ b/source/IJSVGNode.h @@ -8,6 +8,7 @@ #import #import "IJSVGStyle.h" +#import "IJSVGUnitLength.h" @class IJSVG; @class IJSVGGroup; @@ -16,7 +17,7 @@ @class IJSVGGroup; @class IJSVGPattern; -typedef NS_OPTIONS( NSInteger, IJSVGNodeType ) { +typedef NS_ENUM( NSInteger, IJSVGNodeType ) { IJSVGNodeTypeGroup, IJSVGNodeTypePath, IJSVGNodeTypeDef, @@ -36,76 +37,46 @@ typedef NS_OPTIONS( NSInteger, IJSVGNodeType ) { IJSVGNodeTypeImage, IJSVGNodeTypePattern, IJSVGNodeTypeSVG, + IJSVGNodeTypeText, + IJSVGNodeTypeTextSpan, IJSVGNodeTypeNotFound, }; -typedef NS_OPTIONS( NSInteger, IJSVGWindingRule ) { +typedef NS_ENUM( NSInteger, IJSVGWindingRule ) { IJSVGWindingRuleNonZero, IJSVGWindingRuleEvenOdd, IJSVGWindingRuleInherit }; -typedef NS_OPTIONS( NSInteger, IJSVGLineCapStyle ) { +typedef NS_ENUM( NSInteger, IJSVGLineCapStyle ) { IJSVGLineCapStyleButt, IJSVGLineCapStyleRound, IJSVGLineCapStyleSquare, IJSVGLineCapStyleInherit }; -typedef NS_OPTIONS( NSInteger, IJSVGLineJoinStyle ) { +typedef NS_ENUM( NSInteger, IJSVGLineJoinStyle ) { IJSVGLineJoinStyleMiter, IJSVGLineJoinStyleRound, IJSVGLineJoinStyleBevel, IJSVGLineJoinStyleInherit }; +typedef NS_OPTIONS( NSInteger, IJSVGFontTraits ) { + IJSVGFontTraitNone = 1 << 0, + IJSVGFontTraitBold = 1 << 1, + IJSVGFontTraitItalic = 1 << 2 +}; + +typedef NS_ENUM( NSInteger, IJSVGUnitType) { + IJSVGUnitUserSpaceOnUse, + IJSVGUnitObjectBoundingBox, + IJSVGUnitInherit +}; + static CGFloat IJSVGInheritedFloatValue = -99.9999991; -@interface IJSVGNode : NSObject { - - IJSVGNodeType type; - NSString * name; - NSString * unicode; - NSString * className; - NSArray * classNameList; - - CGFloat x; - CGFloat y; - CGFloat width; - CGFloat height; - - IJSVGGradient * fillGradient; - IJSVGPattern * fillPattern; - - BOOL usesDefaultFillColor; - BOOL shouldRender; - - NSColor * fillColor; - NSColor * strokeColor; - - CGFloat opacity; - CGFloat strokeWidth; - CGFloat fillOpacity; - CGFloat strokeOpacity; - - CGFloat * strokeDashArray; - NSInteger strokeDashArrayCount; - CGFloat strokeDashOffset; - - NSString * identifier; - - IJSVGNode * parentNode; - IJSVGGroup * clipPath; - NSArray * transforms; - - IJSVGWindingRule windingRule; - IJSVGLineCapStyle lineCapStyle; - IJSVGLineJoinStyle joinStyle; - - IJSVGDef * def; - IJSVG * svg; - -} +@interface IJSVGNode : NSObject @property ( nonatomic, assign ) IJSVGNodeType type; @property ( nonatomic, copy ) NSString * name; @@ -114,19 +85,20 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; @property ( nonatomic, copy ) NSString * unicode; @property ( nonatomic, assign ) BOOL shouldRender; @property ( nonatomic, assign ) BOOL usesDefaultFillColor; -@property ( nonatomic, assign ) CGFloat x; -@property ( nonatomic, assign ) CGFloat y; -@property ( nonatomic, assign ) CGFloat width; -@property ( nonatomic, assign ) CGFloat height; -@property ( nonatomic, assign ) CGFloat opacity; -@property ( nonatomic, assign ) CGFloat fillOpacity; -@property ( nonatomic, assign ) CGFloat strokeOpacity; -@property ( nonatomic, assign ) CGFloat strokeWidth; +@property ( nonatomic, retain ) IJSVGUnitLength * x; +@property ( nonatomic, retain ) IJSVGUnitLength * y; +@property ( nonatomic, retain ) IJSVGUnitLength * width; +@property ( nonatomic, retain ) IJSVGUnitLength * height; +@property ( nonatomic, retain ) IJSVGUnitLength * opacity; +@property ( nonatomic, retain ) IJSVGUnitLength * fillOpacity; +@property ( nonatomic, retain ) IJSVGUnitLength * strokeOpacity; +@property ( nonatomic, retain ) IJSVGUnitLength * strokeWidth; @property ( nonatomic, retain ) NSColor * fillColor; @property ( nonatomic, retain ) NSColor * strokeColor; @property ( nonatomic, copy ) NSString * identifier; @property ( nonatomic, assign ) IJSVGNode * parentNode; @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; @@ -136,10 +108,13 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; @property ( nonatomic, retain ) IJSVGPattern * fillPattern; @property ( nonatomic, assign ) CGFloat * strokeDashArray; @property ( nonatomic, assign ) NSInteger strokeDashArrayCount; -@property ( nonatomic, assign ) CGFloat strokeDashOffset; +@property ( nonatomic, retain ) IJSVGUnitLength * strokeDashOffset; @property ( nonatomic, retain ) IJSVG * svg; +@property ( nonatomic, assign ) IJSVGUnitType contentUnits; +@property ( nonatomic, assign ) IJSVGUnitType units; -+ (IJSVGNodeType)typeForString:(NSString *)string; ++ (IJSVGNodeType)typeForString:(NSString *)string + kind:(NSXMLNodeKind)kind; - (void)applyPropertiesFromNode:(IJSVGNode *)node; - (id)initWithDef:(BOOL)flag; diff --git a/source/IJSVGNode.m b/source/IJSVGNode.m index 87c75c2..82b8a82 100644 --- a/source/IJSVGNode.m +++ b/source/IJSVGNode.m @@ -42,10 +42,22 @@ @synthesize strokeDashOffset; @synthesize usesDefaultFillColor; @synthesize svg; +@synthesize mask; +@synthesize units; +@synthesize contentUnits; - (void)dealloc { free(strokeDashArray); + [x release], x = nil; + [y release], y = nil; + [width release], width = nil; + [height release], height = nil; + [opacity release], opacity = nil; + [fillOpacity release], fillOpacity = nil; + [strokeOpacity release], strokeOpacity = nil; + [strokeWidth release], strokeWidth = nil; + [strokeDashOffset release], strokeDashOffset = nil; [unicode release], unicode = nil; [fillGradient release], fillGradient = nil; [transforms release], transforms = nil; @@ -59,10 +71,12 @@ [fillPattern release], fillPattern = nil; [clipPath release], clipPath = nil; [svg release], svg = nil; + [mask release], mask = nil; [super dealloc]; } + (IJSVGNodeType)typeForString:(NSString *)string + kind:(NSXMLNodeKind)kind { string = [string lowercaseString]; if( [string isEqualToString:@"defs"] ) @@ -103,6 +117,11 @@ return IJSVGNodeTypePattern; if([string isEqualToString:@"svg"]) return IJSVGNodeTypeSVG; + if([string isEqualToString:@"text"]) + return IJSVGNodeTypeText; + if([string isEqualToString:@"tspan"] || kind == NSXMLTextKind) { + return IJSVGNodeTypeTextSpan; + } return IJSVGNodeTypeNotFound; } @@ -134,6 +153,9 @@ self.strokeColor = node.strokeColor; self.clipPath = node.clipPath; + self.units = node.units; + self.contentUnits = node.contentUnits; + self.opacity = node.opacity; self.strokeWidth = node.strokeWidth; self.fillOpacity = node.fillOpacity; @@ -170,15 +192,17 @@ { if( ( self = [super init] ) != nil ) { - self.opacity = 0.f; - self.fillOpacity = 1.f; - self.strokeOpacity = 1.f; - self.strokeDashOffset = 0.f; + self.opacity = [IJSVGUnitLength unitWithFloat:0.f]; + self.fillOpacity = [IJSVGUnitLength unitWithFloat:1.f]; + self.strokeOpacity = [IJSVGUnitLength unitWithFloat:1.f]; + self.strokeDashOffset = [IJSVGUnitLength unitWithFloat:0.f]; self.shouldRender = YES; - self.strokeWidth = IJSVGInheritedFloatValue; + self.strokeWidth = [IJSVGUnitLength unitWithFloat:0.f]; self.windingRule = IJSVGWindingRuleInherit; self.lineCapStyle = IJSVGLineCapStyleInherit; self.lineJoinStyle = IJSVGLineJoinStyleInherit; + self.units = IJSVGUnitInherit; + if( flag ) { def = [[IJSVGDef alloc] init]; } @@ -204,8 +228,9 @@ // winding rule can inherit.. - (IJSVGWindingRule)windingRule { - if( windingRule == IJSVGWindingRuleInherit && parentNode != nil ) + if( windingRule == IJSVGWindingRuleInherit && parentNode != nil ) { return parentNode.windingRule; + } return windingRule; } @@ -231,33 +256,32 @@ // these are all recursive, so go up the chain // if they dont exist on this specific node -- (CGFloat)opacity +- (IJSVGUnitLength *)opacity { - if( opacity == IJSVGInheritedFloatValue && parentNode != nil ) + if(opacity.inherit && parentNode != nil) { return parentNode.opacity; - if( opacity != 0.f ) - return opacity; - return 0.f; -} - -- (CGFloat)fillOpacity -{ - if( fillOpacity == IJSVGInheritedFloatValue && parentNode != nil ) - return parentNode.fillOpacity; - if( fillOpacity != 0.f ) - return fillOpacity; - return 0.f; + } + return opacity; } // these are all recursive, so go up the chain // if they dont exist on this specific node -- (CGFloat)strokeWidth +- (IJSVGUnitLength *)fillOpacity { - if( strokeWidth == IJSVGInheritedFloatValue && parentNode != nil ) + if(fillOpacity.inherit && parentNode != nil) { + return parentNode.fillOpacity; + } + return fillOpacity; +} + +// these are all recursive, so go up the chain +// if they dont exist on this specific node +- (IJSVGUnitLength *)strokeWidth +{ + if(strokeWidth.inherit && parentNode != nil) { return parentNode.strokeWidth; - if( strokeWidth != 0.f ) - return strokeWidth; - return 0; + } + return strokeWidth; } // these are all recursive, so go up the chain @@ -271,13 +295,12 @@ return nil; } -- (CGFloat)strokeOpacity +- (IJSVGUnitLength *)strokeOpacity { - if( strokeOpacity == IJSVGInheritedFloatValue && parentNode != nil ) + if(strokeOpacity.inherit && parentNode != nil) { return parentNode.strokeOpacity; - if( strokeOpacity != 0.f ) - return strokeOpacity; - return 0.f; + } + return strokeOpacity; } // even though the spec explicity states fill color @@ -289,6 +312,8 @@ return fillColor; } +// these are all recursive, so go up the chain +// if they dont exist on this specific node - (IJSVGGradient *)fillGradient { if(fillGradient == nil && parentNode != nil) { @@ -297,6 +322,8 @@ return fillGradient; } +// these are all recursive, so go up the chain +// if they dont exist on this specific node - (IJSVGPattern *)fillPattern { if(fillPattern == nil && parentNode != nil) { diff --git a/source/IJSVGParser.h b/source/IJSVGParser.h index e19c9dc..0b0f9cb 100644 --- a/source/IJSVGParser.h +++ b/source/IJSVGParser.h @@ -21,6 +21,48 @@ #import "IJSVGStyleSheet.h" #import "IJSVGPattern.h" #import "IJSVGImage.h" +#import "IJSVGText.h" + +static NSString const * IJSVGAttributeViewBox = @"viewBox"; +static NSString const * IJSVGAttributeID = @"id"; +static NSString const * IJSVGAttributeClass = @"class"; +static NSString const * IJSVGAttributeX = @"x"; +static NSString const * IJSVGAttributeY = @"y"; +static NSString const * IJSVGAttributeWidth = @"width"; +static NSString const * IJSVGAttributeHeight = @"height"; +static NSString const * IJSVGAttributeOpacity = @"opacity"; +static NSString const * IJSVGAttributeStrokeOpacity = @"stroke-opacity"; +static NSString const * IJSVGAttributeStrokeWidth = @"stroke-width"; +static NSString const * IJSVGAttributeStrokeDashOffset = @"stroke-dashoffset"; +static NSString const * IJSVGAttributeFillOpacity = @"fill-opacity"; +static NSString const * IJSVGAttributeClipPath = @"clip-path"; +static NSString const * IJSVGAttributeMask = @"mask"; +static NSString const * IJSVGAttributeGradientUnits = @"gradientUnits"; +static NSString const * IJSVGAttributeMaskUnits = @"maskUnits"; +static NSString const * IJSVGAttributeMaskContentUnits = @"maskContentUnits"; +static NSString const * IJSVGAttributeTransform = @"transform"; +static NSString const * IJSVGAttributeGradientTransform = @"gradientTransform"; +static NSString const * IJSVGAttributeUnicode = @"unicode"; +static NSString const * IJSVGAttributeStrokeLineCap = @"stroke-linecap"; +static NSString const * IJSVGAttributeLineJoin = @"stroke-linejoin"; +static NSString const * IJSVGAttributeStroke = @"stroke"; +static NSString const * IJSVGAttributeStrokeDashArray = @"stroke-dasharray"; +static NSString const * IJSVGAttributeFill = @"fill"; +static NSString const * IJSVGAttributeFillRule = @"fill-rule"; +static NSString const * IJSVGAttributeDisplay = @"display"; +static NSString const * IJSVGAttributeStyle = @"style"; +static NSString const * IJSVGAttributeD = @"d"; +static NSString const * IJSVGAttributeXLink = @"xlink:href"; +static NSString const * IJSVGAttributeX1 = @"x1"; +static NSString const * IJSVGAttributeX2 = @"x2"; +static NSString const * IJSVGAttributeY1 = @"y1"; +static NSString const * IJSVGAttributeY2 = @"y2"; +static NSString const * IJSVGAttributeRX = @"rx"; +static NSString const * IJSVGAttributeRY = @"ry"; +static NSString const * IJSVGAttributeCX = @"cx"; +static NSString const * IJSVGAttributeCY = @"cy"; +static NSString const * IJSVGAttributeR = @"r"; +static NSString const * IJSVGAttributePoints = @"points"; @class IJSVGParser; diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index ddf68f7..9cfd35b 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -180,7 +180,7 @@ // find the sizebox! NSXMLNode * attribute = nil; - if( ( attribute = [svgElement attributeForName:@"viewBox"] ) != nil ) + if( ( attribute = [svgElement attributeForName:(NSString *)IJSVGAttributeViewBox] ) != nil ) { // we have a viewbox... @@ -191,8 +191,8 @@ } else { // there is no view box so find the width and height - CGFloat w = [[[svgElement attributeForName:@"width"] stringValue] floatValue]; - CGFloat h = [[[svgElement attributeForName:@"height"] stringValue] floatValue]; + CGFloat w = [[[svgElement attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] floatValue]; + CGFloat h = [[[svgElement attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] floatValue]; if( h == 0.f && w != 0.f ) h = w; else if( w == 0.f && h != 0.f ) @@ -213,8 +213,8 @@ } // parse the width and height.... - CGFloat w = [[[svgElement attributeForName:@"width"] stringValue] floatValue]; - CGFloat h = [[[svgElement attributeForName:@"height"] stringValue] floatValue]; + CGFloat w = [[[svgElement attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] floatValue]; + CGFloat h = [[[svgElement attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] floatValue]; if( w == 0.f && h == 0.f ) { w = viewBox.size.width; @@ -276,13 +276,6 @@ if(defaults.count != 0) { // we have default, we need to store these per ID and remove them from the array for(NSXMLElement * defs in defaults) { - - // parse the def - this will force - // parse any glyphs inside the fonts - [self _parseBlock:defs - intoGroup:nil - def:NO]; - // store each object for(NSXMLElement * childDef in defs.children) { NSString * defID = [[childDef attributeForName:@"id"] stringValue]; @@ -314,316 +307,171 @@ node:(IJSVGNode *)node { - // ID - NSXMLNode * idAttribute = [element attributeForName:@"id"]; - if( idAttribute != nil ) { - node.identifier = [idAttribute stringValue]; + // first of all, compute a style sheet + IJSVGStyle * sheetStyle = nil; + __block IJSVGStyle * style = nil; + + // attribute helpers + typedef void (^cp)(NSString *); + void (^attr)(const NSString *, cp) = ^(NSString * key, cp block) { + NSString * v = [element attributeForName:key].stringValue + ?: [style property:key]; + if(v != nil && v.length != 0) { + block(v); + } + }; + + typedef id (^cap)(NSString *); + void (^atts)(NSDictionary *, cap) = + ^(NSDictionary* kv, cap block) { + for(NSString * key in kv.allKeys) { + attr(key, ^(NSString * value) { + [node setValue:block(value) + forKey:kv[key]]; + }); + } + }; + + // id, this must be here for the style sheet to actually + // render and parse, basically it relies on ID/CSS selectors so these + // must be in place before its computed + attr(IJSVGAttributeID, ^(NSString * value) { + node.identifier = value; _defNodes[node.identifier] = element; + }); + + // + attr(IJSVGAttributeClass, ^(NSString * value) { + node.className = value; + node.classNameList = [value componentsSeparatedByString:@" "]; + }); + + // work out the style sheet + if(_styleSheet != nil) { + sheetStyle = [_styleSheet styleForNode:node]; } - // unicode - NSXMLNode * unicodeAttribute = [element attributeForName:@"unicode"]; - if( unicodeAttribute != nil ) - { - NSString * str = [unicodeAttribute stringValue]; - node.unicode = [NSString stringWithFormat:@"%04x",[str characterAtIndex:0]]; + // is there a + attr(IJSVGAttributeStyle, ^(NSString * value) { + style = [IJSVGStyle parseStyleString:value]; + }); + + // merge to two together + if(sheetStyle != nil) { + style = [sheetStyle mergedStyle:style]; } - // x and y - NSXMLNode * xAttribute = [element attributeForName:@"x"]; - if( xAttribute != nil ) - node.x = [[xAttribute stringValue] floatValue]; + // floats + atts(@{IJSVGAttributeX:@"x", + IJSVGAttributeY:@"y", + IJSVGAttributeWidth:@"width", + IJSVGAttributeHeight:@"height", + IJSVGAttributeOpacity:@"opacity", + IJSVGAttributeStrokeOpacity:@"strokeOpacity", + IJSVGAttributeStrokeWidth:@"strokeWidth", + IJSVGAttributeStrokeDashArray:@"strokeDashOffset", + IJSVGAttributeFillOpacity:@"fillOpacity"}, ^id (NSString * value) { + return [IJSVGUnitLength unitWithString:value]; + }); - NSXMLNode * yAttribute = [element attributeForName:@"y"]; - if( yAttribute ) - node.y = [[yAttribute stringValue] floatValue]; + // nodes + atts(@{IJSVGAttributeClipPath:@"clipPath", + IJSVGAttributeMask:@"mask"}, ^id (NSString * value) { + NSString * url = [IJSVGUtils defURL:value]; + if(url != nil) { + return [self definedObjectForID:url + node:node + fromGroup:nil]; + } + return nil; + }); - // width - NSXMLNode * widthAttribute = [element attributeForName:@"width"]; - if( widthAttribute != nil ) - { - if( [[widthAttribute stringValue] isEqualToString:@"100%"] ) - node.width = self.viewBox.size.width; - else - node.width = [IJSVGUtils floatValue:[widthAttribute stringValue]]; - } - - // height - NSXMLNode * heightAttribute = [element attributeForName:@"height"]; - if( heightAttribute != nil ) - { - if( [[heightAttribute stringValue] isEqualToString:@"100%"] ) - node.height = self.viewBox.size.height; - else - node.height = [IJSVGUtils floatValue:[heightAttribute stringValue]]; - } - - // any clippath? - NSXMLNode * clipPathAttribute = [element attributeForName:@"clip-path"]; - if( clipPathAttribute != nil ) - { - NSString * clipID = [IJSVGUtils defURL:[clipPathAttribute stringValue]]; - if( clipID ) { - node.clipPath = (IJSVGGroup *)[self definedObjectForID:clipID - node:node - fromGroup:nil]; - } - } - - // any mask? - NSXMLNode * maskAttribute = [element attributeForName:@"mask"]; - if( maskAttribute != nil ) - { - NSString * maskID = [IJSVGUtils defURL:[maskAttribute stringValue]]; - if( maskID ) { - node.clipPath = (IJSVGGroup *)[self definedObjectForID:maskID - node:node - fromGroup:nil]; - } - } + // units + atts(@{IJSVGAttributeGradientUnits:@"units", + IJSVGAttributeMaskUnits:@"units", + IJSVGAttributeMaskContentUnits:@"contentUnits"}, ^id (NSString * value) { + return @([IJSVGUtils unitTypeForString:value]); + }); // transforms - NSXMLNode * transformAttribute = [element attributeForName:@"transform"]; - if( transformAttribute != nil ) - { - NSMutableArray * tran = [[[NSMutableArray alloc] init] autorelease]; - [tran addObjectsFromArray:[IJSVGTransform transformsForString:[transformAttribute stringValue]]]; - if( node.transforms != nil ) - [tran addObjectsFromArray:node.transforms]; - node.transforms = tran; - } + atts(@{IJSVGAttributeTransform:@"transforms", + IJSVGAttributeGradientTransform:@"transforms"}, ^(NSString * value) { + NSMutableArray * tempTransforms = [[[NSMutableArray alloc] init] autorelease]; + [tempTransforms addObjectsFromArray:[IJSVGTransform transformsForString:value]]; + if(node.transforms != nil) { + [tempTransforms addObjectsFromArray:node.transforms]; + } + return tempTransforms; + }); - if( node.type == IJSVGNodeTypeMask ) - return; +#pragma mark attributes that require custom rules + // unicode + attr(IJSVGAttributeUnicode, ^(NSString * value) { + node.unicode = [NSString stringWithFormat:@"%04x",[value characterAtIndex:0]]; + }); -#pragma mark Styles + // linecap + attr(IJSVGAttributeStrokeLineCap, ^(NSString * value) { + node.lineCapStyle = [IJSVGUtils lineCapStyleForString:value]; + }); - // any line cap style? - NSXMLNode * lineCapAttribute = [element attributeForName:@"stroke-linecap"]; - if( lineCapAttribute != nil ) - node.lineCapStyle = [IJSVGUtils lineCapStyleForString:[lineCapAttribute stringValue]]; - - // any line join style? - NSXMLNode * lineJoinAttribute = [element attributeForName:@"stroke-linejoin"]; - if( lineJoinAttribute != nil ) - node.lineJoinStyle = [IJSVGUtils lineJoinStyleForString:[lineCapAttribute stringValue]]; - - // work out any extra attributes - // opacity - NSXMLNode * opacityAttribute = [element attributeForName:@"opacity"]; - if( opacityAttribute != nil ) - node.opacity = [IJSVGUtils floatValue:[opacityAttribute stringValue]]; - - NSXMLNode * strokeOpacityAttribute = [element attributeForName:@"stroke-opacity"]; - if( strokeOpacityAttribute != nil ) - node.strokeOpacity = [IJSVGUtils floatValue:[strokeOpacityAttribute stringValue]]; + // line join + attr(IJSVGAttributeLineJoin, ^(NSString * value) { + node.lineJoinStyle = [IJSVGUtils lineJoinStyleForString:value]; + }); // stroke color - NSXMLNode * strokeAttribute = [element attributeForName:@"stroke"]; - if( strokeAttribute != nil ) - { - node.strokeColor = [IJSVGColor colorFromString:[strokeAttribute stringValue]]; - if( node.strokeOpacity != 1.f ) - node.strokeColor = [IJSVGColor changeAlphaOnColor:node.strokeColor - to:node.strokeOpacity]; - } + attr(IJSVGAttributeStroke, ^(NSString * value) { + node.strokeColor = [IJSVGColor colorFromString:value]; + }); - // dash node - NSXMLNode * dashArrayAttribute = [element attributeForName:@"stroke-dasharray"]; - if( dashArrayAttribute != nil ) - { + // stroke dash array + attr(IJSVGAttributeStrokeDashArray, ^(NSString * value) { NSInteger paramCount = 0; - CGFloat * params = [IJSVGUtils commandParameters:[dashArrayAttribute stringValue] + CGFloat * params = [IJSVGUtils commandParameters:value count:¶mCount]; node.strokeDashArray = params; node.strokeDashArrayCount = paramCount; - } + }); - // dash offset - NSXMLNode * dashOffsetAttribute = [element attributeForName:@"stroke-dashoffset"]; - if( dashOffsetAttribute != nil ) - node.strokeDashOffset = [[dashOffsetAttribute stringValue] floatValue]; - - // fill opacity - NSXMLNode * fillOpacityAttribute = [element attributeForName:@"fill-opacity"]; - if( fillOpacityAttribute != nil ) - node.fillOpacity = [IJSVGUtils floatValue:[fillOpacityAttribute stringValue]]; - - // fill color - NSXMLNode * fillAttribute = [element attributeForName:@"fill"]; - if( fillAttribute != nil ) - { - NSString * defID = [IJSVGUtils defURL:[fillAttribute stringValue]]; - if( defID != nil ) { - + // fill - seems kinda complicated for what it actually is + attr(IJSVGAttributeFill, ^(NSString * value) { + NSString * fillDefID = [IJSVGUtils defURL:value]; + if(fillDefID != nil) { // find the object - id obj = [self definedObjectForID:defID + id obj = [self definedObjectForID:fillDefID node:node fromGroup:nil]; - // fill gradient + // what type is it? if([obj isKindOfClass:[IJSVGGradient class]]) { node.fillGradient = (IJSVGGradient *)obj; } else if([obj isKindOfClass:[IJSVGPattern class]]) { node.fillPattern = (IJSVGPattern *)obj; } - } else { - // change the fill color over if its allowed - node.fillColor = [IJSVGColor colorFromString:[fillAttribute stringValue]]; - if( node.fillOpacity != 1.f ) + // its a color + node.fillColor = [IJSVGColor colorFromString:value]; + if(node.fillOpacity.value != 1.f) { node.fillColor = [IJSVGColor changeAlphaOnColor:node.fillColor - to:node.fillOpacity]; - } - } - - // stroke width - NSXMLNode * strokeWidthAttribute = [element attributeForName:@"stroke-width"]; - if( strokeWidthAttribute != nil ) - node.strokeWidth = [IJSVGUtils floatValue:[strokeWidthAttribute stringValue]]; - - - // gradient transforms - NSXMLNode * gradTransformAttribute = [element attributeForName:@"gradientTransform"]; - if( gradTransformAttribute != nil ) - { - NSMutableArray * tran = [[[NSMutableArray alloc] init] autorelease]; - [tran addObjectsFromArray:[IJSVGTransform transformsForString:[gradTransformAttribute stringValue]]]; - if( node.transforms != nil ) - [tran addObjectsFromArray:node.transforms]; - node.transforms = tran; - } - - // winding rule - NSXMLNode * windingRuleAttribute = [element attributeForName:@"fill-rule"]; - if( windingRuleAttribute != nil ) - { - node.windingRule = [IJSVGUtils windingRuleForString:[windingRuleAttribute stringValue]]; - } else - node.windingRule = IJSVGWindingRuleInherit; - - // display - NSXMLNode * displayAttribute = [element attributeForName:@"display"]; - if( [[[displayAttribute stringValue] lowercaseString] isEqualToString:@"none"] ) - node.shouldRender = NO; - -#pragma mark style sheets - - // now we need to work out if there is any style...apparently this is a thing now, - // people use the style attribute... -_- - // style sheet... - - // work out the class name and list of names - NSString * nClassName = [[element attributeForName:@"class"] stringValue]; - if(nClassName != nil) - { - node.className = nClassName; - node.classNameList = [nClassName componentsSeparatedByString:@" "]; - } - - NSXMLNode * styleNode = [element attributeForName:@"style"]; - IJSVGStyle * sheetStyle = nil; - if(_styleSheet != nil) - sheetStyle = [_styleSheet styleForNode:node]; - - if( styleNode != nil || sheetStyle != nil ) - { - IJSVGStyle * style = nil; - - if(styleNode != nil) - style = [IJSVGStyle parseStyleString:[styleNode stringValue]]; - - if(sheetStyle != nil) - style = [sheetStyle mergedStyle:style]; - - // actual display - NSString * display = nil; - if( ( display = [style property:@"display"] ) != nil ) - { - if( [display isEqualToString:@"none"] ) - node.shouldRender = NO; - } - - // fill color - NSColor * fill = nil; - if( ( fill = [style property:@"fill"] ) != nil ) - { - if( [fill isKindOfClass:[NSString class]] ) - { - NSString * defID = [IJSVGUtils defURL:(NSString *)fill]; - if( defID != nil ) { - IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; - node.fillGradient = grad; - node.transforms = grad.transforms; - } - } else { - if( [IJSVGColor computeColor:fill] != nil ) - node.fillColor = fill; + to:node.fillOpacity.value]; } } - - // fill opacity - NSNumber * num = nil; - if( ( num = [style property:@"fill-opacity"] ) != nil ) - node.fillOpacity = num.floatValue; - - // stroke colour - NSColor * stroke = nil; - if( ( stroke = [style property:@"stroke"] ) != nil ) - { - if( [IJSVGColor computeColor:stroke] ) - node.strokeColor = stroke; + }); + + // fill rule + attr(IJSVGAttributeFillRule, ^(NSString * value) { + node.windingRule = [IJSVGUtils windingRuleForString:value]; + }); + + // display + attr(IJSVGAttributeDisplay, ^(NSString * value) { + if([value.lowercaseString isEqualToString:@"none"]) { + node.shouldRender = NO; } - - // stroke width - if( [style property:@"stroke-width"] != 0 ) - node.strokeWidth = [[style property:@"stroke-width"] floatValue]; - - // line cap style - if( [style property:@"stroke-linecap"] != nil ) - node.lineCapStyle = [IJSVGUtils lineCapStyleForString:[style property:@"stroke-linecap"]]; - - // line join style - if( [style property:@"stroke-linejoin"] != nil ) - node.lineJoinStyle = [IJSVGUtils lineJoinStyleForString:[style property:@"stroke-linejoin"]]; - - // opacity - if( [style property:@"opacity"] != nil ) - node.opacity = [[style property:@"opacity"] floatValue]; - - // stroke opacity - if( [style property:@"stroke-opacity"] != nil ) - node.strokeOpacity = [[style property:@"stroke-opacity"] floatValue]; - - // dash - if( [style property:@"stroke-dasharray"] != nil ) - { - NSInteger paramCount = 0; - CGFloat * params = [IJSVGUtils commandParameters:[style property:@"stroke-dasharray"] - count:¶mCount]; - node.strokeDashArray = params; - node.strokeDashArrayCount = paramCount; - } - - // dash offset - if( [style property:@"stroke-dashoffset"] != nil ) - node.strokeDashOffset = [[style property:@"stroke-dashoffset"] floatValue]; - - // winding rule - if( [style property:@"fill-rule"] != nil ) - node.windingRule = [IJSVGUtils windingRuleForString:[style property:@"fill-rule"]]; - - } + }); -} - -- (void)_cleanupSVGElementFromSpriteSheet:(NSXMLElement *)element -{ - NSArray * attsToRemove = @[@"x",@"y",@"opacity"]; - for(NSString * att in attsToRemove) { - [element removeAttributeForName:att]; - } } - (id)definedObjectForID:(NSString *)anID @@ -687,15 +535,38 @@ node:node]; } +- (void)_setupDefaultsForNode:(IJSVGNode *)node +{ + switch(node.type) { + // mask + case IJSVGNodeTypeMask :{ + node.units = IJSVGUnitObjectBoundingBox; + break; + } + + // gradient + case IJSVGNodeTypeRadialGradient: + case IJSVGNodeTypeLinearGradient: { + node.units = IJSVGUnitObjectBoundingBox; + break; + } + + default: { + } + } +} + - (void)_parseBaseBlock:(NSXMLElement *)element intoGroup:(IJSVGGroup *)parentGroup def:(BOOL)flag node:(IJSVGNode *)currentNode { NSString * subName = element.name; - IJSVGNodeType aType = [IJSVGNode typeForString:subName]; - switch( aType ) - { + NSXMLNodeKind nodeKind = element.kind; + IJSVGNodeType aType = [IJSVGNode typeForString:subName + kind:nodeKind]; + switch( aType ) { + default: case IJSVGNodeTypeDef: case IJSVGNodeTypeNotFound: @@ -710,20 +581,16 @@ path.parentNode = parentGroup; // grab common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; - - // due to this being a sub SVG, we need to remove the - // X and Y so its not parsed and double up when being - // drawn, and any other attributes that will screw up - [self _cleanupSVGElementFromSpriteSheet:element]; // work out the SVG NSError * error = nil; NSString * SVGString = element.XMLString; - IJSVG * anSVG = [[IJSVG alloc] initWithSVGString:SVGString - error:&error - delegate:nil]; + IJSVG * anSVG = [[[IJSVG alloc] initWithSVGString:SVGString + error:&error + delegate:nil] autorelease]; // handle sub SVG if(error == nil && _respondsTo.handleSubSVG == 1) { @@ -748,8 +615,8 @@ case IJSVGNodeTypeGlyph: { // no path data - if( [element attributeForName:@"d"] == nil || - [[element attributeForName:@"d"] stringValue].length == 0 ) { + if( [element attributeForName:(NSString *)IJSVGAttributeD] == nil || + [[element attributeForName:(NSString *)IJSVGAttributeD] stringValue].length == 0 ) { break; } @@ -759,11 +626,12 @@ path.parentNode = parentGroup; // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; // pass the commands for it - [self _parsePathCommandData:[[element attributeForName:@"d"] stringValue] + [self _parsePathCommandData:[[element attributeForName:(NSString *)IJSVGAttributeD] stringValue] intoPath:path]; // check the size... @@ -781,22 +649,19 @@ case IJSVGNodeTypeMask: case IJSVGNodeTypeGroup: { - // skip blank groups - if(aType == IJSVGNodeTypeGroup && element.childCount == 0) { - break; - } - // create a new group IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; group.type = aType; group.name = subName; group.parentNode = parentGroup; - if(!flag) { + // only groups get added to parent, rest is added as a def + if(!flag && aType == IJSVGNodeTypeGroup) { [parentGroup addChild:group]; } // find common attributes + [self _setupDefaultsForNode:group]; [self _parseElementForCommonAttributes:element node:group]; @@ -809,6 +674,7 @@ break; } + // path case IJSVGNodeTypePath: { IJSVGPath * path = [[[IJSVGPath alloc] init] autorelease]; @@ -821,9 +687,10 @@ } // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; - [self _parsePathCommandData:[[element attributeForName:@"d"] stringValue] + [self _parsePathCommandData:[[element attributeForName:(NSString *)IJSVGAttributeD] stringValue] intoPath:path]; [parentGroup addDef:path]; @@ -842,6 +709,7 @@ } // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; [self _parsePolygon:element @@ -857,11 +725,13 @@ path.name = subName; path.parentNode = parentGroup; + if( !flag ) { [parentGroup addChild:path]; } // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; [self _parsePolyline:element @@ -885,6 +755,7 @@ [self _parseRect:element intoPath:path]; + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; [parentGroup addDef:path]; @@ -898,9 +769,11 @@ path.name = subName; path.parentNode = parentGroup; + [parentGroup addChild:path]; // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; [self _parseLine:element @@ -916,11 +789,13 @@ path.name = subName; path.parentNode = parentGroup; + if( !flag ) { [parentGroup addChild:path]; } // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; [self _parseCircle:element @@ -941,6 +816,7 @@ } // find common attributes + [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element node:path]; [self _parseEllipse:element @@ -952,7 +828,7 @@ // use case IJSVGNodeTypeUse: { - NSString * xlink = [[element attributeForName:@"xlink:href"] stringValue]; + NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; IJSVGNode * node = [self definedObjectForID:xlinkID node:nil @@ -974,7 +850,7 @@ // linear gradient case IJSVGNodeTypeLinearGradient: { - NSString * xlink = [[element attributeForName:@"xlink:href"] stringValue]; + NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; IJSVGNode * node = [parentGroup defForID:xlinkID]; if( node != nil ) { @@ -989,6 +865,8 @@ gradient:grad startPoint:&startPoint endPoint:&endPoint]; + + [self _setupDefaultsForNode:grad]; [self _parseElementForCommonAttributes:element node:grad]; grad.startPoint = startPoint; @@ -1006,6 +884,7 @@ startPoint:&startPoint endPoint:&endPoint]; + [self _setupDefaultsForNode:gradient]; [self _parseElementForCommonAttributes:element node:gradient]; gradient.startPoint = startPoint; @@ -1017,7 +896,7 @@ // radial gradient case IJSVGNodeTypeRadialGradient: { - NSString * xlink = [[element attributeForName:@"xlink:href"] stringValue]; + NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; IJSVGNode * node = [parentGroup defForID:xlinkID]; if( node != nil ) @@ -1033,6 +912,8 @@ gradient:grad startPoint:&startPoint endPoint:&endPoint]; + + [self _setupDefaultsForNode:grad]; [self _parseElementForCommonAttributes:element node:grad]; grad.startPoint = startPoint; @@ -1051,6 +932,8 @@ endPoint:&endPoint]; gradient.startPoint = startPoint; gradient.endPoint = endPoint; + + [self _setupDefaultsForNode:gradient]; [self _parseElementForCommonAttributes:element node:gradient]; [parentGroup addDef:gradient]; @@ -1065,6 +948,8 @@ group.name = subName; group.parentNode = parentGroup; + [self _setupDefaultsForNode:group]; + // find common attributes [self _parseElementForCommonAttributes:element node:group]; @@ -1081,6 +966,8 @@ case IJSVGNodeTypePattern: { IJSVGPattern * pattern = [[[IJSVGPattern alloc] init] autorelease]; + [self _setupDefaultsForNode:pattern]; + // find common attributes [self _parseElementForCommonAttributes:element node:pattern]; @@ -1098,12 +985,14 @@ case IJSVGNodeTypeImage: { IJSVGImage * image = [[[IJSVGImage alloc] init] autorelease]; + [self _setupDefaultsForNode:image]; + // find common attributes [self _parseElementForCommonAttributes:element node:image]; // from base64 - [image loadFromBase64EncodedString:[[element attributeForName:@"xlink:href"] stringValue]]; + [image loadFromBase64EncodedString:[[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]]; // add to parent [parentGroup addChild:image]; @@ -1237,10 +1126,10 @@ static NSCharacterSet * _commandCharSet = nil; { // convert a line into a command, // basically MX1 Y1LX2 Y2 - CGFloat x1 = [[[element attributeForName:@"x1"] stringValue] floatValue]; - CGFloat y1 = [[[element attributeForName:@"y1"] stringValue] floatValue]; - CGFloat x2 = [[[element attributeForName:@"x2"] stringValue] floatValue]; - CGFloat y2 = [[[element attributeForName:@"y2"] stringValue] floatValue]; + CGFloat x1 = [[[element attributeForName:(NSString *)IJSVGAttributeX1] stringValue] floatValue]; + CGFloat y1 = [[[element attributeForName:(NSString *)IJSVGAttributeY1] stringValue] floatValue]; + CGFloat x2 = [[[element attributeForName:(NSString *)IJSVGAttributeX2] stringValue] floatValue]; + CGFloat y2 = [[[element attributeForName:(NSString *)IJSVGAttributeY2] stringValue] floatValue]; // use sprintf as its quicker then stringWithFormat... char buffer[50]; @@ -1254,9 +1143,9 @@ static NSCharacterSet * _commandCharSet = nil; - (void)_parseCircle:(NSXMLElement *)element intoPath:(IJSVGPath *)path { - CGFloat cX = [[[element attributeForName:@"cx"] stringValue] floatValue]; - CGFloat cY = [[[element attributeForName:@"cy"] stringValue] floatValue]; - CGFloat r = [[[element attributeForName:@"r"] stringValue] floatValue]; + CGFloat cX = [[[element attributeForName:(NSString *)IJSVGAttributeCX] stringValue] floatValue]; + CGFloat cY = [[[element attributeForName:(NSString *)IJSVGAttributeCY] stringValue] floatValue]; + CGFloat r = [[[element attributeForName:(NSString *)IJSVGAttributeR] stringValue] floatValue]; NSRect rect = NSMakeRect( cX - r, cY - r, r*2, r*2); [path overwritePath:[NSBezierPath bezierPathWithOvalInRect:rect]]; } @@ -1264,10 +1153,10 @@ static NSCharacterSet * _commandCharSet = nil; - (void)_parseEllipse:(NSXMLElement *)element intoPath:(IJSVGPath *)path { - CGFloat cX = [[[element attributeForName:@"cx"] stringValue] floatValue]; - CGFloat cY = [[[element attributeForName:@"cy"] stringValue] floatValue]; - CGFloat rX = [[[element attributeForName:@"rx"] stringValue] floatValue]; - CGFloat rY = [[[element attributeForName:@"ry"] stringValue] floatValue]; + CGFloat cX = [[[element attributeForName:(NSString *)IJSVGAttributeCX] stringValue] floatValue]; + CGFloat cY = [[[element attributeForName:(NSString *)IJSVGAttributeCY] stringValue] floatValue]; + CGFloat rX = [[[element attributeForName:(NSString *)IJSVGAttributeRX] stringValue] floatValue]; + CGFloat rY = [[[element attributeForName:(NSString *)IJSVGAttributeRY] stringValue] floatValue]; NSRect rect = NSMakeRect( cX-rX, cY-rY, rX*2, rY*2); [path overwritePath:[NSBezierPath bezierPathWithOvalInRect:rect]]; } @@ -1292,7 +1181,7 @@ static NSCharacterSet * _commandCharSet = nil; intoPath:(IJSVGPath *)path closePath:(BOOL)closePath { - NSString * points = [[element attributeForName:@"points"] stringValue]; + NSString * points = [[element attributeForName:(NSString *)IJSVGAttributePoints] stringValue]; NSInteger count = 0; CGFloat * params = [IJSVGUtils commandParameters:points count:&count]; @@ -1322,57 +1211,60 @@ static NSCharacterSet * _commandCharSet = nil; intoPath:(IJSVGPath *)path { CGFloat aX, aY, aWidth, aHeight; - if([self namespacedAttribute:@"x" + if([self namespacedAttribute:(NSString *)IJSVGAttributeX element:element] != nil) { // already namespaced, find them - aX = [[self namespacedAttribute:@"x" + aX = [[self namespacedAttribute:(NSString *)IJSVGAttributeX element:element] floatValue]; - aY = [[self namespacedAttribute:@"y" + aY = [[self namespacedAttribute:(NSString *)IJSVGAttributeY element:element] floatValue]; - aWidth = [IJSVGUtils floatValue:[self namespacedAttribute:@"width" element:element] + aWidth = [IJSVGUtils floatValue:[self namespacedAttribute:(NSString *)IJSVGAttributeWidth + element:element] fallBackForPercent:self.viewBox.size.width]; - aHeight = [IJSVGUtils floatValue:[self namespacedAttribute:@"height" element:element] + aHeight = [IJSVGUtils floatValue:[self namespacedAttribute:(NSString *)IJSVGAttributeHeight + element:element] fallBackForPercent:self.viewBox.size.height]; } else { // reassign X - [self applyNamespacedAttribute:@"x" - value:[[element attributeForName:@"x"] stringValue] + [self applyNamespacedAttribute:(NSString *)IJSVGAttributeX + value:[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] element:element]; - aX = [[[element attributeForName:@"x"] stringValue] floatValue]; + aX = [[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] floatValue]; // reassign Y - [self applyNamespacedAttribute:@"y" - value:[[element attributeForName:@"y"] stringValue] + [self applyNamespacedAttribute:(NSString *)IJSVGAttributeY + value:[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] element:element]; - aY = [[[element attributeForName:@"y"] stringValue] floatValue]; + aY = [[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] floatValue]; // reassign width - [self applyNamespacedAttribute:@"width" - value:[[element attributeForName:@"width"] stringValue] + [self applyNamespacedAttribute:(NSString *)IJSVGAttributeWidth + value:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] element:element]; - aWidth = [IJSVGUtils floatValue:[[element attributeForName:@"width"] stringValue] + aWidth = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] fallBackForPercent:self.viewBox.size.width]; // reassign height - [self applyNamespacedAttribute:@"height" - value:[[element attributeForName:@"height"] stringValue] + [self applyNamespacedAttribute:(NSString *)IJSVGAttributeHeight + value:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] element:element]; - aHeight = [IJSVGUtils floatValue:[[element attributeForName:@"height"] stringValue] + 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:@"x"]; - [element removeAttributeForName:@"y"]; - [element removeAttributeForName:@"width"]; - [element removeAttributeForName:@"height"]; + [element removeAttributeForName:(NSString *)IJSVGAttributeX]; + [element removeAttributeForName:(NSString *)IJSVGAttributeY]; + [element removeAttributeForName:(NSString *)IJSVGAttributeWidth]; + [element removeAttributeForName:(NSString *)IJSVGAttributeHeight]; } - CGFloat rX = [[[element attributeForName:@"rx"] stringValue] floatValue]; - CGFloat rY = [[[element attributeForName:@"ry"] stringValue] floatValue]; - if( [element attributeForName:@"ry"] == nil ) + 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 diff --git a/source/IJSVGPath.h b/source/IJSVGPath.h index 702bae5..cbfbe7b 100644 --- a/source/IJSVGPath.h +++ b/source/IJSVGPath.h @@ -28,5 +28,6 @@ - (void)close; - (NSPoint)currentPoint; - (void)overwritePath:(NSBezierPath *)aPath; +- (CGPathRef)newPathRefByAutoClosingPath:(BOOL)autoClose; @end diff --git a/source/IJSVGPath.m b/source/IJSVGPath.m index 50b0cae..2fb0ca6 100644 --- a/source/IJSVGPath.m +++ b/source/IJSVGPath.m @@ -17,8 +17,9 @@ - (void)dealloc { - if(subpath!=nil) + if(subpath!=nil) { [subpath release], subpath = nil; + } [super dealloc]; } @@ -61,4 +62,66 @@ path = subpath; } +- (CGPathRef)newPathRefByAutoClosingPath:(BOOL)autoClose +{ + NSInteger i = 0; + NSInteger numElements = [self.path elementCount]; + NSBezierPath * bezPath = self.path; + + // nothing to return + if(numElements == 0) { + return NULL; + } + + CGMutablePathRef aPath = CGPathCreateMutable(); + + NSPoint points[3]; + BOOL didClosePath = YES; + + for (i = 0; i < numElements; i++) { + switch ([bezPath elementAtIndex:i associatedPoints:points]) + { + + // move + case NSMoveToBezierPathElement: { + CGPathMoveToPoint(aPath, NULL, points[0].x, points[0].y); + break; + } + + // line + case NSLineToBezierPathElement: { + CGPathAddLineToPoint(aPath, NULL, points[0].x, points[0].y); + didClosePath = NO; + break; + } + + // curve + case NSCurveToBezierPathElement: { + CGPathAddCurveToPoint(aPath, NULL, points[0].x, points[0].y, + points[1].x, points[1].y, + points[2].x, points[2].y); + didClosePath = NO; + break; + } + + // close + case NSClosePathBezierPathElement: { + CGPathCloseSubpath(aPath); + didClosePath = YES; + break; + } + } + } + + if (!didClosePath && autoClose) { + CGPathCloseSubpath(aPath); + } + + // create immutable and release + CGPathRef pathToReturn = CGPathCreateCopy(aPath); + CGPathRelease(aPath); + + return pathToReturn; +} + @end diff --git a/source/IJSVGPattern.h b/source/IJSVGPattern.h index b55959c..dca6aeb 100644 --- a/source/IJSVGPattern.h +++ b/source/IJSVGPattern.h @@ -14,7 +14,4 @@ } -- (void)drawInContextRef:(CGContextRef)ctx - path:(IJSVGPath *)path; - @end diff --git a/source/IJSVGPattern.m b/source/IJSVGPattern.m index 1f5273a..6d5420e 100644 --- a/source/IJSVGPattern.m +++ b/source/IJSVGPattern.m @@ -10,14 +10,4 @@ @implementation IJSVGPattern -- (void)drawInContextRef:(CGContextRef)context - path:(IJSVGPath *)path -{ - // currently only support images - for(IJSVGImage * image in self.children) { - [image drawInContextRef:context - path:path]; - } -} - @end diff --git a/source/IJSVGPatternLayer.h b/source/IJSVGPatternLayer.h new file mode 100644 index 0000000..3565fca --- /dev/null +++ b/source/IJSVGPatternLayer.h @@ -0,0 +1,18 @@ +// +// IJSVGPatternLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGLayer.h" +#import "IJSVGPattern.h" + +@interface IJSVGPatternLayer : IJSVGLayer + +@property (nonatomic, retain) IJSVGLayer * pattern; +@property (nonatomic, retain) IJSVGPattern * patternNode; + +@end diff --git a/source/IJSVGPatternLayer.m b/source/IJSVGPatternLayer.m new file mode 100644 index 0000000..c3a5d09 --- /dev/null +++ b/source/IJSVGPatternLayer.m @@ -0,0 +1,65 @@ +// +// IJSVGPatternLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGPatternLayer.h" + +@implementation IJSVGPatternLayer + +@synthesize pattern; +@synthesize patternNode; + +- (void)dealloc +{ + [pattern release], pattern = nil; + [patternNode release], patternNode = nil; + [super dealloc]; +} + +- (id)init +{ + if((self = [super init]) != nil) { + self.requiresBackingScaleHelp = YES; + } + return self; +} + +void IJSVGPatternDrawingCallBack(void * info, CGContextRef ctx) { + // reassign the layer + IJSVGPatternLayer * layer = (IJSVGPatternLayer *)info; + [layer.pattern renderInContext:ctx]; +}; + +- (void)drawInContext:(CGContextRef)ctx +{ + // holder for callback + static const CGPatternCallbacks callbacks = {0, &IJSVGPatternDrawingCallBack, NULL}; + + // create base pattern space + CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL); + CGContextSetFillColorSpace(ctx, patternSpace); + CGColorSpaceRelease(patternSpace); + + // create the pattern + CGRect rect = self.bounds; + CGPatternRef ref = CGPatternCreate( (void *)self, self.bounds, + CGAffineTransformIdentity, + roundf(rect.size.width*self.patternNode.width.value), + roundf(rect.size.height*self.patternNode.height.value), + kCGPatternTilingConstantSpacing, + true, &callbacks); + + // set the pattern then release it + CGFloat alpha = 1.f; + CGContextSetFillPattern(ctx, ref, &alpha); + CGPatternRelease(ref); + + // fill it + CGContextFillRect(ctx, rect); +} + +@end diff --git a/source/IJSVGRadialGradient.h b/source/IJSVGRadialGradient.h index b9613c2..0c1d64c 100644 --- a/source/IJSVGRadialGradient.h +++ b/source/IJSVGRadialGradient.h @@ -9,21 +9,13 @@ #import #import "IJSVGGradient.h" -@interface IJSVGRadialGradient : IJSVGGradient { - - CGFloat cx; - CGFloat cy; - CGFloat fx; - CGFloat fy; - CGFloat radius; - -} +@interface IJSVGRadialGradient : IJSVGGradient -@property ( nonatomic, assign ) CGFloat cx; -@property ( nonatomic, assign ) CGFloat cy; -@property ( nonatomic, assign ) CGFloat fx; -@property ( nonatomic, assign ) CGFloat fy; -@property ( nonatomic, assign ) CGFloat radius; +@property ( nonatomic, retain ) IJSVGUnitLength * cx; +@property ( nonatomic, retain ) IJSVGUnitLength * cy; +@property ( nonatomic, retain ) IJSVGUnitLength * fx; +@property ( nonatomic, retain ) IJSVGUnitLength * fy; +@property ( nonatomic, retain ) IJSVGUnitLength * radius; + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGRadialGradient *)gradient diff --git a/source/IJSVGRadialGradient.m b/source/IJSVGRadialGradient.m index 29ca510..872c909 100644 --- a/source/IJSVGRadialGradient.m +++ b/source/IJSVGRadialGradient.m @@ -16,6 +16,16 @@ @synthesize fy; @synthesize radius; +- (void)dealloc +{ + [cx release], cx = nil; + [cy release], cy = nil; + [fx release], fx = nil; + [fy release], fy = nil; + [radius release], radius = nil; + [super dealloc]; +} + - (id)copyWithZone:(NSZone *)zone { IJSVGRadialGradient * grad = [super copyWithZone:zone]; @@ -36,39 +46,36 @@ endPoint:(CGPoint *)endPoint { // cx defaults to 50% if not specified - if( [element attributeForName:@"cx"] ) - gradient.cx = [[[element attributeForName:@"cx"] stringValue] floatValue]/100; - else - gradient.cx = .5f; + NSDictionary * kv = @{@"cx":@"cx", + @"cy":@"cy", + @"r":@"radius", + @"fx":@"fx"}; - // cy defaults to 50% is not specified - if([element attributeForName:@"cy"]) - gradient.cy = [[[element attributeForName:@"cy"] stringValue] floatValue]/100; - else - gradient.cy = .5f; - - if( [element attributeForName:@"r"] ) - gradient.radius = [[[element attributeForName:@"r"] stringValue] floatValue]/100; - else - gradient.radius = .5f; - - // fx defaults to cx if not specified - if( [element attributeForName:@"fx"] != nil ) - gradient.fx = [[[element attributeForName:@"fx"] stringValue] floatValue]/100; - else - gradient.fx = gradient.cx; + for(NSString * key in kv.allKeys) { + NSString * str = [element attributeForName:key].stringValue; + IJSVGUnitLength * unit = nil; + if(str != nil) { + unit = [IJSVGUnitLength unitWithPercentageString:str]; + } else { + unit = [IJSVGUnitLength unitWithPercentageFloat:50]; + } + [gradient setValue:unit + forKey:kv[key]]; + } // fy defaults to cy if not specified - if( [element attributeForName:@"fy"] != nil ) - gradient.fy = [[[element attributeForName:@"fx"] stringValue] floatValue]/100; - else + NSString * fy = [element attributeForName:@"fy"].stringValue; + if(fy != nil) { + gradient.fy = [IJSVGUnitLength unitWithPercentageString:fy]; + } else { gradient.fy = gradient.cy; + } if( gradient.gradient != nil ) return nil; - *startPoint = CGPointMake(gradient.cx, gradient.cy); - *endPoint = CGPointMake(gradient.fx, gradient.fy); + *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]; @@ -87,14 +94,13 @@ // 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) - { + if( transform.command == IJSVGTransformCommandRotate ) { + switch(index) { case 1: case 2: { - if(value<1.f) - return max*value; + if(value<1.f) { + return (max*value); + } break; } } @@ -104,11 +110,10 @@ } - (void)drawInContextRef:(CGContextRef)ctx - path:(IJSVGPath *)path + rect:(NSRect)rect { - CGRect bounds = path.path.bounds; - for( IJSVGTransform * transform in self.transforms ) - { + CGRect bounds = rect; + for( IJSVGTransform * transform in self.transforms ) { IJSVGTransformParameterModifier modifier = ^CGFloat(NSInteger index, CGFloat value) { return [self _handleTransform:transform bounds:bounds @@ -121,20 +126,28 @@ CGPoint sp = self.startPoint; CGPoint ep = self.endPoint; - if( self.startPoint.x == .5f ) + if( self.startPoint.x == .5f ) { sp.x = bounds.size.width*self.startPoint.x; - if(startPoint.y == .5f) + } + + if(self.startPoint.y == .5f) { sp.y = bounds.size.height*self.startPoint.y; + } - if(self.endPoint.x == .5f) + if(self.endPoint.x == .5f) { ep.x = bounds.size.width*self.endPoint.x; - if(self.endPoint.y == .5f) + } + + if(self.endPoint.y == .5f) { ep.y = bounds.size.height*self.endPoint.y; + } - CGFloat r = self.radius; - if(r == .5f) + 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); diff --git a/source/IJSVGShapeLayer.h b/source/IJSVGShapeLayer.h new file mode 100644 index 0000000..8744e56 --- /dev/null +++ b/source/IJSVGShapeLayer.h @@ -0,0 +1,16 @@ +// +// IJSVGShapeLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import +#import "IJSVGLayer.h" + +@interface IJSVGShapeLayer : CAShapeLayer + +IJSVG_LAYER_DEFAULT_PROPERTIES + +@end diff --git a/source/IJSVGShapeLayer.m b/source/IJSVGShapeLayer.m new file mode 100644 index 0000000..75d723a --- /dev/null +++ b/source/IJSVGShapeLayer.m @@ -0,0 +1,22 @@ +// +// IJSVGShapeLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 07/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGShapeLayer.h" + +@implementation IJSVGShapeLayer + +IJSVG_LAYER_DEFAULT_SYNTHESIZE + +- (void)dealloc +{ + IJSVG_LAYER_DEFAULT_DEALLOC_INSTRUCTIONS +} + +IJSVG_LAYER_ADD_SUBVIEW_DEFAULT_IMPLEMENTATION + +@end diff --git a/source/IJSVGStrokeLayer.h b/source/IJSVGStrokeLayer.h new file mode 100644 index 0000000..8be5292 --- /dev/null +++ b/source/IJSVGStrokeLayer.h @@ -0,0 +1,13 @@ +// +// IJSVGStrokeLayer.h +// IJSVGExample +// +// Created by Curtis Hard on 09/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGShapeLayer.h" + +@interface IJSVGStrokeLayer : IJSVGShapeLayer + +@end diff --git a/source/IJSVGStrokeLayer.m b/source/IJSVGStrokeLayer.m new file mode 100644 index 0000000..53c7010 --- /dev/null +++ b/source/IJSVGStrokeLayer.m @@ -0,0 +1,13 @@ +// +// IJSVGStrokeLayer.m +// IJSVGExample +// +// Created by Curtis Hard on 09/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGStrokeLayer.h" + +@implementation IJSVGStrokeLayer + +@end diff --git a/source/IJSVGStyle.m b/source/IJSVGStyle.m index 48a25dd..1f53e9a 100644 --- a/source/IJSVGStyle.m +++ b/source/IJSVGStyle.m @@ -19,8 +19,7 @@ - (id)init { - if( ( self = [super init] ) != nil ) - { + if( ( self = [super init] ) != nil ) { _dict = [[NSMutableDictionary alloc] init]; } return self; @@ -56,8 +55,7 @@ [_reg enumerateMatchesInString:string options:0 range:NSMakeRange( 0, string.length ) - usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) - { + usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { NSString * key = [string substringWithRange:[result rangeAtIndex:1]]; NSString * value = [string substringWithRange:[result rangeAtIndex:2]]; [[self class] computeStyleProperty:key @@ -83,23 +81,8 @@ { key = [[self class] trimString:key]; value = [[self class] trimString:value]; - id val = nil; - - // is it a color? - NSColor * color = [IJSVGColor colorFromString:value]; - if( color == nil || ![[self allowedColourKeys] containsObject:key] ) - { - // value is numeric, convert to a float - val = value; - if( [[self class] isNumeric:value] ) - val = @([value floatValue]); - } else - val = color; - - // set the value - if( val != nil ) - [style setPropertyValue:val - forProperty:key]; + [style setPropertyValue:value + forProperty:key]; } @@ -111,8 +94,9 @@ - (void)setProperties:(NSDictionary *)properties replaceAll:(BOOL)flag { - if(flag) + if(flag) { [_dict removeAllObjects]; + } [_dict addEntriesFromDictionary:properties]; } diff --git a/source/IJSVGStyleSheet.m b/source/IJSVGStyleSheet.m index 1f064b5..8fe4f5d 100644 --- a/source/IJSVGStyleSheet.m +++ b/source/IJSVGStyleSheet.m @@ -167,7 +167,8 @@ if([rule matchesNode:node selector:&matchedSelector]) { // make a wrapper for the selector with the rule - IJSVGStyleSheetSelectorListItem * listItem = [[[IJSVGStyleSheetSelectorListItem alloc] init] autorelease]; + IJSVGStyleSheetSelectorListItem * listItem = nil; + listItem = [[[IJSVGStyleSheetSelectorListItem alloc] init] autorelease]; listItem.rule = rule; listItem.selector = matchedSelector; @@ -178,7 +179,8 @@ // now we have all the wrappers, we need to sort them // by specificity - NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"selector.specificity" ascending:YES]; + NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"selector.specificity" + ascending:YES]; [matchedRules sortUsingDescriptors:@[sort]]; // combine the rule diff --git a/source/IJSVGStyleSheetSelector.m b/source/IJSVGStyleSheetSelector.m index dcb52c0..608c13a 100644 --- a/source/IJSVGStyleSheetSelector.m +++ b/source/IJSVGStyleSheetSelector.m @@ -26,12 +26,15 @@ BOOL IJSVGStyleSheetIsSiblingCombinator(IJSVGStyleSheetSelectorCombinator combin IJSVGStyleSheetSelectorCombinator IJSVGStyleSheetCombinatorForUnichar(unichar aChar) { - if(aChar == '+') + if(aChar == '+') { return IJSVGStyleSheetSelectorCombinatorNextSibling; - if(aChar == '~') + } + if(aChar == '~') { return IJSVGStyleSheetSelectorCombinatorPrecededSibling; - if(aChar == '>') + } + if(aChar == '>') { return IJSVGStyleSheetSelectorCombinatorDirectDescendant; + } return IJSVGStyleSheetSelectorCombinatorDescendant; }; @@ -41,35 +44,40 @@ IJSVGNode * IJSVGStyleSheetPreviousNode(IJSVGNode * node) if([group isKindOfClass:[IJSVGGroup class]] == NO) return nil; NSInteger currentIndex = [group.children indexOfObject:node]; - if(currentIndex == 0) + if(currentIndex == 0) { return nil; + } return group.children[currentIndex-1]; }; IJSVGNode * IJSVGStyleSheetNextNode(IJSVGNode * node) { IJSVGGroup * group = (IJSVGGroup *)node.parentNode; - if([group isKindOfClass:[IJSVGGroup class]] == NO) + if([group isKindOfClass:[IJSVGGroup class]] == NO) { return nil; + } NSInteger currentIndex = [group.children indexOfObject:node]; - if(currentIndex == group.children.count-1) + if(currentIndex == group.children.count-1) { return nil; + } return group.children[currentIndex+1]; }; IJSVGStyleSheetSelectorRaw * IJSVGStyleSheetPreviousSelector(IJSVGStyleSheetSelectorRaw * aSelector, NSArray * _rawSelectors) { NSInteger index = [_rawSelectors indexOfObject:aSelector]; - if(index == 0) + if(index == 0) { return nil; + } return _rawSelectors[index-1]; }; IJSVGStyleSheetSelectorRaw * IJSVGStyleSheetNextSelector(IJSVGStyleSheetSelectorRaw * aSelector, NSArray * _rawSelectors) { NSInteger index = [_rawSelectors indexOfObject:aSelector]; - if(index == _rawSelectors.count-1) + if(index == _rawSelectors.count-1) { return nil; + } return _rawSelectors[index+1]; }; @@ -82,15 +90,17 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * // check if the classes match the class if(rawSelector.classes.count != 0) { for(NSString * className in rawSelector.classes) { - if([node.classNameList containsObject:className] == NO) + if([node.classNameList containsObject:className] == NO) { return NO; + } } } // check the idenfitier if(rawSelector.identifier != nil && - [rawSelector.identifier isEqualToString:node.identifier] == NO) + [rawSelector.identifier isEqualToString:node.identifier] == NO) { return NO; + } return YES; }; @@ -210,8 +220,9 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * IJSVGGroup * parentNode = (IJSVGGroup *)aNode.parentNode; // no parent, just return - if(parentNode == nil) + if(parentNode == nil) { return NO; + } // really straight forward, just check if the parent // matches the next selector and... contains the node in question @@ -290,8 +301,9 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * unichar ca = [selector characterAtIndex:a]; if([alphaNumeric characterIsMember:ca] == NO || a == length-1) { // if at end, add 1 to a so it gets the last character - if( a == length-1 ) + if( a == length-1 ) { a++; + } [rawSelector addClassName:[selector substringWithRange:NSMakeRange(i, a-i)]]; i = a-1; break; @@ -306,8 +318,9 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * unichar ca = [selector characterAtIndex:a]; if([alphaNumeric characterIsMember:ca] == NO || a == length-1) { // if at end, add 1 to a so it gets the last character - if( a == length-1 ) + if( a == length-1 ) { a++; + } rawSelector.identifier = [selector substringWithRange:NSMakeRange(i, a-i)]; i = a-1; break; @@ -334,8 +347,9 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * } // reset the raw selector - if(!(i == length-1)) + if(!(i == length-1)) { rawSelector = [[[IJSVGStyleSheetSelectorRaw alloc] init] autorelease]; + } } // combinator @@ -362,8 +376,9 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * for( NSUInteger a = i; a < length; a++ ) { unichar ca = [selector characterAtIndex:a]; if([alphaNumeric characterIsMember:ca] == NO || a == length-1) { - if( a == length-1 ) + if( a == length-1 ) { a++; + } rawSelector.tag = [selector substringWithRange:NSMakeRange(i, a-i)]; i = a-1; break; @@ -373,8 +388,9 @@ BOOL IJSVGStyleSheetMatchSelector(IJSVGNode * node, IJSVGStyleSheetSelectorRaw * // add raw selector if( i == length - 1 ) { - if(rawSelector != nil) + if(rawSelector != nil) { [sels addObject:rawSelector]; + } rawSelector = nil; } diff --git a/source/IJSVGText.h b/source/IJSVGText.h new file mode 100644 index 0000000..39ea14a --- /dev/null +++ b/source/IJSVGText.h @@ -0,0 +1,19 @@ +// +// IJSVGText.h +// IJSVGExample +// +// Created by Curtis Hard on 01/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGGroup.h" + +@interface IJSVGText : IJSVGGroup { + + NSString * text; + +} + +@property (nonatomic, copy) NSString * text; + +@end diff --git a/source/IJSVGText.m b/source/IJSVGText.m new file mode 100644 index 0000000..be56aed --- /dev/null +++ b/source/IJSVGText.m @@ -0,0 +1,28 @@ +// +// IJSVGText.m +// IJSVGExample +// +// Created by Curtis Hard on 01/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGText.h" + +@implementation IJSVGText + +@synthesize text; + +- (void)dealloc +{ + [text release], text = nil; + [super dealloc]; +} + +- (IJSVGText *)copyWithZone:(NSZone *)zone +{ + IJSVGText * node = [super copyWithZone:zone]; + node.text = self.text; + return node; +} + +@end diff --git a/source/IJSVGTransaction.h b/source/IJSVGTransaction.h new file mode 100644 index 0000000..8229860 --- /dev/null +++ b/source/IJSVGTransaction.h @@ -0,0 +1,13 @@ +// +// IJSVGTransaction.h +// IconJar +// +// Created by Curtis Hard on 11/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import + +void IJSVGBeginTransactionLock(); +void IJSVGEndTransactionLock(); +void IJSVGObtainTransactionLock(dispatch_block_t block, BOOL renderOnMainThread); diff --git a/source/IJSVGTransaction.m b/source/IJSVGTransaction.m new file mode 100644 index 0000000..2d5d93d --- /dev/null +++ b/source/IJSVGTransaction.m @@ -0,0 +1,31 @@ +// +// IJSVGTransaction.m +// IconJar +// +// Created by Curtis Hard on 11/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGTransaction.h" + +void IJSVGBeginTransactionLock() { + [CATransaction begin]; + [CATransaction lock]; + [CATransaction setDisableActions:YES]; +}; + +void IJSVGEndTransactionLock() { + [CATransaction unlock]; + [CATransaction commit]; +}; + +void IJSVGObtainTransactionLock(dispatch_block_t block, BOOL renderOnMainThread) +{ + IJSVGBeginTransactionLock(); + if(renderOnMainThread) { + dispatch_sync(dispatch_get_main_queue(), block); + } else { + block(); + } + IJSVGEndTransactionLock(); +}; diff --git a/source/IJSVGTransform.h b/source/IJSVGTransform.h index c5b7b03..36af4b4 100644 --- a/source/IJSVGTransform.h +++ b/source/IJSVGTransform.h @@ -16,6 +16,8 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { IJSVGTransformCommandTranslate, IJSVGTransformCommandScale, IJSVGTransformCommandRotate, + IJSVGTransformCommandSkewX, + IJSVGTransformCommandSkewY, IJSVGTransformCommandNotImplemented }; @@ -34,11 +36,11 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { @property ( nonatomic, assign ) NSInteger sort; + (NSArray *)transformsForString:(NSString *)string; -+ (void)performTransform:(IJSVGTransform *)transform - inContext:(CGContextRef)context; + (NSBezierPath *)transformedPath:(IJSVGPath *)path; ++ (NSArray *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform; - (CGAffineTransform)CGAffineTransform; - (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier; +- (CGAffineTransform)stackIdentity:(CGAffineTransform)identity; - (void)recalculateWithBounds:(CGRect)bounds; @end diff --git a/source/IJSVGTransform.m b/source/IJSVGTransform.m index 485e8a8..81497e6 100644 --- a/source/IJSVGTransform.m +++ b/source/IJSVGTransform.m @@ -7,6 +7,7 @@ // #import "IJSVGTransform.h" +#import "IJSVGMath.h" @implementation IJSVGTransform @@ -49,12 +50,17 @@ + (IJSVGTransformCommand)commandForCommandString:(NSString *)str { + str = str.lowercaseString; if( [str isEqualToString:@"matrix"] ) return IJSVGTransformCommandMatrix; if( [str isEqualToString:@"translate"] ) return IJSVGTransformCommandTranslate; if( [str isEqualToString:@"scale"] ) return IJSVGTransformCommandScale; + if( [str isEqualToString:@"skewx"]) + return IJSVGTransformCommandSkewX; + if( [str isEqualToString:@"skewy"]) + return IJSVGTransformCommandSkewY; if( [str isEqualToString:@"rotate"] ) return IJSVGTransformCommandRotate; return IJSVGTransformCommandNotImplemented; @@ -135,6 +141,36 @@ }; break; } + + // skewX + case IJSVGTransformCommandSkewX: { + CGFloat degrees = transform.parameters[0]; + CGFloat radians = degrees * M_PI / 180.f; + at.transformStruct = (NSAffineTransformStruct) { + .m11 = 1.f, + .m12 = 0.f, + .m21 = tan(radians), + .m22 = 1.f, + .tX = 0.f, + .tY = 0.f + }; + break; + } + + // skewX + case IJSVGTransformCommandSkewY: { + CGFloat degrees = transform.parameters[0]; + CGFloat radians = degrees * M_PI / 180.f; + at.transformStruct = (NSAffineTransformStruct) { + .m11 = 1.f, + .m12 = tan(radians), + .m21 = 0.f, + .m22 = 1.f, + .tX = 0.f, + .tY = 0.f + }; + break; + } // translate case IJSVGTransformCommandTranslate: { @@ -185,68 +221,79 @@ return cop; } -+ (void)performTransform:(IJSVGTransform *)transform - inContext:(CGContextRef)context +- (CGAffineTransform)CGAffineTransform { - switch( transform.command ) - { - // matrix - case IJSVGTransformCommandMatrix: { - CGContextConcatCTM( context, CGAffineTransformMake( transform.parameters[0], - transform.parameters[1], - transform.parameters[2], - transform.parameters[3], - transform.parameters[4], - transform.parameters[5])); - break; - } + return [self CGAffineTransformWithModifier:nil]; +} + +- (CGAffineTransform)stackIdentity:(CGAffineTransform)identity +{ + switch(self.command) { // translate case IJSVGTransformCommandTranslate: { - if( transform.parameterCount == 1 ) - CGContextTranslateCTM( context, transform.parameters[0], 0 ); - else - CGContextTranslateCTM( context, transform.parameters[0], transform.parameters[1]); - break; + if(self.parameterCount == 1) { + return CGAffineTransformTranslate(identity, self.parameters[0], 0.f); + } + return CGAffineTransformTranslate(identity, self.parameters[0], self.parameters[1]); + } + + // rotate + case IJSVGTransformCommandRotate: { + if(self.parameterCount == 1) { + return CGAffineTransformRotate(identity, (self.parameters[0]/180) * M_PI); + } + CGFloat p0 = self.parameters[0]; + CGFloat p1 = self.parameters[1]; + CGFloat p2 = self.parameters[2]; + CGFloat angle = p0*(M_PI/180.f); + + identity = CGAffineTransformTranslate(identity, p1, p2); + identity = CGAffineTransformRotate(identity, angle); + return CGAffineTransformTranslate(identity, -1.f*p1, -1.f*p2); } // scale case IJSVGTransformCommandScale: { - if( transform.parameterCount == 1 ) - CGContextScaleCTM( context, transform.parameters[0], transform.parameters[0] ); - else - CGContextScaleCTM( context, transform.parameters[0], transform.parameters[1] ); - break; - } - - // rotate - case IJSVGTransformCommandRotate: { - // these are in radians, not degrees - if( transform.parameterCount == 1 ) - { - CGContextRotateCTM( context, (transform.parameters[0] / 180) * M_PI); - } else { - // need support for rotate around a point - CGFloat centerX = transform.parameters[1]; - CGFloat centerY = transform.parameters[2]; - CGFloat angle = transform.parameters[0]*(M_PI/180.f); - CGContextTranslateCTM(context, centerX, centerY); - CGContextRotateCTM(context, angle); - CGContextTranslateCTM(context, -1.f*centerX, -1.f*centerY); + CGFloat p0 = self.parameters[0]; + CGFloat p1 = self.parameters[1]; + if(self.parameterCount == 1) { + return CGAffineTransformScale(identity, p0, p0); } + return CGAffineTransformScale(identity, p0, p1); + } + + // matrix + case IJSVGTransformCommandMatrix: { + CGFloat p0 = self.parameters[0]; + CGFloat p1 = self.parameters[1]; + CGFloat p2 = self.parameters[2]; + CGFloat p3 = self.parameters[3]; + CGFloat p4 = self.parameters[4]; + CGFloat p5 = self.parameters[5]; + return CGAffineTransformMake(p0, p1, p2, p3, p4, p5); + } + + // skewX + case IJSVGTransformCommandSkewX: { + CGFloat degrees = self.parameters[0]; + CGFloat radians = degrees * M_PI / 180.f; + return CGAffineTransformMake( 1.f, 0.f, tan(radians), 1.f, 0.f, 0.f); + } + + // skewY + case IJSVGTransformCommandSkewY: { + CGFloat degrees = self.parameters[0]; + CGFloat radians = degrees * M_PI / 180.f; + return CGAffineTransformMake( 1.f, tan(radians), 0.f, 1.f, 0.f, 0.f); } - // do nothing case IJSVGTransformCommandNotImplemented: { - + return CGAffineTransformIdentity; } - } - -} -- (CGAffineTransform)CGAffineTransform -{ - return [self CGAffineTransformWithModifier:nil]; + } + return CGAffineTransformIdentity; } - (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier @@ -300,6 +347,26 @@ return CGAffineTransformMakeScale( p0, p0); return CGAffineTransformMakeScale( p0, p1); } + + // skewX + case IJSVGTransformCommandSkewX: { + CGFloat degrees = self.parameters[0]; + if(modifier != nil) { + degrees = modifier(0,degrees); + } + CGFloat radians = degrees * M_PI / 180.f; + return CGAffineTransformMake( 1.f, 0.f, tan(radians), 1.f, 0.f, 0.f); + } + + // skewY + case IJSVGTransformCommandSkewY: { + CGFloat degrees = self.parameters[0]; + if(modifier != nil) { + degrees = modifier(0,degrees); + } + CGFloat radians = degrees * M_PI / 180.f; + return CGAffineTransformMake( 1.f, tan(radians), 0.f, 1.f, 0.f, 0.f); + } // rotate case IJSVGTransformCommandRotate: { @@ -333,4 +400,104 @@ return CGAffineTransformIdentity; } +// this is an Object-C version of the matrixToTransform method from SVGO ++ (NSArray *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform +{ + const CGFloat data[6] = { + affineTransform.a, + affineTransform.b, + affineTransform.c, + affineTransform.d, + affineTransform.tx, + affineTransform.ty + }; + + CGFloat sx = sqrtf(data[0]*data[0] + data[1] * data[1]); + CGFloat sy = (data[0]*data[3] - data[1]*data[2])/sx; + CGFloat colSum = data[0]*data[2] + data[1]*data[3]; + CGFloat rowSum = data[0]*data[1] + data[2]*data[3]; + BOOL scaleBefore = rowSum != 0.f || (sx == sy); + + NSMutableArray * trans = [[[NSMutableArray alloc] init] autorelease]; + + // translate + if(data[4] != 0.f || data[5] != 0.f) { + NSString * str = [NSString stringWithFormat:@"translate(%g, %g)",data[4],data[5]]; + [trans addObject:str]; + } + + // skewX + if(data[1] == 0.f && data[2] != 0.f) { + NSString * str = [NSString stringWithFormat:@"skewX(%g)",IJSVGMathAtan(data[2]/sy)]; + [trans addObject:str]; + + // skewY + } else if(data[1] != 0.f && data[2] == 0.f) { + NSString * str = [NSString stringWithFormat:@"skewY(%g)",IJSVGMathAtan(data[1]/data[0])]; + [trans addObject:str]; + sx = data[0]; + sy = data[3]; + } else if(colSum == 0.f || (sx == 1.f && sy == 1.f) || scaleBefore == NO) { + if(scaleBefore == NO) { + sx = (data[0] < 0.f ? -1.f : 1.f * sqrtf(data[0] * data[0] + data[2] * data[2])); + sy = (data[3] < 0.f ? -1.f : 1.f * sqrtf(data[1] * data[1] + data[3] * data[3])); + NSString * str = nil; + if(sx == sy) { + str = [NSString stringWithFormat:@"scale(%g)",sx]; + } else { + str = [NSString stringWithFormat:@"scale(%g, %g)",sx,sy]; + } + [trans addObject:str]; + } + + // rotate + CGFloat rotate = IJSVGMathAcos(data[0]/sx)*(data[1]*sy < 0.f ? -1.f : 1.f); + NSString * rotateString = nil; + if(rotate != 0.f) { + rotateString = [NSString stringWithFormat:@"rotate(%g)",rotate]; + } + + // skewX + if(rowSum != 0.f && colSum != 0.f) { + NSString * str = [NSString stringWithFormat:@"skewX(%g)",IJSVGMathAtan(colSum/(sx * sx))]; + [trans addObject:str]; + } + + // rotate around center + if(rotate != 0.f && (data[4] != 0.f || data[5] != 0.f)) { + [trans removeObjectAtIndex:0]; + + CGFloat cos = data[0]/sx; + CGFloat sin = data[1]/(scaleBefore ? sx : sy); + CGFloat x = data[4] * (scaleBefore ? 1.f : sy); + CGFloat y = data[5] * (scaleBefore ? 1.f : sx); + CGFloat denom = (powf(1.f - cos, 2.f) + powf(sin,2.f)) * (scaleBefore ? 1.f : sx * sy); + + CGFloat r1 = rotate; + CGFloat r2 = ((1.f - cos) * x - sin * y) / denom; + CGFloat r3 = ((1.f - cos) * y + sin * x) / denom; + + rotateString = [NSString stringWithFormat:@"rotate(%g, %g, %g)",r1,r2,r3]; + } + + if(rotateString != nil) { + [trans addObject:rotateString]; + } + } + + // scale + if((scaleBefore && (sx != 1.f || sy != 1.f)) || trans.count == 0.f) { + NSString * str = nil; + if(sx == sy) { + str = [NSString stringWithFormat:@"scale(%g)",sx]; + } else { + str = [NSString stringWithFormat:@"scale(%g, %g)",sx, sy]; + } + [trans addObject:str]; + } + + return trans; +} + + @end diff --git a/source/IJSVGUnitLength.h b/source/IJSVGUnitLength.h new file mode 100644 index 0000000..0d6a4c0 --- /dev/null +++ b/source/IJSVGUnitLength.h @@ -0,0 +1,33 @@ +// +// IJSVGUnitLength.h +// IJSVGExample +// +// Created by Curtis Hard on 13/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, IJSVGUnitLengthType) { + IJSVGUnitLengthTypeNumber, + IJSVGUnitLengthTypePercentage +}; + +@interface IJSVGUnitLength : NSObject + +@property (nonatomic, assign) IJSVGUnitLengthType type; +@property (nonatomic, assign) CGFloat value; +@property (nonatomic, assign) BOOL inherit; + ++ (IJSVGUnitLength *)unitWithFloat:(CGFloat)number; ++ (IJSVGUnitLength *)unitWithFloat:(CGFloat)number + type:(IJSVGUnitLengthType)type; ++ (IJSVGUnitLength *)unitWithPercentageFloat:(CGFloat)number; ++ (IJSVGUnitLength *)unitWithString:(NSString *)string; ++ (IJSVGUnitLength *)unitWithPercentageString:(NSString *)string; + +- (CGFloat)valueAsPercentage; +- (CGFloat)computeValue:(CGFloat)anotherValue; +- (NSString *)stringValue; + +@end diff --git a/source/IJSVGUnitLength.m b/source/IJSVGUnitLength.m new file mode 100644 index 0000000..7843571 --- /dev/null +++ b/source/IJSVGUnitLength.m @@ -0,0 +1,79 @@ +// +// IJSVGUnitLength.m +// IJSVGExample +// +// Created by Curtis Hard on 13/01/2017. +// Copyright © 2017 Curtis Hard. All rights reserved. +// + +#import "IJSVGUnitLength.h" + +@implementation IJSVGUnitLength + +@synthesize value; +@synthesize type; +@synthesize inherit; + ++ (IJSVGUnitLength *)unitWithFloat:(CGFloat)number +{ + IJSVGUnitLength * unit = [[[self alloc] init] autorelease]; + unit.value = number; + unit.type = IJSVGUnitLengthTypeNumber; + return unit; +} + ++ (IJSVGUnitLength *)unitWithFloat:(CGFloat)number + type:(IJSVGUnitLengthType)type +{ + IJSVGUnitLength * unit = [[[self alloc] init] autorelease]; + unit.value = number; + unit.type = type; + return unit; +} + ++ (IJSVGUnitLength *)unitWithPercentageFloat:(CGFloat)number +{ + return [self unitWithFloat:number + type:IJSVGUnitLengthTypePercentage]; +} + ++ (IJSVGUnitLength *)unitWithPercentageString:(NSString *)string +{ + IJSVGUnitLength * unit = [self unitWithString:string]; + unit.type = IJSVGUnitLengthTypePercentage; + return unit; +} + ++ (IJSVGUnitLength *)unitWithString:(NSString *)string +{ + IJSVGUnitLength * unit = [[[self alloc] init] autorelease]; + unit.value = [string floatValue]; + unit.type = IJSVGUnitLengthTypeNumber; + if([string hasSuffix:@"%"] == YES) { + unit.type = IJSVGUnitLengthTypePercentage; + } + return unit; +} + +- (CGFloat)computeValue:(CGFloat)anotherValue +{ + if(self.type == IJSVGUnitLengthTypePercentage) { + return ((anotherValue/100)*self.value); + } + return self.value; +} + +- (CGFloat)valueAsPercentage +{ + return self.value/100; +} + +- (NSString *)stringValue +{ + if(self.type == IJSVGUnitLengthTypePercentage) { + return [NSString stringWithFormat:@"%g%%",self.value]; + } + return [NSString stringWithFormat:@"%g",self.value]; +} + +@end diff --git a/source/IJSVGUnits.h b/source/IJSVGUnits.h deleted file mode 100644 index 33e76dd..0000000 --- a/source/IJSVGUnits.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// IJSVGUnits.h -// IJSVGExample -// -// Created by Curtis Hard on 27/05/2016. -// Copyright © 2016 Curtis Hard. All rights reserved. -// - -#import - -@class IJSVGPath; - -typedef struct { - CGFloat value; - BOOL isPercentage; -} IJSVGUnit; - -IJSVGUnit IJSVGUnitFromString(NSString * string); -CGFloat IJSVGFloatFromUnit(IJSVGUnit unit, IJSVGPath * path, BOOL width); diff --git a/source/IJSVGUnits.m b/source/IJSVGUnits.m deleted file mode 100644 index 144e905..0000000 --- a/source/IJSVGUnits.m +++ /dev/null @@ -1,29 +0,0 @@ -// -// IJSVGUnits.m -// IJSVGExample -// -// Created by Curtis Hard on 27/05/2016. -// Copyright © 2016 Curtis Hard. All rights reserved. -// - -#import "IJSVGUnits.h" -#import "IJSVGPath.h" - -IJSVGUnit IJSVGUnitFromString(NSString * string) -{ - IJSVGUnit unit; - unit.value = [string floatValue]; - if([string hasSuffix:@"%"]) { - unit.isPercentage = YES; - } - return unit; -}; - -CGFloat IJSVGFloatFromUnit(IJSVGUnit unit, IJSVGPath * path, BOOL width) { - NSSize bounds = path.path.bounds.size; - if(unit.isPercentage) { - CGFloat val = width ? bounds.width : bounds.height; - return ((val/100)*unit.value); - } - return unit.value; -}; diff --git a/source/IJSVGUtils.h b/source/IJSVGUtils.h index 29643e7..705e0ec 100644 --- a/source/IJSVGUtils.h +++ b/source/IJSVGUtils.h @@ -8,7 +8,6 @@ #import #import "IJSVGCommand.h" -#import "IJSVGUnits.h" @interface IJSVGUtils : NSObject @@ -25,6 +24,7 @@ CGFloat degrees_to_radians( CGFloat degrees ); + (IJSVGWindingRule)windingRuleForString:(NSString *)string; + (IJSVGLineJoinStyle)lineJoinStyleForString:(NSString *)string; + (IJSVGLineCapStyle)lineCapStyleForString:(NSString *)string; ++ (IJSVGUnitType)unitTypeForString:(NSString *)string; + (void)logParameters:(CGFloat *)param count:(NSInteger)count; + (CGFloat)floatValue:(NSString *)string; @@ -35,5 +35,8 @@ CGFloat degrees_to_radians( CGFloat degrees ); fallBackForPercent:(CGFloat)viewBox; + (CGFloat *)scanFloatsFromString:(NSString *)string size:(NSInteger *)length; ++ (IJSVGFontTraits)fontStyleStringForString:(NSString *)string; ++ (IJSVGFontTraits)fontWeightTraitForString:(NSString *)string + weight:(CGFloat *)weight; @end diff --git a/source/IJSVGUtils.m b/source/IJSVGUtils.m index 02b3ca5..4327a97 100644 --- a/source/IJSVGUtils.m +++ b/source/IJSVGUtils.m @@ -60,6 +60,22 @@ CGFloat degrees_to_radians( CGFloat degrees ) return foundID; } ++ (IJSVGFontTraits)fontWeightTraitForString:(NSString *)string + weight:(CGFloat *)weight +{ + *weight = string.floatValue; + if([string isEqualToString:@"bold"]) + return IJSVGFontTraitBold; + return IJSVGFontTraitNone; +} + ++ (IJSVGFontTraits)fontStyleStringForString:(NSString *)string +{ + if([string isEqualToString:@"italic"]) + return IJSVGFontTraitItalic; + return IJSVGFontTraitNone; +} + + (IJSVGWindingRule)windingRuleForString:(NSString *)string { if( [string isEqualToString:@"evenodd"] ) @@ -95,6 +111,14 @@ CGFloat degrees_to_radians( CGFloat degrees ) return IJSVGLineCapStyleButt; } ++ (IJSVGUnitType)unitTypeForString:(NSString *)string +{ + if([string isEqualToString:@"userSpaceOnUse"]) { + return IJSVGUnitUserSpaceOnUse; + } + return IJSVGUnitObjectBoundingBox; +} + + (CGFloat *)commandParameters:(NSString *)command count:(NSInteger *)count {