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