Compare commits

..

340 Commits

Author SHA1 Message Date
Curtis Hard 35fc5157b3 fix crash with realloc call being incorrect 2025-01-26 12:44:09 +00:00
Curtis Hard 3742cfd16e implement better sizing 2024-12-15 20:47:30 +00:00
Curtis Hard b47b487a8d Speed up Xcode 16 build times 2024-09-17 23:00:34 +01:00
Curtis Hard 6dc767f88e Remove file 2024-07-21 13:45:39 +01:00
Curtis Hard f8efb68277 Merge branch 'fixes/gradient-stop-parsing' 2024-07-21 13:44:32 +01:00
Curtis Hard 27c4638fa7 Add the inner transforms 2024-07-16 18:48:13 +01:00
Curtis Hard 7c8bfa937e Fixes crash when large whitespace string is used 2024-07-03 17:11:38 +01:00
Curtis Hard fdc30123a4 be sure to copy children 2024-06-27 16:45:04 +01:00
Curtis Hard 80ef3790d2 Update Xcode settings 2024-02-11 19:26:26 +00:00
Curtis Hard ded0c9b839 updated proj 2024-02-06 19:24:47 +00:00
Curtis Hard 6585501f5d Merge branch 'fixes/masking-offsets' 2023-12-20 23:10:05 +00:00
Curtis Hard 846f0fe27c Fix memory leak 2023-11-09 21:59:22 +00:00
Curtis Hard 0c64626e3e Fix for colours using weird methods 2023-10-30 22:47:27 +00:00
Curtis Hard 9ea09ebeee Typo 2023-10-30 20:03:17 +00:00
Curtis Hard 0c04202ad1 Possible masking alpha channel fixes 2023-10-30 20:02:29 +00:00
Curtis Hard e66ee5e5a1 Possible fix for masking offsets 2023-10-30 15:59:11 +00:00
Curtis Hard a97272252d Merge branch 'fixes/masking-units' into enhancements/parsing 2023-10-20 20:53:34 +01:00
Curtis Hard a95a046c64 FIx for transforms being applied to root nodes 2023-10-20 20:28:19 +01:00
Curtis Hard d4d8fcfe78 Fix color parsing with CSS attributes 2023-10-20 20:09:36 +01:00
Curtis Hard e75d862430 Adds display attribute into base list for passing 2023-10-20 19:50:15 +01:00
Curtis Hard 44358cc00a Move all child layers within a mask if they are userSpaceOnUse 2023-10-18 20:42:19 +01:00
Curtis Hard c7e490fcba Fixes for new Xcode 2023-07-23 13:02:24 +01:00
Curtis Hard 63d682f5d9 Remove lock + fix for threading issue 2023-06-27 21:32:44 +01:00
Curtis Hard 13a7fb5431 Fixes clipping issue when maintaining ratio 2023-04-22 13:42:13 +01:00
Curtis Hard f52ee76e2d Possible threadinf fix 2023-04-20 21:47:20 +01:00
Curtis Hard 9c1ad0b44b Threading issue (only apparently from Swift) 2023-04-20 19:42:46 +01:00
Curtis Hard c608d01fa4 Adds umbrella header + changes to module map 2023-04-16 17:24:19 +01:00
Curtis Hard bb8b3f915b Deletes the old file 2023-01-30 19:42:10 +00:00
Curtis Hard ad78aa6598 Adds module map file in 2023-01-30 19:42:01 +00:00
Curtis Hard d558f90932 Adds really fast attribute checking and parsing 2022-09-08 21:29:12 +01:00
Curtis Hard e7716dbd7b fixes header missing 2022-09-06 21:46:40 +01:00
Curtis Hard 1d0fce8f2a Vast performance increases to parser when parsing attributes 2022-09-05 13:49:45 +01:00
Curtis Hard 24825d867d Further parsing performance increases 2022-09-04 22:22:14 +01:00
Curtis Hard 3b63e225c3 Odd commit 2022-09-04 19:24:10 +01:00
Curtis Hard 1f852e0cf9 Further performance enhancements 2022-09-04 19:13:10 +01:00
Curtis Hard 2547f47126 Memory fixes 2022-09-04 12:23:59 +01:00
Curtis Hard 4769ebeac7 Performance optimisations 2022-09-01 18:25:07 +01:00
Curtis Hard fe51568c83 Only parse filters if flag is enabled for them 2022-08-20 21:39:26 +01:00
Curtis Hard b91235323b Make sure we calculate the outerBoundingBox for masks that have transforms 2022-08-20 13:30:27 +01:00
Curtis Hard cefc304e6e Fixes gradient exports with transforms 2022-08-20 11:36:58 +01:00
Curtis Hard a23425c9b6 Fixes gradient rendering
- concat gradients and applying a single one does not work for some reason, however, applying each one individually into the context works
2022-08-20 11:26:26 +01:00
Curtis Hard 2f2c61e559 Added a comment 2022-08-17 13:31:32 +01:00
Curtis Hard 061a5fb52c Removal of useless code 2022-08-14 14:37:53 +01:00
Curtis Hard 8598bea11e Added IJSVGPerformTransactionBlock 2022-08-14 11:41:12 +01:00
Curtis Hard faac432117 Fixes a leak that can occur when the framework is used by code that involved ARC 2022-08-13 19:29:45 +01:00
Curtis Hard 29a5c5e533 - adds sizeByMaintainingAspectRatioWithSize: 2022-08-12 20:48:31 +01:00
Curtis Hard f286f4e993 Fixes a crash that could occur when the intrinsicSize is dynamic
- size should be calculated instead of static
2022-08-12 20:35:08 +01:00
Curtis Hard 6bbd213eb0 Delete _config.yml 2022-08-10 21:09:25 +01:00
Curtis Hard c82db3469e Update README.md 2022-08-09 21:57:04 +01:00
Curtis Hard 8269727063 Update README.md 2022-08-09 21:56:38 +01:00
Curtis Hard a3d2c6dca9 Update README.md 2022-08-09 20:58:22 +01:00
Curtis Hard 1c61ab18e4 Update README.md 2022-08-09 20:56:13 +01:00
Curtis Hard 64588b6e6d Update README.md 2022-08-08 18:21:27 +01:00
Curtis Hard 3c81cd6634 Merge branch 'features/filters' 2022-08-08 18:00:39 +01:00
Curtis Hard 258c79d208 Fixes a memory leak 2022-08-04 14:37:07 +01:00
Curtis Hard 5228c87f16 Added nodes matching traits 2022-08-01 21:33:55 +01:00
Curtis Hard 05ec5c5a29 Adds stroke to path on export option
- fixed stroke opacity
- adds convenience method for grabbing a stroke layer from another layer
2022-07-17 17:53:14 +01:00
Curtis Hard 8628b38938 Start of converting strokes to paths 2022-07-13 20:39:03 +01:00
Curtis Hard 3cc8f1f355 Adds feature flags, updates example with correct calls 2022-07-13 19:45:32 +01:00
Curtis Hard c7b56ba877 Added normalisation of viewBox's 2022-07-11 20:35:40 +01:00
Curtis Hard e25b00efb7 Fixes image resizing 2022-07-07 17:51:07 +01:00
Curtis Hard 37d6cc7fd8 FIxes for image export 2022-07-07 17:27:12 +01:00
Curtis Hard b2eaf7ead5 Adds convenience methods for unit sizes 2022-07-07 17:12:05 +01:00
Curtis Hard f60f07ae70 Removed references for delegate as its useless now 2022-07-06 19:01:32 +01:00
Curtis Hard bb9ea4ade8 Fixes aspect ratio method 2022-07-06 17:35:31 +01:00
Curtis Hard d4f96823ec Various testing SVG’s 2022-07-05 17:22:32 +01:00
Curtis Hard c1b024f7a7 Removed refs to delegate and colors 2022-07-05 17:17:30 +01:00
Curtis Hard 2aa004f4b2 Removed font converter as its now useless 2022-07-05 17:06:52 +01:00
Curtis Hard 4a80c51070 Various methods to find nested children 2022-07-04 09:01:03 +01:00
Curtis Hard c8a813a29e Various renaming and refactors 2022-07-02 19:56:59 +01:00
Curtis Hard 6ca015aeb7 Few performance refactors for stylesheets 2022-07-02 12:11:58 +01:00
Curtis Hard 3647c91ab2 Prevents recursion when working out clip-rule 2022-07-02 11:04:51 +01:00
Curtis Hard ee66d25b9e Fixes clipRule on paths 2022-07-01 21:02:31 +01:00
Curtis Hard 03dfc7deb6 Adds clipRule support in 2022-07-01 20:06:10 +01:00
Curtis Hard 1b75f8964d Code formattering and refactor of matching masks on SVGs 2022-07-01 16:25:00 +01:00
Curtis Hard 1506840957 Adds lineCap, lineJoin, width and miterLimit style settings back in 2022-07-01 13:48:34 +01:00
Curtis Hard ae7e1e0196 Fixes absolute to relative path conversion
- adds support for varied dimensions on root SVG when exported
- adds constants for infinite and intrisic sizes
2022-07-01 13:13:07 +01:00
Curtis Hard 20589bfd83 Implement colour changing back into layer tree 2022-06-30 22:34:52 +01:00
Curtis Hard 96156fe1e0 Adds colors and storage into IJSVG 2022-06-30 21:35:40 +01:00
Curtis Hard daf843dd26 start refactor of colorLists 2022-06-30 18:59:37 +01:00
Curtis Hard 8789c7cef7 Adds image exporting + fixes a few things 2022-06-29 18:10:00 +01:00
Curtis Hard 551548579c Fixes circle -> ellipsis parsing 2022-06-28 18:48:12 +01:00
Curtis Hard 5206052905 Adds better pattern export support and also fixes clipPath exporting 2022-06-28 17:58:52 +01:00
Curtis Hard 6ee54e42bb Various improvements and constants 2022-06-28 13:05:05 +01:00
Curtis Hard 38d7fe15ea Adds clipPath support in to exporter 2022-06-27 20:02:08 +01:00
Curtis Hard 0905545edc Few formatting and fixes 2022-06-27 19:22:54 +01:00
Curtis Hard 3ae7f1b6d5 More export fixes 2022-06-27 15:16:27 +01:00
Curtis Hard 23f861584c Initial start of export refactor to use new layer things 2022-06-26 18:53:29 +01:00
Curtis Hard 94b7e3fe87 Fixes lack of viewbox or dimensions 2022-06-12 19:14:59 +01:00
Curtis Hard df7ceb3df1 Tidy up of viewBox code 2022-06-12 17:26:07 +01:00
Curtis Hard 3efea34d76 refactor methods into root class 2022-06-09 22:33:47 +01:00
Curtis Hard 7f74e66e90 Fixes clipPath and possible patterns... 2022-06-09 20:45:37 +01:00
Curtis Hard 35473e11c9 Fixes issue with patterns without a viewBox 2022-06-09 12:57:44 +01:00
Curtis Hard 4d00aab469 Nice refactor of viewBox’s 2022-06-07 19:46:31 +01:00
Curtis Hard bea50a033a Possible fix for gradients and pattern alignment 2022-06-07 11:45:16 +01:00
Curtis Hard fe9226a921 Code tidy 2022-06-06 21:30:31 +01:00
Curtis Hard fa62c7d334 Fix for patterns viewBox 2022-06-06 21:02:21 +01:00
Curtis Hard 3f58044f9d Adds a better call for viewboxes 2022-06-06 20:19:19 +01:00
Curtis Hard 96218c3fbe Images now obey their preserveAspectRatio 2022-06-06 17:50:16 +01:00
Curtis Hard d1b2fc882d Will use clipPaths for stroke patterns and gradient fills 2022-06-06 12:21:43 +01:00
Curtis Hard 008c70079f refactor gradients calls so they are nicer 2022-06-05 21:48:48 +01:00
Curtis Hard 05677c7c82 Fixes scale being applied twice #oops 2022-06-05 12:53:19 +01:00
Curtis Hard 8042ed7dc7 Fix for compiler warnings 2022-06-04 22:11:56 +01:00
Curtis Hard ff0b7e0866 Reverts to vector clippaths for clippaths and groups 2022-06-04 20:10:59 +01:00
Curtis Hard 304cac0a09 Remove integral transform on the rect, it breaks things 2022-06-04 17:20:31 +01:00
Curtis Hard 832527134f Stops defaulting to backing scale from screen 2022-06-04 17:07:34 +01:00
Curtis Hard 8657228b26 Fixes viewPort sizing 2022-06-04 16:50:31 +01:00
Curtis Hard 28c36337c2 Basic clipPath support on layers too 2022-06-04 16:30:18 +01:00
Curtis Hard 44504371ea Added clipPaths support (i think) 2022-06-04 15:51:54 +01:00
Curtis Hard 566527b6c0 Added clipPath as its own node 2022-06-02 16:06:01 +01:00
Curtis Hard e235b833ed Fixes root node positions and size 2022-06-02 16:05:29 +01:00
Curtis Hard 60547feb98 Mostly fixes masks, as far as I can tell 2022-05-28 20:41:39 +01:00
Curtis Hard 153258fa31 Fixes some masking issues 2022-05-25 21:45:58 +01:00
Curtis Hard 77741e3f38 Fixes fill opacity 2022-05-25 15:39:58 +01:00
Curtis Hard 22983a33a7 Fixes mask 2022-05-25 15:03:44 +01:00
Curtis Hard e9421025d9 Added wildcard combinator to stylesheets 2022-05-24 21:00:54 +01:00
Curtis Hard 33bd390f94 Fixes fill opacity for paintable layers 2022-05-24 20:44:47 +01:00
Curtis Hard 767ecb0b62 Fix radial gradient userspaceonuse 2022-05-24 20:28:32 +01:00
Curtis Hard 2afae1516a Fixes patterns and masking 2022-05-24 19:58:46 +01:00
Curtis Hard 6f9412c3c0 Fixes preserveAspectRatio if there is a implicit size 2022-05-21 17:56:52 +01:00
Curtis Hard fac780b47a Possible fix for radial gradients objectBoundingBox 2022-05-19 10:54:53 +01:00
Curtis Hard 2f7c34cf68 Fixes linear gradient drawing with transform 2022-05-17 20:30:52 +01:00
Curtis Hard f2c50ec5a4 Few more fixes 2022-05-13 18:22:12 +01:00
Curtis Hard 3cc75701fc Fixes parsing of clippaths 2022-05-13 10:45:13 +01:00
Curtis Hard 5ba7e94d24 Adds better recursie def parsing (some people reference other elements that have yet to be parsed..) 2022-05-12 19:47:18 +01:00
Curtis Hard 4e53a2cfa9 Fixes switch statements in SVG’s 2022-05-12 18:44:50 +01:00
Curtis Hard a1eb04ab1d Fixes inherit crash 2022-05-12 17:08:06 +01:00
Curtis Hard 88d7bd048d Fixes various use things and dash patterns 2022-05-12 17:02:20 +01:00
Curtis Hard 0dd8e1755e Wrong class used for filter effects detection 2022-05-10 16:31:39 +01:00
Curtis Hard e34f28cbda oops, fixed! 2022-05-10 15:54:55 +01:00
Curtis Hard f7a566d078 Fixes use and transform 2022-05-10 15:39:26 +01:00
Curtis Hard 7ebfa75ccd Fixes viewbox transforms 2022-05-10 12:37:34 +01:00
Curtis Hard 84ade44557 Fixes default colour opacity 2022-05-10 12:29:16 +01:00
Curtis Hard 1f0d0b41dc Clip path and userSpaceOnUse fixes 2022-05-10 12:06:38 +01:00
Curtis Hard 4c280e9694 Filter fixes 2022-05-09 18:37:42 +01:00
Curtis Hard 240a62127c Fixes parsing crash 2022-05-09 17:51:51 +01:00
Curtis Hard 7eb2e12164 more parsing fixes and CA issues resolved 2022-05-09 09:19:27 +01:00
Curtis Hard 2224a2538d Fixes buffer with whitespace parsing issue
- adds more examples
2022-05-08 17:00:33 +01:00
Curtis Hard 7513fc92fd Gradients no longer need their NS counterpart 2022-05-04 11:49:34 +01:00
Curtis Hard 7a0390b377 Uses angle brackers instead of quotes for imports 2022-04-20 22:28:23 +01:00
Curtis Hard 1cefc6aeed More ARC fixes 2022-04-20 21:37:26 +01:00
Curtis Hard 824e87d69a More ARC things 2022-04-20 20:27:30 +01:00
Curtis Hard e80088fc71 Dont know.... 2022-04-20 18:07:01 +01:00
Curtis Hard 46ba1ddcbd Converted everything to ARC.. ARC ALL THINGS! 2022-04-20 18:06:53 +01:00
Curtis Hard 533c369eb8 Adds threadmanager in to deal with per thread memory 2022-04-20 12:14:59 +01:00
Curtis Hard 5ac8aa8760 Fixes inner/outer bounding box frames for masks and snapshots 2022-04-19 22:36:00 +01:00
Curtis Hard b6ebd598e7 Initial filters 2022-04-19 18:11:54 +01:00
Curtis Hard 050df84672 Adds isNoneOrTransparent to COlor 2022-04-19 18:11:02 +01:00
Curtis Hard ac61d3d6d6 Adds image generation to IJSVGLayer 2022-04-18 17:32:20 +01:00
Curtis Hard fdde1fd5f6 Fixes transforms and masking 2022-04-18 14:45:24 +01:00
Curtis Hard fe179076cc More fixes 2022-04-18 11:45:04 +01:00
Curtis Hard 01dd0d25d2 Make sure the node actually exists 2022-04-17 17:57:52 +01:00
Curtis Hard 799593d7d8 Removed log 2022-04-17 17:57:09 +01:00
Curtis Hard 0c7cfd776c Fixes stylesheets 2022-04-17 17:57:01 +01:00
Curtis Hard 757c4317d3 Adds better rendering calls into SVG 2022-04-17 12:45:44 +01:00
Curtis Hard 1727505968 Removed IJSVG_DRAWABLE_LAYER 2022-04-17 11:58:49 +01:00
Curtis Hard 775c28e91a Updated example and added better support for ratios 2022-04-16 17:50:57 +01:00
Curtis Hard ec539a12b3 More alignment stuff 2022-04-16 13:51:33 +01:00
Curtis Hard 43210c018e Fixes strokes with layer of constant fill color
- started to out viewboxes
2022-04-15 21:10:48 +01:00
Curtis Hard 3773e10f44 Fixes strokes, some patterns, changes how we parse commands ready for units 2022-04-13 21:20:53 +01:00
Curtis Hard 51fb9cc871 added referencing layers into layer tree 2022-04-12 20:00:58 +01:00
Curtis Hard be08ada5da Fixes gradients 2022-04-11 10:14:08 +01:00
Curtis Hard a3a6ee5a05 Fixes absolute transform and gradients 2022-04-08 21:56:03 +01:00
Curtis Hard cccfc0db68 Various fixes 2022-04-07 15:36:33 +01:00
Curtis Hard 19db898bdd More gradient things that dont work 2022-04-03 20:05:58 +01:00
Curtis Hard 8abbdf390a Continue of refactor 2022-04-02 13:51:47 +01:00
Curtis Hard 6e98e2cfed Removes useless properties 2022-03-31 13:36:53 +01:00
Curtis Hard 2c536a8962 Adds .children and improvements to addChild/removeChild 2022-03-30 19:52:30 +01:00
Curtis Hard 65a8c0a691 Proper clip path 2022-03-30 15:26:32 +01:00
Curtis Hard 2942cc0a58 Added IJSVGColorNode and traits 2022-03-29 20:12:53 +01:00
Curtis Hard ba7a05553e Adds frame calculations onto group 2022-03-29 11:37:28 +01:00
Curtis Hard c9a7788d98 Added correctly nested detached nodes 2022-03-29 09:34:08 +01:00
Curtis Hard 774ff48074 Refactor, IJSVGParser is not the root anymore, there is a new IJSVGRootNode as the root of the node tree 2022-03-28 20:31:27 +01:00
Curtis Hard 2eb629cd21 Overhaul of parser 2022-03-28 19:14:46 +01:00
Curtis Hard 137e34623f hacky things! 2022-03-27 22:05:19 +01:00
Curtis Hard 39b0b5bbb4 More image fixes 2022-03-27 20:45:31 +01:00
Curtis Hard 820fca2679 Merge branch 'features/patterns' of https://github.com/curthard89/IJSVG into features/patterns 2022-03-27 15:38:09 +01:00
Curtis Hard 2e5819183d More pattern things 2022-03-27 15:36:33 +01:00
Curtis Hard eff56d3c60 More pattern things, this is not fun 2022-03-27 15:35:51 +01:00
Curtis Hard 7a4fd35a64 initial pattern fixes 2022-03-25 13:21:59 +00:00
Curtis Hard 964dc3005d Merge branch 'fixes/const-memory-alloc-issues' of https://github.com/curthard89/IJSVG 2022-03-23 18:27:54 +00:00
Curtis Hard 1a8d44ec95 Fixes masking for gradients 2022-03-23 18:27:41 +00:00
Curtis Hard 717198457b Adds new method to create a trimmed string
- adds `const` in various places
- uses the new trimmed string method
- adds various memory releases in
2022-01-24 14:33:54 +00:00
Curtis Hard e2991ab8b0 Fixes smooth quad / curves having incorrect path data on export 2022-01-04 14:04:23 +00:00
Curtis Hard dda3e5ed8c Merge branch 'feature/ijsvgcolorsub' 2022-01-04 11:17:38 +00:00
Curtis Hard c3a0fb5b91 Fixes 10.9 issue with const 2022-01-04 11:17:15 +00:00
Curtis Hard 7ce4520a56 Adds currentColor as a predefined constant 2021-12-29 16:25:52 +00:00
Curtis Hard 368d19ea81 Nullable types 2021-12-28 22:33:12 +00:00
Curtis Hard d26c2f489f Adds delegate methods for color and ID 2021-12-28 19:28:32 +00:00
Curtis Hard 75b0d55b63 #oops, wrong list being used 2021-12-26 18:50:51 +00:00
Curtis Hard 310308cd8a Fixes stroke colorList stop colors not being identified correctly 2021-12-26 18:11:30 +00:00
Curtis Hard 278f405a41 Possible fix for older OS's 2021-12-15 15:16:59 +00:00
Curtis Hard 7addd97d0c Fixes issue with clipPath not using correct units
- adds support for overflow attribute
2021-11-24 15:15:54 +00:00
Curtis Hard d01ca0f3bb Fixes issue with wrong colors being used 2021-10-19 20:49:57 +01:00
Curtis Hard 6d7ad17e2a Fixes threading race condition 2021-05-11 22:06:56 +01:00
Curtis Hard deb09eaf04 Merge branch 'feature/color-stroke-checking' 2021-04-27 09:45:29 +01:00
Curtis Hard 1e7b8b38a5 Fixes the double quotes being used instead of angled brackets 2021-04-26 13:45:32 +01:00
Curtis Hard 58e1a8c45d fixes tempalte void things 2021-04-26 10:35:08 +01:00
Curtis Hard a525e4351a Fixes computeColorList for gradient export 2021-04-24 13:41:05 +01:00
Curtis Hard c87716b311 Code style 2021-04-21 20:18:07 +01:00
Curtis Hard 583a930bf7 Fixes multithread issue with strtok 2021-04-21 15:24:51 +01:00
Curtis Hard ec2473d450 Adds transaction to fix memory issue 2021-04-21 13:39:31 +01:00
Curtis Hard 3cc477955a Fixes enums and options 2021-04-20 20:36:37 +01:00
Curtis Hard 1f0ff52dff Adds color types 2021-04-20 16:31:52 +01:00
Curtis Hard e96bfa54b0 Make sure to check it has alpha 2021-04-20 12:15:23 +01:00
Curtis Hard cd3b540274 Initial commit 2021-04-20 09:31:10 +01:00
Curtis Hard 1cfbaf14e4 added utils for converting CGLineCap and Joi nto IJSVG counterparts 2021-04-11 17:13:45 +01:00
Curtis Hard 5a389f88d3 Added initWithDataAssetNamed:bundle:error:
Added support for loading SVG’s from the data asset catalogue
2021-04-07 21:42:00 +01:00
Curtis Hard 9f43832ff3 Fixes elliptical arc 2021-02-17 21:32:13 +00:00
Curtis Hard 65623ec552 Fixes colour forced from a style 2021-02-16 21:11:26 +00:00
Curtis Hard 5192520b7c Fixes memory crash 2021-02-16 09:37:22 +00:00
Curtis Hard 5123ee7308 Fixes crash 2021-02-11 21:39:31 +00:00
Curtis Hard c886a6a80d trolled by CGPath not being identical to bezierpath 2021-02-10 22:21:13 +00:00
Curtis Hard 4cd2895703 Seems to improve performance 2021-02-10 19:27:08 +00:00
Curtis Hard a98d84f209 Removed bezier path additions as its now useless
- more perfromance increases
2021-02-08 20:56:08 +00:00
Curtis Hard 8faab28823 Performance increases 2021-02-08 17:23:43 +00:00
Curtis Hard cca5524a11 Converts all bezier path storage over to CGPath
There was no need to use NSBezierPath, we only used to to convert to CGPath eventually, so swapped it all overm much faster
2021-02-06 18:33:23 +00:00
Curtis Hard c14e2367a0 Formatting 2021-02-06 13:21:46 +00:00
Curtis Hard db4b0e9a18 Fixes crash if string is nil 2021-02-06 12:25:16 +00:00
Curtis Hard 93d6a868a9 reformat and use of strtok - did not realise it was already built into C 2021-02-05 21:28:48 +00:00
Curtis Hard 60fa627d99 Perf 2021-02-05 15:51:15 +00:00
Curtis Hard ccaa350190 Fixes issue with mem crash when no width or height is specified as a fallback 2021-02-03 22:03:38 +00:00
Curtis Hard 727f8a2dea Modern Object-C refactor
This refactors most of the old style synthesized properties and lets the compiler do it for us.

