├── LICENSE ├── Makefile ├── README.md ├── XGetopt.c ├── XGetopt.h ├── nanosvg.h └── svg2gcode.c /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = clang 2 | CFLAGS = -Ofast 3 | 4 | all: svg2gcode 5 | 6 | svg2gcode: svg2gcode.c nanosvg.h 7 | $(CC) -o svg2gcode -g svg2gcode.c -lm 8 | 9 | clean: 10 | rm -fr svg2code *.o 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | svg2gcode 2 | ========= 3 | 4 | an svg-file to gcode converter using optimized paths. Written in C and nanosvg.h by Mikko Mononen. 5 | Compiles on OS X, Linux, Windows or maybe on every platform having C-compiler. 6 | 7 | The paths are optimized to minimize unnecessary movements of the cutter or whatever you're using. 8 | 9 | Tested on Linux, OS X and Win7. To change the inits and and other needed g-codes, just change the #defines and compile. 10 | No extra needed libraries. 11 | 12 | 13 | -------------------------------------------------------------------------------- /XGetopt.c: -------------------------------------------------------------------------------- 1 | // XGetopt.cpp Version 1.1 2 | // 3 | // Author: Hans Dietrich 4 | // hdietrich2@hotmail.com 5 | // 6 | // Modified: David Smith 7 | // dave.s@earthcorp.com 8 | // Moved two char declarations from body of function so 9 | // that it can compile as a C function. 10 | // Thanks so much Hans 11 | // 12 | // This software is released into the public domain. 13 | // You are free to use it in any way you like. 14 | // 15 | // This software is provided "as is" with no expressed 16 | // or implied warranty. I accept no liability for any 17 | // damage or loss of business that this software may cause. 18 | // 19 | /////////////////////////////////////////////////////////////////////////////// 20 | 21 | #include 22 | #include 23 | #include "XGetopt.h" 24 | 25 | /////////////////////////////////////////////////////////////////////////////// 26 | // 27 | // X G e t o p t . c p p 28 | // 29 | // 30 | // NAME 31 | // getopt -- parse command line options 32 | // 33 | // SYNOPSIS 34 | // int getopt(int argc, char *argv[], char *optstring) 35 | // 36 | // extern char *optarg; 37 | // extern int optind; 38 | // 39 | // DESCRIPTION 40 | // The getopt() function parses the command line arguments. Its 41 | // arguments argc and argv are the argument count and array as 42 | // passed into the application on program invocation. In the case 43 | // of Visual C++ programs, argc and argv are available via the 44 | // variables __argc and __argv (double underscores), respectively. 45 | // getopt returns the next option letter in argv that matches a 46 | // letter in optstring. 47 | // 48 | // optstring is a string of recognized option letters; if a letter 49 | // is followed by a colon, the option is expected to have an argument 50 | // that may or may not be separated from it by white space. optarg 51 | // is set to point to the start of the option argument on return from 52 | // getopt. 53 | // 54 | // Option letters may be combined, e.g., "-ab" is equivalent to 55 | // "-a -b". Option letters are case sensitive. 56 | // 57 | // getopt places in the external variable optind the argv index 58 | // of the next argument to be processed. optind is initialized 59 | // to 0 before the first call to getopt. 60 | // 61 | // When all options have been processed (i.e., up to the first 62 | // non-option argument), getopt returns EOF, optarg will point 63 | // to the argument, and optind will be set to the argv index of 64 | // the argument. If there are no non-option arguments, optarg 65 | // will be set to NULL. 66 | // 67 | // The special option "--" may be used to delimit the end of the 68 | // options; EOF will be returned, and "--" (and everything after it) 69 | // will be skipped. 70 | // 71 | // RETURN VALUE 72 | // For option letters contained in the string optstring, getopt 73 | // will return the option letter. getopt returns a question mark (?) 74 | // when it encounters an option letter not included in optstring. 75 | // EOF is returned when processing is finished. 76 | // 77 | // BUGS 78 | // 1) Long options are not supported. 79 | // 2) The GNU double-colon extension is not supported. 80 | // 3) The environment variable POSIXLY_CORRECT is not supported. 81 | // 4) The + syntax is not supported. 82 | // 5) The automatic permutation of arguments is not supported. 83 | // 6) This implementation of getopt() returns EOF if an error is 84 | // encountered, instead of -1 as the latest standard requires. 85 | // 86 | // EXAMPLE 87 | // BOOL CMyApp::ProcessCommandLine(int argc, char *argv[]) 88 | // { 89 | // int c; 90 | // 91 | // while ((c = getopt(argc, argv, "aBn:")) != EOF) 92 | // { 93 | // switch (c) 94 | // { 95 | // case 'a': 96 | // TRACE(_T("option a\n")); 97 | // // 98 | // // set some flag here 99 | // // 100 | // break; 101 | // 102 | // case 'B': 103 | // TRACE( _T("option B\n")); 104 | // // 105 | // // set some other flag here 106 | // // 107 | // break; 108 | // 109 | // case 'n': 110 | // TRACE(_T("option n: value=%d\n"), atoi(optarg)); 111 | // // 112 | // // do something with value here 113 | // // 114 | // break; 115 | // 116 | // case '?': 117 | // TRACE(_T("ERROR: illegal option %s\n"), argv[optind-1]); 118 | // return FALSE; 119 | // break; 120 | // 121 | // default: 122 | // TRACE(_T("WARNING: no handler for option %c\n"), c); 123 | // return FALSE; 124 | // break; 125 | // } 126 | // } 127 | // // 128 | // // check for non-option args here 129 | // // 130 | // return TRUE; 131 | // } 132 | // 133 | /////////////////////////////////////////////////////////////////////////////// 134 | 135 | char *optarg; // global argument pointer 136 | int optind = 0; // global argv index 137 | 138 | int getopt(int argc, char *argv[], char *optstring) 139 | { 140 | static char *next = NULL; 141 | char c, *cp; 142 | if (optind == 0) 143 | next = NULL; 144 | 145 | optarg = NULL; 146 | 147 | if (next == NULL || *next == '\0') 148 | { 149 | if (optind == 0) 150 | optind++; 151 | 152 | if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0') 153 | { 154 | optarg = NULL; 155 | if (optind < argc) 156 | optarg = argv[optind]; 157 | return EOF; 158 | } 159 | 160 | if (strcmp(argv[optind], "--") == 0) 161 | { 162 | optind++; 163 | optarg = NULL; 164 | if (optind < argc) 165 | optarg = argv[optind]; 166 | return EOF; 167 | } 168 | 169 | next = argv[optind]+1; 170 | optind++; 171 | } 172 | 173 | c = *next++; 174 | cp = strchr(optstring, c); 175 | 176 | if (cp == NULL || c == ':') 177 | return '?'; 178 | 179 | cp++; 180 | if (*cp == ':') 181 | { 182 | if (*next != '\0') 183 | { 184 | optarg = next; 185 | next = NULL; 186 | } 187 | else if (optind < argc) 188 | { 189 | optarg = argv[optind]; 190 | optind++; 191 | } 192 | else 193 | { 194 | return '?'; 195 | } 196 | } 197 | 198 | return c; 199 | } 200 | -------------------------------------------------------------------------------- /XGetopt.h: -------------------------------------------------------------------------------- 1 | // XGetopt.h 2 | // 3 | // Author: Hans Dietrich 4 | // hdietrich2@hotmail.com 5 | // 6 | // This software is released into the public domain. 7 | // You are free to use it in any way you like. 8 | // 9 | // This software is provided "as is" with no expressed 10 | // or implied warranty. I accept no liability for any 11 | // damage or loss of business that this software may cause. 12 | // 13 | /////////////////////////////////////////////////////////////////////////////// 14 | 15 | #ifndef XGETOPT_H 16 | #define XGETOPT_H 17 | 18 | extern int optind, opterr; 19 | extern char *optarg; 20 | 21 | int getopt(int argc, char *argv[], char *optstring); 22 | 23 | #endif //XGETOPT_H 24 | -------------------------------------------------------------------------------- /nanosvg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-14 Mikko Mononen memon@inside.org 3 | * 4 | * This software is provided 'as-is', without any express or implied 5 | * warranty. In no event will the authors be held liable for any damages 6 | * arising from the use of this software. 7 | * 8 | * Permission is granted to anyone to use this software for any purpose, 9 | * including commercial applications, and to alter it and redistribute it 10 | * freely, subject to the following restrictions: 11 | * 12 | * 1. The origin of this software must not be misrepresented; you must not 13 | * claim that you wrote the original software. If you use this software 14 | * in a product, an acknowledgment in the product documentation would be 15 | * appreciated but is not required. 16 | * 2. Altered source versions must be plainly marked as such, and must not be 17 | * misrepresented as being the original software. 18 | * 3. This notice may not be removed or altered from any source distribution. 19 | * 20 | * The SVG parser is based on Anti-Graim Geometry 2.4 SVG example 21 | * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) 22 | * 23 | * Arc calculation code based on canvg (https://code.google.com/p/canvg/) 24 | * 25 | * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html 26 | * 27 | */ 28 | 29 | #ifndef NANOSVG_H 30 | #define NANOSVG_H 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | // NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. 37 | // 38 | // The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. 39 | // 40 | // NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! 41 | // 42 | // The shapes in the SVG images are transformed by the viewBox and converted to specified units. 43 | // That is, you should get the same looking data as your designed in your favorite app. 44 | // 45 | // NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose 46 | // to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. 47 | // 48 | // The units passed to NanoVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. 49 | // DPI (dots-per-inch) controls how the unit conversion is done. 50 | // 51 | // If you don't know or care about the units stuff, "px" and 96 should get you going. 52 | 53 | 54 | /* Example Usage: 55 | // Load 56 | SNVGImage* image; 57 | image = nsvgParseFromFile("test.svg", "px", 96); 58 | printf("size: %f x %f\n", image->width, image->height); 59 | // Use... 60 | for (shape = image->shapes; shape != NULL; shape = shape->next) { 61 | for (path = shape->paths; path != NULL; path = path->next) { 62 | for (i = 0; i < path->npts-1; i += 3) { 63 | float* p = &path->pts[i*2]; 64 | drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); 65 | } 66 | } 67 | } 68 | // Delete 69 | nsvgDelete(image); 70 | */ 71 | 72 | #define NSVG_PAINT_NONE 0 73 | #define NSVG_PAINT_COLOR 1 74 | #define NSVG_PAINT_LINEAR_GRADIENT 2 75 | #define NSVG_PAINT_RADIAL_GRADIENT 3 76 | 77 | #define NSVG_SPREAD_PAD 0 78 | #define NSVG_SPREAD_REFLECT 1 79 | #define NSVG_SPREAD_REPEAT 2 80 | 81 | typedef struct NSVGgradientStop { 82 | unsigned int color; 83 | float offset; 84 | } NSVGgradientStop; 85 | 86 | typedef struct NSVGgradient { 87 | float xform[6]; 88 | char spread; 89 | float fx, fy; 90 | int nstops; 91 | NSVGgradientStop stops[1]; 92 | } NSVGgradient; 93 | 94 | typedef struct NSVGpaint { 95 | char type; 96 | union { 97 | unsigned int color; 98 | NSVGgradient* gradient; 99 | }; 100 | } NSVGpaint; 101 | 102 | typedef struct NSVGpath 103 | { 104 | float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... 105 | int npts; // Total number of bezier points. 106 | char closed; // Flag indicating if shapes should be treated as closed. 107 | float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. 108 | struct NSVGpath* next; // Pointer to next path, or NULL if last element. 109 | } NSVGpath; 110 | 111 | typedef struct NSVGshape 112 | { 113 | NSVGpaint fill; // Fill paint 114 | NSVGpaint stroke; // Stroke paint 115 | float opacity; // Opacity of the shape. 116 | float strokeWidth; // Stroke width (scaled) 117 | float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. 118 | NSVGpath* paths; // Linked list of paths in the image. 119 | struct NSVGshape* next; // Pointer to next shape, or NULL if last element. 120 | } NSVGshape; 121 | 122 | typedef struct NSVGimage 123 | { 124 | float width; // Width of the image. 125 | float height; // Height of the image. 126 | NSVGshape* shapes; // Linked list of shapes in the image. 127 | } NSVGimage; 128 | 129 | // Parses SVG file from a file, returns SVG image as paths. 130 | NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); 131 | 132 | // Parses SVG file from a null terminated string, returns SVG image as paths. 133 | NSVGimage* nsvgParse(char* input, const char* units, float dpi); 134 | 135 | // Deletes list of paths. 136 | void nsvgDelete(NSVGimage* image); 137 | 138 | #ifdef __cplusplus 139 | }; 140 | #endif 141 | 142 | #endif // NANOSVG_H 143 | 144 | #ifdef NANOSVG_IMPLEMENTATION 145 | 146 | #include 147 | #include 148 | #include 149 | 150 | #define NSVG_PI (3.14159265358979323846264338327f) 151 | #define NSVG_KAPPA90 (0.5522847493f) // Lenght proportional to radius of a cubic bezier handle for 90deg arcs. 152 | 153 | #define NSVG_ALIGN_MIN 0 154 | #define NSVG_ALIGN_MID 1 155 | #define NSVG_ALIGN_MAX 2 156 | #define NSVG_ALIGN_NONE 0 157 | #define NSVG_ALIGN_MEET 1 158 | #define NSVG_ALIGN_SLICE 2 159 | 160 | #define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) 161 | #define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) 162 | 163 | #ifdef _MSC_VER 164 | #pragma warning (disable: 4996) // Switch off security warnings 165 | #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings 166 | #ifdef __cplusplus 167 | #define NSVG_INLINE inline 168 | #else 169 | #define NSVG_INLINE 170 | #endif 171 | #else 172 | #define NSVG_INLINE inline 173 | #endif 174 | 175 | 176 | static int nsvg__isspace(char c) 177 | { 178 | return strchr(" \t\n\v\f\r", c) != 0; 179 | } 180 | 181 | static int nsvg__isdigit(char c) 182 | { 183 | return strchr("0123456789", c) != 0; 184 | } 185 | 186 | static int nsvg__isnum(char c) 187 | { 188 | return strchr("0123456789+-.eE", c) != 0; 189 | } 190 | 191 | static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } 192 | static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } 193 | 194 | 195 | // Simple XML parser 196 | 197 | #define NSVG_XML_TAG 1 198 | #define NSVG_XML_CONTENT 2 199 | #define NSVG_XML_MAX_ATTRIBS 256 200 | 201 | static void nsvg__parseContent(char* s, 202 | void (*contentCb)(void* ud, const char* s), 203 | void* ud) 204 | { 205 | // Trim start white spaces 206 | while (*s && nsvg__isspace(*s)) s++; 207 | if (!*s) return; 208 | 209 | if (contentCb) 210 | (*contentCb)(ud, s); 211 | } 212 | 213 | static void nsvg__parseElement(char* s, 214 | void (*startelCb)(void* ud, const char* el, const char** attr), 215 | void (*endelCb)(void* ud, const char* el), 216 | void* ud) 217 | { 218 | const char* attr[NSVG_XML_MAX_ATTRIBS]; 219 | int nattr = 0; 220 | char* name; 221 | int start = 0; 222 | int end = 0; 223 | char quote; 224 | 225 | // Skip white space after the '<' 226 | while (*s && nsvg__isspace(*s)) s++; 227 | 228 | // Check if the tag is end tag 229 | if (*s == '/') { 230 | s++; 231 | end = 1; 232 | } else { 233 | start = 1; 234 | } 235 | 236 | // Skip comments, data and preprocessor stuff. 237 | if (!*s || *s == '?' || *s == '!') 238 | return; 239 | 240 | // Get tag name 241 | name = s; 242 | while (*s && !nsvg__isspace(*s)) s++; 243 | if (*s) { *s++ = '\0'; } 244 | 245 | // Get attribs 246 | while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { 247 | // Skip white space before the attrib name 248 | while (*s && nsvg__isspace(*s)) s++; 249 | if (!*s) break; 250 | if (*s == '/') { 251 | end = 1; 252 | break; 253 | } 254 | attr[nattr++] = s; 255 | // Find end of the attrib name. 256 | while (*s && !nsvg__isspace(*s) && *s != '=') s++; 257 | if (*s) { *s++ = '\0'; } 258 | // Skip until the beginning of the value. 259 | while (*s && *s != '\"' && *s != '\'') s++; 260 | if (!*s) break; 261 | quote = *s; 262 | s++; 263 | // Store value and find the end of it. 264 | attr[nattr++] = s; 265 | while (*s && *s != quote) s++; 266 | if (*s) { *s++ = '\0'; } 267 | } 268 | 269 | // List terminator 270 | attr[nattr++] = 0; 271 | attr[nattr++] = 0; 272 | 273 | // Call callbacks. 274 | if (start && startelCb) 275 | (*startelCb)(ud, name, attr); 276 | if (end && endelCb) 277 | (*endelCb)(ud, name); 278 | } 279 | 280 | int nsvg__parseXML(char* input, 281 | void (*startelCb)(void* ud, const char* el, const char** attr), 282 | void (*endelCb)(void* ud, const char* el), 283 | void (*contentCb)(void* ud, const char* s), 284 | void* ud) 285 | { 286 | char* s = input; 287 | char* mark = s; 288 | int state = NSVG_XML_CONTENT; 289 | while (*s) { 290 | if (*s == '<' && state == NSVG_XML_CONTENT) { 291 | // Start of a tag 292 | *s++ = '\0'; 293 | nsvg__parseContent(mark, contentCb, ud); 294 | mark = s; 295 | state = NSVG_XML_TAG; 296 | } else if (*s == '>' && state == NSVG_XML_TAG) { 297 | // Start of a content or new tag. 298 | *s++ = '\0'; 299 | nsvg__parseElement(mark, startelCb, endelCb, ud); 300 | mark = s; 301 | state = NSVG_XML_CONTENT; 302 | } else { 303 | s++; 304 | } 305 | } 306 | 307 | return 1; 308 | } 309 | 310 | 311 | /* Simple SVG parser. */ 312 | 313 | #define NSVG_MAX_ATTR 128 314 | 315 | #define NSVG_USER_SPACE 0 316 | #define NSVG_OBJECT_SPACE 1 317 | 318 | typedef struct NSVGlinearData { 319 | float x1, y1, x2, y2; 320 | } NSVGlinearData; 321 | 322 | typedef struct NSVGradialData { 323 | float cx, cy, r, fx, fy; 324 | } NSVGradialData; 325 | 326 | typedef struct NSVGgradientData 327 | { 328 | char id[64]; 329 | char ref[64]; 330 | char type; 331 | union { 332 | NSVGlinearData linear; 333 | NSVGradialData radial; 334 | }; 335 | char spread; 336 | char units; 337 | float xform[6]; 338 | int nstops; 339 | NSVGgradientStop* stops; 340 | struct NSVGgradientData* next; 341 | } NSVGgradientData; 342 | 343 | typedef struct NSVGattrib 344 | { 345 | float xform[6]; 346 | unsigned int fillColor; 347 | unsigned int strokeColor; 348 | float opacity; 349 | float fillOpacity; 350 | float strokeOpacity; 351 | char fillGradient[64]; 352 | char strokeGradient[64]; 353 | float strokeWidth; 354 | float fontSize; 355 | unsigned int stopColor; 356 | float stopOpacity; 357 | float stopOffset; 358 | char hasFill; 359 | char hasStroke; 360 | char visible; 361 | } NSVGattrib; 362 | 363 | typedef struct NSVGparser 364 | { 365 | NSVGattrib attr[NSVG_MAX_ATTR]; 366 | int attrHead; 367 | float* pts; 368 | int npts; 369 | int cpts; 370 | NSVGpath* plist; 371 | NSVGimage* image; 372 | NSVGgradientData* gradients; 373 | float viewMinx, viewMiny, viewWidth, viewHeight; 374 | int alignX, alignY, alignType; 375 | float dpi; 376 | char pathFlag; 377 | char defsFlag; 378 | } NSVGparser; 379 | 380 | static void nsvg__xformIdentity(float* t) 381 | { 382 | t[0] = 1.0f; t[1] = 0.0f; 383 | t[2] = 0.0f; t[3] = 1.0f; 384 | t[4] = 0.0f; t[5] = 0.0f; 385 | } 386 | 387 | static void nsvg__xformSetTranslation(float* t, float tx, float ty) 388 | { 389 | t[0] = 1.0f; t[1] = 0.0f; 390 | t[2] = 0.0f; t[3] = 1.0f; 391 | t[4] = tx; t[5] = ty; 392 | } 393 | 394 | static void nsvg__xformSetScale(float* t, float sx, float sy) 395 | { 396 | t[0] = sx; t[1] = 0.0f; 397 | t[2] = 0.0f; t[3] = sy; 398 | t[4] = 0.0f; t[5] = 0.0f; 399 | } 400 | 401 | static void nsvg__xformSetSkewX(float* t, float a) 402 | { 403 | t[0] = 1.0f; t[1] = 0.0f; 404 | t[2] = tanf(a); t[3] = 1.0f; 405 | t[4] = 0.0f; t[5] = 0.0f; 406 | } 407 | 408 | static void nsvg__xformSetSkewY(float* t, float a) 409 | { 410 | t[0] = 1.0f; t[1] = tanf(a); 411 | t[2] = 0.0f; t[3] = 1.0f; 412 | t[4] = 0.0f; t[5] = 0.0f; 413 | } 414 | 415 | static void nsvg__xformSetRotation(float* t, float a) 416 | { 417 | float cs = cosf(a), sn = sinf(a); 418 | t[0] = cs; t[1] = sn; 419 | t[2] = -sn; t[3] = cs; 420 | t[4] = 0.0f; t[5] = 0.0f; 421 | } 422 | 423 | static void nsvg__xformMultiply(float* t, float* s) 424 | { 425 | float t0 = t[0] * s[0] + t[1] * s[2]; 426 | float t2 = t[2] * s[0] + t[3] * s[2]; 427 | float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; 428 | t[1] = t[0] * s[1] + t[1] * s[3]; 429 | t[3] = t[2] * s[1] + t[3] * s[3]; 430 | t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; 431 | t[0] = t0; 432 | t[2] = t2; 433 | t[4] = t4; 434 | } 435 | 436 | static void nsvg__xformInverse(float* inv, float* t) 437 | { 438 | double det = (double)t[0] * t[3] - (double)t[2] * t[1]; 439 | double invdet = 1.0 / det; 440 | if (det > -1e-6 && det < -1e-6) { 441 | nsvg__xformIdentity(t); 442 | return; 443 | } 444 | inv[0] = (float)(t[3] * invdet); 445 | inv[2] = (float)(-t[2] * invdet); 446 | inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); 447 | inv[1] = (float)(-t[1] * invdet); 448 | inv[3] = (float)(t[0] * invdet); 449 | inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); 450 | } 451 | 452 | static void nsvg__xformPremultiply(float* t, float* s) 453 | { 454 | float s2[6]; 455 | memcpy(s2, s, sizeof(float)*6); 456 | nsvg__xformMultiply(s2, t); 457 | memcpy(t, s2, sizeof(float)*6); 458 | } 459 | 460 | static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) 461 | { 462 | *dx = x*t[0] + y*t[2] + t[4]; 463 | *dy = x*t[1] + y*t[3] + t[5]; 464 | } 465 | 466 | static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) 467 | { 468 | *dx = x*t[0] + y*t[2]; 469 | *dy = x*t[1] + y*t[3]; 470 | } 471 | 472 | #define NSVG_EPSILON (1e-12) 473 | 474 | static int nsvg__ptInBounds(float* pt, float* bounds) 475 | { 476 | return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; 477 | } 478 | 479 | 480 | static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) 481 | { 482 | double it = 1.0-t; 483 | return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; 484 | } 485 | 486 | static void nsvg__curveBounds(float* bounds, float* curve) 487 | { 488 | int i, j, count; 489 | double roots[2], a, b, c, b2ac, t, v; 490 | float* v0 = &curve[0]; 491 | float* v1 = &curve[2]; 492 | float* v2 = &curve[4]; 493 | float* v3 = &curve[6]; 494 | 495 | // Start the bounding box by end points 496 | bounds[0] = nsvg__minf(v0[0], v3[0]); 497 | bounds[1] = nsvg__minf(v0[1], v3[1]); 498 | bounds[2] = nsvg__maxf(v0[0], v3[0]); 499 | bounds[3] = nsvg__maxf(v0[1], v3[1]); 500 | 501 | // Bezier curve fits inside the convex hull of it's control points. 502 | // If control points are inside the bounds, we're done. 503 | if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) 504 | return; 505 | 506 | // Add bezier curve inflection points in X and Y. 507 | for (i = 0; i < 2; i++) { 508 | a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; 509 | b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; 510 | c = 3.0 * v1[i] - 3.0 * v0[i]; 511 | count = 0; 512 | if (fabs(a) < NSVG_EPSILON) { 513 | if (fabs(b) > NSVG_EPSILON) { 514 | t = -c / b; 515 | if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) 516 | roots[count++] = t; 517 | } 518 | } else { 519 | b2ac = b*b - 4.0*c*a; 520 | if (b2ac > NSVG_EPSILON) { 521 | t = (-b + sqrt(b2ac)) / (2.0 * a); 522 | if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) 523 | roots[count++] = t; 524 | t = (-b - sqrt(b2ac)) / (2.0 * a); 525 | if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) 526 | roots[count++] = t; 527 | } 528 | } 529 | for (j = 0; j < count; j++) { 530 | v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); 531 | bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); 532 | bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); 533 | } 534 | } 535 | } 536 | 537 | static NSVGparser* nsvg__createParser() 538 | { 539 | NSVGparser* p; 540 | p = (NSVGparser*)malloc(sizeof(NSVGparser)); 541 | if (p == NULL) goto error; 542 | memset(p, 0, sizeof(NSVGparser)); 543 | 544 | p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); 545 | if (p->image == NULL) goto error; 546 | memset(p->image, 0, sizeof(NSVGimage)); 547 | 548 | // Init style 549 | nsvg__xformIdentity(p->attr[0].xform); 550 | p->attr[0].fillColor = NSVG_RGB(0,0,0); 551 | p->attr[0].strokeColor = NSVG_RGB(0,0,0); 552 | p->attr[0].opacity = 1; 553 | p->attr[0].fillOpacity = 1; 554 | p->attr[0].strokeOpacity = 1; 555 | p->attr[0].stopOpacity = 1; 556 | p->attr[0].strokeWidth = 1; 557 | p->attr[0].hasFill = 1; 558 | p->attr[0].hasStroke = 0; 559 | p->attr[0].visible = 1; 560 | 561 | return p; 562 | 563 | error: 564 | if (p) { 565 | if (p->image) free(p->image); 566 | free(p); 567 | } 568 | return NULL; 569 | } 570 | 571 | static void nsvg__deletePaths(NSVGpath* path) 572 | { 573 | while (path) { 574 | NSVGpath *next = path->next; 575 | if (path->pts != NULL) 576 | free(path->pts); 577 | free(path); 578 | path = next; 579 | } 580 | } 581 | 582 | static void nsvg__deletePaint(NSVGpaint* paint) 583 | { 584 | if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_LINEAR_GRADIENT) 585 | free(paint->gradient); 586 | } 587 | 588 | static void nsvg__deleteGradientData(NSVGgradientData* grad) 589 | { 590 | NSVGgradientData* next; 591 | while (grad != NULL) { 592 | next = grad->next; 593 | free(grad->stops); 594 | free(grad); 595 | grad = next; 596 | } 597 | } 598 | 599 | static void nsvg__deleteParser(NSVGparser* p) 600 | { 601 | if (p != NULL) { 602 | nsvg__deletePaths(p->plist); 603 | nsvg__deleteGradientData(p->gradients); 604 | nsvgDelete(p->image); 605 | free(p->pts); 606 | free(p); 607 | } 608 | } 609 | 610 | static void nsvg__resetPath(NSVGparser* p) 611 | { 612 | p->npts = 0; 613 | } 614 | 615 | static void nsvg__addPoint(NSVGparser* p, float x, float y) 616 | { 617 | if (p->npts+1 > p->cpts) { 618 | p->cpts = p->cpts ? p->cpts*2 : 8; 619 | p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); 620 | if (!p->pts) return; 621 | } 622 | p->pts[p->npts*2+0] = x; 623 | p->pts[p->npts*2+1] = y; 624 | p->npts++; 625 | } 626 | 627 | static void nsvg__moveTo(NSVGparser* p, float x, float y) 628 | { 629 | if (p->npts > 0) { 630 | p->pts[(p->npts-1)*2+0] = x; 631 | p->pts[(p->npts-1)*2+1] = y; 632 | } else { 633 | nsvg__addPoint(p, x, y); 634 | } 635 | } 636 | 637 | static void nsvg__lineTo(NSVGparser* p, float x, float y) 638 | { 639 | float px,py, dx,dy; 640 | if (p->npts > 0) { 641 | px = p->pts[(p->npts-1)*2+0]; 642 | py = p->pts[(p->npts-1)*2+1]; 643 | dx = x - px; 644 | dy = y - py; 645 | nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); 646 | nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); 647 | nsvg__addPoint(p, x, y); 648 | } 649 | } 650 | 651 | static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) 652 | { 653 | nsvg__addPoint(p, cpx1, cpy1); 654 | nsvg__addPoint(p, cpx2, cpy2); 655 | nsvg__addPoint(p, x, y); 656 | } 657 | 658 | static NSVGattrib* nsvg__getAttr(NSVGparser* p) 659 | { 660 | return &p->attr[p->attrHead]; 661 | } 662 | 663 | static void nsvg__pushAttr(NSVGparser* p) 664 | { 665 | if (p->attrHead < NSVG_MAX_ATTR-1) { 666 | p->attrHead++; 667 | memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); 668 | } 669 | } 670 | 671 | static void nsvg__popAttr(NSVGparser* p) 672 | { 673 | if (p->attrHead > 0) 674 | p->attrHead--; 675 | } 676 | 677 | static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) 678 | { 679 | NSVGgradientData* grad = p->gradients; 680 | while (grad) { 681 | if (strcmp(grad->id, id) == 0) 682 | return grad; 683 | grad = grad->next; 684 | } 685 | return NULL; 686 | } 687 | 688 | static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* bounds, char* paintType) 689 | { 690 | NSVGattrib* attr = nsvg__getAttr(p); 691 | NSVGgradientData* data = NULL; 692 | NSVGgradientData* ref = NULL; 693 | NSVGgradientStop* stops = NULL; 694 | NSVGgradient* grad; 695 | float dx, dy, d; 696 | int nstops = 0; 697 | NSVG_NOTUSED(bounds); 698 | 699 | data = nsvg__findGradientData(p, id); 700 | if (data == NULL) return NULL; 701 | 702 | // TODO: use ref to fill in all unset values too. 703 | ref = data; 704 | while (ref != NULL) { 705 | if (ref->stops != NULL) { 706 | stops = ref->stops; 707 | nstops = ref->nstops; 708 | break; 709 | } 710 | ref = nsvg__findGradientData(p, ref->ref); 711 | } 712 | if (stops == NULL) return NULL; 713 | 714 | grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); 715 | if (grad == NULL) return NULL; 716 | 717 | // TODO: handle data->units == NSVG_OBJECT_SPACE. 718 | 719 | if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { 720 | // Calculate transform aligned to the line 721 | dx = data->linear.x2 - data->linear.x1; 722 | dy = data->linear.y2 - data->linear.y1; 723 | d = sqrtf(dx*dx + dy*dy); 724 | grad->xform[0] = dy; grad->xform[1] = -dx; 725 | grad->xform[2] = dx; grad->xform[3] = dy; 726 | grad->xform[4] = data->linear.x1; grad->xform[5] = data->linear.y1; 727 | } else { 728 | // Calculate transform aligned to the circle 729 | grad->xform[0] = data->radial.r; grad->xform[1] = 0; 730 | grad->xform[2] = 0; grad->xform[3] = data->radial.r; 731 | grad->xform[4] = data->radial.cx; grad->xform[5] = data->radial.cy; 732 | grad->fx = data->radial.fx / data->radial.r; 733 | grad->fy = data->radial.fy / data->radial.r; 734 | } 735 | 736 | nsvg__xformMultiply(grad->xform, attr->xform); 737 | nsvg__xformMultiply(grad->xform, data->xform); 738 | 739 | grad->spread = data->spread; 740 | memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); 741 | grad->nstops = nstops; 742 | 743 | *paintType = data->type; 744 | 745 | return grad; 746 | } 747 | 748 | static void nsvg__addShape(NSVGparser* p) 749 | { 750 | NSVGattrib* attr = nsvg__getAttr(p); 751 | float scale = 1.0f; 752 | NSVGshape *shape, *cur, *prev; 753 | NSVGpath* path; 754 | 755 | if (p->plist == NULL) 756 | return; 757 | 758 | shape = (NSVGshape*)malloc(sizeof(NSVGshape)); 759 | if (shape == NULL) goto error; 760 | memset(shape, 0, sizeof(NSVGshape)); 761 | 762 | scale = nsvg__maxf(fabsf(attr->xform[0]), fabsf(attr->xform[3])); 763 | shape->strokeWidth = attr->strokeWidth * scale; 764 | shape->opacity = attr->opacity; 765 | 766 | shape->paths = p->plist; 767 | p->plist = NULL; 768 | 769 | // Calculate shape bounds 770 | shape->bounds[0] = shape->paths->bounds[0]; 771 | shape->bounds[1] = shape->paths->bounds[1]; 772 | shape->bounds[2] = shape->paths->bounds[2]; 773 | shape->bounds[3] = shape->paths->bounds[3]; 774 | for (path = shape->paths->next; path != NULL; path = path->next) { 775 | shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); 776 | shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); 777 | shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); 778 | shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); 779 | } 780 | 781 | // Set fill 782 | if (attr->hasFill == 0) { 783 | shape->fill.type = NSVG_PAINT_NONE; 784 | } else if (attr->hasFill == 1) { 785 | shape->fill.type = NSVG_PAINT_COLOR; 786 | shape->fill.color = attr->fillColor; 787 | shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; 788 | } else if (attr->hasFill == 2) { 789 | shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, shape->bounds, &shape->fill.type); 790 | if (shape->fill.gradient == NULL) { 791 | shape->fill.type = NSVG_PAINT_NONE; 792 | } 793 | } 794 | 795 | // Set stroke 796 | if (attr->hasStroke == 0) { 797 | shape->stroke.type = NSVG_PAINT_NONE; 798 | } else if (attr->hasStroke == 1) { 799 | shape->stroke.type = NSVG_PAINT_COLOR; 800 | shape->stroke.color = attr->strokeColor; 801 | shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; 802 | } else if (attr->hasStroke == 2) { 803 | shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, shape->bounds, &shape->stroke.type); 804 | if (shape->stroke.gradient == NULL) 805 | shape->stroke.type = NSVG_PAINT_NONE; 806 | } 807 | 808 | // Add to tail 809 | prev = NULL; 810 | cur = p->image->shapes; 811 | while (cur != NULL) { 812 | prev = cur; 813 | cur = cur->next; 814 | } 815 | if (prev == NULL) 816 | p->image->shapes = shape; 817 | else 818 | prev->next = shape; 819 | 820 | return; 821 | 822 | error: 823 | if (shape) free(shape); 824 | } 825 | 826 | static void nsvg__addPath(NSVGparser* p, char closed) 827 | { 828 | NSVGattrib* attr = nsvg__getAttr(p); 829 | NSVGpath* path = NULL; 830 | float bounds[4]; 831 | float* curve; 832 | int i; 833 | 834 | if (p->npts < 4) 835 | return; 836 | 837 | if (closed) 838 | nsvg__lineTo(p, p->pts[0], p->pts[1]); 839 | 840 | path = (NSVGpath*)malloc(sizeof(NSVGpath)); 841 | if (path == NULL) goto error; 842 | memset(path, 0, sizeof(NSVGpath)); 843 | 844 | path->pts = (float*)malloc(p->npts*2*sizeof(float)); 845 | if (path->pts == NULL) goto error; 846 | path->closed = closed; 847 | path->npts = p->npts; 848 | 849 | // Transform path. 850 | for (i = 0; i < p->npts; ++i) 851 | nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); 852 | 853 | // Find bounds 854 | for (i = 0; i < path->npts-1; i += 3) { 855 | curve = &path->pts[i*2]; 856 | nsvg__curveBounds(bounds, curve); 857 | if (i == 0) { 858 | path->bounds[0] = bounds[0]; 859 | path->bounds[1] = bounds[1]; 860 | path->bounds[2] = bounds[2]; 861 | path->bounds[3] = bounds[3]; 862 | } else { 863 | path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); 864 | path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); 865 | path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); 866 | path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); 867 | } 868 | } 869 | 870 | path->next = p->plist; 871 | p->plist = path; 872 | 873 | return; 874 | 875 | error: 876 | if (path != NULL) { 877 | if (path->pts != NULL) free(path->pts); 878 | free(path); 879 | } 880 | } 881 | 882 | static const char* nsvg__getNextPathItem(const char* s, char* it) 883 | { 884 | int i = 0; 885 | it[0] = '\0'; 886 | // Skip white spaces and commas 887 | while (*s && (nsvg__isspace(*s) || *s == ',')) s++; 888 | if (!*s) return s; 889 | if (*s == '-' || *s == '+' || nsvg__isdigit(*s)) { 890 | // sign 891 | if (*s == '-' || *s == '+') { 892 | if (i < 63) it[i++] = *s; 893 | s++; 894 | } 895 | // integer part 896 | while (*s && nsvg__isdigit(*s)) { 897 | if (i < 63) it[i++] = *s; 898 | s++; 899 | } 900 | if (*s == '.') { 901 | // decimal point 902 | if (i < 63) it[i++] = *s; 903 | s++; 904 | // fraction part 905 | while (*s && nsvg__isdigit(*s)) { 906 | if (i < 63) it[i++] = *s; 907 | s++; 908 | } 909 | } 910 | // exponent 911 | if (*s == 'e' || *s == 'E') { 912 | if (i < 63) it[i++] = *s; 913 | s++; 914 | if (*s == '-' || *s == '+') { 915 | if (i < 63) it[i++] = *s; 916 | s++; 917 | } 918 | while (*s && nsvg__isdigit(*s)) { 919 | if (i < 63) it[i++] = *s; 920 | s++; 921 | } 922 | } 923 | it[i] = '\0'; 924 | } else { 925 | // Parse command 926 | it[0] = *s++; 927 | it[1] = '\0'; 928 | return s; 929 | } 930 | 931 | return s; 932 | } 933 | 934 | static float nsvg__actualWidth(NSVGparser* p) 935 | { 936 | return p->viewWidth; 937 | } 938 | 939 | static float nsvg__actualHeight(NSVGparser* p) 940 | { 941 | return p->viewHeight; 942 | } 943 | 944 | static float nsvg__actualLength(NSVGparser* p) 945 | { 946 | float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); 947 | return sqrtf(w*w + h*h) / sqrtf(2.0f); 948 | } 949 | 950 | 951 | static unsigned int nsvg__parseColorHex(const char* str) 952 | { 953 | unsigned int c = 0, r = 0, g = 0, b = 0; 954 | int n = 0; 955 | str++; // skip # 956 | // Calculate number of characters. 957 | while(str[n] && !nsvg__isspace(str[n])) 958 | n++; 959 | if (n == 6) { 960 | sscanf(str, "%x", &c); 961 | } else if (n == 3) { 962 | sscanf(str, "%x", &c); 963 | c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); 964 | c |= c<<4; 965 | } 966 | r = (c >> 16) & 0xff; 967 | g = (c >> 8) & 0xff; 968 | b = c & 0xff; 969 | return NSVG_RGB(r,g,b); 970 | } 971 | 972 | static unsigned int nsvg__parseColorRGB(const char* str) 973 | { 974 | int r = -1, g = -1, b = -1; 975 | char s1[32]="", s2[32]=""; 976 | sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); 977 | if (strchr(s1, '%')) { 978 | return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); 979 | } else { 980 | return NSVG_RGB(r,g,b); 981 | } 982 | } 983 | 984 | typedef struct NSVGNamedColor { 985 | const char* name; 986 | unsigned int color; 987 | } NSVGNamedColor; 988 | 989 | NSVGNamedColor nsvg__colors[] = { 990 | 991 | { "red", NSVG_RGB(255, 0, 0) }, 992 | { "green", NSVG_RGB( 0, 128, 0) }, 993 | { "blue", NSVG_RGB( 0, 0, 255) }, 994 | { "yellow", NSVG_RGB(255, 255, 0) }, 995 | { "cyan", NSVG_RGB( 0, 255, 255) }, 996 | { "magenta", NSVG_RGB(255, 0, 255) }, 997 | { "black", NSVG_RGB( 0, 0, 0) }, 998 | { "grey", NSVG_RGB(128, 128, 128) }, 999 | { "gray", NSVG_RGB(128, 128, 128) }, 1000 | { "white", NSVG_RGB(255, 255, 255) }, 1001 | 1002 | #ifdef NANOSVG_ALL_COLOR_KEYWORDS 1003 | { "aliceblue", NSVG_RGB(240, 248, 255) }, 1004 | { "antiquewhite", NSVG_RGB(250, 235, 215) }, 1005 | { "aqua", NSVG_RGB( 0, 255, 255) }, 1006 | { "aquamarine", NSVG_RGB(127, 255, 212) }, 1007 | { "azure", NSVG_RGB(240, 255, 255) }, 1008 | { "beige", NSVG_RGB(245, 245, 220) }, 1009 | { "bisque", NSVG_RGB(255, 228, 196) }, 1010 | { "blanchedalmond", NSVG_RGB(255, 235, 205) }, 1011 | { "blueviolet", NSVG_RGB(138, 43, 226) }, 1012 | { "brown", NSVG_RGB(165, 42, 42) }, 1013 | { "burlywood", NSVG_RGB(222, 184, 135) }, 1014 | { "cadetblue", NSVG_RGB( 95, 158, 160) }, 1015 | { "chartreuse", NSVG_RGB(127, 255, 0) }, 1016 | { "chocolate", NSVG_RGB(210, 105, 30) }, 1017 | { "coral", NSVG_RGB(255, 127, 80) }, 1018 | { "cornflowerblue", NSVG_RGB(100, 149, 237) }, 1019 | { "cornsilk", NSVG_RGB(255, 248, 220) }, 1020 | { "crimson", NSVG_RGB(220, 20, 60) }, 1021 | { "darkblue", NSVG_RGB( 0, 0, 139) }, 1022 | { "darkcyan", NSVG_RGB( 0, 139, 139) }, 1023 | { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, 1024 | { "darkgray", NSVG_RGB(169, 169, 169) }, 1025 | { "darkgreen", NSVG_RGB( 0, 100, 0) }, 1026 | { "darkgrey", NSVG_RGB(169, 169, 169) }, 1027 | { "darkkhaki", NSVG_RGB(189, 183, 107) }, 1028 | { "darkmagenta", NSVG_RGB(139, 0, 139) }, 1029 | { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, 1030 | { "darkorange", NSVG_RGB(255, 140, 0) }, 1031 | { "darkorchid", NSVG_RGB(153, 50, 204) }, 1032 | { "darkred", NSVG_RGB(139, 0, 0) }, 1033 | { "darksalmon", NSVG_RGB(233, 150, 122) }, 1034 | { "darkseagreen", NSVG_RGB(143, 188, 143) }, 1035 | { "darkslateblue", NSVG_RGB( 72, 61, 139) }, 1036 | { "darkslategray", NSVG_RGB( 47, 79, 79) }, 1037 | { "darkslategrey", NSVG_RGB( 47, 79, 79) }, 1038 | { "darkturquoise", NSVG_RGB( 0, 206, 209) }, 1039 | { "darkviolet", NSVG_RGB(148, 0, 211) }, 1040 | { "deeppink", NSVG_RGB(255, 20, 147) }, 1041 | { "deepskyblue", NSVG_RGB( 0, 191, 255) }, 1042 | { "dimgray", NSVG_RGB(105, 105, 105) }, 1043 | { "dimgrey", NSVG_RGB(105, 105, 105) }, 1044 | { "dodgerblue", NSVG_RGB( 30, 144, 255) }, 1045 | { "firebrick", NSVG_RGB(178, 34, 34) }, 1046 | { "floralwhite", NSVG_RGB(255, 250, 240) }, 1047 | { "forestgreen", NSVG_RGB( 34, 139, 34) }, 1048 | { "fuchsia", NSVG_RGB(255, 0, 255) }, 1049 | { "gainsboro", NSVG_RGB(220, 220, 220) }, 1050 | { "ghostwhite", NSVG_RGB(248, 248, 255) }, 1051 | { "gold", NSVG_RGB(255, 215, 0) }, 1052 | { "goldenrod", NSVG_RGB(218, 165, 32) }, 1053 | { "greenyellow", NSVG_RGB(173, 255, 47) }, 1054 | { "honeydew", NSVG_RGB(240, 255, 240) }, 1055 | { "hotpink", NSVG_RGB(255, 105, 180) }, 1056 | { "indianred", NSVG_RGB(205, 92, 92) }, 1057 | { "indigo", NSVG_RGB( 75, 0, 130) }, 1058 | { "ivory", NSVG_RGB(255, 255, 240) }, 1059 | { "khaki", NSVG_RGB(240, 230, 140) }, 1060 | { "lavender", NSVG_RGB(230, 230, 250) }, 1061 | { "lavenderblush", NSVG_RGB(255, 240, 245) }, 1062 | { "lawngreen", NSVG_RGB(124, 252, 0) }, 1063 | { "lemonchiffon", NSVG_RGB(255, 250, 205) }, 1064 | { "lightblue", NSVG_RGB(173, 216, 230) }, 1065 | { "lightcoral", NSVG_RGB(240, 128, 128) }, 1066 | { "lightcyan", NSVG_RGB(224, 255, 255) }, 1067 | { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, 1068 | { "lightgray", NSVG_RGB(211, 211, 211) }, 1069 | { "lightgreen", NSVG_RGB(144, 238, 144) }, 1070 | { "lightgrey", NSVG_RGB(211, 211, 211) }, 1071 | { "lightpink", NSVG_RGB(255, 182, 193) }, 1072 | { "lightsalmon", NSVG_RGB(255, 160, 122) }, 1073 | { "lightseagreen", NSVG_RGB( 32, 178, 170) }, 1074 | { "lightskyblue", NSVG_RGB(135, 206, 250) }, 1075 | { "lightslategray", NSVG_RGB(119, 136, 153) }, 1076 | { "lightslategrey", NSVG_RGB(119, 136, 153) }, 1077 | { "lightsteelblue", NSVG_RGB(176, 196, 222) }, 1078 | { "lightyellow", NSVG_RGB(255, 255, 224) }, 1079 | { "lime", NSVG_RGB( 0, 255, 0) }, 1080 | { "limegreen", NSVG_RGB( 50, 205, 50) }, 1081 | { "linen", NSVG_RGB(250, 240, 230) }, 1082 | { "maroon", NSVG_RGB(128, 0, 0) }, 1083 | { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, 1084 | { "mediumblue", NSVG_RGB( 0, 0, 205) }, 1085 | { "mediumorchid", NSVG_RGB(186, 85, 211) }, 1086 | { "mediumpurple", NSVG_RGB(147, 112, 219) }, 1087 | { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, 1088 | { "mediumslateblue", NSVG_RGB(123, 104, 238) }, 1089 | { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, 1090 | { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, 1091 | { "mediumvioletred", NSVG_RGB(199, 21, 133) }, 1092 | { "midnightblue", NSVG_RGB( 25, 25, 112) }, 1093 | { "mintcream", NSVG_RGB(245, 255, 250) }, 1094 | { "mistyrose", NSVG_RGB(255, 228, 225) }, 1095 | { "moccasin", NSVG_RGB(255, 228, 181) }, 1096 | { "navajowhite", NSVG_RGB(255, 222, 173) }, 1097 | { "navy", NSVG_RGB( 0, 0, 128) }, 1098 | { "oldlace", NSVG_RGB(253, 245, 230) }, 1099 | { "olive", NSVG_RGB(128, 128, 0) }, 1100 | { "olivedrab", NSVG_RGB(107, 142, 35) }, 1101 | { "orange", NSVG_RGB(255, 165, 0) }, 1102 | { "orangered", NSVG_RGB(255, 69, 0) }, 1103 | { "orchid", NSVG_RGB(218, 112, 214) }, 1104 | { "palegoldenrod", NSVG_RGB(238, 232, 170) }, 1105 | { "palegreen", NSVG_RGB(152, 251, 152) }, 1106 | { "paleturquoise", NSVG_RGB(175, 238, 238) }, 1107 | { "palevioletred", NSVG_RGB(219, 112, 147) }, 1108 | { "papayawhip", NSVG_RGB(255, 239, 213) }, 1109 | { "peachpuff", NSVG_RGB(255, 218, 185) }, 1110 | { "peru", NSVG_RGB(205, 133, 63) }, 1111 | { "pink", NSVG_RGB(255, 192, 203) }, 1112 | { "plum", NSVG_RGB(221, 160, 221) }, 1113 | { "powderblue", NSVG_RGB(176, 224, 230) }, 1114 | { "purple", NSVG_RGB(128, 0, 128) }, 1115 | { "rosybrown", NSVG_RGB(188, 143, 143) }, 1116 | { "royalblue", NSVG_RGB( 65, 105, 225) }, 1117 | { "saddlebrown", NSVG_RGB(139, 69, 19) }, 1118 | { "salmon", NSVG_RGB(250, 128, 114) }, 1119 | { "sandybrown", NSVG_RGB(244, 164, 96) }, 1120 | { "seagreen", NSVG_RGB( 46, 139, 87) }, 1121 | { "seashell", NSVG_RGB(255, 245, 238) }, 1122 | { "sienna", NSVG_RGB(160, 82, 45) }, 1123 | { "silver", NSVG_RGB(192, 192, 192) }, 1124 | { "skyblue", NSVG_RGB(135, 206, 235) }, 1125 | { "slateblue", NSVG_RGB(106, 90, 205) }, 1126 | { "slategray", NSVG_RGB(112, 128, 144) }, 1127 | { "slategrey", NSVG_RGB(112, 128, 144) }, 1128 | { "snow", NSVG_RGB(255, 250, 250) }, 1129 | { "springgreen", NSVG_RGB( 0, 255, 127) }, 1130 | { "steelblue", NSVG_RGB( 70, 130, 180) }, 1131 | { "tan", NSVG_RGB(210, 180, 140) }, 1132 | { "teal", NSVG_RGB( 0, 128, 128) }, 1133 | { "thistle", NSVG_RGB(216, 191, 216) }, 1134 | { "tomato", NSVG_RGB(255, 99, 71) }, 1135 | { "turquoise", NSVG_RGB( 64, 224, 208) }, 1136 | { "violet", NSVG_RGB(238, 130, 238) }, 1137 | { "wheat", NSVG_RGB(245, 222, 179) }, 1138 | { "whitesmoke", NSVG_RGB(245, 245, 245) }, 1139 | { "yellowgreen", NSVG_RGB(154, 205, 50) }, 1140 | #endif 1141 | }; 1142 | 1143 | static unsigned int nsvg__parseColorName(const char* str) 1144 | { 1145 | int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); 1146 | 1147 | for (i = 0; i < ncolors; i++) { 1148 | if (strcmp(nsvg__colors[i].name, str) == 0) { 1149 | return nsvg__colors[i].color; 1150 | } 1151 | } 1152 | 1153 | return NSVG_RGB(128, 128, 128); 1154 | } 1155 | 1156 | static unsigned int nsvg__parseColor(const char* str) 1157 | { 1158 | int len = 0; 1159 | while(*str == ' ') ++str; 1160 | len = strlen(str); 1161 | if (len >= 1 && *str == '#') 1162 | return nsvg__parseColorHex(str); 1163 | else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') 1164 | return nsvg__parseColorRGB(str); 1165 | return nsvg__parseColorName(str); 1166 | } 1167 | 1168 | static float nsvg__convertToPixels(NSVGparser* p, float val, const char* units, int dir) 1169 | { 1170 | NSVGattrib* attr; 1171 | 1172 | if (p != NULL) { 1173 | // Convert units to pixels. 1174 | if (units[0] == '\0') { 1175 | return val; 1176 | } else if (units[0] == 'p' && units[1] == 'x') { 1177 | return val; 1178 | } else if (units[0] == 'p' && units[1] == 't') { 1179 | return val / 72.0f * p->dpi; 1180 | } else if (units[0] == 'p' && units[1] == 'c') { 1181 | return val / 6.0f * p->dpi; 1182 | } else if (units[0] == 'm' && units[1] == 'm') { 1183 | return val / 25.4f * p->dpi; 1184 | } else if (units[0] == 'c' && units[1] == 'm') { 1185 | return val / 2.54f * p->dpi; 1186 | } else if (units[0] == 'i' && units[1] == 'n') { 1187 | return val * p->dpi; 1188 | } else if (units[0] == '%') { 1189 | if (p != NULL) { 1190 | attr = nsvg__getAttr(p); 1191 | if (dir == 0) 1192 | return (val/100.0f) * nsvg__actualWidth(p); 1193 | else if (dir == 1) 1194 | return (val/100.0f) * nsvg__actualHeight(p); 1195 | else if (dir == 2) 1196 | return (val/100.0f) * nsvg__actualLength(p); 1197 | } else { 1198 | return (val/100.0f); 1199 | } 1200 | } else if (units[0] == 'e' && units[1] == 'm') { 1201 | if (p != NULL) { 1202 | attr = nsvg__getAttr(p); 1203 | return val * attr->fontSize; 1204 | } 1205 | } else if (units[0] == 'e' && units[1] == 'x') { 1206 | if (p != NULL) { 1207 | attr = nsvg__getAttr(p); 1208 | return val * attr->fontSize * 0.52f; // x-height of Helvetica. 1209 | } 1210 | } 1211 | } else { 1212 | // Convert units to pixels. 1213 | if (units[0] == '\0') { 1214 | return val; 1215 | } else if (units[0] == 'p' && units[1] == 'x') { 1216 | return val; 1217 | } else if (units[0] == '%') { 1218 | return (val/100.0f); 1219 | } 1220 | } 1221 | return val; 1222 | } 1223 | 1224 | static float nsvg__parseFloat(NSVGparser* p, const char* str, int dir) 1225 | { 1226 | float val = 0; 1227 | char units[32]=""; 1228 | sscanf(str, "%f%s", &val, units); 1229 | return nsvg__convertToPixels(p, val, units, dir); 1230 | } 1231 | 1232 | static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) 1233 | { 1234 | const char* end; 1235 | const char* ptr; 1236 | 1237 | *na = 0; 1238 | ptr = str; 1239 | while (*ptr && *ptr != '(') ++ptr; 1240 | if (*ptr == 0) 1241 | return 1; 1242 | end = ptr; 1243 | while (*end && *end != ')') ++end; 1244 | if (*end == 0) 1245 | return 1; 1246 | 1247 | while (ptr < end) { 1248 | if (nsvg__isnum(*ptr)) { 1249 | if (*na >= maxNa) return 0; 1250 | args[(*na)++] = (float)atof(ptr); 1251 | while (ptr < end && nsvg__isnum(*ptr)) ++ptr; 1252 | } else { 1253 | ++ptr; 1254 | } 1255 | } 1256 | return (int)(end - str); 1257 | } 1258 | 1259 | static int nsvg__parseMatrix(float* xform, const char* str) 1260 | { 1261 | float t[6]; 1262 | int na = 0; 1263 | int len = nsvg__parseTransformArgs(str, t, 6, &na); 1264 | if (na != 6) return len; 1265 | memcpy(xform, t, sizeof(float)*6); 1266 | return len; 1267 | } 1268 | 1269 | static int nsvg__parseTranslate(float* xform, const char* str) 1270 | { 1271 | float args[2]; 1272 | float t[6]; 1273 | int na = 0; 1274 | int len = nsvg__parseTransformArgs(str, args, 2, &na); 1275 | if (na == 1) args[1] = 0.0; 1276 | 1277 | nsvg__xformSetTranslation(t, args[0], args[1]); 1278 | memcpy(xform, t, sizeof(float)*6); 1279 | return len; 1280 | } 1281 | 1282 | static int nsvg__parseScale(float* xform, const char* str) 1283 | { 1284 | float args[2]; 1285 | int na = 0; 1286 | float t[6]; 1287 | int len = nsvg__parseTransformArgs(str, args, 2, &na); 1288 | if (na == 1) args[1] = args[0]; 1289 | nsvg__xformSetScale(t, args[0], args[1]); 1290 | memcpy(xform, t, sizeof(float)*6); 1291 | return len; 1292 | } 1293 | 1294 | static int nsvg__parseSkewX(float* xform, const char* str) 1295 | { 1296 | float args[1]; 1297 | int na = 0; 1298 | float t[6]; 1299 | int len = nsvg__parseTransformArgs(str, args, 1, &na); 1300 | nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); 1301 | memcpy(xform, t, sizeof(float)*6); 1302 | return len; 1303 | } 1304 | 1305 | static int nsvg__parseSkewY(float* xform, const char* str) 1306 | { 1307 | float args[1]; 1308 | int na = 0; 1309 | float t[6]; 1310 | int len = nsvg__parseTransformArgs(str, args, 1, &na); 1311 | nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); 1312 | memcpy(xform, t, sizeof(float)*6); 1313 | return len; 1314 | } 1315 | 1316 | static int nsvg__parseRotate(float* xform, const char* str) 1317 | { 1318 | float args[3]; 1319 | int na = 0; 1320 | float m[6]; 1321 | float t[6]; 1322 | int len = nsvg__parseTransformArgs(str, args, 3, &na); 1323 | if (na == 1) 1324 | args[1] = args[2] = 0.0f; 1325 | nsvg__xformIdentity(m); 1326 | 1327 | if (na > 1) { 1328 | nsvg__xformSetTranslation(t, -args[1], -args[2]); 1329 | nsvg__xformMultiply(m, t); 1330 | } 1331 | 1332 | nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); 1333 | nsvg__xformMultiply(m, t); 1334 | 1335 | if (na > 1) { 1336 | nsvg__xformSetTranslation(t, args[1], args[2]); 1337 | nsvg__xformMultiply(m, t); 1338 | } 1339 | 1340 | memcpy(xform, m, sizeof(float)*6); 1341 | 1342 | return len; 1343 | } 1344 | 1345 | static void nsvg__parseTransform(float* xform, const char* str) 1346 | { 1347 | float t[6]; 1348 | nsvg__xformIdentity(xform); 1349 | while (*str) 1350 | { 1351 | if (strncmp(str, "matrix", 6) == 0) 1352 | str += nsvg__parseMatrix(t, str); 1353 | else if (strncmp(str, "translate", 9) == 0) 1354 | str += nsvg__parseTranslate(t, str); 1355 | else if (strncmp(str, "scale", 5) == 0) 1356 | str += nsvg__parseScale(t, str); 1357 | else if (strncmp(str, "rotate", 6) == 0) 1358 | str += nsvg__parseRotate(t, str); 1359 | else if (strncmp(str, "skewX", 5) == 0) 1360 | str += nsvg__parseSkewX(t, str); 1361 | else if (strncmp(str, "skewY", 5) == 0) 1362 | str += nsvg__parseSkewY(t, str); 1363 | else{ 1364 | ++str; 1365 | continue; 1366 | } 1367 | 1368 | nsvg__xformPremultiply(xform, t); 1369 | } 1370 | } 1371 | 1372 | static void nsvg__parseUrl(char* id, const char* str) 1373 | { 1374 | int i = 0; 1375 | str += 4; // "url("; 1376 | if (*str == '#') 1377 | str++; 1378 | while (i < 63 && *str != ')') { 1379 | id[i] = *str++; 1380 | i++; 1381 | } 1382 | id[i] = '\0'; 1383 | } 1384 | 1385 | static void nsvg__parseStyle(NSVGparser* p, const char* str); 1386 | 1387 | static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) 1388 | { 1389 | float xform[6]; 1390 | NSVGattrib* attr = nsvg__getAttr(p); 1391 | if (!attr) return 0; 1392 | 1393 | if (strcmp(name, "style") == 0) { 1394 | nsvg__parseStyle(p, value); 1395 | } else if (strcmp(name, "display") == 0) { 1396 | if (strcmp(value, "none") == 0) 1397 | attr->visible = 0; 1398 | else 1399 | attr->visible = 1; 1400 | } else if (strcmp(name, "fill") == 0) { 1401 | if (strcmp(value, "none") == 0) { 1402 | attr->hasFill = 0; 1403 | } else if (strncmp(value, "url(", 4) == 0) { 1404 | attr->hasFill = 2; 1405 | nsvg__parseUrl(attr->fillGradient, value); 1406 | } else { 1407 | attr->hasFill = 1; 1408 | attr->fillColor = nsvg__parseColor(value); 1409 | } 1410 | } else if (strcmp(name, "opacity") == 0) { 1411 | attr->opacity = nsvg__parseFloat(p, value, 2); 1412 | } else if (strcmp(name, "fill-opacity") == 0) { 1413 | attr->fillOpacity = nsvg__parseFloat(p, value, 2); 1414 | } else if (strcmp(name, "stroke") == 0) { 1415 | if (strcmp(value, "none") == 0) { 1416 | attr->hasStroke = 0; 1417 | } else if (strncmp(value, "url(", 4) == 0) { 1418 | attr->hasStroke = 2; 1419 | nsvg__parseUrl(attr->strokeGradient, value); 1420 | } else { 1421 | attr->hasStroke = 1; 1422 | attr->strokeColor = nsvg__parseColor(value); 1423 | } 1424 | } else if (strcmp(name, "stroke-width") == 0) { 1425 | attr->strokeWidth = nsvg__parseFloat(p, value, 2); 1426 | } else if (strcmp(name, "stroke-opacity") == 0) { 1427 | attr->strokeOpacity = nsvg__parseFloat(NULL, value, 2); 1428 | } else if (strcmp(name, "font-size") == 0) { 1429 | attr->fontSize = nsvg__parseFloat(p, value, 2); 1430 | } else if (strcmp(name, "transform") == 0) { 1431 | nsvg__parseTransform(xform, value); 1432 | nsvg__xformPremultiply(attr->xform, xform); 1433 | } else if (strcmp(name, "stop-color") == 0) { 1434 | attr->stopColor = nsvg__parseColor(value); 1435 | } else if (strcmp(name, "stop-opacity") == 0) { 1436 | attr->stopOpacity = nsvg__parseFloat(NULL, value, 2); 1437 | } else if (strcmp(name, "offset") == 0) { 1438 | attr->stopOffset = nsvg__parseFloat(NULL, value, 2); 1439 | } else { 1440 | return 0; 1441 | } 1442 | return 1; 1443 | } 1444 | 1445 | static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) 1446 | { 1447 | const char* str; 1448 | const char* val; 1449 | char name[512]; 1450 | char value[512]; 1451 | int n; 1452 | 1453 | str = start; 1454 | while (str < end && *str != ':') ++str; 1455 | 1456 | val = str; 1457 | 1458 | // Right Trim 1459 | while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; 1460 | ++str; 1461 | 1462 | n = (int)(str - start); 1463 | if (n > 511) n = 511; 1464 | if (n) memcpy(name, start, n); 1465 | name[n] = 0; 1466 | 1467 | while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; 1468 | 1469 | n = (int)(end - val); 1470 | if (n > 511) n = 511; 1471 | if (n) memcpy(value, val, n); 1472 | value[n] = 0; 1473 | 1474 | return nsvg__parseAttr(p, name, value); 1475 | } 1476 | 1477 | static void nsvg__parseStyle(NSVGparser* p, const char* str) 1478 | { 1479 | const char* start; 1480 | const char* end; 1481 | 1482 | while (*str) { 1483 | // Left Trim 1484 | while(*str && nsvg__isspace(*str)) ++str; 1485 | start = str; 1486 | while(*str && *str != ';') ++str; 1487 | end = str; 1488 | 1489 | // Right Trim 1490 | while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; 1491 | ++end; 1492 | 1493 | nsvg__parseNameValue(p, start, end); 1494 | if (*str) ++str; 1495 | } 1496 | } 1497 | 1498 | static void nsvg__parseAttribs(NSVGparser* p, const char** attr) 1499 | { 1500 | int i; 1501 | for (i = 0; attr[i]; i += 2) 1502 | { 1503 | if (strcmp(attr[i], "style") == 0) 1504 | nsvg__parseStyle(p, attr[i + 1]); 1505 | else 1506 | nsvg__parseAttr(p, attr[i], attr[i + 1]); 1507 | } 1508 | } 1509 | 1510 | static int nsvg__getArgsPerElement(char cmd) 1511 | { 1512 | switch (cmd) { 1513 | case 'v': 1514 | case 'V': 1515 | case 'h': 1516 | case 'H': 1517 | return 1; 1518 | case 'm': 1519 | case 'M': 1520 | case 'l': 1521 | case 'L': 1522 | case 't': 1523 | case 'T': 1524 | return 2; 1525 | case 'q': 1526 | case 'Q': 1527 | case 's': 1528 | case 'S': 1529 | return 4; 1530 | case 'c': 1531 | case 'C': 1532 | return 6; 1533 | case 'a': 1534 | case 'A': 1535 | return 7; 1536 | } 1537 | return 0; 1538 | } 1539 | 1540 | static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) 1541 | { 1542 | if (rel) { 1543 | *cpx += args[0]; 1544 | *cpy += args[1]; 1545 | } else { 1546 | *cpx = args[0]; 1547 | *cpy = args[1]; 1548 | } 1549 | nsvg__moveTo(p, *cpx, *cpy); 1550 | } 1551 | 1552 | static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) 1553 | { 1554 | if (rel) { 1555 | *cpx += args[0]; 1556 | *cpy += args[1]; 1557 | } else { 1558 | *cpx = args[0]; 1559 | *cpy = args[1]; 1560 | } 1561 | nsvg__lineTo(p, *cpx, *cpy); 1562 | } 1563 | 1564 | static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) 1565 | { 1566 | if (rel) 1567 | *cpx += args[0]; 1568 | else 1569 | *cpx = args[0]; 1570 | nsvg__lineTo(p, *cpx, *cpy); 1571 | } 1572 | 1573 | static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) 1574 | { 1575 | if (rel) 1576 | *cpy += args[0]; 1577 | else 1578 | *cpy = args[0]; 1579 | nsvg__lineTo(p, *cpx, *cpy); 1580 | } 1581 | 1582 | static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, 1583 | float* cpx2, float* cpy2, float* args, int rel) 1584 | { 1585 | float x1, y1, x2, y2, cx1, cy1, cx2, cy2; 1586 | 1587 | x1 = *cpx; 1588 | y1 = *cpy; 1589 | if (rel) { 1590 | cx1 = *cpx + args[0]; 1591 | cy1 = *cpy + args[1]; 1592 | cx2 = *cpx + args[2]; 1593 | cy2 = *cpy + args[3]; 1594 | x2 = *cpx + args[4]; 1595 | y2 = *cpy + args[5]; 1596 | } else { 1597 | cx1 = args[0]; 1598 | cy1 = args[1]; 1599 | cx2 = args[2]; 1600 | cy2 = args[3]; 1601 | x2 = args[4]; 1602 | y2 = args[5]; 1603 | } 1604 | 1605 | nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); 1606 | 1607 | *cpx2 = cx2; 1608 | *cpy2 = cy2; 1609 | *cpx = x2; 1610 | *cpy = y2; 1611 | } 1612 | 1613 | static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, 1614 | float* cpx2, float* cpy2, float* args, int rel) 1615 | { 1616 | float x1, y1, x2, y2, cx1, cy1, cx2, cy2; 1617 | 1618 | x1 = *cpx; 1619 | y1 = *cpy; 1620 | if (rel) { 1621 | cx2 = *cpx + args[0]; 1622 | cy2 = *cpy + args[1]; 1623 | x2 = *cpx + args[2]; 1624 | y2 = *cpy + args[3]; 1625 | } else { 1626 | cx2 = args[0]; 1627 | cy2 = args[1]; 1628 | x2 = args[2]; 1629 | y2 = args[3]; 1630 | } 1631 | 1632 | cx1 = 2*x1 - *cpx2; 1633 | cy1 = 2*y1 - *cpy2; 1634 | 1635 | nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); 1636 | 1637 | *cpx2 = cx2; 1638 | *cpy2 = cy2; 1639 | *cpx = x2; 1640 | *cpy = y2; 1641 | } 1642 | 1643 | static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, 1644 | float* cpx2, float* cpy2, float* args, int rel) 1645 | { 1646 | float x1, y1, x2, y2, cx, cy; 1647 | float cx1, cy1, cx2, cy2; 1648 | 1649 | x1 = *cpx; 1650 | y1 = *cpy; 1651 | if (rel) { 1652 | cx = *cpx + args[0]; 1653 | cy = *cpy + args[1]; 1654 | x2 = *cpx + args[2]; 1655 | y2 = *cpy + args[3]; 1656 | } else { 1657 | cx = args[0]; 1658 | cy = args[1]; 1659 | x2 = args[2]; 1660 | y2 = args[3]; 1661 | } 1662 | 1663 | // Convert to cubic bezier 1664 | cx1 = x1 + 2.0f/3.0f*(cx - x1); 1665 | cy1 = y1 + 2.0f/3.0f*(cy - y1); 1666 | cx2 = x2 + 2.0f/3.0f*(cx - x2); 1667 | cy2 = y2 + 2.0f/3.0f*(cy - y2); 1668 | 1669 | nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); 1670 | 1671 | *cpx2 = cx; 1672 | *cpy2 = cy; 1673 | *cpx = x2; 1674 | *cpy = y2; 1675 | } 1676 | 1677 | static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, 1678 | float* cpx2, float* cpy2, float* args, int rel) 1679 | { 1680 | float x1, y1, x2, y2, cx, cy; 1681 | float cx1, cy1, cx2, cy2; 1682 | 1683 | x1 = *cpx; 1684 | y1 = *cpy; 1685 | if (rel) { 1686 | x2 = *cpx + args[0]; 1687 | y2 = *cpy + args[1]; 1688 | } else { 1689 | x2 = args[0]; 1690 | y2 = args[1]; 1691 | } 1692 | 1693 | cx = 2*x1 - *cpx2; 1694 | cy = 2*y1 - *cpy2; 1695 | 1696 | // Convert to cubix bezier 1697 | cx1 = x1 + 2.0f/3.0f*(cx - x1); 1698 | cy1 = y1 + 2.0f/3.0f*(cy - y1); 1699 | cx2 = x2 + 2.0f/3.0f*(cx - x2); 1700 | cy2 = y2 + 2.0f/3.0f*(cy - y2); 1701 | 1702 | nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); 1703 | 1704 | *cpx2 = cx; 1705 | *cpy2 = cy; 1706 | *cpx = x2; 1707 | *cpy = y2; 1708 | } 1709 | 1710 | static float nsvg__sqr(float x) { return x*x; } 1711 | static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } 1712 | 1713 | static float nsvg__vecrat(float ux, float uy, float vx, float vy) 1714 | { 1715 | return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); 1716 | } 1717 | 1718 | static float nsvg__vecang(float ux, float uy, float vx, float vy) 1719 | { 1720 | float r = nsvg__vecrat(ux,uy, vx,vy); 1721 | if (r < -1.0f) r = -1.0f; 1722 | if (r > 1.0f) r = 1.0f; 1723 | return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); 1724 | } 1725 | 1726 | static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) 1727 | { 1728 | // Ported from canvg (https://code.google.com/p/canvg/) 1729 | float rx, ry, rotx; 1730 | float x1, y1, x2, y2, cx, cy, dx, dy, d; 1731 | float x1p, y1p, cxp, cyp, s, sa, sb; 1732 | float ux, uy, vx, vy, a1, da; 1733 | float x, y, tanx, tany, a, px, py, ptanx, ptany, t[6]; 1734 | float sinrx, cosrx; 1735 | int fa, fs; 1736 | int i, ndivs; 1737 | float hda, kappa; 1738 | 1739 | rx = fabsf(args[0]); // y radius 1740 | ry = fabsf(args[1]); // x radius 1741 | rotx = args[2] / 180.0f * NSVG_PI; // x rotation engle 1742 | fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc 1743 | fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction 1744 | x1 = *cpx; // start point 1745 | y1 = *cpy; 1746 | if (rel) { // end point 1747 | x2 = *cpx + args[5]; 1748 | y2 = *cpy + args[6]; 1749 | } else { 1750 | x2 = args[5]; 1751 | y2 = args[6]; 1752 | } 1753 | 1754 | dx = x1 - x2; 1755 | dy = y1 - y2; 1756 | d = sqrtf(dx*dx + dy*dy); 1757 | if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { 1758 | // The arc degenerates to a line 1759 | nsvg__lineTo(p, x2, y2); 1760 | *cpx = x2; 1761 | *cpy = y2; 1762 | return; 1763 | } 1764 | 1765 | sinrx = sinf(rotx); 1766 | cosrx = cosf(rotx); 1767 | 1768 | // Convert to center point parameterization. 1769 | // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes 1770 | // 1) Compute x1', y1' 1771 | x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; 1772 | y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; 1773 | d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); 1774 | if (d > 1) { 1775 | d = sqrtf(d); 1776 | rx *= d; 1777 | ry *= d; 1778 | } 1779 | // 2) Compute cx', cy' 1780 | s = 0.0f; 1781 | sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); 1782 | sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); 1783 | if (sa < 0.0f) sa = 0.0f; 1784 | if (sb > 0.0f) 1785 | s = sqrtf(sa / sb); 1786 | if (fa == fs) 1787 | s = -s; 1788 | cxp = s * rx * y1p / ry; 1789 | cyp = s * -ry * x1p / rx; 1790 | 1791 | // 3) Compute cx,cy from cx',cy' 1792 | cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; 1793 | cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; 1794 | 1795 | // 4) Calculate theta1, and delta theta. 1796 | ux = (x1p - cxp) / rx; 1797 | uy = (y1p - cyp) / ry; 1798 | vx = (-x1p - cxp) / rx; 1799 | vy = (-y1p - cyp) / ry; 1800 | a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle 1801 | da = nsvg__vecang(ux,uy, vx,vy); // Delta angle 1802 | 1803 | // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; 1804 | // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; 1805 | 1806 | if (fa) { 1807 | // Choose large arc 1808 | if (da > 0.0f) 1809 | da = da - 2*NSVG_PI; 1810 | else 1811 | da = 2*NSVG_PI + da; 1812 | } 1813 | 1814 | // Approximate the arc using cubic spline segments. 1815 | t[0] = cosrx; t[1] = sinrx; 1816 | t[2] = -sinrx; t[3] = cosrx; 1817 | t[4] = cx; t[5] = cy; 1818 | 1819 | // Split arc into max 90 degree segments. 1820 | ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 0.5f); 1821 | hda = (da / (float)ndivs) / 2.0f; 1822 | kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); 1823 | if (da < 0.0f) 1824 | kappa = -kappa; 1825 | 1826 | for (i = 0; i <= ndivs; i++) { 1827 | a = a1 + da * (i/(float)ndivs); 1828 | dx = cosf(a); 1829 | dy = sinf(a); 1830 | nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position 1831 | nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent 1832 | if (i > 0) 1833 | nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); 1834 | px = x; 1835 | py = y; 1836 | ptanx = tanx; 1837 | ptany = tany; 1838 | } 1839 | 1840 | *cpx = x2; 1841 | *cpy = y2; 1842 | } 1843 | 1844 | static void nsvg__parsePath(NSVGparser* p, const char** attr) 1845 | { 1846 | const char* s = NULL; 1847 | char cmd; 1848 | float args[10]; 1849 | int nargs; 1850 | int rargs; 1851 | float cpx, cpy, cpx2, cpy2; 1852 | const char* tmp[4]; 1853 | char closedFlag; 1854 | int i; 1855 | char item[64]; 1856 | 1857 | for (i = 0; attr[i]; i += 2) { 1858 | if (strcmp(attr[i], "d") == 0) { 1859 | s = attr[i + 1]; 1860 | } else { 1861 | tmp[0] = attr[i]; 1862 | tmp[1] = attr[i + 1]; 1863 | tmp[2] = 0; 1864 | tmp[3] = 0; 1865 | nsvg__parseAttribs(p, tmp); 1866 | } 1867 | } 1868 | 1869 | if (s) { 1870 | nsvg__resetPath(p); 1871 | cpx = 0; cpy = 0; 1872 | closedFlag = 0; 1873 | nargs = 0; 1874 | 1875 | while (*s) { 1876 | s = nsvg__getNextPathItem(s, item); 1877 | if (!*item) break; 1878 | if (nsvg__isnum(item[0])) { 1879 | if (nargs < 10) 1880 | args[nargs++] = (float)atof(item); 1881 | if (nargs >= rargs) { 1882 | switch (cmd) { 1883 | case 'm': 1884 | case 'M': 1885 | nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); 1886 | // Moveto can be followed by multiple coordinate pairs, 1887 | // which should be treated as linetos. 1888 | cmd = (cmd == 'm') ? 'l' : 'L'; 1889 | rargs = nsvg__getArgsPerElement(cmd); 1890 | cpx2 = cpx; cpy2 = cpy; 1891 | break; 1892 | case 'l': 1893 | case 'L': 1894 | nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); 1895 | cpx2 = cpx; cpy2 = cpy; 1896 | break; 1897 | case 'H': 1898 | case 'h': 1899 | nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); 1900 | cpx2 = cpx; cpy2 = cpy; 1901 | break; 1902 | case 'V': 1903 | case 'v': 1904 | nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); 1905 | cpx2 = cpx; cpy2 = cpy; 1906 | break; 1907 | case 'C': 1908 | case 'c': 1909 | nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); 1910 | break; 1911 | case 'S': 1912 | case 's': 1913 | nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); 1914 | break; 1915 | case 'Q': 1916 | case 'q': 1917 | nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); 1918 | break; 1919 | case 'T': 1920 | case 't': 1921 | nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); 1922 | break; 1923 | case 'A': 1924 | case 'a': 1925 | nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); 1926 | cpx2 = cpx; cpy2 = cpy; 1927 | break; 1928 | default: 1929 | if (nargs >= 2) { 1930 | cpx = args[nargs-2]; 1931 | cpy = args[nargs-1]; 1932 | cpx2 = cpx; cpy2 = cpy; 1933 | } 1934 | break; 1935 | } 1936 | nargs = 0; 1937 | } 1938 | } else { 1939 | cmd = item[0]; 1940 | rargs = nsvg__getArgsPerElement(cmd); 1941 | if (cmd == 'M' || cmd == 'm') { 1942 | // Commit path. 1943 | if (p->npts > 0) 1944 | nsvg__addPath(p, closedFlag); 1945 | // Start new subpath. 1946 | nsvg__resetPath(p); 1947 | closedFlag = 0; 1948 | nargs = 0; 1949 | } else if (cmd == 'Z' || cmd == 'z') { 1950 | closedFlag = 1; 1951 | // Commit path. 1952 | if (p->npts > 0) { 1953 | // Move current point to first point 1954 | cpx = p->pts[0]; 1955 | cpy = p->pts[1]; 1956 | cpx2 = cpx; cpy2 = cpy; 1957 | nsvg__addPath(p, closedFlag); 1958 | } 1959 | // Start new subpath. 1960 | nsvg__resetPath(p); 1961 | nsvg__moveTo(p, cpx, cpy); 1962 | closedFlag = 0; 1963 | nargs = 0; 1964 | } 1965 | } 1966 | } 1967 | // Commit path. 1968 | if (p->npts) 1969 | nsvg__addPath(p, closedFlag); 1970 | } 1971 | 1972 | nsvg__addShape(p); 1973 | } 1974 | 1975 | static void nsvg__parseRect(NSVGparser* p, const char** attr) 1976 | { 1977 | float x = 0.0f; 1978 | float y = 0.0f; 1979 | float w = 0.0f; 1980 | float h = 0.0f; 1981 | float rx = -1.0f; // marks not set 1982 | float ry = -1.0f; 1983 | int i; 1984 | 1985 | for (i = 0; attr[i]; i += 2) { 1986 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 1987 | if (strcmp(attr[i], "x") == 0) x = nsvg__parseFloat(p, attr[i+1], 0); 1988 | if (strcmp(attr[i], "y") == 0) y = nsvg__parseFloat(p, attr[i+1], 1); 1989 | if (strcmp(attr[i], "width") == 0) w = nsvg__parseFloat(p, attr[i+1], 0); 1990 | if (strcmp(attr[i], "height") == 0) h = nsvg__parseFloat(p, attr[i+1], 1); 1991 | if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseFloat(p, attr[i+1], 0)); 1992 | if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseFloat(p, attr[i+1], 1)); 1993 | } 1994 | } 1995 | 1996 | if (rx < 0.0f && ry > 0.0f) rx = ry; 1997 | if (ry < 0.0f && rx > 0.0f) ry = rx; 1998 | if (rx < 0.0f) rx = 0.0f; 1999 | if (ry < 0.0f) ry = 0.0f; 2000 | if (rx > w/2.0f) rx = w/2.0f; 2001 | if (ry > h/2.0f) ry = h/2.0f; 2002 | 2003 | if (w != 0.0f && h != 0.0f) { 2004 | nsvg__resetPath(p); 2005 | 2006 | if (rx < 0.00001f || ry < 0.0001f) { 2007 | nsvg__moveTo(p, x, y); 2008 | nsvg__lineTo(p, x+w, y); 2009 | nsvg__lineTo(p, x+w, y+h); 2010 | nsvg__lineTo(p, x, y+h); 2011 | } else { 2012 | // Rounded rectangle 2013 | nsvg__moveTo(p, x+rx, y); 2014 | nsvg__lineTo(p, x+w-rx, y); 2015 | nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); 2016 | nsvg__lineTo(p, x+w, y+h-ry); 2017 | nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); 2018 | nsvg__lineTo(p, x+rx, y+h); 2019 | nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); 2020 | nsvg__lineTo(p, x, y+ry); 2021 | nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); 2022 | } 2023 | 2024 | nsvg__addPath(p, 1); 2025 | 2026 | nsvg__addShape(p); 2027 | } 2028 | } 2029 | 2030 | static void nsvg__parseCircle(NSVGparser* p, const char** attr) 2031 | { 2032 | float cx = 0.0f; 2033 | float cy = 0.0f; 2034 | float r = 0.0f; 2035 | int i; 2036 | 2037 | for (i = 0; attr[i]; i += 2) { 2038 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 2039 | if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseFloat(p, attr[i+1], 0); 2040 | if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseFloat(p, attr[i+1], 1); 2041 | if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseFloat(p, attr[i+1], 2)); 2042 | } 2043 | } 2044 | 2045 | if (r > 0.0f) { 2046 | nsvg__resetPath(p); 2047 | 2048 | nsvg__moveTo(p, cx+r, cy); 2049 | nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); 2050 | nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); 2051 | nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); 2052 | nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); 2053 | 2054 | nsvg__addPath(p, 1); 2055 | 2056 | nsvg__addShape(p); 2057 | } 2058 | } 2059 | 2060 | static void nsvg__parseEllipse(NSVGparser* p, const char** attr) 2061 | { 2062 | float cx = 0.0f; 2063 | float cy = 0.0f; 2064 | float rx = 0.0f; 2065 | float ry = 0.0f; 2066 | int i; 2067 | 2068 | for (i = 0; attr[i]; i += 2) { 2069 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 2070 | if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseFloat(p, attr[i+1], 0); 2071 | if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseFloat(p, attr[i+1], 1); 2072 | if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseFloat(p, attr[i+1], 0)); 2073 | if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseFloat(p, attr[i+1], 1)); 2074 | } 2075 | } 2076 | 2077 | if (rx > 0.0f && ry > 0.0f) { 2078 | 2079 | nsvg__resetPath(p); 2080 | 2081 | nsvg__moveTo(p, cx+rx, cy); 2082 | nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); 2083 | nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); 2084 | nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); 2085 | nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); 2086 | 2087 | nsvg__addPath(p, 1); 2088 | 2089 | nsvg__addShape(p); 2090 | } 2091 | } 2092 | 2093 | static void nsvg__parseLine(NSVGparser* p, const char** attr) 2094 | { 2095 | float x1 = 0.0; 2096 | float y1 = 0.0; 2097 | float x2 = 0.0; 2098 | float y2 = 0.0; 2099 | int i; 2100 | 2101 | for (i = 0; attr[i]; i += 2) { 2102 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 2103 | if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseFloat(p, attr[i + 1], 0); 2104 | if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseFloat(p, attr[i + 1], 1); 2105 | if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseFloat(p, attr[i + 1], 0); 2106 | if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseFloat(p, attr[i + 1], 1); 2107 | } 2108 | } 2109 | 2110 | nsvg__resetPath(p); 2111 | 2112 | nsvg__moveTo(p, x1, y1); 2113 | nsvg__lineTo(p, x2, y2); 2114 | 2115 | nsvg__addPath(p, 0); 2116 | 2117 | nsvg__addShape(p); 2118 | } 2119 | 2120 | static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) 2121 | { 2122 | int i; 2123 | const char* s; 2124 | float args[2]; 2125 | int nargs, npts = 0; 2126 | char item[64]; 2127 | 2128 | nsvg__resetPath(p); 2129 | 2130 | for (i = 0; attr[i]; i += 2) { 2131 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 2132 | if (strcmp(attr[i], "points") == 0) { 2133 | s = attr[i + 1]; 2134 | nargs = 0; 2135 | while (*s) { 2136 | s = nsvg__getNextPathItem(s, item); 2137 | args[nargs++] = (float)atof(item); 2138 | if (nargs >= 2) { 2139 | if (npts == 0) 2140 | nsvg__moveTo(p, args[0], args[1]); 2141 | else 2142 | nsvg__lineTo(p, args[0], args[1]); 2143 | nargs = 0; 2144 | npts++; 2145 | } 2146 | } 2147 | } 2148 | } 2149 | } 2150 | 2151 | nsvg__addPath(p, (char)closeFlag); 2152 | 2153 | nsvg__addShape(p); 2154 | } 2155 | 2156 | static void nsvg__parseSVG(NSVGparser* p, const char** attr) 2157 | { 2158 | int i; 2159 | for (i = 0; attr[i]; i += 2) { 2160 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 2161 | if (strcmp(attr[i], "width") == 0) { 2162 | p->image->width = nsvg__parseFloat(p, attr[i + 1], 0); 2163 | } else if (strcmp(attr[i], "height") == 0) { 2164 | p->image->height = nsvg__parseFloat(p, attr[i + 1], 1); 2165 | } else if (strcmp(attr[i], "viewBox") == 0) { 2166 | sscanf(attr[i + 1], "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", &p->viewMinx, &p->viewMiny, &p->viewWidth, &p->viewHeight); 2167 | } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { 2168 | if (strstr(attr[i + 1], "none") != 0) { 2169 | // No uniform scaling 2170 | p->alignType = NSVG_ALIGN_NONE; 2171 | } else { 2172 | // Parse X align 2173 | if (strstr(attr[i + 1], "xMin") != 0) 2174 | p->alignX = NSVG_ALIGN_MIN; 2175 | else if (strstr(attr[i + 1], "xMid") != 0) 2176 | p->alignX = NSVG_ALIGN_MID; 2177 | else if (strstr(attr[i + 1], "xMax") != 0) 2178 | p->alignX = NSVG_ALIGN_MAX; 2179 | // Parse X align 2180 | if (strstr(attr[i + 1], "yMin") != 0) 2181 | p->alignY = NSVG_ALIGN_MIN; 2182 | else if (strstr(attr[i + 1], "yMid") != 0) 2183 | p->alignY = NSVG_ALIGN_MID; 2184 | else if (strstr(attr[i + 1], "yMax") != 0) 2185 | p->alignY = NSVG_ALIGN_MAX; 2186 | // Parse meet/slice 2187 | p->alignType = NSVG_ALIGN_MEET; 2188 | if (strstr(attr[i + 1], "slice") != 0) 2189 | p->alignType = NSVG_ALIGN_SLICE; 2190 | } 2191 | } 2192 | } 2193 | } 2194 | } 2195 | 2196 | static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) 2197 | { 2198 | int i; 2199 | NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); 2200 | if (grad == NULL) return; 2201 | memset(grad, 0, sizeof(NSVGgradientData)); 2202 | 2203 | grad->type = type; 2204 | nsvg__xformIdentity(grad->xform); 2205 | 2206 | // TODO: does not handle percent and objectBoundingBox correctly yet. 2207 | for (i = 0; attr[i]; i += 2) { 2208 | if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { 2209 | if (strcmp(attr[i], "gradientUnits") == 0) { 2210 | if (strcmp(attr[i+1], "objectBoundingBox") == 0) 2211 | grad->units = NSVG_OBJECT_SPACE; 2212 | else 2213 | grad->units = NSVG_USER_SPACE; 2214 | } else if (strcmp(attr[i], "gradientTransform") == 0) { 2215 | nsvg__parseTransform(grad->xform, attr[i + 1]); 2216 | } else if (strcmp(attr[i], "cx") == 0) { 2217 | grad->radial.cx = nsvg__parseFloat(p, attr[i + 1], 0); 2218 | } else if (strcmp(attr[i], "cy") == 0) { 2219 | grad->radial.cy = nsvg__parseFloat(p, attr[i + 1], 1); 2220 | } else if (strcmp(attr[i], "r") == 0) { 2221 | grad->radial.r = nsvg__parseFloat(p, attr[i + 1], 2); 2222 | } else if (strcmp(attr[i], "fx") == 0) { 2223 | grad->radial.fx = nsvg__parseFloat(p, attr[i + 1], 0); 2224 | } else if (strcmp(attr[i], "fy") == 0) { 2225 | grad->radial.fy = nsvg__parseFloat(p, attr[i + 1], 1); 2226 | } else if (strcmp(attr[i], "x1") == 0) { 2227 | grad->linear.x1 = nsvg__parseFloat(p, attr[i + 1], 0); 2228 | } else if (strcmp(attr[i], "y1") == 0) { 2229 | grad->linear.y1 = nsvg__parseFloat(p, attr[i + 1], 1); 2230 | } else if (strcmp(attr[i], "x2") == 0) { 2231 | grad->linear.x2 = nsvg__parseFloat(p, attr[i + 1], 0); 2232 | } else if (strcmp(attr[i], "y2") == 0) { 2233 | grad->linear.y2 = nsvg__parseFloat(p, attr[i + 1], 1); 2234 | } else if (strcmp(attr[i], "spreadMethod") == 0) { 2235 | if (strcmp(attr[i+1], "pad") == 0) 2236 | grad->spread = NSVG_SPREAD_PAD; 2237 | else if (strcmp(attr[i+1], "reflect") == 0) 2238 | grad->spread = NSVG_SPREAD_REFLECT; 2239 | else if (strcmp(attr[i+1], "repeat") == 0) 2240 | grad->spread = NSVG_SPREAD_REPEAT; 2241 | } else if (strcmp(attr[i], "xlink:href") == 0) { 2242 | strncpy(grad->ref, attr[i+1], 63); 2243 | grad->ref[63] = '\0'; 2244 | } else if (strcmp(attr[i], "id") == 0) { 2245 | strncpy(grad->id, attr[i+1], 63); 2246 | grad->id[63] = '\0'; 2247 | } 2248 | } 2249 | } 2250 | 2251 | grad->next = p->gradients; 2252 | p->gradients = grad; 2253 | } 2254 | 2255 | static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) 2256 | { 2257 | NSVGattrib* curAttr = nsvg__getAttr(p); 2258 | NSVGgradientData* grad; 2259 | NSVGgradientStop* stop; 2260 | int i, idx; 2261 | 2262 | curAttr->stopOffset = 0; 2263 | curAttr->stopColor = 0; 2264 | curAttr->stopOpacity = 1.0f; 2265 | 2266 | for (i = 0; attr[i]; i += 2) { 2267 | nsvg__parseAttr(p, attr[i], attr[i + 1]); 2268 | } 2269 | 2270 | // Add stop to the last gradient. 2271 | grad = p->gradients; 2272 | if (grad == NULL) return; 2273 | 2274 | grad->nstops++; 2275 | grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); 2276 | if (grad->stops == NULL) return; 2277 | 2278 | // Insert 2279 | idx = grad->nstops-1; 2280 | for (i = 0; i < grad->nstops-1; i++) { 2281 | if (curAttr->stopOffset < grad->stops[i].offset) { 2282 | idx = i; 2283 | break; 2284 | } 2285 | } 2286 | if (idx != grad->nstops-1) { 2287 | for (i = grad->nstops-1; i > idx; i--) 2288 | grad->stops[i] = grad->stops[i-1]; 2289 | } 2290 | 2291 | stop = &grad->stops[idx]; 2292 | stop->color = curAttr->stopColor; 2293 | stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; 2294 | stop->offset = curAttr->stopOffset; 2295 | } 2296 | 2297 | static void nsvg__startElement(void* ud, const char* el, const char** attr) 2298 | { 2299 | NSVGparser* p = (NSVGparser*)ud; 2300 | 2301 | if (p->defsFlag) { 2302 | // Skip everything but gradients in defs 2303 | if (strcmp(el, "linearGradient") == 0) { 2304 | nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); 2305 | } else if (strcmp(el, "radialGradient") == 0) { 2306 | nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); 2307 | } else if (strcmp(el, "stop") == 0) { 2308 | nsvg__parseGradientStop(p, attr); 2309 | } 2310 | return; 2311 | } 2312 | 2313 | if (strcmp(el, "g") == 0) { 2314 | nsvg__pushAttr(p); 2315 | nsvg__parseAttribs(p, attr); 2316 | } else if (strcmp(el, "path") == 0) { 2317 | if (p->pathFlag) // Do not allow nested paths. 2318 | return; 2319 | nsvg__pushAttr(p); 2320 | nsvg__parsePath(p, attr); 2321 | nsvg__popAttr(p); 2322 | } else if (strcmp(el, "rect") == 0) { 2323 | nsvg__pushAttr(p); 2324 | nsvg__parseRect(p, attr); 2325 | nsvg__popAttr(p); 2326 | } else if (strcmp(el, "circle") == 0) { 2327 | nsvg__pushAttr(p); 2328 | nsvg__parseCircle(p, attr); 2329 | nsvg__popAttr(p); 2330 | } else if (strcmp(el, "ellipse") == 0) { 2331 | nsvg__pushAttr(p); 2332 | nsvg__parseEllipse(p, attr); 2333 | nsvg__popAttr(p); 2334 | } else if (strcmp(el, "line") == 0) { 2335 | nsvg__pushAttr(p); 2336 | nsvg__parseLine(p, attr); 2337 | nsvg__popAttr(p); 2338 | } else if (strcmp(el, "polyline") == 0) { 2339 | nsvg__pushAttr(p); 2340 | nsvg__parsePoly(p, attr, 0); 2341 | nsvg__popAttr(p); 2342 | } else if (strcmp(el, "polygon") == 0) { 2343 | nsvg__pushAttr(p); 2344 | nsvg__parsePoly(p, attr, 1); 2345 | nsvg__popAttr(p); 2346 | } else if (strcmp(el, "linearGradient") == 0) { 2347 | nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); 2348 | } else if (strcmp(el, "radialGradient") == 0) { 2349 | nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); 2350 | } else if (strcmp(el, "stop") == 0) { 2351 | nsvg__parseGradientStop(p, attr); 2352 | } else if (strcmp(el, "defs") == 0) { 2353 | p->defsFlag = 1; 2354 | } else if (strcmp(el, "svg") == 0) { 2355 | nsvg__parseSVG(p, attr); 2356 | } 2357 | } 2358 | 2359 | static void nsvg__endElement(void* ud, const char* el) 2360 | { 2361 | NSVGparser* p = (NSVGparser*)ud; 2362 | 2363 | if (strcmp(el, "g") == 0) { 2364 | nsvg__popAttr(p); 2365 | } else if (strcmp(el, "path") == 0) { 2366 | p->pathFlag = 0; 2367 | } else if (strcmp(el, "defs") == 0) { 2368 | p->defsFlag = 0; 2369 | } 2370 | } 2371 | 2372 | static void nsvg__content(void* ud, const char* s) 2373 | { 2374 | NSVG_NOTUSED(ud); 2375 | NSVG_NOTUSED(s); 2376 | // empty 2377 | } 2378 | 2379 | static void nsvg__imageBounds(NSVGparser* p, float* bounds) 2380 | { 2381 | NSVGshape* shape; 2382 | shape = p->image->shapes; 2383 | if (shape == NULL) return; 2384 | bounds[0] = shape->bounds[0]; 2385 | bounds[1] = shape->bounds[1]; 2386 | bounds[2] = shape->bounds[2]; 2387 | bounds[3] = shape->bounds[3]; 2388 | for (shape = shape->next; shape != NULL; shape = shape->next) { 2389 | bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); 2390 | bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); 2391 | bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); 2392 | bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); 2393 | } 2394 | } 2395 | 2396 | static float nsvg__viewAlign(float content, float container, int type) 2397 | { 2398 | if (type == NSVG_ALIGN_MIN) 2399 | return 0; 2400 | else if (type == NSVG_ALIGN_MAX) 2401 | return container - content; 2402 | // mid 2403 | return (container - content) * 0.5f; 2404 | } 2405 | 2406 | static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) 2407 | { 2408 | grad->xform[0] *= sx; 2409 | grad->xform[1] *= sx; 2410 | grad->xform[2] *= sy; 2411 | grad->xform[3] *= sy; 2412 | grad->xform[4] += tx*sx; 2413 | grad->xform[5] += ty*sx; 2414 | } 2415 | 2416 | static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) 2417 | { 2418 | NSVGshape* shape; 2419 | NSVGpath* path; 2420 | float tx, ty, sx, sy, us, bounds[4], t[6]; 2421 | int i; 2422 | float* pt; 2423 | 2424 | // Guess image size if not set completely. 2425 | nsvg__imageBounds(p, bounds); 2426 | if (p->viewWidth == 0) { 2427 | if (p->image->width > 0) 2428 | p->viewWidth = p->image->width; 2429 | else 2430 | p->viewWidth = bounds[2]; 2431 | } 2432 | if (p->viewHeight == 0) { 2433 | if (p->image->height > 0) 2434 | p->viewHeight = p->image->height; 2435 | else 2436 | p->viewHeight = bounds[3]; 2437 | } 2438 | if (p->image->width == 0) 2439 | p->image->width = p->viewWidth; 2440 | if (p->image->height == 0) 2441 | p->image->height = p->viewHeight; 2442 | 2443 | tx = -p->viewMinx; 2444 | ty = -p->viewMiny; 2445 | sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; 2446 | sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; 2447 | us = 1.0f / nsvg__convertToPixels(p, 1.0f, units, 0); 2448 | 2449 | // Fix aspect ratio 2450 | if (p->alignType == NSVG_ALIGN_MEET) { 2451 | // fit whole image into viewbox 2452 | sx = sy = nsvg__minf(sx, sy); 2453 | tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; 2454 | ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; 2455 | } else if (p->alignType == NSVG_ALIGN_SLICE) { 2456 | // fill whole viewbox with image 2457 | sx = sy = nsvg__maxf(sx, sy); 2458 | tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; 2459 | ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; 2460 | } 2461 | 2462 | // Transform 2463 | sx *= us; 2464 | sy *= us; 2465 | for (shape = p->image->shapes; shape != NULL; shape = shape->next) { 2466 | shape->bounds[0] = (shape->bounds[0] + tx) * sx; 2467 | shape->bounds[1] = (shape->bounds[1] + ty) * sy; 2468 | shape->bounds[2] = (shape->bounds[2] + tx) * sx; 2469 | shape->bounds[3] = (shape->bounds[3] + ty) * sy; 2470 | for (path = shape->paths; path != NULL; path = path->next) { 2471 | path->bounds[0] = (path->bounds[0] + tx) * sx; 2472 | path->bounds[1] = (path->bounds[1] + ty) * sy; 2473 | path->bounds[2] = (path->bounds[2] + tx) * sx; 2474 | path->bounds[3] = (path->bounds[3] + ty) * sy; 2475 | for (i =0; i < path->npts; i++) { 2476 | pt = &path->pts[i*2]; 2477 | pt[0] = (pt[0] + tx) * sx; 2478 | pt[1] = (pt[1] + ty) * sy; 2479 | } 2480 | } 2481 | 2482 | if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { 2483 | nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); 2484 | memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); 2485 | nsvg__xformInverse(shape->fill.gradient->xform, t); 2486 | } 2487 | if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { 2488 | nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); 2489 | memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); 2490 | nsvg__xformInverse(shape->stroke.gradient->xform, t); 2491 | } 2492 | 2493 | } 2494 | 2495 | sx *= us; 2496 | sy *= us; 2497 | } 2498 | 2499 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) 2500 | { 2501 | NSVGparser* p; 2502 | NSVGimage* ret = 0; 2503 | 2504 | p = nsvg__createParser(); 2505 | if (p == NULL) { 2506 | return NULL; 2507 | } 2508 | p->dpi = dpi; 2509 | 2510 | nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); 2511 | 2512 | // Scale to viewBox 2513 | nsvg__scaleToViewbox(p, units); 2514 | 2515 | ret = p->image; 2516 | p->image = NULL; 2517 | 2518 | nsvg__deleteParser(p); 2519 | 2520 | return ret; 2521 | } 2522 | 2523 | NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) 2524 | { 2525 | FILE* fp = NULL; 2526 | int size; 2527 | char* data = NULL; 2528 | NSVGimage* image = NULL; 2529 | 2530 | fp = fopen(filename, "rb"); 2531 | if (!fp) goto error; 2532 | fseek(fp, 0, SEEK_END); 2533 | size = ftell(fp); 2534 | fseek(fp, 0, SEEK_SET); 2535 | data = (char*)malloc(size+1); 2536 | if (data == NULL) goto error; 2537 | fread(data, size, 1, fp); 2538 | data[size] = '\0'; // Must be null terminated. 2539 | fclose(fp); 2540 | image = nsvgParse(data, units, dpi); 2541 | free(data); 2542 | 2543 | return image; 2544 | 2545 | error: 2546 | if (fp) fclose(fp); 2547 | if (data) free(data); 2548 | if (image) nsvgDelete(image); 2549 | return NULL; 2550 | } 2551 | 2552 | void nsvgDelete(NSVGimage* image) 2553 | { 2554 | NSVGshape *snext, *shape; 2555 | if (image == NULL) return; 2556 | shape = image->shapes; 2557 | while (shape != NULL) { 2558 | snext = shape->next; 2559 | nsvg__deletePaths(shape->paths); 2560 | nsvg__deletePaint(&shape->fill); 2561 | nsvg__deletePaint(&shape->stroke); 2562 | free(shape); 2563 | shape = snext; 2564 | } 2565 | free(image); 2566 | } 2567 | 2568 | #endif 2569 | -------------------------------------------------------------------------------- /svg2gcode.c: -------------------------------------------------------------------------------- 1 | /* 2 | * svg2gcode (c) Matti Koskinen 2014 3 | * 4 | * reorder-function based on StippleGen 5 | * Copyright (C) 2012 by Windell H. Oskay, www.evilmadscientist.com 6 | * 7 | * nanosvg.h (c) 2014 Mikko Mononen 8 | * some routines based on nanosvg-master example1.c 9 | * 10 | * Xgetopt used on VS2010 (or later) by Hans Dietrich,David Smith 11 | * code public domain 12 | * 13 | * No comments :-) 14 | * 15 | * This is free software; you can redistribute it and/or 16 | * modify it under the terms of the GNU Lesser General Public 17 | * License as published by the Free Software Foundation; either 18 | * version 2.1 of the License, or (at your option) any later version. 19 | * 20 | * http://creativecommons.org/licenses/LGPL/2.1/ 21 | * 22 | * This library is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 25 | * Lesser General Public License for more details. 26 | * 27 | * You should have received a copy of the GNU Lesser General Public 28 | * License along with this library; if not, write to the Free Software 29 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 30 | */ 31 | 32 | #include 33 | #include 34 | #ifdef _MSC_VER 35 | #include "XGetopt.h" 36 | #else 37 | #include 38 | #endif 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | //#define TESTRNG // remove if on linux or osx 45 | //#define DO_HPGL //remove comment if you want to get a HPGL-code 46 | #define NANOSVG_IMPLEMENTATION 47 | #include "nanosvg.h" 48 | #define GHEADER "G90\nG92 X0 Y0\n" //add here your specific G-codes 49 | #define GHEADER_NEW "nG90\nG92 X0 Y0\n" //add here your specific G-codes 50 | //separated with newline \n 51 | #define G32 1 52 | #ifdef G32 53 | #define CUTTERON "G0 M3 S%d\n" 54 | #else 55 | #define CUTTERON "M3 S%d\n" //I chose this, change to yours or add comment 56 | // or add newline "\n" if not needed 57 | #endif 58 | #define CUTTEROFF "M5\n" // same for this 59 | #define GFOOTER "M5\nG0 X0 Y0\n" //end G-code here 60 | #define GMODE "M4\n" 61 | //#define DO_HPGL //uncomment to get hpgl-file named test.hpgl on current folder 62 | static float minf(float a, float b) { return a < b ? a : b; } 63 | static float maxf(float a, float b) { return a > b ? a : b; } 64 | static float bounds[4]; 65 | static int pathCount,pointsCount,shapeCount; 66 | static int doBez = 0; 67 | static int simplify = 0; 68 | static struct NSVGimage* g_image = NULL; 69 | 70 | typedef struct { 71 | float x; 72 | float y; 73 | } SVGPoint; 74 | 75 | typedef struct { 76 | float points[8]; 77 | int city; 78 | char closed; 79 | } ToolPath; 80 | 81 | static SVGPoint bezPoints[64]; 82 | static SVGPoint first,last; 83 | static int bezCount = 0; 84 | #ifdef _WIN32 85 | //typedef unsigned long int uint64_t; 86 | //typedef unsigned int uint32_t; 87 | 88 | 89 | static uint64_t seed; 90 | 91 | static int32_t rand31() { 92 | uint64_t tmp1; 93 | uint32_t tmp2; 94 | 95 | /* x = (16807 * x) % 0x7FFFFFFF */ 96 | tmp1 = (uint64_t) ((int32_t) seed * (int64_t) 16807); 97 | tmp2 = (uint32_t) tmp1 & (uint32_t) 0x7FFFFFFF; 98 | tmp2 += (uint32_t) (tmp1 >> 31); 99 | if ((int32_t) tmp2 < (int32_t) 0) 100 | tmp2 = (tmp2 + (uint32_t) 1) & (uint32_t) 0x7FFFFFFF; 101 | return (int32_t)tmp2; 102 | } 103 | static void seedrand(float seedval) { 104 | seed = (int32_t) ((double) seedval + 0.5); 105 | if (seed < 1L) { /* seed from current time */ 106 | seed = time(NULL); 107 | seed = ((seed - 1UL) % 0x7FFFFFFEUL) + 1UL; 108 | } 109 | else { 110 | seed = ((seed - 1L) % 0x7FFFFFFEL) + 1L; 111 | } 112 | seed = rand31(seed); 113 | seed = rand31(seed); 114 | } 115 | 116 | static double drnd31() { 117 | double x; 118 | seed = rand31(); 119 | x = (double)(seed-0x3FFFFFFFL) * (2.0 / 1073741823.015625); 120 | if(fabs(x) > 1.0) 121 | x = drnd31(); 122 | return fabs(x); 123 | } 124 | #endif 125 | 126 | static float distPtSeg(float x, float y, float px, float py, float qx, float qy) 127 | { 128 | float pqx, pqy, dx, dy, d, t; 129 | pqx = qx-px; 130 | pqy = qy-py; 131 | dx = x-px; 132 | dy = y-py; 133 | d = pqx*pqx + pqy*pqy; 134 | t = pqx*dx + pqy*dy; 135 | if (d > 0) t /= d; 136 | if (t < 0) t = 0; 137 | else if (t > 1) t = 1; 138 | dx = px + t*pqx - x; 139 | dy = py + t*pqy - y; 140 | return dx*dx + dy*dy; 141 | } 142 | // bezier smoothing 143 | static void cubicBez(float x1, float y1, float x2, float y2, 144 | float x3, float y3, float x4, float y4, 145 | float tol, int level) 146 | { 147 | float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; 148 | float d; 149 | 150 | if (level > 12) return; 151 | 152 | x12 = (x1+x2)*0.5f; 153 | y12 = (y1+y2)*0.5f; 154 | x23 = (x2+x3)*0.5f; 155 | y23 = (y2+y3)*0.5f; 156 | x34 = (x3+x4)*0.5f; 157 | y34 = (y3+y4)*0.5f; 158 | x123 = (x12+x23)*0.5f; 159 | y123 = (y12+y23)*0.5f; 160 | x234 = (x23+x34)*0.5f; 161 | y234 = (y23+y34)*0.5f; 162 | x1234 = (x123+x234)*0.5f; 163 | y1234 = (y123+y234)*0.5f; 164 | 165 | d = distPtSeg(x1234, y1234, x1,y1, x4,y4); 166 | if (d > tol*tol) { 167 | cubicBez(x1,y1, x12,y12, x123,y123, x1234,y1234, tol, level+1); 168 | cubicBez(x1234,y1234, x234,y234, x34,y34, x4,y4, tol, level+1); 169 | } else { 170 | bezPoints[bezCount].x = x4; 171 | bezPoints[bezCount].y = y4; 172 | bezCount++; 173 | if(bezCount > 63) { 174 | printf("!bez count\n"); 175 | bezCount = 63; 176 | } 177 | } 178 | } 179 | //#define TESTRNG 180 | #ifdef _WIN32 //win doesn't have good RNG 181 | #define RANDOM() drnd31() //((double)rand()/(double)RAND_MAX) 182 | #else //OSX LINUX much faster than win 183 | #define RANDOM() (drand48()) 184 | #endif 185 | 186 | 187 | static int pcomp(const void* a, const void* b) { 188 | SVGPoint* ap = (SVGPoint*)a; 189 | SVGPoint* bp = (SVGPoint*)b; 190 | if(sqrt(ap->x*ap->x + ap->y*ap->y) > sqrt(bp->x*bp->x+bp->y*bp->y)) 191 | return 1; 192 | return -1; 193 | } 194 | 195 | // get all paths and add a city for each path 196 | static void calcPaths(SVGPoint* points, ToolPath* paths,int *cities, int *npaths) { 197 | struct NSVGshape* shape; 198 | struct NSVGpath* path; 199 | FILE *f; 200 | int i,j,k,l,p,b; 201 | SVGPoint* pts; 202 | #ifdef DO_HPGL 203 | f=fopen("test.hpgl","w"); 204 | fprintf(f,"IN;SP1;"); 205 | #endif 206 | i=0; 207 | k=0; 208 | j=0; 209 | p=0; 210 | for(shape = g_image->shapes; shape != NULL; shape=shape->next) { 211 | for(path = shape->paths; path != NULL; path=path->next) { 212 | if(path->closed && simplify) { 213 | pts = (SVGPoint*)malloc(path->npts*sizeof(SVGPoint)); 214 | 215 | for(l=0;lnpts-1;l++) { 216 | float *pp=&path->pts[l*2]; 217 | if(l==0) { 218 | points[i].x = pp[0]; 219 | points[i].y = pp[1]; 220 | } 221 | pts[l].x = pp[0]; 222 | pts[l].y = pp[1]; 223 | } 224 | qsort((void*)pts,path->npts-1,sizeof(SVGPoint),pcomp); 225 | paths[k].points[0] = pts[path->npts-2].x; 226 | paths[k].points[1] = pts[path->npts-2].y; 227 | paths[k].points[2] = pts[0].x; 228 | paths[k].points[3] = pts[0].y; 229 | paths[k].closed = path->closed; 230 | paths[k].city = i; 231 | k++; 232 | //fprintf(stderr,"i %d pts %f %f %f %f\n",i,pts[path->npts-2].x,pts[path->npts-2].y,pts[0].x,pts[0].y); 233 | free(pts); 234 | 235 | goto cont; 236 | } 237 | 238 | for(j=0;jnpts-1;(doBez ? j+=3 : j++)) { 239 | float *pp = &path->pts[j*2]; 240 | if(j==0) { 241 | 242 | points[i].x = pp[0]; 243 | points[i].y = pp[1]; 244 | #ifdef DO_HPGL 245 | fprintf(f,"PU%d,%d;",(int)pp[0],(int)pp[1]); 246 | fflush(f); 247 | 248 | } else { 249 | fprintf(f,"PD%d,%d;",(int)pp[0],(int)pp[1]); 250 | fflush(f); 251 | #endif 252 | } 253 | if(doBez) { 254 | for(b=0;b<8;b++) 255 | paths[k].points[b]=pp[b]; 256 | } else { 257 | paths[k].points[0] = pp[0]; 258 | paths[k].points[1] = pp[1]; 259 | paths[k].points[2] = pp[0]; 260 | paths[k].points[3] = pp[1]; 261 | } 262 | paths[k].closed = path->closed; 263 | paths[k].city = i; 264 | k++; 265 | 266 | } 267 | cont: 268 | if(k>pointsCount) { 269 | printf("error k > \n"); 270 | #ifdef DO_HPGL 271 | fprintf(f,"PU0,0;\n"); 272 | fclose(f); 273 | #endif 274 | *npaths = 0; 275 | return; 276 | 277 | } 278 | if(i>pathCount) { 279 | printf("error i > \n"); 280 | #ifdef DO_HPGL 281 | fprintf(f,"PU0,0;\n"); 282 | fclose(f); 283 | #endif 284 | exit(-1); 285 | /* 286 | *npaths=k; 287 | return; 288 | */ 289 | } 290 | cities[i] = i; 291 | i++; 292 | } 293 | 294 | j++; 295 | } 296 | printf("total paths %d, total points %d\n",i,k); 297 | *npaths = k; 298 | #ifdef DO_HPGL 299 | fprintf(f,"PU0,0;\n"); 300 | fclose(f); 301 | #endif 302 | } 303 | 304 | static void calcBounds(struct NSVGimage* image) 305 | { 306 | struct NSVGshape* shape; 307 | struct NSVGpath* path; 308 | int i; 309 | bounds[0] = FLT_MAX; 310 | bounds[1] = FLT_MAX; 311 | bounds[2] = -FLT_MAX; 312 | bounds[3] = -FLT_MAX; 313 | pathCount = 0; 314 | pointsCount = 0; 315 | shapeCount = 0; 316 | for (shape = image->shapes; shape != NULL; shape = shape->next) { 317 | for (path = shape->paths; path != NULL; path = path->next) { 318 | for (i = 0; i < path->npts-1; i++) { 319 | float* p = &path->pts[i*2]; 320 | bounds[0] = minf(bounds[0], p[0]); 321 | bounds[1] = minf(bounds[1], p[1]); 322 | bounds[2] = maxf(bounds[2], p[0]); 323 | bounds[3] = maxf(bounds[3], p[1]); 324 | pointsCount++; 325 | } 326 | pathCount++; 327 | } 328 | shapeCount++; 329 | } 330 | printf("shapes %d\n",shapeCount); 331 | } 332 | //reorder the paths to minimize cutter movement 333 | static void reorder(SVGPoint* pts, int* cities, int ncity,char xy) { 334 | int i,j,k,temp1,temp2,indexA,indexB, indexH, indexL; 335 | float dx,dy,dist,dist2; 336 | SVGPoint p1,p2,p3,p4; 337 | for(i=0;i<800*ncity;i++) { 338 | indexA = (int)(RANDOM()*(ncity-2)); 339 | indexB = (int)(RANDOM()*(ncity-2)); 340 | if(abs(indexB-indexA) < 2) 341 | continue; 342 | if(indexB < indexA) { 343 | temp1 = indexB; 344 | indexB = indexA; 345 | indexA = temp1; 346 | } 347 | p1 = pts[cities[indexA]]; 348 | p2 = pts[cities[indexA+1]]; 349 | p3 = pts[cities[indexB]]; 350 | p4 = pts[cities[indexB+1]]; 351 | dx = p1.x-p2.x; 352 | dy = p1.y-p2.y; 353 | if(xy) 354 | dist = dx*dx+dy*dy; 355 | else 356 | dist = dy*dy; 357 | dx = p3.x-p4.x; 358 | dy = p3.y-p4.y; 359 | if(xy) 360 | dist += (dx*dx+dy*dy); 361 | else 362 | dist += dy*dy; 363 | dx = p1.x-p3.x; 364 | dy = p1.y-p3.y; 365 | if(xy) 366 | dist2 = dx*dx+dy*dy; 367 | else 368 | dist2 = dy*dy; 369 | dx = p2.x-p4.x; 370 | dy = p2.y-p4.y; 371 | if(xy) 372 | dist2 += (dx*dx+dy*dy); 373 | else 374 | dist2 += dy*dy; 375 | if(dist2 < dist) { 376 | indexH = indexB; 377 | indexL = indexA+1; 378 | while(indexH > indexL) { 379 | temp1 = cities[indexL]; 380 | cities[indexL]=cities[indexH]; 381 | cities[indexH] = temp1; 382 | indexH--; 383 | indexL++; 384 | } 385 | } 386 | } 387 | } 388 | 389 | void help() { 390 | printf("usage: svg2gcode [options] svg-file gcode-file\n"); 391 | printf("options:\n"); 392 | printf("\t-Y shift Y-ax\n"); 393 | printf("\t-X sfit X-ax\n"); 394 | printf("\t-c use z-axis instead of laser\n"); 395 | printf("\t-f feed rate (3500)\n"); 396 | printf("\t-n # number of reorders (30)\n"); 397 | printf("\t-s scale (1.0)\n"); 398 | printf("\t-F flip Y-axis\n"); 399 | printf("\t-w final width in mm\n"); 400 | printf("\t-t Bezier tolerance (0.5)\n"); 401 | printf("\t-m machine accuracy (0.1)\n"); 402 | printf("\t-z z-traverse (1.0)\n"); 403 | printf("\t-Z z-engage (-1.0)\n"); 404 | printf("\t-B do Bezier curve smoothing\n"); 405 | printf("\t-T engrave only TSP-path\n"); 406 | printf("\t-V optmize for Voronoi Stipples\n"); 407 | printf("\t-h this help\n"); 408 | } 409 | 410 | int main(int argc, char* argv[]) { 411 | 412 | int i,j,k,l,first = 1; 413 | //struct NSVGimage* image; 414 | struct NSVGshape *shape1,*shape2; 415 | struct NSVGpath *path1,*path2; 416 | SVGPoint* points; 417 | ToolPath* paths; 418 | int *cities,npaths; 419 | int feed = 3500; 420 | //int shiftY = 30; 421 | int fullspeed=4800; 422 | float ztraverse = 1.; 423 | float zengage = -1.; 424 | float width = -1; 425 | char xy = 1; 426 | float w,widthInmm = -1.; 427 | int numReord = 30; 428 | float scale = 1.; 429 | float tol = 0.5; 430 | float size = 100; 431 | float accuracy = 0.1; 432 | float x,y,bx,by,bxold,byold,d,firstx,firsty; 433 | float xold,yold; 434 | int flip = 0; 435 | int skip = 0; 436 | int units = 0; 437 | int printed=0; 438 | int cncMode = 0; 439 | int tsp = 0; 440 | int tspFirst = 1; 441 | int autoshift = 0; 442 | float ashift = 0.; 443 | int firstandonly = 2; // first svg 444 | int append = 0; 445 | int last = 0; // last svg 446 | int waitTime = 25; 447 | float maxx = -1000.,minx=1000.,maxy = -1000.,miny=1000.,zmax = -1000.,zmin = 1000; 448 | float shiftX = 0.; 449 | float shiftY = 0.; 450 | float zeroX = 0.; 451 | float zeroY = 0.; 452 | FILE *gcode; 453 | int pwr = 90; 454 | int ch; 455 | int dwell = -1; 456 | char gbuff[128]; 457 | int center = 0; 458 | printf("v0.0001 8.11.2020\n"); 459 | //seed48(NULL); 460 | if(argc < 3) { 461 | help(); 462 | return -1; 463 | } 464 | simplify = 0; 465 | while((ch=getopt(argc,argv,"D:ABhf:n:s:Fz:Z:S:w:t:m:cTV1aLP:CY:X:")) != EOF) { 466 | switch(ch) { 467 | case 'C': center = 1; 468 | break; 469 | case 'P': pwr = atoi(optarg); 470 | break; 471 | case 'D': waitTime=atoi(optarg); 472 | dwell = atoi(optarg); 473 | break; 474 | case 'a': append = 1; firstandonly = 0; 475 | break; 476 | case 'V': xy = 0; 477 | break; 478 | case 'T': tsp = 1; 479 | if(xy == 0) 480 | xy = 1; 481 | break; 482 | case 'c': cncMode = 1; 483 | break; 484 | 485 | case 'Y': shiftY = atof(optarg); // shift 486 | break; 487 | 488 | 489 | case 'X': shiftX = atof(optarg); // shift 490 | break; 491 | 492 | case 'A':autoshift = 1; 493 | break; 494 | case 'm': accuracy=atof(optarg); 495 | break; 496 | case 'B': doBez = 1; 497 | break; 498 | case 'h': help(); 499 | break; 500 | case 'f': feed = atoi(optarg); 501 | break; 502 | case 'n': numReord = atoi(optarg); 503 | break; 504 | case 's': scale = atof(optarg); 505 | break; 506 | case 't': tol = atof(optarg); 507 | break; 508 | case 'F': flip = 1; 509 | break; 510 | case 'z': ztraverse = atof(optarg); 511 | break; 512 | case 'Z': zengage = atof(optarg); 513 | break; 514 | case 'w': widthInmm = atof(optarg); 515 | break; 516 | default: help(); 517 | return(1); 518 | break; 519 | } 520 | } 521 | if(shiftY != 30. && flip == 1) 522 | shiftY = -shiftY; 523 | g_image = nsvgParseFromFile(argv[optind],"px",96); 524 | if(g_image == NULL) { 525 | printf("error: Can't open input %s\n",argv[optind]); 526 | return -1; 527 | } 528 | calcBounds(g_image); 529 | fprintf(stderr,"bounds %f %f X %f %f\n",bounds[0],bounds[1],bounds[2],bounds[3]); 530 | width = g_image->width; 531 | fprintf(stderr,"width %f\n",width); 532 | w = fabs(bounds[0]-bounds[2]); 533 | if(widthInmm != -1.0) 534 | scale = widthInmm/w; 535 | fprintf(stderr,"width %f w %f scale %f width in mm %f\n",width,w,scale,widthInmm); 536 | zeroX = -bounds[0]; 537 | zeroY = -bounds[1]; 538 | /* old code 539 | if(bounds[0] < 0) 540 | zeroX = fabs(bounds[0]); 541 | if(bounds[2] < 0) 542 | zeroY = fabs(bounds[1]); 543 | //shiftY = shiftY+zeroY+scale*fabs(bounds[1]-bounds[3])/2.; 544 | //shiftX = shiftX+zeroX+scale*fabs(bounds[0]-bounds[2])/2.; 545 | // if(center == 0.) { 546 | // shiftX = 0.; 547 | // shiftY = 0.; 548 | //} 549 | */ 550 | 551 | #ifdef _WIN32 552 | seedrand((float)time(0)); 553 | #endif 554 | if(append) 555 | gcode = fopen(argv[optind+1],"a"); 556 | else 557 | gcode=fopen(argv[optind+1],"w"); 558 | if(gcode == NULL) { 559 | printf("can't open output %s\n",argv[optind+1]); 560 | return -1; 561 | } 562 | printf("paths %d points %d\n",pathCount, pointsCount); 563 | // allocate memory 564 | points = (SVGPoint*)malloc(pathCount*2*sizeof(SVGPoint)); 565 | cities = (int*)malloc(pathCount*2*sizeof(int)); 566 | paths = (ToolPath*)malloc(pointsCount*2*sizeof(ToolPath)); 567 | 568 | npaths = 0; 569 | calcPaths(points,paths,cities,&npaths); 570 | /* if(doBez) { 571 | Bez = fopen("bez.lst","w"); 572 | if(Bez == NULL) { 573 | printf("bez file!\n"); 574 | return -1; 575 | } 576 | fprintf(Bez,"paths = %d\n",npaths); 577 | } 578 | */ 579 | printf("reorder "); 580 | for(k=0;k= npaths-1) { 607 | //printf("k > \n"); 608 | continue; 609 | } 610 | firstx = x = (paths[k].points[0]+zeroX)*scale+shiftX; 611 | firsty = y = (paths[k].points[1]+zeroY)*scale+shiftY; // changed 612 | if(flip) { 613 | firsty = -firsty; 614 | y = -y; 615 | } 616 | if(x > maxx) 617 | maxx = x; 618 | if(x < minx) 619 | minx = x; 620 | if(y > maxy) 621 | maxy = y; 622 | if(y < miny) 623 | miny = y; 624 | if(tsp) { 625 | if(tspFirst) { 626 | fprintf(gcode,"G1 X%.4f Y%.4f F4800 \n",x,y); 627 | fprintf(gcode,"G4 P0\n"); 628 | tspFirst = 0; 629 | fprintf(gcode,CUTTERON,pwr); 630 | } 631 | else { 632 | fprintf(gcode,"G1 X%.1f Y%.1f F%d\n",x,y,feed); 633 | fprintf(gcode,"G4 P0\n"); 634 | } 635 | continue; 636 | } 637 | if(!cncMode) 638 | fprintf(gcode,"G0 X%.4f Y%.4f\n",x,y); 639 | #ifndef G32 640 | else 641 | fprintf(gcode,"G0 X%.1f Y%.1f\n",x,y); 642 | fprintf(gcode,"G4 P0\n"); 643 | #endif 644 | fprintf(gcode,"( city %d )\n",paths[k].city); 645 | #ifndef G32 646 | if(!cncMode) 647 | fprintf(gcode,CUTTERON,pwr); 648 | fprintf(gcode,"G4 P0\n"); 649 | #endif 650 | printed=0; 651 | if(tsp) 652 | continue; 653 | for(j=k;j bounds[2] || bezPoints[l].x < bounds[0] || isnan(bezPoints[l].x)) { 670 | printf("bezPoints %f %f\n",bezPoints[l].x,bounds[0]); 671 | continue; 672 | } 673 | if(bezPoints[l].y > bounds[3]) { 674 | printf("bezPoints y %d\n",l); 675 | continue; 676 | } 677 | bx = (bezPoints[l].x+zeroX)*scale+shiftX; 678 | by = (bezPoints[l].y+zeroY)*scale+shiftY; 679 | if(flip) 680 | by = -by; 681 | if(bx > maxx) 682 | maxx = bx; 683 | if(bx < minx) 684 | minx = x; 685 | if(by > maxy) 686 | maxy = by; 687 | if(y < miny) 688 | miny = by; 689 | 690 | d = sqrt((bx-bxold)*(bx-bxold)+(by-byold)*(by-byold)); 691 | if(1) { 692 | printed = 1; 693 | fprintf(gcode,"G1 X%.4f Y%.4f F%d\n",bx,by,feed); 694 | #ifndef G32 695 | fprintf(gcode,"G4 P0\n"); 696 | #endif 697 | } else { 698 | fprintf(gcode,"( acc )\n");;//continue; 699 | //fprintf(gcode,"G05 P%d\n",(int)(pwr*0.33)); 700 | fprintf(gcode,"G1 X%.4f Y%.4f F%d\n",bx,by,feed); 701 | } 702 | bxold = bx; 703 | byold = by; 704 | } 705 | } else { 706 | x = (paths[j].points[0]-fabs(bounds[0]))*scale+shiftX; 707 | y = (paths[j].points[1]-fabs(bounds[1]))*scale+shiftY; 708 | if(flip) 709 | y = -y; 710 | if(x > maxx) 711 | maxx = x; 712 | if(x < minx) 713 | minx = x; 714 | if(y > maxy) 715 | maxy = y; 716 | if(y < miny) 717 | miny = y; 718 | 719 | // if(paths[j].points[0]==paths[j].points[2] && paths[j].points[1]==paths[j].points[3]) { 720 | if(1) { 721 | //d = sqrt((x-xold)*(x-xold)+(y-yold)*(y-yold)); 722 | if(1) { 723 | printed = 1; 724 | fprintf(gcode,"G1 X%.4f Y%.4f F%d\n",x,y,feed); 725 | #ifndef G32 726 | fprintf(gcode,"G4 P0\n"); 727 | #endif 728 | } else { 729 | ;//continue; 730 | //fprintf(gcode,"G05 P%d\n",(int)(pwr*0.33)); 731 | //fprintf(gcode,"G01 X%.4f Y%.4f F%d\n",x,y,feed); 732 | } 733 | first = 0; 734 | xold = x; 735 | yold = y; 736 | } else { 737 | x = (paths[j].points[0]-fabs(bounds[0]))*scale+shiftX; 738 | y = (paths[j].points[1]-fabs(bounds[1]))*scale+shiftY; 739 | if(flip) 740 | y = -y; 741 | fprintf(gcode,CUTTEROFF); 742 | fprintf(gcode,"( simplified )\n"); 743 | fprintf(gcode,"G0 X%.4f Y%.4f\n",x,y); 744 | #ifndef G32 745 | fprintf(gcode,"G4 P0\n"); 746 | #endif 747 | x = (paths[j].points[2]-fabs(bounds[0]))*scale+shiftX; 748 | y = (paths[j].points[3]-fabs(bounds[1]))*scale+shiftY; 749 | if(flip) 750 | y = -y; 751 | #ifndef G32 752 | fprintf(gcode,CUTTERON,pwr); 753 | #endif 754 | //fprintf(gcode,"G01 X%.4f Y%.4f F%d\n",x,y,feed); 755 | xold = x; 756 | yold = y; 757 | } 758 | } 759 | paths[j].city = -1; 760 | /* 761 | if(printed == 0) { 762 | if(doBez) 763 | fprintf(gcode,"G01 X%.4f Y%.4f F%d\n",bx,by,feed); 764 | else 765 | fprintf(gcode,"G01 X%.4f Y%.4f F%d\n",x,y,feed); 766 | } 767 | */ 768 | } else 769 | break; 770 | } 771 | if(tsp) 772 | continue; 773 | if(paths[j].closed) { 774 | fprintf(gcode, "( end )\n"); 775 | //bx = (bezPoints[0].x-bounds[0])*scale; 776 | //by = (flip ? (bounds[3]-bezPoints[0].y)*scale : (bezPoints[0].y-bounds[1])*scale); 777 | 778 | fprintf(gcode,"G1 X%.4f Y%.4f F%d\n",firstx,firsty,feed); 779 | #ifndef G32 780 | fprintf(gcode,"G4 P0\n"); 781 | #endif 782 | printed = 1; 783 | } 784 | if(!cncMode) { 785 | if(!printed) { 786 | #ifndef G32 787 | if(dwell != -1) { 788 | fprintf(gcode,"( lowpwr )\n"); 789 | fprintf(gcode,"M3 S10\n"); 790 | sprintf(gbuff,"G4 P%d\n",dwell); 791 | fprintf(gcode,"%s",gbuff); 792 | } 793 | fprintf(gcode,"M5\n"); 794 | #else 795 | fprintf(gcode,"( new dwell? )\n"); 796 | #endif 797 | //fprintf(gcode,"G05 P%d\n",pwr); 798 | } 799 | #ifndef G32 800 | fprintf(gcode,CUTTEROFF); 801 | #endif 802 | } else { 803 | fprintf(gcode,"G1 ZF%d\n",feed); 804 | fprintf(gcode,"G4 P0\n"); 805 | printed = 0; 806 | } 807 | } 808 | #ifndef G32 809 | if(tsp) 810 | fprintf(gcode,CUTTEROFF); 811 | #else 812 | fprintf(gcode,"M5\n"); 813 | #endif 814 | fprintf(gcode,GFOOTER); 815 | printf("( size X%.4f Y%.4f x X%.4f Y%.4f )\n",minx,miny,maxx,maxy); 816 | fprintf(gcode,"( size X%.4f Y%.4f x X%.4f Y%.4f )\n",minx,miny,maxx,maxy); 817 | fclose(gcode); 818 | free(points); 819 | free(cities); 820 | free(paths); 821 | nsvgDelete(g_image); 822 | return 0; 823 | } 824 | --------------------------------------------------------------------------------