The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

SvgFile.vala in libsvgbird

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 libsvgbird/SvgFile.vala.
SVG M instruction
1 /* 2 Copyright (C) 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 Cairo; 17 using Math; 18 19 namespace SvgBird { 20 21 public enum SvgFormat { 22 NONE, 23 INKSCAPE, 24 ILLUSTRATOR 25 } 26 27 public class SvgFile : GLib.Object { 28 29 SvgDrawing drawing; 30 SvgFormat format = SvgFormat.ILLUSTRATOR; 31 32 public SvgFile () { 33 } 34 35 public SvgDrawing parse_svg_data (string xml_data) { 36 XmlTree tree = new XmlTree (xml_data); 37 38 if (xml_data.index_of ("Illustrator") > -1 || xml_data.index_of ("illustrator") > -1) { 39 format = SvgFormat.ILLUSTRATOR; 40 } else if (xml_data.index_of ("Inkscape") > -1 || xml_data.index_of ("inkscape") > -1) { 41 format = SvgFormat.INKSCAPE; 42 } 43 44 return parse_svg_file (tree.get_root (), format); 45 } 46 47 public SvgDrawing parse_svg_file (XmlElement svg_tag, SvgFormat format) { 48 drawing = new SvgDrawing (); 49 this.format = format; 50 51 SvgStyle style = new SvgStyle (); 52 SvgStyle.parse (drawing.defs, style, svg_tag, null); 53 54 foreach (Attribute attr in svg_tag.get_attributes ()) { 55 if (attr.get_name () == "width") { 56 drawing.width = parse_number (attr.get_content ()); 57 } 58 59 if (attr.get_name () == "height") { 60 drawing.height = parse_number (attr.get_content ()); 61 } 62 63 if (attr.get_name () == "viewBox") { 64 drawing.view_box = parse_view_box (svg_tag); 65 } 66 } 67 68 foreach (XmlElement t in svg_tag) { 69 string name = t.get_name (); 70 71 if (name == "g") { 72 parse_layer (drawing.root_layer, style, t); 73 } 74 75 if (name == "svg") { 76 SvgDrawing embedded = parse_svg_file (t, format); 77 drawing.root_layer.add_object (embedded); 78 } 79 80 if (name == "defs") { 81 parse_defs (drawing, t); 82 } 83 84 if (name == "a") { 85 parse_link (drawing.root_layer, style, svg_tag); 86 } 87 88 parse_object (drawing.root_layer, style, t); 89 } 90 91 set_object_properties (drawing, new SvgStyle (), svg_tag); 92 93 return drawing; 94 } 95 96 public static ViewBox? parse_view_box (XmlElement tag) { 97 string arguments; 98 string parameters = ""; 99 string aspect_ratio = ""; 100 bool slice = true; 101 bool preserve_aspect_ratio = true; 102 103 foreach (Attribute attribute in tag.get_attributes ()) { 104 if (attribute.get_name () == "viewBox") { 105 parameters = attribute.get_content (); 106 } 107 108 if (attribute.get_name () == "preserveAspectRatio") { 109 aspect_ratio = attribute.get_content (); 110 111 string[] preserveSettings = aspect_ratio.split (" "); 112 113 if (preserveSettings.length >= 1) { 114 aspect_ratio = preserveSettings[0]; 115 } 116 117 if (preserveSettings.length >= 2) { 118 slice = preserveSettings[1] == "slice"; 119 } 120 121 preserve_aspect_ratio = false; 122 } 123 } 124 125 arguments = parameters.replace (",", " "); 126 127 while (arguments.index_of (" ") > -1) { 128 arguments = arguments.replace (" ", " "); 129 } 130 131 string[] view_box_parameters = arguments.split (" "); 132 133 if (view_box_parameters.length != 4) { 134 warning ("Expecting four arguments in view box."); 135 return null; 136 } 137 138 double minx = parse_number (view_box_parameters[0]); 139 double miny = parse_number (view_box_parameters[1]); 140 double width = parse_number (view_box_parameters[2]); 141 double height = parse_number (view_box_parameters[3]); 142 143 uint alignment = ViewBox.XMID_YMID; 144 aspect_ratio = aspect_ratio.up (); 145 146 if (aspect_ratio == "NONE") { 147 alignment = ViewBox.NONE; 148 } else if (aspect_ratio == "XMINYMIN") { 149 alignment = ViewBox.XMIN_YMIN; 150 } else if (aspect_ratio == "XMINYMIN") { 151 alignment = ViewBox.XMIN_YMIN; 152 } else if (aspect_ratio == "XMAXYMIN") { 153 alignment = ViewBox.XMIN_YMIN; 154 } else if (aspect_ratio == "XMINYMID") { 155 alignment = ViewBox.XMIN_YMIN; 156 } else if (aspect_ratio == "XMIDYMID") { 157 alignment = ViewBox.XMIN_YMIN; 158 } else if (aspect_ratio == "XMAXYMID") { 159 alignment = ViewBox.XMIN_YMIN; 160 } else if (aspect_ratio == "XMINYMAX") { 161 alignment = ViewBox.XMIN_YMIN; 162 } else if (aspect_ratio == "XMIDYMAX") { 163 alignment = ViewBox.XMIN_YMIN; 164 } else if (aspect_ratio == "XMAXYMAX") { 165 alignment = ViewBox.XMIN_YMIN; 166 } 167 168 return new ViewBox (minx, miny, width, height, alignment, slice, preserve_aspect_ratio); 169 } 170 171 private void parse_layer (Layer layer, SvgStyle parent_style, XmlElement tag) { 172 foreach (XmlElement t in tag) { 173 string name = t.get_name (); 174 175 if (name == "g") { 176 Layer sublayer = new Layer (); 177 parse_layer (layer, parent_style, t); 178 layer.objects.add (sublayer); 179 } 180 181 if (name == "a") { 182 parse_link (layer, parent_style, t); 183 } 184 185 parse_object (layer, parent_style, t); 186 } 187 188 set_object_properties (layer, parent_style, tag); 189 } 190 191 void parse_clip_path (SvgDrawing drawing, XmlElement tag) { 192 ClipPath clip_path; 193 194 Layer layer = new Layer (); 195 parse_layer (layer, new SvgStyle (), tag); 196 clip_path = new ClipPath (layer); 197 198 drawing.defs.clip_paths.add (clip_path); 199 } 200 201 void parse_defs (SvgDrawing drawing, XmlElement tag) { 202 foreach (XmlElement t in tag) { 203 string name = t.get_name (); 204 205 if (name == "linearGradient") { 206 parse_linear_gradient (drawing, t); 207 } else if (name == "radialGradient") { 208 parse_radial_gradient (drawing, t); 209 } else if (name == "clipPath") { 210 parse_clip_path (drawing, t); 211 } 212 } 213 214 foreach (Gradient gradient in drawing.defs.gradients) { 215 if (gradient.href != null) { 216 Gradient? referenced; 217 referenced = drawing.defs.get_gradient_for_id ((!) gradient.href); 218 219 if (referenced != null) { 220 gradient.copy_stops ((!) referenced); 221 } 222 223 gradient.href = null; 224 } 225 } 226 227 foreach (XmlElement t in tag) { 228 // FIXME: radial 229 string name = t.get_name (); 230 231 if (name == "style") { 232 drawing.defs.style_sheet = StyleSheet.parse (drawing.defs, t); 233 } 234 } 235 236 } 237 238 void parse_radial_gradient (SvgDrawing drawing, XmlElement tag) { 239 RadialGradient gradient = new RadialGradient (); 240 241 drawing.defs.add_radial_gradient (gradient); 242 243 foreach (Attribute attr in tag.get_attributes ()) { 244 string name = attr.get_name (); 245 246 // FIXME: gradientUnits 247 248 if (name == "gradientTransform") { 249 gradient.transforms = parse_transform (attr.get_content ()); 250 } 251 252 if (name == "href") { 253 gradient.href = attr.get_content (); 254 } 255 256 if (name == "cx") { 257 gradient.cx = parse_number (attr.get_content ()); 258 } 259 260 if (name == "cy") { 261 gradient.cy = parse_number (attr.get_content ()); 262 } 263 264 if (name == "fx") { 265 gradient.fx = parse_number (attr.get_content ()); 266 } 267 268 if (name == "fy") { 269 gradient.fy = parse_number (attr.get_content ()); 270 } 271 272 if (name == "r") { 273 gradient.r = parse_number (attr.get_content ()); 274 } 275 276 if (name == "id") { 277 gradient.id = attr.get_content (); 278 } 279 } 280 281 foreach (XmlElement t in tag) { 282 string name = t.get_name (); 283 284 if (name == "stop") { 285 parse_stop (gradient, t); 286 } 287 } 288 } 289 290 void parse_linear_gradient (SvgDrawing drawing, XmlElement tag) { 291 LinearGradient gradient = new LinearGradient (); 292 293 drawing.defs.add_linear_gradient (gradient); 294 295 foreach (Attribute attr in tag.get_attributes ()) { 296 string name = attr.get_name (); 297 298 // FIXME: gradientUnits 299 300 if (name == "gradientTransform") { 301 gradient.transforms = parse_transform (attr.get_content ()); 302 } 303 304 if (name == "href") { 305 gradient.href = attr.get_content (); 306 } 307 308 if (name == "x1") { 309 gradient.x1 = parse_number (attr.get_content ()); 310 } 311 312 if (name == "y1") { 313 gradient.y1 = parse_number (attr.get_content ()); 314 } 315 316 if (name == "x2") { 317 gradient.x2 = parse_number (attr.get_content ()); 318 } 319 320 if (name == "y2") { 321 gradient.y2 = parse_number (attr.get_content ()); 322 } 323 324 if (name == "id") { 325 gradient.id = attr.get_content (); 326 } 327 } 328 329 foreach (XmlElement t in tag) { 330 string name = t.get_name (); 331 332 if (name == "stop") { 333 parse_stop (gradient, t); 334 } 335 } 336 } 337 338 void parse_stop (Gradient gradient, XmlElement tag) { 339 SvgStyle parent_style = new SvgStyle (); // not inherited 340 SvgStyle style = SvgStyle.parse (drawing.defs, parent_style, tag, null); 341 Stop stop = new Stop (); 342 343 gradient.stops.add (stop); 344 345 foreach (Attribute attr in tag.get_attributes ()) { 346 string name = attr.get_name (); 347 348 if (name == "offset") { 349 string stop_offset = attr.get_content (); 350 351 if (stop_offset.index_of ("%") > -1) { 352 stop_offset = stop_offset.replace ("%", ""); 353 stop.offset = parse_number (stop_offset) / 100.0; 354 } else { 355 stop.offset = parse_number (stop_offset); 356 } 357 } 358 } 359 360 string? stop_color = style.style.get ("stop-color"); 361 string? stop_opacity = style.style.get ("stop-opacity"); 362 Color? color = new Color (0, 0, 0, 1); 363 364 if (stop_color != null) { 365 color = Color.parse (stop_color); 366 367 if (color != null) { 368 stop.color = (!) color; 369 } 370 } 371 372 if (stop_opacity != null && color != null) { 373 ((!) color).a = parse_number (stop_opacity); 374 } 375 } 376 377 // links are ignored, add the content to the layer 378 void parse_link (Layer layer, SvgStyle parent_style, XmlElement tag) { 379 parse_layer (layer, parent_style, tag); 380 } 381 382 void parse_object (Layer layer, SvgStyle parent_style, XmlElement tag) { 383 string name = tag.get_name (); 384 385 if (name == "path") { 386 parse_path (layer, parent_style, tag); 387 } 388 389 if (name == "polygon") { 390 parse_polygon (layer, parent_style, tag); 391 } 392 393 if (name == "polyline") { 394 parse_polyline (layer, parent_style, tag); 395 } 396 397 if (name == "rect") { 398 parse_rect (layer, parent_style, tag); 399 } 400 401 if (name == "circle") { 402 parse_circle (layer, parent_style, tag); 403 } 404 405 if (name == "ellipse") { 406 parse_ellipse (layer, parent_style, tag); 407 } 408 409 if (name == "line") { 410 parse_line (layer, parent_style, tag); 411 } 412 413 if (name == "svg") { 414 SvgDrawing embedded = parse_svg_file (tag, format); 415 layer.add_object (embedded); 416 } 417 418 } 419 420 private void parse_polygon (Layer layer, SvgStyle parent_style, XmlElement tag) { 421 Polygon polygon = new Polygon (); 422 423 foreach (Attribute attr in tag.get_attributes ()) { 424 if (attr.get_name () == "points") { 425 string data = add_separators (attr.get_content ()); 426 string[] point_data = data.split (" "); 427 428 foreach (string number in point_data) { 429 polygon.points.add (parse_number (number)); 430 } 431 } 432 } 433 434 set_object_properties (polygon, parent_style, tag); 435 layer.add_object (polygon); 436 } 437 438 void set_object_properties (Object object, SvgStyle parent_style, XmlElement tag) { 439 Attributes attributes = tag.get_attributes (); 440 441 foreach (Attribute attribute in attributes) { 442 string name = attribute.get_name (); 443 444 if (name == "id") { 445 object.id = attribute.get_content (); 446 } else if (name == "class") { 447 object.css_class = attribute.get_content (); 448 } 449 } 450 451 object.clip_path = get_clip_path (attributes); 452 object.transforms = get_transform (attributes); 453 object.style = SvgStyle.parse (drawing.defs, parent_style, tag, null); 454 object.visible = is_visible (tag); 455 } 456 457 ClipPath? get_clip_path (Attributes attributes) { 458 foreach (Attribute attribute in attributes) { 459 if (attribute.get_name () == "clip-path") { 460 return drawing.defs.get_clip_path_for_url (attribute.get_content ()); 461 } 462 } 463 464 return null; 465 } 466 467 private void parse_polyline (Layer layer, SvgStyle parent_style, XmlElement tag) { 468 Polyline polyline = new Polyline (); 469 470 foreach (Attribute attr in tag.get_attributes ()) { 471 if (attr.get_name () == "points") { 472 string data = add_separators (attr.get_content ()); 473 string[] point_data = data.split (" "); 474 475 foreach (string number in point_data) { 476 polyline.points.add (parse_number (number)); 477 } 478 } 479 } 480 481 set_object_properties (polyline, parent_style, tag); 482 layer.add_object (polyline); 483 } 484 485 private void parse_rect (Layer layer, SvgStyle parent_style, XmlElement tag) { 486 Rectangle rectangle = new Rectangle (); 487 488 foreach (Attribute attr in tag.get_attributes ()) { 489 string attribute = attr.get_name (); 490 491 if (attribute == "x") { 492 rectangle.x = parse_number (attr.get_content ()); 493 } 494 495 if (attribute == "y") { 496 rectangle.y = parse_number (attr.get_content ()); 497 } 498 499 if (attribute == "width") { 500 rectangle.width = parse_number (attr.get_content ()); 501 } 502 503 if (attribute == "height") { 504 rectangle.height = parse_number (attr.get_content ()); 505 } 506 507 if (attribute == "rx") { 508 rectangle.rx = parse_number (attr.get_content ()); 509 } 510 511 if (attribute == "ry") { 512 rectangle.ry = parse_number (attr.get_content ()); 513 } 514 } 515 516 set_object_properties (rectangle, parent_style, tag); 517 layer.add_object (rectangle); 518 } 519 520 private void parse_circle (Layer layer, SvgStyle parent_style, XmlElement tag) { 521 Circle circle = new Circle (); 522 523 foreach (Attribute attr in tag.get_attributes ()) { 524 string name = attr.get_name (); 525 526 if (name == "cx") { 527 circle.cx = parse_number (attr.get_content ()); 528 } 529 530 if (name == "cy") { 531 circle.cy = parse_number (attr.get_content ()); 532 } 533 534 if (name == "r") { 535 circle.r = parse_number (attr.get_content ()); 536 } 537 } 538 539 set_object_properties (circle, parent_style, tag); 540 layer.add_object (circle); 541 } 542 543 private void parse_ellipse (Layer layer, SvgStyle parent_style, XmlElement tag) { 544 Ellipse ellipse = new Ellipse (); 545 546 foreach (Attribute attr in tag.get_attributes ()) { 547 string name = attr.get_name (); 548 549 if (name == "cx") { 550 ellipse.cx = parse_number (attr.get_content ()); 551 } 552 553 if (name == "cy") { 554 ellipse.cy = parse_number (attr.get_content ()); 555 } 556 557 if (name == "rx") { 558 ellipse.rx = parse_number (attr.get_content ()); 559 } 560 561 if (name == "ry") { 562 ellipse.ry = parse_number (attr.get_content ()); 563 } 564 } 565 566 set_object_properties (ellipse, parent_style, tag); 567 layer.add_object (ellipse); 568 } 569 570 private void parse_line (Layer layer, SvgStyle parent_style, XmlElement tag) { 571 Line line = new Line (); 572 573 foreach (Attribute attr in tag.get_attributes ()) { 574 string name = attr.get_name (); 575 576 if (name == "x1") { 577 line.x1 = parse_number (attr.get_content ()); 578 } 579 580 if (name == "y1") { 581 line.y1 = parse_number (attr.get_content ()); 582 } 583 584 if (name == "x2") { 585 line.x2 = parse_number (attr.get_content ()); 586 } 587 588 if (name == "y2") { 589 line.y2 = parse_number (attr.get_content ()); 590 } 591 } 592 593 set_object_properties (line, parent_style, tag); 594 layer.add_object (line); 595 } 596 597 // FIXME: reverse order? 598 public static SvgTransforms parse_transform (string transforms) { 599 string[] functions; 600 string transform = transforms; 601 SvgTransforms transform_functions; 602 603 transform_functions = new SvgTransforms (); 604 605 transform = transform.replace ("\t", " "); 606 transform = transform.replace ("\n", " "); 607 transform = transform.replace ("\r", " "); 608 609 // use only a single space as separator 610 while (transform.index_of (" ") > -1) { 611 transform = transform.replace (" ", " "); 612 } 613 614 if (unlikely (transform.index_of (")") == -1)) { 615 warning ("No parenthesis in transform function."); 616 return transform_functions; 617 } 618 619 // add separator 620 transform = transform.replace (") ", "|"); 621 transform = transform.replace (")", "|"); 622 functions = transform.split ("|"); 623 624 for (int i = 0; i < functions.length; i++) { 625 if (functions[i].has_prefix ("translate")) { 626 transform_functions.add (translate (functions[i])); 627 } 628 629 if (functions[i].has_prefix ("scale")) { 630 transform_functions.add (scale (functions[i])); 631 } 632 633 if (functions[i].has_prefix ("matrix")) { 634 transform_functions.add (matrix (functions[i])); 635 } 636 637 // TODO: rotate etc. 638 } 639 640 return transform_functions; 641 } 642 643 private static SvgTransform matrix (string function) { 644 string parameters = get_transform_parameters (function); 645 string[] p = parameters.split (" "); 646 SvgTransform transform = new SvgTransform (); 647 transform.type = TransformType.MATRIX; 648 649 if (unlikely (p.length != 6)) { 650 warning ("Expecting six parameters for matrix transformation."); 651 return transform; 652 } 653 654 for (int i = 0; i < 6; i++) { 655 double argument = parse_double (p[i]); 656 transform.arguments.add (argument); 657 } 658 659 return transform; 660 } 661 662 private static string remove_unit (string d) { 663 string s = d.replace ("pt", ""); 664 s = s.replace ("pc", ""); 665 s = s.replace ("mm", ""); 666 s = s.replace ("cm", ""); 667 s = s.replace ("in", ""); 668 s = s.replace ("px", ""); 669 return s; 670 } 671 672 public static double parse_number (string? number_with_unit) { 673 if (number_with_unit == null) { 674 return 0; 675 } 676 677 string d = (!) number_with_unit; 678 string s = remove_unit (d); 679 double n = parse_double (s); 680 681 if (d.has_suffix ("pt")) { 682 n *= 1.25; 683 } else if (d.has_suffix ("pc")) { 684 n *= 15; 685 } else if (d.has_suffix ("mm")) { 686 n *= 3.543307; 687 } else if (d.has_suffix ("cm")) { 688 n *= 35.43307; 689 } else if (d.has_suffix ("in")) { 690 n *= 90; 691 } 692 693 return n; 694 } 695 696 private static SvgTransform scale (string function) { 697 string parameters = get_transform_parameters (function); 698 string[] p = parameters.split (" "); 699 SvgTransform transform = new SvgTransform (); 700 transform.type = TransformType.SCALE; 701 702 if (p.length > 0) { 703 transform.arguments.add (parse_double (p[0])); 704 } 705 706 if (p.length > 1) { 707 transform.arguments.add (parse_double (p[1])); 708 } 709 710 return transform; 711 } 712 713 private static SvgTransform translate (string function) { 714 string parameters = get_transform_parameters (function); 715 string[] p = parameters.split (" "); 716 SvgTransform transform = new SvgTransform (); 717 transform.type = TransformType.TRANSLATE; 718 719 if (p.length > 0) { 720 transform.arguments.add (parse_double (p[0])); 721 } 722 723 if (p.length > 1) { 724 transform.arguments.add (parse_double (p[1])); 725 } 726 727 return transform; 728 } 729 730 private static string get_transform_parameters (string function) { 731 int i; 732 string param = ""; 733 734 i = function.index_of ("("); 735 return_val_if_fail (i != -1, param); 736 param = function.substring (i); 737 738 param = param.replace ("(", ""); 739 param = param.replace ("\n", " "); 740 param = param.replace ("\t", " "); 741 param = param.replace (",", " "); 742 743 while (param.index_of (" ") > -1) { 744 param = param.replace (" ", " "); 745 } 746 747 return param.strip(); 748 } 749 750 private bool is_visible (XmlElement tag) { 751 bool hidden = false; 752 753 foreach (Attribute attr in tag.get_attributes ()) { 754 if (attr.get_name () == "display" && attr.get_content () == "none") { 755 hidden = true; 756 } 757 758 if (attr.get_name () == "visibility" 759 && (attr.get_content () == "hidden" 760 || attr.get_content () == "collapse")) { 761 hidden = true; 762 } 763 } 764 765 return !hidden; 766 } 767 768 private SvgTransforms get_transform (Attributes attributes) { 769 foreach (Attribute attr in attributes) { 770 if (attr.get_name () == "transform") { 771 return parse_transform (attr.get_content ()); 772 } 773 } 774 775 return new SvgTransforms (); 776 } 777 778 private void parse_path (Layer layer, SvgStyle parent_style, XmlElement tag) { 779 SvgPath path = new SvgPath (); 780 781 foreach (Attribute attr in tag.get_attributes ()) { 782 if (attr.get_name () == "d") { 783 path.points = parse_points (attr.get_content (), format); 784 } 785 } 786 787 set_object_properties (path, parent_style, tag); 788 layer.add_object (path); 789 } 790 791 public static Gee.ArrayList<Points> parse_points (string data, SvgFormat format) { 792 Gee.ArrayList<Points> path_data = new Gee.ArrayList<Points> (); 793 Points points = new Points (); 794 BezierPoints[] bezier_points; 795 int points_size; 796 797 get_bezier_points (data, out bezier_points, out points_size, true); 798 799 // all instructions are padded 800 801 for (int i = 0; i < points_size; i++) { 802 // FIXME: add more types 803 if (bezier_points[i].type == 'M') { 804 points.add_type (POINT_LINE); 805 points.add (bezier_points[i].x0); 806 points.add (bezier_points[i].y0); 807 points.add (0); 808 points.add (0); 809 points.add (0); 810 points.add (0); 811 points.add (0); 812 } else if (bezier_points[i].type == 'C') { 813 points.add_type (POINT_CUBIC); 814 points.add (bezier_points[i].x0); 815 points.add (bezier_points[i].y0); 816 points.add (bezier_points[i].x1); 817 points.add (bezier_points[i].y1); 818 points.add (bezier_points[i].x2); 819 points.add (bezier_points[i].y2); 820 points.add (0); 821 } else if (bezier_points[i].type == 'L') { 822 points.add_type (POINT_LINE); 823 points.add (bezier_points[i].x0); 824 points.add (bezier_points[i].y0); 825 points.add (0); 826 points.add (0); 827 points.add (0); 828 points.add (0); 829 points.add (0); 830 } else if (bezier_points[i].type == 'A') { 831 BezierPoints b = bezier_points[i]; 832 double angle_start; 833 double angle_extent; 834 double center_x; 835 double center_y; 836 double rotation = b.angle; 837 838 get_arc_arguments (b.x0, b.y0, b.rx, b.ry, 839 b.angle, b.large_arc, b.sweep, b.x1, b.y1, 840 out angle_start, out angle_extent, 841 out center_x, out center_y); 842 843 points.add_type (POINT_ARC); 844 points.add (center_x); 845 points.add (center_y); 846 points.add (b.rx); 847 points.add (b.ry); 848 points.add (angle_start); 849 points.add (angle_extent); 850 points.add (rotation); 851 } else if (bezier_points[i].type == 'z') { 852 points.closed = true; 853 path_data.add (points); 854 points = new Points (); 855 } else { 856 string type = (!) bezier_points[i].type.to_string (); 857 warning (@"SVG conversion not implemented for $type"); 858 } 859 } 860 861 if (points.size > 0) { 862 path_data.add (points); 863 } 864 865 if (format == SvgFormat.ILLUSTRATOR) { 866 Gee.ArrayList<Points> illustrator_path_data = new Gee.ArrayList<Points> (); 867 868 foreach (Points p in path_data) { 869 return_val_if_fail (p.point_data.size % 8 == 0, path_data); 870 871 if (p.point_data.size > 8) { 872 Points illustrator_points = new Points (); 873 874 if (p.point_data.get_point_type (p.point_data.size - 8) == POINT_CUBIC) { 875 illustrator_points.insert (0, POINT_LINE); 876 illustrator_points.insert (1, p.point_data.get_double (p.point_data.size - 3)); 877 illustrator_points.insert (2, p.point_data.get_double (p.point_data.size - 2)); 878 illustrator_points.insert (3, 0); 879 illustrator_points.insert (4, 0); 880 illustrator_points.insert (5, 0); 881 illustrator_points.insert (6, 0); 882 illustrator_points.insert (7, 0); 883 } else { 884 illustrator_points.insert (0, POINT_LINE); 885 illustrator_points.insert (1, p.point_data.get_double (p.point_data.size - 7)); 886 illustrator_points.insert (2, p.point_data.get_double (p.point_data.size - 6)); 887 illustrator_points.insert (3, 0); 888 illustrator_points.insert (4, 0); 889 illustrator_points.insert (5, 0); 890 illustrator_points.insert (6, 0); 891 illustrator_points.insert (7, 0); 892 } 893 894 int start = 0; 895 896 for (int i = start; i < p.point_data.size; i += 1) { 897 illustrator_points.point_data.add (p.point_data.get_double (i)); 898 } 899 900 illustrator_points.closed = p.closed; 901 illustrator_path_data.add (illustrator_points); 902 } 903 } 904 905 path_data = illustrator_path_data; 906 } 907 908 return path_data; 909 } 910 911 public static double parse_double (string? s) { 912 if (unlikely (s == null)) { 913 warning ("number is null"); 914 return 0; 915 } 916 917 if (unlikely (!double.try_parse ((!) s))) { 918 warning (@"Expecting a double got: $((!) s)"); 919 return 0; 920 } 921 922 return double.parse ((!) s); 923 } 924 925 public static void get_bezier_points (string point_data, 926 out BezierPoints[] bezier_points, out int points, bool svg_glyph) { 927 928 double px = 0; 929 double py = 0; 930 double px2 = 0; 931 double py2 = 0; 932 double cx = 0; 933 double cy = 0; 934 string[] c; 935 double arc_rx, arc_ry; 936 double arc_rotation; 937 int large_arc; 938 int arc_sweep; 939 double arc_dest_x, arc_dest_y; 940 941 int bi = 0; 942 943 string data = add_separators (point_data); 944 c = data.split (" "); 945 946 // the arc instruction can use up to eight points 947 int bezier_points_length = 8 * c.length + 1; 948 bezier_points = new BezierPoints[bezier_points_length]; 949 950 for (int i = 0; i < bezier_points_length; i++) { 951 bezier_points[i] = new BezierPoints (); 952 } 953 954 // parse path 955 int i = -1; 956 while (++i < c.length && bi < bezier_points.length) { 957 if (c[i] == "m") { 958 while (i + 2 < c.length && is_point (c[i + 1])) { 959 bezier_points[bi].type = 'M'; 960 bezier_points[bi].svg_type = 'm'; 961 962 px += parse_double (c[++i]); 963 964 if (svg_glyph) { 965 py += parse_double (c[++i]); 966 } else { 967 py += -parse_double (c[++i]); 968 } 969 970 bezier_points[bi].x0 = px; 971 bezier_points[bi].y0 = py; 972 bi++; 973 } 974 } else if (c[i] == "M") { 975 while (i + 2 < c.length && is_point (c[i + 1])) { 976 bezier_points[bi].type = 'M'; 977 bezier_points[bi].svg_type = 'M'; 978 979 px = parse_double (c[++i]); 980 981 if (svg_glyph) { 982 py = parse_double (c[++i]); 983 } else { 984 py = -parse_double (c[++i]); 985 } 986 987 bezier_points[bi].x0 = px; 988 bezier_points[bi].y0 = py; 989 bi++; 990 } 991 } else if (c[i] == "h") { 992 while (i + 1 < c.length && is_point (c[i + 1])) { 993 bezier_points[bi].type = 'L'; 994 bezier_points[bi].svg_type = 'h'; 995 996 px += parse_double (c[++i]); 997 998 bezier_points[bi].x0 = px; 999 bezier_points[bi].y0 = py; 1000 bi++; 1001 } 1002 } else if (i + 1 < c.length && c[i] == "H") { 1003 while (is_point (c[i + 1])) { 1004 bezier_points[bi].type = 'L'; 1005 bezier_points[bi].svg_type = 'H'; 1006 1007 px = parse_double (c[++i]); 1008 1009 bezier_points[bi].x0 = px; 1010 bezier_points[bi].y0 = py; 1011 bi++; 1012 } 1013 } else if (c[i] == "v") { 1014 while (i + 1 < c.length && is_point (c[i + 1])) { 1015 bezier_points[bi].type = 'L'; 1016 bezier_points[bi].svg_type = 'v'; 1017 1018 if (svg_glyph) { 1019 py = py + parse_double (c[++i]); 1020 } else { 1021 py = py - parse_double (c[++i]); 1022 } 1023 1024 bezier_points[bi].x0 = px; 1025 bezier_points[bi].y0 = py; 1026 bi++; 1027 } 1028 } else if (i + 1 < c.length && c[i] == "V") { 1029 while (is_point (c[i + 1])) { 1030 bezier_points[bi].type = 'L'; 1031 bezier_points[bi].svg_type = 'V'; 1032 1033 if (svg_glyph) { 1034 py = parse_double (c[++i]); 1035 } else { 1036 py = -parse_double (c[++i]); 1037 } 1038 1039 bezier_points[bi].x0 = px; 1040 bezier_points[bi].y0 = py; 1041 bi++; 1042 } 1043 } else if (c[i] == "l") { 1044 while (i + 2 < c.length && is_point (c[i + 1])) { 1045 bezier_points[bi].type = 'L'; 1046 bezier_points[bi].svg_type = 'l'; 1047 1048 cx = px + parse_double (c[++i]); 1049 1050 if (svg_glyph) { 1051 cy = py + parse_double (c[++i]); 1052 } else { 1053 cy = py - parse_double (c[++i]); 1054 } 1055 1056 px = cx; 1057 py = cy; 1058 1059 bezier_points[bi].x0 = cx; 1060 bezier_points[bi].y0 = cy; 1061 bi++; 1062 } 1063 } else if (c[i] == "L") { 1064 while (i + 2 < c.length && is_point (c[i + 1])) { 1065 bezier_points[bi].type = 'L'; 1066 bezier_points[bi].svg_type = 'L'; 1067 1068 cx = parse_double (c[++i]); 1069 1070 if (svg_glyph) { 1071 cy = parse_double (c[++i]); 1072 } else { 1073 cy = -parse_double (c[++i]); 1074 } 1075 1076 px = cx; 1077 py = cy; 1078 1079 bezier_points[bi].x0 = cx; 1080 bezier_points[bi].y0 = cy; 1081 bi++; 1082 } 1083 } else if (c[i] == "c") { 1084 while (i + 6 < c.length && is_point (c[i + 1])) { 1085 bezier_points[bi].type = 'C'; 1086 bezier_points[bi].svg_type = 'C'; 1087 1088 cx = px + parse_double (c[++i]); 1089 1090 if (svg_glyph) { 1091 cy = py + parse_double (c[++i]); 1092 } else { 1093 cy = py - parse_double (c[++i]); 1094 } 1095 1096 bezier_points[bi].x0 = cx; 1097 bezier_points[bi].y0 = cy; 1098 1099 cx = px + parse_double (c[++i]); 1100 1101 if (svg_glyph) { 1102 cy = py + parse_double (c[++i]); 1103 } else { 1104 cy = py - parse_double (c[++i]); 1105 } 1106 1107 px2 = cx; 1108 py2 = cy; 1109 1110 bezier_points[bi].x1 = px2; 1111 bezier_points[bi].y1 = py2; 1112 1113 cx = px + parse_double (c[++i]); 1114 1115 if (svg_glyph) { 1116 cy = py + parse_double (c[++i]); 1117 } else { 1118 cy = py + -parse_double (c[++i]); 1119 } 1120 1121 bezier_points[bi].x2 = cx; 1122 bezier_points[bi].y2 = cy; 1123 1124 px = cx; 1125 py = cy; 1126 1127 bi++; 1128 } 1129 } else if (c[i] == "C") { 1130 while (i + 6 < c.length && is_point (c[i + 1])) { 1131 bezier_points[bi].type = 'C'; 1132 bezier_points[bi].svg_type = 'C'; 1133 1134 cx = parse_double (c[++i]); 1135 1136 if (svg_glyph) { 1137 cy = parse_double (c[++i]); 1138 } else { 1139 cy = -parse_double (c[++i]); 1140 } 1141 1142 bezier_points[bi].x0 = cx; 1143 bezier_points[bi].y0 = cy; 1144 1145 cx = parse_double (c[++i]); 1146 1147 if (svg_glyph) { 1148 cy = parse_double (c[++i]); 1149 } else { 1150 cy = -parse_double (c[++i]); 1151 } 1152 1153 px2 = cx; 1154 py2 = cy; 1155 1156 bezier_points[bi].x1 = cx; 1157 bezier_points[bi].y1 = cy; 1158 1159 cx = parse_double (c[++i]); 1160 1161 if (svg_glyph) { 1162 cy = parse_double (c[++i]); 1163 } else { 1164 cy = -parse_double (c[++i]); 1165 } 1166 1167 bezier_points[bi].x2 = cx; 1168 bezier_points[bi].y2 = cy; 1169 1170 px = cx; 1171 py = cy; 1172 1173 bi++; 1174 } 1175 } else if (c[i] == "q") { 1176 while (i + 4 < c.length && is_point (c[i + 1])) { 1177 bezier_points[bi].type = 'Q'; 1178 bezier_points[bi].svg_type = 'q'; 1179 1180 cx = px + parse_double (c[++i]); 1181 1182 if (svg_glyph) { 1183 cy = py + parse_double (c[++i]); 1184 } else { 1185 cy = py - parse_double (c[++i]); 1186 } 1187 1188 bezier_points[bi].x0 = cx; 1189 bezier_points[bi].y0 = cy; 1190 1191 px2 = cx; 1192 py2 = cy; 1193 1194 cx = px + parse_double (c[++i]); 1195 1196 if (svg_glyph) { 1197 cy = py + parse_double (c[++i]); 1198 } else { 1199 cy = py - parse_double (c[++i]); 1200 } 1201 1202 bezier_points[bi].x1 = cx; 1203 bezier_points[bi].y1 = cy; 1204 1205 px = cx; 1206 py = cy; 1207 1208 bi++; 1209 } 1210 } else if (c[i] == "Q") { 1211 while (i + 4 < c.length && is_point (c[i + 1])) { 1212 bezier_points[bi].type = 'Q'; 1213 bezier_points[bi].svg_type = 'Q'; 1214 1215 cx = parse_double (c[++i]); 1216 1217 if (svg_glyph) { 1218 cy = parse_double (c[++i]); 1219 } else { 1220 cy = -parse_double (c[++i]); 1221 } 1222 1223 bezier_points[bi].x0 = cx; 1224 bezier_points[bi].y0 = cy; 1225 1226 px2 = cx; 1227 py2 = cy; 1228 1229 cx = parse_double (c[++i]); 1230 1231 if (svg_glyph) { 1232 cy = parse_double (c[++i]); 1233 } else { 1234 cy = -parse_double (c[++i]); 1235 } 1236 1237 px = cx; 1238 py = cy; 1239 1240 bezier_points[bi].x1 = cx; 1241 bezier_points[bi].y1 = cy; 1242 1243 bi++; 1244 } 1245 } else if (c[i] == "t") { 1246 while (i + 2 < c.length && is_point (c[i + 1])) { 1247 bezier_points[bi].type = 'Q'; 1248 bezier_points[bi].svg_type = 't'; 1249 1250 // the first point is the reflection 1251 cx = 2 * px - px2; 1252 cy = 2 * py - py2; // if (svg_glyph) ? 1253 1254 bezier_points[bi].x0 = cx; 1255 bezier_points[bi].y0 = cy; 1256 1257 px2 = cx; 1258 py2 = cy; 1259 1260 cx = px + parse_double (c[++i]); 1261 1262 if (svg_glyph) { 1263 cy = py + parse_double (c[++i]); 1264 } else { 1265 cy = py - parse_double (c[++i]); 1266 } 1267 1268 px = cx; 1269 py = cy; 1270 1271 bezier_points[bi].x1 = px; 1272 bezier_points[bi].y1 = py; 1273 1274 bi++; 1275 } 1276 } else if (c[i] == "T") { 1277 while (i + 2 < c.length && is_point (c[i + 1])) { 1278 bezier_points[bi].type = 'Q'; 1279 bezier_points[bi].svg_type = 'T'; 1280 1281 // the reflection 1282 cx = 2 * px - px2; 1283 cy = 2 * py - py2; 1284 1285 bezier_points[bi].x0 = cx; 1286 bezier_points[bi].y0 = cy; 1287 1288 px2 = cx; 1289 py2 = cy; 1290 1291 cx = parse_double (c[++i]); 1292 1293 if (svg_glyph) { 1294 cy = parse_double (c[++i]); 1295 } else { 1296 cy = -parse_double (c[++i]); 1297 } 1298 1299 px = cx; 1300 py = cy; 1301 1302 bezier_points[bi].x1 = px; 1303 bezier_points[bi].y1 = py; 1304 1305 bi++; 1306 } 1307 } else if (c[i] == "s") { 1308 while (i + 4 < c.length && is_point (c[i + 1])) { 1309 bezier_points[bi].type = 'C'; 1310 bezier_points[bi].svg_type = 's'; 1311 1312 // the first point is the reflection 1313 cx = 2 * px - px2; 1314 cy = 2 * py - py2; // if (svg_glyph) ? 1315 1316 bezier_points[bi].x0 = cx; 1317 bezier_points[bi].y0 = cy; 1318 1319 cx = px + parse_double (c[++i]); 1320 1321 if (svg_glyph) { 1322 cy = py + parse_double (c[++i]); 1323 } else { 1324 cy = py - parse_double (c[++i]); 1325 } 1326 1327 px2 = cx; 1328 py2 = cy; 1329 1330 bezier_points[bi].x1 = px2; 1331 bezier_points[bi].y1 = py2; 1332 1333 cx = px + parse_double (c[++i]); 1334 1335 if (svg_glyph) { 1336 cy = py + parse_double (c[++i]); 1337 } else { 1338 cy = py - parse_double (c[++i]); 1339 } 1340 1341 bezier_points[bi].x2 = cx; 1342 bezier_points[bi].y2 = cy; 1343 1344 px = cx; 1345 py = cy; 1346 1347 bi++; 1348 } 1349 } else if (c[i] == "S") { 1350 while (i + 4 < c.length && is_point (c[i + 1])) { 1351 bezier_points[bi].type = 'C'; 1352 bezier_points[bi].svg_type = 'S'; 1353 1354 // the reflection 1355 cx = 2 * px - px2; 1356 cy = 2 * py - py2; // if (svg_glyph) ? 1357 1358 bezier_points[bi].x0 = cx; 1359 bezier_points[bi].y0 = cy; 1360 1361 // the other two are regular cubic points 1362 cx = parse_double (c[++i]); 1363 1364 if (svg_glyph) { 1365 cy = parse_double (c[++i]); 1366 } else { 1367 cy = -parse_double (c[++i]); 1368 } 1369 1370 px2 = cx; 1371 py2 = cy; 1372 1373 bezier_points[bi].x1 = px2; 1374 bezier_points[bi].y1 = py2; 1375 1376 cx = parse_double (c[++i]); 1377 1378 if (svg_glyph) { 1379 cy = parse_double (c[++i]); 1380 } else { 1381 cy = -parse_double (c[++i]); 1382 } 1383 1384 bezier_points[bi].x2 = cx; 1385 bezier_points[bi].y2 = cy; 1386 1387 px = cx; 1388 py = cy; 1389 1390 bi++; 1391 } 1392 } else if (c[i] == "a") { 1393 while (i + 7 < c.length && is_point (c[i + 1])) { 1394 arc_rx = parse_double (c[++i]); 1395 arc_ry = parse_double (c[++i]); 1396 1397 arc_rotation = PI * (parse_double (c[++i]) / 180.0); 1398 large_arc = parse_int (c[++i]); 1399 arc_sweep = parse_int (c[++i]); 1400 1401 cx = px + parse_double (c[++i]); 1402 1403 if (svg_glyph) { 1404 cy = py + parse_double (c[++i]); 1405 } else { 1406 cy = py - parse_double (c[++i]); 1407 } 1408 1409 arc_dest_x = cx; 1410 arc_dest_y = cy; 1411 1412 bezier_points[bi].type = 'A'; 1413 bezier_points[bi].svg_type = 'a'; 1414 bezier_points[bi].x0 = px; 1415 bezier_points[bi].y0 = py; 1416 bezier_points[bi].x1 = cx; 1417 bezier_points[bi].y1 = cy; 1418 bezier_points[bi].rx = arc_rx; 1419 bezier_points[bi].ry = arc_ry; 1420 bezier_points[bi].angle = arc_rotation; 1421 bezier_points[bi].large_arc = large_arc == 1; 1422 bezier_points[bi].sweep = arc_sweep == 1; 1423 bi++; 1424 1425 px = cx; 1426 py = cy; 1427 } 1428 } else if (i + 7 < c.length && c[i] == "A") { 1429 while (is_point (c[i + 1])) { 1430 arc_rx = parse_double (c[++i]); 1431 arc_ry = parse_double (c[++i]); 1432 1433 arc_rotation = PI * (parse_double (c[++i]) / 180.0); 1434 large_arc = parse_int (c[++i]); 1435 arc_sweep = parse_int (c[++i]); 1436 1437 cx = parse_double (c[++i]); 1438 1439 if (svg_glyph) { 1440 cy = parse_double (c[++i]); 1441 } else { 1442 cy = -parse_double (c[++i]); 1443 } 1444 1445 arc_dest_x = cx; 1446 arc_dest_y = cy; 1447 1448 bezier_points[bi].type = 'A'; 1449 bezier_points[bi].svg_type = 'A'; 1450 bezier_points[bi].x0 = px; 1451 bezier_points[bi].y0 = py; 1452 bezier_points[bi].x1 = cx; 1453 bezier_points[bi].y1 = cy; 1454 bezier_points[bi].rx = arc_rx; 1455 bezier_points[bi].ry = arc_ry; 1456 bezier_points[bi].angle = arc_rotation; 1457 bezier_points[bi].large_arc = large_arc == 1; 1458 bezier_points[bi].sweep = arc_sweep == 1; 1459 bi++; 1460 1461 px = cx; 1462 py = cy; 1463 } 1464 } else if (c[i] == "z") { 1465 bezier_points[bi].type = 'z'; 1466 bezier_points[bi].svg_type = 'z'; 1467 1468 bi++; 1469 } else if (c[i] == "Z") { 1470 bezier_points[bi].type = 'z'; 1471 bezier_points[bi].svg_type = 'z'; 1472 1473 bi++; 1474 } else if (c[i] == "") { 1475 } else if (c[i] == " ") { 1476 } else { 1477 warning (@"Unknown instruction: $(c[i])"); 1478 } 1479 } 1480 1481 if (bi == 0) { 1482 warning ("No points in path."); 1483 } 1484 1485 points = bi; 1486 } 1487 1488 static int parse_int (string? s) { 1489 if (unlikely (s == null)) { 1490 warning ("null instead of string"); 1491 return 0; 1492 } 1493 1494 if (unlikely (!int64.try_parse ((!) s))) { 1495 warning (@"Expecting an integer: $((!) s)"); 1496 return 0; 1497 } 1498 1499 return int.parse ((!) s); 1500 } 1501 1502 static bool is_point (string? s) { 1503 if (unlikely (s == null)) { 1504 warning ("s is null"); 1505 return false; 1506 } 1507 1508 return double.try_parse ((!) s); 1509 } 1510 1511 /** Add space as separator to svg data. 1512 * @param d svg data 1513 */ 1514 public static string add_separators (string d) { 1515 string data = d; 1516 1517 data = data.replace (",", " "); 1518 data = data.replace ("a", " a "); 1519 data = data.replace ("A", " A "); 1520 data = data.replace ("m", " m "); 1521 data = data.replace ("M", " M "); 1522 data = data.replace ("h", " h "); 1523 data = data.replace ("H", " H "); 1524 data = data.replace ("v", " v "); 1525 data = data.replace ("V", " V "); 1526 data = data.replace ("l", " l "); 1527 data = data.replace ("L", " L "); 1528 data = data.replace ("q", " q "); 1529 data = data.replace ("Q", " Q "); 1530 data = data.replace ("c", " c "); 1531 data = data.replace ("C", " C "); 1532 data = data.replace ("t", " t "); 1533 data = data.replace ("T", " T "); 1534 data = data.replace ("s", " s "); 1535 data = data.replace ("S", " S "); 1536 data = data.replace ("zM", " z M "); 1537 data = data.replace ("zm", " z m "); 1538 data = data.replace ("z", " z "); 1539 data = data.replace ("Z", " Z "); 1540 data = data.replace ("-", " -"); 1541 data = data.replace ("e -", "e-"); // minus can be either separator or a negative exponent 1542 data = data.replace ("\t", " "); 1543 data = data.replace ("\r\n", " "); 1544 data = data.replace ("\n", " "); 1545 1546 // use only a single space as separator 1547 while (data.index_of (" ") > -1) { 1548 data = data.replace (" ", " "); 1549 } 1550 1551 return data; 1552 } 1553 } 1554 1555 } 1556