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