This also changes how unit lengths works (now does correct parsing and using cstrings instead of nsstring where applicable)
2021-01-30 18:40:29 +00:00
Curtis Hard 972812b3f6 Fixes arc commands 2021-01-26 08:56:04 +00:00
Curtis Hard ad8f440c72 Merge branch 'feature/cm-mm-units' 2021-01-25 19:32:47 +00:00
Curtis Hard 68393b296b Faster low level parsing for paths 2021-01-25 19:29:53 +00:00
Curtis Hard 1cfcb596d9 Much faster command parsing
tl;dr, stops converting between char*/unichar* and NSString*, the low level code for passing commands wants char* so no point of converting it to a higher level NSString* just to convert it back again, waste of time.
2021-01-24 22:18:07 +00:00
Curtis Hard 69077e49cf Faster parsing for very large SVGs 2021-01-24 19:16:10 +00:00
Curtis Hard 3bdf2151ca Rewrote how we parse transforms (its much faster)
Also much faster arc transform processsing
2021-01-23 18:58:39 +00:00
Curtis Hard f7e28a2962 Added originalType for future reference 2021-01-20 21:39:10 +00:00
Curtis Hard 2c07cfabdd Added more units and neatened up methods 2021-01-20 21:19:28 +00:00
Curtis Hard 3e356b3fdc Possible fix for CM and MM units 2021-01-20 21:00:15 +00:00
Curtis Hard 49f759edc0 Added extra header in 2021-01-10 16:55:28 +00:00
Curtis Hard 38e314eb99 Cant use description as its already a thing on NSObject. #oops 2020-11-22 18:50:09 +00:00
Curtis Hard d0eb015cf1 Added title and desc parsing into the IJSVGNode 2020-11-22 18:14:04 +00:00
Curtis Hard 080626a022 Removed warning 2020-11-14 16:44:13 +00:00
Curtis Hard 36c20bc55c Updated clang warning 2020-11-13 20:01:37 +00:00
Curtis Hard 4d89631e9a Brace in the wrong place! #oops 2020-09-21 20:16:53 +01:00
Curtis Hard 65f62007f3 Fixes exception being thrown and lower cased move command 2020-09-21 19:55:31 +01:00
Curtis Hard af5f16288d Updated opyright 2020-09-21 09:58:38 +01:00
Curtis Hard 08e0f9288d Adds support to remove default attributes from exported files 2020-09-16 18:40:05 +01:00
Curtis Hard 7650f6205c More improvements to exporting data 2020-09-13 20:36:29 +01:00
Curtis Hard 263768af5b Initial compression changes 2020-09-12 22:59:54 +01:00
Curtis Hard 4b1be17f9a Reduces exported data by a fair amount on larger SVGs by using NSXMLNodeCompactEmptyElement to self close tags
- also added option to get back an IJSVG object from the current export string
2020-09-08 18:57:20 +01:00
Curtis Hard 6b0d6b1452 This adds in floating point export options
- aswell as an option to round matrix values
2020-09-03 19:37:37 +01:00
Curtis Hard f80d4145bb Default rounds numbers, but not for transforms 2020-08-14 20:48:33 +01:00
Curtis Hard f68c83285b Fixes issuew here stroke-opacity was not exported 2020-08-14 20:13:53 +01:00
Curtis Hard 1f4bd989d8 Fixes RX and RY on rect 2020-08-13 12:10:49 +01:00
Curtis Hard 5b5d0b738b Added instruction passing for c -> s + t 2020-08-04 20:58:05 +01:00
Curtis Hard bd82c5d81b Only add the xlink namespace attribute on if required 2020-07-31 18:27:16 +01:00
Curtis Hard 145bbb17f8 Adds xml declaration removal to options 2020-07-29 20:13:01 +01:00
Curtis Hard d9f40551a4 Added ijsvg_isHexString for faster hex comparison 2020-07-27 12:06:03 +01:00
Curtis Hard 4448f6aeaf Small perf increase 2020-07-12 14:36:12 +01:00
Curtis Hard 6834abba19 Typo fix 2020-07-11 11:56:42 +01:00
Curtis Hard a86b4e80ba Refacator name 2020-07-09 16:32:20 +01:00
Curtis Hard 2e8d039599 Memory reductions 2020-06-27 19:00:38 +01:00
Curtis Hard 4ba6bb776d Xcode stuff 2020-06-27 12:28:35 +01:00
Curtis Hard 9c80412e88 Various image additions 2020-06-15 09:25:53 +01:00
Curtis Hard 509f0e0b0a Only allow removal of 0 if the float contains a floating point 2020-06-05 13:34:44 +01:00
Curtis Hard ce30877a26 Xcode stuff 2020-05-25 19:58:48 +01:00
Curtis Hard 122271bf36 Fixes for layer tree masking 2020-04-18 17:16:29 +01:00
Curtis Hard 28b4c6b85c Fixes issue with intrinsicSize size and dom being discarded 2020-03-01 18:19:38 +00:00
Curtis Hard 4b308d3a3e Wrong unit used 2020-02-27 08:33:16 +00:00
Curtis Hard d3ee05d8ac #oops 2020-02-25 08:29:59 +00:00
Curtis Hard 204b516e77 Fixes width and height not working correctly 2020-02-12 22:32:52 +00:00
Curtis Hard a832a986fd Swaps back to CATransaction 2020-01-16 20:37:48 +00:00
Curtis Hard f875714609 FOrmatting 2020-01-14 21:12:38 +00:00
Curtis Hard b8f166f4c1 Uses NSAnimationContext over CATransaction for safety 2020-01-14 21:12:33 +00:00
Curtis Hard 7901c9eafe Possible fixes for main thread 2020-01-14 20:41:59 +00:00
Curtis Hard 5de88cb1d7 #oopsie 2020-01-14 16:38:08 +00:00
Curtis Hard 6216b61c19 Fixes compat for 10.13 with CGPath block applying 2020-01-14 16:34:52 +00:00
Curtis Hard e05f5a0884 Fixes compatibility issues for 10.9 2020-01-14 15:51:15 +00:00
Curtis Hard 849807d3c5 Updated build number 2020-01-05 17:11:41 +00:00
Curtis Hard eeb03bd89c Fixes exporter being wrong if the viewbox is not zero 2020-01-02 14:16:17 +00:00
Curtis Hard fdc0859e3f Faster parsing still 2020-01-01 22:06:29 +00:00
Curtis Hard e2a7f51507 Fixes 2020-01-01 17:48:15 +00:00
Curtis Hard ab69ed94dc Formatting 2019-12-29 20:50:44 +00:00
Curtis Hard c5be83f57f Code cleanup 2019-12-28 15:48:43 +00:00
Curtis Hard ed00aca06e Refactor 2019-12-27 16:48:12 +00:00
Curtis Hard f51fda5f1e Removed cache
- also fixes center if no scale was applied
2019-12-27 15:01:20 +00:00
Curtis Hard dd655053ca Finally fixes memory leak
- due to this being fixed, we can also get rid of the transaction lock
2019-12-26 20:10:47 +00:00
Curtis Hard 8c89627f74 Just use matrix, attributes are a pain in the butt 2019-12-26 16:08:14 +00:00
Curtis Hard eb1d27decd Much better path compression and float rewriting for exporting 2019-12-26 13:49:41 +00:00
Curtis Hard e6141d7a0f Fixes a long time ago bug with transforms with matrix converting to attribute strings 2019-12-25 21:05:56 +00:00
Curtis Hard 3b7ad40794 Vey strange... 2019-12-25 18:40:50 +00:00
Curtis Hard 674d17863f Fixes memory overflow issues
- also added centering for exported SVG’s as an option
2019-12-25 18:19:28 +00:00
Curtis Hard a59072fa1d Refactor 2019-12-25 13:53:42 +00:00
Curtis Hard 910b90cd4e More perf stuff 2019-12-25 13:45:13 +00:00
Curtis Hard bb3b9c5378 More improvements 2019-12-25 11:58:10 +00:00
Curtis Hard 6d10b172e7 Increases again 2019-12-24 14:47:47 +00:00
Curtis Hard c6e59a9280 More perf increases 2019-12-24 09:27:06 +00:00
Curtis Hard df5b3219ca Refactor of naming 2019-12-23 21:59:19 +00:00
Curtis Hard 2c1ae8d0f3 Credit where its due 2019-12-23 21:40:02 +00:00
Curtis Hard 7ba939aabf This is insanely fast 2019-12-23 20:48:12 +00:00
Curtis Hard 24e097fedf Faster again! 2019-12-23 20:30:05 +00:00
Curtis Hard 025ac84958 Refactor of command parsing 2019-12-23 19:37:56 +00:00
Curtis Hard ac9ccdda25 Refactor 2019-12-23 16:17:47 +00:00
Curtis Hard 14641ddd60 More goodies 2019-12-23 15:40:41 +00:00
Curtis Hard 5f32d03744 Intiial commit 2019-12-23 14:24:49 +00:00
Curtis Hard af086622b1 Faster implementation of transaction 2019-12-22 22:19:20 +00:00
Curtis Hard 79187326bc Added CGImageRef method 2019-12-21 13:23:52 +00:00
Curtis Hard ee570eb77f Fixes scale being incorrect 2019-12-20 08:36:12 +00:00
Curtis Hard d8e1ce8d70 This is much more performant then using nsimage lockFocus 2019-12-19 22:32:55 +00:00
Curtis Hard a900e2dc50 Resorted this list as its actually faster, basically stick the commonly used commands first 2019-12-17 21:48:52 +00:00
Curtis Hard 21f97babec Refactor of contsant names 2019-12-12 11:02:49 +00:00
Curtis Hard 0273a491de Refactor of path - uses less memory and makes more sense 2019-12-12 10:51:20 +00:00
Curtis Hard 488a0c3654 Update all things! 2019-12-12 08:09:08 +00:00
Curtis Hard 7f7321e6a7 Merge branch 'feature/primitiveExportTypes' 2019-12-12 08:07:05 +00:00
Curtis Hard d0878c9f1b Added convenience method in 2019-12-11 15:47:12 +00:00
Curtis Hard cd135ce271 Refactor and more performant string passing 2019-12-11 13:22:16 +00:00
Curtis Hard b369891e96 Added sequences to float parsing 2019-12-10 22:25:21 +00:00
Curtis Hard cde99fc501 Fixes memory leak 2019-12-06 12:03:44 +00:00
Curtis Hard efc5f139f6 Formatting 2019-12-06 08:26:03 +00:00
Curtis Hard f1c62ed828 Fixes a few things 2019-12-05 21:07:40 +00:00
Curtis Hard d66332ad07 Added is main thread check to transaction 2019-12-05 11:12:45 +00:00
Curtis Hard f2363769bf replaced calls with prefix 2019-12-04 12:58:08 +00:00
Curtis Hard ca1f0d9bf3 Performance increases and memory enhancements 2019-12-04 12:52:58 +00:00
Curtis Hard 8b7fc3171d More goodies 2019-12-04 08:59:55 +00:00
Curtis Hard 38df2094c9 Refactor and moving of groups 2019-12-04 08:59:46 +00:00
Curtis Hard 2355e86ed7 Fixes memory leak 2019-12-03 22:43:25 +00:00
Curtis Hard 192a9259fe Fixes performance here 2019-12-03 22:38:20 +00:00
Curtis Hard 2a9eb3cdc5 Added support for transparent 2019-12-03 14:26:11 +00:00
Curtis Hard 6ed4e3fb8e Export primitive shapes if they were that on import 2019-12-03 14:17:31 +00:00
Curtis Hard f6e1f1494e Initial stuff 2019-12-03 09:22:46 +00:00
Curtis Hard 775e5c15a0 Performance increases and modern syntax things 2019-12-02 22:30:52 +00:00
Curtis Hard abd19c78dd This updates formatting to meet the WebKit standard 2019-12-02 21:05:12 +00:00
Curtis Hard 29090f30f9 Fixes stuff 2019-11-28 21:56:56 +00:00
Curtis Hard e2a2fc615c Fixes warning about commas 2019-11-28 20:33:04 +00:00
Curtis Hard 5466e0f2ec More framework stuff 2019-11-28 13:26:37 +00:00
Curtis Hard c2f0c25913 Updated framework stuff 2019-11-28 13:21:08 +00:00
Curtis Hard cddc9554b4 Start of framework 2019-11-28 12:36:14 +00:00
Curtis Hard ffdbe1ec9c Fixes userSpaceOnUse for masks and clipPaths 2019-11-27 22:08:12 +00:00
Curtis Hard 63586f1181 Reformat 2019-11-18 22:57:55 +00:00
Curtis Hard dc94d9761b Fixes subcommands, this is fairly big bug… 2019-11-18 22:55:44 +00:00
Curtis Hard 8deef07f7f Merge branch 'feature/primitive-colors' of https://github.com/curthard89/IJSVG into feature/primitive-colors 2019-10-24 19:31:54 +01:00
Curtis Hard 2c962db6cc Refactoring of commands 2019-10-24 19:31:51 +01:00
Curtis Hard 9dda7915fa Fixes shorthand 2019-10-22 14:11:12 +01:00
Curtis Hard 1fbb56b443 Fixes shorthand not working correctly 2019-10-22 14:10:23 +01:00
Curtis Hard 4bbf2277bc Fixes exporter 2019-10-20 18:50:34 +01:00
Curtis Hard 7eeeea5167 Adds support for RRGGBBAA 2019-10-20 17:07:07 +01:00
290 changed files with 75686 additions and 27044 deletions
+1
View File
@@ -0,0 +1 @@
.DS_Store
+5
View File
@@ -0,0 +1,5 @@
framework module IJSVG {
umbrella header "IJSVGUmbrella.h"
export *
module * { export * }
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:IJSVG.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "594CF46E238FF38E009B251B"
BuildableName = "IJSVG.framework"
BlueprintName = "IJSVG"
ReferencedContainer = "container:IJSVG.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "594CF46E238FF38E009B251B"
BuildableName = "IJSVG.framework"
BlueprintName = "IJSVG"
ReferencedContainer = "container:IJSVG.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>IJSVG.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>594CF46E238FF38E009B251B</key>
<dict>
<key>primary</key>
<true />
</dict>
</dict>
</dict>
</plist>
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Curtis Hard. All rights reserved.</string>
</dict>
</plist>
@@ -10,9 +10,10 @@
@interface NSString (IJSVGAdditions)
- (NSArray<NSString *> *)componentsSeparatedByChars:(char *)aChar;
- (BOOL)isNumeric;
- (BOOL)containsAlpha;
- (NSArray *)componentsSplitByWhiteSpace;
- (NSArray<NSString*>*)ijsvg_componentsSeparatedByChars:(const char*)aChar;
- (BOOL)ijsvg_isNumeric;
- (BOOL)ijsvg_containsAlpha;
- (NSArray*)ijsvg_componentsSplitByWhiteSpace;
- (BOOL)ijsvg_isHexString;
@end
@@ -0,0 +1,71 @@
//
// IJSVGStringAdditions.m
// IconJar
//
// Created by Curtis Hard on 07/06/2019.
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGStringAdditions.h>
#import <IJSVG/IJSVGUtils.h>
@implementation NSString (IJSVGAdditions)
- (NSArray<NSString*>*)ijsvg_componentsSeparatedByChars:(const char*)aChar
{
char* chars = (char*)self.UTF8String;
if(chars == NULL || strlen(chars) == 0) {
return @[];
}
NSMutableArray<NSString*>* strings = nil;
strings = [[NSMutableArray alloc] init];
char* copy = strdup(chars);
char* spt = NULL;
char* ptr = strtok_r(copy, aChar, &spt);
while(ptr != NULL) {
NSString* possibleString = nil;
if((possibleString = [NSString stringWithUTF8String:ptr]) != nil) {
[strings addObject:possibleString];
}
ptr = strtok_r(NULL, aChar, &spt);
}
(void)free(copy), copy = NULL;
return strings;
}
- (BOOL)ijsvg_containsAlpha
{
const char* buffer = self.UTF8String;
char currentChar;
while((currentChar = *buffer++) ) {
if(isalpha(currentChar)) {
return YES;
}
}
return NO;
}
- (BOOL)ijsvg_isNumeric
{
const char* buffer = self.UTF8String;
char currentChar;
while((currentChar = *buffer++) ) {
if(!isnumber(currentChar)) {
return NO;
}
}
return YES;
}
- (NSArray*)ijsvg_componentsSplitByWhiteSpace
{
return [self ijsvg_componentsSeparatedByChars:"\t\n\r "];
}
- (BOOL)ijsvg_isHexString
{
const char* chars = self.UTF8String;
return IJSVGCharBufferIsHEX((char*)chars);
}
@end
@@ -0,0 +1,18 @@
//
// NSImage+IJSVGAdditions.h
// IJSVG
//
// Created by Curtis Hard on 07/06/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
IJSVG* IJSVGGetFromNSImage(NSImage* image);
@interface NSImage (IJSVGAdditions)
+ (NSImage*)SVGImageNamed:(NSString*)imageName;
@end
@@ -0,0 +1,53 @@
//
// NSImage+IJSVGAdditions.m
// IJSVG
//
// Created by Curtis Hard on 07/06/2020.
// Copyright © 2020 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGImageRep.h>
#import <IJSVG/NSImage+IJSVGAdditions.h>
IJSVG* IJSVGGetFromNSImage(NSImage* image)
{
for (NSImageRep* rep in image.representations) {
if([rep isKindOfClass:IJSVGImageRep.class]) {
return ((IJSVGImageRep*)rep).SVG;
}
}
return nil;
}
@implementation NSImage (IJSVGAdditions)
+ (NSImage*)SVGImageNamed:(NSString*)imageName
{
// find the image
NSBundle* bundle = NSBundle.mainBundle;
NSString* str = nil;
NSString* ext = imageName.pathExtension;
if(ext == nil || ext.length == 0) {
ext = @"svg";
}
if((str = [bundle pathForResource:imageName.stringByDeletingPathExtension
ofType:ext])
!= nil) {
// work out if we can get the data
NSData* data = [[NSData alloc] initWithContentsOfFile:str];
if(data == nil) {
return nil;
}
// grab the image rep
IJSVGImageRep* rep = [[IJSVGImageRep alloc] initWithData:data];
NSImage* image = [[NSImage alloc] init];
[image addRepresentation:rep];
return image;
}
return nil;
}
@end
@@ -6,9 +6,18 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
typedef NS_ENUM( NSInteger, IJSVGPredefinedColor ) {
typedef NS_OPTIONS(NSInteger, IJSVGColorStringOptions) {
IJSVGColorStringOptionNone = 1 << 0,
IJSVGColorStringOptionForceHEX = 1 << 1,
IJSVGColorStringOptionAllowShortHand = 1 << 2,
IJSVGColorStringOptionAllowRRGGBBAA = 1 << 3,
IJSVGColorStringOptionDefault = IJSVGColorStringOptionAllowShortHand
};
typedef NS_ENUM(NSInteger, IJSVGPredefinedColor) {
IJSVGColorAliceblue,
IJSVGColorAntiquewhite,
IJSVGColorAqua,
@@ -158,29 +167,34 @@ typedef NS_ENUM( NSInteger, IJSVGPredefinedColor ) {
IJSVGColorYellowgreen
};
extern NSString* const IJSVGColorCurrentColorName;
@interface IJSVGColor : NSObject
CGFloat * IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness);
CGFloat* IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness);
+ (NSColor *)computeColorSpace:(NSColor *)color;
+ (NSColorSpace *)defaultColorSpace;
+ (BOOL)isColor:(NSString *)string;
+ (NSString *)colorStringFromColor:(NSColor *)color
forceHex:(BOOL)forceHex
allowShorthand:(BOOL)allowShorthand;
+ (NSString *)colorStringFromColor:(NSColor *)color;
+ (NSColor *)colorFromHEXInteger:(NSInteger)hex;
+ (NSColor *)computeColor:(id)colour;
+ (NSColor *)colorFromString:(NSString *)string;
+ (NSColor *)colorFromHEXString:(NSString *)string
alpha:(CGFloat)alpha;
+ (NSColor *)colorFromRString:(NSString *)rString
gString:(NSString *)gString
bString:(NSString *)bString
aString:(NSString *)aString;
+ (NSColor *)colorFromPredefinedColorName:(NSString *)name;
+ (NSString *)colorNameFromPredefinedColor:(IJSVGPredefinedColor)color;
+ (NSColor *)changeAlphaOnColor:(NSColor *)color
to:(CGFloat)alphaValue;
+ (NSColor*)computeColorSpace:(NSColor*)color;
+ (NSColorSpace*)defaultColorSpace;
+ (BOOL)isColor:(NSString*)string;
+ (NSString*)colorStringFromColor:(NSColor*)color
options:(IJSVGColorStringOptions)options;
+ (NSString*)colorStringFromColor:(NSColor*)color;
+ (NSColor*)colorFromHEXInteger:(NSInteger)hex;
+ (NSColor*)computeColor:(id)colour;
+ (BOOL)isNoneOrTransparent:(NSString*)string;
+ (NSColor*)colorFromString:(NSString*)string;
+ (NSColor*)colorFromHEXString:(NSString*)string;
+ (NSColor*)colorFromHEXString:(NSString*)string
containsAlphaComponent:(BOOL*)containsAlphaComponent;
+ (BOOL)HEXContainsAlphaComponent:(NSUInteger)hex;
+ (unsigned long)lengthOfHEXInteger:(NSUInteger)hex;
+ (NSColor*)colorFromRString:(NSString*)rString
gString:(NSString*)gString
bString:(NSString*)bString
aString:(NSString*)aString;
+ (NSColor*)colorFromPredefinedColorName:(NSString*)name;
+ (NSString*)colorNameFromPredefinedColor:(IJSVGPredefinedColor)color;
+ (NSColor*)changeAlphaOnColor:(NSColor*)color
to:(CGFloat)alphaValue;
@end
@@ -0,0 +1,841 @@
//
// IJSVGColor.m
// IconJar
//
// Created by Curtis Hard on 31/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGColor.h>
#import <IJSVG/IJSVGUtils.h>
#import <IJSVG/IJSVGStringAdditions.h>
#import <IJSVG/IJSVGParsing.h>
#import <IJSVG/IJSVGParser.h>
NSString* const IJSVGColorCurrentColorName = @"currentColor";
@implementation IJSVGColor
static NSDictionary* _colorTree = nil;
CGFloat* IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightness)
{
hue *= (1.f / 360.f);
hue = (hue - floorf(hue));
saturation *= 0.01;
lightness *= 0.01;
lightness *= 2.f;
CGFloat s = saturation * ((lightness < 1.f) ? lightness : (2.f - lightness));
CGFloat brightness = (lightness + s) * .5f;
if(s != 0.f) {
s = (2.f * s) / (lightness + s);
}
CGFloat* floats = (CGFloat*)malloc(3 * sizeof(CGFloat));
floats[0] = hue;
floats[1] = s;
floats[2] = brightness;
return floats;
};
+ (void)load
{
[self.class _generateTree];
}
+ (NSColorSpace*)defaultColorSpace
{
return NSColorSpace.deviceRGBColorSpace;
}
+ (NSColor*)computeColorSpace:(NSColor*)color
{
NSColorSpace* space = [self defaultColorSpace];
if(color.colorSpace != space) {
color = [color colorUsingColorSpace:space];
}
return color;
}
+ (void)_generateTree
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_colorTree = @{
@"aliceblue" : @(0xf0f8ff),
@"antiquewhite" : @(0xfaebd7),
@"aqua" : @(0x00ffff),
@"aquamarine" : @(0x7fffd4),
@"azure" : @(0xf0ffff),
@"beige" : @(0xf5f5dc),
@"bisque" : @(0xffe4c4),
@"black" : @(0x000000),
@"blanchedalmond" : @(0xffebcd),
@"blue" : @(0x0000ff),
@"blueviolet" : @(0x8a2be2),
@"brown" : @(0xa52a2a),
@"burlywood" : @(0xdeb887),
@"cadetblue" : @(0x5f9ea0),
@"chartreuse" : @(0x7fff00),
@"chocolate" : @(0xd2691e),
@"coral" : @(0xff7f50),
@"cornflowerblue" : @(0x6495ed),
@"cornsilk" : @(0xfff8dc),
@"crimson" : @(0xdc143c),
@"currentcolor" : @(0x000000),
@"cyan" : @(0x00ffff),
@"darkblue" : @(0x00008b),
@"darkcyan" : @(0x008b8b),
@"darkgoldenrod" : @(0xb8860b),
@"darkgray" : @(0xa9a9a9),
@"darkgreen" : @(0x006400),
@"darkgrey" : @(0xa9a9a9),
@"darkkhaki" : @(0xbdb76b),
@"darkmagenta" : @(0x8b008b),
@"darkolivegreen" : @(0x556b2f),
@"darkorange" : @(0xff8c00),
@"darkorchid" : @(0x9932cc),
@"darkred" : @(0x8b0000),
@"darksalmon" : @(0xe9967a),
@"darkseagreen" : @(0x8fbc8f),
@"darkslateblue" : @(0x483d8b),
@"darkslategray" : @(0x2f4f4f),
@"darkturquoise" : @(0x00ced1),
@"darkviolet" : @(0x9400d3),
@"deeppink" : @(0xff1493),
@"deepskyblue" : @(0x00bfff),
@"dimgray" : @(0x696969),
@"dimgrey" : @(0x696969),
@"dodgerblue" : @(0x1e90ff),
@"firebrick" : @(0xb22222),
@"floralwhite" : @(0xfffaf0),
@"forestgreen" : @(0x228b22),
@"fuchsia" : @(0xff00ff),
@"gainsboro" : @(0xdcdcdc),
@"ghostwhite" : @(0xf8f8ff),
@"gold" : @(0xffd700),
@"goldenrod" : @(0xdaa520),
@"gray" : @(0x808080),
@"green" : @(0x008000),
@"greenyellow" : @(0xadff2f),
@"grey" : @(0x808080),
@"honeydew" : @(0xf0fff0),
@"hotpink" : @(0xff69b4),
@"indianred" : @(0xcd5c5c),
@"indigo" : @(0x4b0082),
@"ivory" : @(0xfffff0),
@"khaki" : @(0xf0e68c),
@"lavender" : @(0xe6e6fa),
@"lavenderblush" : @(0xfff0f5),
@"lawngreen" : @(0x7cfc00),
@"lemonchiffon" : @(0xfffacd),
@"lightblue" : @(0xadd8e6),
@"lightcoral" : @(0xf08080),
@"lightcyan" : @(0xe0ffff),
@"lightgoldenrodyellow" : @(0xfafad2),
@"lightgray" : @(0xd3d3d3),
@"lightgreen" : @(0x90ee90),
@"lightgrey" : @(0xd3d3d3),
@"lightpink" : @(0xffb6c1),
@"lightsalmon" : @(0xffa07a),
@"lightseagreen" : @(0x20b2aa),
@"lightskyblue" : @(0x87cefa),
@"lightslategray" : @(0x778899),
@"lightsteelblue" : @(0xb0c4de),
@"lightyellow" : @(0xffffe0),
@"lime" : @(0x00ff00),
@"limegreen" : @(0x32cd32),
@"linen" : @(0xfaf0e6),
@"magenta" : @(0xff00ff),
@"maroon" : @(0x800000),
@"mediumaquamarine" : @(0x66cdaa),
@"mediumblue" : @(0x0000cd),
@"mediumorchid" : @(0xba55d3),
@"mediumpurple" : @(0x9370db),
@"mediumseagreen" : @(0x3cb371),
@"mediumslateblue" : @(0x7b68ee),
@"mediumspringgreen" : @(0x00fa9a),
@"mediumturquoise" : @(0x48d1cc),
@"mediumvioletred" : @(0xc71585),
@"midnightblue" : @(0x191970),
@"mintcream" : @(0xf5fffa),
@"mistyrose" : @(0xffe4e1),
@"moccasin" : @(0xffe4b5),
@"navajowhite" : @(0xffdead),
@"navy" : @(0x000080),
@"oldlace" : @(0xfdf5e6),
@"olive" : @(0x808000),
@"olivedrab" : @(0x6b8e23),
@"orange" : @(0xffa500),
@"orangered" : @(0xff4500),
@"orchid" : @(0xda70d6),
@"palegoldenrod" : @(0xeee8aa),
@"palegreen" : @(0x98fb98),
@"paleturquoise" : @(0xafeeee),
@"palevioletred" : @(0xdb7093),
@"papayawhip" : @(0xffefd5),
@"peachpuff" : @(0xffdab9),
@"peru" : @(0xcd853f),
@"pink" : @(0xffc0cb),
@"plum" : @(0xdda0dd),
@"powderblue" : @(0xb0e0e6),
@"purple" : @(0x800080),
@"red" : @(0xff0000),
@"rosybrown" : @(0xbc8f8f),
@"royalblue" : @(0x4169e1),
@"saddlebrown" : @(0x8b4513),
@"salmon" : @(0xfa8072),
@"sandybrown" : @(0xf4a460),
@"seagreen" : @(0x2e8b57),
@"seashell" : @(0xfff5ee),
@"sienna" : @(0xa0522d),
@"silver" : @(0xc0c0c0),
@"skyblue" : @(0x87ceeb),
@"slateblue" : @(0x6a5acd),
@"slategrey" : @(0x708090),
@"snow" : @(0xfffafa),
@"springgreen" : @(0x00ff7f),
@"steelblue" : @(0x4682b4),
@"tan" : @(0xd2b48c),
@"teal" : @(0x008080),
@"thistle" : @(0xd8bfd8),
@"tomato" : @(0xff6347),
@"turquoise" : @(0x40e0d0),
@"violet" : @(0xee82ee),
@"wheat" : @(0xf5deb3),
@"white" : @(0xffffff),
@"whitesmoke" : @(0xf5f5f5),
@"yellow" : @(0xffff00),
@"yellowgreen" : @(0x9acd32)
};
});
}
+ (NSColor*)computeColor:(id)colour
{
if([colour isKindOfClass:[NSColor class]])
return colour;
return nil;
}
+ (NSColor*)colorFromRString:(NSString*)rString
gString:(NSString*)gString
bString:(NSString*)bString
aString:(NSString*)aString
{
return [self colorFromRUnit:[IJSVGUnitLength unitWithString:rString]
gUnit:[IJSVGUnitLength unitWithString:gString]
bUnit:[IJSVGUnitLength unitWithString:bString]
aUnit:[IJSVGUnitLength unitWithString:aString]];
}
+ (NSColor*)colorFromRUnit:(IJSVGUnitLength*)rUnit
gUnit:(IJSVGUnitLength*)gUnit
bUnit:(IJSVGUnitLength*)bUnit
aUnit:(IJSVGUnitLength*)aUnit
{
CGFloat r = rUnit.type == IJSVGUnitLengthTypePercentage ? [rUnit computeValue:255.f] : [rUnit computeValue:1.f];
CGFloat g = gUnit.type == IJSVGUnitLengthTypePercentage ? [gUnit computeValue:255.f] : [gUnit computeValue:1.f];
CGFloat b = bUnit.type == IJSVGUnitLengthTypePercentage ? [bUnit computeValue:255.f] : [bUnit computeValue:1.f];
CGFloat a = [aUnit computeValue:100.f];
return [self computeColorSpace:[NSColor colorWithDeviceRed:(r / 255.f)
green:(g / 255.f)
blue:(b / 255.f)
alpha:a]];
}
+ (BOOL)isNoneOrTransparent:(NSString*)string
{
const char* str = string.UTF8String;
return IJSVGCharBufferCaseInsensitiveCompare(str, "none") == YES ||
IJSVGCharBufferCaseInsensitiveCompare(str, "transparent") == YES;
}
+ (NSColor*)colorFromString:(NSString*)string
{
// swap over to C for performance
if(string == nil) {
return nil;
}
const char* oString = string.UTF8String;
if(strlen(oString) == 0) {
return nil;
}
char* str = IJSVGTimmedCharBufferCreate(oString);
if(IJSVGCharBufferIsHEX(str) == YES) {
string = [NSString stringWithUTF8String:str];
(void)free(str), str = NULL;
return [self.class colorFromHEXString:string];
}
// is it RGB?
if(IJSVGCharBufferHasPrefix(str, "rgb") == YES) {
NSUInteger count = 0;
IJSVGParsingStringMethod** methods = NULL;
methods = IJSVGParsingMethodParseString(str, &count);
IJSVGParsingStringMethod* method = methods[0];
// memory clean for the string
(void)free(str), str = NULL;
// nothing to return, just mem clean and get out of here
if(count == 0 || methods == NULL) {
if(methods != NULL) {
IJSVGParsingStringMethodsRelease(methods, count);
methods = NULL;
}
return nil;
}
// Make sure we have actual floats within the parameters
NSInteger floatCount = 0;
CGFloat *params = [IJSVGUtils scanFloatsFromCString:method->parameters
size:&floatCount];
(void)free(params), params = NULL;
if(floatCount < 3) {
return [self computeColorSpace:NSColor.blackColor];
}
// Make sure the floats are not negative
// parse the parameters
NSString* parameters = [NSString stringWithUTF8String:method->parameters];
NSArray* parts = [parameters ijsvg_componentsSeparatedByChars:","];
NSString* alpha = @"100%";
if(parts.count == 4) {
alpha = parts[3];
}
IJSVGParsingStringMethodsRelease(methods, count);
methods = NULL;
return [self colorFromRString:parts[0]
gString:parts[1]
bString:parts[2]
aString:alpha];
}
// is it HSL?
if(IJSVGCharBufferHasPrefix(str, "hsl")) {
NSInteger count = 0;
CGFloat* params = [IJSVGUtils scanFloatsFromCString:str
size:&count];
CGFloat alpha = 1;
if(count == 4) {
alpha = params[3];
}
// convert HSL to HSB
CGFloat* hsb = IJSVGColorCSSHSLToHSB(params[0], params[1], params[2]);
NSColor* color = [NSColor colorWithDeviceHue:hsb[0]
saturation:hsb[1]
brightness:hsb[2]
alpha:alpha];
color = [self computeColorSpace:color];
// memory clean!
(void)free(str), str = NULL;
(void)free(hsb), hsb = NULL;
(void)free(params), params = NULL;
return color;
}
// is simply a clear color, dont fill
if(IJSVGCharBufferCompare(str, "none") == YES ||
IJSVGCharBufferCompare(str, "transparent") == YES) {
(void)free(str), str = NULL;
return nil;
}
// could return nil
(void)free(str), str = NULL;
return [self.class colorFromPredefinedColorName:string];
}
+ (NSColor*)colorFromPredefinedColorName:(NSString*)name
{
NSNumber* hex = nil;
name = [name.lowercaseString stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
if((hex = _colorTree[name]) == nil) {
return nil;
}
return [self.class colorFromHEXInteger:hex.integerValue];
}
+ (NSString*)colorStringFromColor:(NSColor*)color
{
IJSVGColorStringOptions options = IJSVGColorStringOptionDefault;
return [self colorStringFromColor:color
options:options];
}
+ (NSString*)colorStringFromColor:(NSColor*)color
options:(IJSVGColorStringOptions)options
{
// convert to RGB
color = [self computeColorSpace:color];
int red = color.redComponent * 0xFF;
int green = color.greenComponent * 0xFF;
int blue = color.blueComponent * 0xFF;
int alpha = (int)(color.alphaComponent * 100);
BOOL forceHex = (options & IJSVGColorStringOptionForceHEX) != 0;
BOOL allowShortHand = (options & IJSVGColorStringOptionAllowShortHand) != 0;
BOOL allowRRGGBBAA = (options & IJSVGColorStringOptionAllowRRGGBBAA) != 0;
// jsut return none
if(alpha == 0 && forceHex == NO) {
return IJSVGStringNone;
}
// always return hex unless criteria is met
if(forceHex == YES || allowRRGGBBAA == YES || alpha == 100 || (red == 0 && green == 0 && blue == 0 && alpha == 0) || (red == 255 && green == 255 && blue == 255 && alpha == 100)) {
// we need to make sure the last 2 chars
// are the same or we cant enable shorthand
if(allowRRGGBBAA == YES) {
NSString* alphaHexString = [NSString stringWithFormat:@"%02X",
(int)(color.alphaComponent * 0xFF)];
if([alphaHexString characterAtIndex:0] !=
[alphaHexString characterAtIndex:1]) {
allowShortHand = NO;
}
}
if(allowShortHand == YES) {
NSString* r = [NSString stringWithFormat:@"%02X", red];
NSString* g = [NSString stringWithFormat:@"%02X", green];
NSString* b = [NSString stringWithFormat:@"%02X", blue];
if([r characterAtIndex:0] == [r characterAtIndex:1] &&
[g characterAtIndex:0] == [g characterAtIndex:1] &&
[b characterAtIndex:0] == [b characterAtIndex:1]) {
// allow shorthand alpha
if(allowRRGGBBAA == YES && alpha != 100) {
NSString* a = [NSString stringWithFormat:@"%02X",
(int)(color.alphaComponent * 0xFF)];
return [NSString stringWithFormat:@"#%c%c%c%c",
[r characterAtIndex:0], [g characterAtIndex:0],
[b characterAtIndex:0], [a characterAtIndex:0]];
}
return [NSString stringWithFormat:@"#%c%c%c", [r characterAtIndex:0],
[g characterAtIndex:0], [b characterAtIndex:0]];
}
}
if(allowRRGGBBAA == YES && alpha != 100) {
return [NSString stringWithFormat:@"#%02X%02X%02X%02X", red, green,
blue, (int)(color.alphaComponent * 0xFF)];
}
return [NSString stringWithFormat:@"#%02X%02X%02X", red, green, blue];
}
// note the %g, CSS alpha is 0 to 1, not 0 - 100, my bad!
return [NSString stringWithFormat:@"rgba(%d,%d,%d,%@)", red, green, blue,
IJSVGShortFloatString((float)alpha / 100.f)];
}
+ (NSString*)colorNameFromPredefinedColor:(IJSVGPredefinedColor)color
{
switch (color) {
case IJSVGColorAliceblue:
return @"aliceblue";
case IJSVGColorAntiquewhite:
return @"antiquewhite";
case IJSVGColorAqua:
return @"aqua";
case IJSVGColorAquamarine:
return @"aquamarine";
case IJSVGColorAzure:
return @"azure";
case IJSVGColorBeige:
return @"beige";
case IJSVGColorBisque:
return @"bisque";
case IJSVGColorBlack:
return @"black";
case IJSVGColorBlanchedalmond:
return @"blanchedalmond";
case IJSVGColorBlue:
return @"blue";
case IJSVGColorBlueviolet:
return @"blueviolet";
case IJSVGColorBrown:
return @"brown";
case IJSVGColorBurlywood:
return @"burlywood";
case IJSVGColorCadetblue:
return @"cadetblue";
case IJSVGColorChartreuse:
return @"chartreuse";
case IJSVGColorChocolate:
return @"chocolate";
case IJSVGColorCoral:
return @"coral";
case IJSVGColorCornflowerblue:
return @"cornflowerblue";
case IJSVGColorCornsilk:
return @"cornsilk";
case IJSVGColorCrimson:
return @"crimson";
case IJSVGColorCyan:
return @"cyan";
case IJSVGColorDarkblue:
return @"darkblue";
case IJSVGColorDarkcyan:
return @"darkcyan";
case IJSVGColorDarkgoldenrod:
return @"darkgoldenrod";
case IJSVGColorDarkgray:
return @"darkgray";
case IJSVGColorDarkgreen:
return @"darkgreen";
case IJSVGColorDarkgrey:
return @"darkgrey";
case IJSVGColorDarkkhaki:
return @"darkkhaki";
case IJSVGColorDarkmagenta:
return @"darkmagenta";
case IJSVGColorDarkolivegreen:
return @"darkolivegreen";
case IJSVGColorDarkorange:
return @"darkorange";
case IJSVGColorDarkorchid:
return @"darkorchid";
case IJSVGColorDarkred:
return @"darkred";
case IJSVGColorDarksalmon:
return @"darksalmon";
case IJSVGColorDarkseagreen:
return @"darkseagreen";
case IJSVGColorDarkslateblue:
return @"darkslateblue";
case IJSVGColorDarkslategray:
return @"darkslategray";
case IJSVGColorDarkslategrey:
return @"darkslategrey";
case IJSVGColorDarkturquoise:
return @"darkturquoise";
case IJSVGColorDarkviolet:
return @"darkviolet";
case IJSVGColorDeeppink:
return @"deeppink";
case IJSVGColorDeepskyblue:
return @"deepskyblue";
case IJSVGColorDimgray:
return @"dimgray";
case IJSVGColorDimgrey:
return @"dimgrey";
case IJSVGColorDodgerblue:
return @"dodgerblue";
case IJSVGColorFirebrick:
return @"firebrick";
case IJSVGColorFloralwhite:
return @"floralwhite";
case IJSVGColorForestgreen:
return @"forestgreen";
case IJSVGColorFuchsia:
return @"fuchsia";
case IJSVGColorGainsboro:
return @"gainsboro";
case IJSVGColorGhostwhite:
return @"ghostwhite";
case IJSVGColorGold:
return @"gold";
case IJSVGColorGoldenrod:
return @"goldenrod";
case IJSVGColorGray:
return @"gray";
case IJSVGColorGreen:
return @"green";
case IJSVGColorGreenyellow:
return @"greenyellow";
case IJSVGColorGrey:
return @"grey";
case IJSVGColorHoneydew:
return @"honeydew";
case IJSVGColorHotpink:
return @"hotpink";
case IJSVGColorIndianred:
return @"indianred";
case IJSVGColorIndigo:
return @"indigo";
case IJSVGColorIvory:
return @"ivory";
case IJSVGColorKhaki:
return @"khaki";
case IJSVGColorLavender:
return @"lavender";
case IJSVGColorLavenderblush:
return @"lavenderblush";
case IJSVGColorLawngreen:
return @"lawngreen";
case IJSVGColorLemonchiffon:
return @"lemonchiffon";
case IJSVGColorLightblue:
return @"lightblue";
case IJSVGColorLightcoral:
return @"lightcoral";
case IJSVGColorLightcyan:
return @"lightcyan";
case IJSVGColorLightgoldenrodyellow:
return @"lightgoldenrodyellow";
case IJSVGColorLightgray:
return @"lightgray";
case IJSVGColorLightgreen:
return @"lightgreen";
case IJSVGColorLightgrey:
return @"lightgrey";
case IJSVGColorLightpink:
return @"lightpink";
case IJSVGColorLightsalmon:
return @"lightsalmon";
case IJSVGColorLightseagreen:
return @"lightseagreen";
case IJSVGColorLightskyblue:
return @"lightskyblue";
case IJSVGColorLightslategray:
return @"lightslategray";
case IJSVGColorLightslategrey:
return @"lightslategrey";
case IJSVGColorLightsteelblue:
return @"lightsteelblue";
case IJSVGColorLightyellow:
return @"lightyellow";
case IJSVGColorLime:
return @"lime";
case IJSVGColorLimegreen:
return @"limegreen";
case IJSVGColorLinen:
return @"linen";
case IJSVGColorMagenta:
return @"magenta";
case IJSVGColorMaroon:
return @"maroon";
case IJSVGColorMediumaquamarine:
return @"mediumaquamarine";
case IJSVGColorMediumblue:
return @"mediumblue";
case IJSVGColorMediumorchid:
return @"mediumorchid";
case IJSVGColorMediumpurple:
return @"mediumpurple";
case IJSVGColorMediumseagreen:
return @"mediumseagreen";
case IJSVGColorMediumslateblue:
return @"mediumslateblue";
case IJSVGColorMediumspringgreen:
return @"mediumspringgreen";
case IJSVGColorMediumturquoise:
return @"mediumturquoise";
case IJSVGColorMediumvioletred:
return @"mediumvioletred";
case IJSVGColorMidnightblue:
return @"midnightblue";
case IJSVGColorMintcream:
return @"mintcream";
case IJSVGColorMistyrose:
return @"mistyrose";
case IJSVGColorMoccasin:
return @"moccasin";
case IJSVGColorNavajowhite:
return @"navajowhite";
case IJSVGColorNavy:
return @"navy";
case IJSVGColorOldlace:
return @"oldlace";
case IJSVGColorOlive:
return @"olive";
case IJSVGColorOlivedrab:
return @"olivedrab";
case IJSVGColorOrange:
return @"orange";
case IJSVGColorOrangered:
return @"orangered";
case IJSVGColorOrchid:
return @"orchid";
case IJSVGColorPalegoldenrod:
return @"palegoldenrod";
case IJSVGColorPalegreen:
return @"palegreen";
case IJSVGColorPaleturquoise:
return @"paleturquoise";
case IJSVGColorPalevioletred:
return @"palevioletred";
case IJSVGColorPapayawhip:
return @"papayawhip";
case IJSVGColorPeachpuff:
return @"peachpuff";
case IJSVGColorPeru:
return @"peru";
case IJSVGColorPink:
return @"pink";
case IJSVGColorPlum:
return @"plum";
case IJSVGColorPowderblue:
return @"powderblue";
case IJSVGColorPurple:
return @"purple";
case IJSVGColorRed:
return @"red";
case IJSVGColorRosybrown:
return @"rosybrown";
case IJSVGColorRoyalblue:
return @"royalblue";
case IJSVGColorSaddlebrown:
return @"saddlebrown";
case IJSVGColorSalmon:
return @"salmon";
case IJSVGColorSandybrown:
return @"sandybrown";
case IJSVGColorSeagreen:
return @"seagreen";
case IJSVGColorSeashell:
return @"seashell";
case IJSVGColorSienna:
return @"sienna";
case IJSVGColorSilver:
return @"silver";
case IJSVGColorSkyblue:
return @"skyblue";
case IJSVGColorSlateblue:
return @"slateblue";
case IJSVGColorSlategray:
return @"slategray";
case IJSVGColorSlategrey:
return @"slategrey";
case IJSVGColorSnow:
return @"snow";
case IJSVGColorSpringgreen:
return @"springgreen";
case IJSVGColorSteelblue:
return @"steelblue";
case IJSVGColorTan:
return @"tan";
case IJSVGColorTeal:
return @"teal";
case IJSVGColorThistle:
return @"thistle";
case IJSVGColorTomato:
return @"tomato";
case IJSVGColorTurquoise:
return @"turquoise";
case IJSVGColorViolet:
return @"violet";
case IJSVGColorWheat:
return @"wheat";
case IJSVGColorWhite:
return @"white";
case IJSVGColorWhitesmoke:
return @"whitesmoke";
case IJSVGColorYellow:
return @"yellow";
case IJSVGColorYellowgreen:
return @"yellowgreen";
}
return nil;
}
+ (NSColor*)changeAlphaOnColor:(NSColor*)color
to:(CGFloat)alphaValue
{
color = [self computeColorSpace:color];
return [self computeColorSpace:[NSColor colorWithDeviceRed:color.redComponent
green:color.greenComponent
blue:color.blueComponent
alpha:alphaValue]];
}
+ (BOOL)isColor:(NSString*)string
{
return [string hasPrefix:@"#"] || [string hasPrefix:@"rgb"];
}
+ (BOOL)isHex:(NSString*)string
{
return string.ijsvg_isHexString;
}
+ (unsigned long)lengthOfHEXInteger:(NSUInteger)hex
{
char* buffer;
asprintf(&buffer, "%lX", (long)hex);
unsigned long length = strlen(buffer);
free(buffer);
return length;
}
+ (BOOL)HEXContainsAlphaComponent:(NSUInteger)hex
{
return [self lengthOfHEXInteger:hex] == 8;
}
+ (NSColor*)colorFromHEXInteger:(NSInteger)hex
{
CGFloat alpha = 1.f;
if([self HEXContainsAlphaComponent:hex] == YES) {
alpha = (hex & 0xFF) / 255.f;
hex = hex >> 8;
}
return [self computeColorSpace:[NSColor colorWithDeviceRed:((hex >> 16) & 0xFF) / 255.f
green:((hex >> 8) & 0xFF) / 255.f
blue:(hex & 0xFF) / 255.f
alpha:alpha]];
}
+ (unsigned long)HEXFromArbitraryHexString:(NSString*)aString
{
const char* hexString = [aString cStringUsingEncoding:NSUTF8StringEncoding];
return strtoul(hexString, NULL, 16);
}
+ (NSColor*)colorFromHEXString:(NSString*)string
{
return [self colorFromHEXString:string
containsAlphaComponent:nil];
}
+ (NSColor*)colorFromHEXString:(NSString*)string
containsAlphaComponent:(BOOL*)containsAlphaComponent
{
// absolutely no string
if(string == nil) {
return nil;
}
char* str = (char*)string.UTF8String;
size_t length = strlen(str);
if(length == 0 || IJSVGCharBufferIsHEX(str) == NO) {
return nil;
}
// remove the hash from the front of the string
if(str[0] == '#') {
length--;
str++;
}
unsigned long hex;
// we need to work out if its shorthand
// if it is, the length needs to be length*2
if(length == 3 || length == 4) {
char* chars = NULL;
chars = (char*)calloc(sizeof(char),length*2+1);
for(int i = 0; i < length; i++) {
chars[i*2] = chars[i*2+1] = str[i];
}
hex = strtoul(chars, NULL, 16);
(void)free(chars), chars = NULL;
} else {
hex = strtoul(str, NULL, 16);
}
// now convert rest to hex
if(containsAlphaComponent != nil) {
*containsAlphaComponent = [self HEXContainsAlphaComponent:hex];
}
return [self colorFromHEXInteger:hex];
}
@end
@@ -0,0 +1,32 @@
//
// IJSVGColorType.h
// IJSVG
//
// Created by Curtis Hard on 20/04/2021.
// Copyright © 2021 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
typedef NS_OPTIONS(NSInteger, IJSVGColorUsageTraits) {
IJSVGColorUsageTraitNone = 0,
IJSVGColorUsageTraitUnknown = 1 << 0,
IJSVGColorUsageTraitFill = 1 << 1,
IJSVGColorUsageTraitStroke = 1 << 2,
IJSVGColorUsageTraitGradientStop = 1 << 3,
IJSVGColorUsageTraitAll = IJSVGColorUsageTraitFill | IJSVGColorUsageTraitGradientStop |
IJSVGColorUsageTraitStroke
};
@interface IJSVGTraitedColor : NSObject {
}
@property (nonatomic, strong) NSColor* color;
@property (nonatomic, assign) IJSVGColorUsageTraits traits;
+ (IJSVGTraitedColor*)typeWithColor:(NSColor*)color
traits:(IJSVGColorUsageTraits)mask;
@end
@@ -0,0 +1,36 @@
//
// IJSVGColorType.h
// IJSVG
//
// Created by Curtis Hard on 20/04/2021.
// Copyright © 2021 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
typedef NS_OPTIONS(NSInteger, IJSVGColorUsageTraits) {
IJSVGColorUsageTraitNone = 0,
IJSVGColorUsageTraitUnknown = 1 << 0,
IJSVGColorUsageTraitFill = 1 << 1,
IJSVGColorUsageTraitStroke = 1 << 2,
IJSVGColorUsageTraitGradientStop = 1 << 3,
IJSVGColorUsageTraitAll = IJSVGColorUsageTraitFill | IJSVGColorUsageTraitGradientStop |
IJSVGColorUsageTraitStroke
};
@interface IJSVGTraitedColor : NSObject {
}
@property (nonatomic, strong) NSColor* color;
@property (nonatomic, assign) IJSVGColorUsageTraits traits;
+ (IJSVGTraitedColor*)colorWithColor:(NSColor*)color
traits:(IJSVGColorUsageTraits)mask;
- (void)addTraits:(IJSVGColorUsageTraits)traits;
- (void)removeTraits:(IJSVGColorUsageTraits)traits;
- (BOOL)matchesTraits:(IJSVGColorUsageTraits)traits;
@end
@@ -0,0 +1,64 @@
//
// IJSVGColorType.m
// IJSVG
//
// Created by Curtis Hard on 20/04/2021.
// Copyright © 2021 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGTraitedColor.h>
#import <IJSVG/IJSVGColor.h>
@implementation IJSVGTraitedColor
+ (IJSVGTraitedColor*)colorWithColor:(NSColor*)color
traits:(IJSVGColorUsageTraits)traits
{
IJSVGTraitedColor* type = [[self alloc] init];
type.color = color;
type.traits = traits;
return type;
}
- (instancetype)init
{
if((self = [super init]) != nil) {
_traits = IJSVGColorUsageTraitNone;
}
return self;
}
- (BOOL)isEqual:(id)object
{
if([object isKindOfClass:IJSVGTraitedColor.class] == NO) {
return NO;
}
return [self.color isEqual:((IJSVGTraitedColor*)object).color];
}
- (void)setColor:(NSColor *)color
{
_color = [IJSVGColor computeColorSpace:color];
}
- (NSUInteger)hash
{
return self.color.hash;
}
- (void)addTraits:(IJSVGColorUsageTraits)traits
{
_traits |= traits;
}
- (void)removeTraits:(IJSVGColorUsageTraits)traits
{
_traits = _traits & ~traits;
}
- (BOOL)matchesTraits:(IJSVGColorUsageTraits)traits
{
return (self.traits & traits) == traits;
}
@end
@@ -0,0 +1,43 @@
//
// IJSVGColorList.h
// IconJar
//
// Created by Curtis Hard on 07/07/2019.
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGColor.h>
#import <IJSVG/IJSVGTraitedColor.h>
#import <Foundation/Foundation.h>
@interface IJSVGReplacementColor : IJSVGTraitedColor
@property (nonatomic, strong) NSColor* replacementColor;
@end
@interface IJSVGTraitedColorStorage : NSObject {
@private
NSMutableArray<IJSVGReplacementColor*>* _replacementColors;
NSMutableSet<IJSVGTraitedColor*>* _colors;
IJSVGColorUsageTraits _traits;
IJSVGColorUsageTraits _replacementTraits;
}
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) NSSet<IJSVGTraitedColor*>* colors;
@property (nonatomic, readonly) NSUInteger replacedColorCount;
- (void)addTraits:(IJSVGColorUsageTraits)traits;
- (void)addColor:(IJSVGTraitedColor*)color;
- (void)replaceColor:(NSColor*)replaceColor
withColor:(NSColor*)withColor
traits:(IJSVGColorUsageTraits)traits;
- (void)unionColorStorage:(IJSVGTraitedColorStorage*)colorList;
- (NSColor*)colorForColor:(NSColor*)color
matchingTraits:(IJSVGColorUsageTraits)traits;
- (BOOL)matchesReplacementTraits:(IJSVGColorUsageTraits)traits;
- (BOOL)matchesTraits:(IJSVGColorUsageTraits)traits;
@end
@@ -0,0 +1,146 @@
//
// IJSVGColorList.m
// IconJar
//
// Created by Curtis Hard on 07/07/2019.
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGTraitedColorStorage.h>
@implementation IJSVGReplacementColor
- (void)setReplacementColor:(NSColor*)replacementColor
{
_replacementColor = [IJSVGColor computeColorSpace:replacementColor];
}
@end
@implementation IJSVGTraitedColorStorage
- (instancetype)init
{
if((self = [super init]) != nil) {
_replacementColors = [[NSMutableArray alloc] init];
_colors = [[NSMutableSet alloc] init];
_replacementTraits = IJSVGColorUsageTraitNone;
_traits = IJSVGColorUsageTraitNone;
}
return self;
}
- (void)addColor:(IJSVGTraitedColor*)color
{
if([_colors containsObject:color] == YES) {
void (^handler)(IJSVGTraitedColor * _Nonnull obj, BOOL * _Nonnull stop) =
^(IJSVGTraitedColor * _Nonnull obj, BOOL * _Nonnull stop) {
if([obj isEqual:color] == YES) {
[obj addTraits:color.traits];
*stop = YES;
}
};
[_colors enumerateObjectsUsingBlock:handler];
return;
}
_traits |= color.traits;
[_colors addObject:color];
}
- (void)addTraits:(IJSVGColorUsageTraits)traits
{
for(IJSVGTraitedColor* color in _colors) {
[color addTraits:traits];
}
}
- (void)replaceColor:(NSColor*)replaceColor
withColor:(NSColor*)withColor
traits:(IJSVGColorUsageTraits)traits
{
replaceColor = [IJSVGColor computeColorSpace:replaceColor];
withColor = [IJSVGColor computeColorSpace:withColor];
IJSVGReplacementColor* repColor = [self replacementColorForColor:replaceColor
withColor:withColor];
if(repColor == nil) {
repColor = [[IJSVGReplacementColor alloc] init];
repColor.color = replaceColor;
repColor.replacementColor = withColor;
[_replacementColors addObject:repColor];
}
_replacementTraits |= traits;
[repColor addTraits:traits];
}
- (IJSVGReplacementColor*)replacementColorForColor:(NSColor*)replacementColor
withColor:(NSColor*)withColor
{
for(IJSVGReplacementColor* color in _replacementColors) {
if([color.color isEqual:color] == YES &&
[color.replacementColor isEqual:withColor] == YES) {
return color;
}
}
return nil;
}
- (IJSVGReplacementColor*)replacementColorForColor:(NSColor*)color
matchingTraits:(IJSVGColorUsageTraits)traits
{
for(IJSVGReplacementColor* replacementColor in _replacementColors) {
if([replacementColor.color isEqual:color] &&
[replacementColor matchesTraits:traits] == YES) {
return replacementColor;
}
}
return nil;
}
- (NSColor*)colorForColor:(NSColor*)color
matchingTraits:(IJSVGColorUsageTraits)traits
{
if([self matchesReplacementTraits:traits] == NO) {
return nil;
}
color = [IJSVGColor computeColorSpace:color];
IJSVGReplacementColor* repColor = nil;
if((repColor = [self replacementColorForColor:color
matchingTraits:traits]) != nil) {
return repColor.replacementColor;
}
return nil;
}
- (void)unionColorStorage:(IJSVGTraitedColorStorage*)colorList
{
for(IJSVGTraitedColor* traitedColor in colorList.colors) {
[self addColor:traitedColor];
}
}
- (BOOL)matchesTraits:(IJSVGColorUsageTraits)traits
{
return (_traits & traits) == traits;
}
- (BOOL)matchesReplacementTraits:(IJSVGColorUsageTraits)traits
{
return (_replacementTraits & traits) == traits;
}
- (NSUInteger)replacedColorCount
{
return _replacementColors.count;
}
- (NSUInteger)count
{
return _colors.count;
}
- (NSSet<IJSVGTraitedColor*>*)colors
{
return _colors;
}
@end
@@ -0,0 +1,73 @@
//
// IJSVGCommand.h
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandParser.h>
#import <IJSVG/IJSVGPath.h>
#import <Foundation/Foundation.h>
static const NSInteger IJSVGCustomVariableParameterCount = NSNotFound;
typedef NS_ENUM(NSInteger, IJSVGCommandType) {
kIJSVGCommandTypeAbsolute,
kIJSVGCommandTypeRelative
};
@interface IJSVGCommand : NSObject <NSCopying> {
@private
NSInteger _currentIndex;
}
@property (nonatomic, assign) char command;
@property (nonatomic, assign) CGFloat* parameters;
@property (nonatomic, assign) NSInteger parameterCount;
@property (nonatomic, assign) IJSVGCommandType type;
@property (nonatomic, strong) NSArray<IJSVGCommand*>* subCommands;
@property (nonatomic, assign) IJSVGCommand* previousCommand;
@property (nonatomic, assign) BOOL isSubCommand;
+ (Class)commandClassForCommandChar:(char)aChar;
+ (NSInteger)requiredParameterCount;
+ (NSPoint)readCoordinatePair:(CGFloat*)pairs
index:(NSInteger)index;
+ (IJSVGPathDataSequence*)pathDataSequence;
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path;
+ (void)parseParams:(CGFloat*)params
paramCount:(NSInteger)paramCount
intoArray:(NSMutableArray<IJSVGCommand*>*)commands
parentCommand:(IJSVGCommand*)parentCommand;
+ (NSArray<IJSVGCommand*>*)commandsForDataCharacters:(const char*)buffer;
+ (NSArray<IJSVGCommand*>*)commandsForDataCharacters:(const char*)buffer
dataStream:(IJSVGPathDataStream*)dataStream;
+ (CGMutablePathRef)newPathForCommandsArray:(NSArray<IJSVGCommand*>*)commands;
+ (NSArray<IJSVGCommand*>*)convertCommands:(NSArray<IJSVGCommand*>*)commands
toUnits:(IJSVGUnitType)unitType
bounds:(CGRect)bounds;
- (id)initWithCommandStringBuffer:(const char*)str
dataStream:(IJSVGPathDataStream*)dataStream;
- (IJSVGCommand*)subcommandWithParameters:(CGFloat*)subParams
paramCount:(NSInteger)paramCount
previousCommand:(IJSVGCommand*)command;
- (void)convertToUnits:(IJSVGUnitType)units
boundingBox:(CGRect)boundingBox;
- (IJSVGCommand*)commandByConvertingToUnits:(IJSVGUnitType)unitType
boundingBox:(CGRect)boundingBox;
- (CGFloat)readFloat;
- (NSPoint)readPoint;
- (BOOL)readBOOL;
- (void)resetRead;
@end
@@ -0,0 +1,365 @@
//
// IJSVGCommand.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <IJSVG/IJSVGUtils.h>
#import <IJSVG/IJSVGCommandClose.h>
#import <IJSVG/IJSVGCommandCurve.h>
#import <IJSVG/IJSVGCommandEllipticalArc.h>
#import <IJSVG/IJSVGCommandHorizontalLine.h>
#import <IJSVG/IJSVGCommandLineTo.h>
#import <IJSVG/IJSVGCommandMove.h>
#import <IJSVG/IJSVGCommandQuadraticCurve.h>
#import <IJSVG/IJSVGCommandSmoothCurve.h>
#import <IJSVG/IJSVGCommandSmoothQuadraticCurve.h>
#import <IJSVG/IJSVGCommandVerticalLine.h>
#import <IJSVG/IJSVGThreadManager.h>
@implementation IJSVGCommand
+ (BOOL)requiresCustomParameterParsing
{
return NO;
}
+ (NSInteger)requiredParameterCount
{
return 1;
}
+ (IJSVGPathDataSequence*)pathDataSequence
{
return NULL;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
}
+ (void)parseParams:(CGFloat*)params
paramCount:(NSInteger)paramCount
intoArray:(NSMutableArray<IJSVGCommand*>*)commands
parentCommand:(IJSVGCommand*)parentCommand
{
}
+ (NSPoint)readCoordinatePair:(CGFloat*)pairs
index:(NSInteger)index
{
return NSMakePoint(pairs[index * 2], pairs[index * 2 + 1]);
}
+ (void)load
{
// register here...
}
+ (Class)commandClassForCommandChar:(char)aChar
{
aChar = tolower(aChar);
switch (aChar) {
case 'a':
return IJSVGCommandEllipticalArc.class;
case 'c':
return IJSVGCommandCurve.class;
case 'h':
return IJSVGCommandHorizontalLine.class;
case 'l':
return IJSVGCommandLineTo.class;
case 'm':
return IJSVGCommandMove.class;
case 'q':
return IJSVGCommandQuadraticCurve.class;
case 's':
return IJSVGCommandSmoothCurve.class;
case 't':
return IJSVGCommandSmoothQuadraticCurve.class;
case 'v':
return IJSVGCommandVerticalLine.class;
case 'z':
return IJSVGCommandClose.class;
}
return nil;
}
+ (CGMutablePathRef)newPathForCommandsArray:(NSArray<IJSVGCommand*>*)commands
{
CGMutablePathRef path = CGPathCreateMutable();
IJSVGCommand* preCommand = nil;
for(IJSVGCommand* command in commands) {
for (IJSVGCommand* subCommand in command.subCommands) {
[command.class runWithParams:subCommand.parameters
paramCount:subCommand.parameterCount
command:subCommand
previousCommand:preCommand
type:subCommand.type
path:path];
preCommand = subCommand;
}
}
return path;
}
+ (NSArray<IJSVGCommand*>*)commandsForDataCharacters:(const char*)buffer
{
IJSVGThreadManager* manager = IJSVGThreadManager.currentManager;
NSArray<IJSVGCommand*>* commands = [self commandsForDataCharacters:buffer
dataStream:manager.pathDataStream];
return commands;
}
+ (NSArray<IJSVGCommand*>*)commandsForDataCharacters:(const char*)unsafeBuffer
dataStream:(IJSVGPathDataStream*)dataStream
{
NSMutableArray<IJSVGCommand*>* commands = [[NSMutableArray alloc] init];
NSUInteger len = strlen(unsafeBuffer);
NSUInteger lastIndex = len - 1;
// we need to trim the buffer first as any given string given to us
// could be unsafe and have random whitespace at the extremes.
size_t bufferLength = sizeof(char)*(len + 1);
char* buffer = (char*)malloc(bufferLength);
memcpy(buffer, unsafeBuffer, bufferLength);
IJSVGTrimCharBuffer(buffer);
// make sure we plus 1 for the null byte
char* charBuffer = (char*)malloc(bufferLength);
NSInteger start = 0;
for (NSInteger i = start; i < len; i++) {
char nextChar = buffer[i + 1];
BOOL atEnd = i == lastIndex;
BOOL isStartCommand = IJSVGIsLegalCommandCharacter(nextChar);
if(isStartCommand == YES || atEnd == YES) {
// copy memory from current buffer
NSInteger index = ((i + 1) - start);
memcpy(&charBuffer[0], &buffer[start], sizeof(char)*index);
charBuffer[index] = '\0';
// create the command from the substring
unsigned long length = index + 1;
size_t mlength = sizeof(char)*length;
char* commandString = (char*)malloc(mlength);
memcpy(commandString, &charBuffer[0], mlength);
// reset start position
start = (i + 1);
// previous command is actual subcommand
Class commandClass = [IJSVGCommand commandClassForCommandChar:commandString[0]];
if(commandClass != nil) {
IJSVGCommand* command = nil;
command = (IJSVGCommand*)[[commandClass alloc] initWithCommandStringBuffer:commandString
dataStream:dataStream];
[commands addObject:command];
}
#if DEBUG
else {
// if we get here then the command buffer is invalid, nothing we can
// do about it, just invalid data.
NSLog(@"\"%s\" is not a valid command instruction set", commandString);
}
#endif
// free the memory as at this point, we are done with it
(void)free(commandString), commandString = NULL;
}
}
(void)free(charBuffer), charBuffer = NULL;
(void)free(buffer), buffer = NULL;
return commands;
}
+ (NSArray<IJSVGCommand*>*)convertCommands:(NSArray<IJSVGCommand*>*)commands
toUnits:(IJSVGUnitType)unitType
bounds:(CGRect)bounds
{
NSMutableArray<IJSVGCommand*>* newCommands = nil;
newCommands = [[NSMutableArray alloc] initWithCapacity:commands.count];
for(IJSVGCommand* command in commands) {
IJSVGCommand* nCommand = [command commandByConvertingToUnits:unitType
boundingBox:bounds];
[newCommands addObject:nCommand];
}
return newCommands;
}
- (void)dealloc
{
if(_parameters) {
(void)(free(_parameters)), _parameters = nil;
}
}
- (id)initWithCommandStringBuffer:(const char*)str
dataStream:(IJSVGPathDataStream*)dataStream
{
if((self = [super init]) != nil) {
// work out the basics
_currentIndex = 0;
_command = str[0];
_type = [IJSVGUtils typeForCommandChar:_command];
NSInteger sets = 0;
NSInteger paramCount = [self.class requiredParameterCount];
IJSVGPathDataSequence* sequence = [self.class pathDataSequence];
_parameters = IJSVGParsePathDataStreamSequence(str, strlen(str),
dataStream, sequence, paramCount, &sets);
if(sets <= 1) {
CGFloat* subParams = [self parametersFromIndexOffset:0];
IJSVGCommand* command = [self subcommandWithParameters:subParams
paramCount:paramCount
previousCommand:nil];
_subCommands = @[ command ];
} else {
NSMutableArray<IJSVGCommand*>* subCommandArray = nil;
subCommandArray = [[NSMutableArray alloc] initWithCapacity:sets];
// interate over the sets
IJSVGCommand* lastCommand = nil;
for (NSInteger i = 0; i < sets; i++) {
// memory for this will be handled by the created subcommand
CGFloat* subParams = [self parametersFromIndexOffset:i];
// generate the subcommand
IJSVGCommand* command = [self subcommandWithParameters:subParams
paramCount:paramCount
previousCommand:lastCommand];
// make sure we assign the last command or hell breaks
// lose and the firey demons will run wild, namely, commands will break
// if they are multiples of a set
lastCommand = command;
[subCommandArray addObject:command];
}
// store the retained value
_subCommands = subCommandArray.copy;
}
}
return self;
}
- (void)setSubCommands:(NSArray<IJSVGCommand*>*)subCommands
{
_subCommands = subCommands;
}
- (id)copyWithZone:(NSZone *)zone
{
IJSVGCommand* command = [[self.class alloc] init];
command.type = self.type;
command.command = self.command;
command.isSubCommand = self.isSubCommand;
command.parameterCount = self.parameterCount;
size_t memsize = sizeof(CGFloat)*self.parameterCount;
command.parameters = (CGFloat*)malloc(memsize);
memcpy(command.parameters, self.parameters, memsize);
IJSVGCommand* lastCommand = nil;
NSMutableArray<IJSVGCommand*>* subcommands = nil;
subcommands = [[NSMutableArray alloc] initWithCapacity:self.subCommands.count];
for(IJSVGCommand* subcommand in self.subCommands) {
IJSVGCommand* subCopy = subcommand.copy;
subCopy.previousCommand = lastCommand;
subCopy.isSubCommand = lastCommand != nil;
[subcommands addObject:subCopy];
}
command.subCommands = subcommands;
return command;
}
- (CGFloat*)parametersFromIndexOffset:(NSInteger)index
{
CGFloat* subParams = 0;
NSInteger req = [self.class requiredParameterCount];
if(req != 0) {
subParams = (CGFloat*)malloc(req * sizeof(CGFloat));
memcpy(subParams, &self.parameters[index * req], sizeof(CGFloat) * req);
}
return subParams;
}
- (IJSVGCommand*)subcommandWithParameters:(CGFloat*)subParams
paramCount:(NSInteger)paramCount
previousCommand:(IJSVGCommand*)aPreviousCommand
{
// create a subcommand per set
IJSVGCommand* c = [[self.class alloc] init];
c.parameterCount = paramCount;
c.parameters = subParams;
c.type = self.type;
c.command = self.command;
c.previousCommand = aPreviousCommand;
c.isSubCommand = aPreviousCommand != nil;
return c;
}
- (CGFloat)readFloat
{
CGFloat f = _parameters[_currentIndex];
_currentIndex++;
return f;
}
- (NSPoint)readPoint
{
CGFloat x = _parameters[_currentIndex];
CGFloat y = _parameters[_currentIndex + 1];
_currentIndex += 2;
return NSMakePoint(x, y);
}
- (BOOL)readBOOL
{
return [self readFloat] == 1;
}
- (void)resetRead
{
_currentIndex = 0;
}
- (void)convertToUnits:(IJSVGUnitType)units
boundingBox:(CGRect)boundingBox
{
for(IJSVGCommand* command in _subCommands) {
[command convertToUnits:units
boundingBox:boundingBox];
}
}
- (NSString *)description
{
NSMutableString* str = [[NSMutableString alloc] init];
[str appendFormat:@"%c ",_command];
NSMutableArray* args = [[NSMutableArray alloc] initWithCapacity:_parameterCount];
for(int i = 0; i < _parameterCount; i++) {
[args addObject:[NSString stringWithFormat:@"%f",_parameters[i]]];
}
[str appendString:[args componentsJoinedByString:@", "]];
return str;
}
- (IJSVGCommand*)commandByConvertingToUnits:(IJSVGUnitType)unitType
boundingBox:(CGRect)boundingBox
{
IJSVGCommand* copy = self.copy;
[copy convertToUnits:unitType
boundingBox:boundingBox];
return copy;
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandClose : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandClose : IJSVGCommand
@end
@@ -6,7 +6,7 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import "IJSVGCommandClose.h"
#import <IJSVG/IJSVGCommandClose.h>
@implementation IJSVGCommandClose
@@ -15,14 +15,14 @@
return 0;
}
+ (void)runWithParams:(CGFloat *)params
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand *)currentCommand
previousCommand:(IJSVGCommand *)command
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(IJSVGPath *)path
path:(CGMutablePathRef)path
{
[path close];
CGPathCloseSubpath(path);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandCurve : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandCurve : IJSVGCommand
@end
@@ -0,0 +1,38 @@
//
// IJSVGCommandCurve.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandCurve.h>
@implementation IJSVGCommandCurve
+ (NSInteger)requiredParameterCount
{
return 6;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddCurveToPoint(path, NULL, params[0], params[1],
params[2], params[3],
params[4], params[5]);
return;
}
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPathAddCurveToPoint(path, NULL,
currentPoint.x + params[0], currentPoint.y + params[1],
currentPoint.x + params[2], currentPoint.y + params[3],
currentPoint.x + params[4], currentPoint.y + params[5]);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandArc : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandEllipticalArc : IJSVGCommand
@end
@@ -0,0 +1,139 @@
//
// IJSVGCommandArc.m
// IJSVGExample
//
// Created by Curtis Hard on 03/09/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandEllipticalArc.h>
#import <IJSVG/IJSVGUtils.h>
@implementation IJSVGCommandEllipticalArc
static IJSVGPathDataSequence* _sequence;
+ (NSInteger)requiredParameterCount
{
return 7;
}
+ (IJSVGPathDataSequence*)pathDataSequence
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sequence = (IJSVGPathDataSequence*)malloc(sizeof(IJSVGPathDataSequence) * 7);
_sequence[0] = kIJSVGPathDataSequenceTypeFloat;
_sequence[1] = kIJSVGPathDataSequenceTypeFloat;
_sequence[2] = kIJSVGPathDataSequenceTypeFloat;
_sequence[3] = kIJSVGPathDataSequenceTypeFlag;
_sequence[4] = kIJSVGPathDataSequenceTypeFlag;
_sequence[5] = kIJSVGPathDataSequenceTypeFloat;
_sequence[6] = kIJSVGPathDataSequenceTypeFloat;
});
return _sequence;
}
// modified from https://github.com/SVGKit/SVGKit/blob/880c94a5b77b6f22beb491a7a7e02ace220c32af/Source/Parsers/SVGKPointsAndPathsParser.m
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
CGPoint radii = CGPointZero;
CGPoint arcEndPoint = CGPointZero;
CGPoint pathCurrentPoint = CGPathGetCurrentPoint(path);
CGFloat xAxisRotation = 0.f;
BOOL largeArcFlag = NO;
BOOL sweepFlag = NO;
radii = currentCommand.readPoint;
xAxisRotation = currentCommand.readFloat;
largeArcFlag = currentCommand.readBOOL;
sweepFlag = currentCommand.readBOOL;
arcEndPoint = currentCommand.readPoint;
CGFloat rx = fabs(radii.x);
CGFloat ry = fabs(radii.y);
xAxisRotation *= M_PI / 180.f;
xAxisRotation = fmod(xAxisRotation, 2.f * M_PI);
if(type == kIJSVGCommandTypeRelative) {
arcEndPoint.x += pathCurrentPoint.x;
arcEndPoint.y += pathCurrentPoint.y;
}
CGFloat x1 = pathCurrentPoint.x;
CGFloat y1 = pathCurrentPoint.y;
CGFloat x2 = arcEndPoint.x;
CGFloat y2 = arcEndPoint.y;
if(rx == 0.f || ry == 0.f) {
CGPathAddLineToPoint(path, NULL, x2, y2);
return;
}
CGFloat cosPhi = cos(xAxisRotation);
CGFloat sinPhi = sin(xAxisRotation);
CGFloat x1p = cosPhi * (x1 - x2) / 2.f + sinPhi * (y1 - y2) / 2.f;
CGFloat y1p = -sinPhi * (x1 - x2) / 2.f + cosPhi * (y1 - y2) / 2.f;
CGFloat rx_2 = rx * rx;
CGFloat ry_2 = ry * ry;
CGFloat xp_2 = x1p * x1p;
CGFloat yp_2 = y1p * y1p;
CGFloat delta = xp_2 / rx_2 + yp_2 / ry_2;
if(delta > 1.f) {
rx *= sqrt(delta);
ry *= sqrt(delta);
rx_2 = rx * rx;
ry_2 = ry * ry;
}
CGFloat sign = (largeArcFlag == sweepFlag) ? -1.f : 1.f;
CGFloat numerator = MAX(0.f, rx_2 * ry_2 - rx_2 * yp_2 - ry_2 * xp_2);
CGFloat denom = rx_2 * yp_2 + ry_2 * xp_2;
CGFloat lhs = denom == 0.f ? 0.f : sign * sqrt(numerator / denom);
CGFloat cxp = lhs * (rx * y1p) / ry;
CGFloat cyp = lhs * -((ry * x1p) / rx);
CGFloat cx = cosPhi * cxp + -sinPhi * cyp + (x1 + x2) / 2.f;
CGFloat cy = cxp * sinPhi + cyp * cosPhi + (y1 + y2) / 2.f;
CGAffineTransform transform = CGAffineTransformMakeScale(1.f / rx, 1.f / ry);
transform = CGAffineTransformRotate(transform, -xAxisRotation);
transform = CGAffineTransformTranslate(transform, -cx, -cy);
CGPoint arcPt1 = CGPointApplyAffineTransform(CGPointMake(x1, y1), transform);
CGPoint arcPt2 = CGPointApplyAffineTransform(CGPointMake(x2, y2), transform);
CGFloat startAngle = atan2(arcPt1.y, arcPt1.x);
CGFloat endAngle = atan2(arcPt2.y, arcPt2.x);
CGFloat angleDelta = endAngle - startAngle;;
if(sweepFlag == YES) {
if(angleDelta < 0.f) {
angleDelta += 2.f * M_PI;
}
} else if(angleDelta > 0.f) {
angleDelta = angleDelta - 2.f * M_PI;
}
transform = CGAffineTransformMakeTranslation(cx, cy);
transform = CGAffineTransformRotate(transform, xAxisRotation);
transform = CGAffineTransformScale(transform, rx, ry);
CGPathAddRelativeArc(path, &transform, 0.f, 0.f, 1.f,
startAngle, angleDelta);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandHorizontalLine : NSObject <IJSVGCommandProtocol>
@interface IJSVGCommandHorizontalLine : IJSVGCommand
@end
@@ -0,0 +1,34 @@
//
// IJSVGCommandHorizontalLine.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandHorizontalLine.h>
@implementation IJSVGCommandHorizontalLine
+ (NSInteger)requiredParameterCount
{
return 1;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddLineToPoint(path, NULL, params[0], CGPathGetCurrentPoint(path).y);
return;
}
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPathAddLineToPoint(path, NULL, currentPoint.x + params[0],
currentPoint.y);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandLineTo : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandLineTo : IJSVGCommand
@end
@@ -0,0 +1,45 @@
//
// IJSVGCommandLineTo.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandLineTo.h>
@implementation IJSVGCommandLineTo
+ (NSInteger)requiredParameterCount
{
return 2;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddLineToPoint(path, NULL, params[0], params[1]);
return;
}
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPathAddLineToPoint(path, NULL, currentPoint.x + params[0],
currentPoint.y + params[1]);
}
- (void)convertToUnits:(IJSVGUnitType)units
boundingBox:(CGRect)boundingBox
{
if(units == IJSVGUnitObjectBoundingBox) {
self.parameters[0] = [[IJSVGUnitLength unitWithPercentageFloat:self.parameters[0]] computeValue:boundingBox.size.width];
self.parameters[1] = [[IJSVGUnitLength unitWithPercentageFloat:self.parameters[1]] computeValue:boundingBox.size.height];
}
[super convertToUnits:units
boundingBox:boundingBox];
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandMove : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandMove : IJSVGCommand
@end
@@ -0,0 +1,65 @@
//
// IJSVGCommandMove.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandLineTo.h>
#import <IJSVG/IJSVGCommandMove.h>
@implementation IJSVGCommandMove
+ (NSInteger)requiredParameterCount
{
return 2;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
// move to's allow more then one move to, but if there are more then one,
// we need to run the line to instead...who knew!
if(command.class == self.class && currentCommand.isSubCommand == YES) {
[IJSVGCommandLineTo runWithParams:params
paramCount:count
command:currentCommand
previousCommand:command
type:type
path:path];
return;
}
// actual move to command - do a moveToPoint only
// if the type is absolute, or its possible the type is
// relative but there is no previous command which means
// there is no current point. Asking for current point on an empty
// path will result in an exception being thrown
if(type == kIJSVGCommandTypeAbsolute || command == nil) {
CGPathMoveToPoint(path, NULL,
params[0], params[1]);
return;
}
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPathMoveToPoint(path, NULL,
currentPoint.x + params[0],
currentPoint.y + params[1]);
}
- (void)convertToUnits:(IJSVGUnitType)units
boundingBox:(CGRect)boundingBox
{
if(units == IJSVGUnitObjectBoundingBox) {
self.parameters[0] = [[IJSVGUnitLength unitWithPercentageFloat:self.parameters[0]] computeValue:boundingBox.size.width];
self.parameters[1] = [[IJSVGUnitLength unitWithPercentageFloat:self.parameters[1]] computeValue:boundingBox.size.height];
}
[super convertToUnits:units
boundingBox:boundingBox];
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandQuadraticCurve : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandQuadraticCurve : IJSVGCommand
@end
@@ -0,0 +1,37 @@
//
// IJSVGCommandQuadraticCurve.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandQuadraticCurve.h>
#import <IJSVG/IJSVGUtils.h>
@implementation IJSVGCommandQuadraticCurve
+ (NSInteger)requiredParameterCount
{
return 4;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddQuadCurveToPoint(path, NULL, params[0], params[1],
params[2], params[3]);
return;
}
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPathAddQuadCurveToPoint(path, NULL,
currentPoint.x + params[0], currentPoint.y + params[1],
currentPoint.x + params[2], currentPoint.y + params[3]);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandSmoothCurve : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandSmoothCurve : IJSVGCommand
@end
@@ -0,0 +1,64 @@
//
// IJSVGCommandSmoothCurve.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandCurve.h>
#import <IJSVG/IJSVGCommandSmoothCurve.h>
#import <IJSVG/IJSVGUtils.h>
@implementation IJSVGCommandSmoothCurve
+ (NSInteger)requiredParameterCount
{
return 4;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPoint firstControl = CGPointMake(currentPoint.x, currentPoint.y);
if(command != nil) {
if(command.class == [IJSVGCommandCurve class] || command.class == self.class) {
if(command.class == [IJSVGCommandCurve class]) {
if(command.type == kIJSVGCommandTypeAbsolute) {
firstControl = CGPointMake(-1 * command.parameters[2] + 2 * currentPoint.x,
-1 * command.parameters[3] + 2 * currentPoint.y);
} else {
NSPoint oldPoint = CGPointMake(currentPoint.x - command.parameters[4],
currentPoint.y - command.parameters[5]);
firstControl = CGPointMake(-1 * (command.parameters[2] + oldPoint.x) + 2 * currentPoint.x,
-1 * (command.parameters[3] + oldPoint.y) + 2 * currentPoint.y);
}
} else {
if(command.type == kIJSVGCommandTypeAbsolute) {
firstControl = CGPointMake(-1 * command.parameters[0] + 2 * currentPoint.x,
-1 * command.parameters[1] + 2 * currentPoint.y);
} else {
NSPoint oldPoint = CGPointMake(currentPoint.x - command.parameters[2],
currentPoint.y - command.parameters[3]);
firstControl = CGPointMake(-1 * (command.parameters[0] + oldPoint.x) + 2 * currentPoint.x,
-1 * (command.parameters[1] + oldPoint.y) + 2 * currentPoint.y);
}
}
}
}
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddCurveToPoint(path, NULL, firstControl.x, firstControl.y,
params[0], params[1], params[2], params[3]);
return;
}
CGPathAddCurveToPoint(path, NULL, firstControl.x, firstControl.y,
currentPoint.x + params[0], currentPoint.y + params[1],
currentPoint.x + params[2], currentPoint.y + params[3]);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandCommandSmoothQuadraticCurve : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandSmoothQuadraticCurve : IJSVGCommand
@end
@@ -0,0 +1,58 @@
//
// IJSVGCommandCommandQuadraticCurve.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandQuadraticCurve.h>
#import <IJSVG/IJSVGCommandSmoothQuadraticCurve.h>
#import <IJSVG/IJSVGUtils.h>
@implementation IJSVGCommandSmoothQuadraticCurve
+ (NSInteger)requiredParameterCount
{
return 2;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
CGPoint lastControlPoint = IJSVGPathGetLastQuadraticCommandPoint(path);
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPoint commandPoint = CGPointMake(currentPoint.x, currentPoint.y);
if(command != nil) {
if(command.class == IJSVGCommandQuadraticCurve.class) {
// quadratic curve
if(command.type == kIJSVGCommandTypeAbsolute) {
commandPoint = NSMakePoint(-1 * command.parameters[0] + 2 * currentPoint.x,
-1 * command.parameters[1] + 2 * currentPoint.y);
} else {
CGPoint oldPoint = CGPointMake(currentPoint.x - command.parameters[2],
currentPoint.y - command.parameters[3]);
commandPoint = CGPointMake(-1 * (command.parameters[0] + oldPoint.x) + 2 * (currentPoint.x),
-1 * (command.parameters[1] + oldPoint.y) + 2 * currentPoint.y);
}
} else if(command.class == self.class) {
// smooth quadratic curve
commandPoint = CGPointMake(-1 * (lastControlPoint.x) + 2 * (currentPoint.x),
-1 * (lastControlPoint.y) + 2 * currentPoint.y);
}
}
// path.lastControlPoint = commandPoint;
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddQuadCurveToPoint(path, NULL, commandPoint.x, commandPoint.y,
params[0], params[1]);
return;
}
CGPathAddQuadCurveToPoint(path, NULL, commandPoint.x, commandPoint.y,
currentPoint.x + params[0], currentPoint.y + params[1]);
}
@end
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommand.h>
#import <Foundation/Foundation.h>
#import "IJSVGCommand.h"
@interface IJSVGCommandVerticalLine : IJSVGCommand <IJSVGCommandProtocol>
@interface IJSVGCommandVerticalLine : IJSVGCommand
@end
@@ -0,0 +1,33 @@
//
// IJSVGCommandVerticalLine.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGCommandVerticalLine.h>
@implementation IJSVGCommandVerticalLine
+ (NSInteger)requiredParameterCount
{
return 1;
}
+ (void)runWithParams:(CGFloat*)params
paramCount:(NSInteger)count
command:(IJSVGCommand*)currentCommand
previousCommand:(IJSVGCommand*)command
type:(IJSVGCommandType)type
path:(CGMutablePathRef)path
{
if(type == kIJSVGCommandTypeAbsolute) {
CGPathAddLineToPoint(path, NULL, CGPathGetCurrentPoint(path).x, params[0]);
return;
}
CGPoint currentPoint = CGPathGetCurrentPoint(path);
CGPathAddLineToPoint(path, NULL, currentPoint.x, currentPoint.y + params[0]);
}
@end
+162
View File
@@ -0,0 +1,162 @@
//
// IJSVGImage.h
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGTraitedColorStorage.h>
#import <IJSVG/IJSVGRootNode.h>
#import <IJSVG/IJSVGExporter.h>
#import <IJSVG/IJSVGGradientLayer.h>
#import <IJSVG/IJSVGGroupLayer.h>
#import <IJSVG/IJSVGRootLayer.h>
#import <IJSVG/IJSVGImageLayer.h>
#import <IJSVG/IJSVGLayerTree.h>
#import <IJSVG/IJSVGParser.h>
#import <IJSVG/IJSVGRendering.h>
#import <IJSVG/IJSVGStyle.h>
#import <IJSVG/IJSVGTransaction.h>
#import <Foundation/Foundation.h>
@class IJSVG;
@class IJSVGParser;
@interface IJSVG : NSObject <NSPasteboardWriting> {
@private
IJSVGRootNode* _rootNode;
IJSVGLayerTree* _layerTree;
CGRect _viewBox;
CGFloat _backingScale;
IJSVGUnitSize* _intrinsicSize;
IJSVGParser* _parser;
}
// 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, assign) IJSVGRenderQuality renderQuality;
@property (nonatomic, strong) IJSVGStyle* style;
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* desc;
@property (nonatomic, strong) IJSVGLayerTree* layerTree;
@property (nonatomic, strong) IJSVGRootLayer* rootLayer;
@property (nonatomic, assign) BOOL ignoreIntrinsicSize;
@property (nonatomic, readonly) IJSVGTraitedColorStorage* colors;
// The size of the SVG either computed by its intrinsicSize of its viewBox
// If the size if % values, it will use the defaultSize
@property (nonatomic, readonly) CGSize size;
// Will return true if the intrinsic size is a % value
@property (nonatomic, readonly) BOOL hasDynamicSize;
// This is used when the intrinsic size is a % value, e.g. 100% x 100%
@property (nonatomic, assign) CGSize defaultSize;
// bitmask of which dimentions were implicitly set on the SVG
@property (nonatomic, readonly) IJSVGIntrinsicDimensions intrinsicDimensions;
- (void)prepForDrawingInView:(NSView*)view;
- (IJSVGRootNode*)rootNode;
- (CGRect)viewBox;
- (CGSize)sizeWithDefaultSize:(CGSize)size;
- (CGSize)sizeByMaintainingAspectRatioWithSize:(CGSize)aSize;
- (NSString*)identifier;
- (NSSet<IJSVG*>*)directDescendSVGs;
- (IJSVGExporter*)exporterWithSize:(CGSize)size
options:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (NSString*)SVGStringWithSize:(CGSize)size
options:(IJSVGExporterOptions)options;
- (NSString*)SVGStringWithSize:(CGSize)size
options:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options;
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (id)SVGNamed:(NSString*)string;
+ (IJSVG*)SVGFromCGPathRef:(CGPathRef)path;
+ (IJSVG*)SVGFromCGPathRef:(CGPathRef)path
flipped:(BOOL)flipped;
- (id)initWithImage:(NSImage*)image;
- (id)initWithRootNode:(IJSVGRootNode*)rootNode;
- (id)initWithSVGLayer:(IJSVGGroupLayer*)group
viewBox:(CGRect)viewBox;
- (id)initWithSVGString:(NSString*)string;
- (id)initWithSVGString:(NSString*)string
error:(NSError**)error;
- (id)initWithSVGData:(NSData*)data;
- (id)initWithSVGData:(NSData*)data
error:(NSError**)error;
- (id)initWithFile:(NSString*)file;
- (id)initWithFile:(NSString*)file
error:(NSError**)error;
- (id)initWithFilePathURL:(NSURL*)aURL;
- (id)initWithFilePathURL:(NSURL*)aURL
error:(NSError**)error;
- (id)initWithDataAssetNamed:(NSDataAssetName)name
error:(NSError**)error;
- (id)initWithDataAssetNamed:(NSDataAssetName)name
bundle:(NSBundle*)bundle
error:(NSError**)error;
- (NSImage*)imageWithSize:(CGSize)aSize;
- (NSImage*)imageWithSize:(CGSize)aSize
error:(NSError**)error;
- (NSImage*)imageWithSize:(CGSize)aSize
flipped:(BOOL)flipped;
- (NSImage*)imageWithSize:(CGSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error;
- (NSImage*)imageByMaintainingAspectRatioWithSize:(CGSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error;
- (CGImageRef)newCGImageRefWithSize:(CGSize)size
flipped:(BOOL)flipped
error:(NSError**)error;
- (BOOL)drawAtPoint:(CGPoint)point
size:(CGSize)size;
- (BOOL)drawAtPoint:(CGPoint)point
size:(CGSize)aSize
error:(NSError**)error;
- (BOOL)drawInRect:(CGRect)rect;
- (BOOL)drawInRect:(CGRect)rect
error:(NSError**)error;
- (void)drawInRect:(CGRect)rect
context:(CGContextRef)context;
- (NSData*)PDFData;
- (NSData*)PDFData:(NSError**)error;
- (NSData*)PDFDataWithRect:(CGRect)rect;
- (NSData*)PDFDataWithRect:(CGRect)rect
error:(NSError**)error;
// call this to invalidate the render tree when you change the style
- (void)setNeedsDisplay;
// colors
- (void)performBlock:(dispatch_block_t)block;
// matching
- (BOOL)containsNodesMatchingTraits:(IJSVGNodeTraits)mask;
@end
+747
View File
@@ -0,0 +1,747 @@
//
// IJSVGImage.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVG.h>
#import <IJSVG/IJSVGExporter.h>
#import <IJSVG/IJSVGTransaction.h>
#import <IJSVG/IJSVGThreadManager.h>
@interface IJSVG (private)
@property (nonatomic, strong) IJSVGParser* parser;
@end
@implementation IJSVG
// these are explicitly implemented
@synthesize title = _title;
@synthesize desc = _desc;
- (void)dealloc
{
// thread manager will deal with this for us, but if we are main thread,
// we want to kick this off as soon as possible, or if the memory is set
// to quick.
IJSVGThreadManager* threadManager = IJSVGThreadManager.currentManager;
BOOL flag = IJSVGBeginTransaction();
_layerTree = nil;
_rootLayer = nil;
if(flag == YES) {
IJSVGEndTransaction();
}
// tell the thread manager we are done with
[threadManager remove:self];
}
+ (id)SVGNamed:(NSString*)string
{
return [self.class SVGNamed:string
error:nil];
}
+ (id)SVGNamed:(NSString*)string
error:(NSError**)error
{
NSBundle* bundle = NSBundle.mainBundle;
NSString* str = nil;
NSString* ext = [string pathExtension];
if(ext == nil || ext.length == 0) {
ext = @"svg";
}
if((str = [bundle pathForResource:[string stringByDeletingPathExtension]
ofType:ext]) != nil) {
return [[self alloc] initWithFile:str
error:error];
}
// check the asset catalogues
return [[self alloc] initWithDataAssetNamed:string
error:error];
}
+ (IJSVG*)SVGFromCGPathRef:(CGPathRef)path
{
return [self SVGFromCGPathRef:path
flipped:NO];
}
+ (IJSVG*)SVGFromCGPathRef:(CGPathRef)path
flipped:(BOOL)flipped
{
CGRect box = CGPathGetPathBoundingBox(path);
IJSVGRootNode* rootNode = [[IJSVGRootNode alloc] init];
rootNode.viewBox = [IJSVGUnitRect rectWithCGRect:box];
CGMutablePathRef nPath = NULL;
if(flipped) {
CGPathRef transformedPath = [IJSVGUtils newFlippedCGPath:path];
nPath = CGPathCreateMutableCopy(transformedPath);
CGPathRelease(transformedPath);
} else {
nPath = CGPathCreateMutableCopy(path);
}
IJSVGPath* childPath = [[IJSVGPath alloc] init];
childPath.path = nPath;
[rootNode addChild:childPath];
CGPathRelease(nPath);
return [[self.class alloc] initWithRootNode:rootNode];
}
- (id)initWithDataAssetNamed:(NSDataAssetName)name
error:(NSError**)error
{
return [self initWithDataAssetNamed:name
bundle:NSBundle.mainBundle
error:error];
}
- (id)initWithDataAssetNamed:(NSDataAssetName)name
bundle:(NSBundle*)bundle
error:(NSError**)error
{
NSDataAsset* dataAsset = [[NSDataAsset alloc] initWithName:name
bundle:bundle];
if(dataAsset != nil) {
return [self initWithSVGData:dataAsset.data
error:error];
}
return nil;
}
- (id)initWithImage:(NSImage*)image
{
IJSVGRootNode* rootNode = [[IJSVGRootNode alloc] init];
IJSVGImage* imageNode = [[IJSVGImage alloc] init];
imageNode.image = image;
IJSVGUnitSize* size = [IJSVGUnitSize sizeWithCGSize:imageNode.intrinsicSize];
IJSVGUnitRect* viewBox = [IJSVGUnitRect rectWithOrigin:IJSVGUnitPoint.zeroPoint
size:size];
imageNode.width = size.width.copy;
imageNode.height = size.height.copy;
rootNode.viewBox = viewBox;
[rootNode addChild:imageNode];
return [self initWithRootNode:rootNode];
}
- (id)initWithSVGLayer:(IJSVGGroupLayer*)group
viewBox:(CGRect)viewBox
{
// this completely bypasses passing of files
if((self = [super init]) != nil) {
// keep the layer tree
_viewBox = viewBox;
// any setups
[self _setupBasicsFromAnyInitializer];
}
return self;
}
- (id)initWithRootNode:(IJSVGRootNode*)rootNode
{
if((self = [super init]) != nil) {
_rootNode = rootNode;
[self _setupBasicInfoFromGroup];
[self _setupBasicsFromAnyInitializer];
}
return self;
}
- (id)initWithFile:(NSString*)file
{
return [self initWithFile:file
error:nil];
}
- (id)initWithFile:(NSString*)file
error:(NSError**)error
{
return [self initWithFilePathURL:[NSURL fileURLWithPath:file isDirectory:NO]
error:error];
}
- (id)initWithFilePathURL:(NSURL*)aURL
{
return [self initWithFilePathURL:aURL
error:nil];
}
- (id)initWithFilePathURL:(NSURL*)aURL
error:(NSError**)error
{
// create the object
if((self = [super init]) != nil) {
NSError* anError = nil;
// create the group
IJSVGParser *parser = [IJSVGParser parserForFileURL:aURL
error:&anError];
self.parser = parser;
[self _setupBasicInfoFromGroup];
[self _setupBasicsFromAnyInitializer];
// something went wrong...
if(_rootNode == nil) {
if(error != NULL) {
*error = anError;
}
self = nil;
return nil;
}
}
return self;
}
- (id)initWithSVGData:(NSData*)data
{
return [self initWithSVGData:data
error:nil];
}
- (void)setParser:(IJSVGParser*)parser {
_rootNode = [parser rootNodeWithSize:CGSizeZero];
// if the rootNode has any form of relative units, we need to keep hold of
// the parser so when we render, we can ask for new values.
if(_rootNode.viewBoxContainsRelativeUnits) {
_parser = parser;
}
}
- (id)initWithSVGData:(NSData*)data
error:(NSError**)error
{
NSString* svgString = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
return [self initWithSVGString:svgString
error:error];
}
- (id)initWithSVGString:(NSString*)string
{
return [self initWithSVGString:string
error:nil];
}
- (id)initWithSVGString:(NSString*)string
error:(NSError**)error
{
if((self = [super init]) != nil) {
// this is basically the same as init with URL just
// bypasses the loading of a file
NSError* anError = nil;
// setup the parser
IJSVGParser* parser = [[IJSVGParser alloc] initWithSVGString:string
error:&anError];
self.parser = parser;
[self _setupBasicInfoFromGroup];
[self _setupBasicsFromAnyInitializer];
// something went wrong :(
if(_rootNode == nil) {
if(error != NULL) {
*error = anError;
}
self = nil;
return nil;
}
}
return self;
}
- (void)performBlock:(dispatch_block_t)block
{
IJSVGPerformTransactionBlock(^{
block();
});
}
- (void)_setupBasicInfoFromGroup
{
_viewBox = [_rootNode.viewBox computeValue:CGSizeZero];
_intrinsicSize = _rootNode.intrinsicSize;
}
- (CGSize)size
{
return [_intrinsicSize computeValue:self.defaultSize];
}
- (CGSize)sizeWithDefaultSize:(CGSize)size
{
return [_intrinsicSize computeValue:size];
}
- (void)_setupBasicsFromAnyInitializer
{
self.style = [[IJSVGStyle alloc] init];
self.ignoreIntrinsicSize = YES;
self.renderQuality = kIJSVGRenderQualityFullResolution;
self.defaultSize = CGSizeMake(200.f, 200.f);
self.renderingBackingScaleHelper = ^CGFloat {
if(NSScreen.mainScreen != nil) {
return NSScreen.mainScreen.backingScaleFactor;
}
return 1.f;
};
// tell the thread manager we exist
[IJSVGThreadManager.currentManager adopt:self];
}
- (BOOL)hasDynamicSize
{
IJSVGUnitSize* size = _intrinsicSize;
return size.width.type == IJSVGUnitLengthTypePercentage ||
size.height.type == IJSVGUnitLengthTypePercentage;
}
- (IJSVGIntrinsicDimensions)intrinsicDimensions
{
return self.rootNode.intrinsicDimensions;
}
- (void)setTitle:(NSString*)title
{
_rootNode.title = title;
}
- (NSString*)title
{
return _rootNode.title;
}
- (void)setDesc:(NSString*)description
{
_rootNode.desc = description;
}
- (NSString*)desc
{
return _rootNode.desc;
}
- (NSString*)identifier
{
return _rootNode.identifier;
}
- (CGRect)viewBox
{
return _viewBox;
}
- (IJSVGRootNode*)rootNode
{
return _rootNode;
}
- (NSSet<IJSVG*>*)directDescendSVGs
{
NSMutableSet<IJSVG*>* svgs = [[NSMutableSet alloc] init];
NSSet<IJSVGNode*>* nodes = [self.rootNode childrenOfType:IJSVGNodeTypeSVG];
for(IJSVGNode* node in nodes) {
IJSVG* newSVG = nil;
newSVG = [[self.class alloc] initWithRootNode:(IJSVGRootNode*)node];
[svgs addObject:newSVG];
}
return svgs;
}
- (IJSVGExporter*)exporterWithSize:(CGSize)size
options:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
return [[IJSVGExporter alloc] initWithSVG:self
size:size
options:options
floatingPointOptions:floatingPointOptions];
}
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
{
IJSVGFloatingPointOptions fpo = IJSVGFloatingPointOptionsDefault();
return [self exporterWithSize:_viewBox.size
options:options
floatingPointOptions:fpo].SVGString;
}
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
return [self exporterWithSize:_viewBox.size
options:options
floatingPointOptions:floatingPointOptions].SVGString;
}
- (NSString *)SVGStringWithSize:(CGSize)size
options:(IJSVGExporterOptions)options
{
IJSVGFloatingPointOptions fpo = IJSVGFloatingPointOptionsDefault();
return [self exporterWithSize:size
options:options
floatingPointOptions:fpo].SVGString;
}
- (NSString *)SVGStringWithSize:(CGSize)size
options:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
return [self exporterWithSize:size
options:options
floatingPointOptions:floatingPointOptions].SVGString;
}
- (NSImage*)imageWithSize:(CGSize)aSize
{
return [self imageWithSize:aSize
flipped:NO
error:nil];
}
- (NSImage*)imageWithSize:(CGSize)aSize
error:(NSError**)error;
{
return [self imageWithSize:aSize
flipped:NO
error:error];
}
- (NSImage*)imageWithSize:(CGSize)aSize
flipped:(BOOL)flipped
{
return [self imageWithSize:aSize
flipped:flipped
error:nil];
}
- (CGImageRef)newCGImageRefWithSize:(CGSize)size
flipped:(BOOL)flipped
error:(NSError**)error
{
// setup the drawing rect, this is used for both the intial drawing
// and the backing scale helper block
CGRect rect = (CGRect) {
.origin = CGPointZero,
.size = (CGSize)size
};
// make sure we setup the scale based on the backing scale factor
CGFloat scale = [self backingScaleFactor];
// create the context and colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ref = CGBitmapContextCreate(NULL, (int)size.width * scale,
(int)size.height * scale, 8, 0, colorSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
// scale the context
CGContextScaleCTM(ref, scale, scale);
if(flipped == YES) {
CGContextTranslateCTM(ref, 0.f, size.height);
CGContextScaleCTM(ref, 1.f, -1.f);
}
// draw the SVG into the context
[self _drawInRect:rect
context:ref
error:error];
// create the image from the context
CGImageRef imageRef = CGBitmapContextCreateImage(ref);
// release all things!
CGColorSpaceRelease(colorSpace);
CGContextRelease(ref);
return imageRef;
}
- (NSImage*)imageWithSize:(CGSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error
{
CGImageRef ref = [self newCGImageRefWithSize:aSize
flipped:flipped
error:error];
NSImage* image = [[NSImage alloc] initWithCGImage:ref
size:aSize];
CGImageRelease(ref);
return image;
}
- (CGSize)sizeByMaintainingAspectRatioWithSize:(CGSize)aSize
{
CGSize ogSize = [_rootNode.intrinsicSize computeValue:aSize];
CGFloat ratio = 0.f;
CGFloat imageWidth = ogSize.width;
CGFloat imageHeight = ogSize.height;
CGFloat maxWidth = aSize.width;
CGFloat maxHeight = aSize.height;
if(imageWidth > imageHeight) {
ratio = maxWidth / imageWidth;
} else {
ratio = maxHeight / imageHeight;
}
ogSize.width = ceilf(imageWidth * ratio);
ogSize.height = ceilf(imageHeight * ratio);
return ogSize;
}
- (NSImage*)imageByMaintainingAspectRatioWithSize:(CGSize)aSize
flipped:(BOOL)flipped
error:(NSError**)error
{
CGSize ogSize = [self sizeByMaintainingAspectRatioWithSize:aSize];
return [self imageWithSize:ogSize
flipped:flipped
error:error];
}
- (NSData*)PDFData
{
return [self PDFData:nil];
}
- (NSData*)PDFData:(NSError**)error
{
return [self
PDFDataWithRect:(CGRect) { .origin = NSZeroPoint, .size = _viewBox.size }
error:error];
}
- (NSData*)PDFDataWithRect:(CGRect)rect
{
return [self PDFDataWithRect:rect error:nil];
}
- (NSData*)PDFDataWithRect:(CGRect)rect
error:(NSError**)error
{
// create the data for the PDF
NSMutableData* data = [[NSMutableData alloc] init];
// 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);
// create the context
CGContextRef context = CGPDFContextCreate(dataConsumer, &box, NULL);
CGContextBeginPage(context, &box);
// the context is currently upside down, doh! flip it...
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -box.size.height);
// make sure we set the masks to path bits n bobs
// draw the icon
[self _drawInRect:(CGRect)box
context:context
error:error];
CGContextEndPage(context);
// clean up
CGPDFContextClose(context);
CGContextRelease(context);
CGDataConsumerRelease(dataConsumer);
return data;
}
- (void)prepForDrawingInView:(NSView*)view
{
// kill the render
if(view == nil) {
self.renderingBackingScaleHelper = nil;
return;
}
// construct the layer before drawing
[self rootLayer];
// set the scale
__weak NSView* weakView = view;
self.renderingBackingScaleHelper = ^CGFloat {
return weakView.window.screen.backingScaleFactor;
};
}
- (BOOL)drawAtPoint:(CGPoint)point
size:(CGSize)aSize
{
return [self drawAtPoint:point
size:aSize
error:nil];
}
- (BOOL)drawAtPoint:(CGPoint)point
size:(CGSize)aSize
error:(NSError**)error
{
return [self drawInRect:NSMakeRect(point.x, point.y,
aSize.width, aSize.height)
error:error];
}
- (BOOL)drawInRect:(CGRect)rect
{
return [self drawInRect:rect error:nil];
}
- (BOOL)drawInRect:(CGRect)rect
error:(NSError**)error
{
CGContextRef currentCGContext;
currentCGContext = NSGraphicsContext.currentContext.CGContext;
return [self _drawInRect:rect
context:currentCGContext
error:error];
}
- (void)drawInRect:(CGRect)rect
context:(CGContextRef)context
{
[self _drawInRect:rect
context:context
error:nil];
}
- (BOOL)_drawInRect:(CGRect)rect
context:(CGContextRef)ctx
error:(NSError**)error
{
BOOL transaction = IJSVGBeginTransaction();
CGContextSaveGState(ctx);
CGFloat backingScale = MAX([self backingScaleFactor], 1.f);
CGInterpolationQuality quality;
switch (_renderQuality) {
case kIJSVGRenderQualityLow: {
quality = kCGInterpolationLow;
break;
}
case kIJSVGRenderQualityOptimized: {
quality = kCGInterpolationMedium;
break;
}
default: {
quality = kCGInterpolationHigh;
}
}
CGContextSetInterpolationQuality(ctx, quality);
IJSVGRootLayer* rootLayer = [self rootLayerWithRect:rect];
[rootLayer renderInContext:ctx
viewPort:rect
backingScale:backingScale
quality:_renderQuality
ignoreIntrinsicSize:_ignoreIntrinsicSize];
CGContextRestoreGState(ctx);
if(transaction == YES) {
IJSVGEndTransaction();
}
return YES;
}
- (IJSVGLayerTree*)layerTree
{
if(_layerTree == nil) {
_layerTree = [[IJSVGLayerTree alloc] init];
_layerTree.style = _style;
}
return _layerTree;
}
- (IJSVGRootLayer*)rootLayerWithRect:(CGRect)rect {
// no parser, which means there is no need to recompute the value
if(_parser == nil || !_rootNode.viewBoxContainsRelativeUnits ||
CGSizeEqualToSize(_rootNode.clientSize, rect.size)) {
return self.rootLayer;
}
// if we do have a parser, that means the node has relative values, lets recompute
__weak IJSVG* weakSelf = self;
[self performBlock:^{
IJSVG* strongSelf = weakSelf;
strongSelf->_rootNode = [strongSelf->_parser rootNodeWithSize:rect.size];
strongSelf->_rootLayer = [strongSelf.layerTree rootLayerForRootNode:strongSelf->_rootNode];
}];
return _rootLayer;
}
- (IJSVGRootLayer*)rootLayer
{
if(_rootLayer != nil) {
return _rootLayer;
}
__weak IJSVG* weakSelf = self;
[self performBlock:^{
IJSVG* strongSelf = weakSelf;
strongSelf->_rootLayer = [strongSelf.layerTree rootLayerForRootNode:strongSelf->_rootNode];
}];
return _rootLayer;
}
- (CGFloat)backingScaleFactor
{
__block CGFloat scale = 1.f;
if(self.renderingBackingScaleHelper != nil) {
scale = (self.renderingBackingScaleHelper)();
}
scale = MAX(1.f, scale);
return _backingScale = scale;
}
- (void)setNeedsDisplay
{
[self invalidateLayerTree];
}
- (void)invalidateLayerTree
{
__weak IJSVG* weakSelf = self;
[self performBlock:^{
IJSVG* strongSelf = weakSelf;
strongSelf->_rootLayer = nil;
strongSelf->_layerTree = nil;
}];
}
- (IJSVGTraitedColorStorage*)colors
{
return self.rootLayer.colors;
}
#pragma mark NSPasteboard
- (NSArray*)writableTypesForPasteboard:(NSPasteboard*)pasteboard
{
return @[ NSPasteboardTypePDF ];
}
- (id)pasteboardPropertyListForType:(NSString*)type
{
if([type isEqualToString:NSPasteboardTypePDF]) {
return [self PDFData];
}
return nil;
}
#pragma mark matching
- (BOOL)containsNodesMatchingTraits:(IJSVGNodeTraits)traits
{
return [_rootNode containsNodesMatchingTraits:traits];
}
@end
@@ -8,9 +8,9 @@
#import <Foundation/Foundation.h>
static NSString * const IJSVGErrorDomain = @"IJSVGErrorDomain";
static NSString* const IJSVGErrorDomain = @"IJSVGErrorDomain";
NS_ENUM(NSInteger) {
NS_ENUM(NSInteger){
IJSVGErrorReadingFile,
IJSVGErrorParsingFile,
IJSVGErrorParsingSVG,
@@ -6,17 +6,20 @@
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGParser.h>
#import <Cocoa/Cocoa.h>
#import "IJSVGParser.h"
@class IJSVG;
@interface IJSVGImageRep : NSImageRep {
@private
IJSVG * _svg;
IJSVG* _svg;
}
- (instancetype)initWithData:(NSData*)data;
@property (nonatomic, readonly) CGRect viewBox;
@property (nonatomic, readonly) IJSVG* SVG;
@end
@@ -6,69 +6,68 @@
// Copyright © 2019 Curtis Hard. All rights reserved.
//
#import "IJSVGImageRep.h"
#import "IJSVG.h"
#import <IJSVG/IJSVG.h>
#import <IJSVG/IJSVGImageRep.h>
@implementation IJSVGImageRep
@synthesize viewBox = _viewBox;
+ (void)load
{
[NSBitmapImageRep registerImageRepClass:self];
}
+ (BOOL)canInitWithData:(NSData *)data
+ (BOOL)canInitWithData:(NSData*)data
{
return [IJSVGParser isDataSVG:data];
}
+ (NSArray<NSString *> *)imageTypes
+ (NSArray<NSString*>*)imageTypes
{
return @[(NSString *)kUTTypeScalableVectorGraphics, @"svg"];
if(@available(macOS 10.10, *)) {
return @[ (NSString*)kUTTypeScalableVectorGraphics, @"svg" ];
} else {
return @[ @"public.svg-image", @"svg" ];
}
}
+ (NSArray<NSString *> *)imageUnfilteredTypes
+ (NSArray<NSString*>*)imageUnfilteredTypes
{
return @[(NSString *)kUTTypeScalableVectorGraphics, @"svg"];
if(@available(macOS 10.10, *)) {
return @[ (NSString*)kUTTypeScalableVectorGraphics, @"svg" ];
} else {
return @[ @"public.svg-image", @"svg" ];
}
}
+ (NSArray<NSImageRep *> *)imageRepsWithData:(NSData *)data
+ (NSArray<NSImageRep*>*)imageRepsWithData:(NSData*)data
{
IJSVGImageRep * instance = [self imageRepWithData:data];
IJSVGImageRep* instance = [self imageRepWithData:data];
if(instance == nil) {
return @[];
}
return @[instance];
return @[ instance ];
}
+ (instancetype)imageRepWithData:(NSData *)data
+ (instancetype)imageRepWithData:(NSData*)data
{
return [[[self alloc] initWithData:data] autorelease];
return [[self alloc] initWithData:data];
}
- (void)dealloc
{
[_svg release], _svg = nil;
[super dealloc];
}
- (instancetype)initWithData:(NSData *)data
- (instancetype)initWithData:(NSData*)data
{
if((self = [super init]) != nil) {
// grab the string from the data
// its more then likely UTF-8...
NSString * string = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSString* string = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
_svg = [[IJSVG alloc] initWithSVGString:string];
// no valid SVG, just return nil;
if(_svg == nil) {
[self release];
return nil;
}
// set default properties
self.pixelsWide = _svg.viewBox.size.width;
self.pixelsHigh = _svg.viewBox.size.height;
@@ -101,4 +100,9 @@
return _svg.viewBox;
}
- (IJSVG*)SVG
{
return _svg;
}
@end
@@ -0,0 +1,37 @@
//
// IJSVGUmbrella.h
// IJSVG
//
// Created by Curtis Hard on 16/04/2023.
// Copyright © 2023 Curtis Hard. All rights reserved.
//
#ifndef IJSVGUmbrella_h
#define IJSVGUmbrella_h
#import <IJSVG/IJSVG.h>
#import <IJSVG/IJSVGCommandClose.h>
#import <IJSVG/IJSVGCommandCurve.h>
#import <IJSVG/IJSVGCommandEllipticalArc.h>
#import <IJSVG/IJSVGCommandHorizontalLine.h>
#import <IJSVG/IJSVGCommandLineTo.h>
#import <IJSVG/IJSVGCommandMove.h>
#import <IJSVG/IJSVGCommandQuadraticCurve.h>
#import <IJSVG/IJSVGCommandSmoothQuadraticCurve.h>
#import <IJSVG/IJSVGCommandSmoothCurve.h>
#import <IJSVG/IJSVGCommandVerticalLine.h>
#import <IJSVG/IJSVGExporterPathInstruction.h>
#import <IJSVG/IJSVGFeatureFlag.h>
#import <IJSVG/IJSVGFeatureFlags.h>
#import <IJSVG/IJSVGFilterEffectGaussianBlur.h>
#import <IJSVG/IJSVGFilterLayer.h>
#import <IJSVG/IJSVGImageRep.h>
#import <IJSVG/IJSVGMath.h>
#import <IJSVG/IJSVGParsing.h>
#import <IJSVG/IJSVGPatternLayer.h>
#import <IJSVG/IJSVGStrokeLayer.h>
#import <IJSVG/IJSVGThreadManager.h>
#import <IJSVG/IJSVGView.h>
#import <IJSVG/NSImage+IJSVGAdditions.h>
#endif /* IJSVGUmbrella_h */
@@ -0,0 +1,25 @@
//
// IJSVGView.h
// IconJar
//
// Created by Curtis Hard on 04/04/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVG.h>
#import <Cocoa/Cocoa.h>
IB_DESIGNABLE
@interface IJSVGView : NSView {
IBInspectable NSString* imageName;
IBInspectable NSColor* tintColor;
IJSVG* SVG;
}
@property (nonatomic, strong) IJSVG* SVG;
+ (IJSVGView*)viewWithSVGNamed:(NSString*)name;
- (id)initWithSVG:(IJSVG*)anSvg;
@end
@@ -6,7 +6,7 @@
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import "IJSVGView.h"
#import <IJSVG/IJSVGView.h>
@implementation IJSVGView
@@ -17,18 +17,15 @@
// make sure we call this, or block may get called for a view
// that doesnt exist
[SVG prepForDrawingInView:nil];
[imageName release], imageName = nil;
[SVG release], SVG = nil;
[super dealloc];
}
+ (IJSVGView *)viewWithSVGNamed:(NSString *)name
+ (IJSVGView*)viewWithSVGNamed:(NSString*)name
{
IJSVG * anSVG = [IJSVG svgNamed:name];
return [[[self alloc] initWithSVG:anSVG] autorelease];
IJSVG* anSVG = [IJSVG SVGNamed:name];
return [[self alloc] initWithSVG:anSVG];
}
- (id)initWithSVG:(IJSVG *)anSvg
- (id)initWithSVG:(IJSVG*)anSvg
{
if((self = [super init]) != nil) {
self.SVG = anSvg;
@@ -40,7 +37,7 @@
{
// image was set via IB
if(imageName != nil) {
IJSVG * anSVG = [IJSVG svgNamed:imageName];
IJSVG* anSVG = [IJSVG SVGNamed:imageName];
if(tintColor != nil) {
anSVG.style.fillColor = tintColor;
}
@@ -48,14 +45,11 @@
}
}
- (void)setSVG:(IJSVG *)anSVG
- (void)setSVG:(IJSVG*)anSVG
{
// memory clean
if(SVG != nil) {
[SVG release], SVG = nil;
}
SVG = [anSVG retain];
SVG = anSVG;
// redisplay ourself!
[SVG prepForDrawingInView:self];
[self setNeedsDisplay:YES];
@@ -72,7 +66,7 @@
if(self.SVG == nil) {
return;
}
// draw the svg
[self.SVG drawInRect:self.bounds];
}
@@ -0,0 +1,120 @@
//
// IJSVGExporter.h
// IJSVGExample
//
// Created by Curtis Hard on 06/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <IJSVG/IJSVGUtils.h>
#import <IJSVG/IJSVGTraitedColor.h>
@class IJSVG;
@class IJSVGExporter;
@class IJSVGLayer;
@class IJSVGNode;
NS_ASSUME_NONNULL_BEGIN
typedef void (^IJSVGCGPathHandler)(const CGPathElement* pathElement);
typedef void (^IJSVGPathElementEnumerationBlock)(const CGPathElement* pathElement, CGPoint currentPoint);
void IJSVGExporterPathCaller(void* info, const CGPathElement* pathElement);
typedef NS_OPTIONS(NSInteger, IJSVGExporterOptions) {
IJSVGExporterOptionNone = 1 << 0,
IJSVGExporterOptionRemoveUselessGroups = 1 << 1,
IJSVGExporterOptionRemoveUselessDef = 1 << 2,
IJSVGExporterOptionMoveAttributesToGroup = 1 << 3,
IJSVGExporterOptionCreateUseForPaths = 1 << 4,
IJSVGExporterOptionSortAttributes = 1 << 5,
IJSVGExporterOptionCollapseGroups = 1 << 6,
IJSVGExporterOptionCleanupPaths = 1 << 7,
IJSVGExporterOptionRemoveHiddenElements = 1 << 8,
IJSVGExporterOptionScaleToSizeIfNecessary DEPRECATED_ATTRIBUTE = 1 << 9,
IJSVGExporterOptionCompressOutput = 1 << 10,
IJSVGExporterOptionCollapseGradients = 1 << 11,
IJSVGExporterOptionCreateClasses DEPRECATED_ATTRIBUTE = 1 << 12,
IJSVGExporterOptionRemoveWidthHeightAttributes = 1 << 13,
IJSVGExporterOptionColorAllowRRGGBBAA = 1 << 14,
IJSVGExporterOptionRemoveComments = 1 << 15,
IJSVGExporterOptionCenterWithinViewBox = 1 << 16,
IJSVGExporterOptionRemoveXMLDeclaration = 1 << 17,
IJSVGExporterOptionConvertArcs = 1 << 18,
IJSVGExporterOptionConvertShapesToPaths = 1 << 19,
IJSVGExporterOptionRoundTransforms = 1 << 20,
IJSVGExporterOptionRemoveDefaultValues = 1 << 21,
IJSVGExporterOptionConvertStrokesToPaths = 1 << 22,
IJSVGExporterOptionAll = IJSVGExporterOptionRemoveUselessDef | IJSVGExporterOptionRemoveUselessGroups |
IJSVGExporterOptionCreateUseForPaths | IJSVGExporterOptionMoveAttributesToGroup |
IJSVGExporterOptionSortAttributes | IJSVGExporterOptionCollapseGroups |
IJSVGExporterOptionCleanupPaths | IJSVGExporterOptionRemoveHiddenElements | IJSVGExporterOptionCompressOutput |
IJSVGExporterOptionCollapseGradients | IJSVGExporterOptionRemoveWidthHeightAttributes |
IJSVGExporterOptionColorAllowRRGGBBAA | IJSVGExporterOptionRemoveComments |
IJSVGExporterOptionCenterWithinViewBox | IJSVGExporterOptionRemoveXMLDeclaration |
IJSVGExporterOptionConvertArcs | IJSVGExporterOptionConvertShapesToPaths |
IJSVGExporterOptionRoundTransforms | IJSVGExporterOptionRemoveDefaultValues |
IJSVGExporterOptionConvertStrokesToPaths
};
BOOL IJSVGExporterHasOption(IJSVGExporterOptions options, NSInteger option);
void IJSVGEnumerateCGPathElements(CGPathRef path, IJSVGPathElementEnumerationBlock enumBlock);
const NSArray<NSString*>* IJSVGShortCharacterArray(void);
const NSDictionary<NSString*, NSString*>* IJSVGDefaultAttributes(void);
@protocol IJSVGExporterDelegate <NSObject>
@optional
- (NSString* _Nullable)svgExporter:(IJSVGExporter*)exporter
identifierForElement:(NSXMLElement* _Nullable)element
type:(IJSVGNodeType)type
defaultID:(NSString* (^)(void))defaultID;
- (NSString* _Nullable)svgExporter:(IJSVGExporter*)exporter
stringForColor:(NSColor*)color
flags:(IJSVGColorUsageTraits)flag
options:(IJSVGColorStringOptions)options;
@end
@interface IJSVGExporter : NSObject {
@private
IJSVG* _svg;
CGSize _size;
IJSVGExporterOptions _options;
NSXMLDocument* _dom;
NSXMLElement* _defElement;
NSInteger _idCount;
NSInteger _shortIdCount;
BOOL _appliedXLink;
IJSVGThreadManager* _threadManager;
struct {
unsigned int identifierForElement: 1;
unsigned int stringForColor: 1;
} _respondsTo;
}
@property (nonatomic, assign) id<IJSVGExporterDelegate> delegate;
@property (nonatomic, assign) IJSVGFloatingPointOptions floatingPointOptions;
@property (nonatomic, copy, nullable) NSString* title;
@property (nonatomic, copy, nullable) NSString* desc;
- (id)initWithSVG:(IJSVG*)svg
size:(CGSize)size
options:(IJSVGExporterOptions)options;
- (id)initWithSVG:(IJSVG*)svg
size:(CGSize)size
options:(IJSVGExporterOptions)options
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (NSString*)SVGString;
- (NSData*)SVGData;
- (IJSVG*)SVG:(NSError**)error;
@end
NS_ASSUME_NONNULL_END
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,63 @@
//
// IJSVGExporterPathInstruction.h
// IconJar
//
// Created by Curtis Hard on 08/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface IJSVGExporterPathInstructionCommand : NSObject {
}
@property (nonatomic, assign) char instruction;
@property (nonatomic, strong) NSArray<NSString*>* params;
@end
@interface IJSVGExporterPathInstruction : NSObject {
@private
NSInteger _dataCount;
CGFloat* _data;
CGFloat* _base;
CGFloat* _coords;
}
@property (nonatomic, assign) char instruction;
void IJSVGExporterPathInstructionRoundData(CGFloat* data, NSInteger length, IJSVGFloatingPointOptions options);
CGFloat IJSVGExporterPathFloatToFixed(CGFloat number, int precision);
+ (NSArray<IJSVGExporterPathInstruction*>*)instructionsFromPath:(CGPathRef)path
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
- (id)initWithInstruction:(char)instruction
dataCount:(NSInteger)floatCount;
- (CGFloat*)data;
- (NSInteger)dataLength;
+ (NSArray<IJSVGExporterPathInstruction*>*)convertInstructionsCurves:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (void)convertInstructionsToMixedAbsoluteRelative:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (void)convertInstructionsDataToRounded:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
+ (NSString*)pathStringWithInstructionSet:(NSArray<NSValue*>*)instructionSets
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions;
@end
NS_ASSUME_NONNULL_END
static NSInteger const kIJSVGExporterPathInstructionFloatPrecision = 3;
static CGFloat const kIJSVGExporterPathInstructionErrorThreshold = 1e-2;
#define IJ_SVG_EXPORT_ROUND(value) IJSVGExporterPathFloatToFixed(value, kIJSVGExporterPathInstructionFloatPrecision)
@@ -0,0 +1,652 @@
//
// IJSVGExporterPathInstruction.m
// IconJar
//
// Created by Curtis Hard on 08/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGExporter.h>
#import <IJSVG/IJSVGExporterPathInstruction.h>
#import <IJSVG/IJSVGUtils.h>
#import <math.h>
@implementation IJSVGExporterPathInstructionCommand
@end
@implementation IJSVGExporterPathInstruction
@synthesize instruction = _instruction;
- (void)dealloc
{
if(_data != NULL) {
(void)free(_data), _data = NULL;
}
if(_base != NULL) {
(void)free(_base), _base = NULL;
}
if(_coords != NULL) {
(void)free(_coords), _coords = NULL;
}
}
- (id)initWithInstruction:(char)instruction
dataCount:(NSInteger)floatCount
{
if((self = [super init]) != nil) {
_instruction = instruction;
// only allocate if not zero
if(floatCount != 0) {
_dataCount = floatCount;
_data = (CGFloat*)calloc(sizeof(CGFloat), floatCount);
}
// setup base and coords
_base = (CGFloat*)malloc(sizeof(CGFloat) * 2);
_coords = (CGFloat*)malloc(sizeof(CGFloat) * 2);
}
return self;
}
- (NSInteger)dataLength
{
return _dataCount;
}
- (CGFloat*)data
{
return _data;
}
- (CGFloat*)base
{
return _base;
}
- (CGFloat*)coords
{
return _coords;
}
+ (NSString*)pathStringWithInstructionSet:(NSArray<IJSVGExporterPathInstructionCommand*>*)instructionSets
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
IJSVGExporterPathInstructionCommand* lastCommand = NULL;
NSMutableString* string = [[NSMutableString alloc] init];
for (IJSVGExporterPathInstructionCommand* command in instructionSets) {
// read back the bytes
// add on the instruction character only if there is no current command
// or the last command is not the same as the current command
// if they both are the same, we still need to seperate them via a space
if(lastCommand == nil || (lastCommand != nil && lastCommand.instruction != command.instruction)) {
[string appendFormat:@"%c", command.instruction];
} else {
[string appendString:@" "];
}
// compresses the floats
NSString* compressedFloats = IJSVGCompressFloatParameterArray(command.params);
[string appendString:compressedFloats];
// store last command
lastCommand = command;
}
return string;
}
+ (NSString*)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
NSMutableArray* pathInstructions = [[NSMutableArray alloc] init];
for (IJSVGExporterPathInstruction* instruction in instructions) {
CGFloat* data = instruction.data;
const char lowerInstruction = tolower(instruction.instruction);
NSArray<NSString*>* set = nil;
switch (lowerInstruction) {
case 't':
case 'm':
case 'l': {
set = @[
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions)
];
break;
}
case 'v':
case 'h': {
set = @[
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions)
];
break;
}
case 'c': {
set = @[
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[2], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[3], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[4], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[5], floatingPointOptions)
];
break;
}
case 's':
case 'q': {
set = @[
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[2], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[3], floatingPointOptions)
];
break;
}
case 'a': {
set = @[
IJSVGShortFloatStringWithOptions(data[0], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[1], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[2], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[3], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[4], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[5], floatingPointOptions),
IJSVGShortFloatStringWithOptions(data[6], floatingPointOptions),
];
break;
}
// close path
case 'z': {
set = @[];
}
}
// wrap into the command and give to the array
IJSVGExporterPathInstructionCommand* wrapperCommand = nil;
wrapperCommand = [[IJSVGExporterPathInstructionCommand alloc] init];
wrapperCommand.instruction = instruction.instruction;
wrapperCommand.params = set ?: @[];
[pathInstructions addObject:wrapperCommand];
}
return [self pathStringWithInstructionSet:pathInstructions
floatingPointOptions:floatingPointOptions];
}
CGFloat IJSVGExporterPathFloatToFixed(CGFloat number, int precision)
{
return floorf(pow(10, precision) * number) / pow(10, precision);
}
void IJSVGExporterPathInstructionRoundData(CGFloat* data, NSInteger length,
IJSVGFloatingPointOptions options)
{
for (NSInteger i = length; i-- > 0;) {
CGFloat d = data[i];
CGFloat proposed = IJSVGExporterPathFloatToFixed(d, options.precision);
if(proposed != d) {
CGFloat rounded = +IJSVGExporterPathFloatToFixed(d, options.precision - 1);
data[i] = IJSVGExporterPathFloatToFixed(+fabs(rounded - d), options.precision + 1)
>= kIJSVGExporterPathInstructionErrorThreshold
? +IJSVGExporterPathFloatToFixed(d, options.precision)
: rounded;
}
}
}
+ (void)convertInstructionsToRoundRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
CGFloat relSubPoint[2] = { 0.f, 0.f };
for (IJSVGExporterPathInstruction* instruction in instructions) {
char instructionChar = instruction.instruction;
NSInteger length = instruction.dataLength;
CGFloat* data = instruction.data;
if(strchr("mltqsc", instructionChar) != NULL) {
for (NSInteger i = length; i--;) {
data[i] += instruction.base[i % 2] - relSubPoint[i % 2];
}
} else if(instructionChar == 'h') {
data[0] += instruction.base[0] - relSubPoint[0];
} else if(instructionChar == 'v') {
data[0] += instruction.base[1] - relSubPoint[1];
} else if(instructionChar == 'a') {
data[5] += instruction.base[0] - relSubPoint[0];
data[5] += instruction.base[1] - relSubPoint[1];
}
IJSVGExporterPathInstructionRoundData(data, length, floatingPointOptions);
if(instructionChar == 'h') {
relSubPoint[0] += data[0];
} else if(instructionChar == 'v') {
relSubPoint[1] += data[0];
} else {
relSubPoint[0] += data[length - 2];
relSubPoint[1] += data[length - 1];
}
IJSVGExporterPathInstructionRoundData(relSubPoint, 2, floatingPointOptions);
}
}
+ (void)convertInstructionsToMixedAbsoluteRelative:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
IJSVGExporterPathInstruction* prevInstruction = nil;
for (IJSVGExporterPathInstruction* instruction in instructions) {
if(prevInstruction == nil || instruction.dataLength == 0) {
prevInstruction = instruction;
continue;
}
char instructionChar = instruction.instruction;
CGFloat* data = instruction.data;
NSInteger length = instruction.dataLength;
CGFloat* adata = (CGFloat*)malloc(sizeof(CGFloat) * length);
memcpy(adata, data, sizeof(CGFloat) * length);
if(strchr("mltqsc", instructionChar) != NULL) {
for (NSInteger i = length; i--;) {
adata[i] += instruction.base[i % 2];
}
} else if(instructionChar == 'h') {
adata[0] += instruction.base[0];
} else if(instructionChar == 'v') {
adata[0] += instruction.base[1];
} else if(instructionChar == 'a') {
adata[5] += instruction.base[0];
adata[6] += instruction.base[1];
}
IJSVGExporterPathInstructionRoundData(adata, length, floatingPointOptions);
IJSVGExporterPathInstruction* ainstruction = nil;
ainstruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:instructionChar
dataCount:length];
memcpy(ainstruction.data, adata, sizeof(CGFloat) * length);
// run these through our default string runner
// to compare the outputs
NSString* orig = [self pathStringFromInstructions:@[ instruction ]
floatingPointOptions:floatingPointOptions];
NSString* comp = [self pathStringFromInstructions:@[ ainstruction ]
floatingPointOptions:floatingPointOptions];
if(comp.length < orig.length && !(instructionChar == prevInstruction.instruction &&
prevInstruction.instruction > 96 &&
comp.length == orig.length - 1 &&
data[0] < 0.f &&
fmod(prevInstruction.data[prevInstruction.dataLength - 1], 1) != 0.f)) {
instruction.instruction = toupper(instructionChar);
memcpy(data, adata, sizeof(CGFloat) * length);
}
(void)free(adata), adata = NULL;
prevInstruction = instruction;
}
}
+ (void)convertInstructionsDataToRounded:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
for (IJSVGExporterPathInstruction* instruction in instructions) {
CGFloat* data = instruction.data;
IJSVGExporterPathInstructionRoundData(data, instruction.dataLength,
floatingPointOptions);
}
}
+ (NSArray<IJSVGExporterPathInstruction*>*)convertInstructionsCurves:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
NSMutableArray<IJSVGExporterPathInstruction*>* nInstructions = nil;
nInstructions = [[NSMutableArray alloc] initWithCapacity:instructions.count];
IJSVGExporterPathInstruction* lastInstruction = nil;
for (IJSVGExporterPathInstruction* instruction in instructions) {
lastInstruction = nInstructions.lastObject;
if(lastInstruction == nil) {
[nInstructions addObject:instruction];
continue;
}
if(instruction.instruction == 'c') {
if(lastInstruction.instruction == 'c' &&
instruction.data[0] == -(lastInstruction.data[2] - lastInstruction.data[4]) &&
instruction.data[1] == -(lastInstruction.data[3] - lastInstruction.data[5])) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'s'
dataCount:4];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
nInstruction.data[2] = instruction.data[4];
nInstruction.data[3] = instruction.data[5];
[nInstructions addObject:nInstruction];
continue;
} else if(lastInstruction.instruction == 's' &&
instruction.data[0] == -(lastInstruction.data[0] - lastInstruction.data[2]) &&
instruction.data[1] == -(lastInstruction.data[1] - lastInstruction.data[3])) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'s'
dataCount:4];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
nInstruction.data[2] = instruction.data[4];
nInstruction.data[3] = instruction.data[5];
[nInstructions addObject:nInstruction];
continue;
} else if(lastInstruction.instruction != 'c' &&
lastInstruction.instruction != 's' &&
instruction.data[0] == 0.f && instruction.data[1] == 0.f) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'s'
dataCount:4];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
nInstruction.data[2] = instruction.data[4];
nInstruction.data[3] = instruction.data[5];
[nInstructions addObject:nInstruction];
continue;
}
} else if(instruction.instruction == 'q') {
if(lastInstruction.instruction == 'q' &&
instruction.data[0] == (lastInstruction.data[2] - lastInstruction.data[0]) &&
instruction.data[1] == (lastInstruction.data[3] - lastInstruction.data[1])) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'t'
dataCount:2];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
[nInstructions addObject:nInstruction];
continue;
} else if(lastInstruction.instruction == 't' &&
instruction.data[2] == lastInstruction.data[0] &&
instruction.data[3] == lastInstruction.data[1]) {
IJSVGExporterPathInstruction* nInstruction = nil;
nInstruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'t'
dataCount:2];
nInstruction.data[0] = instruction.data[2];
nInstruction.data[1] = instruction.data[3];
[nInstructions addObject:nInstruction];
continue;
}
}
[nInstructions addObject:instruction];
}
return nInstructions;
}
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction*>*)instructions
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
CGFloat start[2] = {0, 0};
CGFloat cursor[2] = {0, 0};
CGFloat prevCoords[2] = {0, 0};
NSInteger index = 0;
for (IJSVGExporterPathInstruction* anInstruction in instructions) {
char instruction = anInstruction.instruction;
CGFloat* data = anInstruction.data;
CGFloat* base = anInstruction.base;
CGFloat* coords = anInstruction.coords;
switch(instruction) {
case 'm': {
cursor[0] += data[0];
cursor[1] += data[1];
start[0] = cursor[0];
start[1] = cursor[1];
break;
}
case 'M': {
if(index != 0) {
instruction = 'm';
}
data[0] -= cursor[0];
data[1] -= cursor[1];
cursor[0] += data[0];
cursor[1] += data[1];
start[0] = cursor[0];
start[1] = cursor[1];
break;
}
case 'l': {
cursor[0] += data[0];
cursor[1] += data[1];
break;
}
case 'L': {
instruction = 'l';
data[0] -= cursor[0];
data[1] -= cursor[1];
cursor[0] += data[0];
cursor[1] += data[1];
break;
}
case 'h': {
cursor[0] += data[0];
break;
}
case 'H': {
instruction = 'h';
data[0] -= cursor[0];
cursor[0] += data[0];
break;
}
case 'v': {
cursor[1] += data[0];
break;
}
case 'V': {
instruction = 'v';
data[0] -= cursor[1];
cursor[1] += data[0];
break;
}
case 'c': {
cursor[0] += data[4];
cursor[1] += data[5];
break;
}
case 'C': {
instruction = 'c';
data[0] -= cursor[0];
data[1] -= cursor[1];
data[2] -= cursor[0];
data[3] -= cursor[1];
data[4] -= cursor[0];
data[5] -= cursor[1];
cursor[0] += data[4];
cursor[1] += data[5];
break;
}
case 's': {
cursor[0] += data[2];
cursor[1] += data[3];
break;
}
case 'S': {
instruction = 's';
data[0] -= cursor[0];
data[1] -= cursor[1];
data[2] -= cursor[0];
data[3] -= cursor[1];
cursor[0] += data[2];
cursor[1] += data[3];
break;
}
case 'q': {
cursor[0] += data[2];
cursor[1] += data[3];
break;
}
case 'Q': {
instruction = 'q';
data[0] -= cursor[0];
data[1] -= cursor[1];
data[2] -= cursor[0];
data[3] -= cursor[1];
cursor[0] += data[2];
cursor[1] += data[3];
break;
}
case 't': {
cursor[0] += data[0];
cursor[1] += data[1];
break;
}
case 'T': {
instruction = 't';
data[0] -= cursor[0];
data[1] -= cursor[1];
cursor[0] += data[0];
cursor[1] += data[1];
break;
}
case 'a': {
cursor[0] += data[5];
cursor[1] += data[6];
break;
}
case 'A': {
instruction = 'a';
data[5] -= cursor[0];
data[6] -= cursor[1];
cursor[0] += data[5];
cursor[1] += data[6];
break;
}
case 'Z':
case 'z': {
cursor[0] = start[0];
cursor[1] = start[1];
break;
}
}
// set the instruction back
anInstruction.instruction = instruction;
base[0] = prevCoords[0];
base[1] = prevCoords[1];
coords[0] = cursor[0];
coords[1] = cursor[1];
prevCoords[0] = cursor[0];
prevCoords[1] = cursor[1];
index++;
}
}
+ (NSArray<IJSVGExporterPathInstruction*>*)instructionsFromPath:(CGPathRef)path
floatingPointOptions:(IJSVGFloatingPointOptions)floatingPointOptions
{
// keep track of the current point
__block CGPoint currentPoint = CGPointZero;
NSMutableArray* instructions = [[NSMutableArray alloc] init];
// 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];
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];
instruction.data[0] = point.y;
} else if(point.y == currentPoint.y) {
instruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'H'
dataCount:1];
instruction.data[0] = point.x;
} else {
instruction = [[IJSVGExporterPathInstruction alloc] initWithInstruction:'L'
dataCount:2];
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];
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];
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];
[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
@@ -0,0 +1,14 @@
//
// IJSVGBasicLayer.h
// IJSVG
//
// Created by Curtis Hard on 19/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import <IJSVG/IJSVGLayer.h>
@interface IJSVGBasicLayer : CALayer <IJSVGBasicLayer>
@end
@@ -0,0 +1,30 @@
//
// IJSVGBasicLayer.m
// IJSVG
//
// Created by Curtis Hard on 19/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGBasicLayer.h>
@implementation IJSVGBasicLayer
@synthesize backingScaleFactor;
@synthesize requiresBackingScale;
@synthesize renderQuality;
@synthesize debugLayers;
- (id<CAAction>)actionForKey:(NSString*)event
{
return nil;
}
- (void)performRenderInContext:(CGContextRef)ctx
{
}
@end
@@ -0,0 +1,23 @@
//
// IJSVGFilterLayer.h
// IJSVG
//
// Created by Curtis Hard on 19/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGFilter.h>
#import <IJSVG/IJSVGBasicLayer.h>
@interface IJSVGFilterLayer : IJSVGLayer {
@private
IJSVGBasicLayer* _hostingLayer;
CGImageRef _image;
}
@property (nonatomic, strong) CALayer<IJSVGDrawableLayer>* sublayer;
@end
@@ -0,0 +1,69 @@
//
// IJSVGFilterLayer.m
// IJSVG
//
// Created by Curtis Hard on 19/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGFilterLayer.h>
#import <IJSVG/IJSVGTransform.h>
@implementation IJSVGFilterLayer
- (void)dealloc
{
(void)CGImageRelease(_image), _image = nil;
}
- (instancetype)init
{
if((self = [super init]) != nil) {
_hostingLayer = [IJSVGBasicLayer layer];
[self addSublayer:_hostingLayer];
}
return self;
}
- (BOOL)requiresBackingScale
{
return YES;
}
- (void)setBackingScaleFactor:(CGFloat)backingScaleFactor
{
// we are responsible for recursively calling the sublayer
// with the new backing scale factor
[IJSVGLayer setBackingScaleFactor:backingScaleFactor
renderQuality:self.renderQuality
recursivelyToLayer:_sublayer];
BOOL needsChange = self.backingScaleFactor != backingScaleFactor;
[super setBackingScaleFactor:backingScaleFactor];
if(needsChange == YES) {
[self updateImage];
}
}
- (void)updateImage
{
if(_image != nil) {
(void)CGImageRelease(_image), _image = nil;
}
_image = [self.filter newImageByApplyFilterToLayer:_sublayer
scale:self.backingScaleFactor];
}
- (void)layoutSublayers
{
CGRect frame = _sublayer.innerBoundingBox;
_hostingLayer.frame = frame;
_hostingLayer.contents = (__bridge id)_image;
}
- (NSArray<CALayer<IJSVGBasicLayer>*>*)debugLayers
{
return @[_sublayer];
}
@end
@@ -6,19 +6,16 @@
// Copyright © 2016 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGradient.h>
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGPath.h>
#import <QuartzCore/QuartzCore.h>
#import "IJSVGGradient.h"
#import "IJSVGPath.h"
#import "IJSVGLayer.h"
@interface IJSVGGradientLayer : IJSVGLayer {
}
@property (nonatomic, strong) IJSVGGradient* gradient;
@property (nonatomic, assign) CGRect viewBox;
@property (nonatomic, retain) IJSVGGradient * gradient;
@property (nonatomic, assign) CGAffineTransform absoluteTransform;
@property (nonatomic, assign) CGRect objectRect;
@end
@@ -0,0 +1,107 @@
//
// IJSVGGradientLayer.m
// IJSVGExample
//
// Created by Curtis Hard on 29/12/2016.
// Copyright © 2016 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGradientLayer.h>
#import <IJSVG/IJSVGRootLayer.h>
@implementation IJSVGGradientLayer
- (BOOL)requiresBackingScale
{
return YES;
}
- (void)setGradient:(IJSVGGradient*)newGradient
{
_gradient = newGradient;
// lets check its alpha properties on the colors
BOOL hasAlphaChannel = NO;
for (NSColor* color in newGradient.colors) {
if(color.alphaComponent != 1.f) {
hasAlphaChannel = YES;
break;
}
}
self.opaque = hasAlphaChannel == NO;
}
- (void)setOpacity:(float)opacity
{
if(opacity != 1.f) {
self.opaque = NO;
}
[super setOpacity:opacity];
}
- (void)setBackingScaleFactor:(CGFloat)backingScaleFactor
{
switch (self.renderQuality) {
case kIJSVGRenderQualityOptimized: {
backingScaleFactor = (backingScaleFactor * .35f);
break;
}
case kIJSVGRenderQualityLow: {
backingScaleFactor = (backingScaleFactor * .05f);
break;
}
default: {
break;
}
}
[super setBackingScaleFactor:backingScaleFactor];
}
- (CALayer<IJSVGDrawableLayer> *)referencingLayer
{
return [super referencingLayer] ?: self.superlayer;
}
- (void)drawInContext:(CGContextRef)ctx
{
[super drawInContext:ctx];
// nothing to do :(
if(self.gradient == nil) {
return;
}
// perform the draw
CGRect bounds = CGRectZero;
CGAffineTransform transform = CGAffineTransformIdentity;
CALayer<IJSVGDrawableLayer>* layer = (CALayer<IJSVGDrawableLayer>*)self.referencingLayer;
if(self.gradient.units == IJSVGUnitUserSpaceOnUse) {
IJSVGRootLayer* rootNode = (IJSVGRootLayer*)[IJSVGLayer rootLayerForLayer:self];
bounds = [rootNode.viewBox computeValue:CGSizeZero];
transform = [IJSVGLayer userSpaceTransformForLayer:layer];
} else {
bounds = IJSVGLayerGetBoundingBoxBounds(layer);
}
// its possible that this layer is shifted inwards due to a stroke on the
// parent layer
transform = CGAffineTransformConcat(transform, [IJSVGLayer userSpaceTransformForLayer:self]);
[self.gradient drawInContextRef:ctx
bounds:bounds
transform:transform];
}
- (IJSVGTraitedColorStorage*)colors
{
IJSVGTraitedColorStorage* list = [[IJSVGTraitedColorStorage alloc] init];
for(NSColor* color in self.gradient.colors) {
IJSVGTraitedColor* traited = nil;
traited = [IJSVGTraitedColor colorWithColor:color
traits:IJSVGColorUsageTraitNone];
[list addColor:traited];
}
return list;
}
@end
@@ -0,0 +1,23 @@
//
// IJSVGGroupLayer.h
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGShapeLayer.h>
#import <IJSVG/IJSVGUnitRect.h>
#import <QuartzCore/QuartzCore.h>
@interface IJSVGGroupLayer : IJSVGLayer <IJSVGMaskingLayer> {
}
@property (nonatomic, strong) IJSVGUnitSize* intrinsicSize;
@property (nonatomic, strong) IJSVGUnitRect* viewBox;
@property (nonatomic, assign) IJSVGViewBoxAlignment viewBoxAlignment;
@property (nonatomic, assign) IJSVGViewBoxMeetOrSlice viewBoxMeetOrSlice;
@end
@@ -0,0 +1,21 @@
//
// IJSVGGroupLayer.m
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroupLayer.h>
#import <IJSVG/IJSVGViewBox.h>
#import <IJSVG/IJSVGUnitRect.h>
#import <IJSVG/IJSVGLayer.h>
@implementation IJSVGGroupLayer
- (CGRect)innerBoundingBox
{
return self.outerBoundingBox;
}
@end
@@ -0,0 +1,24 @@
//
// IJSVGImageLayer.h
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGImage.h>
#import <IJSVG/IJSVGTransformLayer.h>
#import <IJSVG/IJSVGTileLayer.h>
#import <IJSVG/IJSVGBasicLayer.h>
#import <AppKit/AppKit.h>
#import <QuartzCore/QuartzCore.h>
@interface IJSVGImageLayer : IJSVGTileLayer {
}
@property (nonatomic, strong) IJSVGImage* image;
- (id)initWithImage:(IJSVGImage*)image;
@end
@@ -0,0 +1,48 @@
//
// IJSVGImageLayer.m
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGImageLayer.h>
@implementation IJSVGImageLayer
- (id)initWithImage:(IJSVGImage*)image
{
if((self = [super init]) != nil) {
self.image = image;
}
return self;
}
- (BOOL)requiresBackingScale
{
return YES;
}
- (void)setImage:(IJSVGImage *)image
{
_image = image;
[self setNeedsDisplay];
}
- (void)drawInContext:(CGContextRef)ctx
{
CGImageRef image = _image.CGImage;
CGRect imageDrawRect = _image.bounds;
CGRect currentBounds = self.bounds;
IJSVGViewBoxDrawingBlock drawBlock = ^(CGFloat scale[]) {
// image will be upside down, so just translate it back on itself
CGContextConcatCTM(ctx, CGAffineTransformMakeScale(1.f, -1.f));
CGContextTranslateCTM(ctx, 0.f, -CGRectGetHeight(imageDrawRect));
CGContextDrawImage(ctx, imageDrawRect, image);
};
IJSVGContextDrawViewBox(ctx, imageDrawRect, currentBounds,
_image.viewBoxAlignment,
_image.viewBoxMeetOrSlice, drawBlock);
}
@end
@@ -0,0 +1,191 @@
//
// IJSVGLayer.h
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGRendering.h>
#import <IJSVG/IJSVGTransaction.h>
#import <QuartzCore/QuartzCore.h>
@class IJSVGShapeLayer;
@class IJSVGGradientLayer;
@class IJSVGPatternLayer;
@class IJSVGStrokeLayer;
@class IJSVGGroupLayer;
@class IJSVGLayer;
@class IJSVGFilter;
@class IJSVGTraitedColorStorage;
typedef NS_OPTIONS(NSUInteger, IJSVGLayerTraits) {
IJSVGLayerTraitNone = 0,
IJSVGLayerTraitFilled = 1 << 1,
IJSVGLayerTraitStroked = 1 << 2
};
typedef NS_OPTIONS(NSUInteger, IJSVGLayerDrawingOptions) {
IJSVGLayerDrawingOptionNone = 0,
IJSVGLayerDrawingOptionIgnoreClipping = 1 << 1
};
typedef NS_ENUM(NSUInteger, IJSVGLayerFillType) {
IJSVGLayerFillTypeColor,
IJSVGLayerFillTypePattern,
IJSVGLayerFillTypeGradient,
IJSVGLayerFillTypeUnknown
};
typedef NS_ENUM(NSUInteger, IJSVGLayerUsageType) {
IJSVGLayerUsageTypeFillGeneric,
IJSVGLayerUsageTypeFillPattern,
IJSVGLayerUsageTypeFillGradient,
IJSVGLayerUsageTypeStrokeGeneric,
IJSVGLayerUsageTypeStrokePattern,
IJSVGLayerUsageTypeStrokeGradient,
IJSVGLayerUsageTypeStroke
};
@protocol IJSVGPathableLayer <NSObject>
@required
@property (nonatomic, assign) CGPathRef path;
@end
@protocol IJSVGBasicLayer <NSObject>
@required
@property (nonatomic, assign) CGFloat backingScaleFactor;
@property (nonatomic, readonly) BOOL requiresBackingScale;
@property (nonatomic, assign) IJSVGRenderQuality renderQuality;
@property (nonatomic, readonly) NSArray<CALayer<IJSVGBasicLayer>*>* debugLayers;
@end
@protocol IJSVGMaskingLayer <NSObject>
@required
@property (nonatomic, assign) CGRect maskingBoundingBox;
@property (nonatomic, assign) CGRect maskingClippingRect;
@end
@protocol IJSVGClippingLayer <NSObject>
@required
@property (nonatomic, assign) CGRect clippingBoundingBox;
@property (nonatomic, assign) CGAffineTransform clippingTransform;
@property (nonatomic, assign) CGPathRef clipPath;
@property (nonatomic, assign) CGAffineTransform clipPathTransform;
@end
@protocol IJSVGDrawableLayer <NSObject, IJSVGBasicLayer, IJSVGMaskingLayer, IJSVGClippingLayer>
@required
@property (nonatomic, readonly) BOOL treatImplicitOriginAsTransform;
@property (nonatomic, assign) CGBlendMode blendingMode;
@property (nonatomic, strong) NSArray<CALayer<IJSVGDrawableLayer>*>* clipLayers;
@property (nonatomic, strong) CALayer<IJSVGDrawableLayer>* maskLayer;
@property (nonatomic, copy) CAShapeLayerFillRule clipRule;
@property (nonatomic, copy) CAShapeLayerFillRule fillRule;
@property (nonatomic, readonly) CGPoint absoluteOrigin;
@property (nonatomic, readonly) CGRect absoluteFrame;
@property (nonatomic, assign) CGRect boundingBox;
@property (nonatomic, assign) CGRect outerBoundingBox;
@property (nonatomic, readonly) CGRect innerBoundingBox;
@property (nonatomic, weak) CALayer<IJSVGDrawableLayer>* referencingLayer;
@property (nonatomic, strong) IJSVGFilter* filter;
@property (nonatomic, readonly) IJSVGLayerTraits layerTraits;
@property (nonatomic, readonly) NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* layerUsageMapTable;
@property (nonatomic, readonly) IJSVGTraitedColorStorage* colors;
- (void)performRenderInContext:(CGContextRef)ctx;
- (void)setLayer:(CALayer<IJSVGDrawableLayer>*)layer
forUsageType:(IJSVGLayerUsageType)type;
- (CALayer<IJSVGDrawableLayer>*)layerForUsageType:(IJSVGLayerUsageType)type;
- (CALayer<IJSVGDrawableLayer>*)strokeLayer:(IJSVGLayerUsageType*)usageType;
- (void)addTraits:(IJSVGLayerTraits)traits;
- (void)removeTraits:(IJSVGLayerTraits)traits;
- (BOOL)matchesTraits:(IJSVGLayerTraits)traits;
- (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass;
@end
CGRect IJSVGLayerGetBoundingBoxBounds(CALayer<IJSVGDrawableLayer>* drawableLayer);
NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* IJSVGLayerDefaultUsageMapTable(void);
@interface IJSVGLayer : CALayer <IJSVGDrawableLayer, IJSVGMaskingLayer> {
@private
IJSVGLayer* _maskingLayer;
NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* _layerUsageMapTable;
}
+ (IJSVGLayerFillType)fillTypeForFill:(id)fill;
+ (NSArray*)deepestSublayersOfLayer:(CALayer*)layer;
+ (void)recursivelyWalkLayer:(CALayer<IJSVGBasicLayer>*)layer
withBlock:(void (^)(CALayer<IJSVGBasicLayer>* layer, BOOL* stop))block;
+ (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass
fromLayer:(CALayer<IJSVGDrawableLayer>*)layer;
+ (void)setBackingScaleFactor:(CGFloat)scale
renderQuality:(IJSVGRenderQuality)quality
recursivelyToLayer:(CALayer<IJSVGDrawableLayer>*)layer;
- (void)applySublayerMaskToContext:(CGContextRef)context
forSublayer:(IJSVGLayer*)sublayer
withOffset:(CGPoint)offset;
+ (CGImageRef)newMaskImageForLayer:(CALayer<IJSVGDrawableLayer>*)layer
options:(IJSVGLayerDrawingOptions)options
scale:(CGFloat)scale;
+ (CGImageRef)newImageForLayer:(CALayer<IJSVGDrawableLayer>*)layer
options:(IJSVGLayerDrawingOptions)options
colorSpace:(CGColorSpaceRef)colorSpace
bitmapInfo:(uint32_t)bitmapInfo
scale:(CGFloat)scale;
+ (CGImageRef)newImageWithSize:(CGSize)size
drawBlock:(void (^)(CGContextRef context))drawBlock
colorSpace:(CGColorSpaceRef)colorSpace
bitmapInfo:(uint32_t)bitmapInfo
scale:(CGFloat)scale;
+ (void)renderLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)ctx
options:(IJSVGLayerDrawingOptions)options;
+ (void)applyBlendingMode:(CGBlendMode)blendMode
toContext:(CGContextRef)ctx
drawingBlock:(dispatch_block_t)drawingBlock;
+ (void)clipContextWithClipLayers:(NSArray<CALayer<IJSVGDrawableLayer>*>*)clipLayers
toLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)ctx
drawingBlock:(dispatch_block_t)drawingBlock;
+ (void)clipContextWithMask:(CALayer<IJSVGMaskingLayer>*)maskLayer
toLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)context
drawingBlock:(dispatch_block_t)drawingBlock;
+ (CALayer<IJSVGDrawableLayer>*)rootLayerForLayer:(CALayer<IJSVGDrawableLayer>*)layer;
+ (CGAffineTransform)absoluteTransformForLayer:(CALayer*)layer;
+ (CGRect)absoluteFrameForLayer:(CALayer<IJSVGDrawableLayer>*)layer;
+ (CGRect)calculateFrameForSublayers:(NSArray<CALayer<IJSVGDrawableLayer>*>*)layers;
+ (CGAffineTransform)userSpaceTransformForLayer:(CALayer<IJSVGDrawableLayer>*)layer;
+ (void)transformLayer:(CALayer<IJSVGDrawableLayer>*)layer
intoUserSpaceUnitsFrom:(CALayer<IJSVGDrawableLayer>*)fromLayer;
+ (void)logLayer:(CALayer<IJSVGDrawableLayer>*)layer;
@end
@@ -0,0 +1,780 @@
//
// IJSVGLayer.m
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVG.h>
#import <IJSVG/IJSVGGroupLayer.h>
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGShapeLayer.h>
#import <IJSVG/IJSVGTransformLayer.h>
#import <IJSVG/IJSVGRootLayer.h>
CGRect IJSVGLayerGetBoundingBoxBounds(CALayer<IJSVGDrawableLayer>* drawableLayer)
{
return (CGRect) {
.origin = CGPointZero,
.size = drawableLayer.boundingBox.size
};
}
NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* IJSVGLayerDefaultUsageMapTable(void)
{
return [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsWeakMemory
capacity:3];
}
@implementation IJSVGLayer
@synthesize backingScaleFactor = _backingScaleFactor;
@synthesize requiresBackingScale;
@synthesize renderQuality;
@synthesize blendingMode;
@synthesize absoluteOrigin;
@synthesize clipPath = _clipPath;
@synthesize clipRule = _clipRule;
@synthesize clipLayers = _clipLayers;
@synthesize clippingTransform;
@synthesize clippingBoundingBox;
@synthesize maskingClippingRect;
@synthesize clipPathTransform;
@synthesize colors;
@synthesize boundingBox = _boundingBox;
@synthesize layerTraits = _layerTraits;
@synthesize maskingBoundingBox;
@synthesize filter;
@synthesize referencingLayer = _referencingLayer;
@synthesize outerBoundingBox = _outerBoundingBox;
@synthesize maskLayer = _maskLayer;
@synthesize treatImplicitOriginAsTransform;
@synthesize fillRule;
- (void)dealloc
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
}
- (instancetype)init
{
if((self = [super init]) != nil) {
_boundingBox = CGRectNull;
_outerBoundingBox = CGRectNull;
}
return self;
}
- (id<CAAction>)actionForKey:(NSString*)event
{
return nil;
}
- (CAShapeLayerFillRule)fillRule
{
return kCAFillRuleNonZero;
}
- (CAShapeLayerFillRule)clipRule
{
if(_clipRule == nil) {
return self.fillRule;
}
return _clipRule;
}
+ (IJSVGLayerFillType)fillTypeForFill:(id)fill
{
if([fill isKindOfClass:IJSVGColorNode.class]) {
return IJSVGLayerFillTypeColor;
}
if([fill isKindOfClass:IJSVGGradient.class]) {
return IJSVGLayerFillTypeGradient;
}
if([fill isKindOfClass:IJSVGPattern.class]) {
return IJSVGLayerFillTypePattern;
}
return IJSVGLayerFillTypeUnknown;
}
+ (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass
fromLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
for(CALayer<IJSVGDrawableLayer>* sublayer in layer.sublayers) {
if(sublayer.class == aClass) {
return sublayer;
}
}
return nil;
}
+ (void)applyAbsoluteTransformForLayer:(CALayer<IJSVGDrawableLayer>*)layer
toContext:(CGContextRef)ctx
{
CALayer<IJSVGDrawableLayer>* parentLayer = layer;
while((parentLayer = parentLayer.referencingLayer) != nil) {
CGContextConcatCTM(ctx, parentLayer.affineTransform);
// only go up until we find a root layer, at that point, we know
// we can stop looking
if([parentLayer isKindOfClass:IJSVGRootLayer.class] == YES) {
break;
}
}
CGContextConcatCTM(ctx, layer.affineTransform);
}
+ (CGAffineTransform)absoluteTransformForLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
CGAffineTransform identity = CGAffineTransformIdentity;
CALayer<IJSVGDrawableLayer>* parentLayer = layer;
while((parentLayer = parentLayer.referencingLayer) != nil) {
identity = [self absoluteTransformForLayer:parentLayer];
// only go up until we find a root layer, at that point, we know
// we can stop looking
if([parentLayer isKindOfClass:IJSVGRootLayer.class] == YES) {
break;
}
}
return CGAffineTransformConcat(identity, layer.affineTransform);
}
+ (void)transformLayer:(CALayer<IJSVGDrawableLayer>*)layer
intoUserSpaceUnitsFrom:(CALayer<IJSVGDrawableLayer>*)fromLayer
{
CGAffineTransform transform = layer.affineTransform;
CGAffineTransform userSpaceTransform = [IJSVGLayer userSpaceTransformForLayer:fromLayer];
layer.affineTransform = CGAffineTransformConcat(transform, userSpaceTransform);
}
+ (CGAffineTransform)userSpaceTransformForLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
CGRect absolutePosition = layer.outerBoundingBox;
return CGAffineTransformTranslate(CGAffineTransformIdentity,
-CGRectGetMinX(absolutePosition),
-CGRectGetMinY(absolutePosition));
}
+ (CALayer<IJSVGDrawableLayer>*)rootLayerForLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
CALayer<IJSVGDrawableLayer>* parentLayer = (CALayer<IJSVGDrawableLayer>*)layer.referencingLayer;
while([parentLayer isKindOfClass:IJSVGRootLayer.class] == NO &&
parentLayer.referencingLayer != nil) {
parentLayer = (CALayer<IJSVGDrawableLayer>*)parentLayer.referencingLayer;
}
return parentLayer;
}
+ (void)clipContext:(CGContextRef)ctx
path:(CGPathRef)path
rule:(CAShapeLayerFillRule)rule
drawingBlock:(dispatch_block_t)block
{
CGContextSaveGState(ctx);
CGContextAddPath(ctx, path);
if([rule isEqualToString:kCAFillRuleEvenOdd] == YES) {
CGContextEOClip(ctx);
} else {
CGContextClip(ctx);
}
block();
CGContextRestoreGState(ctx);
}
+ (void)performBasicRenderOfLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)ctx
options:(IJSVGLayerDrawingOptions)options
{
dispatch_block_t drawingBlock = ^{
if(layer.clipPath != NULL) {
[self clipContext:ctx
path:layer.clipPath
rule:layer.clipRule
drawingBlock:^{
[layer performRenderInContext:ctx];
}];
return;
}
[layer performRenderInContext:ctx];
};
[self applyBlendingMode:layer.blendingMode
toContext:ctx
drawingBlock:^{
// we need to clip first
IJSVGLayerDrawingOptions opt = IJSVGLayerDrawingOptionIgnoreClipping;
BOOL ignoreClipping = (options & opt) == opt;
if(ignoreClipping == YES || layer.clipLayers == nil) {
drawingBlock();
return;
}
[self clipContextWithClipLayers:layer.clipLayers
toLayer:layer
inContext:ctx
drawingBlock:drawingBlock];
}];
}
+ (void)renderLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)ctx
options:(IJSVGLayerDrawingOptions)options
{
[self performBasicRenderOfLayer:layer
inContext:ctx
options:options];
}
+ (void)applyBlendingMode:(CGBlendMode)blendMode
toContext:(CGContextRef)ctx
drawingBlock:(dispatch_block_t)drawingBlock
{
if(blendMode == kCGBlendModeNormal) {
drawingBlock();
return;
}
CGContextSaveGState(ctx);
CGContextSetBlendMode(ctx, blendMode);
drawingBlock();
CGContextRestoreGState(ctx);
}
///// Shape layers are the only thing we can clip, as they contain a path, however
///// the layer passed to us from a clip could have groups contained with in it that
///// have transforms on them, so simply recursively iterate over them and concat
///// their transforms down to the path and clip at the end with the path.
//+ (void)recursivelyClip:(CALayer<IJSVGDrawableLayer>*)layer
// transform:(CGAffineTransform)transform
// inContext:(CGContextRef)ctx
//{
// if([layer isKindOfClass:IJSVGShapeLayer.class]) {
// [self clipContextWithShapeLayer:(IJSVGShapeLayer*)layer
// transform:transform
// inContext:ctx];
// return;
// }
//
// for(CALayer<IJSVGDrawableLayer>* drawableLayer in layer.sublayers) {
// CGAffineTransform drawTransform = CGAffineTransformConcat(transform,
// drawableLayer.affineTransform);
//
// // If its not a shape layer, just go down the tree until we find one
// if([drawableLayer isKindOfClass:IJSVGShapeLayer.class] == NO) {
// [self recursivelyClip:drawableLayer
// transform:drawTransform
// inContext:ctx];
// continue;
// }
// IJSVGShapeLayer* shapeLayer = (IJSVGShapeLayer*)drawableLayer;
// [self clipContextWithShapeLayer:shapeLayer
// transform:transform
// inContext:ctx];
// }
//}
//
//+ (void)clipContextWithShapeLayer:(IJSVGShapeLayer*)shapeLayer
// transform:(CGAffineTransform)transform
// inContext:(CGContextRef)ctx
//{
// CGAffineTransform drawTransform = CGAffineTransformConcat(transform,
// shapeLayer.affineTransform);
// // Shape layers paths are reset back to 0,0 origin, so in order to be
// // correct path, we need to shift it back to where it belongs.
// drawTransform = CGAffineTransformTranslate(transform,
// shapeLayer.frame.origin.x,
// shapeLayer.frame.origin.y);
// CGPathRef path = CGPathCreateCopyByTransformingPath(shapeLayer.path,
// &drawTransform);
// CGContextAddPath(ctx, path);
// CGPathRelease(path);
//}
//
//+ (void)clipContextWithClip:(CALayer<IJSVGDrawableLayer>*)clipLayer
// toLayer:(CALayer<IJSVGDrawableLayer>*)layer
// inContext:(CGContextRef)ctx
// drawingBlock:(dispatch_block_t)drawingBlock
//{
// CGContextSaveGState(ctx);
// [self recursivelyClip:clipLayer
// transform:clipLayer.affineTransform
// inContext:ctx];
// if([layer.clipRule isEqualToString:kCAFillRuleEvenOdd]) {
// CGContextEOClip(ctx);
// } else {
// CGContextClip(ctx);
// }
// drawingBlock();
// CGContextRestoreGState(ctx);
//}
+ (void)clipContextWithClipLayers:(NSArray<CALayer<IJSVGDrawableLayer>*>*)clipLayers
toLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)ctx
drawingBlock:(dispatch_block_t)drawingBlock
{
CGContextSaveGState(ctx);
const CGFloat maskTolerance = .5f;
const CGFloat scale = layer.backingScaleFactor;
CGRect rect = layer.clippingBoundingBox;
// weed out the no empty clipPaths
if(CGRectEqualToRect(rect, CGRectZero) == YES) {
drawingBlock();
return;
}
CGSize size = CGSizeMake(CGRectGetWidth(rect),
CGRectGetHeight(rect));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
void (^drawBlock)(CGContextRef maskCtx) = ^(CGContextRef maskCtx) {
CGImageRef maskImage = NULL;
for(CALayer<IJSVGDrawableLayer>* clipLayer in clipLayers) {
CGContextSaveGState(maskCtx);
CGRect layerRect = clipLayer.outerBoundingBox;
layerRect = CGRectApplyAffineTransform(layerRect, layer.clippingTransform);
CGImageRef layerMask = [self newMaskImageForLayer:clipLayer
options:IJSVGLayerDrawingOptionIgnoreClipping
scale:scale];
if(maskImage != NULL) {
CGRect maskRect = CGRectInset(rect, -maskTolerance, -maskTolerance);
CGContextClipToMask(maskCtx, maskRect, maskImage);
}
CGContextDrawImage(maskCtx, layerRect, layerMask);
CGImageRelease(layerMask);
if(maskImage != NULL) {
CGImageRelease(maskImage);
}
maskImage = CGBitmapContextCreateImage(maskCtx);
CGContextRestoreGState(maskCtx);
}
CGImageRelease(maskImage);
};
CGImageRef image = [self newImageWithSize:size
drawBlock:drawBlock
colorSpace:colorSpace
bitmapInfo:kCGImageAlphaNone
scale:scale];
// we need to transform the mask rect back based on the inner bounding
// box of the layer, as this could be a group layer that inner box is
// not at 0,0.
CGRect layerRect = layer.innerBoundingBox;
CGAffineTransform transform = CGAffineTransformMakeTranslation(CGRectGetMinX(layerRect),
CGRectGetMinY(layerRect));
rect = CGRectApplyAffineTransform(rect, transform);
CGContextClipToMask(ctx, rect, image);
drawingBlock();
CGImageRelease(image);
CGColorSpaceRelease(colorSpace);
CGContextRestoreGState(ctx);
}
+ (void)clipContextWithMask:(CALayer<IJSVGDrawableLayer>*)maskLayer
toLayer:(CALayer<IJSVGDrawableLayer>*)layer
inContext:(CGContextRef)ctx
drawingBlock:(dispatch_block_t)drawingBlock
{
CGContextSaveGState(ctx);
CGFloat scale = layer.backingScaleFactor;
CGImageRef maskImage = [self newMaskImageForLayer:maskLayer
options:IJSVGLayerDrawingOptionNone
scale:scale];
CGContextClipToRect(ctx, maskLayer.maskingClippingRect);
CGContextClipToMask(ctx, maskLayer.maskingBoundingBox, maskImage);
drawingBlock();
CGImageRelease(maskImage);
CGContextRestoreGState(ctx);
}
+ (CGImageRef)newMaskImageForLayer:(CALayer<IJSVGDrawableLayer>*)layer
options:(IJSVGLayerDrawingOptions)options
scale:(CGFloat)scale
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGImageRef alphaMask = [self newImageForLayer:layer
options:options
colorSpace:colorSpace
bitmapInfo:kCGImageAlphaNone
scale:scale];
// low - high pairs
const CGFloat colors[6] = {
0.f, 11.f,
0.f, 11.f,
0.f, 11.f
};
CGImageRef masked = CGImageCreateWithMaskingColors(alphaMask, colors);
CGImageRelease(alphaMask);
CGColorSpaceRelease(colorSpace);
return masked;
}
+ (CGImageRef)newImageWithSize:(CGSize)size
drawBlock:(void (^)(CGContextRef context))drawBlock
colorSpace:(CGColorSpaceRef)colorSpace
bitmapInfo:(uint32_t)bitmapInfo
scale:(CGFloat)scale
{
CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
ceilf(size.width*scale),
ceilf(size.height*scale),
8, 0, colorSpace, bitmapInfo);
CGContextScaleCTM(offscreenContext, scale, scale);
drawBlock(offscreenContext);
CGImageRef image = CGBitmapContextCreateImage(offscreenContext);
CGContextRelease(offscreenContext);
return image;
}
+ (CGImageRef)newImageForLayer:(CALayer<IJSVGDrawableLayer>*)layer
options:(IJSVGLayerDrawingOptions)options
colorSpace:(CGColorSpaceRef)colorSpace
bitmapInfo:(uint32_t)bitmapInfo
scale:(CGFloat)scale
{
CALayer<IJSVGDrawableLayer>* referenceLayer = layer.referencingLayer ?: layer;
CGRect frame = layer.outerBoundingBox;
CGRect bounds = CGRectApplyAffineTransform(layer.innerBoundingBox,
[self userSpaceTransformForLayer:referenceLayer]);
CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
ceilf(frame.size.width*scale),
ceilf(frame.size.height*scale),
8, 0, colorSpace, bitmapInfo);
CGContextScaleCTM(offscreenContext, scale, scale);
CGContextConcatCTM(offscreenContext, CGAffineTransformMakeTranslation(-bounds.origin.x,
-bounds.origin.y));
[IJSVGLayer renderLayer:layer
inContext:offscreenContext
options:options];
CGImageRef image = CGBitmapContextCreateImage(offscreenContext);
CGContextRelease(offscreenContext);
return image;
}
+ (CGRect)absoluteFrameForLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
return (CGRect) {
.origin = [self absoluteOriginForLayer:layer],
.size = layer.frame.size
};
}
+ (CGPoint)absoluteOriginForLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
CGPoint point = CGPointZero;
CALayer<IJSVGDrawableLayer>* pLayer = layer;
while (pLayer != nil) {
point.x += pLayer.frame.origin.x;
point.y += pLayer.frame.origin.y;
pLayer = pLayer.referencingLayer;
}
return point;
}
+ (NSArray*)deepestSublayersOfLayer:(CALayer*)layer
{
NSMutableArray* arr = [[NSMutableArray alloc] init];
for (CALayer* subLayer in layer.sublayers) {
if(subLayer.sublayers.count != 0) {
NSArray* moreLayers = [self deepestSublayersOfLayer:(IJSVGLayer*)subLayer];
[arr addObjectsFromArray:moreLayers];
} else {
[arr addObject:subLayer];
}
}
return arr;
}
+ (void)recursivelyWalkLayer:(CALayer<IJSVGBasicLayer>*)layer
withBlock:(void (^)(CALayer<IJSVGBasicLayer>* layer, BOOL* stop))block
{
// call for layer and mask if there is one
BOOL stop = NO;
block(layer, &stop);
if(stop == YES) {
return;
}
// sublayers!!
for (CALayer<IJSVGBasicLayer>* aLayer in layer.sublayers) {
[self recursivelyWalkLayer:aLayer
withBlock:block];
}
}
+ (void)setBackingScaleFactor:(CGFloat)scale
renderQuality:(IJSVGRenderQuality)quality
recursivelyToLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
[self recursivelyWalkLayer:layer
withBlock:^(CALayer<IJSVGDrawableLayer>*sublayer, BOOL *stop) {
if(sublayer.requiresBackingScale == YES) {
sublayer.backingScaleFactor = scale;
}
sublayer.renderQuality = quality;
}];
}
+ (void)logLayer:(CALayer<IJSVGDrawableLayer>*)layer
{
[self logLayer:layer
depth:0];
}
+ (void)logLayer:(CALayer<IJSVGDrawableLayer>*)layer
depth:(NSUInteger)depth
{
NSLog(@"%@ %@ frame: %@ transform: %@",[@"" stringByPaddingToLength:depth
withString:@"- - "
startingAtIndex:0], layer,
NSStringFromRect(layer.frame),
[IJSVGTransform affineTransformToSVGTransformComponentString:layer.affineTransform]);
for(CALayer<IJSVGDrawableLayer>* sublayer in layer.debugLayers) {
[self logLayer:sublayer
depth:depth+1];
}
}
+ (CGRect)calculateFrameForSublayers:(NSArray<CALayer<IJSVGDrawableLayer>*>*)layers
{
CGRect rect = CGRectNull;
for(CALayer<IJSVGDrawableLayer>* layer in layers) {
CGRect layerFrame = layer.outerBoundingBox;
// if we are a transform layer, we can just apply its transform
// to its sublayers and keep going down the tree
if([layer isKindOfClass:IJSVGTransformLayer.class] == YES) {
CGRect frame = [self calculateFrameForSublayers:layer.sublayers];
frame = CGRectApplyAffineTransform(frame, layer.affineTransform);
layerFrame = frame;
}
if(CGRectIsNull(rect)) {
rect = layerFrame;
continue;
}
rect = CGRectUnion(rect, layerFrame);
}
return rect;
}
- (void)setBackingScaleFactor:(CGFloat)newFactor
{
if(_backingScaleFactor == newFactor) {
return;
}
_backingScaleFactor = newFactor;
self.contentsScale = newFactor;
self.rasterizationScale = newFactor;
// make sure its applied to any mask or clipPath
_maskLayer.backingScaleFactor = newFactor;
for(CALayer<IJSVGDrawableLayer>* clipLayer in _clipLayers) {
clipLayer.backingScaleFactor = newFactor;
}
}
- (void)performRenderInContext:(CGContextRef)ctx
{
if(_maskLayer != nil) {
[self.class clipContextWithMask:_maskLayer
toLayer:self
inContext:ctx
drawingBlock:^{
[super renderInContext:ctx];
}];
return;
}
[super renderInContext:ctx];
}
- (void)applySublayerMaskToContext:(CGContextRef)context
forSublayer:(IJSVGLayer*)sublayer
withOffset:(CGPoint)offset
{
// apply any transforms needed
CGPoint layerOffset = offset;
CGAffineTransform sublayerTransform = CATransform3DGetAffineTransform(sublayer.transform);
CGContextConcatCTM(context, CGAffineTransformInvert(sublayerTransform));
// walk up the superlayer chain
CALayer* superlayer = self.superlayer;
if(IJSVGIsSVGLayer(superlayer) == YES) {
[(IJSVGLayer*)superlayer applySublayerMaskToContext:context
forSublayer:(IJSVGLayer*)self
withOffset:layerOffset];
}
// grab the masking layer
IJSVGShapeLayer* maskingLayer = [self maskingLayer];
// if its a group we need to get the lowest level children
// and walk up the chain again
if([maskingLayer isKindOfClass:[IJSVGGroupLayer class]]) {
NSArray* subs = [IJSVGLayer deepestSublayersOfLayer:maskingLayer];
for (IJSVGLayer* subLayer in subs) {
[subLayer applySublayerMaskToContext:context
forSublayer:(IJSVGLayer*)self
withOffset:layerOffset];
}
} else if([maskingLayer isKindOfClass:[IJSVGShapeLayer class]]) {
// is a shape, go for it!
CGPathRef maskPath = maskingLayer.path;
CGContextTranslateCTM(context, -layerOffset.x, -layerOffset.y);
CGContextAddPath(context, maskPath);
CGContextClip(context);
CGContextTranslateCTM(context, layerOffset.x, layerOffset.y);
}
CGContextConcatCTM(context, sublayerTransform);
}
- (CGAffineTransform)absoluteTransform
{
return [IJSVGLayer absoluteTransformForLayer:self];
}
- (BOOL)requiresBackingScale
{
return _maskLayer != nil || (_clipLayers != nil && _clipLayers.count != 0);
}
- (IJSVGShapeLayer*)maskingLayer
{
return (IJSVGShapeLayer*)_maskingLayer ?: nil;
}
- (void)renderInContext:(CGContextRef)ctx
{
[IJSVGLayer renderLayer:self
inContext:ctx
options:IJSVGLayerDrawingOptionNone];
}
- (CGRect)absoluteFrame
{
return [self.class absoluteFrameForLayer:self];
}
- (CGRect)boundingBox
{
return CGRectIsNull(_boundingBox) == NO ? _boundingBox : self.frame;
}
- (CGRect)outerBoundingBox
{
return CGRectIsNull(_outerBoundingBox) == NO ? _outerBoundingBox : self.frame;
}
- (CGRect)boundingBoxBounds
{
return (CGRect) {
.origin = CGPointZero,
.size = self.boundingBox.size
};
}
- (CGRect)innerBoundingBox
{
return (CGRect) {
.origin = CGPointZero,
.size = self.outerBoundingBox.size
};
}
- (CALayer<IJSVGDrawableLayer> *)referencingLayer
{
return _referencingLayer ?: self.superlayer;
}
-(NSArray<CALayer<IJSVGDrawableLayer>*>*)debugLayers
{
return self.sublayers;
}
- (void)setClipPath:(CGPathRef)clipPath
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
_clipPath = CGPathRetain(clipPath);
}
- (NSMapTable<NSNumber*,CALayer<IJSVGDrawableLayer>*>*)layerUsageMapTable
{
if(_layerUsageMapTable == nil) {
_layerUsageMapTable = IJSVGLayerDefaultUsageMapTable();
}
return _layerUsageMapTable;
}
- (void)setLayer:(CALayer<IJSVGDrawableLayer>*)layer
forUsageType:(IJSVGLayerUsageType)type
{
[self.layerUsageMapTable setObject:layer
forKey:@(type)];
}
- (CALayer<IJSVGDrawableLayer>*)layerForUsageType:(IJSVGLayerUsageType)type
{
return [self.layerUsageMapTable objectForKey:@(type)];
}
- (CALayer<IJSVGDrawableLayer>*)strokeLayer:(IJSVGLayerUsageType*)usageType
{
CALayer<IJSVGDrawableLayer>* layer = nil;
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGeneric]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGeneric;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGradient]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGradient;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokePattern]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokePattern;
return layer;
}
return nil;
}
- (void)addTraits:(IJSVGLayerTraits)traits
{
_layerTraits |= traits;
}
- (void)removeTraits:(IJSVGLayerTraits)traits
{
_layerTraits = _layerTraits & ~traits;
}
- (BOOL)matchesTraits:(IJSVGLayerTraits)traits
{
return (_layerTraits & traits) == traits;
}
- (BOOL)treatImplicitOriginAsTransform
{
return YES;
}
- (IJSVGTraitedColorStorage*)colors
{
IJSVGTraitedColorStorage* colorList = [[IJSVGTraitedColorStorage alloc] init];
for(CALayer<IJSVGDrawableLayer>* layer in self.sublayers) {
[colorList unionColorStorage:layer.colors];
}
return colorList;
}
- (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass
{
return [self.class firstSublayerOfClass:aClass
fromLayer:self];
}
@end
@@ -0,0 +1,22 @@
//
// IJSVGPatternLayer.h
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGPattern.h>
#import <QuartzCore/QuartzCore.h>
@interface IJSVGPatternLayer : IJSVGLayer
@property (nonatomic, strong) CALayer<IJSVGDrawableLayer>* pattern;
@property (nonatomic, strong) IJSVGPattern* patternNode;
- (void)computeCellSize:(CGSize*)cellSize
viewBox:(CGRect*)viewBox
origin:(CGPoint*)origin;
@end
@@ -0,0 +1,163 @@
//
// IJSVGPatternLayer.m
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGPatternLayer.h>
#import <IJSVG/IJSVGTransform.h>
#import <IJSVG/IJSVGUnitRect.h>
#import <IJSVG/IJSVGViewBox.h>
@interface IJSVGPatternLayer ()
@property (nonatomic, assign, readonly) CGSize cellSize;
@property (nonatomic, assign) CGRect viewBox;
@end
@implementation IJSVGPatternLayer
- (BOOL)requiresBackingScale
{
return YES;
}
- (void)setBackingScaleFactor:(CGFloat)backingScaleFactor
{
[super setBackingScaleFactor:backingScaleFactor];
[IJSVGLayer setBackingScaleFactor:backingScaleFactor
renderQuality:self.renderQuality
recursivelyToLayer:self.pattern];
}
void IJSVGPatternDrawingCallBack(void* info, CGContextRef ctx)
{
// reassign the layer
IJSVGPatternLayer* layer = (__bridge IJSVGPatternLayer*)info;
CGSize size = layer.cellSize;
CGContextSaveGState(ctx);
CGRect rect = CGRectMake(0.f, 0.f, size.width, size.height);
CGContextClipToRect(ctx, rect);
IJSVGViewBoxAlignment alignment = layer.patternNode.viewBoxAlignment;
IJSVGViewBoxMeetOrSlice meetOrSlice = layer.patternNode.viewBoxMeetOrSlice;
CGRect viewBox = layer.viewBox;
IJSVGViewBoxDrawingBlock drawBlock = ^(CGFloat scale[]) {
[IJSVGLayer renderLayer:layer.pattern
inContext:ctx
options:IJSVGLayerDrawingOptionNone];
};
IJSVGContextDrawViewBox(ctx, viewBox, rect, alignment, meetOrSlice,
drawBlock);
CGContextSaveGState(ctx);
};
- (CALayer<IJSVGDrawableLayer>*)referencingLayer
{
return [super referencingLayer] ?: self.superlayer;
}
- (void)computeCellSize:(CGSize*)cellSize
viewBox:(CGRect*)viewBox
origin:(CGPoint*)origin
{
CALayer<IJSVGDrawableLayer>* layer = (CALayer<IJSVGDrawableLayer>*)self.referencingLayer;
CGRect rect = IJSVGLayerGetBoundingBoxBounds(layer);
// get the bounds, we need these as when we render we might need to swap
// the coordinates over to objectBoundingBox
IJSVGUnitLength* xLength = _patternNode.x;
IJSVGUnitLength* yLength = _patternNode.y;
IJSVGUnitLength* wLength = _patternNode.width;
IJSVGUnitLength* hLength = _patternNode.height;
// actually do the swap if required
if(_patternNode.units == IJSVGUnitObjectBoundingBox) {
wLength = wLength.lengthByMatchingPercentage;
hLength = hLength.lengthByMatchingPercentage;
xLength = xLength.lengthByMatchingPercentage;
yLength = yLength.lengthByMatchingPercentage;
}
*origin = CGPointMake([xLength computeValue:rect.size.width],
[yLength computeValue:rect.size.height]);
CGFloat width = [wLength computeValue:rect.size.width];
CGFloat height = [hLength computeValue:rect.size.height];
*cellSize = CGSizeMake(width, height);
// who knew that patterns have viewBoxes? Not me, but here is an implementation
// of it anyway
if(_patternNode.viewBox != nil && _patternNode.viewBox.isZeroRect == NO) {
IJSVGUnitRect* nViewBox = _patternNode.viewBox;
if(_patternNode.contentUnits == IJSVGUnitObjectBoundingBox) {
nViewBox = [nViewBox copyByConvertingToUnitsLengthType:IJSVGUnitLengthTypePercentage];
}
*viewBox = [nViewBox computeValue:rect.size];
} else {
// no viewbox is assigned, so just map it 1:1 with its cellSize
*viewBox = CGRectMake(0.f, 0.f, cellSize->width, cellSize->height);
}
}
- (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);
CALayer<IJSVGDrawableLayer>* layer = (CALayer<IJSVGDrawableLayer>*)self.referencingLayer;
// transform us back into the correct space
CGAffineTransform transform = CGAffineTransformIdentity;
if(_patternNode.units == IJSVGUnitUserSpaceOnUse) {
transform = [IJSVGLayer userSpaceTransformForLayer:layer];
}
CGPoint origin = CGPointZero;
[self computeCellSize:&_cellSize
viewBox:&_viewBox
origin:&origin];
// transform the X and Y shift
transform = CGAffineTransformConcat(transform, IJSVGConcatTransforms(self.patternNode.transforms));
transform = CGAffineTransformTranslate(transform, origin.x, origin.y);
// its possible that this layer is shifted inwards due to a stroke on the
// parent layer
transform = CGAffineTransformConcat(transform, [IJSVGLayer userSpaceTransformForLayer:self]);
// create the pattern
CGRect selfBounds = IJSVGLayerGetBoundingBoxBounds(self);
CGPatternRef ref = CGPatternCreate((void*)self, selfBounds,
transform, _cellSize.width, _cellSize.height,
kCGPatternTilingConstantSpacing,
true, &callbacks);
// set the pattern then release it
CGFloat alpha = 1.f;
CGContextSetFillPattern(ctx, ref, &alpha);
CGPatternRelease(ref);
// fill it
CGContextFillRect(ctx, selfBounds);
}
- (NSArray<CALayer<IJSVGDrawableLayer>*>*)debugLayers
{
return @[self.pattern];
}
- (IJSVGTraitedColorStorage*)colors
{
return _pattern.colors;
}
@end
@@ -0,0 +1,24 @@
//
// IJSVGRootLayer.h
// IJSVG
//
// Created by Curtis Hard on 15/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVG.h>
@interface IJSVGRootLayer : IJSVGGroupLayer {
@private
BOOL _disableBackingScalePropagation;
}
- (void)renderInContext:(CGContextRef)ctx
viewPort:(CGRect)viewPort
backingScale:(CGFloat)backingScale
quality:(IJSVGRenderQuality)quality
ignoreIntrinsicSize:(BOOL)ignoreIntrinsicSize;
@end
@@ -0,0 +1,93 @@
//
// IJSVGRootLayer.m
// IJSVG
//
// Created by Curtis Hard on 15/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGRootLayer.h>
@implementation IJSVGRootLayer
- (void)performRenderInContext:(CGContextRef)ctx
{
if(self.viewBox != nil) {
CGRect viewBox = [self.viewBox computeValue:CGSizeZero];
__weak IJSVGRootLayer* weakSelf = self;
IJSVGViewBoxDrawingBlock drawingBlock = ^(CGFloat scale[]) {
CGContextSaveGState(ctx);
// we have to make sure we set the backing scale factor once
// we know how scale this will be drawn at
CGFloat nScale = MIN(scale[0], scale[1]);
nScale += weakSelf.backingScaleFactor;
weakSelf.backingScaleFactor = nScale;
// perform the actual render now we have computed backing scale
[super performRenderInContext:ctx];
CGContextRestoreGState(ctx);
};
IJSVGContextDrawViewBox(ctx, viewBox, IJSVGLayerGetBoundingBoxBounds(self),
self.viewBoxAlignment,
self.viewBoxMeetOrSlice, drawingBlock);
return;
}
[super performRenderInContext:ctx];
}
- (void)setBackingScaleFactor:(CGFloat)backingScaleFactor
{
// get nearest .5f
backingScaleFactor = MAX(round(backingScaleFactor * 2.f) / 2.f, .5f);
[super setBackingScaleFactor:backingScaleFactor];
if(_disableBackingScalePropagation == YES) {
return;
}
[self propagateBackingScalePropertiesToSublayers];
}
- (void)propagateBackingScalePropertiesToSublayers
{
for(CALayer<IJSVGDrawableLayer>* layer in self.sublayers) {
[IJSVGLayer setBackingScaleFactor:self.backingScaleFactor
renderQuality:self.renderQuality
recursivelyToLayer:layer];
}
}
- (void)renderInContext:(CGContextRef)ctx
viewPort:(CGRect)viewPort
backingScale:(CGFloat)backingScale
quality:(IJSVGRenderQuality)quality
ignoreIntrinsicSize:(BOOL)ignoreIntrinsicSize
{
CGRect frame = viewPort;
IJSVGUnitSize* size = nil;
// The SVG might have an intrinsic size against it, if so, we need to use
// that instead of the viewPort size to make sure we obey the render correctly.
if(ignoreIntrinsicSize == NO && (size = self.intrinsicSize) != nil) {
CGFloat width = 0.f;
CGFloat height = 0.f;
if((width = [size.width computeValue:frame.size.width]) != 0.f) {
frame.size.width = width;
}
if((height = [size.height computeValue:frame.size.height]) != 0.f) {
frame.size.height = height;
}
}
self.frame = frame;
_disableBackingScalePropagation = YES;
self.backingScaleFactor = backingScale;
self.renderQuality = quality;
_disableBackingScalePropagation = NO;
[self renderInContext:ctx];
}
- (BOOL)treatImplicitOriginAsTransform
{
return NO;
}
@end
@@ -0,0 +1,26 @@
//
// IJSVGShapeLayer.h
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGUtils.h>
#import <QuartzCore/QuartzCore.h>
@interface IJSVGShapeLayer : CAShapeLayer <IJSVGDrawableLayer, IJSVGPathableLayer> {
@private
IJSVGLayer* _maskingLayer;
NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* _layerUsageMapTable;
}
@property (nonatomic, assign) IJSVGPrimitivePathType primitiveType;
- (void)applySublayerMaskToContext:(CGContextRef)context
forSublayer:(IJSVGLayer*)sublayer
withOffset:(CGPoint)offset;
@end
@@ -0,0 +1,312 @@
//
// IJSVGShapeLayer.m
// IJSVGExample
//
// Created by Curtis Hard on 07/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroupLayer.h>
#import <IJSVG/IJSVGShapeLayer.h>
#import <IJSVG/IJSVGStrokeLayer.h>
#import <IJSVG/IJSVGGradientLayer.h>
#import <IJSVG/IJSVGTraitedColorStorage.h>
#import <IJSVG/IJSVGPatternLayer.h>
@implementation IJSVGShapeLayer
@synthesize backingScaleFactor = _backingScaleFactor;
@synthesize requiresBackingScale;
@synthesize renderQuality;
@synthesize blendingMode;
@synthesize absoluteOrigin;
@synthesize clipPath = _clipPath;
@synthesize clipRule;
@synthesize clipLayers = _clipLayers;
@synthesize clippingTransform;
@synthesize clippingBoundingBox;
@synthesize maskingClippingRect;
@synthesize clipPathTransform;
@synthesize colors;
@synthesize boundingBox;
@synthesize layerTraits = _layerTraits;
@synthesize maskingBoundingBox;
@synthesize filter;
@synthesize referencingLayer = _referencingLayer;
@synthesize outerBoundingBox;
@synthesize maskLayer = _maskLayer;
@synthesize treatImplicitOriginAsTransform;
- (void)dealloc
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
}
- (id<CAAction>)actionForKey:(NSString*)event
{
return nil;
}
- (CGRect)absoluteFrame
{
return [IJSVGLayer absoluteFrameForLayer:self];
}
- (CGAffineTransform)absoluteTransform
{
return [IJSVGLayer absoluteTransformForLayer:self];
}
- (CALayer<IJSVGDrawableLayer> *)referencingLayer
{
return _referencingLayer ?: self.superlayer;
}
- (CGRect)boundingBoxBounds
{
return (CGRect) {
.origin = CGPointZero,
.size = self.boundingBox.size
};
}
- (CGRect)innerBoundingBox
{
return self.bounds;
}
- (BOOL)requiresBackingScale
{
return _maskLayer != nil || (_clipLayers != nil && _clipLayers.count != 0);
}
- (void)setClipPath:(CGPathRef)clipPath
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
_clipPath = CGPathRetain(clipPath);
}
- (void)setBackingScaleFactor:(CGFloat)newFactor
{
if(_backingScaleFactor == newFactor) {
return;
}
_backingScaleFactor = newFactor;
self.contentsScale = newFactor;
self.rasterizationScale = newFactor;
// make sure its applied to any mask or clipPath
_maskLayer.backingScaleFactor = newFactor;
for(CALayer<IJSVGDrawableLayer>* clipLayer in _clipLayers) {
clipLayer.backingScaleFactor = newFactor;
}
[self setNeedsDisplay];
}
- (void)performRenderInContext:(CGContextRef)ctx
{
if(_maskLayer != nil) {
[IJSVGLayer clipContextWithMask:_maskLayer
toLayer:self
inContext:ctx
drawingBlock:^{
[super renderInContext:ctx];
}];
return;
}
[super renderInContext:ctx];
}
- (void)applySublayerMaskToContext:(CGContextRef)context
forSublayer:(IJSVGLayer*)sublayer
withOffset:(CGPoint)offset
{
// apply any transforms needed
CGPoint layerOffset = offset;
CGAffineTransform sublayerTransform = CATransform3DGetAffineTransform(sublayer.transform);
CGContextConcatCTM(context, CGAffineTransformInvert(sublayerTransform));
// walk up the superlayer chain
CALayer* superlayer = self.superlayer;
if(IJSVGIsSVGLayer(superlayer) == YES) {
[(IJSVGLayer*)superlayer applySublayerMaskToContext:context
forSublayer:(IJSVGLayer*)self
withOffset:layerOffset];
}
// grab the masking layer
IJSVGShapeLayer* maskingLayer = [self maskingLayer];
// if its a group we need to get the lowest level children
// and walk up the chain again
if([maskingLayer isKindOfClass:[IJSVGGroupLayer class]]) {
NSArray* subs = [IJSVGLayer deepestSublayersOfLayer:maskingLayer];
for (IJSVGLayer* subLayer in subs) {
[subLayer applySublayerMaskToContext:context
forSublayer:(IJSVGLayer*)self
withOffset:layerOffset];
}
} else if([maskingLayer isKindOfClass:[IJSVGShapeLayer class]]) {
// is a shape, go for it!
CGPathRef maskPath = maskingLayer.path;
CGContextTranslateCTM(context, -layerOffset.x, -layerOffset.y);
CGContextAddPath(context, maskPath);
CGContextClip(context);
CGContextTranslateCTM(context, layerOffset.x, layerOffset.y);
}
CGContextConcatCTM(context, sublayerTransform);
}
- (IJSVGShapeLayer*)maskingLayer
{
return (IJSVGShapeLayer*)_maskingLayer ?: nil;
}
- (void)renderInContext:(CGContextRef)ctx
{
[IJSVGLayer renderLayer:(IJSVGLayer*)self
inContext:ctx
options:IJSVGLayerDrawingOptionNone];
}
- (NSMapTable<NSNumber*,CALayer<IJSVGDrawableLayer>*>*)layerUsageMapTable
{
if(_layerUsageMapTable == nil) {
_layerUsageMapTable = IJSVGLayerDefaultUsageMapTable();
}
return _layerUsageMapTable;
}
- (void)setLayer:(CALayer<IJSVGDrawableLayer>*)layer
forUsageType:(IJSVGLayerUsageType)type
{
[self.layerUsageMapTable setObject:layer
forKey:@(type)];
}
- (CALayer<IJSVGDrawableLayer>*)layerForUsageType:(IJSVGLayerUsageType)type
{
return [self.layerUsageMapTable objectForKey:@(type)];
}
- (CALayer<IJSVGDrawableLayer>*)strokeLayer:(IJSVGLayerUsageType*)usageType
{
CALayer<IJSVGDrawableLayer>* layer = nil;
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGeneric]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGeneric;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGradient]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGradient;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokePattern]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokePattern;
return layer;
}
return nil;
}
-(NSArray<CALayer<IJSVGDrawableLayer>*>*)debugLayers
{
return self.sublayers;
}
- (BOOL)treatImplicitOriginAsTransform
{
return YES;
}
- (void)addTraits:(IJSVGLayerTraits)traits
{
_layerTraits |= traits;
}
- (void)removeTraits:(IJSVGLayerTraits)traits
{
_layerTraits = _layerTraits & ~traits;
}
- (BOOL)matchesTraits:(IJSVGLayerTraits)traits
{
return (_layerTraits & traits) == traits;
}
- (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass
{
return [IJSVGLayer firstSublayerOfClass:aClass
fromLayer:self];
}
- (IJSVGTraitedColorStorage*)colors
{
IJSVGTraitedColorStorage* list = [[IJSVGTraitedColorStorage alloc] init];
// we have a fill color
if([self matchesTraits:IJSVGLayerTraitFilled] == YES) {
IJSVGShapeLayer* fillLayer = nil;
if((fillLayer = (IJSVGShapeLayer*)[self layerForUsageType:IJSVGLayerUsageTypeFillGeneric]) != nil) {
CGColorRef colorRef = NULL;
if((colorRef = fillLayer.fillColor) != NULL) {
NSColor* nsColor = [NSColor colorWithCGColor:colorRef];
IJSVGTraitedColor* color = [IJSVGTraitedColor colorWithColor:nsColor
traits:IJSVGColorUsageTraitFill];
[list addColor:color];
}
}
// patterns
if((fillLayer = (IJSVGShapeLayer*)[self layerForUsageType:IJSVGLayerUsageTypeFillPattern]) != nil) {
IJSVGTraitedColorStorage* storage = fillLayer.colors;
[storage addTraits:IJSVGColorUsageTraitFill];
[list unionColorStorage:storage];
}
// gradients
if((fillLayer = (IJSVGShapeLayer*)[self layerForUsageType:IJSVGLayerUsageTypeFillGradient]) != nil) {
IJSVGTraitedColorStorage* storage = fillLayer.colors;
[storage addTraits:IJSVGColorUsageTraitGradientStop];
[list unionColorStorage:storage];
}
}
// we have a stroke color
if([self matchesTraits:IJSVGLayerTraitStroked] == YES) {
IJSVGStrokeLayer* strokeLayer = nil;
if((strokeLayer = (IJSVGStrokeLayer*)[self layerForUsageType:IJSVGLayerUsageTypeStrokeGeneric]) != nil) {
CGColorRef colorRef = NULL;
if((colorRef = strokeLayer.strokeColor) != NULL) {
NSColor* nsColor = [NSColor colorWithCGColor:colorRef];
IJSVGTraitedColor* color = [IJSVGTraitedColor colorWithColor:nsColor
traits:IJSVGColorUsageTraitStroke];
[list addColor:color];
}
}
// patterns
if((strokeLayer = (IJSVGStrokeLayer*)[self layerForUsageType:IJSVGLayerUsageTypeStrokePattern]) != nil) {
IJSVGTraitedColorStorage* storage = strokeLayer.colors;
[storage addTraits:IJSVGColorUsageTraitFill];
[list unionColorStorage:storage];
}
// gradients
if((strokeLayer = (IJSVGStrokeLayer*)[self layerForUsageType:IJSVGLayerUsageTypeStrokeGradient]) != nil) {
IJSVGTraitedColorStorage* storage = strokeLayer.colors;
[storage addTraits:IJSVGColorUsageTraitGradientStop];
[list unionColorStorage:storage];
}
}
return list;
}
@end
@@ -6,7 +6,7 @@
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import "IJSVGShapeLayer.h"
#import <IJSVG/IJSVGShapeLayer.h>
@interface IJSVGStrokeLayer : IJSVGShapeLayer
@@ -6,7 +6,7 @@
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import "IJSVGStrokeLayer.h"
#import <IJSVG/IJSVGStrokeLayer.h>
@implementation IJSVGStrokeLayer
@@ -0,0 +1,17 @@
//
// IJSVGTileLayer.h
// IJSVG
//
// Created by Curtis Hard on 29/06/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import <IJSVG/IJSVGLayer.h>
@interface IJSVGTileLayer : CATiledLayer <IJSVGDrawableLayer> {
@private
NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* _layerUsageMapTable;
}
@end
@@ -0,0 +1,150 @@
//
// IJSVGTileLayer.m
// IJSVG
//
// Created by Curtis Hard on 29/06/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import "IJSVGTileLayer.h"
@implementation IJSVGTileLayer
@synthesize clipLayers;
@synthesize backingScaleFactor;
@synthesize renderQuality;
@synthesize requiresBackingScale;
@synthesize maskLayer = _maskLayer;
@synthesize fillRule = _fillRule;
@synthesize clipRule = _clipRule;
@synthesize absoluteFrame;
@synthesize boundingBox;
@synthesize outerBoundingBox;
@synthesize filter = _filter;
@synthesize innerBoundingBox;
@synthesize maskingBoundingBox;
@synthesize maskingClippingRect;
@synthesize clippingBoundingBox;
@synthesize clippingTransform;
@synthesize layerTraits = _layerTraits;
@synthesize clipPath = _clipPath;
@synthesize clipPathTransform;
@synthesize referencingLayer = _referencingLayer;
@synthesize absoluteOrigin;
@synthesize blendingMode;
@synthesize colors;
- (void)dealloc
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
}
- (id<CAAction>)actionForKey:(NSString*)event
{
return nil;
}
- (CALayer<IJSVGDrawableLayer> *)referencedLayer
{
return self.sublayers.firstObject;
}
- (CALayer<IJSVGDrawableLayer> *)referencingLayer
{
return _referencingLayer ?: self.superlayer;
}
- (void)renderInContext:(CGContextRef)ctx
{
[IJSVGLayer renderLayer:self
inContext:ctx
options:IJSVGLayerDrawingOptionNone];
}
- (void)performRenderInContext:(CGContextRef)ctx
{
[super renderInContext:ctx];
}
- (NSArray<CALayer<IJSVGDrawableLayer>*>*)debugLayers
{
return self.sublayers;
}
- (void)setClipPath:(CGPathRef)clipPath
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
_clipPath = CGPathRetain(clipPath);
}
- (NSMapTable<NSNumber*,CALayer<IJSVGDrawableLayer>*>*)layerUsageMapTable
{
if(_layerUsageMapTable == nil) {
_layerUsageMapTable = IJSVGLayerDefaultUsageMapTable();
}
return _layerUsageMapTable;
}
- (CALayer<IJSVGDrawableLayer>*)layerForUsageType:(IJSVGLayerUsageType)type
{
return [self.layerUsageMapTable objectForKey:@(type)];
}
- (CALayer<IJSVGDrawableLayer>*)strokeLayer:(IJSVGLayerUsageType*)usageType
{
CALayer<IJSVGDrawableLayer>* layer = nil;
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGeneric]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGeneric;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGradient]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGradient;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokePattern]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokePattern;
return layer;
}
return nil;
}
- (void)setLayer:(CALayer<IJSVGDrawableLayer>*)layer
forUsageType:(IJSVGLayerUsageType)type
{
[self.layerUsageMapTable setObject:layer
forKey:@(type)];
}
- (void)addTraits:(IJSVGLayerTraits)traits
{
_layerTraits |= traits;
}
- (void)removeTraits:(IJSVGLayerTraits)traits
{
_layerTraits = _layerTraits & ~traits;
}
- (BOOL)matchesTraits:(IJSVGLayerTraits)traits
{
return (_layerTraits & traits) == traits;
}
- (BOOL)treatImplicitOriginAsTransform
{
return YES;
}
- (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass
{
return [IJSVGLayer firstSublayerOfClass:aClass
fromLayer:self];
}
@end
@@ -0,0 +1,17 @@
//
// IJSVGTransformLayer.h
// IJSVG
//
// Created by Curtis Hard on 31/03/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import <IJSVG/IJSVGLayer.h>
@interface IJSVGTransformLayer : CATransformLayer <IJSVGDrawableLayer> {
@private
NSMapTable<NSNumber*, CALayer<IJSVGDrawableLayer>*>* _layerUsageMapTable;
}
@end
@@ -0,0 +1,160 @@
//
// IJSVGTransformLayer.m
// IJSVG
//
// Created by Curtis Hard on 31/03/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGTransformLayer.h>
#import <IJSVG/IJSVGTraitedColorStorage.h>
@implementation IJSVGTransformLayer
@synthesize clipLayers;
@synthesize backingScaleFactor;
@synthesize renderQuality;
@synthesize requiresBackingScale;
@synthesize maskLayer = _maskLayer;
@synthesize fillRule = _fillRule;
@synthesize clipRule = _clipRule;
@synthesize absoluteFrame;
@synthesize boundingBox;
@synthesize outerBoundingBox;
@synthesize filter = _filter;
@synthesize innerBoundingBox;
@synthesize maskingBoundingBox;
@synthesize maskingClippingRect;
@synthesize clippingBoundingBox;
@synthesize clippingTransform;
@synthesize layerTraits = _layerTraits;
@synthesize clipPath = _clipPath;
@synthesize clipPathTransform;
@synthesize colors;
@synthesize absoluteOrigin;
@synthesize blendingMode;
@synthesize referencingLayer = _referencingLayer;
- (void)dealloc
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
}
- (id<CAAction>)actionForKey:(NSString*)event
{
return nil;
}
- (CALayer<IJSVGDrawableLayer> *)referencedLayer
{
return self.sublayers.firstObject;
}
- (CALayer<IJSVGDrawableLayer> *)referencingLayer
{
return _referencingLayer ?: self.superlayer;
}
- (void)renderInContext:(CGContextRef)ctx
{
[IJSVGLayer renderLayer:self
inContext:ctx
options:IJSVGLayerDrawingOptionNone];
}
- (void)performRenderInContext:(CGContextRef)ctx
{
[super renderInContext:ctx];
}
- (NSArray<CALayer<IJSVGDrawableLayer>*>*)debugLayers
{
return self.sublayers;
}
- (void)setClipPath:(CGPathRef)clipPath
{
if(_clipPath != NULL) {
CGPathRelease(_clipPath);
}
_clipPath = CGPathRetain(clipPath);
}
- (NSMapTable<NSNumber*,CALayer<IJSVGDrawableLayer>*>*)layerUsageMapTable
{
if(_layerUsageMapTable == nil) {
_layerUsageMapTable = IJSVGLayerDefaultUsageMapTable();
}
return _layerUsageMapTable;
}
- (CALayer<IJSVGDrawableLayer>*)layerForUsageType:(IJSVGLayerUsageType)type
{
return [self.layerUsageMapTable objectForKey:@(type)];
}
- (CALayer<IJSVGDrawableLayer>*)strokeLayer:(IJSVGLayerUsageType*)usageType
{
CALayer<IJSVGDrawableLayer>* layer = nil;
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGeneric]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGeneric;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokeGradient]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokeGradient;
return layer;
}
if((layer = [self layerForUsageType:IJSVGLayerUsageTypeStrokePattern]) != nil) {
*usageType = IJSVGLayerUsageTypeStrokePattern;
return layer;
}
return nil;
}
- (void)setLayer:(CALayer<IJSVGDrawableLayer>*)layer
forUsageType:(IJSVGLayerUsageType)type
{
[self.layerUsageMapTable setObject:layer
forKey:@(type)];
}
- (void)addTraits:(IJSVGLayerTraits)traits
{
_layerTraits |= traits;
}
- (void)removeTraits:(IJSVGLayerTraits)traits
{
_layerTraits = _layerTraits & ~traits;
}
- (BOOL)matchesTraits:(IJSVGLayerTraits)traits
{
return (_layerTraits & traits) == traits;
}
- (BOOL)treatImplicitOriginAsTransform
{
return YES;
}
- (CALayer<IJSVGDrawableLayer>*)firstSublayerOfClass:(Class)aClass
{
return [IJSVGLayer firstSublayerOfClass:aClass
fromLayer:self];
}
- (IJSVGTraitedColorStorage*)colors
{
IJSVGTraitedColorStorage* colorList = [[IJSVGTraitedColorStorage alloc] init];
for(CALayer<IJSVGDrawableLayer>* layer in self.sublayers) {
[colorList unionColorStorage:layer.colors];
}
return colorList;
}
@end
@@ -0,0 +1,13 @@
//
// IJSVGFilterEffectGaussianBlur.h
// IJSVG
//
// Created by Curtis Hard on 18/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGFilterEffect.h>
@interface IJSVGFilterEffectGaussianBlur : IJSVGFilterEffect
@end
@@ -0,0 +1,36 @@
//
// IJSVGFilterEffectGaussianBlur.m
// IJSVG
//
// Created by Curtis Hard on 18/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGFilterEffectGaussianBlur.h>
#import <IJSVG/IJSVGThreadManager.h>
@implementation IJSVGFilterEffectGaussianBlur
+ (CIFilter*)sharedFilter
{
IJSVGThreadManager* manager = IJSVGThreadManager.currentManager;
NSString* key = @"CIFilterGaussianBlur";
CIFilter* filter = nil;
if((filter = [manager userInfoObjectForKey:key]) == nil) {
filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[manager setUserInfoObject:filter
forKey:key];
}
return filter;
}
- (CIImage*)processImage:(CIImage*)image
{
CIFilter* filter = [self.class sharedFilter];
[filter setDefaults];
[filter setValue:image forKey:kCIInputImageKey];
[filter setValue:@(self.stdDeviation.value) forKey:kCIInputRadiusKey];
return [filter valueForKey:kCIOutputImageKey];
}
@end
@@ -0,0 +1,16 @@
//
// IJSVGClipPath.h
// IJSVG
//
// Created by Curtis Hard on 29/05/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroup.h>
@interface IJSVGClipPath : IJSVGGroup {
}
@property (nonatomic, readonly) IJSVGWindingRule computedClipRule;
@end
@@ -0,0 +1,88 @@
//
// IJSVGClipPath.m
// IJSVG
//
// Created by Curtis Hard on 29/05/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGClipPath.h>
#import <IJSVG/IJSVGRootNode.h>
@implementation IJSVGClipPath
+ (IJSVGBitFlags*)allowedAttributes
{
IJSVGBitFlags64* storage = [[IJSVGBitFlags64 alloc] init];
[storage addBits:[super allowedAttributes]];
[storage setBit:IJSVGNodeAttributeX];
[storage setBit:IJSVGNodeAttributeY];
[storage setBit:IJSVGNodeAttributeWidth];
[storage setBit:IJSVGNodeAttributeHeight];
[storage setBit:IJSVGNodeAttributeClipPathUnits];
[storage setBit:IJSVGNodeAttributeClipRule];
return storage;
}
- (void)setDefaults
{
self.units = IJSVGUnitObjectBoundingBox;
self.contentUnits = IJSVGUnitUserSpaceOnUse;
self.windingRule = IJSVGWindingRuleNonZero;
self.overflowVisibility = IJSVGOverflowVisibilityHidden;
self.fill = [IJSVGColorNode colorNodeWithColor:NSColor.whiteColor];
}
- (IJSVGUnitType)contentUnitsWithReferencingNodeBounds:(CGRect*)bounds
{
IJSVGNode* node = nil;
IJSVGUnitType units = [self contentUnitsWithReferencingNode:&node];
if(units == IJSVGUnitUserSpaceOnUse) {
*bounds = node.rootNode.bounds;
} else {
*bounds = node.parentNode.bounds;
}
return units;
}
- (void)postProcess
{
// clip paths only allow shapes in them, nothing else, we can simply
// check the node types for the trait of pathed
IJSVGNodeTraits childTraits = IJSVGNodeTraitPathed;
for(IJSVGNode* childNode in self.children.copy) {
if([childNode matchesTraits:childTraits] == NO) {
BOOL remove = YES;
IJSVGNodeType type = childNode.type;
if(type == IJSVGNodeTypeUse) {
remove = [(IJSVGGroup*)childNode childrenMatchTraits:childTraits] == NO;
}
if(remove == YES) {
[self removeChild:childNode];
}
}
}
}
- (IJSVGWindingRule)computedClipRule
{
// find the first use of a clipRule that is useful
__block IJSVGWindingRule rule = IJSVGWindingRuleInherit;
__weak IJSVGClipPath* weakSelf = self;
IJSVGNodeWalkHandler handler = ^(IJSVGNode *node, BOOL *allowChildNodes,
BOOL *stop) {
if(node == weakSelf) {
return;
}
IJSVGWindingRule clipRule = node.clipRule;
if(clipRule != IJSVGWindingRuleInherit) {
rule = clipRule;
*stop = YES;
}
};
[IJSVGNode walkNodeTree:self
handler:handler];
return rule;
}
@end
@@ -0,0 +1,22 @@
//
// IJSVGColorNode.h
// IJSVG
//
// Created by Curtis Hard on 29/03/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGNode.h>
@interface IJSVGColorNode : IJSVGNode {
}
@property (nonatomic, strong) NSColor* color;
@property (nonatomic, assign) BOOL isNoneOrTransparent;
+ (IJSVGNode*)colorNodeWithColor:(NSColor*)color;
- (id)initWithColor:(NSColor*)color;
@end
@@ -0,0 +1,33 @@
//
// IJSVGColorNode.m
// IJSVG
//
// Created by Curtis Hard on 29/03/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGColorNode.h>
@implementation IJSVGColorNode
+ (IJSVGNode*)colorNodeWithColor:(NSColor *)color
{
return [[self alloc] initWithColor:color];
}
- (id)initWithColor:(NSColor*)color {
if((self = [super init]) != nil) {
[self addTraits:IJSVGNodeTraitPaintable];
self.color = color;
}
return self;
}
- (void)applyPropertiesFromNode:(IJSVGNode*)node
{
if([node isKindOfClass:self.class]) {
self.color = ((IJSVGColorNode*)node).color;
}
}
@end
@@ -0,0 +1,19 @@
//
// IJSVGFilter.h
// IJSVG
//
// Created by Curtis Hard on 18/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroup.h>
#import <IJSVG/IJSVGLayer.h>
@interface IJSVGFilter : IJSVGGroup
- (CGImageRef)newImageByApplyFilterToLayer:(CALayer<IJSVGDrawableLayer>*)layer
scale:(CGFloat)scale;
@property (nonatomic, readonly) BOOL valid;
@end
@@ -0,0 +1,57 @@
//
// IJSVGFilter.m
// IJSVG
//
// Created by Curtis Hard on 18/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGFilter.h>
#import <IJSVG/IJSVGFilterEffect.h>
#import <IJSVG/IJSVGLayer.h>
#import <IJSVG/IJSVGThreadManager.h>
@implementation IJSVGFilter
- (BOOL)valid
{
return self.children.count != 0;
}
- (CGImageRef)newImageByApplyFilterToLayer:(CALayer<IJSVGDrawableLayer>*)layer
scale:(CGFloat)scale
{
IJSVGFilter* filter = layer.filter;
layer.filter = nil;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
uint32_t info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little;
CGImageRef originalImage = [IJSVGLayer newImageForLayer:layer
options:IJSVGLayerDrawingOptionNone
colorSpace:colorSpace
bitmapInfo:info
scale:scale];
CIImage* image = [CIImage imageWithCGImage:originalImage];
CGColorSpaceRelease(colorSpace);
CGImageRelease(originalImage);
for(IJSVGFilterEffect* effect in self.children) {
image = [effect processImage:image];
}
IJSVGThreadManager* manager = IJSVGThreadManager.currentManager;
CIContext* context = manager.CIContext;
CGImageRef outputImage = [context createCGImage:image
fromRect:image.extent];
layer.filter = filter;
return outputImage;
}
- (void)addChild:(IJSVGNode*)child
{
if([child isKindOfClass:IJSVGFilterEffect.class] == NO) {
return;
}
[super addChild:child];
}
@end
@@ -0,0 +1,40 @@
//
// IJSVGFilterEffect.h
// IJSVG
//
// Created by Curtis Hard on 18/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroup.h>
typedef NS_ENUM(NSInteger, IJSVGFilterEffectSource) {
IJSVGFilterEffectSourceGraphic,
IJSVGFilterEffectSourceAlpha,
IJSVGFilterEffectSourceBackgroundImage,
IJSVGFilterEffectSourceBackgroundAlpha,
IJSVGFilterEffectSourceFillPaint,
IJSVGFilterEffectSourceStrokePaint,
IJSVGFilterEffectSourcePrimitiveReference,
};
typedef NS_ENUM(NSInteger, IJSVGFilterEffectEdgeMode) {
IJSVGFilterEffectEdgeModeNone,
IJSVGFilterEffectEdgeModeWrap,
IJSVGFilterEffectEdgeModeDuplicate,
};
@interface IJSVGFilterEffect : IJSVGGroup
@property (nonatomic, assign) IJSVGFilterEffectSource source;
@property (nonatomic, strong) IJSVGUnitLength* stdDeviation;
@property (nonatomic, assign) IJSVGFilterEffectEdgeMode edgeMode;
@property (nonatomic, copy) NSString* primitiveReference;
+ (Class)effectClassForElementName:(NSString*)name;
+ (IJSVGFilterEffectSource)sourceForString:(NSString*)string;
+ (IJSVGFilterEffectEdgeMode)edgeModeForString:(NSString*)string;
- (CIImage*)processImage:(CIImage*)image;
@end
@@ -0,0 +1,80 @@
//
// IJSVGFilterEffect.m
// IJSVG
//
// Created by Curtis Hard on 18/04/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGFilterEffect.h>
#import <IJSVG/IJSVGFilterEffectGaussianBlur.h>
#import <IJSVG/IJSVGUtils.h>
@implementation IJSVGFilterEffect
static NSDictionary<NSString*, Class>* _elementClassMap = nil;
+ (void)load
{
_elementClassMap = @{
@"fegaussianblur": IJSVGFilterEffectGaussianBlur.class
};
}
+ (Class)effectClassForElementName:(NSString*)name
{
NSString* key = name.lowercaseString;
return _elementClassMap[key] ?: IJSVGFilterEffect.class;
}
+ (IJSVGFilterEffectSource)sourceForString:(NSString*)string
{
const char* name = string.UTF8String;
if(name == NULL) {
return IJSVGFilterEffectSourceGraphic;
}
if(IJSVGCharBufferCaseInsensitiveCompare(name, "sourcegraphic") == YES) {
return IJSVGFilterEffectSourceGraphic;
}
if(IJSVGCharBufferCaseInsensitiveCompare(name, "sourcealpha") == YES) {
return IJSVGFilterEffectSourceAlpha;
}
if(IJSVGCharBufferCaseInsensitiveCompare(name, "backgroundimage") == YES) {
return IJSVGFilterEffectSourceBackgroundImage;
}
if(IJSVGCharBufferCaseInsensitiveCompare(name, "backgroundalpha") == YES) {
return IJSVGFilterEffectSourceBackgroundAlpha;
}
if(IJSVGCharBufferCaseInsensitiveCompare(name, "fillpaint") == YES) {
return IJSVGFilterEffectSourceFillPaint;
}
if(IJSVGCharBufferCaseInsensitiveCompare(name, "strokepain") == YES) {
return IJSVGFilterEffectSourceStrokePaint;
}
return IJSVGFilterEffectSourcePrimitiveReference;
}
+ (IJSVGFilterEffectEdgeMode)edgeModeForString:(NSString*)string
{
const char* name = string.lowercaseString.UTF8String;
if(name == NULL) {
return IJSVGFilterEffectEdgeModeNone;
}
if(IJSVGCharBufferCompare(name, "none") == YES) {
return IJSVGFilterEffectEdgeModeNone;
}
if(IJSVGCharBufferCompare(name, "wrap") == YES) {
return IJSVGFilterEffectEdgeModeWrap;
}
if(IJSVGCharBufferCompare(name, "duplicate") == YES) {
return IJSVGFilterEffectEdgeModeDuplicate;
}
return IJSVGFilterEffectEdgeModeNone;
}
- (CIImage*)processImage:(CIImage*)image
{
return image;
}
@end
@@ -7,14 +7,11 @@
//
#import <Foundation/Foundation.h>
#import "IJSVGNode.h"
#import <IJSVG/IJSVGNode.h>
@interface IJSVGForeignObject : IJSVGNode {
NSString * requiredExtension;
}
@property ( nonatomic, copy ) NSString * requiredExtension;
@property (nonatomic, copy) NSString* requiredExtension;
@end
@@ -6,16 +6,8 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import "IJSVGForeignObject.h"
#import <IJSVG/IJSVGForeignObject.h>
@implementation IJSVGForeignObject
@synthesize requiredExtension;
- (void)dealloc
{
[requiredExtension release], requiredExtension = nil;
[super dealloc];
}
@end
@@ -0,0 +1,33 @@
//
// IJSVGGradient.h
// IJSVGExample
//
// Created by Curtis Hard on 03/09/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <IJSVG/IJSVGTraitedColorStorage.h>
#import <IJSVG/IJSVGTransform.h>
#import <IJSVG/IJSVGGroup.h>
@interface IJSVGGradient : IJSVGGroup
@property (nonatomic, strong) NSArray<NSColor*>* colors;
@property (nonatomic, assign) CGFloat* locations;
@property (nonatomic, assign) NSUInteger numberOfStops;
@property (nonatomic, assign) CGGradientRef CGGradient;
@property (nonatomic, strong) IJSVGUnitLength* x1;
@property (nonatomic, strong) IJSVGUnitLength* x2;
@property (nonatomic, strong) IJSVGUnitLength* y1;
@property (nonatomic, strong) IJSVGUnitLength* y2;
+ (CGFloat*)computeColorStops:(IJSVGGradient*)gradient
colors:(NSArray**)someColors;
- (CGGradientRef)CGGradient;
- (void)drawInContextRef:(CGContextRef)ctx
bounds:(NSRect)objectRect
transform:(CGAffineTransform)absoluteTransform;
@end
@@ -0,0 +1,114 @@
//
// IJSVGGradient.m
// IJSVGExample
//
// Created by Curtis Hard on 03/09/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGradient.h>
#import <IJSVG/IJSVGParser.h>
@implementation IJSVGGradient
+ (IJSVGBitFlags*)allowedAttributes
{
IJSVGBitFlags64* storage = [[IJSVGBitFlags64 alloc] init];
[storage addBits:[super allowedAttributes]];
[storage setBit:IJSVGNodeAttributeGradientUnits];
[storage setBit:IJSVGNodeAttributeGradientTransform];
return storage;
}
- (void)dealloc
{
if(_locations != NULL) {
(void)free(_locations), _locations = NULL;
}
if(_CGGradient != NULL) {
(void)CGGradientRelease(_CGGradient), _CGGradient = NULL;
}
}
- (id)copyWithZone:(NSZone*)zone
{
IJSVGGradient* clone = [[self.class alloc] init];
[clone applyPropertiesFromNode:self];
return clone;
}
- (void)applyPropertiesFromNode:(IJSVGGradient*)node
{
[super applyPropertiesFromNode:node];
self.numberOfStops = node.numberOfStops;
self.colors = node.colors.copy;
size_t length = sizeof(CGFloat)*node.numberOfStops;
self.locations = (CGFloat*)malloc(length);
memcpy(self.locations, node.locations, length);
self.x1 = node.x1.copy;
self.x2 = node.x2.copy;
self.y1 = node.y1.copy;
self.y2 = node.y2.copy;
}
- (void)setLocations:(CGFloat*)locations
{
if(_locations != NULL) {
(void)free(_locations), _locations = NULL;
}
_locations = locations;
}
+ (CGFloat*)computeColorStops:(IJSVGGradient*)gradient
colors:(NSArray**)someColors
{
NSArray<IJSVGNode*>* stops = gradient.children;
NSMutableArray* colors = [[NSMutableArray alloc] initWithCapacity:stops.count];
CGFloat* stopsParams = (CGFloat*)malloc(stops.count * sizeof(CGFloat));
NSInteger i = 0;
for(IJSVGNode* stopNode in stops) {
NSColor* color = ((IJSVGColorNode*)(stopNode.fill)).color;
CGFloat opacity = stopNode.fillOpacity.value;
CGFloat offset = stopNode.offset.value;
stopsParams[i++] = offset;
if(color == nil) {
color = [IJSVGColor colorFromHEXInteger:0x000000];
if(opacity != 1.f) {
color = [IJSVGColor changeAlphaOnColor:color
to:opacity];
}
}
[colors addObject:color];
}
*someColors = (NSArray*)colors;
return stopsParams;
}
- (CGGradientRef)CGGradient
{
// store it in the cache
if(_CGGradient != nil) {
return _CGGradient;
}
// actually create the gradient
NSInteger num = self.numberOfStops;
CFMutableArrayRef colors = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)num,
&kCFTypeArrayCallBacks);
for (NSColor* color in _colors) {
CFArrayAppendValue(colors, color.CGColor);
}
CGGradientRef result = CGGradientCreateWithColors(IJSVGColor.defaultColorSpace.CGColorSpace,
colors, _locations);
CFRelease(colors);
return _CGGradient = result;
}
- (void)drawInContextRef:(CGContextRef)ctx
bounds:(NSRect)objectRect
transform:(CGAffineTransform)absoluteTransform
{
}
@end
@@ -0,0 +1,30 @@
//
// IJSVGGroup.h
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGNode.h>
#import <IJSVG/IJSVGPath.h>
#import <Foundation/Foundation.h>
@interface IJSVGGroup : IJSVGNode {
@private
NSMutableArray<IJSVGNode*>* _children;
}
@property (weak, nonatomic, readonly) NSArray<IJSVGNode*>* children;
- (void)addChild:(IJSVGNode*)child;
- (void)addChildren:(NSArray<IJSVGNode*>*)children;
- (void)removeChild:(IJSVGNode*)child;
- (void)removeChildren:(NSArray<IJSVGNode*>*)children;
- (BOOL)childrenMatchTraits:(IJSVGNodeTraits)traits;
- (BOOL)containsNodesMatchingTraits:(IJSVGNodeTraits)traits;
- (NSArray<IJSVGNode*>*)nodesMatchingTraits:(IJSVGNodeTraits)traits;
- (NSSet<IJSVGNode*>*)childrenOfType:(IJSVGNodeType)type;
@end
@@ -0,0 +1,138 @@
//
// IJSVGGroup.m
// IconJar
//
// Created by Curtis Hard on 30/08/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroup.h>
#import <IJSVG/IJSVGImage.h>
@implementation IJSVGGroup
- (id)init
{
if((self = [super init]) != nil) {
_children = [[NSMutableArray alloc] init];
}
return self;
}
+ (IJSVGBitFlags*)allowedAttributes
{
IJSVGBitFlags64* storage = [[IJSVGBitFlags64 alloc] init];
[storage addBits:[super allowedAttributes]];
[storage addBits:[IJSVGPath allowedAttributes]];
[storage addBits:[IJSVGImage allowedAttributes]];
return storage;
}
- (void)prepareFromCopy
{
_children = [[NSMutableArray alloc] init];
}
- (id)copyWithZone:(NSZone*)zone
{
IJSVGGroup* node = [super copyWithZone:zone];
[node prepareFromCopy];
for (__strong IJSVGNode* childNode in _children) {
childNode = childNode.copy;
childNode.parentNode = node;
[node addChild:childNode];
}
return node;
}
- (void)addChild:(IJSVGNode*)child
{
if(child == nil || child == self ||
(child.parentNode == self && [_children containsObject:child])) {
return;
}
child.parentNode = self;
[_children addObject:child];
}
- (void)removeChild:(IJSVGNode*)child
{
if(child.parentNode == self) {
[child detach];
}
[_children removeObject:child];
}
- (void)addChildren:(NSArray<IJSVGNode*>*)children
{
for(IJSVGNode* node in children) {
[self addChild:node];
}
}
- (void)removeChildren:(NSArray<IJSVGNode*>*)children
{
for(IJSVGNode* node in children) {
[self removeChild:node];
}
}
- (BOOL)childrenMatchTraits:(IJSVGNodeTraits)traits
{
for(IJSVGNode* node in _children) {
if([node matchesTraits:traits] == NO) {
return NO;
}
}
return YES;
}
- (NSSet<IJSVGNode*>*)childrenOfType:(IJSVGNodeType)type
{
NSMutableSet<IJSVGNode*>* nodes = [[NSMutableSet alloc] init];
for(IJSVGNode* node in self.children) {
if(node.type == type) {
[nodes addObject:node];
}
}
return nodes;
}
- (BOOL)containsNodesMatchingTraits:(IJSVGNodeTraits)traits
{
return [self.class node:self
containsNodesMatchingTraits:traits];
}
- (NSArray<IJSVGNode*>*)nodesMatchingTraits:(IJSVGNodeTraits)traits
{
return [self.class node:self
nodesMatchingTraits:traits];
}
- (CGRect)bounds
{
CGRect rect = CGRectNull;
for(IJSVGNode* node in self.children) {
if(CGRectIsNull(rect)) {
rect = node.bounds;
} else {
rect = CGRectUnion(rect, node.bounds);
}
}
return rect;
}
- (NSArray<IJSVGNode*>*)children
{
return _children;
}
- (NSString*)description
{
return [NSString stringWithFormat:@"%@ - %@",
[super description], self.children];
}
@end
@@ -6,22 +6,21 @@
// Copyright © 2016 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGNode.h>
#import <Foundation/Foundation.h>
#import "IJSVGNode.h"
@class IJSVGPath;
@interface IJSVGImage : IJSVGNode {
NSImage * image;
CGImageRef CGImage;
IJSVGPath * imagePath;
}
@property (nonatomic, readonly) CGSize intrinsicSize;
@property (nonatomic, readonly) CGRect intrinsicBounds;
@property (nonatomic, strong) NSImage* image;
- (CGImageRef)CGImage;
- (void)drawInContextRef:(CGContextRef)context
path:(IJSVGPath *)path;
- (void)loadFromBase64EncodedString:(NSString *)encodedString;
- (void)loadFromString:(NSString*)encodedString;
- (void)loadFromURL:(NSURL*)aURL;
@end
@@ -0,0 +1,107 @@
//
// IJSVGImage.m
// IJSVGExample
//
// Created by Curtis Hard on 28/05/2016.
// Copyright © 2016 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGImage.h>
#import <IJSVG/IJSVGPath.h>
#import <IJSVG/IJSVGTransform.h>
@implementation IJSVGImage
- (void)dealloc
{
(void)(CGImageRelease(CGImage)), CGImage = nil;
}
+ (IJSVGBitFlags*)allowedAttributes
{
IJSVGBitFlags64* storage = [[IJSVGBitFlags64 alloc] init];
[storage addBits:[super allowedAttributes]];
[storage setBit:IJSVGNodeAttributeX];
[storage setBit:IJSVGNodeAttributeY];
[storage setBit:IJSVGNodeAttributeWidth];
[storage setBit:IJSVGNodeAttributeHeight];
[storage setBit:IJSVGNodeAttributePreserveAspectRatio];
return storage;
}
- (void)setDefaults
{
[super setDefaults];
self.viewBoxAlignment = IJSVGViewBoxAlignmentXMidYMid;
self.viewBoxMeetOrSlice = IJSVGViewBoxMeetOrSliceMeet;
}
- (void)loadFromString:(NSString*)encodedString
{
if([encodedString hasPrefix:@"data:"]) {
encodedString = [encodedString stringByReplacingOccurrencesOfString:@"\\s+"
withString:@""
options:NSRegularExpressionSearch
range:NSMakeRange(0, encodedString.length)];
}
NSURL* url = [NSURL URLWithString:encodedString];
if(url != nil) {
[self loadFromURL:url];
}
}
- (void)loadFromURL:(NSURL*)aURL
{
NSData* data = [NSData dataWithContentsOfURL:aURL];
// no data, just ignore...invalid probably
if(data == nil) {
return;
}
// set the image against the container
NSImage* anImage = [[NSImage alloc] initWithData:data];
[self setImage:anImage];
}
- (void)setImage:(NSImage*)anImage
{
_image = anImage;
_intrinsicSize = (CGSize)_image.size;
if(CGImage != nil) {
CGImageRelease(CGImage);
CGImage = nil;
}
CGRect rect = CGRectMake(0.f, 0.f,
_intrinsicSize.width,
_intrinsicSize.height);
CGImage = [_image CGImageForProposedRect:&rect
context:nil
hints:nil];
CGImageRetain(CGImage);
}
- (CGImageRef)CGImage
{
return CGImage;
}
- (CGRect)intrinsicBounds
{
CGRect rect = CGRectZero;
rect.size.width = _intrinsicSize.width;
rect.size.height = _intrinsicSize.height;
return rect;
}
- (CGRect)bounds
{
return CGRectMake(0.f, 0.f,
self.width.value,
self.height.value);
}
@end
@@ -6,12 +6,12 @@
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGradient.h>
#import <Foundation/Foundation.h>
#import "IJSVGGradient.h"
@interface IJSVGLinearGradient : IJSVGGradient
+ (NSGradient *)parseGradient:(NSXMLElement *)element
gradient:(IJSVGLinearGradient *)aGradient;
+ (void)parseGradient:(NSXMLElement*)element
gradient:(IJSVGLinearGradient*)aGradient;
@end
@@ -0,0 +1,87 @@
//
// IJSVGGradient.m
// IJSVGExample
//
// Created by Curtis Hard on 03/09/2014.
// Copyright (c) 2014 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGLinearGradient.h>
#import <IJSVG/IJSVGUtils.h>
#import <IJSVG/IJSVGParser.h>
@implementation IJSVGLinearGradient
+ (IJSVGBitFlags*)allowedAttributes
{
IJSVGBitFlags64* storage = [[IJSVGBitFlags64 alloc] init];
[storage addBits:[super allowedAttributes]];
[storage setBit:IJSVGNodeAttributeX1];
[storage setBit:IJSVGNodeAttributeX2];
[storage setBit:IJSVGNodeAttributeY1];
[storage setBit:IJSVGNodeAttributeY2];
return storage;
}
+ (void)parseGradient:(NSXMLElement*)element
gradient:(IJSVGLinearGradient*)aGradient
{
// just ask unit for the value
NSString* x1 = ([element attributeForName:IJSVGAttributeX1].stringValue ?: @"0");
NSString* x2 = ([element attributeForName:IJSVGAttributeX2].stringValue ?: @"100%");
NSString* y1 = ([element attributeForName:IJSVGAttributeY1].stringValue ?: @"0");
NSString* y2 = ([element attributeForName:IJSVGAttributeY2].stringValue ?: @"0");
aGradient.x1 = [IJSVGGradientUnitLength unitWithString:x1 fromUnitType:aGradient.units];
aGradient.x2 = [IJSVGGradientUnitLength unitWithString:x2 fromUnitType:aGradient.units];
aGradient.y1 = [IJSVGGradientUnitLength unitWithString:y1 fromUnitType:aGradient.units];
aGradient.y2 = [IJSVGGradientUnitLength unitWithString:y2 fromUnitType:aGradient.units];
// compute the color stops and colours
NSArray* colors = nil;
CGFloat* stopsParams = [self.class computeColorStops:aGradient
colors:&colors];
aGradient.colors = colors;
aGradient.locations = stopsParams;
aGradient.numberOfStops = colors.count;
}
- (void)drawInContextRef:(CGContextRef)ctx
bounds:(NSRect)objectRect
transform:(CGAffineTransform)absoluteTransform
{
BOOL inUserSpace = self.units == IJSVGUnitUserSpaceOnUse;
CGPoint gradientStartPoint = CGPointZero;
CGPoint gradientEndPoint = CGPointZero;
CGRect boundingBox = objectRect;
// make sure we apply the absolute position to
// transform us back into the correct space
CGFloat width = CGRectGetWidth(boundingBox);
CGFloat height = CGRectGetHeight(boundingBox);
if(inUserSpace == YES) {
CGContextConcatCTM(ctx, absoluteTransform);
} else {
width = 1.f;
height = 1.f;
CGContextConcatCTM(ctx, CGAffineTransformMakeScale(boundingBox.size.width,
boundingBox.size.height));
}
gradientStartPoint = CGPointMake([self.x1 computeValue:width],
[self.y1 computeValue:height]);
gradientEndPoint = CGPointMake([self.x2 computeValue:width],
[self.y2 computeValue:height]);
// concat the gradient transform into the context
IJSVGConcatTransformsCTM(ctx, self.transforms);
// draw the gradient
CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
CGContextDrawLinearGradient(ctx, self.CGGradient, gradientStartPoint,
gradientEndPoint, options);
}
@end
@@ -0,0 +1,13 @@
//
// IJSVGMask.h
// IJSVG
//
// Created by Curtis Hard on 28/05/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGGroup.h>
@interface IJSVGMask : IJSVGGroup
@end
@@ -0,0 +1,51 @@
//
// IJSVGMask.m
// IJSVG
//
// Created by Curtis Hard on 28/05/2022.
// Copyright © 2022 Curtis Hard. All rights reserved.
//
#import <IJSVG/IJSVGMask.h>
#import <IJSVG/IJSVGRootNode.h>
@implementation IJSVGMask
+ (IJSVGBitFlags*)allowedAttributes
{
IJSVGBitFlags64* storage = [[IJSVGBitFlags64 alloc] init];
[storage addBits:[super allowedAttributes]];
[storage setBit:IJSVGNodeAttributeX];
[storage setBit:IJSVGNodeAttributeY];
[storage setBit:IJSVGNodeAttributeWidth];
[storage setBit:IJSVGNodeAttributeHeight];
[storage setBit:IJSVGNodeAttributeMaskUnits];
[storage setBit:IJSVGNodeAttributeMaskContentUnits];
return storage;
}
- (void)setDefaults
{
[super setDefaults];
self.x = [IJSVGUnitLength unitWithPercentageFloat:-.2f];
self.y = [IJSVGUnitLength unitWithPercentageFloat:-.2f];
self.width = [IJSVGUnitLength unitWithPercentageFloat:1.2f];
self.height = [IJSVGUnitLength unitWithPercentageFloat:1.2f];
self.units = IJSVGUnitObjectBoundingBox;
self.contentUnits = IJSVGUnitUserSpaceOnUse;
self.overflowVisibility = IJSVGOverflowVisibilityHidden;
}
- (IJSVGUnitType)contentUnitsWithReferencingNodeBounds:(CGRect *)bounds
{
IJSVGNode* node = nil;
IJSVGUnitType units = [self contentUnitsWithReferencingNode:&node];
if(units == IJSVGUnitUserSpaceOnUse) {
*bounds = node.rootNode.bounds;
} else {
*bounds = node.parentNode.bounds;
}
return units;
}
@end

Some files were not shown because too many files have changed in this diff Show More