From 6d4eb76803a02abc5ef2fe0d96dfaecde5aedc08 Mon Sep 17 00:00:00 2001 From: Curtis Hard Date: Fri, 2 Mar 2018 13:51:49 +0000 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit ffb55b0f31e3162319ff00a5665132943153221e Author: Curtis Hard Date: Fri Mar 2 13:28:40 2018 +0000 Fixes viewBox origin translate commit a4c032afa2504f30e4c8ff8b092482a41e089c92 Author: Curtis Hard Date: Wed Feb 28 21:29:11 2018 +0000 Added clip to viewport commit abe8f4cba5759e9b7c233093c5c7c5d05ab66bfb Author: Curtis Hard Date: Wed Feb 28 21:00:20 2018 +0000 Fixes drawInRect not obeying origin commit a16842271b57aa08aa95d46e3016285a9d1aca26 Author: Curtis Hard Date: Mon Feb 26 13:46:41 2018 +0000 Resolves namespaces correctly + added common HTML list to be parsed as groups commit 58126e06e46c3185e17fe4df2dbe5a68aa18245d Author: Curtis Hard Date: Sun Feb 25 21:58:49 2018 +0000 Fixes! commit 5af5e054abae1dfdb57b39dbef541900cc46a7e2 Author: Curtis Hard Date: Fri Feb 23 22:37:53 2018 +0000 Fixed defNode being removed commit edd3aa1f330feb3601de23c1c13a146a794b7e5e Author: Curtis Hard Date: Fri Feb 23 16:29:54 2018 +0000 excluded various elements from diff commit 3366bc4fa5fec405e1fa1a6302e73f246e435b2a Author: Curtis Hard Date: Fri Feb 23 14:05:03 2018 +0000 Better optimaztion commit 104002183bd031aca1d4416e51c14680303c0c10 Author: Curtis Hard Date: Thu Feb 22 22:17:50 2018 +0000 Added rudimenatry inline styles -> stylesheet commit 7010b7ea50ca92954b0eaa4022053d5256f61752 Author: Curtis Hard Date: Thu Feb 22 13:33:32 2018 +0000 Correct order of cleanup commit 5266a8c07a0f99020a82cddd21bb3a364d3b9a46 Author: Curtis Hard Date: Thu Feb 22 08:22:17 2018 +0000 corrent length of string commit caf55e8bdfef45bc0a4b6a2ffd8128036309e111 Author: Curtis Hard Date: Wed Feb 21 20:45:17 2018 +0000 removes useless def if required commit 8160d05eba6469f9978d7d66381bc252fcb8c45f Author: Curtis Hard Date: Tue Feb 20 14:12:02 2018 +0000 Refactor of a few methods commit a6d6a065218965b73da41e7b31b9372d1d0dd5c4 Author: Curtis Hard Date: Tue Feb 20 09:40:21 2018 +0000 Added collpasing of gradients commit 103a4d71f676ac1c3d2cb92165dff646fc24f172 Author: Curtis Hard Date: Mon Feb 19 19:07:30 2018 +0000 Reduced floats even more commit 98874b1d2c829706149d795ea7586551b7979f8c Author: Curtis Hard Date: Mon Feb 19 18:53:02 2018 +0000 More compression goodness commit e742db31e0d414156371089cc382d787dee6ca40 Author: Curtis Hard Date: Mon Feb 19 14:02:15 2018 +0000 Added intermediateParent commit 198fd09f0721900567c869c1dade6ad32237181c Author: Curtis Hard Date: Mon Feb 19 11:51:37 2018 +0000 Fixes! and performance increases commit 3493194b1bff74d81219611172ab8f34c26f7c88 Author: Curtis Hard Date: Mon Feb 19 08:20:02 2018 +0000 Scale computation commit 0016775eaf442ca8f3544b3ce269a1e881a7e954 Author: Curtis Hard Date: Sun Feb 18 22:33:26 2018 +0000 More goodness commit 304a04cc22de3ac4a928fd938d02c5866093af59 Author: Curtis Hard Date: Sun Feb 18 22:32:04 2018 +0000 Vastly improved the exporter commit e4fd0af5827f5231d40fa83cb7b84bb128448d22 Author: Curtis Hard Date: Sun Feb 18 15:20:28 2018 +0000 This is insanely important! commit 69a2a0c97e256dd41554a6d91db3b030721b0d60 Author: Curtis Hard Date: Sun Feb 4 22:02:19 2018 +0000 Refactor commit 5299bb0479ac2a7ea9815205ee478e9b888cdc99 Author: Curtis Hard Date: Sun Feb 4 21:58:21 2018 +0000 will continue to use CoreAnimation for the time being commit 4f1943cad1cdee846894b1aca2b747bbbb501f4f Author: Curtis Hard Date: Sun Feb 4 21:57:46 2018 +0000 Fixes gradient strokes commit f02d18629372255e1e258ff9924542b35e21f932 Author: Curtis Hard Date: Sun Feb 4 11:15:04 2018 +0000 trying to get masks to work commit 3291718cfbad99b968a3986c506bceac298f918a Author: Curtis Hard Date: Fri Feb 2 22:35:22 2018 +0000 Beginning of quartz renderer commit 6fbaaf5884d3e9fbd15396cb2f8fb0d4533ee96c Author: Curtis Hard Date: Mon Jan 29 22:32:08 2018 +0000 Removed useless log commit abc65797ea0a6a46742bc5bae00237317e38e651 Author: Curtis Hard Date: Mon Jan 29 21:37:45 2018 +0000 I think gradients work :D commit af5a1c2718540c188689a9bb44582187c2371e56 Author: Curtis Hard Date: Sun Jan 28 22:18:02 2018 +0000 Possible fx and fy things… commit fb9a5282b937e4970069ed4dc3c2c523bc20cef1 Author: Curtis Hard Date: Sun Jan 28 19:22:40 2018 +0000 Even more gradient fixes commit d83933a103254262f8b3da779e5395dd309fc000 Author: Curtis Hard Date: Sun Jan 28 14:21:50 2018 +0000 Various improvements commit bd7a0d5021cb8ef2aad2697b6981dc641ba7ae2d Author: Curtis Hard Date: Sat Jan 27 22:26:33 2018 +0000 Start to linear commit a9a038568c0ecbadcf28a097738eef78c9861c99 Author: Curtis Hard Date: Sat Jan 27 21:14:25 2018 +0000 This kind of actually works... commit 77fbb38b6f0c3cf45c72888e9c11e0a04914d119 Author: Curtis Hard Date: Sat Jan 27 20:31:36 2018 +0000 I guess this could be a good start? Posssible start of fixes? commit 12c3191569607a118f4b1cf0306096754f325581 Author: Curtis Hard Date: Sat Jan 27 15:32:15 2018 +0000 Added method for findind absolute position commit e3e9626ef7d9ca27386180a9771717154e8f0cb9 Author: Curtis Hard Date: Sat Jan 27 14:25:03 2018 +0000 Fixes crash due to parentNode on temp groups being released commit 1160d89f16dfc79ce53692df447f3050ccae9792 Author: Curtis Hard Date: Fri Jan 26 22:29:22 2018 +0000 Fixes use statements with transforms commit 1575cbfde8a87216c14a704ceea273d96d104799 Author: Curtis Hard Date: Fri Jan 26 18:16:03 2018 +0000 Moved color tree over to modern syntax commit 5c4c2eee912b8520d23bc2307117ede34cebc484 Author: Curtis Hard Date: Thu Jan 25 18:23:53 2018 +0000 Added HSL/HSLA support commit 087b13e58f7e13941ce31798cceaca0f017b7538 Author: Curtis Hard Date: Wed Jan 24 22:24:43 2018 +0000 Various image loading issues resolved from base64 images commit 1183e167aa6d176445b57a05c994afdc2b730fff Author: Curtis Hard Date: Wed Jan 24 21:21:41 2018 +0000 Rect issue fix commit 4dbfc594373568a5537c2bd7194576bea11988c7 Author: Curtis Hard Date: Wed Jan 24 21:19:24 2018 +0000 Moved transforms over from being concatinated to seperate calls commit 409bd509fa11b6667cf95c28d255a1ab8b79958e Author: Curtis Hard Date: Wed Jan 24 20:30:38 2018 +0000 Fixes color issue commit 8ae1d1b4e05a32fe7006ab2e4a8f20cf55cfd55b Author: Curtis Hard Date: Wed Jan 24 18:41:08 2018 +0000 Removed check as its not needed here commit 7243fbe5fff89efaf053243463881c210e1f3656 Author: Curtis Hard Date: Wed Jan 24 18:35:43 2018 +0000 Added excludeAttributes list to parseCommonAttributes added x and y to that list for rect commit 51b9a5e85f2910a8b0373eacd586cf4afe209d4f Author: Curtis Hard Date: Tue Jan 23 19:53:04 2018 +0000 Added isSubcommand to IJSVGCommand - Fixes move command going awol when preceding move commands are not subcommands (woah) commit 40098589dee5fbfafef8f0ef81cb252ccb4dff0c Author: Curtis Hard Date: Mon Jan 22 22:04:48 2018 +0000 removed reverseObjectEnumerator IJSVGTransform already deals with this at parse stage (was a test from earlier), spec states transforms are applied in reverse order (which parser already dealth with :-)) commit 7cb96b21f280ef2fa1a35246e29157ee26051bfd Author: Curtis Hard Date: Mon Jan 22 22:01:56 2018 +0000 Removed use of origin here as its computed in apply defaults commit 1bff7c697098f419dbeaa2e89be9e0cee3b8f102 Author: Curtis Hard Date: Mon Jan 22 21:48:07 2018 +0000 Various fixes… still going… --- .../IJSVGExample.xcodeproj/project.pbxproj | 44 +- .../UserInterfaceState.xcuserstate | Bin 5491 -> 49919 bytes .../xcschemes/IJSVGExample.xcscheme | 21 +- .../IJSVGExample/AJ_Digital_Camera.svg | 7415 ++++++++++++++++ .../IJSVGExample/Base.lproj/MainMenu.xib | 38 +- IJSVGExample/IJSVGExample/Group.svg | 13 + IJSVGExample/IJSVGExample/NewTux.svg | 72 + IJSVGExample/IJSVGExample/SVGExampleView1.m | 2 +- IJSVGExample/IJSVGExample/SVGView.h | 3 +- IJSVGExample/IJSVGExample/SVGView.m | 14 +- IJSVGExample/IJSVGExample/car.svg | 7415 ++++++++++++++++ IJSVGExample/IJSVGExample/gradients.svg | 20 + IJSVGExample/IJSVGExample/heart.svg | 30 + IJSVGExample/IJSVGExample/intertwingly.svg | 18 + IJSVGExample/IJSVGExample/json.svg | 5 + IJSVGExample/IJSVGExample/radialgradient2.svg | 72 + IJSVGExample/IJSVGExample/test.svg | 7474 ++++++++++++++++- source/IJSVG.h | 10 + source/IJSVG.m | 66 +- source/IJSVGColor.h | 5 +- source/IJSVGColor.m | 369 +- source/IJSVGCommand.h | 2 + source/IJSVGCommand.m | 4 +- source/IJSVGCommandArc.m | 26 +- source/IJSVGCommandCurve.m | 10 +- source/IJSVGCommandLineTo.m | 5 +- source/IJSVGCommandMove.m | 6 +- source/IJSVGExporter.h | 13 +- source/IJSVGExporter.m | 543 +- source/IJSVGGradient.h | 7 +- source/IJSVGGradient.m | 14 +- source/IJSVGGradientLayer.h | 4 + source/IJSVGGradientLayer.m | 12 +- source/IJSVGImage.m | 8 +- source/IJSVGImageLayer.m | 14 +- source/IJSVGLayer.m | 29 +- source/IJSVGLayerTree.m | 213 +- source/IJSVGLinearGradient.h | 4 +- source/IJSVGLinearGradient.m | 77 +- source/IJSVGNode.h | 4 +- source/IJSVGNode.m | 31 +- source/IJSVGParser.h | 1 + source/IJSVGParser.m | 306 +- source/IJSVGQuartzRenderer.h | 24 + source/IJSVGQuartzRenderer.m | 247 + source/IJSVGRadialGradient.h | 4 +- source/IJSVGRadialGradient.m | 169 +- source/IJSVGShapeLayer.h | 1 + source/IJSVGShapeLayer.m | 1 + source/IJSVGTransform.h | 12 + source/IJSVGTransform.m | 67 +- source/IJSVGUtils.h | 6 + source/IJSVGUtils.m | 167 +- 53 files changed, 24225 insertions(+), 912 deletions(-) create mode 100644 IJSVGExample/IJSVGExample/AJ_Digital_Camera.svg create mode 100644 IJSVGExample/IJSVGExample/Group.svg create mode 100644 IJSVGExample/IJSVGExample/NewTux.svg create mode 100644 IJSVGExample/IJSVGExample/car.svg create mode 100644 IJSVGExample/IJSVGExample/gradients.svg create mode 100644 IJSVGExample/IJSVGExample/heart.svg create mode 100644 IJSVGExample/IJSVGExample/intertwingly.svg create mode 100644 IJSVGExample/IJSVGExample/json.svg create mode 100644 IJSVGExample/IJSVGExample/radialgradient2.svg create mode 100644 source/IJSVGQuartzRenderer.h create mode 100644 source/IJSVGQuartzRenderer.m diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj b/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj index 2b708ea..9319c8a 100644 --- a/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj +++ b/IJSVGExample/IJSVGExample.xcodeproj/project.pbxproj @@ -8,6 +8,12 @@ /* Begin PBXBuildFile section */ 343A19181FB2212C000652A2 /* IJSVGGradientUnitLength.m in Sources */ = {isa = PBXBuildFile; fileRef = 343A19171FB2212C000652A2 /* IJSVGGradientUnitLength.m */; }; + 590C87EB201F9888004A1554 /* json.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87EA201F9888004A1554 /* json.svg */; }; + 590C87ED201FA08C004A1554 /* intertwingly.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87EC201FA08C004A1554 /* intertwingly.svg */; }; + 590C87EF201FA093004A1554 /* NewTux.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87EE201FA092004A1554 /* NewTux.svg */; }; + 590C87F1201FBF27004A1554 /* AJ_Digital_Camera.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87F0201FBF27004A1554 /* AJ_Digital_Camera.svg */; }; + 590C87F4201FC9E4004A1554 /* radialgradient2.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87F3201FC9E3004A1554 /* radialgradient2.svg */; }; + 590C87F6201FD0D4004A1554 /* car.svg in Resources */ = {isa = PBXBuildFile; fileRef = 590C87F5201FD0D4004A1554 /* car.svg */; }; 591704CC1E2016E400012644 /* IJSVGExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 591704CB1E2016E400012644 /* IJSVGExporter.m */; }; 591A13E11E19838F001D1629 /* IJSVGText.m in Sources */ = {isa = PBXBuildFile; fileRef = 591A13E01E19838F001D1629 /* IJSVGText.m */; }; 59265CE81C4F840400F333F0 /* css.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59265CE71C4F840400F333F0 /* css.svg */; }; @@ -33,6 +39,8 @@ 5986308A19BA106D00CF15EA /* SVGExampleView5.m in Sources */ = {isa = PBXBuildFile; fileRef = 5986308919BA106D00CF15EA /* SVGExampleView5.m */; }; 5986308C19BA180E00CF15EA /* dashed.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5986308B19BA180E00CF15EA /* dashed.svg */; }; 598759F61E242C850024CC3F /* IJSVGExporterPathInstruction.m in Sources */ = {isa = PBXBuildFile; fileRef = 598759F51E242C850024CC3F /* IJSVGExporterPathInstruction.m */; }; + 5991A2A9201E30E600913E3B /* gradients.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5991A2A8201E30E600913E3B /* gradients.svg */; }; + 5991A2AB201E310200913E3B /* heart.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5991A2AA201E310200913E3B /* heart.svg */; }; 599465DC1C4AA87200A2EEF3 /* IJSVGStyleSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 599465D51C4AA87200A2EEF3 /* IJSVGStyleSheet.m */; }; 599465DD1C4AA87200A2EEF3 /* IJSVGStyleSheetRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 599465D71C4AA87200A2EEF3 /* IJSVGStyleSheetRule.m */; }; 599465DE1C4AA87200A2EEF3 /* IJSVGStyleSheetSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = 599465D91C4AA87200A2EEF3 /* IJSVGStyleSheetSelector.m */; }; @@ -43,6 +51,8 @@ 59A3DA6D1E283022003E59A9 /* IJSVGTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A3DA6C1E283022003E59A9 /* IJSVGTransaction.m */; }; 59B93C6D19B7D1840063E823 /* paperplane.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59B93C6C19B7D1840063E823 /* paperplane.svg */; }; 59B93C6F19B7D32C0063E823 /* products.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59B93C6E19B7D32C0063E823 /* products.svg */; }; + 59D1E39E2022577500C54672 /* IJSVGQuartzRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 59D1E39D2022577500C54672 /* IJSVGQuartzRenderer.m */; }; + 59D1E3A0202279CA00C54672 /* Group.svg in Resources */ = {isa = PBXBuildFile; fileRef = 59D1E39F202279CA00C54672 /* Group.svg */; }; 59E0F5ED1E29964700F757F7 /* IJSVGUnitLength.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */; }; 59E2645119BA240D008A6FDB /* IJSVG.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2641C19BA240D008A6FDB /* IJSVG.m */; }; 59E2645219BA240D008A6FDB /* IJSVGBezierPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 59E2641E19BA240D008A6FDB /* IJSVGBezierPathAdditions.m */; }; @@ -93,6 +103,12 @@ /* Begin PBXFileReference section */ 343A19161FB2212C000652A2 /* IJSVGGradientUnitLength.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGGradientUnitLength.h; sourceTree = ""; }; 343A19171FB2212C000652A2 /* IJSVGGradientUnitLength.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGGradientUnitLength.m; sourceTree = ""; }; + 590C87EA201F9888004A1554 /* json.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = json.svg; sourceTree = ""; }; + 590C87EC201FA08C004A1554 /* intertwingly.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = intertwingly.svg; sourceTree = ""; }; + 590C87EE201FA092004A1554 /* NewTux.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = NewTux.svg; sourceTree = ""; }; + 590C87F0201FBF27004A1554 /* AJ_Digital_Camera.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = AJ_Digital_Camera.svg; sourceTree = ""; }; + 590C87F3201FC9E3004A1554 /* radialgradient2.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = radialgradient2.svg; sourceTree = ""; }; + 590C87F5201FD0D4004A1554 /* car.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = car.svg; sourceTree = ""; }; 591704CA1E2016E400012644 /* IJSVGExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGExporter.h; sourceTree = ""; }; 591704CB1E2016E400012644 /* IJSVGExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGExporter.m; sourceTree = ""; }; 591A13DF1E19838F001D1629 /* IJSVGText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGText.h; sourceTree = ""; }; @@ -139,6 +155,8 @@ 5986308B19BA180E00CF15EA /* dashed.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dashed.svg; sourceTree = ""; }; 598759F41E242C850024CC3F /* IJSVGExporterPathInstruction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGExporterPathInstruction.h; sourceTree = ""; }; 598759F51E242C850024CC3F /* IJSVGExporterPathInstruction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGExporterPathInstruction.m; sourceTree = ""; }; + 5991A2A8201E30E600913E3B /* gradients.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = gradients.svg; sourceTree = ""; }; + 5991A2AA201E310200913E3B /* heart.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = heart.svg; sourceTree = ""; }; 599465D41C4AA87200A2EEF3 /* IJSVGStyleSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGStyleSheet.h; sourceTree = ""; }; 599465D51C4AA87200A2EEF3 /* IJSVGStyleSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGStyleSheet.m; sourceTree = ""; }; 599465D61C4AA87200A2EEF3 /* IJSVGStyleSheetRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGStyleSheetRule.h; sourceTree = ""; }; @@ -157,6 +175,9 @@ 59A3DA6C1E283022003E59A9 /* IJSVGTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGTransaction.m; sourceTree = ""; }; 59B93C6C19B7D1840063E823 /* paperplane.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = paperplane.svg; sourceTree = ""; }; 59B93C6E19B7D32C0063E823 /* products.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = products.svg; sourceTree = ""; }; + 59D1E39C2022577500C54672 /* IJSVGQuartzRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IJSVGQuartzRenderer.h; sourceTree = ""; }; + 59D1E39D2022577500C54672 /* IJSVGQuartzRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IJSVGQuartzRenderer.m; sourceTree = ""; }; + 59D1E39F202279CA00C54672 /* Group.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = Group.svg; sourceTree = ""; }; 59E0F5EB1E29964700F757F7 /* IJSVGUnitLength.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVGUnitLength.h; sourceTree = ""; }; 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IJSVGUnitLength.m; sourceTree = ""; }; 59E2641B19BA240D008A6FDB /* IJSVG.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IJSVG.h; sourceTree = ""; }; @@ -294,6 +315,15 @@ 5956657A19B62F4600D805FF /* Supporting Files */ = { isa = PBXGroup; children = ( + 59D1E39F202279CA00C54672 /* Group.svg */, + 590C87F5201FD0D4004A1554 /* car.svg */, + 590C87F3201FC9E3004A1554 /* radialgradient2.svg */, + 590C87F0201FBF27004A1554 /* AJ_Digital_Camera.svg */, + 590C87EC201FA08C004A1554 /* intertwingly.svg */, + 590C87EE201FA092004A1554 /* NewTux.svg */, + 590C87EA201F9888004A1554 /* json.svg */, + 5991A2AA201E310200913E3B /* heart.svg */, + 5991A2A8201E30E600913E3B /* gradients.svg */, 59265CE71C4F840400F333F0 /* css.svg */, 59459CEB19B906FE00CE493B /* clipped.svg */, 59F799E119B880CE00096CB7 /* htc_one.svg */, @@ -439,6 +469,8 @@ 597046A11E24352700A60138 /* IJSVGStrokeLayer.m */, 59E0F5EB1E29964700F757F7 /* IJSVGUnitLength.h */, 59E0F5EC1E29964700F757F7 /* IJSVGUnitLength.m */, + 59D1E39C2022577500C54672 /* IJSVGQuartzRenderer.h */, + 59D1E39D2022577500C54672 /* IJSVGQuartzRenderer.m */, ); name = source; path = ../../source; @@ -488,7 +520,7 @@ 5956656F19B62F4600D805FF /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0920; ORGANIZATIONNAME = "Curtis Hard"; TargetAttributes = { 5956657619B62F4600D805FF = { @@ -526,13 +558,22 @@ files = ( 59B93C6D19B7D1840063E823 /* paperplane.svg in Resources */, 5956658219B62F4600D805FF /* Images.xcassets in Resources */, + 590C87ED201FA08C004A1554 /* intertwingly.svg in Resources */, + 590C87F1201FBF27004A1554 /* AJ_Digital_Camera.svg in Resources */, + 590C87EF201FA093004A1554 /* NewTux.svg in Resources */, 59265CE81C4F840400F333F0 /* css.svg in Resources */, 595665DE19B6309C00D805FF /* test.svg in Resources */, + 590C87EB201F9888004A1554 /* json.svg in Resources */, + 59D1E3A0202279CA00C54672 /* Group.svg in Resources */, 59F799E219B880CE00096CB7 /* htc_one.svg in Resources */, 59B93C6F19B7D32C0063E823 /* products.svg in Resources */, + 5991A2A9201E30E600913E3B /* gradients.svg in Resources */, 5986308719BA104800CF15EA /* linecap.svg in Resources */, 5956658519B62F4600D805FF /* MainMenu.xib in Resources */, + 590C87F4201FC9E4004A1554 /* radialgradient2.svg in Resources */, 59459CEC19B906FE00CE493B /* clipped.svg in Resources */, + 590C87F6201FD0D4004A1554 /* car.svg in Resources */, + 5991A2AB201E310200913E3B /* heart.svg in Resources */, 5986308C19BA180E00CF15EA /* dashed.svg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -555,6 +596,7 @@ 59E2646919BA240D008A6FDB /* IJSVGStyle.m in Sources */, 59E2646419BA240D008A6FDB /* IJSVGLinearGradient.m in Sources */, 595665DC19B6302600D805FF /* README.md in Sources */, + 59D1E39E2022577500C54672 /* IJSVGQuartzRenderer.m in Sources */, 5941DAFD1CF9BC9B00B3A911 /* IJSVGImage.m in Sources */, 599465DE1C4AA87200A2EEF3 /* IJSVGStyleSheetSelector.m in Sources */, 5948DED61BB2BFE5004156FF /* IJSVGWriter.m in Sources */, diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate index e0a9c420a19f58d23e5cfc0b1a2ca8b847e4d16d..8039625ccae261935c8aecedc1e52b4e804678f0 100644 GIT binary patch literal 49919 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 literal 5491 zcma)A3wRXO6+UNoW@mPGc6SmWyu%|Q2qrP_2hSuVfj|;48$t+=b+bDp3!B|AJDUfO zaurZe0gXJ2R!f5NkSeuRw5=7bihP!8QPfsZYZU=eY_Sv(AHB1aU9#4GpV==vk9+Pp z=brPQ|DL;ztv+v1&B!D+$JkQZ@^!l9B5oFyVc;rWS8P@_R`n2 z$!efA9`M%Phol|A0S^OUAPj=>a2-s5iEurnLK>t)5fnoS%z#oTgL0S&v!DXz!aSG{ zOJFHD;U;iF69k|YRA`5FK;UM$8@k{g*bX~jC+va;;6dnxJ#YXXg~#9tcp9FC7vVVk z8Qz4q;C=W2F2R4`GJFMB;A^-F-@v!<9WuzG1w~B2!8inm;z%5WNtle+;RMXZJj}-e zEW|P_$C)?_E3gu4(18naDK5h%3}7p&xC+ zKjCrwGro$i;a~6sp2WZ6Df|b1gdgKqcm==4tN0Cmi{Ih*1~8z(U@#h(`N{r}&-VtH zK>!PgV2Ahx)sFNPa%ZrCUi-pam+bbrg27&}f{hr6u@@wWBMezZ5}i_VPCw zT4`o>Sz1n3d0|>XeqnxEc0qALZdOiSUSV0TQyN%NTDBmdtO&Nc+;VBa9cq#NYQ%5? zBtptQ7z{&TC=7$)FakyrmY4`fcw#03vFw9UFdD{C4+cXrj3XlHAf40~mJA{pWUNz~ z)?a_Et8qrauPOnbPgb0nx#c-o8JXp2C1u6sX*u~Bd1=KZxjAX28TsWiO3DlJ$}$R; zYSX=bPoTYAaka=!X$*ZaSMkV-?1{P^qc-T2Qv2J~XJ0cr-|t-+k}EuPncihy>V`Cm ziWj?83fCXk=#+*=y{HWYe2p%pB3S1QdK-Pzp&{4CT#Y6Dh9W_{0rIsVW^tfq&g*u8h11Y+N)h{-X<3vaY|{LKaqsSL}tF~^?B9KVnud2Xf3OCUb$UY5uL7$ z{UClZDoW*`x5@t_8Z;+#{a!h^gu0+Rs!2*{j)Y=1R6an4LHb9GK!3eD5!&l5Z?<6pq>mP!+W6t7LgHTBuQLC)%4G)M4z?w zRk@n{UNz*A=Oq>0UI5zP>(B^p@POQ)mye&~s$Rd9gNv&jrCy4KfZwHb_EM>3_0^8K zVOERy&4i>MMUu(5URVWd$vBb{j-f;LQQDO~(L6e((e>_tM^2}i znB!H`qXX#bSC}JCOMj2jXTOZT3vpR>_%yey!|(HXkR5!8}nMwLT#si9zWtWTT95q;%I&C;r{Ytnro(dI{%dN14u@jcK@ z(tF^3a)ZW_nrNNN7m|a;!TzGZfZdR?dp8-&MY`3*(CXD0 zsiQO2rjA~nm0OrPIx{0XbM4xQ;30TCs^Qlp_nL+$;jpeDBKceReY8%VA^BviUK2;4 zb`LxU&%+-{0hvUmT&s(h;8+YMv*8s=E`=H}OewUo+@p{6ET(fKKXCQav; zDvEI@MTOtvQal>%Xu|psg=d}KmgvupP-6>~Zs<2r8bD{1%FA3KpBlEHs~_F>YMwSd zs8Sr)$gPSTq&F^&8R!lZ7jY;`T@Ai4CHKeB_lPu$2&AG14P>^qm~p!GKKlND2Ku6j zCge2*9GTODW>QJU)>gT^{>Z2m;~;(y+R%;?sUp>6?jDSX3pkL>qY?8eIWmCp~~&y)i#vZ{230g1zKL=O%pHY_f>B=tk)#W9uvBW#PSSVU4$`Iegck2RMulX^;ks$bJ`1bHw3EI9)R{8K>Y> zoJJOtC1fdaK19)Z0ZSm6V$wlwqNt?F8(!y9yc&Pe&0lWUkQS_Qwd?HRl!nEQ_QlWP z4zyBA933+lo*qyr=7Mg;+p2BoG#oJ^VePe@t+F1s**IeNwIEmFTn!i1q_GDsQ2(fJ zakRk+27G-*kNM~ zJ76=$?1D5)Ei^;TVM)F}kKjsFV%QBs*dFz$gRF>nv|96M4X!0V61e6O!Syj7ZNQDB zh4@M0!YBzO6?t!pv3NUfiJ7w%w~B>TOgpk6FmS1EU>`pA%0#oI!=hW{BNDghEC?Qh}F@d0Y! zLHq^orbc$*9+*k5N%i!sFdgi!)GcDfU$hED4=*F z+(a#@q>VtVt-aLB9(t~!wn&JIPyroR2eo?arq0!b>lF{-ubk3&8rdK3AY+5ki;u&y zpDwOj_zgbcl#+gegSb`xDXOuWJJfTD>n)=wP{ceAV=Z3gO(H6cGG z2G#2iZ#MLG3B~D()_~Wq2E!7R#Xb6}UWeM0QCe-$Tl^(q!%`*NC--#`}_wWPS zfZ+RNV-NnF+)BGBV55hut;mzt;njpsKwIwUl=A*WK zY3@KvYlt?nlfsY9fuLH~QIO}%%bB;Z%H^J?1oBcdY5J=jbL39hqnE0zoG@?(ettOV z1nG|-Lt!la$s?DZ^`@f_mGBP-U6gGeq=e9m`|toB#7F5*1n2Q9I@4^h7_0`nANHJVzm}p2fq#H5}S%w@#$gsh%-|)8KigA!J!#LGgX{L1W0+ZtOI686P&jX*|meW+pNP%p_(qGmDwSR5A0I8m5+MW>zw7Ob4@y zS;K5*wla4yUCb_~o4KEPfH}xK%N${jF(;T)%xUH#bD0I!%Eq$;*#vetJCYs6j$tRV z*=z}0!QRL=u&r!6yMf)z-ob8RyV&jQPWE2*m+WioDfS$Dp8cHtg8di!C40$aFfk^R zi8l!*(PT48rtzjZrd6hH(|*(MOfQ>WF&#I(W;$Vd!*tqo(e$P1-=_bVE^`dWab`~B zY@Ec6Ib1b2kE`LF+)bQ|b91d+kPC4g+$wGjw~@P(+sfU=b#dFd z9&RtUpL>Km#68aahI^8GmirU;7w&cLB=Z1MVz$iTj%ShWm~;@+{BsW_|=e zfuF)pse{O!zyxZJsK43m*e$;%({J8l& z^CiJ5BnU%=;lfB^qHw*CDx?ef!W3beFjJT}Ns!g^t&uvNHA=n}RI zJB53N9^s&HSa?-)PSBOOHuMr4OX@aZ=pixFK<4<8tB( + language = "" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,17 +63,22 @@ ReferencedContainer = "container:IJSVGExample.xcodeproj"> + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib b/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib index df8bf04..6876e68 100755 --- a/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib +++ b/IJSVGExample/IJSVGExample/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -671,19 +671,41 @@ - + - - + + - - + + + + - + diff --git a/IJSVGExample/IJSVGExample/Group.svg b/IJSVGExample/IJSVGExample/Group.svg new file mode 100644 index 0000000..435ffa4 --- /dev/null +++ b/IJSVGExample/IJSVGExample/Group.svg @@ -0,0 +1,13 @@ + + + + Group + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/IJSVGExample/IJSVGExample/NewTux.svg b/IJSVGExample/IJSVGExample/NewTux.svg new file mode 100644 index 0000000..291fc56 --- /dev/null +++ b/IJSVGExample/IJSVGExample/NewTux.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IJSVGExample/IJSVGExample/SVGExampleView1.m b/IJSVGExample/IJSVGExample/SVGExampleView1.m index 82a8b2d..6787b84 100644 --- a/IJSVGExample/IJSVGExample/SVGExampleView1.m +++ b/IJSVGExample/IJSVGExample/SVGExampleView1.m @@ -12,7 +12,7 @@ - (IJSVG *)svg { - return [[IJSVG svgNamed:@"paperplane"] retain]; + return [[IJSVG svgNamed:@"heart"] retain]; } @end diff --git a/IJSVGExample/IJSVGExample/SVGView.h b/IJSVGExample/IJSVGExample/SVGView.h index 0ef1238..735d755 100644 --- a/IJSVGExample/IJSVGExample/SVGView.h +++ b/IJSVGExample/IJSVGExample/SVGView.h @@ -15,6 +15,7 @@ } - +- (IBAction)switchToCoreGraphics:(id)sender; +- (IBAction)switchToCoreAnimation:(id)sender; @end diff --git a/IJSVGExample/IJSVGExample/SVGView.m b/IJSVGExample/IJSVGExample/SVGView.m index 8578532..4a2eec0 100644 --- a/IJSVGExample/IJSVGExample/SVGView.m +++ b/IJSVGExample/IJSVGExample/SVGView.m @@ -29,9 +29,21 @@ return self; } +- (void)switchToCoreGraphics:(id)sender +{ + svg.renderingEngine = IJSVGRenderingEngineQuartz; + [self setNeedsDisplay:YES]; +} + +- (void)switchToCoreAnimation:(id)sender +{ + svg.renderingEngine = IJSVGRenderingEngineLayered; + [self setNeedsDisplay:YES]; +} + - (IJSVG *)svg { - return [IJSVG svgNamed:@"test"]; + return [IJSVG svgNamed:@"clipped"]; } - (void)drawRect:(NSRect)dirtyRect diff --git a/IJSVGExample/IJSVGExample/car.svg b/IJSVGExample/IJSVGExample/car.svg new file mode 100644 index 0000000..00f5143 --- /dev/null +++ b/IJSVGExample/IJSVGExample/car.svg @@ -0,0 +1,7415 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/gradients.svg b/IJSVGExample/IJSVGExample/gradients.svg new file mode 100644 index 0000000..08efcd1 --- /dev/null +++ b/IJSVGExample/IJSVGExample/gradients.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IJSVGExample/IJSVGExample/heart.svg b/IJSVGExample/IJSVGExample/heart.svg new file mode 100644 index 0000000..4808edf --- /dev/null +++ b/IJSVGExample/IJSVGExample/heart.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/intertwingly.svg b/IJSVGExample/IJSVGExample/intertwingly.svg new file mode 100644 index 0000000..9495fa8 --- /dev/null +++ b/IJSVGExample/IJSVGExample/intertwingly.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/json.svg b/IJSVGExample/IJSVGExample/json.svg new file mode 100644 index 0000000..a1b7dd5 --- /dev/null +++ b/IJSVGExample/IJSVGExample/json.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/IJSVGExample/IJSVGExample/radialgradient2.svg b/IJSVGExample/IJSVGExample/radialgradient2.svg new file mode 100644 index 0000000..7a7e6da --- /dev/null +++ b/IJSVGExample/IJSVGExample/radialgradient2.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IJSVGExample/IJSVGExample/test.svg b/IJSVGExample/IJSVGExample/test.svg index fd00464..da930e5 100755 --- a/IJSVGExample/IJSVGExample/test.svg +++ b/IJSVGExample/IJSVGExample/test.svg @@ -1,65 +1,7415 @@ - - - - icon - Created with Sketch. - - - - + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/IJSVG.h b/source/IJSVG.h index af602f4..debc619 100644 --- a/source/IJSVG.h +++ b/source/IJSVG.h @@ -13,6 +13,7 @@ #import "IJSVGGroupLayer.h" #import "IJSVGImageLayer.h" #import "IJSVGExporter.h" +#import "IJSVGQuartzRenderer.h" @class IJSVG; @@ -36,6 +37,11 @@ withSVGString:(NSString *)subSVGString; typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); +typedef NS_ENUM(NSInteger, IJSVGRenderingEngine) { + IJSVGRenderingEngineCoreGraphics, + IJSVGRenderingEngineCoreAnimation +}; + @interface IJSVG : NSObject { @private @@ -47,7 +53,9 @@ typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); CGRect _viewBox; CGSize _proposedViewSize; CGFloat _lastProposedBackingScale; + CGFloat _backingScale; NSMutableDictionary * _replacementColors; + IJSVGQuartzRenderer * _quartzRenderer; struct { unsigned int shouldHandleForeignObject: 1; @@ -70,6 +78,8 @@ typedef CGFloat (^IJSVGRenderingBackingScaleFactorHelper)(); @property (nonatomic, assign) CGFloat strokeWidth; @property (nonatomic, assign) IJSVGLineCapStyle lineCapStyle; @property (nonatomic, assign) IJSVGLineJoinStyle lineJoinStyle; +@property (nonatomic, assign) IJSVGRenderingEngine renderingEngine; +@property (nonatomic, assign) BOOL clipToViewport; - (void)prepForDrawingInView:(NSView *)view; - (BOOL)isFont; diff --git a/source/IJSVG.m b/source/IJSVG.m index 15ad563..c6621ce 100644 --- a/source/IJSVG.m +++ b/source/IJSVG.m @@ -19,6 +19,8 @@ @synthesize lineCapStyle; @synthesize lineJoinStyle; @synthesize renderingBackingScaleHelper; +@synthesize renderingEngine; +@synthesize clipToViewport; - (void)dealloc { @@ -28,6 +30,7 @@ [_group release], _group = nil; [_layerTree release], _layerTree = nil; [_replacementColors release], _replacementColors = nil; + [_quartzRenderer release], _quartzRenderer = nil; [super dealloc]; } @@ -333,7 +336,14 @@ - (void)_setupBasicsFromAnyInitializer { - _lastProposedBackingScale = 1.f; + renderingEngine = IJSVGRenderingEngineCoreAnimation; + self.clipToViewport = YES; + + // setup low level backing scale + _lastProposedBackingScale = 0.f; + self.renderingBackingScaleHelper = ^CGFloat{ + return 1.f; + }; } - (NSString *)identifier @@ -557,6 +567,7 @@ - (CGFloat)computeBackingScale:(CGFloat)actualScale { + _backingScale = actualScale; return (CGFloat)(_scale + actualScale); } @@ -566,8 +577,8 @@ // we also need to calculate the viewport so we can clip // the drawing if needed NSRect viewPort = NSZeroRect; - viewPort.origin.x = round(rect.size.width/2-(_proposedViewSize.width/2)*_clipScale); - viewPort.origin.y = round(rect.size.height/2-(_proposedViewSize.height/2)*_clipScale);; + viewPort.origin.x = round((rect.size.width/2-(_proposedViewSize.width/2)*_clipScale) + rect.origin.x); + viewPort.origin.y = round((rect.size.height/2-(_proposedViewSize.height/2)*_clipScale) + rect.origin.y); viewPort.size.width = _proposedViewSize.width*_clipScale; viewPort.size.height = _proposedViewSize.height*_clipScale; @@ -607,17 +618,11 @@ @try { [self _beginDraw:rect]; - - // scale the whole drawing context, but first, we need - // to translate the context so its centered - CGFloat tX = round(rect.size.width/2-(_viewBox.size.width/2)*_scale); - CGFloat tY = round(rect.size.height/2-(_viewBox.size.height/2)*_scale); // we also need to calculate the viewport so we can clip // the drawing if needed BOOL canDraw = NO; - NSRect viewPort = [self computeRectDrawingInRect:rect - isValid:&canDraw]; + NSRect viewPort = [self computeRectDrawingInRect:rect isValid:&canDraw]; // check the viewport if( !canDraw ) { if( error != NULL ) { @@ -630,12 +635,17 @@ } // clip to mask - CGContextClipToRect( ref, viewPort); + if(self.clipToViewport == YES) { + CGContextClipToRect( ref, viewPort); + } - tX -= (_viewBox.origin.x*_scale); - tY -= (_viewBox.origin.y*_scale); + // add the origin back onto the viewport + viewPort.origin.x -= round((_viewBox.origin.x)*_scale); + viewPort.origin.y -= round((_viewBox.origin.y)*_scale); + viewPort = CGRectIntegral(viewPort); - CGContextTranslateCTM( ref, tX, tY ); + // transforms + CGContextTranslateCTM( ref, viewPort.origin.x, viewPort.origin.y); CGContextScaleCTM( ref, _scale, _scale ); // render the layer, its really important we lock @@ -648,7 +658,27 @@ } // render the layers - [self.layer renderInContext:ref]; + switch(self.renderingEngine) { + // CoreGraphics / Quartz + case IJSVGRenderingEngineCoreGraphics: { + if(_quartzRenderer == nil) { + // init the renderer if its not already defined + _quartzRenderer = [[IJSVGQuartzRenderer alloc] init]; + } + _quartzRenderer.scale = _scale; + _quartzRenderer.backingScale = _backingScale; + _quartzRenderer.viewPort = viewPort; + + // render it + [_quartzRenderer renderLayer:self.layer + inContext:ref]; + break; + } + // CALayer tree + case IJSVGRenderingEngineCoreAnimation: { + [self.layer renderInContext:ref]; + } + } IJSVGEndTransactionLock(); } @catch (NSException *exception) { @@ -681,8 +711,9 @@ // walk the tree void (^block)(CALayer * layer, BOOL isMask) = ^void (CALayer * layer, BOOL isMask) { - if(((IJSVGLayer *)layer).requiresBackingScaleHelp == YES) { - ((IJSVGLayer *)layer).backingScaleFactor = scale; + IJSVGLayer * propLayer = ((IJSVGLayer *)layer); + if(propLayer.requiresBackingScaleHelp == YES) { + propLayer.backingScaleFactor = scale; } }; @@ -738,6 +769,7 @@ // force rebuild of the tree IJSVGBeginTransactionLock(); _layerTree = [[tree layerForNode:_group] retain]; + IJSVGEndTransactionLock(); return _layerTree; } diff --git a/source/IJSVGColor.h b/source/IJSVGColor.h index 4b8bd7a..6aa9580 100644 --- a/source/IJSVGColor.h +++ b/source/IJSVGColor.h @@ -160,11 +160,14 @@ typedef NS_ENUM( NSInteger, IJSVGPredefinedColor ) { @interface IJSVGColor : NSObject +CGFloat * IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness); + + (NSColor *)computeColorSpace:(NSColor *)color; + (NSColorSpace *)defaultColorSpace; + (BOOL)isColor:(NSString *)string; + (NSString *)colorStringFromColor:(NSColor *)color - forceHex:(BOOL)forceHex; + forceHex:(BOOL)forceHex + allowShorthand:(BOOL)allowShorthand; + (NSString *)colorStringFromColor:(NSColor *)color; + (NSColor *)computeColor:(id)colour; + (NSColor *)colorFromString:(NSString *)string; diff --git a/source/IJSVGColor.m b/source/IJSVGColor.m index 39d80f0..67d58fa 100644 --- a/source/IJSVGColor.m +++ b/source/IJSVGColor.m @@ -11,8 +11,27 @@ @implementation IJSVGColor -static NSMutableDictionary * _colorTree = nil; +static NSDictionary * _colorTree = nil; +CGFloat * IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness) +{ + hue *= (1.f/360.f); + hue = (hue - floorf(hue)); + saturation *= 0.01; + lightness *= 0.01; + lightness *= 2.f; + + CGFloat s = saturation * ((lightness < 1.f) ? lightness : (2.f - lightness)); + CGFloat brightness = (lightness + s) * .5f; + if(s != 0.f) { + s = (2.f * s) / (lightness + s); + } + CGFloat * floats = (CGFloat *)malloc(3*sizeof(CGFloat)); + floats[0] = hue; + floats[1] = s; + floats[2] = brightness; + return floats; +}; + (void)load { @@ -37,157 +56,156 @@ static NSMutableDictionary * _colorTree = nil; { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - - _colorTree = [[NSMutableDictionary alloc] init]; - // add the colours in - [_colorTree setObject:@"f0f8ff" forKey:@"aliceblue"]; - [_colorTree setObject:@"faebd7" forKey:@"antiquewhite"]; - [_colorTree setObject:@"00ffff" forKey:@"aqua"]; - [_colorTree setObject:@"7fffd4" forKey:@"aquamarine"]; - [_colorTree setObject:@"f0ffff" forKey:@"azure"]; - [_colorTree setObject:@"f5f5dc" forKey:@"beige"]; - [_colorTree setObject:@"ffe4c4" forKey:@"bisque"]; - [_colorTree setObject:@"000000" forKey:@"black"]; - [_colorTree setObject:@"ffebcd" forKey:@"blanchedalmond"]; - [_colorTree setObject:@"0000ff" forKey:@"blue"]; - [_colorTree setObject:@"8a2be2" forKey:@"blueviolet"]; - [_colorTree setObject:@"a52a2a" forKey:@"brown"]; - [_colorTree setObject:@"deb887" forKey:@"burlywood"]; - [_colorTree setObject:@"5f9ea0" forKey:@"cadetblue"]; - [_colorTree setObject:@"7fff00" forKey:@"chartreuse"]; - [_colorTree setObject:@"d2691e" forKey:@"chocolate"]; - [_colorTree setObject:@"ff7f50" forKey:@"coral"]; - [_colorTree setObject:@"6495ed" forKey:@"cornflowerblue"]; - [_colorTree setObject:@"fff8dc" forKey:@"cornsilk"]; - [_colorTree setObject:@"dc143c" forKey:@"crimson"]; - [_colorTree setObject:@"000000" forKey:@"currentcolor"]; - [_colorTree setObject:@"00ffff" forKey:@"cyan"]; - [_colorTree setObject:@"00008b" forKey:@"darkblue"]; - [_colorTree setObject:@"008b8b" forKey:@"darkcyan"]; - [_colorTree setObject:@"b8860b" forKey:@"darkgoldenrod"]; - [_colorTree setObject:@"a9a9a9" forKey:@"darkgray"]; - [_colorTree setObject:@"006400" forKey:@"darkgreen"]; - [_colorTree setObject:@"a9a9a9" forKey:@"darkgrey"]; - [_colorTree setObject:@"bdb76b" forKey:@"darkkhaki"]; - [_colorTree setObject:@"8b008b" forKey:@"darkmagenta"]; - [_colorTree setObject:@"556b2f" forKey:@"darkolivegreen"]; - [_colorTree setObject:@"ff8c00" forKey:@"darkorange"]; - [_colorTree setObject:@"9932cc" forKey:@"darkorchid"]; - [_colorTree setObject:@"8b0000" forKey:@"darkred"]; - [_colorTree setObject:@"e9967a" forKey:@"darksalmon"]; - [_colorTree setObject:@"8fbc8f" forKey:@"darkseagreen"]; - [_colorTree setObject:@"483d8b" forKey:@"darkslateblue"]; - [_colorTree setObject:@"2f4f4f" forKey:@"darkslategray"]; - [_colorTree setObject:@"2f4f4f" forKey:@"darkslategrey"]; - [_colorTree setObject:@"00ced1" forKey:@"darkturquoise"]; - [_colorTree setObject:@"9400d3" forKey:@"darkviolet"]; - [_colorTree setObject:@"ff1493" forKey:@"deeppink"]; - [_colorTree setObject:@"00bfff" forKey:@"deepskyblue"]; - [_colorTree setObject:@"696969" forKey:@"dimgray"]; - [_colorTree setObject:@"696969" forKey:@"dimgrey"]; - [_colorTree setObject:@"1e90ff" forKey:@"dodgerblue"]; - [_colorTree setObject:@"b22222" forKey:@"firebrick"]; - [_colorTree setObject:@"fffaf0" forKey:@"floralwhite"]; - [_colorTree setObject:@"228b22" forKey:@"forestgreen"]; - [_colorTree setObject:@"ff00ff" forKey:@"fuchsia"]; - [_colorTree setObject:@"dcdcdc" forKey:@"gainsboro"]; - [_colorTree setObject:@"f8f8ff" forKey:@"ghostwhite"]; - [_colorTree setObject:@"ffd700" forKey:@"gold"]; - [_colorTree setObject:@"daa520" forKey:@"goldenrod"]; - [_colorTree setObject:@"808080" forKey:@"gray"]; - [_colorTree setObject:@"008000" forKey:@"green"]; - [_colorTree setObject:@"adff2f" forKey:@"greenyellow"]; - [_colorTree setObject:@"808080" forKey:@"grey"]; - [_colorTree setObject:@"f0fff0" forKey:@"honeydew"]; - [_colorTree setObject:@"ff69b4" forKey:@"hotpink"]; - [_colorTree setObject:@"cd5c5c" forKey:@"indianred"]; - [_colorTree setObject:@"4b0082" forKey:@"indigo"]; - [_colorTree setObject:@"fffff0" forKey:@"ivory"]; - [_colorTree setObject:@"f0e68c" forKey:@"khaki"]; - [_colorTree setObject:@"e6e6fa" forKey:@"lavender"]; - [_colorTree setObject:@"fff0f5" forKey:@"lavenderblush"]; - [_colorTree setObject:@"7cfc00" forKey:@"lawngreen"]; - [_colorTree setObject:@"fffacd" forKey:@"lemonchiffon"]; - [_colorTree setObject:@"add8e6" forKey:@"lightblue"]; - [_colorTree setObject:@"f08080" forKey:@"lightcoral"]; - [_colorTree setObject:@"e0ffff" forKey:@"lightcyan"]; - [_colorTree setObject:@"fafad2" forKey:@"lightgoldenrodyellow"]; - [_colorTree setObject:@"d3d3d3" forKey:@"lightgray"]; - [_colorTree setObject:@"90ee90" forKey:@"lightgreen"]; - [_colorTree setObject:@"d3d3d3" forKey:@"lightgrey"]; - [_colorTree setObject:@"ffb6c1" forKey:@"lightpink"]; - [_colorTree setObject:@"ffa07a" forKey:@"lightsalmon"]; - [_colorTree setObject:@"20b2aa" forKey:@"lightseagreen"]; - [_colorTree setObject:@"87cefa" forKey:@"lightskyblue"]; - [_colorTree setObject:@"778899" forKey:@"lightslategray"]; - [_colorTree setObject:@"778899" forKey:@"lightslategrey"]; - [_colorTree setObject:@"b0c4de" forKey:@"lightsteelblue"]; - [_colorTree setObject:@"ffffe0" forKey:@"lightyellow"]; - [_colorTree setObject:@"00ff00" forKey:@"lime"]; - [_colorTree setObject:@"32cd32" forKey:@"limegreen"]; - [_colorTree setObject:@"faf0e6" forKey:@"linen"]; - [_colorTree setObject:@"ff00ff" forKey:@"magenta"]; - [_colorTree setObject:@"800000" forKey:@"maroon"]; - [_colorTree setObject:@"66cdaa" forKey:@"mediumaquamarine"]; - [_colorTree setObject:@"0000cd" forKey:@"mediumblue"]; - [_colorTree setObject:@"ba55d3" forKey:@"mediumorchid"]; - [_colorTree setObject:@"9370db" forKey:@"mediumpurple"]; - [_colorTree setObject:@"3cb371" forKey:@"mediumseagreen"]; - [_colorTree setObject:@"7b68ee" forKey:@"mediumslateblue"]; - [_colorTree setObject:@"00fa9a" forKey:@"mediumspringgreen"]; - [_colorTree setObject:@"48d1cc" forKey:@"mediumturquoise"]; - [_colorTree setObject:@"c71585" forKey:@"mediumvioletred"]; - [_colorTree setObject:@"191970" forKey:@"midnightblue"]; - [_colorTree setObject:@"f5fffa" forKey:@"mintcream"]; - [_colorTree setObject:@"ffe4e1" forKey:@"mistyrose"]; - [_colorTree setObject:@"ffe4b5" forKey:@"moccasin"]; - [_colorTree setObject:@"ffdead" forKey:@"navajowhite"]; - [_colorTree setObject:@"000080" forKey:@"navy"]; - [_colorTree setObject:@"fdf5e6" forKey:@"oldlace"]; - [_colorTree setObject:@"808000" forKey:@"olive"]; - [_colorTree setObject:@"6b8e23" forKey:@"olivedrab"]; - [_colorTree setObject:@"ffa500" forKey:@"orange"]; - [_colorTree setObject:@"ff4500" forKey:@"orangered"]; - [_colorTree setObject:@"da70d6" forKey:@"orchid"]; - [_colorTree setObject:@"eee8aa" forKey:@"palegoldenrod"]; - [_colorTree setObject:@"98fb98" forKey:@"palegreen"]; - [_colorTree setObject:@"afeeee" forKey:@"paleturquoise"]; - [_colorTree setObject:@"db7093" forKey:@"palevioletred"]; - [_colorTree setObject:@"ffefd5" forKey:@"papayawhip"]; - [_colorTree setObject:@"ffdab9" forKey:@"peachpuff"]; - [_colorTree setObject:@"cd853f" forKey:@"peru"]; - [_colorTree setObject:@"ffc0cb" forKey:@"pink"]; - [_colorTree setObject:@"dda0dd" forKey:@"plum"]; - [_colorTree setObject:@"b0e0e6" forKey:@"powderblue"]; - [_colorTree setObject:@"800080" forKey:@"purple"]; - [_colorTree setObject:@"ff0000" forKey:@"red"]; - [_colorTree setObject:@"bc8f8f" forKey:@"rosybrown"]; - [_colorTree setObject:@"4169e1" forKey:@"royalblue"]; - [_colorTree setObject:@"8b4513" forKey:@"saddlebrown"]; - [_colorTree setObject:@"fa8072" forKey:@"salmon"]; - [_colorTree setObject:@"f4a460" forKey:@"sandybrown"]; - [_colorTree setObject:@"2e8b57" forKey:@"seagreen"]; - [_colorTree setObject:@"fff5ee" forKey:@"seashell"]; - [_colorTree setObject:@"a0522d" forKey:@"sienna"]; - [_colorTree setObject:@"c0c0c0" forKey:@"silver"]; - [_colorTree setObject:@"87ceeb" forKey:@"skyblue"]; - [_colorTree setObject:@"6a5acd" forKey:@"slateblue"]; - [_colorTree setObject:@"708090" forKey:@"slategray"]; - [_colorTree setObject:@"708090" forKey:@"slategrey"]; - [_colorTree setObject:@"fffafa" forKey:@"snow"]; - [_colorTree setObject:@"00ff7f" forKey:@"springgreen"]; - [_colorTree setObject:@"4682b4" forKey:@"steelblue"]; - [_colorTree setObject:@"d2b48c" forKey:@"tan"]; - [_colorTree setObject:@"008080" forKey:@"teal"]; - [_colorTree setObject:@"d8bfd8" forKey:@"thistle"]; - [_colorTree setObject:@"ff6347" forKey:@"tomato"]; - [_colorTree setObject:@"40e0d0" forKey:@"turquoise"]; - [_colorTree setObject:@"ee82ee" forKey:@"violet"]; - [_colorTree setObject:@"f5deb3" forKey:@"wheat"]; - [_colorTree setObject:@"ffffff" forKey:@"white"]; - [_colorTree setObject:@"f5f5f5" forKey:@"whitesmoke"]; - [_colorTree setObject:@"ffff00" forKey:@"yellow"]; - [_colorTree setObject:@"9acd32" forKey:@"yellowgreen"]; + _colorTree = [@{ + @"aliceblue":@"f0f8ff", + @"antiquewhite":@"faebd7", + @"aqua":@"00ffff", + @"aquamarine":@"7fffd4", + @"azure":@"f0ffff", + @"beige":@"f5f5dc", + @"bisque":@"ffe4c4", + @"black":@"000000", + @"blanchedalmond":@"ffebcd", + @"blue":@"0000ff", + @"blueviolet":@"8a2be2", + @"brown":@"a52a2a", + @"burlywood":@"deb887", + @"cadetblue":@"5f9ea0", + @"chartreuse":@"7fff00", + @"chocolate":@"d2691e", + @"coral":@"ff7f50", + @"cornflowerblue":@"6495ed", + @"cornsilk":@"fff8dc", + @"crimson":@"dc143c", + @"currentcolor":@"000000", + @"cyan":@"00ffff", + @"darkblue":@"00008b", + @"darkcyan":@"008b8b", + @"darkgoldenrod":@"b8860b", + @"darkgray":@"a9a9a9", + @"darkgreen":@"006400", + @"darkgrey":@"a9a9a9", + @"darkkhaki":@"bdb76b", + @"darkmagenta":@"8b008b", + @"darkolivegreen":@"556b2f", + @"darkorange":@"ff8c00", + @"darkorchid":@"9932cc", + @"darkred":@"8b0000", + @"darksalmon":@"e9967a", + @"darkseagreen":@"8fbc8f", + @"darkslateblue":@"483d8b", + @"darkslategray":@"2f4f4f", + @"darkslategrey":@"2f4f4f", + @"darkturquoise":@"00ced1", + @"darkviolet":@"9400d3", + @"deeppink":@"ff1493", + @"deepskyblue":@"00bfff", + @"dimgray":@"696969", + @"dimgrey":@"696969", + @"dodgerblue":@"1e90ff", + @"firebrick":@"b22222", + @"floralwhite":@"fffaf0", + @"forestgreen":@"228b22", + @"fuchsia":@"ff00ff", + @"gainsboro":@"dcdcdc", + @"ghostwhite":@"f8f8ff", + @"gold":@"ffd700", + @"goldenrod":@"daa520", + @"gray":@"808080", + @"green":@"008000", + @"greenyellow":@"adff2f", + @"grey":@"808080", + @"honeydew":@"f0fff0", + @"hotpink":@"ff69b4", + @"indianred":@"cd5c5c", + @"indigo":@"4b0082", + @"ivory":@"fffff0", + @"khaki":@"f0e68c", + @"lavender":@"e6e6fa", + @"lavenderblush":@"fff0f5", + @"lawngreen":@"7cfc00", + @"lemonchiffon":@"fffacd", + @"lightblue":@"add8e6", + @"lightcoral":@"f08080", + @"lightcyan":@"e0ffff", + @"lightgoldenrodyellow":@"fafad2", + @"lightgray":@"d3d3d3", + @"lightgreen":@"90ee90", + @"lightgrey":@"d3d3d3", + @"lightpink":@"ffb6c1", + @"lightsalmon":@"ffa07a", + @"lightseagreen":@"20b2aa", + @"lightskyblue":@"87cefa", + @"lightslategray":@"778899", + @"lightslategrey":@"778899", + @"lightsteelblue":@"b0c4de", + @"lightyellow":@"ffffe0", + @"lime":@"00ff00", + @"limegreen":@"32cd32", + @"linen":@"faf0e6", + @"magenta":@"ff00ff", + @"maroon":@"800000", + @"mediumaquamarine":@"66cdaa", + @"mediumblue":@"0000cd", + @"mediumorchid":@"ba55d3", + @"mediumpurple":@"9370db", + @"mediumseagreen":@"3cb371", + @"mediumslateblue":@"7b68ee", + @"mediumspringgreen":@"00fa9a", + @"mediumturquoise":@"48d1cc", + @"mediumvioletred":@"c71585", + @"midnightblue":@"191970", + @"mintcream":@"f5fffa", + @"mistyrose":@"ffe4e1", + @"moccasin":@"ffe4b5", + @"navajowhite":@"ffdead", + @"navy":@"000080", + @"oldlace":@"fdf5e6", + @"olive":@"808000", + @"olivedrab":@"6b8e23", + @"orange":@"ffa500", + @"orangered":@"ff4500", + @"orchid":@"da70d6", + @"palegoldenrod":@"eee8aa", + @"palegreen":@"98fb98", + @"paleturquoise":@"afeeee", + @"palevioletred":@"db7093", + @"papayawhip":@"ffefd5", + @"peachpuff":@"ffdab9", + @"peru":@"cd853f", + @"pink":@"ffc0cb", + @"plum":@"dda0dd", + @"powderblue":@"b0e0e6", + @"purple":@"800080", + @"red":@"ff0000", + @"rosybrown":@"bc8f8f", + @"royalblue":@"4169e1", + @"saddlebrown":@"8b4513", + @"salmon":@"fa8072", + @"sandybrown":@"f4a460", + @"seagreen":@"2e8b57", + @"seashell":@"fff5ee", + @"sienna":@"a0522d", + @"silver":@"c0c0c0", + @"skyblue":@"87ceeb", + @"slateblue":@"6a5acd", + @"slategray":@"708090", + @"slategrey":@"708090", + @"snow":@"fffafa", + @"springgreen":@"00ff7f", + @"steelblue":@"4682b4", + @"tan":@"d2b48c", + @"teal":@"008080", + @"thistle":@"d8bfd8", + @"tomato":@"ff6347", + @"turquoise":@"40e0d0", + @"violet":@"ee82ee", + @"wheat":@"f5deb3", + @"white":@"ffffff", + @"whitesmoke":@"f5f5f5", + @"yellow":@"ffff00", + @"yellowgreen":@"9acd32" + } retain]; }); } @@ -226,14 +244,37 @@ static NSMutableDictionary * _colorTree = nil; if( count == 4 ) { alpha = params[3]; } - color = [NSColor colorWithDeviceRed:params[0]/255 - green:params[1]/255 - blue:params[2]/255 + color = [NSColor colorWithDeviceRed:params[0]/255.f + green:params[1]/255.f + blue:params[2]/255.f alpha:alpha]; free(params); return color; } + // is it HSL? + if([[string substringToIndex:3] isEqualToString:@"hsl"]) { + NSInteger count = 0; + CGFloat * params = [IJSVGUtils commandParameters:string + count:&count]; + CGFloat alpha = 1; + if(count == 4) { + alpha = params[3]; + } + + // convert HSL to HSB + CGFloat * hsb = IJSVGColorCSSHSLToHSB(params[0], params[1], params[2]); + color = [NSColor colorWithDeviceHue:hsb[0] + saturation:hsb[1] + brightness:hsb[2] + alpha:alpha]; + + // memory clean! + free(hsb); + free(params); + return color; + } + color = [[self class] colorFromHEXString:string alpha:1.f]; return color; @@ -252,11 +293,13 @@ static NSMutableDictionary * _colorTree = nil; + (NSString *)colorStringFromColor:(NSColor *)color { return [self colorStringFromColor:color - forceHex:NO]; + forceHex:NO + allowShorthand:YES]; } + (NSString *)colorStringFromColor:(NSColor *)color forceHex:(BOOL)forceHex + allowShorthand:(BOOL)allowShorthand { // convert to RGB color = [self computeColorSpace:color]; @@ -275,12 +318,22 @@ static NSMutableDictionary * _colorTree = nil; if(forceHex || alpha == 100 || (red == 0 && green == 0 && blue == 0 && alpha == 0) || (red == 255 && green == 255 && blue == 255 && alpha == 100)) { - // just return hex + if(allowShorthand == YES) { + NSString * r = [NSString stringWithFormat:@"%02X",red]; + NSString * g = [NSString stringWithFormat:@"%02X",green]; + NSString * b = [NSString stringWithFormat:@"%02X",blue]; + if([r characterAtIndex:0] == [r characterAtIndex:1] && + [g characterAtIndex:0] == [g characterAtIndex:1] && + [b characterAtIndex:0] == [b characterAtIndex:1]) { + return [NSString stringWithFormat:@"#%c%c%c",[r characterAtIndex:0], + [g characterAtIndex:0],[b characterAtIndex:0]]; + } + } return [NSString stringWithFormat:@"#%02X%02X%02X",red,green,blue]; } // note the %g, CSS alpha is 0 to 1, not 0 - 100, my bad! - return [NSString stringWithFormat:@"rgba(%d, %d, %d, %g)",red, green, blue, + return [NSString stringWithFormat:@"rgba(%d,%d,%d,%g)",red, green, blue, ((float)alpha/100.f)]; } diff --git a/source/IJSVGCommand.h b/source/IJSVGCommand.h index 98611fa..4bca42c 100644 --- a/source/IJSVGCommand.h +++ b/source/IJSVGCommand.h @@ -41,6 +41,7 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { IJSVGCommand * previousCommand; NSInteger _currentIndex; Class commandClass; + BOOL isSubCommand; } @property ( nonatomic, copy ) NSString * commandString; @@ -52,6 +53,7 @@ typedef NS_ENUM( NSInteger, IJSVGCommandType ) { @property ( nonatomic, retain ) NSMutableArray * subCommands; @property ( nonatomic, assign ) Class commandClass; @property ( nonatomic, assign ) IJSVGCommand * previousCommand; +@property ( nonatomic, assign ) BOOL isSubCommand; - (id)initWithCommandString:(NSString *)commandString; diff --git a/source/IJSVGCommand.m b/source/IJSVGCommand.m index 80411e4..603f1ed 100644 --- a/source/IJSVGCommand.m +++ b/source/IJSVGCommand.m @@ -20,6 +20,7 @@ @synthesize requiredParameters; @synthesize type; @synthesize previousCommand; +@synthesize isSubCommand; static NSMutableDictionary * _classes = nil; @@ -71,8 +72,9 @@ static NSMutableDictionary * _classes = nil; c.parameters = subParams; c.type = self.type; c.command = self.command; - c.previousCommand = [self.subCommands lastObject]; + c.previousCommand = self.subCommands.lastObject; c.commandClass = self.commandClass; + c.isSubCommand = i == 0 ? NO : YES; // add it to our tree [self.subCommands addObject:c]; diff --git a/source/IJSVGCommandArc.m b/source/IJSVGCommandArc.m index 394e3a1..e578c24 100644 --- a/source/IJSVGCommandArc.m +++ b/source/IJSVGCommandArc.m @@ -29,10 +29,6 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { - - // command was taken from: https://github.com/jmenter/JAMSVGImage/blob/89b375c0c3203355a0c693e3b805458415bf4e29/Classes/JAMSVGImage/Path%20and%20Parser/JAMStyledBezierPathFactory.m - // and modified to purpose inside this to be converted into degrees and not radians - CGPoint radii = CGPointZero; CGPoint arcEndPoint = CGPointZero; CGPoint arcStartPoint = path.currentPoint; @@ -46,7 +42,7 @@ sweepFlag = [currentCommand readBOOL]; arcEndPoint = [currentCommand readPoint]; - if ( type == IJSVGCommandTypeRelative ) { + if (type == IJSVGCommandTypeRelative) { arcEndPoint.x += path.currentPoint.x; arcEndPoint.y += path.currentPoint.y; } @@ -82,13 +78,19 @@ } CGFloat radius = MAX(radii.x, radii.y); - CGPoint scale = (radii.x > radii.y) ? CGPointMake(1, radii.y / radii.x) : CGPointMake(radii.x / radii.y, 1); + CGPoint scale = (radii.x > radii.y) ? CGPointMake(1, radii.y / radii.x) : + CGPointMake(radii.x / radii.y, 1); NSAffineTransform * trans = [NSAffineTransform transform]; [trans translateXBy:-centerPoint.x yBy:-centerPoint.y]; - [trans rotateByRadians:-xAxisRotation]; - [trans scaleXBy:(1/scale.x) yBy:(1/scale.y)]; + [path.currentSubpath transformUsingAffineTransform:trans]; + trans = [NSAffineTransform transform]; + [trans rotateByRadians:-xAxisRotation]; + [path.currentSubpath transformUsingAffineTransform:trans]; + + trans = [NSAffineTransform transform]; + [trans scaleXBy:(1/scale.x) yBy:(1/scale.y)]; [path.currentSubpath transformUsingAffineTransform:trans]; [path.currentSubpath appendBezierPathWithArcWithCenter:NSZeroPoint @@ -98,12 +100,16 @@ clockwise:!sweepFlag]; trans = [NSAffineTransform transform]; - [trans translateXBy:centerPoint.x yBy:centerPoint.y]; - [trans rotateByRadians:xAxisRotation]; [trans scaleXBy:scale.x yBy:scale.y]; [path.currentSubpath transformUsingAffineTransform:trans]; + trans = [NSAffineTransform transform]; + [trans rotateByRadians:xAxisRotation]; + [path.currentSubpath transformUsingAffineTransform:trans]; + trans = [NSAffineTransform transform]; + [trans translateXBy:centerPoint.x yBy:centerPoint.y]; + [path.currentSubpath transformUsingAffineTransform:trans]; } @end diff --git a/source/IJSVGCommandCurve.m b/source/IJSVGCommandCurve.m index faf840d..24d8ca9 100644 --- a/source/IJSVGCommandCurve.m +++ b/source/IJSVGCommandCurve.m @@ -28,17 +28,15 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { - if( type == IJSVGCommandTypeAbsolute ) - { + if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] curveToPoint:NSMakePoint( params[4], params[5]) controlPoint1:NSMakePoint( params[0], params[1]) controlPoint2:NSMakePoint( params[2], params[3])]; return; } - NSPoint point = [[path currentSubpath] currentPoint]; - [[path currentSubpath] curveToPoint:NSMakePoint( point.x + params[4], point.y + params[5]) - controlPoint1:NSMakePoint( point.x + params[0], point.y + params[1]) - controlPoint2:NSMakePoint( point.x + params[2], point.y + params[3])]; + [[path currentSubpath] relativeCurveToPoint:NSMakePoint( params[4], params[5]) + controlPoint1:NSMakePoint( params[0], params[1]) + controlPoint2:NSMakePoint( params[2], params[3])]; } diff --git a/source/IJSVGCommandLineTo.m b/source/IJSVGCommandLineTo.m index f15bb06..4228344 100644 --- a/source/IJSVGCommandLineTo.m +++ b/source/IJSVGCommandLineTo.m @@ -28,12 +28,11 @@ type:(IJSVGCommandType)type path:(IJSVGPath *)path { - if( type == IJSVGCommandTypeAbsolute ) - { + if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] lineToPoint:NSMakePoint( params[0], params[1])]; return; } - NSPoint point = NSMakePoint( [path currentSubpath].currentPoint.x + params[0], + NSPoint point = NSMakePoint([path currentSubpath].currentPoint.x + params[0], [path currentSubpath].currentPoint.y + params[1]); [[path currentSubpath] lineToPoint:point]; } diff --git a/source/IJSVGCommandMove.m b/source/IJSVGCommandMove.m index 4970fac..4ef78ab 100644 --- a/source/IJSVGCommandMove.m +++ b/source/IJSVGCommandMove.m @@ -32,8 +32,7 @@ { // move to's allow more then one move to, but if there are more then one, // we need to run the line to instead...who knew! - if( command.commandClass == [self class]) - { + if( command.commandClass == [self class] && currentCommand.isSubCommand == YES) { [IJSVGCommandLineTo runWithParams:params paramCount:count command:currentCommand @@ -44,8 +43,7 @@ } // actual move to command - if( type == IJSVGCommandTypeAbsolute ) - { + if( type == IJSVGCommandTypeAbsolute ) { [[path currentSubpath] moveToPoint:NSMakePoint( params[0], params[1])]; return; } diff --git a/source/IJSVGExporter.h b/source/IJSVGExporter.h index 6e95d5c..7a8db19 100644 --- a/source/IJSVGExporter.h +++ b/source/IJSVGExporter.h @@ -26,6 +26,8 @@ typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { IJSVGExporterOptionRemoveHiddenElements = 1 << 8, IJSVGExporterOptionScaleToSizeIfNecessary = 1 << 9, IJSVGExporterOptionCompressOutput = 1 << 10, + IJSVGExporterOptionCollapseGradients = 1 << 11, + IJSVGExporterOptionCreateClasses = 1 << 12, IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef| IJSVGExporterOptionRemoveUselessGroups| IJSVGExporterOptionCreateUseForPaths| @@ -35,7 +37,8 @@ typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { IJSVGExporterOptionCleanupPaths| IJSVGExporterOptionRemoveHiddenElements| IJSVGExporterOptionScaleToSizeIfNecessary| - IJSVGExporterOptionCompressOutput + IJSVGExporterOptionCompressOutput| + IJSVGExporterOptionCollapseGradients }; @interface IJSVGExporter : NSObject { @@ -46,13 +49,9 @@ typedef NS_OPTIONS( NSInteger, IJSVGExporterOptions) { IJSVGExporterOptions _options; NSXMLDocument * _dom; NSXMLElement * _defElement; - NSInteger _gradCount; - NSInteger _patternCount; - NSInteger _imageCount; - NSInteger _maskCount; - NSInteger _pathCount; NSXMLElement * _scaledRootNode; - + NSInteger _idCount; + NSInteger _shortIdCount; } @property (nonatomic, copy) NSString * title; diff --git a/source/IJSVGExporter.m b/source/IJSVGExporter.m index ed791b0..7768e41 100644 --- a/source/IJSVGExporter.m +++ b/source/IJSVGExporter.m @@ -31,56 +31,69 @@ @synthesize title; @synthesize description; +const NSArray * IJSVGShortCharacterArray() +{ + static NSArray * _array; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _array = [@[@"a",@"b",@"c",@"d",@"e",@"f",@"g",@"h",@"i",@"j",@"k",@"l", + @"m",@"n",@"o",@"p",@"q",@"r",@"s",@"t",@"u",@"v",@"w",@"x",@"y",@"z", + @"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L", + @"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z"] retain]; + }); + return _array; +} + const NSArray * IJSVGInheritableAttributes() { static NSArray * _attributes; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _attributes = [@[ - @"clip-rule", - @"color", - @"color-interpolation", - @"color-interpolation-filters", - @"color-profile", - @"color-rendering", - @"cursor", - @"direction", - @"fill", - @"fill-opacity", - @"fill-rule", - @"font", - @"font-family", - @"font-size", - @"font-size-adjust", - @"font-stretch", - @"font-style", - @"font-variant", - @"font-weight", - @"glyph-orientation-horizontal", - @"glyph-orientation-vertical", - @"image-rendering", - @"kerning", - @"letter-spacing", - @"marker", - @"marker-end", - @"marker-mid", - @"marker-start", - @"pointer-events", - @"shape-rendering", - @"stroke", - @"stroke-dasharray", - @"stroke-dashoffset", - @"stroke-linecap", - @"stroke-linejoin", - @"stroke-miterlimit", - @"stroke-opacity", - @"stroke-width", - @"text-anchor", - @"text-rendering", - @"visibility", - @"white-space", - @"word-spacing", - @"writing-mode"] retain]; + @"clip-rule", + @"color", + @"color-interpolation", + @"color-interpolation-filters", + @"color-profile", + @"color-rendering", + @"cursor", + @"direction", + @"fill", + @"fill-opacity", + @"fill-rule", + @"font", + @"font-family", + @"font-size", + @"font-size-adjust", + @"font-stretch", + @"font-style", + @"font-variant", + @"font-weight", + @"glyph-orientation-horizontal", + @"glyph-orientation-vertical", + @"image-rendering", + @"kerning", + @"letter-spacing", + @"marker", + @"marker-end", + @"marker-mid", + @"marker-start", + @"pointer-events", + @"shape-rendering", + @"stroke", + @"stroke-dasharray", + @"stroke-dashoffset", + @"stroke-linecap", + @"stroke-linejoin", + @"stroke-miterlimit", + @"stroke-opacity", + @"stroke-width", + @"text-anchor", + @"text-rendering", + @"visibility", + @"white-space", + @"word-spacing", + @"writing-mode"] retain]; }); return _attributes; } @@ -97,11 +110,6 @@ NSDictionary * IJSVGElementAttributeDictionary(NSXMLElement * element) { return dict; }; -NSString * IJSVGShortFloatString(CGFloat f) -{ - return [NSString stringWithFormat:@"%g",f]; -}; - NSString * IJSVGHashURL(NSString * key) { return [NSString stringWithFormat:@"url(#%@)",key]; }; @@ -165,11 +173,11 @@ NSString * IJSVGHash(NSString * key) { // sort out viewbox NSRect viewBox = _svg.viewBox; NSDictionary * attributes = @{ - @"viewBox":[self viewBoxWithRect:viewBox], - @"version": [NSString stringWithFormat:@"%g",XML_DOC_VERSION], - @"xmlns": XML_DOC_NS, - @"xmlns:xlink": XML_DOC_NSXLINK - }; + @"viewBox":[self viewBoxWithRect:viewBox], + @"version": [NSString stringWithFormat:@"%g",XML_DOC_VERSION], + @"xmlns": XML_DOC_NS, + @"xmlns:xlink": XML_DOC_NSXLINK + }; // was there a size set? if(CGSizeEqualToSize(CGSizeZero, _size) == NO && @@ -219,6 +227,19 @@ NSString * IJSVGHash(NSString * key) { return root; } +- (NSString *)generateID +{ + const NSArray * chars = IJSVGShortCharacterArray(); + if(_idCount < chars.count) { + return chars[_idCount++]; + } + + if((_idCount % chars.count) == 0) { + _shortIdCount++; + } + return [NSString stringWithFormat:@"%@%ld",chars[(_idCount++ % chars.count)],_shortIdCount]; +} + - (void)_prepare { // create the stand alone DOM @@ -226,18 +247,28 @@ NSString * IJSVGHash(NSString * key) { _dom.version = XML_DOCTYPE_VERSION; _dom.characterEncoding = XML_DOC_CHARSET; - // add defs in - [_dom.rootElement addChild:[self defElement]]; - // sort out header // sort out stuff, so here we go... [self _recursiveParseFromLayer:_svg.layer intoElement:(_scaledRootNode?:_dom.rootElement)]; + // this needs to be added incase it needs to be cleaned + NSXMLElement * defNode = [self defElement]; + if(defNode.childCount != 0) { + [_dom.rootElement insertChild:[self defElement] + atIndex:0]; + } + // cleanup [self _cleanup]; + // could had been removed during cleaning process needs to be added back in! + if(defNode.childCount != 0 && defNode.parent == nil) { + [_dom.rootElement insertChild:[self defElement] + atIndex:0]; + } + // add generator NSXMLNode * generatorNode = [[[NSXMLNode alloc] initWithKind:NSXMLCommentKind] autorelease]; generatorNode.stringValue = XML_DOC_GENERATOR; @@ -262,11 +293,6 @@ NSString * IJSVGHash(NSString * key) { [self _cleanDef]; } - // move any possible attributes to its parent group - if((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) { - [self _moveAttributesToGroup]; - } - // collapse groups if((_options & IJSVGExporterOptionCollapseGroups) != 0) { [self _collapseGroups]; @@ -286,6 +312,65 @@ NSString * IJSVGHash(NSString * key) { if((_options & IJSVGExporterOptionCollapseGroups) != 0) { [self _compressGroups]; } + + // collapse gradients? + if((_options & IJSVGExporterOptionCollapseGradients) != 0) { + [self _collapseGradients]; + } + + // create classes? + if((_options & IJSVGExporterOptionCreateClasses) != 0) { + [self _createClasses]; + } + + // move attributes to group + if((_options & IJSVGExporterOptionMoveAttributesToGroup) != 0) { + [self _moveAttributesToGroupWithElement:_dom.rootElement]; + } +} + +- (void)_createClasses +{ + const NSArray * inhert = IJSVGInheritableAttributes(); + NSArray * elements = [_dom nodesForXPath:@"//*" + error:nil]; + NSMutableDictionary * rules = [[[NSMutableDictionary alloc] init] autorelease]; + for(NSXMLElement * element in elements) { + NSDictionary * inhertEl = [self intersectableAttributes:IJSVGElementAttributeDictionary(element) + inheritableAttributes:inhert]; + NSString * styles = [self styleSheetRulesFromDictionary:inhertEl]; + NSString * className = nil; + if((className = [rules objectForKey:styles]) == nil) { + className = [NSString stringWithFormat:@"%@",[self generateID]]; + rules[styles] = className; + } + + for(NSString * attributeName in inhertEl) { + [element removeAttributeForName:attributeName]; + } + IJSVGApplyAttributesToElement(@{@"class":className},element); + } + + // add styles to dom + NSXMLElement * styles = [[[NSXMLElement alloc] initWithName:@"style"] autorelease]; + NSXMLNode * node = [[[NSXMLNode alloc] initWithKind:NSXMLTextKind] autorelease]; + + NSMutableArray * classes = [[[NSMutableArray alloc] initWithCapacity:rules.count] autorelease]; + for(NSString * r in rules) { + [classes addObject:[NSString stringWithFormat:@".%@%@",rules[r],r]]; + } + node.stringValue = [classes componentsJoinedByString:@""]; + [styles addChild:node]; + [_dom.rootElement insertChild:styles atIndex:0]; +} + +- (NSString *)styleSheetRulesFromDictionary:(NSDictionary *)dict +{ + NSMutableArray * array = [[[NSMutableArray alloc] initWithCapacity:dict.count] autorelease]; + for(NSString * key in dict.allKeys) { + [array addObject:[NSString stringWithFormat:@"%@: %@;",key,dict[key]]]; + } + return [NSString stringWithFormat:@"{%@}",[array componentsJoinedByString:@" "]]; } - (void)_sortAttributesOnElement:(NSXMLElement *)element @@ -304,85 +389,116 @@ NSString * IJSVGHash(NSString * key) { { // find any elements where they have a style, but the element itself // must not be in the defs - NSArray * elements = [_dom nodesForXPath:@"//*[@style][not(ancestor::defs)]" + NSArray * elements = [_dom nodesForXPath:@"//*[@display='none']" error:nil]; for(NSXMLElement * element in elements) { - // parse the style - NSString * styleString = [element attributeForName:@"style"].stringValue; - IJSVGStyle * style = [IJSVGStyle parseStyleString:styleString]; - if([[style property:@"display"] isEqualToString:@"none"]) { - // this can be removed! - NSXMLElement * parent = (NSXMLElement *)element.parent; - [parent removeChildAtIndex:element.index]; + NSXMLElement * parent = (NSXMLElement *)element.parent; + [parent removeChildAtIndex:element.index]; + } +} + +- (void)_collapseGradients +{ + NSString * xPath = @"//defs/*[self::linearGradient or self::radialGradient]"; + NSArray * gradients = [_dom nodesForXPath:xPath error:nil]; + for(NSInteger i = 0; i < gradients.count; i++) { + if(i != 0) { + NSXMLElement * gradientA = gradients[i]; + NSXMLElement * gradientB = nil; + for(NSInteger s = (i - 1); s >= 0; s--) { + gradientB = gradients[s]; + if([self compareElementChildren:gradientA toElement:gradientB] == YES) { + NSString * idString = [gradientB attributeForName:@"id"].stringValue; + if(idString == nil || idString.length == 0) { + idString = [self generateID]; + IJSVGApplyAttributesToElement(@{@"id":idString}, gradientB); + } + NSDictionary * atts = @{@"xlink:href":IJSVGHash(idString)}; + IJSVGApplyAttributesToElement(atts, gradientA); + [gradientA setChildren:nil]; + break; + } + } } } } -- (void)_moveAttributesToGroup +- (BOOL)compareElementChildren:(NSXMLElement *)element + toElement:(NSXMLElement *)toElement { - @autoreleasepool { + NSArray * childrenA = element.children; + NSArray * childrenB = toElement.children; + if(childrenA.count != childrenB.count) { + return NO; + } + for(NSInteger i = 0; i < childrenA.count; i++) { + NSXMLElement * childA = childrenA[i]; + NSXMLElement * childB = childrenB[i]; + if([self compareElement:childA withElement:childB] == NO) { + return NO; + } + } + return YES; +} + +- (void)_moveAttributesToGroupWithElement:(NSXMLElement *)parentElement +{ + + const NSArray * excludedNodes = @[@"script",@"style",@"defs"]; + if([excludedNodes containsObject:parentElement.name] == YES) { + return; + } + + const NSArray * inheritableAttributes = IJSVGInheritableAttributes(); + + NSDictionary * intersection = @{}; + NSMutableArray * grouped = [[[NSMutableArray alloc] init] autorelease]; + + NSInteger counter = 0; + NSInteger size = parentElement.childCount - 1; + for(NSXMLElement * element in parentElement.children) { - NSArray * groups = [_dom nodesForXPath:@"//g" - error:nil]; + NSDictionary * attributes = [self intersectableAttributes:IJSVGElementAttributeDictionary(element) + inheritableAttributes:inheritableAttributes]; - const NSArray * inheritableAttributes = IJSVGInheritableAttributes(); + if(intersection.count == 0) { + intersection = attributes; + } - for(NSXMLElement * group in groups) { - @autoreleasepool { - NSDictionary * intersection = @{}; - - // does it have a mask/clip? - BOOL hasClip = [group attributeForName:@"clip-path"] != nil || - [group attributeForName:@"mask"] != nil; - - NSMutableArray * intersected = [[[NSMutableArray alloc] init] autorelease]; - - // loop around each child - for(NSXMLElement * child in group.children) { - if(child.attributes.count == 0) { - continue; - } - - // straight add on - NSDictionary * atts = [self intersectableAttributes:IJSVGElementAttributeDictionary(child) - inheritableAttributes:inheritableAttributes]; - if(intersection.count == 0) { - intersection = atts; - } else { - - // work out the intersection - NSDictionary * dict = nil; - dict = [self intersectionInheritableAttributes:intersection - currentAttributes:atts - inheritableAttributes:inheritableAttributes]; - - // break if nothing - if(dict == nil) { - continue; - } - - // add them - intersection = dict; - } - // add the child to array - [intersected addObject:child]; + NSDictionary * dict = [self intersectionInheritableAttributes:intersection + currentAttributes:attributes + inheritableAttributes:inheritableAttributes]; + + for(NSString * attributeToRemove in dict.allKeys) { + [element removeAttributeForName:attributeToRemove]; + } + if(dict != nil) { + [grouped addObject:element]; + } + + if(dict == nil || counter == size) { + if(grouped.count > 1) { + NSXMLElement * groupElement = [[[NSXMLElement alloc] initWithName:@"g"] autorelease]; + NSXMLElement * lastElement = (NSXMLElement *)grouped.lastObject; + NSInteger index = lastElement.index; + [parentElement replaceChildAtIndex:index withNode:groupElement]; + for(NSXMLElement * elementToGroup in grouped) { + [elementToGroup detach]; + [groupElement addChild:elementToGroup]; } - - if(intersected.count != 0 && hasClip == NO) { - for(NSXMLElement * child in intersected) { - for(NSString * attributeName in intersection.allKeys) { - // remove attribute - [child removeAttributeForName:attributeName]; - // add the - NSDictionary * atts = nil; - atts = @{attributeName:intersection[attributeName]}; - IJSVGApplyAttributesToElement(atts, group); - } - } + IJSVGApplyAttributesToElement(intersection, groupElement); + } else { + if(grouped.count == 1) { + NSXMLElement * onlyElement = (NSXMLElement *)grouped.lastObject; + IJSVGApplyAttributesToElement(intersection, onlyElement); } } + intersection = @{}; + [grouped removeAllObjects]; } + + counter++; } } @@ -406,6 +522,10 @@ NSString * IJSVGHash(NSString * key) { for(NSString * key in newAttributes.allKeys) { // make sure they are the same and // they are inheritable + if([currentAttributes objectForKey:key] == nil) { + return nil; + } + if([currentAttributes objectForKey:key] != nil && [inheritableAtts containsObject:key] && [newAttributes[key] isEqualToString:currentAttributes[key]]) { @@ -478,7 +598,7 @@ NSString * IJSVGHash(NSString * key) { [(NSXMLElement *)n.parent removeChildAtIndex:n.index]; } } - + } - (void)_collapseGroups @@ -486,11 +606,10 @@ NSString * IJSVGHash(NSString * key) { NSArray * groups = [_dom nodesForXPath:@"//g" error:nil]; const NSArray * inheritable = IJSVGInheritableAttributes(); for(NSXMLElement * group in groups) { - + // dont do anything due to it being referenced - if([group attributeForName:@"id"] != nil || - [group attributeForName:@"transform"] != nil) { - continue; + if([group attributeForName:@"id"] != nil) { + return; } if(group.attributes.count != 0 && group.children.count == 1) { @@ -501,31 +620,35 @@ NSString * IJSVGHash(NSString * key) { continue; } - for(NSXMLNode * gAttribute in group.attributes) { - - // if it just doesnt have the attriute, just add it - if([child attributeForName:gAttribute.name] == NO) { - // remove first, or throws a wobbly - [group removeAttributeForName:gAttribute.name]; - [child addAttribute:gAttribute]; - - } else if([gAttribute.name isEqualToString:@"transform"]) { - - // transform requires concatination - NSXMLNode * childTransform = [child attributeForName:@"transform"]; - childTransform.stringValue = [NSString stringWithFormat:@"%@ %@", - gAttribute.stringValue, childTransform.stringValue]; - - } else if([inheritable containsObject:gAttribute.name] == NO) { - - // if its not inheritable, only remove it if its not equal - NSXMLNode * aAtt = [child attributeForName:gAttribute.name]; - if(aAtt == nil || (aAtt != nil && [aAtt.stringValue isEqualToString:gAttribute.stringValue] == NO)) { - continue; - } - } - [group removeAttributeForName:gAttribute.name]; - } + for(NSXMLNode * gAttribute in group.attributes) { + + // if it just doesnt have the attriute, just add it + if([child attributeForName:gAttribute.name] == NO) { + // remove first, or throws a wobbly + [group removeAttributeForName:gAttribute.name]; + [child addAttribute:gAttribute]; + } else if([gAttribute.name isEqualToString:@"transform"]) { + // transform requires concatination + NSXMLNode * childTransform = [child attributeForName:@"transform"]; + childTransform.stringValue = [NSString stringWithFormat:@"%@ %@", + gAttribute.stringValue, childTransform.stringValue]; + + } else if([inheritable containsObject:gAttribute.name] == NO) { + // if its not inheritable, only remove it if its not equal + NSXMLNode * aAtt = [child attributeForName:gAttribute.name]; + if(aAtt == nil || (aAtt != nil && [aAtt.stringValue isEqualToString:gAttribute.stringValue] == NO)) { + continue; + } + } + [group removeAttributeForName:gAttribute.name]; + } + + // remove the group as its useless! + if(group.attributes.count == 0) { + [child detach]; + [(NSXMLElement *)group.parent replaceChildAtIndex:group.index + withNode:child]; + } } } } @@ -577,7 +700,7 @@ NSString * IJSVGHash(NSString * key) { element.name = @"path"; NSDictionary * atts = @{@"d":data, - @"id":[NSString stringWithFormat:@"path-%ld",(++_pathCount)]}; + @"id":[self generateID]}; IJSVGApplyAttributesToElement(atts, element); // store it against the def @@ -648,39 +771,13 @@ NSString * IJSVGHash(NSString * key) { - (void)applyTransformToElement:(NSXMLElement *)element fromLayer:(IJSVGLayer *)layer { - // dont do anything, they are the same - CGFloat x = layer.frame.origin.x; - CGFloat y = layer.frame.origin.y; - - // check for x or y..they must be included - if(CGAffineTransformEqualToTransform(layer.affineTransform, CGAffineTransformIdentity) && - x == 0.f && y == 0.f) { + CGAffineTransform transform = layer.affineTransform; + if(CGAffineTransformEqualToTransform(transform, CGAffineTransformIdentity) == YES) { return; } - // construct the matrix - CGAffineTransform transform = layer.affineTransform; - - // was there already x and y transform? - BOOL hasXTransform = transform.tx != 0.f; - BOOL hasYTransform = transform.ty != 0.f; - - // move the x and y position - if(x != 0.f || y != 0.f) { - transform = CGAffineTransformConcat( transform, CGAffineTransformMakeTranslation( x, y )); - } - - // x and y were not given so just transform - if(hasXTransform) { - transform.tx /= 2.f; - } - if(hasYTransform) { - transform.ty /= 2.f; - } - // append the string - NSArray * transformArray = [IJSVGTransform affineTransformToSVGTransformAttributeString:transform]; - NSString * transformStr = [transformArray componentsJoinedByString:@" "]; + NSString * transformStr = [IJSVGTransform affineTransformToSVGMatrixString:transform]; // apply it to the node IJSVGApplyAttributesToElement(@{@"transform":transformStr},element); @@ -708,6 +805,9 @@ NSString * IJSVGHash(NSString * key) { - (NSString *)base64EncodedStringFromCGImage:(CGImageRef)image { + if(image == nil) { + return nil; + } // convert the CGImage into an NSImage NSBitmapImageRep * rep = [[[NSBitmapImageRep alloc] initWithCGImage:image] autorelease]; @@ -733,7 +833,7 @@ NSString * IJSVGHash(NSString * key) { patternElement.name = @"pattern"; NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; - dict[@"id"] = [NSString stringWithFormat:@"pattern-%ld",(++_patternCount)]; + dict[@"id"] = [self generateID]; dict[@"width"] = IJSVGShortFloatString(layer.patternNode.width.value); dict[@"height"] = IJSVGShortFloatString(layer.patternNode.height.value); @@ -780,12 +880,12 @@ NSString * IJSVGHash(NSString * key) { toElement:(NSXMLElement *)element { IJSVGGradient * gradient = layer.gradient; - NSString * gradKey = [NSString stringWithFormat:@"gradient-%ld",(++_gradCount)]; + NSString * gradKey = [self generateID]; NSXMLElement * gradientElement = [[[NSXMLElement alloc] init] autorelease]; // work out linear gradient if([gradient isKindOfClass:[IJSVGLinearGradient class]]) { - + IJSVGLinearGradient * lGradient = (IJSVGLinearGradient *)gradient; gradientElement.name = @"linearGradient"; NSDictionary * dict = @{@"id":gradKey, @@ -838,12 +938,21 @@ NSString * IJSVGHash(NSString * key) { atts[@"offset"] = [NSString stringWithFormat:@"%g%%",(location*100)]; // add the color - atts[@"stop-color"] = [IJSVGColor colorStringFromColor:aColor - forceHex:YES]; + NSString * stopColor = [IJSVGColor colorStringFromColor:aColor + forceHex:YES + allowShorthand:YES]; + if([stopColor isEqualToString:@"#000000"] == NO) { + atts[@"stop-color"] = stopColor; + } // we need to work out the color at this point, annoyingly... CGFloat opacity = aColor.alphaComponent; - atts[@"stop-opacity"] = [NSString stringWithFormat:@"%g",opacity]; + + // is opacity is equal to 1, no need to add it as spec + // defaults opacity to 1 anyway :) + if(opacity != 1.f) { + atts[@"stop-opacity"] = IJSVGShortFloatStringWithPrecision(opacity, 2); + } // att the attributes @@ -859,24 +968,9 @@ NSString * IJSVGHash(NSString * key) { // work out the transform NSArray * transforms = layer.gradient.transforms; if(transforms.count != 0.f) { - CGAffineTransform transform = [self affineTransformFromTransforms:transforms]; - - // work out if there is x and y translate - BOOL hasXTransform = transform.tx != 0.f; - BOOL hasYTransform = transform.ty != 0.f; - - // x and y were not given so just transform - if(hasXTransform) { - transform.tx /= 2.f; - } - if(hasYTransform) { - transform.ty /= 2.f; - } - - // apply the attributes - NSArray * transforms = [IJSVGTransform affineTransformToSVGTransformAttributeString:transform]; - NSString * tString = [transforms componentsJoinedByString:@" "]; - IJSVGApplyAttributesToElement(@{@"gradientTransform":tString}, gradientElement); + CGAffineTransform transform = IJSVGConcatTransforms(transforms); + NSString * transformString = [IJSVGTransform affineTransformToSVGMatrixString:transform]; + IJSVGApplyAttributesToElement(@{@"gradientTransform":transformString}, gradientElement); } // add it to the element passed in @@ -885,7 +979,7 @@ NSString * IJSVGHash(NSString * key) { // fill opacity if(layer.opacity != 1.f) { - IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatString(layer.opacity)}, element); + IJSVGApplyAttributesToElement(@{@"fill-opacity":IJSVGShortFloatStringWithPrecision(layer.opacity,2)}, element); } } else { IJSVGApplyAttributesToElement(@{@"stroke":IJSVGHashURL(gradKey)}, element); @@ -905,13 +999,16 @@ NSString * IJSVGHash(NSString * key) { fromParent:(NSXMLElement *)parent { NSString * base64String = [self base64EncodedStringFromCGImage:(CGImageRef)layer.contents]; + if(base64String == nil || layer.contents == nil) { + return nil; + } // image element for the SVG NSXMLElement * imageElement = [[[NSXMLElement alloc] init] autorelease]; imageElement.name = @"image"; NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; - dict[@"id"] = [NSString stringWithFormat:@"image-%ld",(++_imageCount)]; + dict[@"id"] = [self generateID]; dict[@"width"] = IJSVGShortFloatString(layer.frame.size.width); dict[@"height"] = IJSVGShortFloatString(layer.frame.size.height); dict[@"xlink:href"] = base64String; @@ -936,10 +1033,17 @@ NSString * IJSVGHash(NSString * key) { e.name = @"path"; CGPathRef path = layer.path; + // copy the path as we want to translate + CGAffineTransform trans = CGAffineTransformMakeTranslation(layer.originalPathOrigin.x, + layer.originalPathOrigin.y); + CGPathRef transformPath = CGPathCreateCopyByTransformingPath(path, &trans); + NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; // path - dict[@"d"] = [self pathFromCGPath:path]; + dict[@"d"] = [self pathFromCGPath:transformPath]; + + CGPathRelease(transformPath); // work out even odd rule if([layer.fillRule isEqualToString:kCAFillRuleNonZero] == NO) { @@ -996,7 +1100,7 @@ NSString * IJSVGHash(NSString * key) { parentLayer:(IJSVGPatternLayer *)layer stroke:YES toElement:e]; - + } else if(strokeLayer.strokeColor != nil) { NSColor * strokeColor = [NSColor colorWithCGColor:strokeLayer.strokeColor]; NSString * strokeColorString = [IJSVGColor colorStringFromColor:strokeColor]; @@ -1004,6 +1108,10 @@ NSString * IJSVGHash(NSString * key) { // could be none if(strokeColorString != nil) { dict[@"stroke"] = strokeColorString; + if([strokeColorString isEqualToString:@"none"] == YES) { + // remove the stroke width as its completely useless + [dict removeObjectForKey:@"stroke-width"]; + } } } @@ -1045,7 +1153,7 @@ NSString * IJSVGHash(NSString * key) { } } - + // apply the attributes IJSVGApplyAttributesToElement(dict, e); @@ -1062,7 +1170,7 @@ NSString * IJSVGHash(NSString * key) { // opacity if(layer.opacity != 1.f) { - dict[@"opacity"] = IJSVGShortFloatString(layer.opacity); + dict[@"opacity"] = IJSVGShortFloatStringWithPrecision(layer.opacity,2); } // blendmode - we only every apply a stylesheet blend mode @@ -1111,7 +1219,7 @@ NSString * IJSVGHash(NSString * key) { mask.name = @"mask"; // create the key - NSString * maskKey = [NSString stringWithFormat:@"mask-%ld",(++_maskCount)]; + NSString * maskKey = [self generateID]; NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease]; dict[@"id"] = maskKey; dict[@"maskContentUnits"] = @"userSpaceOnUse"; @@ -1229,3 +1337,4 @@ void IJSVGExporterPathCaller(void * info, const CGPathElement * pathElement) { } @end + diff --git a/source/IJSVGGradient.h b/source/IJSVGGradient.h index 40854e1..3da49d4 100644 --- a/source/IJSVGGradient.h +++ b/source/IJSVGGradient.h @@ -13,9 +13,6 @@ @interface IJSVGGradient : IJSVGNode @property ( nonatomic, retain ) NSGradient * gradient; -@property ( nonatomic, assign ) CGFloat angle; -@property ( nonatomic, assign ) CGPoint startPoint; -@property ( nonatomic, assign ) CGPoint endPoint; @property ( nonatomic, assign ) CGGradientRef CGGradient; @property ( nonatomic, retain ) IJSVGUnitLength * x1; @property ( nonatomic, retain ) IJSVGUnitLength * x2; @@ -26,6 +23,8 @@ colors:(NSArray **)someColors; - (CGGradientRef)CGGradient; - (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect; + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox; @end diff --git a/source/IJSVGGradient.m b/source/IJSVGGradient.m index 2b7f4c4..6012d9b 100644 --- a/source/IJSVGGradient.m +++ b/source/IJSVGGradient.m @@ -11,7 +11,6 @@ @implementation IJSVGGradient @synthesize gradient, CGGradient; -@synthesize angle, startPoint, endPoint; @synthesize x1, x2, y1, y2; - (void)dealloc @@ -31,8 +30,6 @@ { IJSVGGradient * clone = [super copyWithZone:zone]; clone.gradient = [[self.gradient copy] autorelease]; - clone.startPoint = self.startPoint; - clone.endPoint = self.endPoint; return clone; } @@ -40,14 +37,11 @@ colors:(NSArray **)someColors { // find each stop element - NSArray * stops = [element nodesForXPath:@"stop" - error:nil]; - + NSArray * stops = [element children]; NSMutableArray * colors = [[[NSMutableArray alloc] initWithCapacity:stops.count] autorelease]; CGFloat * stopsParams = (CGFloat *)malloc(stops.count*sizeof(CGFloat)); NSInteger i = 0; - for( NSXMLElement * stop in stops ) - { + for( NSXMLElement * stop in stops ) { // find the offset CGFloat offset = [stop attributeForName:@"offset"].stringValue.floatValue; if( offset > 1.f ) { @@ -134,7 +128,9 @@ } - (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox { } diff --git a/source/IJSVGGradientLayer.h b/source/IJSVGGradientLayer.h index f8bab11..32d5acc 100644 --- a/source/IJSVGGradientLayer.h +++ b/source/IJSVGGradientLayer.h @@ -15,6 +15,10 @@ } +@property (nonatomic, assign) CGRect viewBox; @property (nonatomic, retain) IJSVGGradient * gradient; +@property (nonatomic, assign) CGAffineTransform absoluteTransform; +@property (nonatomic, assign) CGRect objectRect; @end + diff --git a/source/IJSVGGradientLayer.m b/source/IJSVGGradientLayer.m index 4c09924..c28c0df 100644 --- a/source/IJSVGGradientLayer.m +++ b/source/IJSVGGradientLayer.m @@ -10,7 +10,10 @@ @implementation IJSVGGradientLayer +@synthesize viewBox; @synthesize gradient; +@synthesize absoluteTransform; +@synthesize objectRect; - (void)dealloc { @@ -36,7 +39,14 @@ } // draw the gradient - [self.gradient drawInContextRef:ctx rect:self.bounds]; + CGAffineTransform trans = CGAffineTransformMakeTranslation(-CGRectGetMinX(objectRect), + -CGRectGetMinY(objectRect)); + CGAffineTransform transform = CGAffineTransformConcat(absoluteTransform,trans); + + [self.gradient drawInContextRef:ctx + objectRect:objectRect + absoluteTransform:transform + viewPort:self.viewBox]; } @end diff --git a/source/IJSVGImage.m b/source/IJSVGImage.m index 0b7d488..6b7cb3a 100644 --- a/source/IJSVGImage.m +++ b/source/IJSVGImage.m @@ -22,10 +22,16 @@ - (void)loadFromBase64EncodedString:(NSString *)encodedString { + if([encodedString hasPrefix:@"data:"]) { + encodedString = [encodedString stringByReplacingOccurrencesOfString:@"\\s+" + withString:@"" + options:NSRegularExpressionSearch + range:NSMakeRange(0, encodedString.length)]; + } NSURL * URL = [NSURL URLWithString:encodedString]; NSData * data = [NSData dataWithContentsOfURL:URL]; - // no data, jsut ignore...invalid probably + // no data, just ignore...invalid probably if(data == nil) { return; } diff --git a/source/IJSVGImageLayer.m b/source/IJSVGImageLayer.m index 83fff81..e20c3ad 100644 --- a/source/IJSVGImageLayer.m +++ b/source/IJSVGImageLayer.m @@ -34,11 +34,21 @@ // set the frame, simple stuff self.frame = (CGRect){ .origin = CGPointZero, - .size = CGSizeMake( CGImageGetWidth(imageRef), - CGImageGetHeight(imageRef)) + .size = CGSizeMake(CGImageGetWidth(imageRef), + CGImageGetHeight(imageRef)) }; } return self; } +- (void)setNeedsDisplay +{ + // swap the content around on call + // because set needs display discards previous + // content - yolo! + id oldContent = self.contents; + [super setNeedsDisplay]; + self.contents = oldContent; +} + @end diff --git a/source/IJSVGLayer.m b/source/IJSVGLayer.m index 4ae5dbb..8faee8b 100644 --- a/source/IJSVGLayer.m +++ b/source/IJSVGLayer.m @@ -32,7 +32,6 @@ IJSVGEndTransactionLock(); } - + (NSArray *)deepestSublayersOfLayer:(CALayer *)layer { NSMutableArray * arr = [[[NSMutableArray alloc] init] autorelease]; @@ -80,14 +79,14 @@ - (void)setBackingScaleFactor:(CGFloat)newFactor { - if(self.backingScaleFactor == newFactor) { - return; - } - backingScaleFactor = newFactor; - self.contentsScale = newFactor; - self.rasterizationScale = newFactor; - [self setNeedsDisplay]; -} + if(self.backingScaleFactor == newFactor) { + return; + } + backingScaleFactor = newFactor; + self.contentsScale = newFactor; + self.rasterizationScale = newFactor; + [self setNeedsDisplay]; +} - (void)_customRenderInContext:(CGContextRef)ctx { @@ -176,18 +175,6 @@ return; } [self _customRenderInContext:ctx]; -} - -- (CGPoint)absoluteOrigin -{ - CGPoint point = CGPointZero; - CALayer * pLayer = self; - while(pLayer != nil) { - point.x += pLayer.frame.origin.x; - point.y += pLayer.frame.origin.y; - pLayer = pLayer.superlayer; - } - return point; } @end diff --git a/source/IJSVGLayerTree.m b/source/IJSVGLayerTree.m index 04837a8..6ee8a8d 100644 --- a/source/IJSVGLayerTree.m +++ b/source/IJSVGLayerTree.m @@ -67,44 +67,50 @@ layer = [self layerForImage:(IJSVGImage *)node]; } - // apply any basic defaults - [self applyDefaultsToLayer:layer - fromNode:node]; + [self applyDefaultsToLayer:layer fromNode:node]; - return [self proposedLayerAfterApplyingTransforms:layer - transforms:node.transforms]; + // create the new layer + layer = [self applyTransforms:node.transforms + toLayer:layer]; + + return layer; } -- (IJSVGLayer *)proposedLayerAfterApplyingTransforms:(IJSVGLayer *)layer - transforms:(NSArray *)transforms +- (IJSVGLayer *)applyTransforms:(NSArray *)transforms + toLayer:(IJSVGLayer *)layer + { + + // do some magic transform + if(transforms.count == 0) { + return layer; + } + // add any transforms - if(transforms.count != 0) { - IJSVGLayer * topLayer = nil; - IJSVGLayer * parentLayer = nil; + IJSVGLayer * topLayer = nil; + IJSVGLayer * parentLayer = nil; + + for(IJSVGTransform * transform in transforms) { + // make sure we apply the transform to the parent + // so they stack + IJSVGGroupLayer * childLayer = [[[IJSVGGroupLayer alloc] init] autorelease]; + childLayer.affineTransform = transform.CGAffineTransform; - for(IJSVGTransform * transform in transforms) { - // make sure we apply the transform to the parent - // so they stack - IJSVGGroupLayer * childLayer = [[[IJSVGGroupLayer alloc] init] autorelease]; - childLayer.affineTransform = transform.CGAffineTransform; - - // add it to the parent layer - if(parentLayer != nil) { - [parentLayer addSublayer:childLayer]; - } else { - // make sure we keep track of the top most layer - topLayer = childLayer; - } - - // reset parent layer to the new child - parentLayer = childLayer; + // add it to the parent layer + if(parentLayer != nil) { + [parentLayer addSublayer:childLayer]; + } else { + // make sure we keep track of the top most layer + topLayer = childLayer; } - // swap the layer around - [parentLayer addSublayer:layer]; - layer = topLayer; + // reset parent layer to the new child + parentLayer = childLayer; } + + // swap the layer around + [parentLayer addSublayer:layer]; + layer = topLayer; return layer; } @@ -127,14 +133,25 @@ layer.hidden = YES; } + CGRect frame = layer.frame; + frame.origin.x += [node.x computeValue:frame.size.width]; + frame.origin.y += [node.y computeValue:frame.size.height]; + layer.frame = frame; } - (IJSVGLayer *)layerForImage:(IJSVGImage *)image { IJSVGImageLayer * layer = [[[IJSVGImageLayer alloc] initWithCGImage:image.CGImage] autorelease]; - layer.frame = CGRectMake(image.x.value, image.y.value, image.width.value, image.height.value); layer.affineTransform = CGAffineTransformConcat(layer.affineTransform, CGAffineTransformMakeScale( 1.f, -1.f)); + + // make sure we set the width and height correctly, + // as this may not be exactly the same as the size of the + // given image + CGRect frame = layer.frame; + frame.size.width = image.width.value; + frame.size.height = image.height.value; + layer.frame = frame; return layer; } @@ -145,10 +162,7 @@ [groupLayer addSublayer:[self layerForNode:node]]; } groupLayer.frame = (CGRect){ - .origin = (CGPoint){ - .x = group.x.value, - .y = group.y.value - }, + .origin = CGPointZero, .size = (CGSize){ .width = group.width.value, .height = group.height.value @@ -180,16 +194,21 @@ } - (IJSVGShapeLayer *)basicLayerForPath:(IJSVGPath *)path + originalBoundingBox:(CGRect *)originalBoundingBox { // setup path and layer IJSVGShapeLayer * layer = [[[IJSVGShapeLayer alloc] init] autorelease]; CGPathRef introPath = [path newPathRefByAutoClosingPath:NO]; - CGRect bounds = [self correctedBounds:CGPathGetBoundingBox(introPath)]; + *originalBoundingBox = CGRectIntegral(CGPathGetBoundingBox(introPath)); + layer.originalPathOrigin = (*originalBoundingBox).origin; + + CGRect bounds = [self correctedBounds:*originalBoundingBox]; // zero back the path CGAffineTransform trans = CGAffineTransformMakeTranslation(-bounds.origin.x, -bounds.origin.y); + CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(introPath, &trans); layer.path = transformedPath; @@ -198,9 +217,7 @@ CGPathRelease(introPath); // set the bounds - bounds.origin.x += path.x.value; - bounds.origin.y += path.y.value; - layer.frame = bounds; + layer.frame = CGRectIntegral(bounds); // basic fill color and rule layer.fillColor = nil; @@ -217,6 +234,26 @@ return mask; } +- (CGAffineTransform)absoluteTransform:(IJSVGNode *)node +{ + CGAffineTransform parentAbsoluteTransform = CGAffineTransformIdentity; + IJSVGNode * intermediateNode = node.intermediateParentNode; + node = node.parentNode; + while(node != nil) { + // intermediateParent should be skipped as these are elements + // created by use statements and are technically fake elements, but the + // spec says use them for the transforms, yolo! + if(node == intermediateNode) { + node = node.parentNode; + continue; + } + CGAffineTransform trans = IJSVGConcatTransforms(node.transforms); + parentAbsoluteTransform = CGAffineTransformConcat(trans,parentAbsoluteTransform); + node = node.parentNode; + } + return parentAbsoluteTransform; +} + - (IJSVGLayer *)layerForPath:(IJSVGPath *)path { // is there a sub SVG? @@ -226,7 +263,13 @@ } // garb the basic shape layer - IJSVGShapeLayer * layer = [self basicLayerForPath:path]; + CGRect originalShapeBounds; + IJSVGShapeLayer * layer = [self basicLayerForPath:path + originalBoundingBox:&originalShapeBounds]; + + BOOL hasStroke = (path.strokeColor != nil || + path.strokePattern != nil || + path.strokeGradient != nil); // any gradient? if(self.fillColor == nil && path.fillGradient != nil) { @@ -234,15 +277,12 @@ // create the gradient IJSVGGradientLayer * gradLayer = [self gradientLayerForLayer:layer gradient:path.fillGradient - fromNode:path]; + fromNode:path + objectRect:originalShapeBounds + shouldMask:YES]; // add the gradient and set it against the layer [layer addSublayer:gradLayer]; - - // apply offsets - [self applyOffsetsToLayer:gradLayer - fromNode:path.fillGradient]; - layer.gradientFillLayer = gradLayer; } else if(self.fillColor == nil && path.fillPattern != nil) { @@ -264,6 +304,9 @@ // only use the global if its set and the current colors // alpha channel is not 0.f, otherwise its a blank clear color, // aka, not filled in + + + NSColor * fColor = path.fillColor; BOOL hasColor = (fColor.alphaComponent == 0.f || fColor == nil) == NO; BOOL hasFill = path.fillPattern != nil || path.fillGradient != nil; @@ -292,9 +335,7 @@ } // stroke it - if(path.strokeColor != nil || - path.strokePattern != nil || - path.strokeGradient != nil) { + if(hasStroke == YES) { // load the stroke layer IJSVGStrokeLayer * strokeLayer = [self strokeLayer:layer @@ -311,7 +352,8 @@ // create the gradient IJSVGGradientLayer * gradLayer = [self gradientStrokeLayerForLayer:layer gradient:path.strokeGradient - fromNode:path]; + fromNode:path + objectRect:originalShapeBounds]; moveStrokeLayer = YES; gradLayer.mask = strokeLayer; @@ -402,33 +444,19 @@ - (IJSVGGradientLayer *)gradientStrokeLayerForLayer:(IJSVGShapeLayer *)layer gradient:(IJSVGGradient *)gradient fromNode:(IJSVGNode *)path + objectRect:(CGRect)objectRect { // the gradient drawing layer - IJSVGGradientLayer * gradLayer = [[[IJSVGGradientLayer alloc] init] autorelease]; - gradLayer.gradient = gradient; - - // is there a fill opacity? - if(path.fillOpacity.value != 0.f) { - gradLayer.opacity = path.fillOpacity.value; - } + IJSVGGradientLayer * gradLayer = [self gradientLayerForLayer:layer + gradient:gradient + fromNode:path + objectRect:objectRect + shouldMask:NO]; // set the bounds CGRect bounds = CGPathGetBoundingBox(layer.path); bounds = [self correctBounds:bounds forStrokedPath:path]; gradLayer.frame = bounds; - - // display it - [gradLayer setNeedsDisplay]; - - if(path.fillGradient.units == IJSVGUnitUserSpaceOnUse) { - // move back if needed - gradLayer.frame = (CGRect){ - .size = gradLayer.frame.size, - .origin = CGPointMake(-fabs(gradLayer.frame.origin.x), - -fabs(gradLayer.frame.origin.y)) - }; - } - return gradLayer; } @@ -436,34 +464,28 @@ - (IJSVGGradientLayer *)gradientLayerForLayer:(IJSVGShapeLayer *)layer gradient:(IJSVGGradient *)gradient fromNode:(IJSVGNode *)path + objectRect:(CGRect)objectRect + shouldMask:(BOOL)shouldMask { - // add the mask - IJSVGShapeLayer * mask = [self layerMaskFromLayer:layer - fromNode:path]; - // the gradient drawing layer IJSVGGradientLayer * gradLayer = [[[IJSVGGradientLayer alloc] init] autorelease]; - gradLayer.frame = CGPathGetBoundingBox(((IJSVGShapeLayer *)layer).path); + gradLayer.viewBox = self.viewBox; + gradLayer.frame = layer.bounds; gradLayer.gradient = gradient; - gradLayer.mask = mask; + gradLayer.absoluteTransform = [self absoluteTransform:path]; + gradLayer.objectRect = CGRectApplyAffineTransform(objectRect, gradLayer.absoluteTransform); + + if(shouldMask == YES) { + // add the mask + IJSVGShapeLayer * mask = [self layerMaskFromLayer:layer + fromNode:path]; + gradLayer.mask = mask; + } // is there a fill opacity? if(path.fillOpacity.value != 0.f) { gradLayer.opacity = path.fillOpacity.value; } - - // display it - [gradLayer setNeedsDisplay]; - - if(path.fillGradient.units == IJSVGUnitUserSpaceOnUse) { - // move back if needed - gradLayer.frame = (CGRect){ - .size = gradLayer.frame.size, - .origin = CGPointMake(-fabs(gradLayer.frame.origin.x), - -fabs(gradLayer.frame.origin.y)) - }; - } - return gradLayer; } @@ -551,7 +573,7 @@ } - (IJSVGStrokeLayer *)strokeLayer:(IJSVGShapeLayer *)layer - fromNode:(IJSVGNode *)path + fromNode:(IJSVGPath *)path { // same as fill, dont use global if the alpha is 0.f, but do use it // if there is a pattern or gradient @@ -742,4 +764,17 @@ } } ++ (void)log:(IJSVGLayer *)layer + depth:(NSInteger)depth { + NSLog(@"%@%@: %@, Transforms: %@",[@"" stringByPaddingToLength:depth + withString:@"\t" + startingAtIndex:0],layer, + NSStringFromRect(layer.frame), + [IJSVGTransform affineTransformToSVGTransformAttributeString:layer.affineTransform]); + for(IJSVGLayer * sublayer in layer.sublayers) { + [self log:(IJSVGLayer *)sublayer + depth:depth++]; + } +} + @end diff --git a/source/IJSVGLinearGradient.h b/source/IJSVGLinearGradient.h index 57c8d7d..5601bd0 100644 --- a/source/IJSVGLinearGradient.h +++ b/source/IJSVGLinearGradient.h @@ -12,8 +12,6 @@ @interface IJSVGLinearGradient : IJSVGGradient + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGLinearGradient *)aGradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint; + gradient:(IJSVGLinearGradient *)aGradient; @end diff --git a/source/IJSVGLinearGradient.m b/source/IJSVGLinearGradient.m index f8ab845..76007c2 100644 --- a/source/IJSVGLinearGradient.m +++ b/source/IJSVGLinearGradient.m @@ -13,8 +13,6 @@ + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGLinearGradient *)aGradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint { CGFloat px1 = [[element attributeForName:@"x1"] stringValue].floatValue; @@ -25,10 +23,8 @@ // work out each coord, and work out if its a % or not // annoyingly we need to check them all against each other -_- BOOL isPercent = NO; - if(px1 <= 1.f && px2 <= 1.f && py1 <= 1.f && py2 <= 1.f) { - isPercent = YES; - } else if((px1 >= 0.f && px1 <= 1.f) && (px2 >= 0.f && px2 <= 1.f) && - (py1 >= 0.f && py1 <= 1.f) && (py2 >= 0.f && py2 <= 1.f)) { + if((px1 >= 0.f && px1 <= 1.f) && (px2 >= 0.f && px2 <= 1.f) && + (py1 >= 0.f && py1 <= 1.f) && (py2 >= 0.f && py2 <= 1.f)) { isPercent = YES; } @@ -62,32 +58,53 @@ } - (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox { - // grab the start and end point - CGPoint aStartPoint = (CGPoint){ - .x = [self.x1 computeValue:rect.size.width], - .y = [self.y1 computeValue:rect.size.height] + BOOL inUserSpace = self.units == IJSVGUnitUserSpaceOnUse; + + CGPoint gradientStartPoint = CGPointZero; + CGPoint gradientEndPoint = CGPointZero; + CGAffineTransform absTransform = absoluteTransform; + CGAffineTransform selfTransform = IJSVGConcatTransforms(self.transforms); + +#pragma mark User Space On Use + CGContextSaveGState(ctx); + { + if(inUserSpace == YES) { + CGFloat width = CGRectGetWidth(viewBox); + CGFloat height = CGRectGetHeight(viewBox); + gradientStartPoint = CGPointMake([self.x1 computeValue:width], + [self.y1 computeValue:height]); + + gradientEndPoint = CGPointMake([self.x2 computeValue:width], + [self.y2 computeValue:height]); + + // transform absolute - due to user space + CGContextConcatCTM(ctx, absTransform); + } else { +#pragma mark Object Bounding Box + CGFloat width = CGRectGetWidth(objectRect); + CGFloat height = CGRectGetHeight(objectRect); + gradientStartPoint = CGPointMake([self.x1 computeValue:width], + [self.y1 computeValue:height]); + + gradientEndPoint = CGPointMake([self.x2 computeValue:width], + [self.y2 computeValue:height]); + } + + // transform the context + CGContextConcatCTM(ctx, selfTransform); + + // draw the gradient + CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation| + kCGGradientDrawsAfterEndLocation; + + CGContextDrawLinearGradient(ctx, self.CGGradient, gradientStartPoint, + gradientEndPoint, options); }; - - CGPoint aEndPoint = (CGPoint){ - .x = [self.x2 computeValue:rect.size.width], - .y = [self.y2 computeValue:rect.size.height] - }; - - // convert the nsgradient to a CGGradient - CGGradientRef gRef = [self CGGradient]; - - // apply transform for each point - for( IJSVGTransform * transform in self.transforms ) { - CGAffineTransform trans = transform.CGAffineTransform; - aStartPoint = CGPointApplyAffineTransform(aStartPoint, trans); - aEndPoint = CGPointApplyAffineTransform(aEndPoint, trans); - } - - // draw the gradient - CGGradientDrawingOptions opt = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; - CGContextDrawLinearGradient(ctx, gRef, aStartPoint, aEndPoint, opt); + CGContextRestoreGState(ctx); } @end diff --git a/source/IJSVGNode.h b/source/IJSVGNode.h index 0adf80f..b3f3527 100644 --- a/source/IJSVGNode.h +++ b/source/IJSVGNode.h @@ -16,6 +16,7 @@ @class IJSVGGradient; @class IJSVGGroup; @class IJSVGPattern; +@class IJSVGTransform; typedef NS_ENUM( NSInteger, IJSVGNodeType ) { IJSVGNodeTypeGroup, @@ -120,12 +121,13 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; @property ( nonatomic, retain ) NSColor * strokeColor; @property ( nonatomic, copy ) NSString * identifier; @property ( nonatomic, assign ) IJSVGNode * parentNode; +@property ( nonatomic, assign ) IJSVGNode * intermediateParentNode; @property ( nonatomic, retain ) IJSVGGroup * clipPath; @property ( nonatomic, retain ) IJSVGGroup * mask; @property ( nonatomic, assign ) IJSVGWindingRule windingRule; @property ( nonatomic, assign ) IJSVGLineCapStyle lineCapStyle; @property ( nonatomic, assign ) IJSVGLineJoinStyle lineJoinStyle; -@property ( nonatomic, retain ) NSArray * transforms; +@property ( nonatomic, retain ) NSArray * transforms; @property ( nonatomic, retain ) IJSVGDef * def; @property ( nonatomic, retain ) IJSVGGradient * fillGradient; @property ( nonatomic, retain ) IJSVGPattern * fillPattern; diff --git a/source/IJSVGNode.m b/source/IJSVGNode.m index 8a03b5a..04a33d4 100644 --- a/source/IJSVGNode.m +++ b/source/IJSVGNode.m @@ -8,6 +8,7 @@ #import "IJSVGNode.h" #import "IJSVGDef.h" +#import "IJSVGUtils.h" @implementation IJSVGNode @@ -29,6 +30,7 @@ @synthesize opacity; @synthesize identifier; @synthesize parentNode; +@synthesize intermediateParentNode; @synthesize transforms; @synthesize windingRule; @synthesize def; @@ -131,6 +133,12 @@ if([string isEqualToString:@"tspan"] || kind == NSXMLTextKind) { return IJSVGNodeTypeTextSpan; } + + // are we commong HTML? - if so just treat as a group + if(IJSVGIsCommonHTMLElementName(string) == YES) { + return IJSVGNodeTypeGroup; + } + return IJSVGNodeTypeNotFound; } @@ -233,10 +241,12 @@ - (IJSVGDef *)defForID:(NSString *)anID { IJSVGDef * aDef = nil; - if( (aDef = [def defForID:anID]) != nil ) + if( (aDef = [def defForID:anID]) != nil ) { return aDef; - if( parentNode != nil ) + } + if( parentNode != nil ) { return [parentNode defForID:anID]; + } return nil; } @@ -248,7 +258,7 @@ // winding rule can inherit.. - (IJSVGWindingRule)windingRule { - if( windingRule == IJSVGWindingRuleInherit && parentNode != nil ) { + if(windingRule == IJSVGWindingRuleInherit && parentNode != nil) { return parentNode.windingRule; } return windingRule; @@ -256,20 +266,20 @@ - (IJSVGLineCapStyle)lineCapStyle { - if( lineCapStyle == IJSVGLineCapStyleInherit ) - { - if( parentNode != nil ) + if( lineCapStyle == IJSVGLineCapStyleInherit ) { + if( parentNode != nil ) { return parentNode.lineCapStyle; + } } return lineCapStyle; } - (IJSVGLineJoinStyle)lineJoinStyle { - if( lineJoinStyle == IJSVGLineJoinStyleInherit ) - { - if( parentNode != nil ) + if( lineJoinStyle == IJSVGLineJoinStyleInherit ) { + if( parentNode != nil ) { return parentNode.lineJoinStyle; + } } return lineJoinStyle; } @@ -327,8 +337,9 @@ // must be on the path, it can also be on the - (NSColor *)fillColor { - if( fillColor == nil && parentNode != nil ) + if( fillColor == nil && parentNode != nil ) { return parentNode.fillColor; + } return fillColor; } diff --git a/source/IJSVGParser.h b/source/IJSVGParser.h index 8365d39..5d6b68a 100644 --- a/source/IJSVGParser.h +++ b/source/IJSVGParser.h @@ -95,6 +95,7 @@ handleForeignObject:(IJSVGForeignObject *)foreignObject NSMutableDictionary * _defNodes; NSMutableDictionary * _baseDefNodes; NSMutableArray * _svgs; + NSMutableArray * _definedGroups; struct { unsigned int shouldHandleForeignObject: 1; diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index 907ed37..11d961e 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -45,6 +45,7 @@ [_parsedNodes release], _parsedNodes = nil; [_defNodes release], _defNodes = nil; [_baseDefNodes release], _baseDefNodes = nil; + [_definedGroups release], _definedGroups = nil; [_svgs release], _svgs = nil; [super dealloc]; } @@ -178,7 +179,8 @@ // parse common attributes on the SVG element [self _parseElementForCommonAttributes:svgElement - node:self]; + node:self + ignoreAttributes:nil]; // find the sizebox! NSXMLNode * attribute = nil; @@ -224,7 +226,8 @@ // now everything has been done we need to compute the style tree for(NSDictionary * dict in _parsedNodes) { [self _postParseElementForCommonAttributes:dict[@"element"] - node:dict[@"node"]]; + node:dict[@"node"] + ignoreAttributes:nil]; } // dont need the style sheet or the parsed nodes as this point @@ -235,6 +238,7 @@ - (void)_postParseElementForCommonAttributes:(NSXMLElement *)element node:(IJSVGNode *)node + ignoreAttributes:(NSArray *)ignoredAttributes { // first of all, compute a style sheet @@ -244,6 +248,9 @@ // attribute helpers typedef void (^cp)(NSString *); void (^attr)(const NSString *, cp) = ^(NSString * key, cp block) { + if([ignoredAttributes containsObject:key]) { + return; + } NSString * v = [element attributeForName:key].stringValue ?: [style property:key]; if(v != nil && v.length != 0) { @@ -397,12 +404,15 @@ node.fillPattern = (IJSVGPattern *)obj; } } else { - // its a color node.fillColor = [IJSVGColor colorFromString:value]; - if(node.fillOpacity.value != 1.f) { - node.fillColor = [IJSVGColor changeAlphaOnColor:node.fillColor - to:node.fillOpacity.value]; - } + } + }); + + // fill opacity + attr(IJSVGAttributeFillOpacity, ^(NSString * value) { + if(node.fillOpacity.value != 1.f) { + node.fillColor = [IJSVGColor changeAlphaOnColor:node.fillColor + to:node.fillOpacity.value]; } }); @@ -425,22 +435,34 @@ } - (id)definedObjectForID:(NSString *)anID + xmlElement:(NSXMLElement **)element { // check base def nodes first, then check rest of document NSXMLElement * parseElement = _baseDefNodes[anID] ?: _defNodes[anID]; if(parseElement != nil) { - // parse the element - IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; - // parse the block + if(element != nil && element != NULL) { + *element = parseElement; + } + IJSVGGroup * group = [[[IJSVGGroup alloc] init] autorelease]; [self _parseBaseBlock:parseElement intoGroup:group def:NO]; + if(_definedGroups == nil) { + _definedGroups = [[NSMutableArray alloc] init]; + } + [_definedGroups addObject:group]; return [group defForID:anID]; } return nil; } +- (id)definedObjectForID:(NSString *)anID +{ + return [self definedObjectForID:anID + xmlElement:nil]; +} + - (BOOL)isFont { return [_glyphs count] != 0; @@ -476,9 +498,11 @@ - (void)_parseElementForCommonAttributes:(NSXMLElement *)element node:(IJSVGNode *)node + ignoreAttributes:(NSArray *)ignoredAttributes { [self _postParseElementForCommonAttributes:element - node:node]; + node:node + ignoreAttributes:ignoredAttributes]; } - (void)_setupDefaultsForNode:(IJSVGNode *)node @@ -511,7 +535,7 @@ for(NSXMLElement * element in anElement.children) { // not a def - if([IJSVGNode typeForString:element.name + if([IJSVGNode typeForString:element.localName kind:element.kind] != IJSVGNodeTypeDef) { continue; } @@ -519,7 +543,7 @@ // store each object for(NSXMLElement * childDef in element.children) { // is there any stylesheets within this? - IJSVGNodeType childType = [IJSVGNode typeForString:childDef.name + IJSVGNodeType childType = [IJSVGNode typeForString:childDef.localName kind:element.kind]; switch(childType) { @@ -552,7 +576,7 @@ intoGroup:(IJSVGGroup *)parentGroup def:(BOOL)flag { - NSString * subName = element.name; + NSString * subName = element.localName; NSXMLNodeKind nodeKind = element.kind; IJSVGNodeType aType = [IJSVGNode typeForString:subName kind:nodeKind]; @@ -587,7 +611,8 @@ // grab common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; // work out the SVG NSError * error = nil; @@ -632,7 +657,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; // pass the commands for it [self _parsePathCommandData:[[element attributeForName:(NSString *)IJSVGAttributeD] stringValue] @@ -673,7 +699,8 @@ // find common attributes [self _setupDefaultsForNode:group]; [self _parseElementForCommonAttributes:element - node:group]; + node:group + ignoreAttributes:nil]; // recursively parse blocks [self _parseBlock:element @@ -699,7 +726,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parsePathCommandData:[[element attributeForName:(NSString *)IJSVGAttributeD] stringValue] intoPath:path]; @@ -721,7 +749,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parsePolygon:element intoPath:path]; [parentGroup addDef:path]; @@ -743,7 +772,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parsePolyline:element intoPath:path]; [parentGroup addDef:path]; @@ -767,7 +797,8 @@ [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:@[@"x",@"y"]]; [parentGroup addDef:path]; break; } @@ -785,7 +816,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parseLine:element intoPath:path]; [parentGroup addDef:path]; @@ -807,7 +839,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parseCircle:element intoPath:path]; [parentGroup addDef:path]; @@ -828,7 +861,8 @@ // find common attributes [self _setupDefaultsForNode:path]; [self _parseElementForCommonAttributes:element - node:path]; + node:path + ignoreAttributes:nil]; [self _parseEllipse:element intoPath:path]; [parentGroup addDef:path]; @@ -847,13 +881,30 @@ node.identifier = nil; } - node.parentNode = parentGroup; + // at this point, we need to create another group! + IJSVGGroup * subGroup = [[[IJSVGGroup alloc] init] autorelease]; + subGroup.parentNode = parentGroup; + [subGroup addChild:node]; + node.parentNode = subGroup; + node.intermediateParentNode = subGroup; + + // is there a width and height? + CGFloat x = [element attributeForName:(NSString *)IJSVGAttributeX].stringValue.floatValue; + CGFloat y = [element attributeForName:(NSString *)IJSVGAttributeY].stringValue.floatValue; + + // we need to add a transform to the subgroup + subGroup.transforms = @[[IJSVGTransform transformByTranslatingX:x y:y]]; + if(!flag) { - [parentGroup addChild:node]; + [parentGroup addChild:subGroup]; } + // parse attributes from element onto group - but spec + // says ignore x, y, width, height and xlink:href... [self _parseElementForCommonAttributes:element - node:node]; + node:node + ignoreAttributes:@[@"x",@"y",@"width", + @"height",@"xlink:href"]]; [parentGroup addDef:node]; break; @@ -864,43 +915,36 @@ NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; - IJSVGNode * node = [self definedObjectForID:xlinkID]; + NSXMLElement * referenceElement; + IJSVGNode * node = [self definedObjectForID:xlinkID + xmlElement:&referenceElement]; if( node != nil ) { // we are a clone + NSXMLElement * elementCopy = [self mergedElement:element + withReferenceElement:referenceElement]; + IJSVGLinearGradient * grad = [[[IJSVGLinearGradient alloc] init] autorelease]; grad.type = aType; - [grad applyPropertiesFromNode:node]; - - grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; - CGPoint startPoint, endPoint; - [IJSVGLinearGradient parseGradient:element - gradient:grad - startPoint:&startPoint - endPoint:&endPoint]; + grad.gradient = [IJSVGLinearGradient parseGradient:elementCopy + gradient:grad]; [self _setupDefaultsForNode:grad]; - [self _parseElementForCommonAttributes:element - node:grad]; - grad.startPoint = startPoint; - grad.endPoint = endPoint; + [self _parseElementForCommonAttributes:elementCopy + node:grad + ignoreAttributes:nil]; [parentGroup addDef:grad]; break; } IJSVGLinearGradient * gradient = [[[IJSVGLinearGradient alloc] init] autorelease]; gradient.type = aType; - - CGPoint startPoint, endPoint; gradient.gradient = [IJSVGLinearGradient parseGradient:element - gradient:gradient - startPoint:&startPoint - endPoint:&endPoint]; + gradient:gradient]; [self _setupDefaultsForNode:gradient]; [self _parseElementForCommonAttributes:element - node:gradient]; - gradient.startPoint = startPoint; - gradient.endPoint = endPoint; + node:gradient + ignoreAttributes:nil]; [parentGroup addDef:gradient]; break; } @@ -910,44 +954,37 @@ NSString * xlink = [[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]; NSString * xlinkID = [xlink substringFromIndex:1]; - IJSVGNode * node = [self definedObjectForID:xlinkID]; - if( node != nil ) - { + NSXMLElement * referenceElement; + IJSVGNode * node = [self definedObjectForID:xlinkID + xmlElement:&referenceElement]; + if( node != nil ) { // we are a clone IJSVGRadialGradient * grad = [[[IJSVGRadialGradient alloc] init] autorelease]; grad.type = aType; - [grad applyPropertiesFromNode:node]; - grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; - CGPoint startPoint, endPoint; - [IJSVGRadialGradient parseGradient:element - gradient:grad - startPoint:&startPoint - endPoint:&endPoint]; + NSXMLElement * elementCopy = [self mergedElement:element + withReferenceElement:referenceElement]; + + grad.gradient = [IJSVGRadialGradient parseGradient:elementCopy + gradient:grad]; [self _setupDefaultsForNode:grad]; - [self _parseElementForCommonAttributes:element - node:grad]; - grad.startPoint = startPoint; - grad.endPoint = endPoint; + [self _parseElementForCommonAttributes:elementCopy + node:grad + ignoreAttributes:nil]; [parentGroup addDef:grad]; break; } IJSVGRadialGradient * gradient = [[[IJSVGRadialGradient alloc] init] autorelease]; gradient.type = aType; - - CGPoint startPoint, endPoint; gradient.gradient = [IJSVGRadialGradient parseGradient:element - gradient:gradient - startPoint:&startPoint - endPoint:&endPoint]; - gradient.startPoint = startPoint; - gradient.endPoint = endPoint; + gradient:gradient]; [self _setupDefaultsForNode:gradient]; [self _parseElementForCommonAttributes:element - node:gradient]; + node:gradient + ignoreAttributes:nil]; [parentGroup addDef:gradient]; break; } @@ -964,7 +1001,8 @@ // find common attributes [self _parseElementForCommonAttributes:element - node:group]; + node:group + ignoreAttributes:nil]; // recursively parse blocks [self _parseBlock:element @@ -982,7 +1020,8 @@ // find common attributes [self _parseElementForCommonAttributes:element - node:pattern]; + node:pattern + ignoreAttributes:nil]; // pattern has children [self _parseBlock:element @@ -1001,7 +1040,8 @@ // find common attributes [self _parseElementForCommonAttributes:element - node:image]; + node:image + ignoreAttributes:nil]; // from base64 [image loadFromBase64EncodedString:[[element attributeForName:(NSString *)IJSVGAttributeXLink] stringValue]]; @@ -1015,6 +1055,18 @@ } } +- (NSXMLElement *)mergedElement:(NSXMLElement *)element + withReferenceElement:(NSXMLElement *)reference +{ + NSXMLElement * copy = [[reference copy] autorelease]; + for(NSXMLNode * attribute in element.attributes) { + [copy removeAttributeForName:attribute.name]; + attribute = [[attribute copy] autorelease]; + [copy addAttribute:attribute]; + } + return copy; +} + - (void)_parseBlock:(NSXMLElement *)anElement intoGroup:(IJSVGGroup*)parentGroup def:(BOOL)flag @@ -1188,8 +1240,7 @@ NSInteger count = 0; CGFloat * params = [IJSVGUtils commandParameters:points count:&count]; - if( (count % 2) != 0 ) - { + if( (count % 2) != 0 ) { // error occured, free the params free(params); return; @@ -1198,12 +1249,12 @@ // construct a command NSMutableString * str = [[[NSMutableString alloc] init] autorelease]; [str appendFormat:@"M%f,%f L",params[0],params[1]]; - for( NSInteger i = 2; i < count; i+=2 ) - { + for( NSInteger i = 2; i < count; i+=2 ) { [str appendFormat:@"%f,%f ",params[i],params[i+1]]; } - if( closePath ) + if( closePath ) { [str appendString:@"z"]; + } [self _parsePathCommandData:str intoPath:path]; free(params); @@ -1213,86 +1264,33 @@ - (void)_parseRect:(NSXMLElement *)element intoPath:(IJSVGPath *)path { - CGFloat aX, aY, aWidth, aHeight; - if([self namespacedAttribute:(NSString *)IJSVGAttributeX - element:element] != nil) { - - // already namespaced, find them - aX = [[self namespacedAttribute:(NSString *)IJSVGAttributeX - element:element] floatValue]; - aY = [[self namespacedAttribute:(NSString *)IJSVGAttributeY - element:element] floatValue]; - aWidth = [IJSVGUtils floatValue:[self namespacedAttribute:(NSString *)IJSVGAttributeWidth - element:element] - fallBackForPercent:self.viewBox.size.width]; - aHeight = [IJSVGUtils floatValue:[self namespacedAttribute:(NSString *)IJSVGAttributeHeight - element:element] - fallBackForPercent:self.viewBox.size.height]; - } else { - - // reassign X - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeX - value:[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] - element:element]; - aX = [[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] floatValue]; - - // reassign Y - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeY - value:[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] - element:element]; - aY = [[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] floatValue]; - - // reassign width - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeWidth - value:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] - element:element]; - aWidth = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] - fallBackForPercent:self.viewBox.size.width]; - - // reassign height - [self applyNamespacedAttribute:(NSString *)IJSVGAttributeHeight - value:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] - element:element]; - aHeight = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] - fallBackForPercent:self.viewBox.size.height]; - - // set the namespaced versions as we need to remove the attributes - [element removeAttributeForName:(NSString *)IJSVGAttributeX]; - [element removeAttributeForName:(NSString *)IJSVGAttributeY]; - [element removeAttributeForName:(NSString *)IJSVGAttributeWidth]; - [element removeAttributeForName:(NSString *)IJSVGAttributeHeight]; - } - CGFloat rX = [[[element attributeForName:(NSString *)IJSVGAttributeRX] stringValue] floatValue]; - CGFloat rY = [[[element attributeForName:(NSString *)IJSVGAttributeRY] stringValue] floatValue]; - if( [element attributeForName:(NSString *)IJSVGAttributeRY] == nil ) { + // width and height + CGFloat width = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeWidth] stringValue] + fallBackForPercent:self.viewBox.size.width]; + + + CGFloat height = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeHeight] stringValue] + fallBackForPercent:self.viewBox.size.height]; + + + // rect uses x and y as start of path, not move path object -_- + CGFloat x = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeX] stringValue] + fallBackForPercent:self.viewBox.size.width]; + CGFloat y = [IJSVGUtils floatValue:[[element attributeForName:(NSString *)IJSVGAttributeY] stringValue] + fallBackForPercent:self.viewBox.size.height]; + + // radius + CGFloat rX = [element attributeForName:(NSString *)IJSVGAttributeRX].stringValue.floatValue; + CGFloat rY = [element attributeForName:(NSString *)IJSVGAttributeRY].stringValue.floatValue; + if([element attributeForName:(NSString *)IJSVGAttributeRY] == nil) { rY = rX; } - [path overwritePath:[NSBezierPath bezierPathWithRoundedRect:NSMakeRect( aX, aY, aWidth, aHeight) - xRadius:rX - yRadius:rY]]; -} - -- (NSString *)namespacedAttribute:(NSString *)key - element:(NSXMLElement *)element -{ - key = [NSString stringWithFormat:@"ij-svg:%@",key]; - if([element attributeForName:key] != nil) { - return [[element attributeForName:key] stringValue]; - } - return nil; -} - -- (void)applyNamespacedAttribute:(NSString *)key - value:(NSString *)value - element:(NSXMLElement *)element -{ - key = [NSString stringWithFormat:@"ij-svg:%@",key]; - NSXMLNode * node = [[[NSXMLNode alloc] initWithKind:NSXMLAttributeKind] autorelease]; - node.name = key; - node.stringValue= value; - [element addAttribute:node]; + NSBezierPath * newPath = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect( x, y, width, height) + xRadius:rX + yRadius:rY]; + [path overwritePath:newPath]; } @end diff --git a/source/IJSVGQuartzRenderer.h b/source/IJSVGQuartzRenderer.h new file mode 100644 index 0000000..d313535 --- /dev/null +++ b/source/IJSVGQuartzRenderer.h @@ -0,0 +1,24 @@ +// +// IJSVGRenderer.h +// IJSVGExample +// +// Created by Curtis Hard on 31/01/2018. +// Copyright © 2018 Curtis Hard. All rights reserved. +// + +#import + +@class IJSVGLayer; + +@interface IJSVGQuartzRenderer : NSObject { + +} + +@property (nonatomic, assign) CGFloat scale; +@property (nonatomic, assign) CGFloat backingScale; +@property (nonatomic, assign) CGRect viewPort; + +- (void)renderLayer:(IJSVGLayer *)layer + inContext:(CGContextRef)context; + +@end diff --git a/source/IJSVGQuartzRenderer.m b/source/IJSVGQuartzRenderer.m new file mode 100644 index 0000000..eb6052c --- /dev/null +++ b/source/IJSVGQuartzRenderer.m @@ -0,0 +1,247 @@ +// +// IJSVGRenderer.m +// IJSVGExample +// +// Created by Curtis Hard on 31/01/2018. +// Copyright © 2018 Curtis Hard. All rights reserved. +// + +#import "IJSVGQuartzRenderer.h" +#import "IJSVG.h" +#import "IJSVGLayerTree.h" +#import "IJSVGGradientLayer.h" +#import "IJSVGStrokeLayer.h" + +@implementation IJSVGQuartzRenderer + +@synthesize scale, viewPort, backingScale; + + +- (void)renderLayer:(IJSVGLayer *)layer + inContext:(CGContextRef)ctx +{ + // if layer is hidden, no need to continue + // trying to render it, its useless! + if(layer.hidden == YES) { + return; + } + + // check which layers can actually be rendered + if(layer.class == IJSVGStrokeLayer.class || + layer.class == IJSVGGradientLayer.class) { + return; + } + + CGContextSaveGState(ctx); + + // apply any transfroms so children also transform too + CGContextConcatCTM(ctx, layer.affineTransform); + CGContextSetAlpha(ctx, layer.opacity); + + // is there a mask?! + if(layer.mask != nil) { + IJSVGLayer * maskLayer = (IJSVGLayer *)layer.mask; + CGRect rect; + CGImageRef maskImage = [self newMaskedImageForLayer:maskLayer + proposedRect:&rect]; + CGRect maskRect = CGRectMake(0, 0, rect.size.width, rect.size.height); + CGContextClipToMask(ctx, maskRect, maskImage); + CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y); + CGImageRelease(maskImage); + } + + // render itself + [self _renderLayer:layer + inContext:ctx]; + + // render the children recursively + for(IJSVGLayer * sublayer in layer.sublayers) { + [self renderLayer:sublayer + inContext:ctx]; + } + CGContextRestoreGState(ctx); +} + +- (void)_renderLayer:(IJSVGLayer *)layer + inContext:(CGContextRef)ctx +{ + // is a shape layer + if(layer.class == IJSVGShapeLayer.class) { + // move the path + CGContextTranslateCTM(ctx, layer.frame.origin.x, + layer.frame.origin.y); + + // find the shape + IJSVGShapeLayer * shape = (IJSVGShapeLayer *)layer; + CGPathRef path = shape.path; + + // has just a plain fill color + CGContextSaveGState(ctx); { + + CGContextAddPath(ctx, path); + if([shape.fillRule isEqualToString:kCAFillRuleEvenOdd]) { + CGContextEOClip(ctx); + } else { + CGContextClip(ctx); + } + + CGContextAddPath(ctx, path); + if(shape.gradientFillLayer == nil) { + if(shape.fillColor != nil) { + CGContextSetFillColorWithColor(ctx, shape.fillColor); + CGContextFillPath(ctx); + } + // has a gradient fill + } else if(shape.gradientFillLayer != nil) { + CGContextSaveGState(ctx); { + CGContextSetAlpha(ctx, shape.gradientFillLayer.opacity); + [shape.gradientFillLayer drawInContext:ctx]; + } CGContextRestoreGState(ctx); + } + } CGContextRestoreGState(ctx); + + // stroke must be done outside of the fill due to + // clipping path will cause it to render incorrectly + CGContextSaveGState(ctx); { + + // any stroke? + if(shape.strokeLayer != nil) { + IJSVGStrokeLayer * strokeLayer = (IJSVGStrokeLayer *)shape.strokeLayer; + if(strokeLayer.strokeColor != nil) { + CGContextSaveGState(ctx); { + + // set opacity + CGContextSetAlpha(ctx, strokeLayer.opacity); + + CGContextSetLineCap(ctx, [self.class lineCapFromLayer:strokeLayer]); + CGContextSetLineJoin(ctx, [self.class lineJoinFromLayer:strokeLayer]); + + // are there any line dashes? + NSArray * dash = strokeLayer.lineDashPattern; + CGFloat * lengths = (CGFloat *)malloc(sizeof(CGFloat)*dash.count); + NSInteger i = 0; + for(NSNumber * number in dash) { + lengths[i++] = number.floatValue; + } + CGContextSetLineDash(ctx, strokeLayer.lineDashPhase, + lengths, dash.count); + free(lengths); + + // get bounding box of the current path + CGContextAddPath(ctx, strokeLayer.path); + CGContextSetLineWidth(ctx, strokeLayer.lineWidth); + CGContextSetStrokeColorWithColor(ctx, strokeLayer.strokeColor); + CGContextStrokePath(ctx); + + } CGContextRestoreGState(ctx); + } + } else { + CGContextSetLineWidth(ctx, 0.f); + CGContextStrokePath(ctx); + } + + } CGContextRestoreGState(ctx); + + } +} + ++ (CGLineJoin)lineJoinFromLayer:(IJSVGShapeLayer *)layer +{ + if([layer.lineJoin isEqualToString:kCALineJoinBevel]) { + return kCGLineJoinBevel; + } else if([layer.lineJoin isEqualToString:kCALineJoinMiter]) { + return kCGLineJoinMiter; + } + return kCGLineJoinRound; +} + ++ (CGLineCap)lineCapFromLayer:(IJSVGShapeLayer *)layer +{ + if([layer.lineCap isEqualToString:kCALineCapButt]) { + return kCGLineCapButt; + } else if([layer.lineCap isEqualToString:kCALineCapRound]) { + return kCGLineCapRound; + } + return kCGLineCapSquare; +} + ++ (CGRect)findFrameForLayer:(IJSVGLayer *)layer +{ + CGRect rect = layer.frame; + return [self _recursivelyFindFrameForLayer:layer + rect:rect]; +} + ++ (CGRect)_recursivelyFindFrameForLayer:(IJSVGLayer *)layer + rect:(CGRect)rect +{ + CGRect frame = layer.frame; + if(CGRectGetMinX(frame) < CGRectGetMinX(rect)) { + rect.origin.x = CGRectGetMinX(frame); + } + if(CGRectGetMinY(frame) < CGRectGetMinY(rect)) { + rect.origin.y = CGRectGetMinY(frame); + } + if(CGRectGetMaxX(frame) > CGRectGetMaxX(rect)) { + rect.size.width = CGRectGetMaxX(frame); + } + if(CGRectGetMaxY(frame) > CGRectGetMaxY(rect)) { + rect.size.height = CGRectGetMaxY(frame); + } + for(IJSVGLayer * sublayer in layer.sublayers) { + rect = [self _recursivelyFindFrameForLayer:sublayer + rect:rect]; + } + return rect; +} + +- (CGRect)convertLayerFrame:(CGRect)rect +{ + rect.size.width = fabs(rect.origin.x - rect.size.width); + rect.size.height = fabs(rect.origin.y - rect.size.height); + rect.origin.x = rect.origin.y = 0.f; + return rect; +} + +- (CGImageRef)newImageForLayer:(IJSVGLayer *)layer + colorSpace:(CGColorSpaceRef)colorSpace + proposedRect:(CGRect *)proposedRect +{ + // create color space and new context + NSRect cRect = [self.class findFrameForLayer:layer]; + NSRect convertedSize = [self convertLayerFrame:cRect]; + CGSize size = cRect.size; + *proposedRect = cRect; + CGFloat actualScale = self.scale * self.backingScale; + CGContextRef ctx = CGBitmapContextCreate(NULL, size.width * actualScale, + size.height * actualScale, + 8, 0, colorSpace, + kCGImageAlphaPremultipliedLast); + + CGContextScaleCTM(ctx, actualScale, actualScale); + + // render the layer tree into this context + [self renderLayer:layer + inContext:ctx]; + + // grab image from it + CGImageRef image = CGBitmapContextCreateImage(ctx); + + // clean memory + CGContextRelease(ctx); + return image; +} + +- (CGImageRef)newMaskedImageForLayer:(IJSVGLayer *)layer + proposedRect:(CGRect *)proposedRect +{ + // create color space and new context + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + CGImageRef image = [self newImageForLayer:layer + colorSpace:colorSpace + proposedRect:proposedRect]; + CGColorSpaceRelease(colorSpace); + return image; +} + +@end diff --git a/source/IJSVGRadialGradient.h b/source/IJSVGRadialGradient.h index 0c1d64c..7885bec 100644 --- a/source/IJSVGRadialGradient.h +++ b/source/IJSVGRadialGradient.h @@ -18,8 +18,6 @@ @property ( nonatomic, retain ) IJSVGUnitLength * radius; + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGRadialGradient *)gradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint; + gradient:(IJSVGRadialGradient *)gradient; @end diff --git a/source/IJSVGRadialGradient.m b/source/IJSVGRadialGradient.m index 872c909..e65f574 100644 --- a/source/IJSVGRadialGradient.m +++ b/source/IJSVGRadialGradient.m @@ -34,48 +34,55 @@ grad.cx = self.cx; grad.cy = self.cy; grad.radius = self.radius; - grad.startPoint = self.startPoint; - grad.endPoint = self.endPoint; return grad; } + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGRadialGradient *)gradient - startPoint:(CGPoint *)startPoint - endPoint:(CGPoint *)endPoint { // cx defaults to 50% if not specified NSDictionary * kv = @{@"cx":@"cx", @"cy":@"cy", - @"r":@"radius", - @"fx":@"fx"}; + @"r":@"radius"}; for(NSString * key in kv.allKeys) { NSString * str = [element attributeForName:key].stringValue; IJSVGUnitLength * unit = nil; if(str != nil) { - unit = [IJSVGUnitLength unitWithPercentageString:str]; + unit = [IJSVGUnitLength unitWithString:str]; } else { - unit = [IJSVGUnitLength unitWithPercentageFloat:50]; + unit = [IJSVGUnitLength unitWithPercentageFloat:.5f]; } [gradient setValue:unit forKey:kv[key]]; } - // fy defaults to cy if not specified - NSString * fy = [element attributeForName:@"fy"].stringValue; - if(fy != nil) { - gradient.fy = [IJSVGUnitLength unitWithPercentageString:fy]; - } else { - gradient.fy = gradient.cy; + gradient.fx = gradient.cx; + gradient.fy = gradient.cy; + + // needs fixing + NSString * fx = [element attributeForName:@"fx"].stringValue; + if(fx != nil) { + if(fx.floatValue < 1.f) { + gradient.fx = [IJSVGUnitLength unitWithPercentageString:fx]; + } else { + gradient.fx = [IJSVGUnitLength unitWithString:fx]; + } } - if( gradient.gradient != nil ) + NSString * fy = [element attributeForName:@"fy"].stringValue; + if(fx != nil) { + if(fx.floatValue < 1.f) { + gradient.fy = [IJSVGUnitLength unitWithPercentageString:fy]; + } else { + gradient.fy = [IJSVGUnitLength unitWithString:fy]; + } + } + + if( gradient.gradient != nil ) { return nil; - - *startPoint = CGPointMake(gradient.cx.valueAsPercentage, gradient.cy.valueAsPercentage); - *endPoint = CGPointMake(gradient.fx.valueAsPercentage, gradient.fy.valueAsPercentage); + } NSArray * colors = nil; CGFloat * colorStops = [[self class] computeColorStopsFromString:element colors:&colors]; @@ -86,71 +93,79 @@ return ret; } -- (CGFloat)_handleTransform:(IJSVGTransform *)transform - bounds:(CGRect)bounds - index:(NSInteger)index - value:(CGFloat)value +- (void)drawInContextRef:(CGContextRef)ctx + objectRect:(NSRect)objectRect + absoluteTransform:(CGAffineTransform)absoluteTransform + viewPort:(CGRect)viewBox { - // rotate transform, assume its based on percentages - // if lower then 0 is specified for 1 or 2 - CGFloat max = bounds.size.width>bounds.size.height?bounds.size.width:bounds.size.height; - if( transform.command == IJSVGTransformCommandRotate ) { - switch(index) { - case 1: - case 2: { - if(value<1.f) { - return (max*value); + BOOL inUserSpace = self.units == IJSVGUnitUserSpaceOnUse; + CGFloat radius = self.radius.value; + CGPoint startPoint = CGPointZero; + CGPoint gradientStartPoint = CGPointZero; + CGPoint gradientEndPoint = CGPointZero; + + // transforms + CGAffineTransform selfTransform = IJSVGConcatTransforms(self.transforms); + + CGContextSaveGState(ctx); + { +#pragma mark User Space On Use + if(inUserSpace == YES) { + CGFloat rad = radius*2.f; + startPoint = CGPointMake(self.cx.value, self.cy.value); + + // work out the new radius + CGRect rect = CGRectMake(startPoint.x, startPoint.y, rad, rad); + rect = CGRectApplyAffineTransform(rect, selfTransform); + rect = CGRectApplyAffineTransform(rect, absoluteTransform); + radius = CGRectGetHeight(rect)/2.f; + + gradientStartPoint = startPoint; + gradientEndPoint = CGPointMake(self.fx.value, self.fy.value); + + // apply the absolute position + CGContextConcatCTM(ctx, absoluteTransform); + } else { +#pragma mark Object Bounding Box + // compute size based on percentages + CGFloat x = [self.cx computeValue:CGRectGetWidth(objectRect)]; + CGFloat y = [self.cy computeValue:CGRectGetHeight(objectRect)]; + startPoint = CGPointMake(x, y); + CGFloat val = MIN(CGRectGetWidth(objectRect), CGRectGetHeight(objectRect)); + radius = [self.radius computeValue:val]; + + CGFloat ex = [self.fx computeValue:CGRectGetWidth(objectRect)]; + CGFloat ey = [self.fy computeValue:CGRectGetHeight(objectRect)]; + + gradientEndPoint = CGPointMake(ex, ey); + gradientStartPoint = startPoint; + + // transform if width or height is not equal + if(CGRectGetWidth(objectRect) != CGRectGetHeight(objectRect)) { + CGAffineTransform tr = CGAffineTransformMakeTranslation(gradientStartPoint.x, + gradientStartPoint.y); + if(CGRectGetWidth(objectRect) > CGRectGetHeight(objectRect)) { + tr = CGAffineTransformScale(tr, CGRectGetWidth(objectRect)/CGRectGetHeight(objectRect), 1); + } else { + tr = CGAffineTransformScale(tr, 1.f, CGRectGetHeight(objectRect)/CGRectGetWidth(objectRect)); } - break; + tr = CGAffineTransformTranslate(tr, -gradientStartPoint.x, -gradientStartPoint.y); + selfTransform = CGAffineTransformConcat(tr, selfTransform); } } - } - return value; -} - -- (void)drawInContextRef:(CGContextRef)ctx - rect:(NSRect)rect -{ - CGRect bounds = rect; - for( IJSVGTransform * transform in self.transforms ) { - IJSVGTransformParameterModifier modifier = ^CGFloat(NSInteger index, CGFloat value) { - return [self _handleTransform:transform - bounds:bounds - index:index - value:value]; - }; - CGContextConcatCTM(ctx, [transform CGAffineTransformWithModifier:modifier]); - } +#pragma mark Default drawing + // transform the context + CGContextConcatCTM(ctx, selfTransform); - CGPoint sp = self.startPoint; - CGPoint ep = self.endPoint; - - if( self.startPoint.x == .5f ) { - sp.x = bounds.size.width*self.startPoint.x; - } - - if(self.startPoint.y == .5f) { - sp.y = bounds.size.height*self.startPoint.y; - } - - if(self.endPoint.x == .5f) { - ep.x = bounds.size.width*self.endPoint.x; - } - - if(self.endPoint.y == .5f) { - ep.y = bounds.size.height*self.endPoint.y; - } - - CGFloat r = self.radius.value; - if(r == .5f) { - r = (sp.x>sp.y?sp.x:sp.y); - } - - // actually perform the draw - CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; - CGGradientRef grad = self.CGGradient; - CGContextDrawRadialGradient(ctx, grad, sp, 0.f, ep, r, options); + // draw the gradient + CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation| + kCGGradientDrawsAfterEndLocation; + CGContextDrawRadialGradient(ctx, self.CGGradient, + gradientEndPoint, 0, gradientStartPoint, + radius, options); + }; + CGContextRestoreGState(ctx); } @end diff --git a/source/IJSVGShapeLayer.h b/source/IJSVGShapeLayer.h index 1671899..19207db 100644 --- a/source/IJSVGShapeLayer.h +++ b/source/IJSVGShapeLayer.h @@ -25,6 +25,7 @@ @property (nonatomic, assign) CGFloat backingScaleFactor; @property (nonatomic, assign) CGBlendMode blendingMode; @property (nonatomic, assign) CGPoint absoluteOrigin; +@property (nonatomic, assign) CGPoint originalPathOrigin; @property (nonatomic, assign) BOOL convertMasksToPaths; - (void)applySublayerMaskToContext:(CGContextRef)context diff --git a/source/IJSVGShapeLayer.m b/source/IJSVGShapeLayer.m index 0de6555..6ef05d0 100644 --- a/source/IJSVGShapeLayer.m +++ b/source/IJSVGShapeLayer.m @@ -20,6 +20,7 @@ @synthesize backingScaleFactor; @synthesize blendingMode; @synthesize convertMasksToPaths; +@synthesize originalPathOrigin; - (void)dealloc { diff --git a/source/IJSVGTransform.h b/source/IJSVGTransform.h index 36af4b4..12d9ef1 100644 --- a/source/IJSVGTransform.h +++ b/source/IJSVGTransform.h @@ -9,7 +9,10 @@ #import #import "IJSVGUtils.h" +@class IJSVGTransform; + typedef CGFloat (^IJSVGTransformParameterModifier)(NSInteger index, CGFloat value); +typedef void (^IJSVGTransformApplyBlock)(IJSVGTransform * transform); typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { IJSVGTransformCommandMatrix, @@ -35,12 +38,21 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { @property ( nonatomic, assign ) NSInteger parameterCount; @property ( nonatomic, assign ) NSInteger sort; +NSString * IJSVGDebugAffineTransform(CGAffineTransform transform); +NSString * IJSVGDebugTransforms(NSArray * transforms); +void IJSVGApplyTransform(NSArray * transforms, IJSVGTransformApplyBlock block); +CGAffineTransform IJSVGConcatTransforms(NSArray * transforms); + ++ (NSArray *)transformsFromAffineTransform:(CGAffineTransform)affineTransform; + (NSArray *)transformsForString:(NSString *)string; + (NSBezierPath *)transformedPath:(IJSVGPath *)path; + (NSArray *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform; ++ (NSString *)affineTransformToSVGMatrixString:(CGAffineTransform)affineTransform; - (CGAffineTransform)CGAffineTransform; - (CGAffineTransform)CGAffineTransformWithModifier:(IJSVGTransformParameterModifier)modifier; - (CGAffineTransform)stackIdentity:(CGAffineTransform)identity; - (void)recalculateWithBounds:(CGRect)bounds; ++ (IJSVGTransform *)transformByTranslatingX:(CGFloat)x + y:(CGFloat)y; @end diff --git a/source/IJSVGTransform.m b/source/IJSVGTransform.m index 1ec65fb..9eaf9dd 100644 --- a/source/IJSVGTransform.m +++ b/source/IJSVGTransform.m @@ -33,6 +33,51 @@ return trans; } +NSString * IJSVGDebugAffineTransform(CGAffineTransform transform) +{ + NSMutableArray * strings = [[[NSMutableArray alloc] init] autorelease]; + [strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform]]; + return [strings componentsJoinedByString:@" "]; +} + +NSString * IJSVGDebugTransforms(NSArray * transforms) +{ + NSMutableArray * strings = [[[NSMutableArray alloc] init] autorelease]; + IJSVGApplyTransform(transforms, ^(IJSVGTransform *transform) { + [strings addObjectsFromArray:[IJSVGTransform affineTransformToSVGTransformAttributeString:transform.CGAffineTransform]]; + }); + return [strings componentsJoinedByString:@" "]; +} + +CGAffineTransform IJSVGConcatTransforms(NSArray * transforms) +{ + __block CGAffineTransform trans = CGAffineTransformIdentity; + IJSVGApplyTransform(transforms, ^(IJSVGTransform *transform) { + trans = CGAffineTransformConcat(trans, transform.CGAffineTransform); + }); + return trans; +} + +void IJSVGApplyTransform(NSArray * transforms, IJSVGTransformApplyBlock block) +{ + for(IJSVGTransform * transform in transforms) { + block(transform); + } +}; + ++ (IJSVGTransform *)transformByTranslatingX:(CGFloat)x + y:(CGFloat)y +{ + IJSVGTransform * transform = [[[self alloc] init] autorelease]; + transform.command = IJSVGTransformCommandTranslate; + transform.parameterCount = 2; + CGFloat * params = (CGFloat *)malloc(sizeof(CGFloat)*2); + params[0] = x; + params[1] = y; + transform.parameters = params; + return transform; +} + - (void)recalculateWithBounds:(CGRect)bounds { CGFloat max = bounds.size.width>bounds.size.height?bounds.size.width:bounds.size.height; @@ -101,8 +146,9 @@ { NSString * command = [string substringWithRange:[result rangeAtIndex:1]]; IJSVGTransformCommand commandType = [[self class] commandForCommandString:command]; - if( commandType == IJSVGTransformCommandNotImplemented ) + if( commandType == IJSVGTransformCommandNotImplemented ) { return; + } // create the transform NSString * params = [string substringWithRange:[result rangeAtIndex:2]]; @@ -400,6 +446,19 @@ return CGAffineTransformIdentity; } ++ (NSArray *)transformsFromAffineTransform:(CGAffineTransform)affineTransform +{ + NSArray * strings = [self affineTransformToSVGTransformAttributeString:affineTransform]; + return [self transformsForString:[strings componentsJoinedByString:@" "]]; +} + ++ (NSString *)affineTransformToSVGMatrixString:(CGAffineTransform)transform +{ + return [NSString stringWithFormat:@"matrix(%g,%g,%g,%g,%g,%g)", + transform.a, transform.b, transform.c, transform.d, + transform.tx, transform.ty]; +} + // this is an Object-C version of the matrixToTransform method from SVGO + (NSArray *)affineTransformToSVGTransformAttributeString:(CGAffineTransform)affineTransform { @@ -500,5 +559,11 @@ return trans; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ %@",[super description], + [self.class affineTransformToSVGTransformAttributeString:self.CGAffineTransform]]; +} + @end diff --git a/source/IJSVGUtils.h b/source/IJSVGUtils.h index 84b54bb..49d7759 100644 --- a/source/IJSVGUtils.h +++ b/source/IJSVGUtils.h @@ -18,6 +18,12 @@ CGFloat angle( CGPoint a, CGPoint b ); CGFloat radians_to_degrees( CGFloat radians); CGFloat degrees_to_radians( CGFloat degrees ); +BOOL IJSVGIsCommonHTMLElementName(NSString * str); +NSArray * IJSVGCommonHTMLElementNames(); + +NSString * IJSVGShortFloatString(CGFloat f); +NSString * IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision); + BOOL IJSVGIsLegalCommandCharacter(unichar aChar); BOOL IJSVGIsSVGLayer(CALayer * layer); + (IJSVGCommandType)typeForCommandString:(NSString *)string; diff --git a/source/IJSVGUtils.m b/source/IJSVGUtils.m index e018bcf..fc14ad2 100644 --- a/source/IJSVGUtils.m +++ b/source/IJSVGUtils.m @@ -12,6 +12,170 @@ @implementation IJSVGUtils +BOOL IJSVGIsCommonHTMLElementName(NSString * str) +{ + str = str.lowercaseString; + return [IJSVGCommonHTMLElementNames() containsObject:str]; +}; + +NSArray * IJSVGCommonHTMLElementNames() +{ + static NSArray * names = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + names = [@[@"a", + @"abbr", + @"acronym", + @"abbr", + @"address", + @"applet", + @"embed", + @"object", + @"area", + @"article", + @"aside", + @"audio", + @"b", + @"base", + @"basefont", + @"bdi", + @"bdo", + @"big", + @"blockquote", + @"body", + @"br", + @"button", + @"canvas", + @"caption", + @"center", + @"cite", + @"code", + @"col", + @"colgroup", + @"colgroup", + @"datalist", + @"dd", + @"del", + @"details", + @"dfn", + @"dialog", + @"dir", + @"ul", + @"div", + @"dl", + @"dt", + @"em", + @"embed", + @"fieldset", + @"figcaption", + @"figure", + @"figure", + @"font", + @"footer", + @"form", + @"frame", + @"frameset", + @"h1", + @"h6", + @"head", + @"header", + @"hr", + @"html", + @"i", + @"iframe", + @"img", + @"input", + @"ins", + @"kbd", + @"label", + @"input", + @"legend", + @"fieldset", + @"li", + @"link", + @"main", + @"map", + @"mark", + @"menu", + @"menuitem", + @"meta", + @"meter", + @"nav", + @"noframes", + @"noscript", + @"object", + @"ol", + @"optgroup", + @"option", + @"output", + @"p", + @"param", + @"picture", + @"pre", + @"progress", + @"q", + @"rp", + @"rt", + @"ruby", + @"s", + @"samp", + @"script", + @"section", + @"select", + @"small", + @"source", + @"video", + @"audio", + @"span", + @"strike", + @"del", + @"s", + @"strong", + @"style", + @"sub", + @"summary", + @"details", + @"sup", + @"table", + @"tbody", + @"td", + @"template", + @"textarea", + @"tfoot", + @"th", + @"thead", + @"time", + @"title", + @"tr", + @"track", + @"video", + @"audio", + @"tt", + @"u", + @"ul", + @"var", + @"video", + @"wbr"] retain]; + }); + return names; +}; + +NSString * IJSVGShortFloatString(CGFloat f) +{ + return [NSString stringWithFormat:@"%g",f]; +}; + +NSString * IJSVGShortFloatStringWithPrecision(CGFloat f, NSInteger precision) +{ + NSString * format = [NSString stringWithFormat:@"%@.%ld%@",@"%",precision,@"f"]; + NSString * ret = [NSString stringWithFormat:format,f]; + // can it be reduced even more? + if(ret.floatValue == (float)ret.integerValue) { + ret = [NSString stringWithFormat:@"%ld",ret.integerValue]; + } + return ret; +}; + BOOL IJSVGIsLegalCommandCharacter(unichar aChar) { char * validChars = "MmZzLlHhVvCcSsQqTtAa"; @@ -253,7 +417,6 @@ CGFloat degrees_to_radians( CGFloat degrees ) // sizes for the string buffer NSInteger defSize = 50; NSInteger size = defSize; - NSInteger sLength = string.length; // default memory size for the floats NSInteger defFloatSize = 100; @@ -265,6 +428,8 @@ CGFloat degrees_to_radians( CGFloat degrees ) const char * cString = [string cStringUsingEncoding:NSUTF8StringEncoding]; const char * validChars = "0123456789eE+-."; + NSInteger sLength = strlen(cString); + // buffer for the returned floats CGFloat * floats = (CGFloat *)malloc(sizeof(CGFloat)*defFloatSize);