The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

SvgParser.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/SvgParser.vala.
Merge branch '2.x' of github.com:johanmattssonm/birdfont
1 /* 2 Copyright (C) 2012 - 2016 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using B; 16 using Math; 17 using SvgBird; 18 19 namespace BirdFont { 20 21 public enum SvgFormat { 22 NONE, 23 INKSCAPE, 24 ILLUSTRATOR 25 } 26 27 public enum SvgType { 28 COLOR, 29 REGULAR 30 } 31 32 public class SvgParser { 33 34 SvgFormat format = SvgFormat.ILLUSTRATOR; 35 36 public SvgParser () { 37 } 38 39 public void set_format (SvgFormat f) { 40 format = f; 41 } 42 43 public static void import (SvgType type) { 44 FileChooser fc = new FileChooser (); 45 fc.file_selected.connect ((p) => { 46 string path; 47 48 if (p == null) { 49 return; 50 } 51 52 path = (!) p; 53 54 if (type == SvgType.REGULAR) { 55 import_svg (path); 56 } else if (type == SvgType.COLOR) { 57 Glyph glyph = MainWindow.get_current_glyph (); 58 import_color_svg (glyph, path); 59 } 60 }); 61 62 fc.add_extension ("svg"); 63 MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD); 64 } 65 66 public static void import_color_svg (Glyph glyph, string path) { 67 EmbeddedSvg drawing = SvgParser.parse_embedded_svg_file (path); 68 69 Layer layer = new Layer (); 70 layer.name = "SVG"; 71 layer.add_object (drawing); 72 73 glyph.add_layer (layer); 74 75 // FIXME: update GUI 76 } 77 78 public static void import_folder (SvgType type) { 79 FileChooser fc = new FileChooser (); 80 fc.file_selected.connect ((p) => { 81 string path; 82 File svg_folder; 83 File svg; 84 bool imported; 85 FileEnumerator enumerator; 86 FileInfo? file_info; 87 string file_name; 88 Font font; 89 90 if (p == null) { 91 return; 92 } 93 94 path = (!) p; 95 svg_folder = File.new_for_path (path); 96 font = BirdFont.get_current_font (); 97 98 try { 99 enumerator = svg_folder.enumerate_children (FileAttribute.STANDARD_NAME, 0); 100 while ((file_info = enumerator.next_file ()) != null) { 101 file_name = ((!) file_info).get_name (); 102 103 if (file_name.has_suffix (".svg")) { 104 svg = get_child (svg_folder, file_name); 105 imported = import_svg_file (font, svg, type); 106 107 if (!imported) { 108 warning ("Can't import %s.", (!) svg.get_path ()); 109 } else { 110 font.touch (); 111 } 112 } 113 } 114 } catch (Error e) { 115 warning (e.message); 116 } 117 }); 118 119 MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD | FileChooser.DIRECTORY); 120 } 121 122 public static void import_svg_data (string xml_data, SvgFormat format = SvgFormat.NONE) { 123 PathList path_list = new PathList (); 124 Glyph glyph; 125 string[] lines = xml_data.split ("\n"); 126 bool has_format = false; 127 SvgParser parser = new SvgParser (); 128 129 foreach (string l in lines) { 130 if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) { 131 parser.set_format (SvgFormat.ILLUSTRATOR); 132 has_format = true; 133 } 134 135 if (l.index_of ("Inkscape") > -1 || l.index_of ("inkscape") > -1) { 136 parser.set_format (SvgFormat.INKSCAPE); 137 has_format = true; 138 } 139 } 140 141 if (format != SvgFormat.NONE) { 142 parser.set_format (format); 143 } 144 145 // parse the file 146 if (!has_format) { 147 warn_if_test ("No format identifier found in SVG parser.\n"); 148 } 149 150 XmlTree xml_tree = new XmlTree (xml_data); 151 path_list = parser.parse_svg_file (xml_tree.get_root ()); 152 153 glyph = MainWindow.get_current_glyph (); 154 foreach (Path p in path_list.paths) { 155 PathObject path = new PathObject.for_path (p); 156 glyph.add_object (path); 157 glyph.add_active_object (null, path); // FIXME: groups 158 path.update_region_boundaries (); 159 } 160 161 glyph.close_path (); 162 } 163 164 public static string replace (string content, string start, string stop, string replacement) { 165 int i_tag = content.index_of (start); 166 int end_tag = content.index_of (stop, i_tag); 167 string c = ""; 168 169 if (i_tag > -1) { 170 c = content.substring (0, i_tag) 171 + replacement 172 + content.substring (end_tag + stop.length); 173 } else { 174 c = content; 175 } 176 177 return c; 178 } 179 180 public static void import_svg (string path) { 181 string svg_data; 182 try { 183 FileUtils.get_contents (path, out svg_data); 184 } catch (GLib.Error e) { 185 warning (e.message); 186 } 187 import_svg_data (svg_data); 188 } 189 190 private PathList parse_svg_file (XmlElement tag) { 191 Layer pl = new Layer (); 192 193 foreach (XmlElement t in tag) { 194 195 if (t.get_name () == "g") { 196 parse_layer (t, pl); 197 } 198 199 if (t.get_name () == "switch") { 200 parse_layer (t, pl); 201 } 202 203 if (t.get_name () == "path") { 204 parse_path (t, pl); 205 } 206 207 if (t.get_name () == "polygon") { 208 parse_polygon (t, pl); 209 } 210 211 if (t.get_name () == "polyline") { 212 parse_polyline (t, pl); 213 } 214 215 if (t.get_name () == "circle") { 216 parse_circle (t, pl); 217 } 218 219 if (t.get_name () == "ellipse") { 220 parse_ellipse (t, pl); 221 } 222 223 if (t.get_name () == "line") { 224 parse_line (t, pl); 225 } 226 } 227 228 return LayerUtils.get_all_paths (pl); 229 } 230 231 private void parse_layer (XmlElement tag, Layer pl) { 232 Layer layer; 233 bool hidden = false; 234 235 foreach (Attribute attr in tag.get_attributes ()) { 236 if (attr.get_name () == "display" && attr.get_content () == "none") { 237 hidden = true; 238 } 239 240 if (attr.get_name () == "visibility" 241 && (attr.get_content () == "hidden" 242 || attr.get_content () == "collapse")) { 243 hidden = true; 244 } 245 } 246 247 if (hidden) { 248 return; 249 } 250 251 foreach (XmlElement t in tag) { 252 if (t.get_name () == "path") { 253 parse_path (t, pl); 254 } 255 256 if (t.get_name () == "g") { 257 layer = new Layer (); 258 parse_layer (t, layer); 259 pl.objects.add (layer); 260 } 261 262 if (t.get_name () == "polygon") { 263 parse_polygon (t, pl); 264 } 265 266 if (t.get_name () == "polyline") { 267 parse_polyline (t, pl); 268 } 269 270 if (t.get_name () == "rect") { 271 parse_rect (t, pl); 272 } 273 274 if (t.get_name () == "circle") { 275 parse_circle (t, pl); 276 } 277 278 if (t.get_name () == "ellipse") { 279 parse_ellipse (t, pl); 280 } 281 282 if (t.get_name () == "line") { 283 parse_line (t, pl); 284 } 285 } 286 287 foreach (Attribute attr in tag.get_attributes ()) { 288 if (attr.get_name () == "transform") { 289 transform (attr.get_content (), pl); 290 } 291 } 292 } 293 294 private void transform (string transform_functions, Layer layer) { 295 PathList path_list = new PathList (); 296 297 foreach (SvgBird.Object o in layer.objects) { 298 if (o is PathObject) { 299 path_list.add (((PathObject) o).get_path ()); 300 } 301 } 302 303 transform_paths (transform_functions, path_list); 304 transform_subgroups (transform_functions, layer); 305 } 306 307 private void transform_subgroups (string transform_functions, Layer layer) { 308 foreach (Layer subgroup in layer.get_sublayers ()) { 309 transform (transform_functions, subgroup); 310 } 311 } 312 313 private void transform_paths (string transform_functions, PathList pl) { 314 string data = transform_functions.dup (); 315 string[] functions; 316 317 // use only a single space as separator 318 while (data.index_of (" ") > -1) { 319 data = data.replace (" ", " "); 320 } 321 322 return_if_fail (data.index_of (")") > -1); 323 324 // add separator 325 data = data.replace (") ", "|"); 326 data = data.replace (")", "|"); 327 functions = data.split ("|"); 328 329 for (int i = functions.length - 1; i >= 0; i--) { 330 if (functions[i].has_prefix ("translate")) { 331 translate (functions[i], pl); 332 } 333 334 if (functions[i].has_prefix ("scale")) { 335 scale (functions[i], pl); 336 } 337 338 if (functions[i].has_prefix ("matrix")) { 339 matrix (functions[i], pl); 340 } 341 342 // TODO: rotate etc. 343 } 344 } 345 346 /** @param path a path in the cartesian coordinate system 347 * The other parameters are in the SVG coordinate system. 348 */ 349 public static void apply_matrix (Path path, double a, double b, double c, 350 double d, double e, double f){ 351 352 double dx, dy; 353 Font font = BirdFont.get_current_font (); 354 Glyph glyph = MainWindow.get_current_glyph (); 355 356 foreach (EditPoint ep in path.points) { 357 ep.tie_handles = false; 358 ep.reflective_point = false; 359 } 360 361 foreach (EditPoint ep in path.points) { 362 apply_matrix_on_handle (ep.get_right_handle (), a, b, c, d, e, f); 363 apply_matrix_on_handle (ep.get_left_handle (), a, b, c, d, e, f); 364 365 ep.independent_y = font.top_position - ep.independent_y; 366 ep.independent_x -= glyph.left_limit; 367 368 dx = a * ep.independent_x + c * ep.independent_y + e; 369 dy = b * ep.independent_x + d * ep.independent_y + f; 370 371 ep.independent_x = dx; 372 ep.independent_y = dy; 373 374 ep.independent_y = font.top_position - ep.independent_y; 375 ep.independent_x += glyph.left_limit; 376 } 377 } 378 379 public static void apply_matrix_on_handle (EditPointHandle h, 380 double a, double b, double c, 381 double d, double e, double f){ 382 383 double dx, dy; 384 Font font = BirdFont.get_current_font (); 385 Glyph glyph = MainWindow.get_current_glyph (); 386 387 h.y = font.top_position - h.y; 388 h.x -= glyph.left_limit; 389 390 dx = a * h.x + c * h.y + e; 391 dy = b * h.x + d * h.y + f; 392 393 h.x = dx; 394 h.y = dy; 395 396 h.y = font.top_position - h.y; 397 h.x += glyph.left_limit; 398 } 399 400 401 private void matrix (string function, PathList pl) { 402 string parameters = get_transform_parameters (function); 403 string[] p = parameters.split (" "); 404 405 if (p.length != 6) { 406 warning ("Expecting six parameters for matrix transformation."); 407 return; 408 } 409 410 foreach (Path path in pl.paths) { 411 apply_matrix (path, parse_double (p[0]), parse_double (p[1]), 412 parse_double (p[2]), parse_double (p[3]), 413 parse_double (p[4]), parse_double (p[5])); 414 } 415 } 416 417 private void scale (string function, PathList pl) { 418 string parameters = get_transform_parameters (function); 419 string[] p = parameters.split (" "); 420 double x, y; 421 422 x = 1; 423 y = 1; 424 425 if (p.length > 0) { 426 x = parse_double (p[0]); 427 } 428 429 if (p.length > 1) { 430 y = parse_double (p[1]); 431 } 432 433 foreach (Path path in pl.paths) { 434 path.scale (-x, y); 435 } 436 } 437 438 private void translate (string function, PathList pl) { 439 string parameters = get_transform_parameters (function); 440 string[] p = parameters.split (" "); 441 double x, y; 442 443 x = 0; 444 y = 0; 445 446 if (p.length > 0) { 447 x = parse_double (p[0]); 448 } 449 450 if (p.length > 1) { 451 y = parse_double (p[1]); 452 } 453 454 foreach (Path path in pl.paths) { 455 path.move (x, -y); 456 } 457 } 458 459 private string get_transform_parameters (string function) { 460 int i; 461 string param = ""; 462 463 i = function.index_of ("("); 464 return_val_if_fail (i != -1, param); 465 param = function.substring (i); 466 467 param = param.replace ("(", ""); 468 param = param.replace ("\n", " "); 469 param = param.replace ("\t", " "); 470 param = param.replace (",", " "); 471 472 while (param.index_of (" ") > -1) { 473 param.replace (" ", " "); 474 } 475 476 return param.strip(); 477 } 478 479 private void parse_circle (XmlElement tag, Layer pl) { 480 Path p; 481 double x, y, r; 482 Glyph g; 483 PathList npl; 484 BezierPoints[] bezier_points; 485 SvgStyle style = new SvgStyle (); 486 bool hidden = false; 487 488 npl = new PathList (); 489 490 x = 0; 491 y = 0; 492 r = 0; 493 494 foreach (Attribute attr in tag.get_attributes ()) { 495 if (attr.get_name () == "cx") { 496 x = parse_double (attr.get_content ()); 497 } 498 499 if (attr.get_name () == "cy") { 500 y = -parse_double (attr.get_content ()); 501 } 502 503 if (attr.get_name () == "r") { 504 r = parse_double (attr.get_content ()); 505 } 506 507 if (attr.get_name () == "display" && attr.get_content () == "none") { 508 hidden = true; 509 } 510 } 511 512 style = SvgStyle.parse (null, style, tag); 513 514 if (hidden) { 515 return; 516 } 517 518 bezier_points = new BezierPoints[1]; 519 bezier_points[0] = new BezierPoints (); 520 bezier_points[0].type == 'L'; 521 bezier_points[0].x0 = x; 522 bezier_points[0].y0 = y; 523 524 g = MainWindow.get_current_glyph (); 525 move_and_resize (bezier_points, 1, false, 1, g); 526 527 p = CircleTool.create_circle (bezier_points[0].x0, 528 bezier_points[0].y0, r, PointType.CUBIC); 529 530 npl.add (p); 531 532 foreach (Attribute attr in tag.get_attributes ()) { 533 if (attr.get_name () == "transform") { 534 transform_paths (attr.get_content (), npl); 535 } 536 } 537 538 npl.apply_style (style); 539 append_paths (pl, npl); 540 } 541 542 private void parse_ellipse (XmlElement tag, Layer pl) { 543 Path p; 544 double x, y, rx, ry; 545 Glyph g; 546 PathList npl; 547 BezierPoints[] bezier_points; 548 SvgStyle style = new SvgStyle (); 549 bool hidden = false; 550 551 npl = new PathList (); 552 553 x = 0; 554 y = 0; 555 rx = 0; 556 ry = 0; 557 558 foreach (Attribute attr in tag.get_attributes ()) { 559 if (attr.get_name () == "cx") { 560 x = parse_double (attr.get_content ()); 561 } 562 563 if (attr.get_name () == "cy") { 564 y = -parse_double (attr.get_content ()); 565 } 566 567 if (attr.get_name () == "rx") { 568 rx = parse_double (attr.get_content ()); 569 } 570 571 if (attr.get_name () == "ry") { 572 ry = parse_double (attr.get_content ()); 573 } 574 575 if (attr.get_name () == "display" && attr.get_content () == "none") { 576 hidden = true; 577 } 578 } 579 580 style = SvgStyle.parse (null, style, tag); 581 582 if (hidden) { 583 return; 584 } 585 586 bezier_points = new BezierPoints[1]; 587 bezier_points[0] = new BezierPoints (); 588 bezier_points[0].type == 'L'; 589 bezier_points[0].x0 = x; 590 bezier_points[0].y0 = y; 591 592 g = MainWindow.get_current_glyph (); 593 move_and_resize (bezier_points, 1, false, 1, g); 594 595 p = CircleTool.create_ellipse (bezier_points[0].x0, 596 bezier_points[0].y0, rx, ry, PointType.CUBIC); 597 598 npl.add (p); 599 600 foreach (Attribute attr in tag.get_attributes ()) { 601 if (attr.get_name () == "transform") { 602 transform_paths (attr.get_content (), npl); 603 } 604 } 605 606 npl.apply_style (style); 607 append_paths (pl, npl); 608 } 609 610 private void parse_line (XmlElement tag, Layer pl) { 611 Path p; 612 double x1, y1, x2, y2; 613 BezierPoints[] bezier_points; 614 Glyph g; 615 PathList npl = new PathList (); 616 SvgStyle style = new SvgStyle (); 617 bool hidden = false; 618 619 x1 = 0; 620 y1 = 0; 621 x2 = 0; 622 y2 = 0; 623 624 foreach (Attribute attr in tag.get_attributes ()) { 625 if (attr.get_name () == "x1") { 626 x1 = parse_double (attr.get_content ()); 627 } 628 629 if (attr.get_name () == "y1") { 630 y1 = -parse_double (attr.get_content ()); 631 } 632 633 if (attr.get_name () == "x2") { 634 x2 = parse_double (attr.get_content ()); 635 } 636 637 if (attr.get_name () == "xy") { 638 y2 = -parse_double (attr.get_content ()); 639 } 640 641 if (attr.get_name () == "display" && attr.get_content () == "none") { 642 hidden = true; 643 } 644 } 645 646 style = SvgStyle.parse (null, style, tag); 647 648 if (hidden) { 649 return; 650 } 651 652 bezier_points = new BezierPoints[2]; 653 bezier_points[0] = new BezierPoints (); 654 bezier_points[0].type == 'L'; 655 bezier_points[0].x0 = x1; 656 bezier_points[0].y0 = y1; 657 658 bezier_points[1] = new BezierPoints (); 659 bezier_points[1].type == 'L'; 660 bezier_points[1].x0 = x2; 661 bezier_points[1].y0 = y2; 662 663 g = MainWindow.get_current_glyph (); 664 move_and_resize (bezier_points, 2, false, 1, g); 665 666 p = new Path (); 667 668 p.add (bezier_points[0].x0, bezier_points[0].y0); 669 p.add (bezier_points[1].x0, bezier_points[1].y0); 670 671 p.close (); 672 p.create_list (); 673 p.recalculate_linear_handles (); 674 675 npl.add (p); 676 677 foreach (Attribute attr in tag.get_attributes ()) { 678 if (attr.get_name () == "transform") { 679 transform_paths (attr.get_content (), npl); 680 } 681 } 682 683 npl.apply_style (style); 684 append_paths (pl, npl); 685 } 686 687 private void parse_rect (XmlElement tag, Layer layer) { 688 Path p; 689 double x, y, x2, y2; 690 BezierPoints[] bezier_points; 691 Glyph g; 692 PathList npl = new PathList (); 693 SvgStyle style = new SvgStyle (); 694 bool hidden = false; 695 EditPoint ep; 696 697 x = 0; 698 y = 0; 699 x2 = 0; 700 y2 = 0; 701 702 foreach (Attribute attr in tag.get_attributes ()) { 703 if (attr.get_name () == "x") { 704 x = parse_double (attr.get_content ()); 705 } 706 707 if (attr.get_name () == "y") { 708 y = -parse_double (attr.get_content ()); 709 } 710 711 if (attr.get_name () == "width") { 712 x2 = parse_double (attr.get_content ()); 713 } 714 715 if (attr.get_name () == "height") { 716 y2 = -parse_double (attr.get_content ()); 717 } 718 719 if (attr.get_name () == "display" && attr.get_content () == "none") { 720 hidden = true; 721 } 722 } 723 724 style = SvgStyle.parse (null, style, tag); 725 726 if (hidden) { 727 return; 728 } 729 730 x2 += x; 731 y2 += y; 732 733 bezier_points = new BezierPoints[4]; 734 bezier_points[0] = new BezierPoints (); 735 bezier_points[0].type == 'L'; 736 bezier_points[0].x0 = x; 737 bezier_points[0].y0 = y; 738 739 bezier_points[1] = new BezierPoints (); 740 bezier_points[1].type == 'L'; 741 bezier_points[1].x0 = x2; 742 bezier_points[1].y0 = y; 743 744 bezier_points[2] = new BezierPoints (); 745 bezier_points[2].type == 'L'; 746 bezier_points[2].x0 = x2; 747 bezier_points[2].y0 = y2; 748 749 bezier_points[3] = new BezierPoints (); 750 bezier_points[3].type == 'L'; 751 bezier_points[3].x0 = x; 752 bezier_points[3].y0 = y2; 753 754 g = MainWindow.get_current_glyph (); 755 move_and_resize (bezier_points, 4, false, 1, g); 756 757 p = new Path (); 758 759 ep = p.add (bezier_points[0].x0, bezier_points[0].y0); 760 ep.set_point_type (PointType.CUBIC); 761 762 ep = p.add (bezier_points[1].x0, bezier_points[1].y0); 763 ep.set_point_type (PointType.CUBIC); 764 765 ep = p.add (bezier_points[2].x0, bezier_points[2].y0); 766 ep.set_point_type (PointType.CUBIC); 767 768 ep = p.add (bezier_points[3].x0, bezier_points[3].y0); 769 ep.set_point_type (PointType.CUBIC); 770 771 p.close (); 772 p.create_list (); 773 p.recalculate_linear_handles (); 774 775 npl.add (p); 776 777 // FIXME: right layer for other transforms 778 foreach (Attribute attr in tag.get_attributes ()) { 779 if (attr.get_name () == "transform") { 780 transform_paths (attr.get_content (), npl); 781 } 782 } 783 784 npl.apply_style (style); 785 append_paths (layer, npl); 786 } 787 788 private void parse_polygon (XmlElement tag, Layer layer) { 789 PathList path_list = get_polyline (tag); 790 791 foreach (Path p in path_list.paths) { 792 p.close (); 793 } 794 795 append_paths (layer, path_list); 796 } 797 798 static void append_paths (Layer layer, PathList pl) { 799 LayerUtils.append_paths (layer, pl); 800 } 801 802 private void parse_polyline (XmlElement tag, Layer layer) { 803 append_paths (layer, get_polyline (tag)); 804 } 805 806 private PathList get_polyline (XmlElement tag) { 807 Path p = new Path (); 808 bool hidden = false; 809 PathList path_list = new PathList (); 810 SvgStyle style = new SvgStyle (); 811 812 foreach (Attribute attr in tag.get_attributes ()) { 813 if (attr.get_name () == "points") { 814 p = parse_poly_data (attr.get_content ()); 815 } 816 817 if (attr.get_name () == "display" && attr.get_content () == "none") { 818 hidden = true; 819 } 820 } 821 822 style = SvgStyle.parse (null, style, tag); 823 824 if (hidden) { 825 return path_list; 826 } 827 828 path_list.add (p); 829 path_list.apply_style (style); 830 831 foreach (Attribute attr in tag.get_attributes ()) { 832 if (attr.get_name () == "transform") { 833 transform_paths (attr.get_content (), path_list); 834 } 835 } 836 837 return path_list; 838 } 839 840 private void parse_path (XmlElement tag, Layer layer) { 841 Glyph glyph = MainWindow.get_current_glyph (); 842 PathList path_list = new PathList (); 843 SvgStyle style = new SvgStyle (); 844 bool hidden = false; 845 846 foreach (Attribute attr in tag.get_attributes ()) { 847 if (attr.get_name () == "d") { 848 path_list = parse_svg_data (attr.get_content (), glyph); 849 } 850 851 if (attr.get_name () == "display" && attr.get_content () == "none") { 852 hidden = true; 853 } 854 855 if (attr.get_name () == "visibility" 856 && (attr.get_content () == "hidden" 857 || attr.get_content () == "collapse")) { 858 hidden = true; 859 } 860 } 861 862 style = SvgStyle.parse (null, style, tag); 863 864 if (hidden) { 865 return; 866 } 867 868 foreach (Path path in path_list.paths) { 869 LayerUtils.add_path (layer, path); 870 } 871 872 path_list.apply_style (style); 873 874 // assume the even odd rule is applied and convert the path 875 // to a path using the non-zero rule 876 int inside_count; 877 bool inside; 878 foreach (SvgBird.Object o1 in layer.objects) { 879 if (o1 is PathObject) { 880 Path p1 = ((PathObject) o1).get_path (); 881 inside_count = 0; 882 883 foreach (SvgBird.Object o2 in layer.objects) { 884 if (o2 is PathObject) { 885 Path p2 = ((PathObject) o2).get_path (); 886 887 if (p1 != p2) { 888 inside = true; 889 890 foreach (EditPoint ep in p1.points) { 891 if (!is_inside (ep, p2)) { 892 inside = false; 893 } 894 } 895 896 if (inside) { 897 inside_count++; 898 } 899 } 900 } 901 } 902 903 if (inside_count % 2 == 0) { 904 p1.force_direction (Direction.CLOCKWISE); 905 } else { 906 p1.force_direction (Direction.COUNTER_CLOCKWISE); 907 } 908 } 909 } 910 911 foreach (Attribute attr in tag.get_attributes ()) { 912 if (attr.get_name () == "transform") { 913 transform_paths (attr.get_content (), path_list); 914 } 915 } 916 } 917 918 public static void create_lines_for_segment (Path path, EditPoint start, EditPoint end, double tolerance) { 919 double x1, x2, x3; 920 double y1, y2, y3; 921 double step_start, step, step_end; 922 923 path.add (start.x, start.y); 924 925 step_start = 0; 926 step = 0.5; 927 step_end = 1; 928 929 while (true) { 930 Path.get_point_for_step (start, end, step_start, out x1, out y1); 931 Path.get_point_for_step (start, end, step, out x2, out y2); 932 Path.get_point_for_step (start, end, step_end, out x3, out y3); 933 934 if (!StrokeTool.is_flat (x1, y1, x2, y2, x3, y3, tolerance) 935 && step_end - step / 2.0 > step_start 936 && step_end - step / 2.0 > 0.1 937 && step > 0.05 938 && Path.distance_to_point (start, end) > 1) { 939 940 step /= 2.0; 941 942 if (step < 0.05) { 943 step = 0.05; 944 } else { 945 step_end = step_start + 2 * step; 946 } 947 } else { 948 path.add (x3, y3); 949 950 if (step_end + step < 1) { 951 step_start = step_end; 952 step_end += step; 953 } else { 954 break; 955 } 956 } 957 } 958 } 959 960 public static Path get_lines (Path p) { 961 EditPoint start; 962 Path path = new Path (); 963 964 if (p.points.size == 0) { 965 return path; 966 } 967 968 // create a set of straight lines 969 start = p.points.get (p.points.size - 1); 970 971 foreach (EditPoint end in p.points) { 972 create_lines_for_segment (path, start, end, 1); 973 start = end; 974 } 975 976 return path; 977 } 978 979 /** Check if a point is inside using the even odd fill rule. 980 * The path should only have straight lines. 981 */ 982 public static bool is_inside (EditPoint point, Path path) { 983 EditPoint prev; 984 bool inside = false; 985 986 if (path.points.size <= 1) { 987 return false; 988 } 989 990 if (!(path.xmin <= point.x <= path.xmax)) { 991 return false; 992 } 993 994 if (!(path.ymin <= point.y <= path.ymax)) { 995 return false; 996 } 997 998 prev = path.points.get (path.points.size - 1); 999 1000 foreach (EditPoint p in path.points) { 1001 if ((p.y > point.y) != (prev.y > point.y) 1002 && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { 1003 inside = !inside; 1004 } 1005 1006 prev = p; 1007 } 1008 1009 return inside; 1010 } 1011 1012 public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { 1013 PathList p = parse_svg_data (d, g, svg_glyph, units); 1014 foreach (Path path in p.paths) { 1015 g.add_path (path); 1016 } 1017 } 1018 1019 public void get_bezier_points (string svg_data, out BezierPoints[] instructions, out int points, bool svg_glyph) { 1020 SvgFile.get_bezier_points (svg_data, out instructions, out points, svg_glyph); 1021 Gee.ArrayList<BezierPoints> bezier_points = new Gee.ArrayList<BezierPoints> (); 1022 BezierPoints[] arc_data = new BezierPoints[8]; 1023 1024 for (int i = 0; i < points; i++) { 1025 if (instructions[i].type == 'A') { 1026 int arc_index = 0; 1027 1028 add_arc_points (arc_data, ref arc_index, 1029 instructions[i].x0, instructions[i].y0, 1030 instructions[i].rx, instructions[i].ry, 1031 instructions[i].angle, 1032 instructions[i].large_arc, 1033 instructions[i].sweep, 1034 instructions[i].x1, instructions[i].y1); 1035 1036 for (int j = 0; j < arc_index; j++) { 1037 bezier_points.add (instructions[j]); 1038 } 1039 } 1040 1041 bezier_points.add (instructions[i]); 1042 } 1043 1044 instructions = new BezierPoints[bezier_points.size]; 1045 for (int i = 0; i < bezier_points.size; i++) { 1046 instructions[i] = bezier_points.get (i); 1047 } 1048 } 1049 /** 1050 * @param d svg data 1051 * @param glyph use lines from this glyph but don't add the generated paths 1052 * @param svg_glyph parse svg glyph with origo in lower left corner 1053 * 1054 * @return the new paths 1055 */ 1056 public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { 1057 Font font; 1058 PathList path_list = new PathList (); 1059 BezierPoints[] bezier_points; 1060 int points; 1061 1062 font = BirdFont.get_current_font (); 1063 SvgFile.get_bezier_points (d, out bezier_points, out points, svg_glyph); 1064 1065 if (points == 0) { 1066 warning ("No points in path."); 1067 return path_list; 1068 } 1069 1070 move_and_resize (bezier_points, points, svg_glyph, units, glyph); 1071 1072 if (format == SvgFormat.ILLUSTRATOR) { 1073 path_list = create_paths_illustrator (bezier_points, points); 1074 } else { 1075 path_list = create_paths_inkscape (bezier_points, points); 1076 } 1077 1078 // TODO: Find out if it is possible to tie handles. 1079 return path_list; 1080 } 1081 1082 void move_and_resize (BezierPoints[] b, int num_b, bool svg_glyph, double units, Glyph glyph) { 1083 Font font = BirdFont.get_current_font (); 1084 1085 for (int i = 0; i < num_b; i++) { 1086 // resize all points 1087 b[i].x0 *= units; 1088 b[i].y0 *= units; 1089 b[i].x1 *= units; 1090 b[i].y1 *= units; 1091 b[i].x2 *= units; 1092 b[i].y2 *= units; 1093 1094 // move all points 1095 if (svg_glyph) { 1096 b[i].x0 += glyph.left_limit; 1097 b[i].y0 += font.base_line; 1098 b[i].x1 += glyph.left_limit; 1099 b[i].y1 += font.base_line; 1100 b[i].x2 += glyph.left_limit; 1101 b[i].y2 += font.base_line; 1102 } else { 1103 b[i].x0 += glyph.left_limit; 1104 b[i].y0 += font.top_position; 1105 b[i].x1 += glyph.left_limit; 1106 b[i].y1 += font.top_position; 1107 b[i].x2 += glyph.left_limit; 1108 b[i].y2 += font.top_position; 1109 } 1110 } 1111 } 1112 1113 void find_last_handle (int start_index, BezierPoints[] b, int num_b, out double left_x, out double left_y, out PointType last_type) { 1114 BezierPoints last = new BezierPoints (); 1115 bool found = false; 1116 1117 left_x = 0; 1118 left_y = 0; 1119 last_type = PointType.NONE; 1120 1121 return_if_fail (b.length != 0); 1122 return_if_fail (b[0].type != 'z'); 1123 return_if_fail (num_b < b.length); 1124 1125 if (num_b == 2) { 1126 left_x = b[0].x0 + (b[1].x0 - b[0].x0) / 3.0; 1127 left_y = b[0].y0 + (b[1].y0 - b[0].y0) / 3.0; 1128 last_type = PointType.LINE_CUBIC; 1129 return; 1130 } 1131 1132 for (int i = start_index; i < num_b; i++) { 1133 switch (b[i].type) { 1134 case 'Q': 1135 break; 1136 case 'C': 1137 break; 1138 case 'z': 1139 found = true; 1140 break; 1141 default: 1142 break; 1143 } 1144 1145 if (found || i + 1 == num_b) { 1146 1147 return_if_fail (i >= 1); 1148 1149 if (b[i - 1].type == 'Q') { 1150 return_if_fail (i >= 1); 1151 left_x = b[i - 1].x0; 1152 left_y = b[i - 1].y0; 1153 last_type = PointType.QUADRATIC; 1154 } else if (b[i - 1].type == 'C') { 1155 return_if_fail (i >= 1); 1156 left_x = b[i - 1].x1; 1157 left_y = b[i - 1].y1; 1158 last_type = PointType.CUBIC; 1159 } else if (b[i - 1].type == 'S') { 1160 return_if_fail (i >= 1); 1161 left_x = b[i - 1].x1; 1162 left_y = b[i - 1].y1; 1163 last_type = PointType.CUBIC; 1164 } else if (b[i - 1].type == 'L' || last.type == 'M') { 1165 return_if_fail (i >= 2); // FIXME: -2 can be C or L 1166 left_x = b[i - 2].x0 + (b[i - 1].x0 - b[i - 2].x0) / 3.0; 1167 left_y = b[i - 2].y0 + (b[i - 1].y0 - b[i - 2].y0) / 3.0; 1168 last_type = PointType.LINE_CUBIC; 1169 } else { 1170 warning (@"Unexpected type. $(b[i - 1])\n"); 1171 } 1172 return; 1173 } 1174 1175 last = b[i]; 1176 } 1177 1178 warning ("Last point not found."); 1179 } 1180 1181 PathList create_paths_inkscape (BezierPoints[] b, int num_b) { 1182 double last_x; 1183 double last_y; 1184 PointType last_type; 1185 Path path; 1186 PathList path_list = new PathList (); 1187 EditPoint ep = new EditPoint (); 1188 Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); 1189 1190 path = new Path (); 1191 1192 if (num_b == 0) { 1193 warning ("No SVG data"); 1194 return path_list; 1195 } 1196 1197 if (b[0].type != 'M') { 1198 warning ("Path must begin with M or m."); 1199 return path_list; 1200 } 1201 1202 find_last_handle (0, b, num_b, out last_x, out last_y, out last_type); 1203 1204 for (int i = 0; i < num_b; i++) { 1205 if (b[i].type == '\0') { 1206 warning ("Parser error."); 1207 return path_list; 1208 } 1209 1210 if (b[i].type == 'z') { 1211 path.close (); 1212 path.create_list (); 1213 path.recalculate_linear_handles (); 1214 path_list.add (path); 1215 path = new Path (); 1216 1217 if (i + 1 >= num_b) { 1218 break; 1219 } else { 1220 find_last_handle (i + 1, b, num_b, out last_x, out last_y, out last_type); 1221 } 1222 } 1223 1224 if (i >= num_b) { 1225 break; 1226 } 1227 1228 if (b[i].type == 'M') { 1229 ep = path.add (b[i].x0, b[i].y0); 1230 ep.set_point_type (PointType.CUBIC); 1231 1232 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1233 1234 if (i == 0 || (b[i - 1].type == 'z')) { 1235 ep.get_left_handle ().set_point_type (last_type); 1236 ep.get_left_handle ().move_to_coordinate (last_x, last_y); 1237 } else { 1238 if (b[i - 1].type == 'C' || b[i - 1].type == 'S') { 1239 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1240 ep.get_left_handle ().move_to_coordinate (b[i + 1].x1, b[i + 1].y1); 1241 } 1242 1243 if (b[i + 1].type == 'C' || b[i - 1].type == 'S') { 1244 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1245 ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 1246 } else if (b[i + 1].type == 'L' || b[i + 1].type == 'M') { 1247 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1248 } 1249 } 1250 } 1251 1252 if (b[i].type == 'L') { 1253 return_val_if_fail (i != 0, path_list); 1254 1255 ep = path.add (b[i].x0, b[i].y0); 1256 ep.set_point_type (PointType.CUBIC); 1257 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1258 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1259 1260 if (b[i + 1].type == 'L' || b[i + 1].type == 'M' || b[i + 1].type == 'z') { 1261 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1262 } 1263 1264 if (b[i -1].type == 'L' || b[i - 1].type == 'M') { 1265 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1266 } 1267 } 1268 1269 if (b[i].type == 'Q') { 1270 return_val_if_fail (i != 0, path_list); 1271 1272 ep.set_point_type (PointType.QUADRATIC); 1273 1274 ep.get_right_handle ().set_point_type (PointType.QUADRATIC); 1275 ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); 1276 1277 if (b[i + 1].type != 'z') { 1278 ep = path.add (b[i].x1, b[i].y1); 1279 1280 ep.get_left_handle ().set_point_type (PointType.QUADRATIC); 1281 ep.get_left_handle ().move_to_coordinate (b[i].x0, b[i].y0); 1282 } 1283 } 1284 1285 if (b[i].type == 'C' || b[i].type == 'S') { 1286 return_val_if_fail (i != 0, path_list); 1287 1288 ep.set_point_type (PointType.CUBIC); 1289 1290 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1291 ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); 1292 1293 if (b[i].type == 'S') { 1294 smooth_points.add (ep); 1295 } 1296 1297 if (b[i + 1].type != 'z') { 1298 ep = path.add (b[i].x2, b[i].y2); 1299 1300 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1301 ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); 1302 } 1303 } 1304 } 1305 1306 foreach (EditPoint e in smooth_points) { 1307 e.set_point_type (PointType.LINE_DOUBLE_CURVE); 1308 e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1309 e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1310 } 1311 1312 foreach (EditPoint e in smooth_points) { 1313 path.recalculate_linear_handles_for_point (e); 1314 } 1315 1316 for (int i = 0; i < 3; i++) { 1317 foreach (EditPoint e in smooth_points) { 1318 e.set_tie_handle (true); 1319 e.process_tied_handle (); 1320 } 1321 } 1322 1323 if (path.points.size > 0) { 1324 path_list.add (path); 1325 } 1326 1327 foreach (Path p in path_list.paths) { 1328 p.remove_points_on_points (); 1329 } 1330 1331 return path_list; 1332 } 1333 1334 PathList create_paths_illustrator (BezierPoints[] b, int num_b) { 1335 Path path; 1336 PathList path_list = new PathList (); 1337 EditPoint ep; 1338 bool first_point = true; 1339 double first_left_x, first_left_y; 1340 Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); 1341 1342 if (num_b > b.length) { 1343 warning ("num_b > b.length: $num_b > $(b.length)"); 1344 return path_list; 1345 } 1346 1347 path = new Path (); 1348 1349 if (num_b <= 1) { 1350 warning ("No SVG data"); 1351 return path_list; 1352 } 1353 1354 first_left_x = 0; 1355 first_left_y = 0; 1356 1357 ep = new EditPoint (); 1358 1359 for (int i = 0; i < num_b; i++) { 1360 if (b[i].type == '\0') { 1361 warning ("Parser error."); 1362 return path_list; 1363 } else if (b[i].type == 'z') { 1364 path.close (); 1365 path.create_list (); 1366 1367 int first_index = 1; 1368 1369 for (int j = i - 1; j >= 1; j--) { 1370 if (b[j].type == 'z') { 1371 first_index = j + 1; // from z to M 1372 } 1373 } 1374 1375 if (b[first_index].type == 'C' || b[first_index].type == 'S') { 1376 return_val_if_fail (path.points.size != 0, path_list); 1377 ep = path.points.get (path.points.size - 1); 1378 1379 if (b[i - 1].type != 'L' ) { 1380 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1381 ep.get_right_handle ().move_to_coordinate (b[first_index].x0, b[first_index].y0); 1382 } 1383 } else if (b[first_index].type == 'L') { 1384 return_val_if_fail (path.points.size != 0, path_list); 1385 ep = path.points.get (path.points.size - 1); 1386 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1387 path.recalculate_linear_handles_for_point (ep); 1388 } else { 1389 warning ("Unexpected type: %s", (!) b[first_index].type.to_string ()); 1390 } 1391 1392 path.recalculate_linear_handles (); 1393 path_list.add (path); 1394 1395 path = new Path (); 1396 first_point = true; 1397 } else if (b[i].type == 'L' || b[i].type == 'M') { 1398 1399 if (first_point) { 1400 first_left_x = b[i].x0; 1401 first_left_y = b[i].y0; 1402 } 1403 1404 ep = path.add (b[i].x0, b[i].y0); 1405 ep.set_point_type (PointType.CUBIC); // TODO: quadratic 1406 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1407 1408 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1409 ep.get_left_handle ().move_to_coordinate (b[i].x0 - 0.00001, b[i].y0 - 0.00001); 1410 1411 if (b[i + 1].type == 'C' || b[i + 1].type == 'S') { 1412 return_val_if_fail (i + 1 < num_b, path_list); 1413 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1414 ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 1415 } 1416 1417 first_point = false; 1418 } else if (b[i].type == 'Q') { 1419 warning ("Illustrator does not support quadratic control points."); 1420 warning (@"$(b[i])\n"); 1421 } else if (b[i].type == 'C' || b[i].type == 'S') { 1422 1423 if (first_point) { 1424 first_left_x = b[i].x0; 1425 first_left_y = b[i].y0; 1426 } 1427 1428 ep = path.add (b[i].x2, b[i].y2); 1429 ep.set_point_type (PointType.CUBIC); 1430 1431 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1432 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1433 1434 ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); 1435 1436 if (b[i].type == 'S') { 1437 smooth_points.add (ep); 1438 } 1439 1440 if (b[i + 1].type != 'z' && i != num_b - 1) { 1441 ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 1442 } else { 1443 ep.get_right_handle ().move_to_coordinate (first_left_x, first_left_y); 1444 } 1445 1446 first_point = false; 1447 } else { 1448 warning ("Unknown control point type."); 1449 warning (@"$(b[i])\n"); 1450 } 1451 } 1452 1453 foreach (EditPoint e in smooth_points) { 1454 e.set_point_type (PointType.LINE_CUBIC); 1455 e.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1456 e.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1457 } 1458 1459 foreach (EditPoint e in smooth_points) { 1460 path.recalculate_linear_handles_for_point (e); 1461 } 1462 1463 for (int i = 0; i < 3; i++) { 1464 foreach (EditPoint e in smooth_points) { 1465 e.set_tie_handle (true); 1466 e.get_right_handle ().set_point_type (PointType.CUBIC); 1467 e.get_left_handle ().set_point_type (PointType.CUBIC); 1468 e.process_tied_handle (); 1469 } 1470 } 1471 1472 if (path.points.size > 0) { 1473 path_list.add (path); 1474 } 1475 1476 foreach (Path p in path_list.paths) { 1477 p.remove_points_on_points (); 1478 } 1479 1480 return path_list; 1481 } 1482 1483 public static double parse_double (string? s) { 1484 if (unlikely (is_null (s))) { 1485 warning ("Got null instead of expected string."); 1486 return 0; 1487 } 1488 1489 if (unlikely (!is_point ((!) s))) { 1490 warning (@"Expecting a double got: $((!) s)"); 1491 return 0; 1492 } 1493 1494 return double.parse ((!) s); 1495 } 1496 1497 static bool is_point (string? s) { 1498 if (s == null) { 1499 warning ("s is null"); 1500 return false; 1501 } 1502 1503 return double.try_parse ((!) s); 1504 } 1505 1506 Path parse_poly_data (string polygon_points) { 1507 string data = SvgFile.add_separators (polygon_points); 1508 string[] c = data.split (" "); 1509 Path path; 1510 BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; 1511 int bi; 1512 Glyph g; 1513 EditPoint ep; 1514 1515 bi = 0; 1516 for (int i = 0; i < c.length - 1; i += 2) { 1517 if (i + 1 >= c.length) { 1518 warning ("No y value."); 1519 break; 1520 } 1521 1522 if (bi >= bezier_points.length) { 1523 warning ("End of bezier_points"); 1524 break; 1525 } 1526 1527 bezier_points[bi] = new BezierPoints (); 1528 bezier_points[bi].type = 'L'; 1529 bezier_points[bi].x0 = parse_double (c[i]); 1530 bezier_points[bi].y0 = -parse_double (c[i + 1]); 1531 bi++; 1532 } 1533 1534 g = MainWindow.get_current_glyph (); 1535 move_and_resize (bezier_points, bi, false, 1, g); 1536 1537 path = new Path (); 1538 for (int i = 0; i < bi; i++) { 1539 ep = path.add (bezier_points[i].x0, bezier_points[i].y0); 1540 ep.set_point_type (PointType.LINE_CUBIC); 1541 } 1542 1543 path.create_list (); 1544 path.recalculate_linear_handles (); 1545 1546 return path; 1547 } 1548 1549 public static EmbeddedSvg parse_embedded_svg_file (string path) { 1550 string xml_data; 1551 1552 try { 1553 FileUtils.get_contents (path, out xml_data); 1554 return parse_embedded_svg_data (xml_data); 1555 } catch (GLib.Error error) { 1556 warning (error.message); 1557 } 1558 1559 SvgDrawing drawing = new SvgDrawing (); 1560 return new EmbeddedSvg (drawing); 1561 } 1562 1563 public static EmbeddedSvg parse_embedded_svg_data (string xml_data) { 1564 XmlTree tree = new XmlTree (xml_data); 1565 SvgDrawing drawing = new SvgDrawing (); 1566 SvgFile svg_file = new SvgFile (); 1567 1568 XmlElement root = tree.get_root (); 1569 drawing = svg_file.parse_svg_file (root); 1570 EmbeddedSvg svg = new EmbeddedSvg (drawing); 1571 svg.svg_data = xml_data; 1572 return svg; 1573 } 1574 1575 /** Convert an SVG arc instruction to a Beziér path. */ 1576 public static void add_arc_points (BezierPoints[] bezier_points, ref int bi, 1577 double x0, double y0, double rx, double ry, double angle, 1578 bool largeArcFlag, bool sweepFlag, double x, double y) { 1579 1580 double angleStart, angleExtent; 1581 double s, step, theta; 1582 double cx, cy; 1583 1584 cx = 0; 1585 cy = 0; 1586 1587 // Approximate the path with Beziér points 1588 SvgBird.get_arc_arguments (x0, y0, rx, ry, angle, largeArcFlag, sweepFlag, x, y, 1589 out angleStart, out angleExtent, out cx, out cx); 1590 1591 s = (angleExtent > 0) ? 1 : -1; 1592 step = fabs (angleExtent) / (2 * fabs (angleExtent)); 1593 1594 theta = PI - angleStart - angleExtent; 1595 1596 bezier_points[bi].type = 'C'; 1597 bezier_points[bi].svg_type = 'a'; 1598 1599 bezier_points[bi].x0 = cx + rx * cos (theta); 1600 bezier_points[bi].y0 = cy + ry * sin (theta); 1601 1602 bi++; 1603 1604 for (double a = 0; a < fabs (angleExtent); a += step) { 1605 theta = PI - angleStart - angleExtent + s * a; 1606 1607 return_if_fail (0 <= bi < bezier_points.length); 1608 1609 bezier_points[bi].type = 'S'; 1610 bezier_points[bi].svg_type = 'a'; 1611 1612 bezier_points[bi].x0 = cx + rx * cos (theta); 1613 bezier_points[bi].y0 = cy + ry * sin (theta); 1614 1615 bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); 1616 bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); 1617 1618 bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); 1619 bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); 1620 1621 bi++; 1622 } 1623 } 1624 } 1625 1626 } 1627