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