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