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