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