├── .gitignore ├── .jshintrc ├── CONTRIBUTING ├── COPYRIGHT ├── Gruntfile.js ├── LICENSE.md ├── NOTICE ├── README.md ├── demo ├── circle-outside.html ├── ellipse-outside.html ├── polygon-outside.html ├── reference-box.html └── style.css ├── dist ├── CSSShapesEditor-min.js └── CSSShapesEditor.js ├── package.json ├── src ├── CSSShapesEditor.js ├── CSSUtils.js ├── CircleEditor.js ├── Editor.js ├── EllipseEditor.js ├── PolygonEditor.js ├── ToolBar.js ├── fragments │ ├── end.frag │ └── start.frag ├── main.js └── third-party │ ├── almond │ ├── LICENSE │ ├── README.md │ ├── almond.js │ ├── package.json │ └── shrinktest.sh │ ├── eve │ ├── LICENSE │ ├── README.md │ ├── component.json │ ├── e.html │ ├── eve.js │ └── package.json │ ├── jquery │ └── jquery.min.js │ ├── lodash │ ├── LICENSE.txt │ └── lodash.js │ ├── requirejs │ ├── require.js │ └── text.js │ ├── snap.freetransform │ └── snap.freetransform.js │ ├── snap.plugins │ └── snap.plugins.js │ └── snap │ ├── LICENSE.txt │ ├── snap.js │ └── snap.svg-min.js ├── test ├── SpecRunner.html ├── main.js ├── spec │ ├── CSSShapesEditorSpec.js │ ├── CSSUtilsSpec.js │ ├── CircleEditorSpec.js │ ├── EllipseEditorSpec.js │ ├── PolygonEditorSpec.js │ └── test-files │ │ ├── cssutils-markup.html │ │ └── markup.html └── third-party │ └── jasmine │ ├── MIT.LICENSE │ ├── jasmine-html.js │ ├── jasmine.css │ └── jasmine.js └── testem.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist/CSSShapesEditor.js 4 | dist/css-shapes-editor.js 5 | ./test/.DS_Store 6 | private 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "curly" : true, 4 | "eqeqeq" : true, 5 | "forin" : true, 6 | "immed" : true, 7 | "latedef" : true, 8 | "newcap" : true, 9 | "noarg" : true, 10 | "noempty" : true, 11 | "nonew" : true, 12 | "plusplus" : true, 13 | "regexp" : true, 14 | "undef" : true, 15 | "strict" : true, 16 | "trailing" : false, 17 | 18 | "asi" : false, 19 | "boss" : false, 20 | "debug" : false, 21 | "eqnull" : false, 22 | "es5" : false, 23 | "esnext" : false, 24 | "evil" : false, 25 | "expr" : false, 26 | "funcscope" : false, 27 | "globalstrict" : false, 28 | "iterator" : false, 29 | "lastsemic" : false, 30 | "laxbreak" : false, 31 | "laxcomma" : false, 32 | "loopfunc" : false, 33 | "multistr" : false, 34 | "onecase" : false, 35 | "proto" : false, 36 | "regexdash" : false, 37 | "scripturl" : false, 38 | "smarttabs" : false, 39 | "shadow" : false, 40 | "sub" : false, 41 | "supernew" : false, 42 | "validthis" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : false, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "wsh" : false, 55 | 56 | "nomen" : false, 57 | "onevar" : false, 58 | "passfail" : false, 59 | "white" : false, 60 | 61 | "maxerr" : 100, 62 | "predef" : [ 63 | ], 64 | "indent" : 4, 65 | "globals" : [ 66 | "require", 67 | "define", 68 | "brackets", 69 | "$", 70 | "PathUtils", 71 | "window", 72 | "navigator", 73 | "Mustache" 74 | ] 75 | } -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Contributions to this code are covered by the Adobe contributors license agreement. 2 | 3 | Developers must read, sign and submit the Adobe CLA before contributing to this project: 4 | https://adobe.echosign.com/public/hostedForm?formid=9H27ZS66W2S5CX 5 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // @NAME @VERSION 16 | // 17 | // @DESCRIPTION 18 | // 19 | // build: @DATE 20 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global module, require */ 17 | 18 | module.exports = function (grunt) { 19 | 20 | 'use strict'; 21 | 22 | require('load-grunt-tasks')(grunt); 23 | 24 | grunt.initConfig({ 25 | 26 | pkg: grunt.file.readJSON("package.json"), 27 | 28 | // configurable paths 29 | project: { 30 | out: 'dist/CSSShapesEditor.js', 31 | outmin: 'dist/CSSShapesEditor-min.js' 32 | }, 33 | 34 | banner: grunt.file.read('./COPYRIGHT') 35 | .replace(/@NAME/, '<%= pkg.name %>') 36 | .replace(/@DESCRIPTION/, '<%= pkg.description %>') 37 | .replace(/@VERSION/, '<%= pkg.version %>') 38 | .replace(/@DATE/, grunt.template.today("yyyy-mm-dd")), 39 | 40 | watch: { 41 | files: ['src/{,*/}*.js'], 42 | tasks: ['jshint:src'] 43 | }, 44 | 45 | jshint: { 46 | options: { 47 | jshintrc: '.jshintrc' 48 | }, 49 | src: [ 50 | 'src/*.js', 51 | // 'test/spec/{,*/}*.js' 52 | ] 53 | }, 54 | 55 | testem: { 56 | chrome: { 57 | options: JSON.parse(grunt.file.read('testem.json')) 58 | } 59 | }, 60 | 61 | requirejs: { 62 | compile: { 63 | options: { 64 | baseUrl: 'src/', 65 | mainConfigFile: 'src/main.js', 66 | out: '<%= project.out %>', 67 | name: 'main', 68 | include: ['third-party/almond/almond'], 69 | wrap: { 70 | startFile: 'src/fragments/start.frag', 71 | endFile: 'src/fragments/end.frag' 72 | }, 73 | optimize: 'none' 74 | } 75 | } 76 | }, 77 | 78 | uglify: { 79 | options: { 80 | banner: "<%= banner %>", 81 | report: "min" 82 | }, 83 | dist: { 84 | src: "<%= project.out %>", 85 | dest: "<%= project.outmin %>" 86 | } 87 | }, 88 | 89 | // Used just to concat the copyright banner 90 | concat: { 91 | options: { 92 | banner: "<%= banner %>" 93 | }, 94 | dist: { 95 | dest: "<%= project.out %>", 96 | src: ["<%= project.out %>"] 97 | } 98 | }, 99 | 100 | /* 101 | Usage: 102 | grunt bump:minor 103 | grunt bump:build 104 | grunt bump:major 105 | grunt bump --setversion=2.0.1 106 | 107 | grunt bump-only:patch 108 | */ 109 | bump: { 110 | options: { 111 | files: ['package.json'], 112 | updateConfigs: ['pkg'], 113 | commit: true, 114 | commitMessage: 'Release v%VERSION%', 115 | commitFiles: ['package.json','dist/'], // '-a' for all files 116 | createTag: true, 117 | tagName: 'v%VERSION%', 118 | tagMessage: 'Version %VERSION%', 119 | push: false, 120 | pushTo: 'origin', 121 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe' 122 | } 123 | } 124 | }); 125 | 126 | grunt.registerTask('build', ['jshint:src', 'requirejs', 'concat', 'uglify']); 127 | 128 | grunt.registerTask('test', ['testem:run:chrome']); 129 | 130 | grunt.registerTask('lint', ['jshint:src']); 131 | 132 | grunt.registerTask('release', function (type) { 133 | type = type ? type : 'patch'; // Default release type 134 | grunt.task.run('bump:' + type); // Bump up the version 135 | grunt.task.run('build'); // Build the file 136 | }); 137 | }; 138 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2014 Adobe Systems Incorporated. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | Apache License 16 | Version 2.0, January 2004 17 | http://www.apache.org/licenses/ 18 | 19 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 20 | 21 | 1. Definitions. 22 | 23 | "License" shall mean the terms and conditions for use, reproduction, 24 | and distribution as defined by Sections 1 through 9 of this document. 25 | 26 | "Licensor" shall mean the copyright owner or entity authorized by 27 | the copyright owner that is granting the License. 28 | 29 | "Legal Entity" shall mean the union of the acting entity and all 30 | other entities that control, are controlled by, or are under common 31 | control with that entity. For the purposes of this definition, 32 | "control" means (i) the power, direct or indirect, to cause the 33 | direction or management of such entity, whether by contract or 34 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 35 | outstanding shares, or (iii) beneficial ownership of such entity. 36 | 37 | "You" (or "Your") shall mean an individual or Legal Entity 38 | exercising permissions granted by this License. 39 | 40 | "Source" form shall mean the preferred form for making modifications, 41 | including but not limited to software source code, documentation 42 | source, and configuration files. 43 | 44 | "Object" form shall mean any form resulting from mechanical 45 | transformation or translation of a Source form, including but 46 | not limited to compiled object code, generated documentation, 47 | and conversions to other media types. 48 | 49 | "Work" shall mean the work of authorship, whether in Source or 50 | Object form, made available under the License, as indicated by a 51 | copyright notice that is included in or attached to the work 52 | (an example is provided in the Appendix below). 53 | 54 | "Derivative Works" shall mean any work, whether in Source or Object 55 | form, that is based on (or derived from) the Work and for which the 56 | editorial revisions, annotations, elaborations, or other 57 | modifications 58 | represent, as a whole, an original work of authorship. For the 59 | purposes 60 | of this License, Derivative Works shall not include works that remain 61 | separable from, or merely link (or bind by name) to the interfaces 62 | of, 63 | the Work and Derivative Works thereof. 64 | 65 | "Contribution" shall mean any work of authorship, including 66 | the original version of the Work and any modifications or additions 67 | to that Work or Derivative Works thereof, that is intentionally 68 | submitted to Licensor for inclusion in the Work by the copyright 69 | owner 70 | or by an individual or Legal Entity authorized to submit on behalf of 71 | the copyright owner. For the purposes of this definition, "submitted" 72 | means any form of electronic, verbal, or written communication sent 73 | to the Licensor or its representatives, including but not limited to 74 | communication on electronic mailing lists, source code control 75 | systems, 76 | and issue tracking systems that are managed by, or on behalf of, the 77 | Licensor for the purpose of discussing and improving the Work, but 78 | excluding communication that is conspicuously marked or otherwise 79 | designated in writing by the copyright owner as "Not a Contribution." 80 | 81 | "Contributor" shall mean Licensor and any individual or Legal Entity 82 | on behalf of whom a Contribution has been received by Licensor and 83 | subsequently incorporated within the Work. 84 | 85 | 2. Grant of Copyright License. Subject to the terms and conditions of 86 | this License, each Contributor hereby grants to You a perpetual, 87 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 88 | copyright license to reproduce, prepare Derivative Works of, 89 | publicly display, publicly perform, sublicense, and distribute the 90 | Work and such Derivative Works in Source or Object form. 91 | 92 | 3. Grant of Patent License. Subject to the terms and conditions of 93 | this License, each Contributor hereby grants to You a perpetual, 94 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 95 | (except as stated in this section) patent license to make, have made, 96 | use, offer to sell, sell, import, and otherwise transfer the Work, 97 | where such license applies only to those patent claims licensable 98 | by such Contributor that are necessarily infringed by their 99 | Contribution(s) alone or by combination of their Contribution(s) 100 | with the Work to which such Contribution(s) was submitted. If You 101 | institute patent litigation against any entity (including a 102 | cross-claim or counterclaim in a lawsuit) alleging that the Work 103 | or a Contribution incorporated within the Work constitutes direct 104 | or contributory patent infringement, then any patent licenses 105 | granted to You under this License for that Work shall terminate 106 | as of the date such litigation is filed. 107 | 108 | 4. Redistribution. You may reproduce and distribute copies of the 109 | Work or Derivative Works thereof in any medium, with or without 110 | modifications, and in Source or Object form, provided that You 111 | meet the following conditions: 112 | 113 | (a) You must give any other recipients of the Work or 114 | Derivative Works a copy of this License; and 115 | 116 | (b) You must cause any modified files to carry prominent notices 117 | stating that You changed the files; and 118 | 119 | (c) You must retain, in the Source form of any Derivative Works 120 | that You distribute, all copyright, patent, trademark, and 121 | attribution notices from the Source form of the Work, 122 | excluding those notices that do not pertain to any part of 123 | the Derivative Works; and 124 | 125 | (d) If the Work includes a "NOTICE" text file as part of its 126 | distribution, then any Derivative Works that You distribute must 127 | include a readable copy of the attribution notices contained 128 | within such NOTICE file, excluding those notices that do not 129 | pertain to any part of the Derivative Works, in at least one 130 | of the following places: within a NOTICE text file distributed 131 | as part of the Derivative Works; within the Source form or 132 | documentation, if provided along with the Derivative Works; or, 133 | within a display generated by the Derivative Works, if and 134 | wherever such third-party notices normally appear. The contents 135 | of the NOTICE file are for informational purposes only and 136 | do not modify the License. You may add Your own attribution 137 | notices within Derivative Works that You distribute, alongside 138 | or as an addendum to the NOTICE text from the Work, provided 139 | that such additional attribution notices cannot be construed 140 | as modifying the License. 141 | 142 | You may add Your own copyright statement to Your modifications and 143 | may provide additional or different license terms and conditions 144 | for use, reproduction, or distribution of Your modifications, or 145 | for any such Derivative Works as a whole, provided Your use, 146 | reproduction, and distribution of the Work otherwise complies with 147 | the conditions stated in this License. 148 | 149 | 5. Submission of Contributions. Unless You explicitly state otherwise, 150 | any Contribution intentionally submitted for inclusion in the Work 151 | by You to the Licensor shall be under the terms and conditions of 152 | this License, without any additional terms or conditions. 153 | Notwithstanding the above, nothing herein shall supersede or modify 154 | the terms of any separate license agreement you may have executed 155 | with Licensor regarding such Contributions. 156 | 157 | 6. Trademarks. This License does not grant permission to use the trade 158 | names, trademarks, service marks, or product names of the Licensor, 159 | except as required for reasonable and customary use in describing the 160 | origin of the Work and reproducing the content of the NOTICE file. 161 | 162 | 7. Disclaimer of Warranty. Unless required by applicable law or 163 | agreed to in writing, Licensor provides the Work (and each 164 | Contributor provides its Contributions) on an "AS IS" BASIS, 165 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 166 | implied, including, without limitation, any warranties or conditions 167 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 168 | PARTICULAR PURPOSE. You are solely responsible for determining the 169 | appropriateness of using or redistributing the Work and assume any 170 | risks associated with Your exercise of permissions under this 171 | License. 172 | 173 | 8. Limitation of Liability. In no event and under no legal theory, 174 | whether in tort (including negligence), contract, or otherwise, 175 | unless required by applicable law (such as deliberate and grossly 176 | negligent acts) or agreed to in writing, shall any Contributor be 177 | liable to You for damages, including any direct, indirect, special, 178 | incidental, or consequential damages of any character arising as a 179 | result of this License or out of the use or inability to use the 180 | Work (including but not limited to damages for loss of goodwill, 181 | work stoppage, computer failure or malfunction, or any and all 182 | other commercial damages or losses), even if such Contributor 183 | has been advised of the possibility of such damages. 184 | 185 | 9. Accepting Warranty or Additional Liability. While redistributing 186 | the Work or Derivative Works thereof, You may choose to offer, 187 | and charge a fee for, acceptance of support, warranty, indemnity, 188 | or other liability obligations and/or rights consistent with this 189 | License. However, in accepting such obligations, You may act only 190 | on Your own behalf and on Your sole responsibility, not on behalf 191 | of any other Contributor, and only if You agree to indemnify, 192 | defend, and hold each Contributor harmless for any liability 193 | incurred by, or claims asserted against, such Contributor by reason 194 | of your accepting any such warranty or additional liability. 195 | 196 | END OF TERMS AND CONDITIONS 197 | 198 | APPENDIX: How to apply the Apache License to your work. 199 | 200 | To apply the Apache License to your work, attach the following 201 | boilerplate notice, with the fields enclosed by brackets "[]" 202 | replaced with your own identifying information. (Don't include 203 | the brackets!) The text should be enclosed in the appropriate 204 | comment syntax for the file format. We also recommend that a 205 | file or class name and description of purpose be included on the 206 | same "printed page" as the copyright notice for easier 207 | identification within third-party archives. 208 | 209 | Copyright [yyyy] [name of copyright owner] 210 | 211 | Licensed under the Apache License, Version 2.0 (the "License"); 212 | you may not use this file except in compliance with the License. 213 | You may obtain a copy of the License at 214 | 215 | http://www.apache.org/licenses/LICENSE-2.0 216 | 217 | Unless required by applicable law or agreed to in writing, software 218 | distributed under the License is distributed on an "AS IS" BASIS, 219 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 220 | See the License for the specific language governing permissions and 221 | limitations under the License. 222 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The CSS Shapes Editor library 2 | Copyright 2014 Adobe Systems Incorporated 3 | 4 | This software is licensed under the Apache License, Version 2.0 (see LICENSE file). 5 | 6 | It uses the following third party libraries that may have licenses differing from that of CSS Shapes Editor itself. 7 | 8 | Third Party Software Notices: 9 | ============================= 10 | 11 | almond (https://github.com/jrburke/almond) 12 | almond is released under two licenses: new BSD, and MIT. You may pick the 13 | license that best suits your development needs. The text of both licenses are 14 | provided below. 15 | 16 | The "New" BSD License: 17 | ---------------------- 18 | 19 | Copyright (c) 2010-2011, The Dojo Foundation 20 | All rights reserved. 21 | 22 | Redistribution and use in source and binary forms, with or without 23 | modification, are permitted provided that the following conditions are met: 24 | 25 | * Redistributions of source code must retain the above copyright notice, this 26 | list of conditions and the following disclaimer. 27 | * Redistributions in binary form must reproduce the above copyright notice, 28 | this list of conditions and the following disclaimer in the documentation 29 | and/or other materials provided with the distribution. 30 | * Neither the name of the Dojo Foundation nor the names of its contributors 31 | may be used to endorse or promote products derived from this software 32 | without specific prior written permission. 33 | 34 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 35 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 36 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 38 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 39 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 40 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 42 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 43 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 | 45 | 46 | 47 | MIT License 48 | ----------- 49 | 50 | Copyright (c) 2010-2014, The Dojo Foundation 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | 70 | RequireJS (http://requirejs.org/) 71 | RequireJS is released under two licenses: new BSD, and MIT. You may pick the 72 | license that best suits your development needs. The text of both licenses are 73 | provided below. 74 | 75 | The "New" BSD License: 76 | ---------------------- 77 | 78 | Copyright (c) 2010-2014, The Dojo Foundation 79 | All rights reserved. 80 | 81 | Redistribution and use in source and binary forms, with or without 82 | modification, are permitted provided that the following conditions are met: 83 | 84 | * Redistributions of source code must retain the above copyright notice, this 85 | list of conditions and the following disclaimer. 86 | * Redistributions in binary form must reproduce the above copyright notice, 87 | this list of conditions and the following disclaimer in the documentation 88 | and/or other materials provided with the distribution. 89 | * Neither the name of the Dojo Foundation nor the names of its contributors 90 | may be used to endorse or promote products derived from this software 91 | without specific prior written permission. 92 | 93 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 94 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 95 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 96 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 97 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 98 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 99 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 100 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 101 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 102 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 103 | 104 | 105 | 106 | MIT License 107 | ----------- 108 | 109 | Copyright (c) 2010-2014, The Dojo Foundation 110 | 111 | Permission is hereby granted, free of charge, to any person obtaining a copy 112 | of this software and associated documentation files (the "Software"), to deal 113 | in the Software without restriction, including without limitation the rights 114 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 115 | copies of the Software, and to permit persons to whom the Software is 116 | furnished to do so, subject to the following conditions: 117 | 118 | The above copyright notice and this permission notice shall be included in 119 | all copies or substantial portions of the Software. 120 | 121 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 122 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 123 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 124 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 125 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 126 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 127 | THE SOFTWARE. 128 | 129 | 130 | MIT License 131 | ----------- 132 | 133 | Copyright (c) 2010-2011, The Dojo Foundation 134 | 135 | Permission is hereby granted, free of charge, to any person obtaining a copy 136 | of this software and associated documentation files (the "Software"), to deal 137 | in the Software without restriction, including without limitation the rights 138 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 139 | copies of the Software, and to permit persons to whom the Software is 140 | furnished to do so, subject to the following conditions: 141 | 142 | The above copyright notice and this permission notice shall be included in 143 | all copies or substantial portions of the Software. 144 | 145 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 146 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 147 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 148 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 149 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 150 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 151 | THE SOFTWARE. 152 | 153 | lodash (https://github.com/lodash/lodash/) 154 | Copyright 2012-2013 The Dojo Foundation 155 | Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas, 156 | DocumentCloud and Investigative Reporters & Editors 157 | 158 | Permission is hereby granted, free of charge, to any person obtaining 159 | a copy of this software and associated documentation files (the 160 | "Software"), to deal in the Software without restriction, including 161 | without limitation the rights to use, copy, modify, merge, publish, 162 | distribute, sublicense, and/or sell copies of the Software, and to 163 | permit persons to whom the Software is furnished to do so, subject to 164 | the following conditions: 165 | 166 | The above copyright notice and this permission notice shall be 167 | included in all copies or substantial portions of the Software. 168 | 169 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 170 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 171 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 172 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 173 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 174 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 175 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE 176 | 177 | jasmine (https://github.com/pivotal/jasmine) 178 | Copyright (c) 2008-2014 Pivotal Labs. 179 | This software is licensed under the MIT License. 180 | 181 | Permission is hereby granted, free of charge, to any person obtaining a copy 182 | of this software and associated documentation files (the "Software"), to deal 183 | in the Software without restriction, including without limitation the rights 184 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 185 | copies of the Software, and to permit persons to whom the Software is 186 | furnished to do so, subject to the following conditions: 187 | 188 | The above copyright notice and this permission notice shall be included in 189 | all copies or substantial portions of the Software. 190 | 191 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 192 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 193 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 194 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 195 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 196 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 197 | THE SOFTWARE. 198 | 199 | jQuery (https://jquery.org) 200 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license 201 | 202 | Permission is hereby granted, free of charge, to any person obtaining 203 | a copy of this software and associated documentation files (the 204 | "Software"), to deal in the Software without restriction, including 205 | without limitation the rights to use, copy, modify, merge, publish, 206 | distribute, sublicense, and/or sell copies of the Software, and to 207 | permit persons to whom the Software is furnished to do so, subject to 208 | the following conditions: 209 | 210 | The above copyright notice and this permission notice shall be 211 | included in all copies or substantial portions of the Software. 212 | 213 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 214 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 215 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 216 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 217 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 218 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 219 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE 220 | 221 | Snap.FreeTransform, adapted from Raphaël.FreeTransform (https://github.com/ElbertF/Raphael.FreeTransform) 222 | Copyright (c), Elbert Alias 223 | Permission is hereby granted, free of charge, to any person obtaining a copy 224 | of this software and associated documentation files (the "Software"), to deal 225 | in the Software without restriction, including without limitation the rights 226 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 227 | copies of the Software, and to permit persons to whom the Software is 228 | furnished to do so, subject to the following conditions: 229 | 230 | The above copyright notice and this permission notice shall be included in 231 | all copies or substantial portions of the Software. 232 | 233 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 234 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 235 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 236 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 237 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 238 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 239 | THE SOFTWARE. 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSS Shapes Editor 2 | 3 | JavaScript library for interactive editing of CSS Shapes values right in the browser. It works with functional values like `polygon()`, `circle()` and `ellipse()`. 4 | 5 | ## Demo 6 | 7 | See the `demo/` folder for examples. 8 | 9 | ## Basic usage 10 | 11 | Load `dist/CSSShapesEditor.js` into the page: 12 | 13 | ```js 14 | 15 | ``` 16 | 17 | Setup the editor to edit a CSS shape value of an element. An interactive editor for the shape is drawn on top of the element. 18 | 19 | ```js 20 | var element = document.querySelector('#element'); 21 | var shape = window.getComputedStyle(element)['shape-outside']; 22 | var editor = CSSShapesEditor(element, shape); 23 | 24 | editor.on('shapechange', function(){ 25 | // update the CSS shape value on the element 26 | element.style['shape-outside'] = editor.getCSSValue(); 27 | }) 28 | ``` 29 | 30 | 31 | Supported shape values: 32 | 33 | - `polygon()` 34 | - `circle()` 35 | - `ellipse()` 36 | 37 | Create a new shape from scratch by passing a shape declaration with no coordinates. 38 | 39 | ```js 40 | var editor = CSSShapesEditor(element, 'polygon()'); 41 | ``` 42 | 43 | ## Events 44 | 45 | The `"ready"` event is dispatched after the editor was initialized 46 | 47 | ```js 48 | editor.on('ready', function(){ 49 | // editor is ready to work with 50 | }) 51 | ``` 52 | 53 | The `"shapechange"` event is dispatched after the shape was changed in the editor 54 | 55 | ```js 56 | editor.on('shapechange', function(){ 57 | // update the CSS shape value on the element 58 | element.style['shape-outside'] = editor.getCSSValue(); 59 | }) 60 | ``` 61 | 62 | The `"removed"` event is dispatched after the editor has been turned off and removed by using `editor.remove()`. 63 | 64 | ```js 65 | editor.on('removed', function(){ 66 | // editor is gone; do other clean-up 67 | }) 68 | ``` 69 | 70 | ## API 71 | 72 | Get the CSS shape value as a string to use in a stylesheet: 73 | 74 | ```js 75 | editor.getCSSValue() 76 | ``` 77 | 78 | Get the CSS shape value as a string with coordinates converted to a specific unit type: 79 | 80 | ```js 81 | editor.getCSSValue('%') 82 | // supported values: ["px", "in", "cm", "mm", "pt", "pc", "em", "rem", "vw", "vh", "%"] 83 | 84 | ``` 85 | 86 | Programmatically update the shape editor with a new shape value: 87 | 88 | ```js 89 | editor.update("circle(50% at center)") 90 | ``` 91 | 92 | Toggle the free-transform editor (scale, move, rotate) for the shape: 93 | 94 | ```js 95 | editor.toggleFreeTransform(); 96 | ``` 97 | 98 | Turn off editor and remove if from the page. **Unsaved changes will be lost.** 99 | 100 | ```js 101 | editor.remove() 102 | ``` 103 | 104 | ## Contributing 105 | 106 | Your system needs: 107 | 108 | - [Node.JS](http://nodejs.org/) 109 | - [Grunt](http://gruntjs.com/) 110 | 111 | ### Setup dev environment 112 | 113 | Install dependencies: 114 | 115 | $ npm install 116 | 117 | ### Build 118 | 119 | Edit source in the `src/` directory. Build with Grunt: 120 | 121 | $ grunt build 122 | 123 | Build output goes into `dist/`. Do not edit source in `dist/`, it gets replaced automatically by the Grunt build process. 124 | 125 | ### Test 126 | 127 | Add tests to `test/spec/`. Run tests with [Testem](https://github.com/airportyh/testem): 128 | 129 | $ testem 130 | 131 | Testem uses the configuration found in `testem.json` 132 | 133 | ## License 134 | 135 | Apache 2.0. See [LICENSE.md](./LICENSE.md) 136 | 137 | ## Thanks 138 | 139 | The work of many people has contributed, both directly and indirectly, to building the CSS Shapes Editor library: 140 | 141 | - [Razvan Caliman](https://github.com/oslego) 142 | - [Bear Travis](https://github.com/betravis) 143 | - [François Remy](https://github.com/FremyCompany) 144 | - [Laurence Mclister](https://github.com/lmclister) 145 | - [Hans Muller](https://github.com/hansmuller) 146 | - [Lawrence Hsu](https://github.com/larz0) 147 | - [Dmitry Baranovskiy](https://github.com/DmitryBaranovskiy) for creating [Snap.svg](http://snapsvg.io/) 148 | - [Elbert Alias](https://github.com/elbertf) for creating [Raphael.FreeTransform ](https://github.com/ElbertF/Raphael.FreeTransform) 149 | 150 | and many, many others. Thank you! 151 | -------------------------------------------------------------------------------- /demo/circle-outside.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 26 | 27 | Circle shape-outside 28 | 29 | 30 |
31 | This browser does not appear to support CSS Shapes. Find out how to get one which does. 32 |
33 | 34 |
35 |
36 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

