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.
Merge ../birdfont-2.x
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 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 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 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 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 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.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 for (int i = 0; i < points_size; i++) { 697 // FIXME: add more types 698 if (bezier_points[i].type == 'M') { 699 if (i == 0) { 700 points.x = bezier_points[i].x0; 701 points.y = bezier_points[i].y0; 702 } else { 703 points.add_type (POINT_LINE); 704 points.add (bezier_points[i].x0); 705 points.add (bezier_points[i].y0); 706 points.add (0); 707 points.add (0); 708 points.add (0); 709 points.add (0); 710 points.add (0); 711 } 712 } else if (bezier_points[i].type == 'C') { 713 points.add_type (POINT_CUBIC); 714 points.add (bezier_points[i].x0); 715 points.add (bezier_points[i].y0); 716 points.add (bezier_points[i].x1); 717 points.add (bezier_points[i].y1); 718 points.add (bezier_points[i].x2); 719 points.add (bezier_points[i].y2); 720 points.add (0); 721 } else if (bezier_points[i].type == 'L') { 722 points.add_type (POINT_LINE); 723 points.add (bezier_points[i].x0); 724 points.add (bezier_points[i].y0); 725 points.add (0); 726 points.add (0); 727 points.add (0); 728 points.add (0); 729 points.add (0); 730 } else if (bezier_points[i].type == 'A') { 731 BezierPoints b = bezier_points[i]; 732 double angle_start; 733 double angle_extent; 734 double center_x; 735 double center_y; 736 double rotation = b.angle; 737 738 get_arc_arguments (b.x0, b.y0, b.rx, b.ry, 739 b.angle, b.large_arc, b.sweep, b.x1, b.y1, 740 out angle_start, out angle_extent, 741 out center_x, out center_y); 742 743 points.add_type (POINT_ARC); 744 points.add (center_x); 745 points.add (center_y); 746 points.add (b.rx); 747 points.add (b.ry); 748 points.add (angle_start); 749 points.add (angle_extent); 750 points.add (rotation); 751 } else if (bezier_points[i].type == 'z') { 752 points.closed = true; 753 path_data.add (points); 754 points = new Points (); 755 } else { 756 string type = (!) bezier_points[i].type.to_string (); 757 warning (@"SVG conversion not implemented for $type"); 758 } 759 } 760 761 if (points.size > 0) { 762 path_data.add (points); 763 } 764 765 return path_data; 766 } 767 768 public static double parse_double (string? s) { 769 if (unlikely (s == null)) { 770 warning ("number is null"); 771 return 0; 772 } 773 774 if (unlikely (!double.try_parse ((!) s))) { 775 warning (@"Expecting a double got: $((!) s)"); 776 return 0; 777 } 778 779 return double.parse ((!) s); 780 } 781 782 // FIXME: rename to instructions 783 public static void get_bezier_points (string point_data, out BezierPoints[] bezier_points, out int points, bool svg_glyph) { 784 double px = 0; 785 double py = 0; 786 double px2 = 0; 787 double py2 = 0; 788 double cx = 0; 789 double cy = 0; 790 string[] c; 791 double arc_rx, arc_ry; 792 double arc_rotation; 793 int large_arc; 794 int arc_sweep; 795 double arc_dest_x, arc_dest_y; 796 797 int bi = 0; 798 799 string data = add_separators (point_data); 800 c = data.split (" "); 801 802 // the arc instruction can use up to eight points 803 int bezier_points_length = 8 * c.length + 1; 804 bezier_points = new BezierPoints[bezier_points_length]; 805 806 for (int i = 0; i < bezier_points_length; i++) { 807 bezier_points[i] = new BezierPoints (); 808 } 809 810 // parse path 811 int i = -1; 812 while (++i < c.length && bi < bezier_points.length) { 813 if (c[i] == "m") { 814 while (i + 2 < c.length && is_point (c[i + 1])) { 815 bezier_points[bi].type = 'M'; 816 bezier_points[bi].svg_type = 'm'; 817 818 px += parse_double (c[++i]); 819 820 if (svg_glyph) { 821 py += parse_double (c[++i]); 822 } else { 823 py += -parse_double (c[++i]); 824 } 825 826 bezier_points[bi].x0 = px; 827 bezier_points[bi].y0 = py; 828 bi++; 829 } 830 } else if (c[i] == "M") { 831 while (i + 2 < c.length && is_point (c[i + 1])) { 832 bezier_points[bi].type = 'M'; 833 bezier_points[bi].svg_type = 'M'; 834 835 px = parse_double (c[++i]); 836 837 if (svg_glyph) { 838 py = parse_double (c[++i]); 839 } else { 840 py = -parse_double (c[++i]); 841 } 842 843 bezier_points[bi].x0 = px; 844 bezier_points[bi].y0 = py; 845 bi++; 846 } 847 } else if (c[i] == "h") { 848 while (i + 1 < c.length && is_point (c[i + 1])) { 849 bezier_points[bi].type = 'L'; 850 bezier_points[bi].svg_type = 'h'; 851 852 px += parse_double (c[++i]); 853 854 bezier_points[bi].x0 = px; 855 bezier_points[bi].y0 = py; 856 bi++; 857 } 858 } else if (i + 1 < c.length && c[i] == "H") { 859 while (is_point (c[i + 1])) { 860 bezier_points[bi].type = 'L'; 861 bezier_points[bi].svg_type = 'H'; 862 863 px = parse_double (c[++i]); 864 865 bezier_points[bi].x0 = px; 866 bezier_points[bi].y0 = py; 867 bi++; 868 } 869 } else if (c[i] == "v") { 870 while (i + 1 < c.length && is_point (c[i + 1])) { 871 bezier_points[bi].type = 'L'; 872 bezier_points[bi].svg_type = 'v'; 873 874 if (svg_glyph) { 875 py = py + parse_double (c[++i]); 876 } else { 877 py = py - parse_double (c[++i]); 878 } 879 880 bezier_points[bi].x0 = px; 881 bezier_points[bi].y0 = py; 882 bi++; 883 } 884 } else if (i + 1 < c.length && c[i] == "V") { 885 while (is_point (c[i + 1])) { 886 bezier_points[bi].type = 'L'; 887 bezier_points[bi].svg_type = 'V'; 888 889 if (svg_glyph) { 890 py = parse_double (c[++i]); 891 } else { 892 py = -parse_double (c[++i]); 893 } 894 895 bezier_points[bi].x0 = px; 896 bezier_points[bi].y0 = py; 897 bi++; 898 } 899 } else if (c[i] == "l") { 900 while (i + 2 < c.length && is_point (c[i + 1])) { 901 bezier_points[bi].type = 'L'; 902 bezier_points[bi].svg_type = 'l'; 903 904 cx = px + parse_double (c[++i]); 905 906 if (svg_glyph) { 907 cy = py + parse_double (c[++i]); 908 } else { 909 cy = py - parse_double (c[++i]); 910 } 911 912 px = cx; 913 py = cy; 914 915 bezier_points[bi].x0 = cx; 916 bezier_points[bi].y0 = cy; 917 bi++; 918 } 919 } else if (c[i] == "L") { 920 while (i + 2 < c.length && is_point (c[i + 1])) { 921 bezier_points[bi].type = 'L'; 922 bezier_points[bi].svg_type = 'L'; 923 924 cx = parse_double (c[++i]); 925 926 if (svg_glyph) { 927 cy = parse_double (c[++i]); 928 } else { 929 cy = -parse_double (c[++i]); 930 } 931 932 px = cx; 933 py = cy; 934 935 bezier_points[bi].x0 = cx; 936 bezier_points[bi].y0 = cy; 937 bi++; 938 } 939 } else if (c[i] == "c") { 940 while (i + 6 < c.length && is_point (c[i + 1])) { 941 bezier_points[bi].type = 'C'; 942 bezier_points[bi].svg_type = 'C'; 943 944 cx = px + parse_double (c[++i]); 945 946 if (svg_glyph) { 947 cy = py + parse_double (c[++i]); 948 } else { 949 cy = py - parse_double (c[++i]); 950 } 951 952 bezier_points[bi].x0 = cx; 953 bezier_points[bi].y0 = cy; 954 955 cx = px + parse_double (c[++i]); 956 957 if (svg_glyph) { 958 cy = py + parse_double (c[++i]); 959 } else { 960 cy = py - parse_double (c[++i]); 961 } 962 963 px2 = cx; 964 py2 = cy; 965 966 bezier_points[bi].x1 = px2; 967 bezier_points[bi].y1 = py2; 968 969 cx = px + parse_double (c[++i]); 970 971 if (svg_glyph) { 972 cy = py + parse_double (c[++i]); 973 } else { 974 cy = py + -parse_double (c[++i]); 975 } 976 977 bezier_points[bi].x2 = cx; 978 bezier_points[bi].y2 = cy; 979 980 px = cx; 981 py = cy; 982 983 bi++; 984 } 985 } else if (c[i] == "C") { 986 while (i + 6 < c.length && is_point (c[i + 1])) { 987 bezier_points[bi].type = 'C'; 988 bezier_points[bi].svg_type = 'C'; 989 990 cx = parse_double (c[++i]); 991 992 if (svg_glyph) { 993 cy = parse_double (c[++i]); 994 } else { 995 cy = -parse_double (c[++i]); 996 } 997 998 bezier_points[bi].x0 = cx; 999 bezier_points[bi].y0 = cy; 1000 1001 cx = parse_double (c[++i]); 1002 1003 if (svg_glyph) { 1004 cy = parse_double (c[++i]); 1005 } else { 1006 cy = -parse_double (c[++i]); 1007 } 1008 1009 px2 = cx; 1010 py2 = cy; 1011 1012 bezier_points[bi].x1 = cx; 1013 bezier_points[bi].y1 = cy; 1014 1015 cx = parse_double (c[++i]); 1016 1017 if (svg_glyph) { 1018 cy = parse_double (c[++i]); 1019 } else { 1020 cy = -parse_double (c[++i]); 1021 } 1022 1023 bezier_points[bi].x2 = cx; 1024 bezier_points[bi].y2 = cy; 1025 1026 px = cx; 1027 py = cy; 1028 1029 bi++; 1030 } 1031 } else if (c[i] == "q") { 1032 while (i + 4 < c.length && is_point (c[i + 1])) { 1033 bezier_points[bi].type = 'Q'; 1034 bezier_points[bi].svg_type = 'q'; 1035 1036 cx = px + parse_double (c[++i]); 1037 1038 if (svg_glyph) { 1039 cy = py + parse_double (c[++i]); 1040 } else { 1041 cy = py - parse_double (c[++i]); 1042 } 1043 1044 bezier_points[bi].x0 = cx; 1045 bezier_points[bi].y0 = cy; 1046 1047 px2 = cx; 1048 py2 = cy; 1049 1050 cx = px + parse_double (c[++i]); 1051 1052 if (svg_glyph) { 1053 cy = py + parse_double (c[++i]); 1054 } else { 1055 cy = py - parse_double (c[++i]); 1056 } 1057 1058 bezier_points[bi].x1 = cx; 1059 bezier_points[bi].y1 = cy; 1060 1061 px = cx; 1062 py = cy; 1063 1064 bi++; 1065 } 1066 } else if (c[i] == "Q") { 1067 while (i + 4 < c.length && is_point (c[i + 1])) { 1068 bezier_points[bi].type = 'Q'; 1069 bezier_points[bi].svg_type = 'Q'; 1070 1071 cx = parse_double (c[++i]); 1072 1073 if (svg_glyph) { 1074 cy = parse_double (c[++i]); 1075 } else { 1076 cy = -parse_double (c[++i]); 1077 } 1078 1079 bezier_points[bi].x0 = cx; 1080 bezier_points[bi].y0 = cy; 1081 1082 px2 = cx; 1083 py2 = cy; 1084 1085 cx = parse_double (c[++i]); 1086 1087 if (svg_glyph) { 1088 cy = parse_double (c[++i]); 1089 } else { 1090 cy = -parse_double (c[++i]); 1091 } 1092 1093 px = cx; 1094 py = cy; 1095 1096 bezier_points[bi].x1 = cx; 1097 bezier_points[bi].y1 = cy; 1098 1099 bi++; 1100 } 1101 } else if (c[i] == "t") { 1102 while (i + 2 < c.length && is_point (c[i + 1])) { 1103 bezier_points[bi].type = 'Q'; 1104 bezier_points[bi].svg_type = 't'; 1105 1106 // the first point is the reflection 1107 cx = 2 * px - px2; 1108 cy = 2 * py - py2; // if (svg_glyph) ? 1109 1110 bezier_points[bi].x0 = cx; 1111 bezier_points[bi].y0 = cy; 1112 1113 px2 = cx; 1114 py2 = cy; 1115 1116 cx = px + parse_double (c[++i]); 1117 1118 if (svg_glyph) { 1119 cy = py + parse_double (c[++i]); 1120 } else { 1121 cy = py - parse_double (c[++i]); 1122 } 1123 1124 px = cx; 1125 py = cy; 1126 1127 bezier_points[bi].x1 = px; 1128 bezier_points[bi].y1 = py; 1129 1130 bi++; 1131 } 1132 } else if (c[i] == "T") { 1133 while (i + 2 < c.length && is_point (c[i + 1])) { 1134 bezier_points[bi].type = 'Q'; 1135 bezier_points[bi].svg_type = 'T'; 1136 1137 // the reflection 1138 cx = 2 * px - px2; 1139 cy = 2 * py - py2; 1140 1141 bezier_points[bi].x0 = cx; 1142 bezier_points[bi].y0 = cy; 1143 1144 px2 = cx; 1145 py2 = cy; 1146 1147 cx = parse_double (c[++i]); 1148 1149 if (svg_glyph) { 1150 cy = parse_double (c[++i]); 1151 } else { 1152 cy = -parse_double (c[++i]); 1153 } 1154 1155 px = cx; 1156 py = cy; 1157 1158 bezier_points[bi].x1 = px; 1159 bezier_points[bi].y1 = py; 1160 1161 bi++; 1162 } 1163 } else if (c[i] == "s") { 1164 while (i + 4 < c.length && is_point (c[i + 1])) { 1165 bezier_points[bi].type = 'C'; 1166 bezier_points[bi].svg_type = 's'; 1167 1168 // the first point is the reflection 1169 cx = 2 * px - px2; 1170 cy = 2 * py - py2; // if (svg_glyph) ? 1171 1172 bezier_points[bi].x0 = cx; 1173 bezier_points[bi].y0 = cy; 1174 1175 cx = px + parse_double (c[++i]); 1176 1177 if (svg_glyph) { 1178 cy = py + parse_double (c[++i]); 1179 } else { 1180 cy = py - parse_double (c[++i]); 1181 } 1182 1183 px2 = cx; 1184 py2 = cy; 1185 1186 bezier_points[bi].x1 = px2; 1187 bezier_points[bi].y1 = py2; 1188 1189 cx = px + parse_double (c[++i]); 1190 1191 if (svg_glyph) { 1192 cy = py + parse_double (c[++i]); 1193 } else { 1194 cy = py - parse_double (c[++i]); 1195 } 1196 1197 bezier_points[bi].x2 = cx; 1198 bezier_points[bi].y2 = cy; 1199 1200 px = cx; 1201 py = cy; 1202 1203 bi++; 1204 } 1205 } else if (c[i] == "S") { 1206 while (i + 4 < c.length && is_point (c[i + 1])) { 1207 bezier_points[bi].type = 'C'; 1208 bezier_points[bi].svg_type = 'S'; 1209 1210 // the reflection 1211 cx = 2 * px - px2; 1212 cy = 2 * py - py2; // if (svg_glyph) ? 1213 1214 bezier_points[bi].x0 = cx; 1215 bezier_points[bi].y0 = cy; 1216 1217 // the other two are regular cubic points 1218 cx = parse_double (c[++i]); 1219 1220 if (svg_glyph) { 1221 cy = parse_double (c[++i]); 1222 } else { 1223 cy = -parse_double (c[++i]); 1224 } 1225 1226 px2 = cx; 1227 py2 = cy; 1228 1229 bezier_points[bi].x1 = px2; 1230 bezier_points[bi].y1 = py2; 1231 1232 cx = parse_double (c[++i]); 1233 1234 if (svg_glyph) { 1235 cy = parse_double (c[++i]); 1236 } else { 1237 cy = -parse_double (c[++i]); 1238 } 1239 1240 bezier_points[bi].x2 = cx; 1241 bezier_points[bi].y2 = cy; 1242 1243 px = cx; 1244 py = cy; 1245 1246 bi++; 1247 } 1248 } else if (c[i] == "a") { 1249 while (i + 7 < c.length && is_point (c[i + 1])) { 1250 arc_rx = parse_double (c[++i]); 1251 arc_ry = parse_double (c[++i]); 1252 1253 arc_rotation = PI * (parse_double (c[++i]) / 180.0); 1254 large_arc = parse_int (c[++i]); 1255 arc_sweep = parse_int (c[++i]); 1256 1257 cx = px + parse_double (c[++i]); 1258 1259 if (svg_glyph) { 1260 cy = py + parse_double (c[++i]); 1261 } else { 1262 cy = py - parse_double (c[++i]); 1263 } 1264 1265 arc_dest_x = cx; 1266 arc_dest_y = cy; 1267 1268 bezier_points[bi].type = 'A'; 1269 bezier_points[bi].svg_type = 'a'; 1270 bezier_points[bi].x0 = px; 1271 bezier_points[bi].y0 = py; 1272 bezier_points[bi].x1 = cx; 1273 bezier_points[bi].y1 = cy; 1274 bezier_points[bi].rx = arc_rx; 1275 bezier_points[bi].ry = arc_ry; 1276 bezier_points[bi].angle = arc_rotation; 1277 bezier_points[bi].large_arc = large_arc == 1; 1278 bezier_points[bi].sweep = arc_sweep == 1; 1279 bi++; 1280 1281 px = cx; 1282 py = cy; 1283 } 1284 } else if (i + 7 < c.length && c[i] == "A") { 1285 while (is_point (c[i + 1])) { 1286 arc_rx = parse_double (c[++i]); 1287 arc_ry = parse_double (c[++i]); 1288 1289 arc_rotation = PI * (parse_double (c[++i]) / 180.0); 1290 large_arc = parse_int (c[++i]); 1291 arc_sweep = parse_int (c[++i]); 1292 1293 cx = parse_double (c[++i]); 1294 1295 if (svg_glyph) { 1296 cy = parse_double (c[++i]); 1297 } else { 1298 cy = -parse_double (c[++i]); 1299 } 1300 1301 arc_dest_x = cx; 1302 arc_dest_y = cy; 1303 1304 bezier_points[bi].type = 'A'; 1305 bezier_points[bi].svg_type = 'A'; 1306 bezier_points[bi].x0 = px; 1307 bezier_points[bi].y0 = py; 1308 bezier_points[bi].x1 = cx; 1309 bezier_points[bi].y1 = cy; 1310 bezier_points[bi].rx = arc_rx; 1311 bezier_points[bi].ry = arc_ry; 1312 bezier_points[bi].angle = arc_rotation; 1313 bezier_points[bi].large_arc = large_arc == 1; 1314 bezier_points[bi].sweep = arc_sweep == 1; 1315 bi++; 1316 1317 px = cx; 1318 py = cy; 1319 } 1320 } else if (c[i] == "z") { 1321 bezier_points[bi].type = 'z'; 1322 bezier_points[bi].svg_type = 'z'; 1323 1324 bi++; 1325 } else if (c[i] == "Z") { 1326 bezier_points[bi].type = 'z'; 1327 bezier_points[bi].svg_type = 'z'; 1328 1329 bi++; 1330 } else if (c[i] == "") { 1331 } else if (c[i] == " ") { 1332 } else { 1333 warning (@"Unknown instruction: $(c[i])"); 1334 } 1335 } 1336 1337 if (bi == 0) { 1338 warning ("No points in path."); 1339 } 1340 1341 points = bi; 1342 } 1343 1344 static int parse_int (string? s) { 1345 if (unlikely (s == null)) { 1346 warning ("null instead of string"); 1347 return 0; 1348 } 1349 1350 if (unlikely (!int64.try_parse ((!) s))) { 1351 warning (@"Expecting an integer: $((!) s)"); 1352 return 0; 1353 } 1354 1355 return int.parse ((!) s); 1356 } 1357 1358 static bool is_point (string? s) { 1359 if (unlikely (s == null)) { 1360 warning ("s is null"); 1361 return false; 1362 } 1363 1364 return double.try_parse ((!) s); 1365 } 1366 1367 /** Add space as separator to svg data. 1368 * @param d svg data 1369 */ 1370 public static string add_separators (string d) { 1371 string data = d; 1372 1373 data = data.replace (",", " "); 1374 data = data.replace ("a", " a "); 1375 data = data.replace ("A", " A "); 1376 data = data.replace ("m", " m "); 1377 data = data.replace ("M", " M "); 1378 data = data.replace ("h", " h "); 1379 data = data.replace ("H", " H "); 1380 data = data.replace ("v", " v "); 1381 data = data.replace ("V", " V "); 1382 data = data.replace ("l", " l "); 1383 data = data.replace ("L", " L "); 1384 data = data.replace ("q", " q "); 1385 data = data.replace ("Q", " Q "); 1386 data = data.replace ("c", " c "); 1387 data = data.replace ("C", " C "); 1388 data = data.replace ("t", " t "); 1389 data = data.replace ("T", " T "); 1390 data = data.replace ("s", " s "); 1391 data = data.replace ("S", " S "); 1392 data = data.replace ("zM", " z M "); 1393 data = data.replace ("zm", " z m "); 1394 data = data.replace ("z", " z "); 1395 data = data.replace ("Z", " Z "); 1396 data = data.replace ("-", " -"); 1397 data = data.replace ("e -", "e-"); // minus can be either separator or a negative exponent 1398 data = data.replace ("\t", " "); 1399 data = data.replace ("\r\n", " "); 1400 data = data.replace ("\n", " "); 1401 1402 // use only a single space as separator 1403 while (data.index_of (" ") > -1) { 1404 data = data.replace (" ", " "); 1405 } 1406 1407 return data; 1408 } 1409 1410 } 1411 1412 } 1413