37 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

38 |
39 | 40 | 41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo/ellipse-outside.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 26 | 27 | Ellipse shape-outside 28 | 29 | 30 |
31 | This browser does not appear to support CSS Shapes. Find out how to get one which does. 32 |
33 | 34 |
35 |
36 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

37 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

38 |
39 | 40 | 41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo/polygon-outside.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 26 | 27 | Polygon shape-outside 28 | 29 | 30 |
31 | This browser does not appear to support CSS Shapes. Find out how to get one which does. 32 |
33 | 34 |
35 |
36 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

37 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

38 |
39 | 40 | 41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo/reference-box.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 29 | 30 | Reference box for shape-outside 31 | 32 | 33 |
34 | This browser does not appear to support CSS Shapes. Find out how to get one which does. 35 |
36 | 37 |
38 |
39 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

40 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

41 |
42 | 43 | 44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /demo/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | .support-warning{ 17 | padding: 1em; 18 | background: #fc5; 19 | color: #444; 20 | } 21 | @supports (shape-outside: circle(50% at 50% 50%)){ 22 | .support-warning { 23 | display: none; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-shapes-editor", 3 | "main": "./dist/CSSShapesEditor.js", 4 | "description": "Editor for CSS Shapes in the browser.", 5 | "version": "0.10.0", 6 | "author": "Adobe Systems Inc.", 7 | "contributors": [ 8 | "Razvan Caliman ", 9 | "François REMY ", 10 | "Bear Travis " 11 | ], 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "grunt": "~0.4.1", 15 | "grunt-bump": "0.0.13", 16 | "grunt-contrib-jshint": "~0.6.3", 17 | "grunt-contrib-requirejs": "~0.4.1", 18 | "grunt-contrib-watch": "~0.5.2", 19 | "grunt-contrib-uglify": "^0.5.1", 20 | "grunt-contrib-concat": "^0.5.0", 21 | "grunt-contrib-testem": "^0.5.16", 22 | "testem": "^0.6.15", 23 | "load-grunt-tasks": "~0.2.1" 24 | }, 25 | "engines": { 26 | "node": ">=0.8.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CSSShapesEditor.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define */ 17 | 18 | define(['PolygonEditor', 'CircleEditor', 'EllipseEditor', 'lodash'], function(PolygonEditor, CircleEditor, EllipseEditor, _){ 19 | 20 | 'use strict'; 21 | 22 | function CSSShapesEditor(target, value, options){ 23 | 24 | var _defaults = { 25 | // multiple objects with attributes are used into separate objects for the shape path 26 | path: [ 27 | { 28 | stroke: 'rgba(255, 255, 255, 0.5)', 29 | }, 30 | { 31 | stroke: 'rgba(0, 162, 255, 1)', 32 | 'stroke-dasharray': '4, 5' 33 | } 34 | ], 35 | point: { 36 | radius: 4, 37 | stroke: 'rgba(0, 162, 255, 1)', 38 | fill: 'rgba(252, 252, 252, 1)', 39 | }, 40 | bboxAttrs: { 41 | stroke: 'rgba(0, 162, 255, 1)', 42 | fill: 'none', 43 | 'stroke-dasharray': '0, 0', 44 | opacity: 0.8 45 | }, 46 | defaultRefBox: 'margin-box' 47 | }; 48 | 49 | options = _.extend({}, _defaults, options); 50 | 51 | /* 52 | Get shape type from provided string. 53 | 54 | @param {String} string with function-like notation such as: 55 | polygon(...), circle(), ellipse() or rectangle() 56 | @throws {TypeError} if input does not contain function-like notation 57 | @return {String} name of shape 58 | */ 59 | function _getShape(string){ 60 | if (string.indexOf('(') < 0) { 61 | throw new TypeError('Value does not contain a shape function'); 62 | } 63 | return string.split('(')[0].trim(); 64 | } 65 | /* 66 | Get shape editor class appropriate for given shape. 67 | 68 | @param {String} shape Any of: polygon, circle, ellipse, rectangle 69 | @throws {TypeError} if shape is not recognized 70 | @return {Object} shape editor class 71 | */ 72 | function _getFactory(shape){ 73 | var factory; 74 | 75 | switch (shape) { 76 | case 'polygon': 77 | factory = PolygonEditor; 78 | break; 79 | 80 | case 'circle': 81 | factory = CircleEditor; 82 | break; 83 | 84 | case 'ellipse': 85 | factory = EllipseEditor; 86 | break; 87 | 88 | default: 89 | throw new TypeError('Value does not contain a valid shape function'); 90 | } 91 | 92 | return factory; 93 | } 94 | 95 | // ensure omitting 'new' is harmless 96 | if (!(this instanceof CSSShapesEditor)){ 97 | return new CSSShapesEditor(target, value, options); 98 | } 99 | 100 | if (!(target instanceof HTMLElement)){ 101 | throw new TypeError('Target is not instance of HTMLElement'); 102 | } 103 | 104 | if (!value || typeof value !== 'string'){ 105 | throw new TypeError('Value is not string'); 106 | } 107 | 108 | var _shape = _getShape(value), 109 | Factory = _getFactory(_shape), 110 | _editor = new Factory(target, value, options); 111 | 112 | return _editor; 113 | } 114 | 115 | return CSSShapesEditor; 116 | }); 117 | -------------------------------------------------------------------------------- /src/CSSUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define */ 17 | 18 | define(function(){ 19 | "use strict"; 20 | 21 | var unitConverters = { 22 | 'px' : function(x) { return x; }, 23 | 'in' : function(x) { return x * 96; }, 24 | 'cm' : function(x) { return x / 0.02645833333; }, 25 | 'mm' : function(x) { return x / 0.26458333333; }, 26 | 'pt' : function(x) { return x / 0.75; }, 27 | 'pc' : function(x) { return x / 0.0625; }, 28 | 'em' : function(x, e) { return x*parseFloat(getComputedStyle(e).fontSize); }, 29 | 'rem': function(x, e) { return x*parseFloat(getComputedStyle(e.ownerDocument.documentElement).fontSize); }, 30 | 'vw' : function(x, e) { return x/100*window.innerWidth; }, 31 | 'vh' : function(x, e) { return x/100*window.innerHeight; }, 32 | '%' : function(x, e, opts) { 33 | 34 | opts = opts || {}; 35 | 36 | var box = e ? getBox(e, opts.boxType) : { 37 | top: 0, 38 | left: 0, 39 | width: 0, 40 | height: 0 41 | }; 42 | 43 | // special case for computing radius for circle() 44 | // @see http://www.w3.org/TR/css-shapes/#funcdef-circle 45 | if (opts.isRadius){ 46 | return Math.round(x/100 * (Math.sqrt(box.height * box.height + box.width * box.width) / Math.sqrt(2))); 47 | } 48 | 49 | 50 | if (opts.isHeightRelated) { return x/100*box.height; } 51 | else { return x/100*box.width; } 52 | 53 | } 54 | }; 55 | 56 | var unitBackConverters = { 57 | 'px' : function(x) { return x; }, 58 | 'in' : function(x) { return x / 96; }, 59 | 'cm' : function(x) { return x * 0.02645833333; }, 60 | 'mm' : function(x) { return x * 0.26458333333; }, 61 | 'pt' : function(x) { return x * 0.75; }, 62 | 'pc' : function(x) { return x * 0.0625; }, 63 | 'em' : function(x, e) { return x/parseFloat(getComputedStyle(e).fontSize); }, 64 | 'rem': function(x, e) { return x/parseFloat(getComputedStyle(e.ownerDocument.documentElement).fontSize); }, 65 | 'vw' : function(x, e) { return x*100/window.innerWidth; }, 66 | 'vh' : function(x, e) { return x*100/window.innerHeight; }, 67 | '%' : function(x, e, opts) { 68 | 69 | opts = opts || {}; 70 | 71 | // get the box from which to compute the percentages 72 | var box = e ? getBox(e, opts.boxType) : { 73 | top: 0, 74 | left: 0, 75 | width: 0, 76 | height: 0 77 | }; 78 | 79 | // special case for computing radius for circle() 80 | // @see http://www.w3.org/TR/css-shapes/#funcdef-circle 81 | if (opts.isRadius){ 82 | return Math.round(x*100/(Math.sqrt(box.height*box.height+box.width*box.width)/Math.sqrt(2))); 83 | } 84 | 85 | // otherwise, we use the width or height 86 | if (opts.isHeightRelated) { return x*100/box.height; } 87 | else { return x*100/box.width; } 88 | 89 | } 90 | }; 91 | 92 | function convertToPixels(cssLength, element, opts) { 93 | 94 | var match = cssLength.match(/^\s*(-?\d+(?:\.\d+)?)(\S*)\s*$/), 95 | currentLength = match ? parseFloat(match[1]) : 0.0, 96 | currentUnit = match ? match[2] : '', 97 | converter = unitConverters[currentUnit]; 98 | 99 | if (match && converter) { 100 | 101 | return { 102 | value: Math.round(20*converter.call(null, currentLength, element, opts))/20, 103 | unit: currentUnit 104 | }; 105 | 106 | } else { 107 | 108 | return { 109 | value: currentLength ? currentLength : 0.0, 110 | unit: currentUnit ? currentUnit : 'px' 111 | }; 112 | 113 | } 114 | } 115 | 116 | function convertFromPixels(pixelLength, destinUnit, element, opts) { 117 | 118 | var converter = unitBackConverters[destinUnit]; 119 | 120 | if(converter) { 121 | return '' + (Math.round(20*converter.call(null, pixelLength, element, opts))/20) + '' + destinUnit; 122 | } else { 123 | return '' + pixelLength + 'px'; 124 | } 125 | } 126 | 127 | /* 128 | Returns coordinates and dimensions for an element's given box type. 129 | Boxes are relative to the element's border-box. 130 | 131 | @param {Object} element 132 | @param {String} boxType one of 'content-box', 'padding-box', 'border-box', 'margin-box' 133 | */ 134 | function getBox(element, boxType){ 135 | var width = element.offsetWidth, 136 | height = element.offsetHeight, 137 | 138 | style = getComputedStyle(element), 139 | 140 | leftBorder = parseFloat(style.borderLeftWidth), 141 | rightBorder = parseFloat(style.borderRightWidth), 142 | topBorder = parseFloat(style.borderTopWidth), 143 | bottomBorder = parseFloat(style.borderBottomWidth), 144 | 145 | leftPadding = parseFloat(style.paddingLeft), 146 | rightPadding = parseFloat(style.paddingRight), 147 | topPadding = parseFloat(style.paddingTop), 148 | bottomPadding = parseFloat(style.paddingBottom), 149 | 150 | leftMargin = parseFloat(style.marginLeft), 151 | rightMargin = parseFloat(style.marginRight), 152 | topMargin = parseFloat(style.marginTop), 153 | bottomMargin = parseFloat(style.marginBottom); 154 | 155 | var box = { 156 | top: 0, 157 | left: 0, 158 | width: 0, 159 | height: 0 160 | }; 161 | 162 | switch (boxType){ 163 | case 'content-box': 164 | box.top = topBorder + topPadding; 165 | box.left = leftBorder + leftPadding; 166 | box.width = width - leftBorder - leftPadding - rightPadding - rightBorder; 167 | box.height = height - topBorder - topPadding - bottomPadding - bottomBorder; 168 | break; 169 | 170 | case 'padding-box': 171 | box.top = topBorder; 172 | box.left = leftBorder; 173 | box.width = width - leftBorder - rightBorder; 174 | box.height = height - topBorder - bottomBorder; 175 | break; 176 | 177 | case 'border-box': 178 | box.top = 0; 179 | box.left = 0; 180 | box.width = width; 181 | box.height = height; 182 | break; 183 | 184 | case 'margin-box': 185 | box.top = 0 - topMargin; 186 | box.left = 0 - leftMargin; 187 | box.width = width + leftMargin + rightMargin; 188 | box.height = height + topMargin + bottomMargin; 189 | break; 190 | 191 | default: 192 | throw new TypeError('Invalid parameter, boxType: ' + boxType); 193 | } 194 | 195 | return box; 196 | } 197 | 198 | /* 199 | Decode a position string into x/y coordinates for an origin such as: 200 | - circle() / ellipse() center 201 | - transform-origin 202 | - background-position 203 | 204 | Returns an object with x, y positions for the origin as: 205 | - original CSS units, if input is in units 206 | - percentages, if input is position keywords like 'center', 'top', 'left', 'bottom' 207 | 208 | @param {String} str String with one or two string-separated position details for x / y 209 | @return {Object} with x, y keys and CSS unit string values 210 | 211 | @example "center 50px" -> {x: '50%', y: '50px'} 212 | */ 213 | function getOriginCoords(str){ 214 | if (!str || typeof str !== 'string'){ 215 | throw new TypeError('Invalid input, expected string, got ' + str); 216 | } 217 | 218 | var xPos = ['left', 'right'], 219 | yPos = ['top', 'bottom'], 220 | defaultXPos = '50%', 221 | defaultYPos = '50%', 222 | origin = {}, 223 | posMap = { 224 | 'top': '0%', 225 | 'right': '100%', 226 | 'left': '0%', 227 | 'bottom': '100%', 228 | 'center': '50%' 229 | }; 230 | 231 | var parts = str.trim().split(/\s+/); 232 | 233 | switch (parts.length){ 234 | case 1: 235 | if (xPos.indexOf(parts[0]) > -1) { 236 | origin.x = posMap[parts[0]]; 237 | origin.y = defaultYPos; 238 | break; 239 | } 240 | 241 | if (yPos.indexOf(parts[0]) > -1) { 242 | origin.x = defaultXPos; 243 | origin.y = posMap[parts[0]]; 244 | break; 245 | } 246 | 247 | if (parts[0] === 'center') { 248 | origin.x = defaultXPos; 249 | origin.y = defaultYPos; 250 | } else { 251 | // input is assumed css unit, like 100px, 33rem, etc. 252 | origin.x = parts[0]; 253 | origin.y = defaultYPos; 254 | } 255 | break; 256 | 257 | case 2: 258 | 259 | /* Invalid cases: 260 | 0 in xPos 261 | 1 in xPos 262 | --- 263 | 0 in yPos 264 | 1 in yPos 265 | --- 266 | 0 in yPos 267 | 1 is not in xPos or 'center' 268 | --- 269 | 0 is not in yPos or 'center' 270 | 1 in xPos 271 | */ 272 | if (( xPos.indexOf(parts[0]) > -1 && xPos.indexOf(parts[1]) > -1 ) || 273 | ( yPos.indexOf(parts[0]) > -1 && yPos.indexOf(parts[1]) > -1 ) || 274 | ( yPos.indexOf(parts[0]) > -1 && xPos.concat('center').indexOf(parts[1]) < 0) || 275 | ( xPos.indexOf(parts[1]) > -1 && yPos.concat('center').indexOf(parts[0]) < 0) ) { 276 | 277 | throw new Error('Invalid origin string provided: ' + str); 278 | } 279 | 280 | if (xPos.indexOf(parts[0]) > -1) { 281 | origin.x = posMap[parts[0]]; 282 | // assume y is either keyword or css unit 283 | origin.y = posMap[parts[1]] || parts[1]; 284 | break; 285 | } 286 | 287 | if (yPos.indexOf(parts[0]) > -1) { 288 | // assume x is either keyword or css unit 289 | origin.x = posMap[parts[1]] || parts[1]; 290 | origin.y = posMap[parts[0]]; 291 | break; 292 | } 293 | 294 | if (yPos.indexOf(parts[1]) > -1) { 295 | // assume x is either keyword or css unit 296 | origin.x = posMap[parts[0]] || parts[0]; 297 | origin.y = posMap[parts[1]]; 298 | break; 299 | } 300 | 301 | if (parts[0] === 'center'){ 302 | origin.x = defaultXPos; 303 | origin.y = posMap[parts[1]] || parts[1]; 304 | break; 305 | } 306 | 307 | if (parts[1] === 'center'){ 308 | origin.x = posMap[parts[0]] || parts[0]; 309 | origin.y = defaultYPos; 310 | break; 311 | } 312 | 313 | origin.x = parts[0]; 314 | origin.y = parts[1]; 315 | break; 316 | } 317 | 318 | return origin; 319 | } 320 | 321 | function Utils(){ 322 | 323 | if (!(this instanceof Utils)){ 324 | return new Utils(); 325 | } 326 | 327 | return { 328 | 'convertToPixels': convertToPixels, 329 | 'convertFromPixels': convertFromPixels, 330 | 'getOriginCoords': getOriginCoords, 331 | 'getBox': getBox, 332 | 'units': Object.keys(unitConverters) 333 | }; 334 | } 335 | 336 | return new Utils(); 337 | }); 338 | -------------------------------------------------------------------------------- /src/CircleEditor.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define */ 17 | 18 | define(['Editor','CSSUtils', 'snap', 'lodash'], function(Editor, CSSUtils, Snap, _){ 19 | "use strict"; 20 | 21 | var _defaults = { 22 | path: { 23 | stroke: 'black', 24 | fill: 'rgba(0, 0, 0, 0)' // tricks transform editor to accept self-drag 25 | }, 26 | point: { 27 | radius: 5, 28 | stroke: 'rgba(0, 0, 0, 1)', 29 | fill: 'rgba(252, 252, 252, 1)' 30 | }, 31 | bboxAttrs: {}, 32 | cxUnit: 'px', 33 | cyUnit: 'px', 34 | rUnit: 'px' 35 | }; 36 | 37 | function CircleEditor(target, value, options){ 38 | Editor.apply(this, arguments); 39 | 40 | this.type = 'circle'; 41 | 42 | // coordinates for circle: cx, cy, radius and corresponding units 43 | this.coords = null; 44 | 45 | this.config = _.extend({}, _defaults, options); 46 | 47 | this.setup(); 48 | this.applyOffsets(); 49 | this.draw(); 50 | 51 | this.toggleFreeTransform(); 52 | } 53 | 54 | CircleEditor.prototype = Object.create(Editor.prototype); 55 | CircleEditor.prototype.constructor = CircleEditor; 56 | 57 | CircleEditor.prototype.setup = function(){ 58 | // parse corods from shape or infer 59 | this.setupCoordinates(); 60 | 61 | // Sets up: this.holder, this.paper, this.snap, this.offsets 62 | Editor.prototype.setup.call(this); 63 | 64 | this.shape = this.paper.circle().attr('fill', 'rgba(0, 0, 0, 0)'); 65 | 66 | // Apply decorations for the shape 67 | Editor.prototype.setupShapeDecoration.call(this, this.config.path); 68 | 69 | window.addEventListener('resize', this.refresh.bind(this)); 70 | }; 71 | 72 | CircleEditor.prototype.setupCoordinates = function(){ 73 | this.coords = this.parseShape(this.value); 74 | }; 75 | 76 | CircleEditor.prototype.update = function(value){ 77 | this.value = value; 78 | 79 | this.turnOffFreeTransform(); 80 | this.removeOffsets(); 81 | this.setupCoordinates(); 82 | this.applyOffsets(); 83 | this.draw(); 84 | this.turnOnFreeTransform(); 85 | }; 86 | 87 | CircleEditor.prototype.refresh = function(){ 88 | this.turnOffFreeTransform(); 89 | this.removeOffsets(); 90 | Editor.prototype.setupOffsets.call(this); 91 | this.applyOffsets(); 92 | this.draw(); 93 | this.turnOnFreeTransform(); 94 | }; 95 | 96 | /* 97 | Add the element's offsets to the circle coordinates. 98 | 99 | The editor surface covers 100% of the viewport and we're working 100 | with absolute units while editing. 101 | 102 | @see CircleEditor.removeOffsets() 103 | */ 104 | CircleEditor.prototype.applyOffsets = function(){ 105 | var cx = this.coords.cx + this.offsets.left, 106 | cy = this.coords.cy + this.offsets.top; 107 | 108 | this.coords.cx = cx; 109 | this.coords.cy = cy; 110 | }; 111 | 112 | /* 113 | Subtract the element's offsets from the circle coordinates. 114 | 115 | @see CircleEditor.applyOffsets() 116 | */ 117 | CircleEditor.prototype.removeOffsets = function(){ 118 | var cx = this.coords.cx - this.offsets.left, 119 | cy = this.coords.cy - this.offsets.top; 120 | 121 | this.coords.cx = cx; 122 | this.coords.cy = cy; 123 | }; 124 | 125 | /* 126 | Parse circle string into object with coordinates for center, radius and units. 127 | Returns undefined if cannot parse shape. 128 | 129 | @example: 130 | { 131 | cx: 0, // circle center x 132 | cxUnit: 'px', 133 | cy: 0, // circle center y 134 | cyUnit: 'px', 135 | r: 50, // circle radius 136 | rUnit: '%' 137 | } 138 | 139 | @param {String} shape CSS circle function shape 140 | 141 | @return {Object | undefined} 142 | */ 143 | CircleEditor.prototype.parseShape = function(shape){ 144 | var element = this.target, 145 | defaultRefBox = this.defaultRefBox, 146 | coords, 147 | infos, 148 | args = [], 149 | center, 150 | box, 151 | shapeRE; 152 | 153 | // superficial check for shape declaration 154 | if (typeof shape !== 'string' || !/^circle\(.*?\)/i.test(shape.trim())){ 155 | throw new Error('No circle() function definition in provided value'); 156 | } 157 | 158 | /* 159 | Regular expression for matching circle shapes 160 | 161 | matches: 162 | circle(? [at (|){1,2}]?) ? 163 | 164 | examples: 165 | circle() 166 | circle(50%) 167 | circle(50% at center) 168 | circle(50% at center top) 169 | circle(50% at 100px) 170 | circle(50% at 100px 10rem) 171 | circle(50% at 100px 10rem) border-box; 172 | 173 | TODO: handle 'closest-side' and 'farthest-side' 174 | */ 175 | shapeRE = /circle\s*\((\s*[0-9\.]+[a-z%]{0,3})?(?:\s*at((?:\s+(?:top|right|bottom|left|center|-?[0-9\.]+[a-z%]{0,3})){1,2}))?\s*\)\s*((?:margin|content|border|padding)-box)?/i; 176 | 177 | /* 178 | infos[1] = radius 179 | infos[2] = center coordinates, space separated x and y 180 | infos[3] = reference box 181 | */ 182 | infos = shapeRE.exec(shape.trim()); 183 | 184 | if (!infos){ 185 | throw new Error('Invalid shape provided: ' + shape); 186 | } 187 | 188 | // if no radius given, compute naive 'closest-side' by assuming center is 50% 50% 189 | if (!infos[1]){ 190 | box = CSSUtils.getBox(element, infos[3] || defaultRefBox); 191 | args.push((Math.min(box.height, box.width) / 2) + 'px'); 192 | } 193 | else{ 194 | args.push(infos[1]); 195 | } 196 | 197 | // if no center coords given, assume 50% 50% 198 | if (!infos[2]){ 199 | args.push('50%'); 200 | args.push('50%'); 201 | } 202 | else{ 203 | center = CSSUtils.getOriginCoords(infos[2]); 204 | args.push(center.x); 205 | args.push(center.y); 206 | } 207 | 208 | // if reference box is undefined (falsy), default reference box will be used later in the code 209 | this.refBox = infos[3]; 210 | 211 | /* 212 | args[0] = radius 213 | args[1] = cx 214 | args[2] = cy 215 | */ 216 | args = args.map(function(arg, i){ 217 | var options = {}; 218 | 219 | options.boxType = infos[3] || defaultRefBox; 220 | 221 | // radius has a special case for computing size from % 222 | options.isRadius = (i === 0) ? true : false; 223 | 224 | // `isHeightRelated = true` makes the algorithm compute % from the box's height 225 | options.isHeightRelated = (i !== 1) ? true : false; 226 | 227 | return CSSUtils.convertToPixels(arg, element, options); 228 | }); 229 | 230 | coords = { 231 | r: args[0].value, 232 | rUnit: args[0].unit, 233 | cx: args[1].value, 234 | cxUnit: args[1].unit, 235 | cy: args[2].value, 236 | cyUnit: args[2].unit 237 | }; 238 | 239 | return coords; 240 | }; 241 | 242 | /* 243 | Return a valid circle CSS Shape value from the current editor's state. 244 | @example circle(50% at 0 0); 245 | 246 | @param {String} unit Convert all the shape coordinates to the given unit type, 247 | overriding the original input unit types. 248 | 249 | @return {String} 250 | */ 251 | CircleEditor.prototype.getCSSValue = function(unit){ 252 | var cx = this.coords.cx - this.offsets.left, 253 | cy = this.coords.cy - this.offsets.top, 254 | r = this.coords.r, 255 | refBox = this.refBox || this.defaultRefBox, 256 | /*jshint -W004*/ // ignore 'variable already defined' error from jsHint' 257 | unit = CSSUtils.units.indexOf(unit > -1) ? unit : null, 258 | value; 259 | 260 | cx = CSSUtils.convertFromPixels(cx, unit || this.coords.cxUnit, this.target, { isHeightRelated: false, boxType: refBox }); 261 | cy = CSSUtils.convertFromPixels(cy, unit || this.coords.cyUnit, this.target, { isHeightRelated: true, boxType: refBox }); 262 | r = CSSUtils.convertFromPixels(r, unit || this.coords.rUnit, this.target, { isHeightRelated: true, isRadius: true, boxType: refBox }); 263 | 264 | value = 'circle(' + [r, 'at', cx, cy].join(' ') + ')'; 265 | 266 | // expose reference box keyword only if it was given as input, 267 | if (this.refBox){ 268 | value += ' ' + this.refBox; 269 | } 270 | 271 | return value; 272 | }; 273 | 274 | CircleEditor.prototype.toggleFreeTransform = function(){ 275 | 276 | // make a clone to avoid compound tranforms 277 | var coordsClone = (JSON.parse(JSON.stringify(this.coords))); 278 | var scope = this; 279 | 280 | function _transformPoints(){ 281 | var matrix = scope.shapeClone.transform().localMatrix; 282 | 283 | scope.coords.cx = matrix.x(coordsClone.cx, coordsClone.cy).toFixed(); 284 | scope.coords.cy = matrix.y(coordsClone.cx, coordsClone.cy).toFixed(); 285 | scope.coords.r = (scope.transformEditor.attrs.scale.x * coordsClone.r).toFixed(); 286 | 287 | scope.draw(); 288 | } 289 | 290 | if (this.transformEditor){ 291 | this.shapeClone.remove(); 292 | this.transformEditor.unplug(); 293 | delete this.transformEditor; 294 | 295 | return; 296 | } 297 | 298 | // using a phantom shape because we already redraw the path by the transformed coordinates. 299 | // using the same path would result in double transformations for the shape 300 | this.shapeClone = this.shape.clone().attr('stroke', 'none'); 301 | 302 | this.transformEditor = Snap.freeTransform(this.shapeClone, { 303 | draw: ['bbox'], 304 | drag: ['self','center'], 305 | keepRatio: ['bboxCorners'], 306 | rotate: [], 307 | scale: ['bboxCorners'], 308 | distance: '0.6', 309 | attrs: this.config.point, 310 | bboxAttrs: this.config.bboxAttrs, 311 | size: this.config.point.radius 312 | }, 313 | _transformPoints); 314 | }; 315 | 316 | CircleEditor.prototype.draw = function(){ 317 | // draw the circle shape 318 | this.shape.attr(this.coords); 319 | 320 | this.trigger('shapechange', this); 321 | }; 322 | 323 | return CircleEditor; 324 | }); 325 | -------------------------------------------------------------------------------- /src/Editor.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define, eve */ 17 | 18 | define(['eve', 'CSSUtils', 'snap'], function(eve, CSSUtils, Snap){ 19 | "use strict"; 20 | 21 | var REFERENCE_BOXES = ['margin-box','border-box','padding-box','content-box']; 22 | 23 | function Editor(target, value, options){ 24 | 25 | if (!target || !(target instanceof HTMLElement)){ 26 | throw new TypeError('Target expected as HTMLElement object, but was: ' + typeof target); 27 | } 28 | 29 | this.target = target; 30 | this.value = value; 31 | 32 | // container for editor SVG element; 33 | // set up by setupEditorHolder() 34 | this.holder = null; 35 | 36 | // default reference box for shape coordinate system 37 | this.defaultRefBox = REFERENCE_BOXES[0]; 38 | 39 | // accept new default refence box if defined and recognized 40 | // @see: https://github.com/adobe-webplatform/css-shapes-editor/issues/12 41 | if (options && options.defaultRefBox && REFERENCE_BOXES.indexOf(options.defaultRefBox) > -1){ 42 | this.defaultRefBox = options.defaultRefBox; 43 | } 44 | 45 | // reference box for coordinate system as parsed from shape value string 46 | // set up by parseShape() higher in the prototype chanin 47 | this.refBox = null; 48 | 49 | // target element offsets with regards to the page 50 | // set up by setupOffsets() higher in the prototype chain 51 | this.offsets = { 52 | left: 0, 53 | top: 0 54 | }; 55 | } 56 | 57 | Editor.prototype = { 58 | setup: function(){ 59 | 60 | this.setupEditorHolder(); 61 | this.setupDrawingSurface(); 62 | this.setupOffsets(); 63 | 64 | window.setTimeout(function(){ 65 | this.trigger('ready'); 66 | }.bind(this)); 67 | }, 68 | 69 | setupEditorHolder: function() { 70 | if (!this.holder) { 71 | // create an element for the holder 72 | this.holder = document.createElement('div'); 73 | 74 | // position this element so that it fills the viewport 75 | this.holder.style.position = "absolute"; 76 | this.holder.style.top = 0; 77 | this.holder.style.left = 0; 78 | this.holder.style.right = 0; 79 | this.holder.style.bottom = 0; 80 | // make sure editor is the top-most thing on the page 81 | // see http://softwareas.com/whats-the-maximum-z-index 82 | this.holder.style.zIndex = 2147483647; 83 | 84 | // prevents text selection when doing dbl click 85 | this.holder.style.webkitUserSelect = "none"; 86 | this.holder.style.userSelect = "none"; 87 | 88 | this.holder.setAttribute('data-role', 'shape-editor'); 89 | 90 | // add this layer to the document 91 | document.body.appendChild(this.holder); 92 | } 93 | 94 | // resize tricks 95 | this.sizeEditorHolder(); 96 | }, 97 | 98 | /* 99 | Adjusts the size of the editor holder to account for any scroll; 100 | */ 101 | sizeEditorHolder: function(){ 102 | var root = document.documentElement; 103 | this.holder.style.display = 'none'; 104 | this.holder.style.minHeight = root.scrollHeight + 'px'; 105 | this.holder.style.minWidth = root.scrollWidth + 'px'; 106 | this.holder.style.display = 'block'; 107 | }, 108 | 109 | setupDrawingSurface: function(){ 110 | this.snap = new Snap('100%','100%'); 111 | this.holder.appendChild(this.snap.node); 112 | this.paper = this.snap.paper; 113 | }, 114 | 115 | setupOffsets: function() { 116 | var rect = this.target.getBoundingClientRect(), 117 | refBox = this.refBox || this.defaultRefBox, 118 | box = CSSUtils.getBox(this.target, refBox); 119 | 120 | this.offsets.left = rect.left + window.scrollX + box.left; 121 | this.offsets.top = rect.top + window.scrollY + box.top; 122 | }, 123 | 124 | /* 125 | Visually decorates this.shape 126 | 127 | Uses stacked `` SVG elements based on the shape, 128 | with different styling to achieve complex decoration, such as the two-color dashed outlines 129 | 130 | @param {Array} path An array with objects with decoration attributes. 131 | 132 | */ 133 | setupShapeDecoration: function(path) { 134 | if (!path){ 135 | return; 136 | } 137 | 138 | // enforce an array of path attribute objects 139 | if (!Array.isArray(path)){ 140 | path = [path]; 141 | } 142 | 143 | var shape = this.shape; 144 | var group = this.paper.group(); 145 | 146 | path.forEach(function(pathAttr){ 147 | group.add(shape.use().attr(pathAttr)); 148 | }); 149 | 150 | group.toBack(); 151 | }, 152 | 153 | remove: function() { 154 | var holder = this.holder; 155 | 156 | if (holder && holder.parentElement){ 157 | holder.parentNode.removeChild(holder); 158 | } 159 | 160 | this.trigger('removed', {}); 161 | }, 162 | 163 | toggleFreeTransform: function(){ 164 | // to be implemented by specialized editors, higher in the prototype chain 165 | }, 166 | 167 | turnOnFreeTransform: function(){ 168 | if (!this.transformEditor){ 169 | this.toggleFreeTransform(); 170 | } 171 | }, 172 | 173 | turnOffFreeTransform: function(){ 174 | if (this.transformEditor){ 175 | this.toggleFreeTransform(); 176 | } 177 | }, 178 | 179 | on: eve.on, 180 | off: eve.off, 181 | trigger: eve 182 | }; 183 | 184 | return Editor; 185 | }); 186 | -------------------------------------------------------------------------------- /src/EllipseEditor.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define */ 17 | 18 | define(['Editor','CSSUtils', 'snap', 'lodash'], function(Editor, CSSUtils, Snap, _){ 19 | "use strict"; 20 | 21 | var _defaults = { 22 | path: { 23 | stroke: 'black', 24 | fill: 'rgba(0, 0, 0, 0)' // tricks transform editor to accept self-drag 25 | }, 26 | point: { 27 | radius: 5, 28 | stroke: 'rgba(0, 0, 0, 1)', 29 | fill: 'rgba(252, 252, 252, 1)' 30 | }, 31 | bboxAttrs: {}, 32 | cxUnit: 'px', 33 | cyUnit: 'px', 34 | rxUnit: 'px', 35 | ryUnit: 'px' 36 | }; 37 | 38 | function EllipseEditor(target, value, options){ 39 | Editor.apply(this, arguments); 40 | 41 | this.type = 'ellipse'; 42 | 43 | // coordinates for circle: cx, cy, x and y radii and corresponding units 44 | this.coords = null; 45 | 46 | this.config = _.extend({}, _defaults, options); 47 | 48 | this.setup(); 49 | this.applyOffsets(); 50 | this.draw(); 51 | 52 | this.toggleFreeTransform(); 53 | } 54 | 55 | EllipseEditor.prototype = Object.create(Editor.prototype); 56 | EllipseEditor.prototype.constructor = EllipseEditor; 57 | 58 | EllipseEditor.prototype.setup = function(){ 59 | 60 | this.setupCoordinates(); 61 | 62 | // Sets up: this.holder, this.paper, this.snap, this.offsets 63 | Editor.prototype.setup.call(this); 64 | 65 | this.shape = this.paper.ellipse().attr('fill', 'rgba(0, 0, 0, 0)'); 66 | 67 | // Apply decorations for the shape 68 | Editor.prototype.setupShapeDecoration.call(this, this.config.path); 69 | 70 | window.addEventListener('resize', this.refresh.bind(this)); 71 | }; 72 | 73 | EllipseEditor.prototype.setupCoordinates = function(){ 74 | this.coords = this.parseShape(this.value); 75 | }; 76 | 77 | EllipseEditor.prototype.update = function(value){ 78 | this.value = value; 79 | 80 | this.turnOffFreeTransform(); 81 | this.removeOffsets(); 82 | this.setupCoordinates(); 83 | this.applyOffsets(); 84 | this.draw(); 85 | this.turnOnFreeTransform(); 86 | }; 87 | 88 | EllipseEditor.prototype.refresh = function(){ 89 | this.turnOffFreeTransform(); 90 | this.removeOffsets(); 91 | Editor.prototype.setupOffsets.call(this); 92 | this.applyOffsets(); 93 | this.draw(); 94 | this.turnOnFreeTransform(); 95 | }; 96 | 97 | /* 98 | Add the element's offsets to the ellipse center coordinates. 99 | 100 | The editor surface covers 100% of the viewport and we're working 101 | with absolute units while editing. 102 | 103 | @see EllipseEditor.removeOffsets() 104 | */ 105 | EllipseEditor.prototype.applyOffsets = function(){ 106 | var cx = this.coords.cx + this.offsets.left, 107 | cy = this.coords.cy + this.offsets.top; 108 | 109 | this.coords.cx = cx; 110 | this.coords.cy = cy; 111 | }; 112 | 113 | /* 114 | Subtract the element's offsets from the ellipse center coordinates. 115 | 116 | @see EllipseEditor.applyOffsets() 117 | */ 118 | EllipseEditor.prototype.removeOffsets = function(){ 119 | var cx = this.coords.cx - this.offsets.left, 120 | cy = this.coords.cy - this.offsets.top; 121 | 122 | this.coords.cx = cx; 123 | this.coords.cy = cy; 124 | }; 125 | 126 | /* 127 | Parse ellipse string into object with coordinates for center, radii and units. 128 | Infers shape from element if empty ellipse function given. 129 | 130 | @example: 131 | { 132 | cx: 0, // ellipse center x 133 | cxUnit: 'px', 134 | cy: 0, // ellipse center y 135 | cyUnit: 'px', 136 | rx: 50, // ellipse x radius 137 | rxUnit: '%', 138 | ry: 50, // ellipse y radius 139 | ryUnit: '%' 140 | } 141 | 142 | @throws {Error} if input shape is not valid ellipse shape function 143 | @param {String} shape CSS ellipse shape function 144 | @return {Object} 145 | */ 146 | EllipseEditor.prototype.parseShape = function(shape){ 147 | var element = this.target, 148 | defaultRefBox = this.defaultRefBox, 149 | coords, 150 | center, 151 | radii, 152 | box, 153 | infos, 154 | args = [], 155 | shapeRE; 156 | 157 | // superficial check for ellipse declaration 158 | if (typeof shape !== 'string' || !/^ellipse\(.*?\)/i.test(shape.trim())){ 159 | throw new Error('No ellipse() function definition in provided value'); 160 | } 161 | 162 | /* 163 | Regular expression for matching ellipse shapes 164 | 165 | matches: 166 | ellipse({1,2}? [at (|){1,2}]?) ? 167 | 168 | examples: 169 | ellipse() 170 | ellipse(50%) 171 | ellipse(50% closest-side) 172 | ellipse(50% closest-side at center) 173 | ellipse(50% closest-side at center top) 174 | ellipse(50% closest-side at 100px) 175 | ellipse(50% closest-side at 100px 10rem) 176 | ellipse(50% closest-side at 100px 10rem) border-box 177 | */ 178 | shapeRE = /ellipse\s*\(\s*((?:\b(?:farthest-side|closest-side|[0-9\.]+[a-z%]{0,3})\s*){1,2})?(?:\bat((?:\s+(?:top|right|bottom|left|center|-?[0-9\.]+[a-z%]{0,3})){1,2}))?\s*\)\s*((?:margin|content|border|padding)-box)?/i; 179 | 180 | /* 181 | infos[1] = radii coordinates, space separated rx and ry 182 | infos[2] = center coordinates, space separated x and y 183 | infos[3] = reference box 184 | */ 185 | infos = shapeRE.exec(shape.trim()); 186 | 187 | if (!infos){ 188 | throw new Error('Invalid shape provided: ' + shape); 189 | } 190 | 191 | // no radii given, assume closest-side like the browser default 192 | if (!infos[1]){ 193 | args.push('closest-side'); // rx 194 | args.push('closest-side'); // ry 195 | } else { 196 | radii = infos[1].split(/\s+/); 197 | args.push(radii[0]); // rx 198 | args.push(radii[1] || 'closest-side'); // ry 199 | } 200 | 201 | // TODO move decoding closest-side/ farthest-side to CSSUtils 202 | // TODO consider actual given center, do not assume 50% 50% 203 | var keywords = ['closest-side', 'farthest-side']; 204 | 205 | if (keywords.indexOf(args[0]) > -1){ 206 | // naively assume center is 50% 50% 207 | // TODO: improve by considering actual center 208 | box = CSSUtils.getBox(element, infos[3] || defaultRefBox); 209 | args[0] = box.width / 2 + 'px'; 210 | } 211 | 212 | if (keywords.indexOf(args[1]) > -1){ 213 | box = CSSUtils.getBox(element, infos[3] || defaultRefBox); 214 | // naively assume center is 50% 50% 215 | args[1] = box.height / 2 + 'px'; 216 | } 217 | 218 | // if no center coords given, assume 50% 50% 219 | if (!infos[2]){ 220 | args.push('50%'); 221 | args.push('50%'); 222 | } else { 223 | center = CSSUtils.getOriginCoords(infos[2]); 224 | args.push(center.x); 225 | args.push(center.y); 226 | } 227 | 228 | // if reference box is undefined (falsy), default reference box will be used later in the code 229 | this.refBox = infos[3]; 230 | 231 | args = args.map(function(arg, i){ 232 | var options = {}; 233 | 234 | options.boxType = infos[3] || defaultRefBox; 235 | 236 | // 0 = rx 237 | // 1 = ry 238 | // 2 = cx 239 | // 3 = cy 240 | // if percentages, cy and ry are calculated from the element's reference box height 241 | options.isHeightRelated = (i === 1 || i === 3) ? true : false; 242 | 243 | return CSSUtils.convertToPixels(arg, element, options); 244 | }); 245 | 246 | coords = { 247 | rx: args[0].value, 248 | rxUnit: args[0].unit, 249 | ry: args[1].value, 250 | ryUnit: args[1].unit, 251 | cx: args[2].value, 252 | cxUnit: args[2].unit, 253 | cy: args[3].value, 254 | cyUnit: args[3].unit, 255 | }; 256 | 257 | return coords; 258 | }; 259 | 260 | /* 261 | Return a valid ellipse CSS Shape value from the current editor's state. 262 | @example ellipse(50% 50% at 0 0); 263 | 264 | @param {String} unit Convert all the shape coordinates to the given unit type, 265 | overriding the original input unit types. 266 | 267 | @return {String} 268 | */ 269 | EllipseEditor.prototype.getCSSValue = function(unit){ 270 | var cx = this.coords.cx - this.offsets.left, 271 | cy = this.coords.cy - this.offsets.top, 272 | rx = this.coords.rx, 273 | ry = this.coords.ry, 274 | refBox = this.refBox || this.defaultRefBox, 275 | /*jshint -W004*/ // ignore 'variable already defined' error from jsHint' 276 | unit = CSSUtils.units.indexOf(unit > -1) ? unit : null, 277 | value; 278 | 279 | cx = CSSUtils.convertFromPixels(cx, unit || this.coords.cxUnit, this.target, { isHeightRelated: false, boxType: refBox }); 280 | cy = CSSUtils.convertFromPixels(cy, unit || this.coords.cyUnit, this.target, { isHeightRelated: true, boxType: refBox }); 281 | rx = CSSUtils.convertFromPixels(rx, unit || this.coords.rxUnit, this.target, { isHeightRelated: false, boxType: refBox }); 282 | ry = CSSUtils.convertFromPixels(ry, unit || this.coords.ryUnit, this.target, { isHeightRelated: true, boxType: refBox }); 283 | 284 | value = 'ellipse(' + [rx, ry, 'at', cx, cy].join(' ') + ')'; 285 | 286 | // expose reference box keyword only if it was given as input, 287 | if (this.refBox){ 288 | value += ' ' + this.refBox; 289 | } 290 | 291 | return value; 292 | }; 293 | 294 | EllipseEditor.prototype.toggleFreeTransform = function(){ 295 | 296 | // make a clone to avoid compound tranforms 297 | var coordsClone = (JSON.parse(JSON.stringify(this.coords))), 298 | scope = this; 299 | 300 | function _transformPoints(){ 301 | var matrix = scope.shapeClone.transform().localMatrix; 302 | 303 | scope.coords.cx = matrix.x(coordsClone.cx, coordsClone.cy).toFixed(); 304 | scope.coords.cy = matrix.y(coordsClone.cx, coordsClone.cy).toFixed(); 305 | scope.coords.rx = (scope.transformEditor.attrs.scale.x * coordsClone.rx).toFixed(); 306 | scope.coords.ry = (scope.transformEditor.attrs.scale.y * coordsClone.ry).toFixed(); 307 | 308 | scope.draw(); 309 | } 310 | 311 | if (this.transformEditor){ 312 | this.shapeClone.remove(); 313 | this.transformEditor.unplug(); 314 | delete this.transformEditor; 315 | 316 | return; 317 | } 318 | 319 | // using a phantom shape because we already redraw the path by the transformed coordinates. 320 | // using the same path would result in double transformations for the shape 321 | this.shapeClone = this.shape.clone().attr('stroke', 'none'); 322 | 323 | this.transformEditor = Snap.freeTransform(this.shapeClone, { 324 | draw: ['bbox'], 325 | drag: ['self','center'], 326 | keepRatio: ['bboxCorners'], 327 | rotate: [], // ellipses do not rotate 328 | scale: ['bboxCorners','bboxSides'], 329 | distance: '0.6', 330 | attrs: this.config.point, 331 | bboxAttrs: this.config.bboxAttrs, 332 | size: this.config.point.radius 333 | }, _transformPoints); 334 | }; 335 | 336 | 337 | EllipseEditor.prototype.draw = function(){ 338 | // draw the ellipse shape 339 | this.shape.attr(this.coords); 340 | 341 | this.trigger('shapechange', this); 342 | }; 343 | 344 | return EllipseEditor; 345 | }); 346 | -------------------------------------------------------------------------------- /src/ToolBar.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define */ 17 | 18 | define(['lodash', 'snap'], function(_, Snap){ 19 | "use strict"; 20 | 21 | var _defaults = { 22 | toolSize: 24 23 | }; 24 | 25 | var _defaultTool = { 26 | name: "tool", 27 | activeFill: 'red', 28 | inactiveFill: 'gray', 29 | onActivate: function () { /* 'this' scoped to ToolBar instance */ }, 30 | onDeactivate: function () { /* 'this' scoped to ToolBar instance */ }, 31 | }; 32 | 33 | function ToolBar(options) { 34 | this.config = _.extend({}, _defaults, options); 35 | 36 | this.type = this.config.type; 37 | 38 | this.paper = this.config.paper || new Snap('100%','100%').paper; 39 | 40 | this.body = this.paper.g().drag(); 41 | 42 | this.tools = {}; 43 | 44 | // click handler with 'this' bound to ToolBar instance scope; 45 | this.onToolClick = (function(scope){ 46 | return function(e){ 47 | // 'this' is ToolBar instance 48 | 49 | var target = e.target, 50 | tool = this.tools[target.id]; 51 | 52 | if (!tool){ 53 | return; 54 | } 55 | 56 | // if undefined, it's falsy and that's ok; continue 57 | if (tool.el.data('selected')){ 58 | return; 59 | } 60 | 61 | // toggle off all tools 62 | Object.keys(this.tools).forEach(function(id){ 63 | this.deactivate(id); 64 | }.bind(this)); 65 | 66 | // toggle on this tool 67 | this.activate(target.id); 68 | 69 | }.bind(scope); 70 | })(this); 71 | } 72 | 73 | ToolBar.prototype.activate = function(id){ 74 | if (!this.tools[id]){ 75 | return; 76 | } 77 | 78 | var tool = this.tools[id]; 79 | tool.el.data('selected', true); 80 | tool.el.attr({fill: tool.activeFill}); 81 | tool.onActivate.call(this); 82 | }; 83 | 84 | ToolBar.prototype.deactivate = function(id){ 85 | if (!this.tools[id]){ 86 | return; 87 | } 88 | 89 | var tool = this.tools[id]; 90 | 91 | // only deactivate if already active 92 | if (!tool.el.data('selected')){ 93 | return; 94 | } 95 | 96 | tool.el.data('selected', false); 97 | tool.el.attr({fill: tool.inactiveFill}); 98 | tool.onDeactivate.call(this); 99 | }; 100 | 101 | ToolBar.prototype.add = function(id, options){ 102 | if (this.tools[id]){ 103 | throw new Error('Tool with id "' + id + '" already exists.'); 104 | } 105 | 106 | var tool = _.extend({}, _defaultTool, options), 107 | size = this.config.toolSize; 108 | 109 | tool.el = this.paper.rect(); 110 | tool.el.attr({ 111 | width: size, 112 | height: size, 113 | id: id, 114 | fill: "red", 115 | x: 0, 116 | y: Object.keys(this.tools).length * size, 117 | }); 118 | 119 | tool.el.attr({ 120 | fill: tool.inactiveFill 121 | }); 122 | 123 | tool.el.click(this.onToolClick.bind(this)); 124 | 125 | this.tools[id] = tool; 126 | 127 | [tool.inactiveFill, tool.activeFill].forEach(function(fill){ 128 | if (fill && fill.type && fill.type === 'pattern'){ 129 | fill.attr({ 130 | width: size, 131 | height: size 132 | }); 133 | } 134 | }); 135 | 136 | this.height(Object.keys(this.tools).length * size); 137 | this.body.append(tool.el); 138 | this.body.transform('translate(100, 100)'); 139 | }; 140 | 141 | ToolBar.prototype.remove = function(id){ 142 | if (!this.tools[id]){ 143 | return; 144 | } 145 | 146 | delete this.tools[id]; 147 | }; 148 | 149 | ToolBar.prototype.position = function(pos){ 150 | var oldX = this.body.attr('x'), 151 | oldY = this.body.attr('y'), 152 | newPos; 153 | 154 | if (!pos || typeof pos !== 'object'){ 155 | return { x: oldX, y: oldY }; 156 | } 157 | 158 | newPos = { 159 | x: (typeof pos.x === 'number') ? pos.x : oldX, 160 | y: (typeof pos.y === 'number') ? pos.y : oldY 161 | }; 162 | 163 | this.body.transform('translate('+newPos.x+','+newPos.y+')'); 164 | 165 | return newPos; 166 | }; 167 | 168 | ToolBar.prototype.dimension = function(type, value){ 169 | var oldValue = this.body.getBBox()[type]; 170 | 171 | if (!value || typeof value !== 'number' || value === oldValue){ 172 | return oldValue; 173 | } else { 174 | this.body.attr(type, value); 175 | return value; 176 | } 177 | }; 178 | 179 | ToolBar.prototype.height = function(value){ 180 | return this.dimension("height", value); 181 | }; 182 | 183 | ToolBar.prototype.width = function(value){ 184 | return this.dimension("width", value); 185 | }; 186 | 187 | return ToolBar; 188 | }); 189 | -------------------------------------------------------------------------------- /src/fragments/end.frag: -------------------------------------------------------------------------------- 1 | //The modules for your project will be inlined above 2 | //this snippet. Ask almond to synchronously require the 3 | //module value for 'main' here and return it as the 4 | //value to use for the public API for the built file. 5 | 6 | return require('main'); 7 | })); -------------------------------------------------------------------------------- /src/fragments/start.frag: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(factory); 4 | } else { 5 | root.CSSShapesEditor = factory(); 6 | } 7 | }(this, function () { 8 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 16 | /*global define, require */ 17 | 18 | require.config({ 19 | // baseUrl: './', // infered from data-main on 6 | 15 | 65 |

66 | 
67 | 


--------------------------------------------------------------------------------
/src/third-party/eve/eve.js:
--------------------------------------------------------------------------------
  1 | // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
  2 | // 
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | // 
  7 | // http://www.apache.org/licenses/LICENSE-2.0
  8 | // 
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | // ┌────────────────────────────────────────────────────────────┐ \\
 15 | // │ Eve 0.4.2 - JavaScript Events Library                      │ \\
 16 | // ├────────────────────────────────────────────────────────────┤ \\
 17 | // │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
 18 | // └────────────────────────────────────────────────────────────┘ \\
 19 | 
 20 | (function (glob) {
 21 |     var version = "0.4.2",
 22 |         has = "hasOwnProperty",
 23 |         separator = /[\.\/]/,
 24 |         wildcard = "*",
 25 |         fun = function () {},
 26 |         numsort = function (a, b) {
 27 |             return a - b;
 28 |         },
 29 |         current_event,
 30 |         stop,
 31 |         events = {n: {}},
 32 |     /*\
 33 |      * eve
 34 |      [ method ]
 35 | 
 36 |      * Fires event with given `name`, given scope and other parameters.
 37 | 
 38 |      > Arguments
 39 | 
 40 |      - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
 41 |      - scope (object) context for the event handlers
 42 |      - varargs (...) the rest of arguments will be sent to event handlers
 43 | 
 44 |      = (object) array of returned values from the listeners
 45 |     \*/
 46 |         eve = function (name, scope) {
 47 | 			name = String(name);
 48 |             var e = events,
 49 |                 oldstop = stop,
 50 |                 args = Array.prototype.slice.call(arguments, 2),
 51 |                 listeners = eve.listeners(name),
 52 |                 z = 0,
 53 |                 f = false,
 54 |                 l,
 55 |                 indexed = [],
 56 |                 queue = {},
 57 |                 out = [],
 58 |                 ce = current_event,
 59 |                 errors = [];
 60 |             current_event = name;
 61 |             stop = 0;
 62 |             for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
 63 |                 indexed.push(listeners[i].zIndex);
 64 |                 if (listeners[i].zIndex < 0) {
 65 |                     queue[listeners[i].zIndex] = listeners[i];
 66 |                 }
 67 |             }
 68 |             indexed.sort(numsort);
 69 |             while (indexed[z] < 0) {
 70 |                 l = queue[indexed[z++]];
 71 |                 out.push(l.apply(scope, args));
 72 |                 if (stop) {
 73 |                     stop = oldstop;
 74 |                     return out;
 75 |                 }
 76 |             }
 77 |             for (i = 0; i < ii; i++) {
 78 |                 l = listeners[i];
 79 |                 if ("zIndex" in l) {
 80 |                     if (l.zIndex == indexed[z]) {
 81 |                         out.push(l.apply(scope, args));
 82 |                         if (stop) {
 83 |                             break;
 84 |                         }
 85 |                         do {
 86 |                             z++;
 87 |                             l = queue[indexed[z]];
 88 |                             l && out.push(l.apply(scope, args));
 89 |                             if (stop) {
 90 |                                 break;
 91 |                             }
 92 |                         } while (l)
 93 |                     } else {
 94 |                         queue[l.zIndex] = l;
 95 |                     }
 96 |                 } else {
 97 |                     out.push(l.apply(scope, args));
 98 |                     if (stop) {
 99 |                         break;
100 |                     }
101 |                 }
102 |             }
103 |             stop = oldstop;
104 |             current_event = ce;
105 |             return out.length ? out : null;
106 |         };
107 | 		// Undocumented. Debug only.
108 | 		eve._events = events;
109 |     /*\
110 |      * eve.listeners
111 |      [ method ]
112 | 
113 |      * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
114 | 
115 |      > Arguments
116 | 
117 |      - name (string) name of the event, dot (`.`) or slash (`/`) separated
118 | 
119 |      = (array) array of event handlers
120 |     \*/
121 |     eve.listeners = function (name) {
122 |         var names = name.split(separator),
123 |             e = events,
124 |             item,
125 |             items,
126 |             k,
127 |             i,
128 |             ii,
129 |             j,
130 |             jj,
131 |             nes,
132 |             es = [e],
133 |             out = [];
134 |         for (i = 0, ii = names.length; i < ii; i++) {
135 |             nes = [];
136 |             for (j = 0, jj = es.length; j < jj; j++) {
137 |                 e = es[j].n;
138 |                 items = [e[names[i]], e[wildcard]];
139 |                 k = 2;
140 |                 while (k--) {
141 |                     item = items[k];
142 |                     if (item) {
143 |                         nes.push(item);
144 |                         out = out.concat(item.f || []);
145 |                     }
146 |                 }
147 |             }
148 |             es = nes;
149 |         }
150 |         return out;
151 |     };
152 |     
153 |     /*\
154 |      * eve.on
155 |      [ method ]
156 |      **
157 |      * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
158 |      | eve.on("*.under.*", f);
159 |      | eve("mouse.under.floor"); // triggers f
160 |      * Use @eve to trigger the listener.
161 |      **
162 |      > Arguments
163 |      **
164 |      - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
165 |      - f (function) event handler function
166 |      **
167 |      = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. 
168 |      > Example:
169 |      | eve.on("mouse", eatIt)(2);
170 |      | eve.on("mouse", scream);
171 |      | eve.on("mouse", catchIt)(1);
172 |      * This will ensure that `catchIt()` function will be called before `eatIt()`.
173 | 	 *
174 |      * If you want to put your handler before non-indexed handlers, specify a negative value.
175 |      * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
176 |     \*/
177 |     eve.on = function (name, f) {
178 | 		name = String(name);
179 | 		if (typeof f != "function") {
180 | 			return function () {};
181 | 		}
182 |         var names = name.split(separator),
183 |             e = events;
184 |         for (var i = 0, ii = names.length; i < ii; i++) {
185 |             e = e.n;
186 |             e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
187 |         }
188 |         e.f = e.f || [];
189 |         for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
190 |             return fun;
191 |         }
192 |         e.f.push(f);
193 |         return function (zIndex) {
194 |             if (+zIndex == +zIndex) {
195 |                 f.zIndex = +zIndex;
196 |             }
197 |         };
198 |     };
199 |     /*\
200 |      * eve.f
201 |      [ method ]
202 |      **
203 |      * Returns function that will fire given event with optional arguments.
204 | 	 * Arguments that will be passed to the result function will be also
205 | 	 * concated to the list of final arguments.
206 |  	 | el.onclick = eve.f("click", 1, 2);
207 |  	 | eve.on("click", function (a, b, c) {
208 |  	 |     console.log(a, b, c); // 1, 2, [event object]
209 |  	 | });
210 |      > Arguments
211 | 	 - event (string) event name
212 | 	 - varargs (…) and any other arguments
213 | 	 = (function) possible event handler function
214 |     \*/
215 | 	eve.f = function (event) {
216 | 		var attrs = [].slice.call(arguments, 1);
217 | 		return function () {
218 | 			eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
219 | 		};
220 | 	};
221 |     /*\
222 |      * eve.stop
223 |      [ method ]
224 |      **
225 |      * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
226 |     \*/
227 |     eve.stop = function () {
228 |         stop = 1;
229 |     };
230 |     /*\
231 |      * eve.nt
232 |      [ method ]
233 |      **
234 |      * Could be used inside event handler to figure out actual name of the event.
235 |      **
236 |      > Arguments
237 |      **
238 |      - subname (string) #optional subname of the event
239 |      **
240 |      = (string) name of the event, if `subname` is not specified
241 |      * or
242 |      = (boolean) `true`, if current event’s name contains `subname`
243 |     \*/
244 |     eve.nt = function (subname) {
245 |         if (subname) {
246 |             return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
247 |         }
248 |         return current_event;
249 |     };
250 |     /*\
251 |      * eve.nts
252 |      [ method ]
253 |      **
254 |      * Could be used inside event handler to figure out actual name of the event.
255 |      **
256 |      **
257 |      = (array) names of the event
258 |     \*/
259 |     eve.nts = function () {
260 |         return current_event.split(separator);
261 |     };
262 |     /*\
263 |      * eve.off
264 |      [ method ]
265 |      **
266 |      * Removes given function from the list of event listeners assigned to given name.
267 | 	 * If no arguments specified all the events will be cleared.
268 |      **
269 |      > Arguments
270 |      **
271 |      - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
272 |      - f (function) event handler function
273 |     \*/
274 |     /*\
275 |      * eve.unbind
276 |      [ method ]
277 |      **
278 |      * See @eve.off
279 |     \*/
280 |     eve.off = eve.unbind = function (name, f) {
281 | 		if (!name) {
282 | 		    eve._events = events = {n: {}};
283 | 			return;
284 | 		}
285 |         var names = name.split(separator),
286 |             e,
287 |             key,
288 |             splice,
289 |             i, ii, j, jj,
290 |             cur = [events];
291 |         for (i = 0, ii = names.length; i < ii; i++) {
292 |             for (j = 0; j < cur.length; j += splice.length - 2) {
293 |                 splice = [j, 1];
294 |                 e = cur[j].n;
295 |                 if (names[i] != wildcard) {
296 |                     if (e[names[i]]) {
297 |                         splice.push(e[names[i]]);
298 |                     }
299 |                 } else {
300 |                     for (key in e) if (e[has](key)) {
301 |                         splice.push(e[key]);
302 |                     }
303 |                 }
304 |                 cur.splice.apply(cur, splice);
305 |             }
306 |         }
307 |         for (i = 0, ii = cur.length; i < ii; i++) {
308 |             e = cur[i];
309 |             while (e.n) {
310 |                 if (f) {
311 |                     if (e.f) {
312 |                         for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
313 |                             e.f.splice(j, 1);
314 |                             break;
315 |                         }
316 |                         !e.f.length && delete e.f;
317 |                     }
318 |                     for (key in e.n) if (e.n[has](key) && e.n[key].f) {
319 |                         var funcs = e.n[key].f;
320 |                         for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
321 |                             funcs.splice(j, 1);
322 |                             break;
323 |                         }
324 |                         !funcs.length && delete e.n[key].f;
325 |                     }
326 |                 } else {
327 |                     delete e.f;
328 |                     for (key in e.n) if (e.n[has](key) && e.n[key].f) {
329 |                         delete e.n[key].f;
330 |                     }
331 |                 }
332 |                 e = e.n;
333 |             }
334 |         }
335 |     };
336 |     /*\
337 |      * eve.once
338 |      [ method ]
339 |      **
340 |      * Binds given event handler with a given name to only run once then unbind itself.
341 |      | eve.once("login", f);
342 |      | eve("login"); // triggers f
343 |      | eve("login"); // no listeners
344 |      * Use @eve to trigger the listener.
345 |      **
346 |      > Arguments
347 |      **
348 |      - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
349 |      - f (function) event handler function
350 |      **
351 |      = (function) same return function as @eve.on
352 |     \*/
353 |     eve.once = function (name, f) {
354 |         var f2 = function () {
355 |             eve.unbind(name, f2);
356 |             return f.apply(this, arguments);
357 |         };
358 |         return eve.on(name, f2);
359 |     };
360 |     /*\
361 |      * eve.version
362 |      [ property (string) ]
363 |      **
364 |      * Current version of the library.
365 |     \*/
366 |     eve.version = version;
367 |     eve.toString = function () {
368 |         return "You are running Eve " + version;
369 |     };
370 |     (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
371 | })(this);
372 | 


--------------------------------------------------------------------------------
/src/third-party/eve/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name"    : "eve",
 3 | 
 4 |     "author"  : {
 5 |         "name"  : "Dmitry Baranovskiy",
 6 |         "email" : "dmitry@baranovskiy.com",
 7 |         "url"   : "http://dmitry.baranovskiy.com"
 8 |     },
 9 | 	"description" : "Simple custom events",
10 |     "version" : "0.4.1",
11 | 
12 |     "main"    : "./eve.js",
13 | 
14 |     "repository": {
15 |         "type": "git",
16 |         "url": "git@github.com:adobe-webplatform/eve.git"
17 |     }
18 | }
19 | 


--------------------------------------------------------------------------------
/src/third-party/lodash/LICENSE.txt:
--------------------------------------------------------------------------------
 1 | Copyright 2012-2013 The Dojo Foundation 
 2 | Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
 3 | DocumentCloud and Investigative Reporters & Editors 
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining
 6 | a copy of this software and associated documentation files (the
 7 | "Software"), to deal in the Software without restriction, including
 8 | without limitation the rights to use, copy, modify, merge, publish,
 9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 | 
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 | 
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


--------------------------------------------------------------------------------
/src/third-party/requirejs/require.js:
--------------------------------------------------------------------------------
 1 | /*
 2 |  RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 3 |  Available via the MIT or new BSD license.
 4 |  see: http://github.com/jrburke/requirejs for details
 5 | */
 6 | var requirejs,require,define;
 7 | (function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var e;for(e=0;ethis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{d=i.execCb(c,m,b,d)}catch(e){a=e}else d=i.execCb(c,m,b,d);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==
19 | this.exports?d=b.exports:void 0===d&&this.usingExports&&(d=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else d=m;this.exports=d;if(this.map.isDefine&&!this.ignore&&(r[c]=d,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
20 | !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,e=n(a.prefix);this.depMaps.push(e);s(e,"defined",u(this,function(d){var m,e;e=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(d.normalize&&(e=d.normalize(e,function(a){return c(a,g,!0)})||""),d=n(a.prefix+"!"+e,this.map.parentMap),s(d,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),
21 | e=l(p,d.id)){this.depMaps.push(d);if(this.events.error)e.on("error",u(this,function(a){this.emit("error",a)}));e.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(d,c){var e=a.name,g=n(e),B=O;c&&(d=c);B&&(O=!1);q(g);t(k.config,b)&&(k.config[e]=k.config[b]);try{j.exec(d)}catch(ca){return v(A("fromtexteval",
22 | "fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(e);h([e],m)}),d.load(a.name,h,m,k)}));i.enable(e,this);this.pluginMaps[e.id]=e},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,d;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.defineDep(b,
23 | a);this.check()}));this.errback&&s(a,"error",u(this,this.errback))}c=a.id;d=p[c];!t(N,c)&&(d&&!d.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n,
24 | nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,d={paths:!0,config:!0,map:!0};F(a,function(a,b){d[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,
25 | location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function h(d,c,e){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof d){if(H(c))return v(A("requireargs",
26 | "Invalid require call"),e);if(a&&t(N,d))return N[d](p[a.id]);if(j.get)return j.get(i,d,a,h);g=n(d,a,!1,!0);g=g.id;return!t(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(d,c,e,{enabled:!0});C()});return h}f=f||{};Q(h,{isBrowser:z,toUrl:function(b){var f,e=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==e&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",
34 | b.onScriptError,!1)),h.src=e,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(e),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+e,l,[c]))}};z&&!s.skipDataMain&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,s.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",s.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),s.deps=s.deps?s.deps.concat(q):
35 | [q],!0});define=function(b,c,e){var h,j;"string"!==typeof b&&(e=c,c=b,b=null);I(c)||(e=c,c=null);!c&&H(e)&&(c=[],e.length&&(e.toString().replace(la,"").replace(ma,function(b,e){c.push(e)}),c=(1===e.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j?
36 | j.defQueue:R).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(s)}})(this);
37 | 


--------------------------------------------------------------------------------
/src/third-party/snap.plugins/snap.plugins.js:
--------------------------------------------------------------------------------
 1 | // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 2 | // 
 3 | // Licensed under the Apache License, Version 2.0 (the "License");
 4 | // you may not use this file except in compliance with the License.
 5 | // You may obtain a copy of the License at
 6 | // 
 7 | // http://www.apache.org/licenses/LICENSE-2.0
 8 | // 
 9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | (function (root, factory) {
15 |     if (typeof define === 'function' && define.amd) {
16 |         // AMD. Register as an anonymous module. Require Snap dependency
17 |         define(['snap'], function(Snap) {
18 |             return factory(Snap || root.Snap);
19 |         });
20 |     } else {
21 |         factory(Snap);
22 |     }
23 | }(this, function(Snap) {
24 |     Snap.plugin(function (Snap, Element, Paper, glob) {
25 |         var elproto = Element.prototype;
26 |         elproto.toFront = function() {
27 |             this.appendTo(this.paper);
28 |             return this
29 | 
30 |         };
31 |         elproto.toBack = function() {
32 |             this.prependTo(this.paper);
33 |             return this
34 |         };
35 |     })
36 | }));
37 | 


--------------------------------------------------------------------------------
/src/third-party/snap/LICENSE.txt:
--------------------------------------------------------------------------------
  1 | 
  2 |                                  Apache License
  3 |                            Version 2.0, January 2004
  4 |                         http://www.apache.org/licenses/
  5 | 
  6 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  7 | 
  8 |    1. Definitions.
  9 | 
 10 |       "License" shall mean the terms and conditions for use, reproduction,
 11 |       and distribution as defined by Sections 1 through 9 of this document.
 12 | 
 13 |       "Licensor" shall mean the copyright owner or entity authorized by
 14 |       the copyright owner that is granting the License.
 15 | 
 16 |       "Legal Entity" shall mean the union of the acting entity and all
 17 |       other entities that control, are controlled by, or are under common
 18 |       control with that entity. For the purposes of this definition,
 19 |       "control" means (i) the power, direct or indirect, to cause the
 20 |       direction or management of such entity, whether by contract or
 21 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 22 |       outstanding shares, or (iii) beneficial ownership of such entity.
 23 | 
 24 |       "You" (or "Your") shall mean an individual or Legal Entity
 25 |       exercising permissions granted by this License.
 26 | 
 27 |       "Source" form shall mean the preferred form for making modifications,
 28 |       including but not limited to software source code, documentation
 29 |       source, and configuration files.
 30 | 
 31 |       "Object" form shall mean any form resulting from mechanical
 32 |       transformation or translation of a Source form, including but
 33 |       not limited to compiled object code, generated documentation,
 34 |       and conversions to other media types.
 35 | 
 36 |       "Work" shall mean the work of authorship, whether in Source or
 37 |       Object form, made available under the License, as indicated by a
 38 |       copyright notice that is included in or attached to the work
 39 |       (an example is provided in the Appendix below).
 40 | 
 41 |       "Derivative Works" shall mean any work, whether in Source or Object
 42 |       form, that is based on (or derived from) the Work and for which the
 43 |       editorial revisions, annotations, elaborations, or other modifications
 44 |       represent, as a whole, an original work of authorship. For the purposes
 45 |       of this License, Derivative Works shall not include works that remain
 46 |       separable from, or merely link (or bind by name) to the interfaces of,
 47 |       the Work and Derivative Works thereof.
 48 | 
 49 |       "Contribution" shall mean any work of authorship, including
 50 |       the original version of the Work and any modifications or additions
 51 |       to that Work or Derivative Works thereof, that is intentionally
 52 |       submitted to Licensor for inclusion in the Work by the copyright owner
 53 |       or by an individual or Legal Entity authorized to submit on behalf of
 54 |       the copyright owner. For the purposes of this definition, "submitted"
 55 |       means any form of electronic, verbal, or written communication sent
 56 |       to the Licensor or its representatives, including but not limited to
 57 |       communication on electronic mailing lists, source code control systems,
 58 |       and issue tracking systems that are managed by, or on behalf of, the
 59 |       Licensor for the purpose of discussing and improving the Work, but
 60 |       excluding communication that is conspicuously marked or otherwise
 61 |       designated in writing by the copyright owner as "Not a Contribution."
 62 | 
 63 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 64 |       on behalf of whom a Contribution has been received by Licensor and
 65 |       subsequently incorporated within the Work.
 66 | 
 67 |    2. Grant of Copyright License. Subject to the terms and conditions of
 68 |       this License, each Contributor hereby grants to You a perpetual,
 69 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 70 |       copyright license to reproduce, prepare Derivative Works of,
 71 |       publicly display, publicly perform, sublicense, and distribute the
 72 |       Work and such Derivative Works in Source or Object form.
 73 | 
 74 |    3. Grant of Patent License. Subject to the terms and conditions of
 75 |       this License, each Contributor hereby grants to You a perpetual,
 76 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 77 |       (except as stated in this section) patent license to make, have made,
 78 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 79 |       where such license applies only to those patent claims licensable
 80 |       by such Contributor that are necessarily infringed by their
 81 |       Contribution(s) alone or by combination of their Contribution(s)
 82 |       with the Work to which such Contribution(s) was submitted. If You
 83 |       institute patent litigation against any entity (including a
 84 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 85 |       or a Contribution incorporated within the Work constitutes direct
 86 |       or contributory patent infringement, then any patent licenses
 87 |       granted to You under this License for that Work shall terminate
 88 |       as of the date such litigation is filed.
 89 | 
 90 |    4. Redistribution. You may reproduce and distribute copies of the
 91 |       Work or Derivative Works thereof in any medium, with or without
 92 |       modifications, and in Source or Object form, provided that You
 93 |       meet the following conditions:
 94 | 
 95 |       (a) You must give any other recipients of the Work or
 96 |           Derivative Works a copy of this License; and
 97 | 
 98 |       (b) You must cause any modified files to carry prominent notices
 99 |           stating that You changed the files; and
100 | 
101 |       (c) You must retain, in the Source form of any Derivative Works
102 |           that You distribute, all copyright, patent, trademark, and
103 |           attribution notices from the Source form of the Work,
104 |           excluding those notices that do not pertain to any part of
105 |           the Derivative Works; and
106 | 
107 |       (d) If the Work includes a "NOTICE" text file as part of its
108 |           distribution, then any Derivative Works that You distribute must
109 |           include a readable copy of the attribution notices contained
110 |           within such NOTICE file, excluding those notices that do not
111 |           pertain to any part of the Derivative Works, in at least one
112 |           of the following places: within a NOTICE text file distributed
113 |           as part of the Derivative Works; within the Source form or
114 |           documentation, if provided along with the Derivative Works; or,
115 |           within a display generated by the Derivative Works, if and
116 |           wherever such third-party notices normally appear. The contents
117 |           of the NOTICE file are for informational purposes only and
118 |           do not modify the License. You may add Your own attribution
119 |           notices within Derivative Works that You distribute, alongside
120 |           or as an addendum to the NOTICE text from the Work, provided
121 |           that such additional attribution notices cannot be construed
122 |           as modifying the License.
123 | 
124 |       You may add Your own copyright statement to Your modifications and
125 |       may provide additional or different license terms and conditions
126 |       for use, reproduction, or distribution of Your modifications, or
127 |       for any such Derivative Works as a whole, provided Your use,
128 |       reproduction, and distribution of the Work otherwise complies with
129 |       the conditions stated in this License.
130 | 
131 |    5. Submission of Contributions. Unless You explicitly state otherwise,
132 |       any Contribution intentionally submitted for inclusion in the Work
133 |       by You to the Licensor shall be under the terms and conditions of
134 |       this License, without any additional terms or conditions.
135 |       Notwithstanding the above, nothing herein shall supersede or modify
136 |       the terms of any separate license agreement you may have executed
137 |       with Licensor regarding such Contributions.
138 | 
139 |    6. Trademarks. This License does not grant permission to use the trade
140 |       names, trademarks, service marks, or product names of the Licensor,
141 |       except as required for reasonable and customary use in describing the
142 |       origin of the Work and reproducing the content of the NOTICE file.
143 | 
144 |    7. Disclaimer of Warranty. Unless required by applicable law or
145 |       agreed to in writing, Licensor provides the Work (and each
146 |       Contributor provides its Contributions) on an "AS IS" BASIS,
147 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 |       implied, including, without limitation, any warranties or conditions
149 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 |       PARTICULAR PURPOSE. You are solely responsible for determining the
151 |       appropriateness of using or redistributing the Work and assume any
152 |       risks associated with Your exercise of permissions under this License.
153 | 
154 |    8. Limitation of Liability. In no event and under no legal theory,
155 |       whether in tort (including negligence), contract, or otherwise,
156 |       unless required by applicable law (such as deliberate and grossly
157 |       negligent acts) or agreed to in writing, shall any Contributor be
158 |       liable to You for damages, including any direct, indirect, special,
159 |       incidental, or consequential damages of any character arising as a
160 |       result of this License or out of the use or inability to use the
161 |       Work (including but not limited to damages for loss of goodwill,
162 |       work stoppage, computer failure or malfunction, or any and all
163 |       other commercial damages or losses), even if such Contributor
164 |       has been advised of the possibility of such damages.
165 | 
166 |    9. Accepting Warranty or Additional Liability. While redistributing
167 |       the Work or Derivative Works thereof, You may choose to offer,
168 |       and charge a fee for, acceptance of support, warranty, indemnity,
169 |       or other liability obligations and/or rights consistent with this
170 |       License. However, in accepting such obligations, You may act only
171 |       on Your own behalf and on Your sole responsibility, not on behalf
172 |       of any other Contributor, and only if You agree to indemnify,
173 |       defend, and hold each Contributor harmless for any liability
174 |       incurred by, or claims asserted against, such Contributor by reason
175 |       of your accepting any such warranty or additional liability.
176 | 
177 |    END OF TERMS AND CONDITIONS
178 | 
179 |    APPENDIX: How to apply the Apache License to your work.
180 | 
181 |       To apply the Apache License to your work, attach the following
182 |       boilerplate notice, with the fields enclosed by brackets "[]"
183 |       replaced with your own identifying information. (Don't include
184 |       the brackets!)  The text should be enclosed in the appropriate
185 |       comment syntax for the file format. We also recommend that a
186 |       file or class name and description of purpose be included on the
187 |       same "printed page" as the copyright notice for easier
188 |       identification within third-party archives.
189 | 
190 |    Copyright 2013 Adobe Systems Incorporated
191 | 
192 |    Licensed under the Apache License, Version 2.0 (the "License");
193 |    you may not use this file except in compliance with the License.
194 |    You may obtain a copy of the License at
195 | 
196 |        http://www.apache.org/licenses/LICENSE-2.0
197 | 
198 |    Unless required by applicable law or agreed to in writing, software
199 |    distributed under the License is distributed on an "AS IS" BASIS,
200 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 |    See the License for the specific language governing permissions and
202 |    limitations under the License.


--------------------------------------------------------------------------------
/test/SpecRunner.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |   
 5 |   
18 |   
19 |   Jasmine Spec Runner | CSS Shapes Editor
20 | 
21 |   
22 |   
23 |   
24 |   
25 | 
26 | 
27 | 
28 |   
29 | 30 | 31 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*global require, jasmine */ 16 | require.config({ 17 | // set baseUrl to src/ 18 | baseUrl: '../src/', 19 | paths: { 20 | 'jquery': 'third-party/jquery/jquery.min', 21 | 'text': 'third-party/requirejs/text', 22 | 'eve': 'third-party/eve/eve', 23 | 'snap': 'third-party/snap/snap.svg-min', 24 | 'snap.plugins': 'third-party/snap.plugins/snap.plugins', 25 | 'snap.freeTransform': 'third-party/snap.freetransform/snap.freetransform', 26 | 'spec': '../test/spec', 27 | 'lodash': 'third-party/lodash/lodash' 28 | } 29 | }); 30 | require([ 31 | 'text', 32 | 'spec/CSSShapesEditorSpec', 33 | 'spec/PolygonEditorSpec', 34 | 'spec/CircleEditorSpec', 35 | 'spec/EllipseEditorSpec', 36 | 'spec/CSSUtilsSpec' 37 | ], function(){ 38 | 'use strict'; 39 | var env = jasmine.getEnv(); 40 | env.addReporter(new jasmine.HtmlReporter()); 41 | env.execute(); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/CSSShapesEditorSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ 16 | /*global define, describe, it, expect, beforeEach, afterEach, waits, waitsFor, runs, $, waitsForDone, spyOn */ 17 | 18 | // see main.js for path mapping config 19 | define(['jquery', 'text!spec/test-files/markup.html', 'CSSShapesEditor', 'PolygonEditor', 'CircleEditor'], 20 | function($, markup, CSSShapesEditor, PolygonEditor, CircleEditor){ 21 | 22 | // create fixture placeholder for other suites 23 | $('body').append($('
')) 24 | 25 | describe('CSSShapesEditor', function(){ 26 | var editor, 27 | target, 28 | value = 'polygon(nonzero, 0 0, 100px 0, 100px 100px)', 29 | $fixture = $('#test-fixture').html(markup); 30 | 31 | beforeEach(function(){ 32 | // inject markup for test 33 | $fixture.html(markup) 34 | target = $('#test-shape')[0] 35 | }) 36 | 37 | afterEach(function(){ 38 | editor.remove() 39 | $fixture.empty() 40 | }) 41 | 42 | it('should be defined', function(){ 43 | editor = new CSSShapesEditor(target, value); 44 | expect(editor).toBeDefined(); 45 | }); 46 | 47 | it('should return throw error when setup with undefined value', function(){ 48 | function setupWithUndefined(){ 49 | editor = new CSSShapesEditor(undefined, undefined); 50 | } 51 | 52 | function setupWithNull(){ 53 | editor = new CSSShapesEditor(null, null); 54 | } 55 | 56 | expect(setupWithUndefined).toThrow(); 57 | expect(setupWithNull).toThrow(); 58 | }); 59 | 60 | it('should return instance of polygon editor', function(){ 61 | var value = 'polygon(nonzero, 0 0, 100px 0, 100px 100px)'; 62 | 63 | editor = new CSSShapesEditor(target, value); 64 | expect(editor instanceof PolygonEditor).toBe(true); 65 | }); 66 | 67 | it('should return instance of polygon editor with type polygon', function(){ 68 | var value = 'polygon(nonzero, 0 0, 100px 0, 100px 100px)'; 69 | 70 | editor = new CSSShapesEditor(target, value); 71 | expect(editor instanceof PolygonEditor).toBe(true); 72 | expect(editor.type).toBe('polygon'); 73 | }); 74 | 75 | it('should return instance of circle editor', function(){ 76 | var value = 'circle(50% at 50% 50%)'; 77 | 78 | editor = new CSSShapesEditor(target, value); 79 | expect(editor instanceof CircleEditor).toBe(true); 80 | }); 81 | 82 | it('should throw error for unknown shape in value', function(){ 83 | var value = 'fake-shape()'; 84 | 85 | var setup = function() { 86 | editor = new CSSShapesEditor(target, value); 87 | }; 88 | 89 | expect(setup).toThrow(); 90 | }); 91 | 92 | it('should throw error for invalid value', function(){ 93 | var setupWithUndefined = function() { 94 | editor = new CSSShapesEditor(target, undefined); 95 | }; 96 | 97 | var setupWithNull = function() { 98 | editor = new CSSShapesEditor(target, null); 99 | }; 100 | 101 | var setupWithEmpty = function() { 102 | editor = new CSSShapesEditor(target, ''); 103 | }; 104 | 105 | var setupWithZero = function() { 106 | editor = new CSSShapesEditor(target, 0); 107 | }; 108 | 109 | expect(setupWithUndefined).toThrow(); 110 | expect(setupWithNull).toThrow(); 111 | expect(setupWithEmpty).toThrow(); 112 | expect(setupWithZero).toThrow(); 113 | }); 114 | 115 | it('should throw error for invalid target', function(){ 116 | var setupWithUndefined = function() { 117 | editor = new CSSShapesEditor(undefined, value); 118 | }; 119 | 120 | var setupWithNull = function() { 121 | editor = new CSSShapesEditor(null, value); 122 | }; 123 | 124 | var setupWithEmpty = function() { 125 | editor = new CSSShapesEditor('', value); 126 | }; 127 | 128 | var setupWithZero = function() { 129 | editor = new CSSShapesEditor(0, value); 130 | }; 131 | 132 | 133 | expect(setupWithUndefined).toThrow(); 134 | expect(setupWithNull).toThrow(); 135 | expect(setupWithEmpty).toThrow(); 136 | expect(setupWithZero).toThrow(); 137 | }); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /test/spec/CSSUtilsSpec.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ 16 | /*global define, describe, it, expect, beforeEach, afterEach, waits, waitsFor, runs, $, waitsForDone, spyOn */ 17 | 18 | // see main.js for path mapping config 19 | define(['jquery', 'text!spec/test-files/cssutils-markup.html', 'CSSUtils'], 20 | function($, markup, CSSUtils){ 21 | 'use strict'; 22 | 23 | describe('CSSUtils', function(){ 24 | var editor, 25 | target, 26 | $fixture = $('#test-fixture').html(markup); 27 | 28 | beforeEach(function(){ 29 | // inject markup for test 30 | $fixture.html(markup); 31 | target = $('#box')[0]; 32 | }); 33 | 34 | afterEach(function(){ 35 | $fixture.empty(); 36 | }); 37 | 38 | it('should be defined', function(){ 39 | // expect(CSSUtils).toBeDefined(); 40 | }); 41 | 42 | describe('.getBox()', function(){ 43 | it('should be defined', function(){ 44 | expect(CSSUtils.getBox).toBeDefined(); 45 | }); 46 | 47 | it('should throw error for unrecognized box type', function(){ 48 | function fakeBox(){ 49 | CSSUtils.getBox(target, 'fake-box'); 50 | } 51 | 52 | expect(fakeBox).toThrow(); 53 | }); 54 | 55 | it('should compute content box', function(){ 56 | var box = CSSUtils.getBox(target, 'content-box'); 57 | expect(box.width).toEqual(400); 58 | expect(box.height).toEqual(400); 59 | expect(box.top).toEqual(100); 60 | expect(box.left).toEqual(100); 61 | }); 62 | 63 | it('should compute padding box', function(){ 64 | var box = CSSUtils.getBox(target, 'padding-box'); 65 | expect(box.width).toEqual(500); 66 | expect(box.height).toEqual(500); 67 | expect(box.top).toEqual(50); 68 | expect(box.left).toEqual(50); 69 | }); 70 | 71 | it('should compute border box', function(){ 72 | // 'box-sizing' is default content-box; values are additive 73 | var box = CSSUtils.getBox(target, 'border-box'); 74 | expect(box.width).toEqual(600); 75 | expect(box.height).toEqual(600); 76 | expect(box.top).toEqual(0); 77 | expect(box.left).toEqual(0); 78 | }); 79 | 80 | it('should compute margin box', function(){ 81 | var box = CSSUtils.getBox(target, 'margin-box'); 82 | expect(box.width).toEqual(700); 83 | expect(box.height).toEqual(700); 84 | expect(box.top).toEqual(-50); 85 | expect(box.left).toEqual(-50); 86 | }); 87 | 88 | }); 89 | 90 | describe('Get origin', function(){ 91 | function _decodeOrigin(input, expected){ 92 | var origin = CSSUtils.getOriginCoords(input); 93 | expect(origin).toEqual(expected); 94 | } 95 | 96 | it('should have getOriginCoords method', function(){ 97 | expect(CSSUtils.getOriginCoords).toBeDefined(); 98 | }); 99 | 100 | it('should return default Y for single X position keyword', function(){ 101 | _decodeOrigin('left', { x: '0%', y: '50%' }); 102 | _decodeOrigin('right', { x: '100%', y: '50%' }); 103 | }); 104 | 105 | it('should return default Y for single X as CSS unit', function(){ 106 | _decodeOrigin('0', { x: '0', y: '50%' }); 107 | }); 108 | 109 | it('should return default X for single Y position keyword', function(){ 110 | _decodeOrigin('top', { x: '50%', y: '0%' }); 111 | _decodeOrigin('bottom', { x: '50%', y: '100%' }); 112 | }); 113 | 114 | it('should return center X,Y for single "center" position keyword', function(){ 115 | _decodeOrigin('center', { x: '50%', y: '50%' }); 116 | }); 117 | 118 | it('should return X,Y position as CSS units', function(){ 119 | _decodeOrigin('50% 100%', { x: '50%', y: '100%' }); 120 | }); 121 | 122 | it('should decode "center" keyword to default X,Y', function(){ 123 | _decodeOrigin('center 100%', { x: '50%', y: '100%' }); 124 | _decodeOrigin('50% center', { x: '50%', y: '50%' }); 125 | }); 126 | 127 | it('should parse css units', function(){ 128 | _decodeOrigin('0 0', { x: '0', y: '0' }); 129 | _decodeOrigin('100px 0', { x: '100px', y: '0' }); 130 | _decodeOrigin('0 100px', { x: '0', y: '100px' }); 131 | _decodeOrigin('99rem 1%', { x: '99rem', y: '1%' }); 132 | }); 133 | 134 | it('should parse mixed keywords and css units', function(){ 135 | _decodeOrigin('0 top', { x: '0', y: '0%' }); 136 | _decodeOrigin('0 center', { x: '0', y: '50%' }); 137 | _decodeOrigin('0 bottom', { x: '0', y: '100%' }); 138 | _decodeOrigin('left 0', { x: '0%', y: '0' }); 139 | _decodeOrigin('center 0', { x: '50%', y: '0' }); 140 | _decodeOrigin('right 0', { x: '100%', y: '0' }); 141 | }); 142 | 143 | it('should decode position keywords for X,Y', function(){ 144 | _decodeOrigin('left top', { x: '0%', y: '0%' }); 145 | _decodeOrigin('left center', { x: '0%', y: '50%' }); 146 | _decodeOrigin('left bottom', { x: '0%', y: '100%' }); 147 | _decodeOrigin('center top', { x: '50%', y: '0%' }); 148 | _decodeOrigin('center center', { x: '50%', y: '50%' }); 149 | _decodeOrigin('center bottom', { x: '50%', y: '100%' }); 150 | _decodeOrigin('right top', { x: '100%', y: '0%' }); 151 | _decodeOrigin('right center', { x: '100%', y: '50%' }); 152 | _decodeOrigin('right bottom', { x: '100%', y: '100%' }); 153 | }); 154 | 155 | it('should perform keyword swap when both ar keywords, but the order is incorrect', function(){ 156 | _decodeOrigin('top left', { x: '0%', y: '0%' }); 157 | _decodeOrigin('top center', { x: '50%', y: '0%' }); 158 | _decodeOrigin('bottom right', { x: '100%', y: '100%' }); 159 | _decodeOrigin('bottom center', { x: '50%', y: '100%' }); 160 | }); 161 | 162 | it('should throw error when both X,Y are position keywords on the same axis', function(){ 163 | function hAxis(){ 164 | CSSUtils.getOriginCoords('left right'); 165 | } 166 | function vAxis(){ 167 | CSSUtils.getOriginCoords('top bottom'); 168 | } 169 | 170 | expect(hAxis).toThrow(); 171 | expect(vAxis).toThrow(); 172 | }); 173 | 174 | it('should not throw error when X, Y are both position keywords but in the wrong order', function(){ 175 | function wrongOrder1(){ 176 | CSSUtils.getOriginCoords('top left'); 177 | } 178 | 179 | function wrongOrder2(){ 180 | CSSUtils.getOriginCoords('bottom right'); 181 | } 182 | 183 | expect(wrongOrder1).not.toThrow(); 184 | expect(wrongOrder2).not.toThrow(); 185 | }); 186 | 187 | it('should throw error when either X or Y are keywords in the wrong order and the sibling is not also a position keyword (no keyword swap condition)', function(){ 188 | function xWrongOrder(){ 189 | CSSUtils.getOriginCoords('0 left'); 190 | } 191 | 192 | function yWrongOrder(){ 193 | CSSUtils.getOriginCoords('bottom 0'); 194 | } 195 | 196 | expect(xWrongOrder).toThrow(); 197 | expect(yWrongOrder).toThrow(); 198 | }); 199 | }); 200 | 201 | describe('Supported unit types', function(){ 202 | it ('should expose array of unit types', function(){ 203 | var units = ["px", "in", "cm", "mm", "pt", "pc", "em", "rem", "vw", "vh", "%"]; 204 | expect(CSSUtils.units).toEqual(units); 205 | }); 206 | }); 207 | 208 | 209 | describe('Convert from pixels', function(){ 210 | 211 | }); 212 | 213 | describe('Convert to pixels', function(){ 214 | 215 | }); 216 | 217 | 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /test/spec/test-files/cssutils-markup.html: -------------------------------------------------------------------------------- 1 | 16 |
17 | 26 |
27 | -------------------------------------------------------------------------------- /test/spec/test-files/markup.html: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | 24 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 25 |
26 | -------------------------------------------------------------------------------- /test/third-party/jasmine/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/third-party/jasmine/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "launch_in_dev" : [ 3 | "chrome" 4 | ], 5 | "src_files": [ 6 | "src/**/*.js", 7 | "test/spec/*.js" 8 | ], 9 | "test_page" : "test/SpecRunner.html" 10 | } 11 | --------------------------------------------------------------------------------