The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

SvgParser.vala in libbirdfont

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 libbirdfont/SvgParser.vala.
Move the SVG parser and the SVG rendering code to libsvgbird
1 /* 2 Copyright (C) 2012 2013 2014 2015 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 Math; 17 using SvgBird; 18 19 namespace BirdFont { 20 21 public enum SvgFormat { 22 NONE, 23 INKSCAPE, 24 ILLUSTRATOR 25 } 26 27 public enum SvgType { 28 COLOR, 29 REGULAR 30 } 31 32 public class SvgParser { 33 34 SvgFormat format = SvgFormat.ILLUSTRATOR; 35 36 public SvgParser () { 37 } 38 39 public void set_format (SvgFormat f) { 40 format = f; 41 } 42 43 public static void import (SvgType type) { 44 FileChooser fc = new FileChooser (); 45 fc.file_selected.connect ((p) => { 46 string path; 47 48 if (p == null) { 49 return; 50 } 51 52 path = (!) p; 53 54 if (type == SvgType.REGULAR) { 55 import_svg (path); 56 } else if (type == SvgType.COLOR) { 57 Glyph glyph = MainWindow.get_current_glyph (); 58 import_color_svg (glyph, path); 59 } 60 }); 61 62 fc.add_extension ("svg"); 63 MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD); 64 } 65 66 public static void import_color_svg (Glyph glyph, string path) { 67 EmbeddedSvg drawing = SvgParser.parse_embedded_svg_file (path); 68 69 Layer layer = new Layer (); 70 layer.name = "SVG"; 71 layer.add_object (drawing); 72 73 glyph.add_layer (layer); 74 75 // FIXME: update GUI 76 } 77 78 public static void import_folder (SvgType type) { 79 FileChooser fc = new FileChooser (); 80 fc.file_selected.connect ((p) => { 81 string path; 82 File svg_folder; 83 File svg; 84 bool imported; 85 FileEnumerator enumerator; 86 FileInfo? file_info; 87 string file_name; 88 Font font; 89 90 if (p == null) { 91 return; 92 } 93 94 path = (!) p; 95 svg_folder = File.new_for_path (path); 96 font = BirdFont.get_current_font (); 97 98 try { 99 enumerator = svg_folder.enumerate_children (FileAttribute.STANDARD_NAME, 0); 100 while ((file_info = enumerator.next_file ()) != null) { 101 file_name = ((!) file_info).get_name (); 102 103 if (file_name.has_suffix (".svg")) { 104 svg = get_child (svg_folder, file_name); 105 imported = import_svg_file (font, svg, type); 106 107 if (!imported) { 108 warning ("Can't import %s.", (!) svg.get_path ()); 109 } else { 110 font.touch (); 111 } 112 } 113 } 114 } catch (Error e) { 115 warning (e.message); 116 } 117 }); 118 119 MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD | FileChooser.DIRECTORY); 120 } 121 122 public static void import_svg_data (string xml_data, SvgFormat format = SvgFormat.NONE) { 123 PathList path_list = new PathList (); 124 Glyph glyph; 125 string[] lines = xml_data.split ("\n"); 126 bool has_format = false; 127 SvgParser parser = new SvgParser (); 128 XmlParser xmlparser; 129 130 foreach (string l in lines) { 131 if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) { 132 parser.set_format (SvgFormat.ILLUSTRATOR); 133 has_format = true; 134 } 135 136 if (l.index_of ("Inkscape") > -1 || l.index_of ("inkscape") > -1) { 137 parser.set_format (SvgFormat.INKSCAPE); 138 has_format = true; 139 } 140 } 141 142 if (format != SvgFormat.NONE) { 143 parser.set_format (format); 144 } 145 146 // parse the file 147 if (!has_format) { 148 warn_if_test ("No format identifier found in SVG parser.\n"); 149 } 150 151 xmlparser = new XmlParser (xml_data); 152 153 if (!xmlparser.validate()) { 154 warning("Invalid XML in SVG parser."); 155 } 156 157 path_list = parser.parse_svg_file (xmlparser.get_root_tag ()); 158 159 glyph = MainWindow.get_current_glyph (); 160 foreach (Path p in path_list.paths) { 161 PathObject path = new PathObject.for_path (p); 162 glyph.add_object (path); 163 glyph.add_active_object (null, path); // FIXME: groups 164 path.update_region_boundaries (); 165 } 166 167 glyph.close_path (); 168 } 169 170 public static string replace (string content, string start, string stop, string replacement) { 171 int i_tag = content.index_of (start); 172 int end_tag = content.index_of (stop, i_tag); 173 string c = ""; 174 175 if (i_tag > -1) { 176 c = content.substring (0, i_tag) 177 + replacement 178 + content.substring (end_tag + stop.length); 179 } else { 180 c = content; 181 } 182 183 return c; 184 } 185 186 public static void import_svg (string path) { 187 string svg_data; 188 try { 189 FileUtils.get_contents (path, out svg_data); 190 } catch (GLib.Error e) { 191 warning (e.message); 192 } 193 import_svg_data (svg_data); 194 } 195 196 private PathList parse_svg_file (Tag tag) { 197 Layer pl = new Layer (); 198 199 foreach (Tag t in tag) { 200 201 if (t.get_name () == "g") { 202 parse_layer (t, pl); 203 } 204 205 if (t.get_name () == "switch") { 206 parse_layer (t, pl); 207 } 208 209 if (t.get_name () == "path") { 210 parse_path (t, pl); 211 } 212 213 if (t.get_name () == "polygon") { 214 parse_polygon (t, pl); 215 } 216 217 if (t.get_name () == "polyline") { 218 parse_polyline (t, pl); 219 } 220 221 if (t.get_name () == "circle") { 222 parse_circle (t, pl); 223 } 224 225 if (t.get_name () == "ellipse") { 226 parse_ellipse (t, pl); 227 } 228 229 if (t.get_name () == "line") { 230 parse_line (t, pl); 231 } 232 } 233 234 return LayerUtils.get_all_paths (pl); 235 } 236 237 private void parse_layer (Tag tag, Layer pl) { 238 Layer layer; 239 bool hidden = false; 240 241 foreach (Attribute attr in tag.get_attributes ()) { 242 if (attr.get_name () == "display" && attr.get_content () == "none") { 243 hidden = true; 244 } 245 246 if (attr.get_name () == "visibility" 247 && (attr.get_content () == "hidden" 248 || attr.get_content () == "collapse")) { 249 hidden = true; 250 } 251 } 252 253 if (hidden) { 254 return; 255 } 256 257 foreach (Tag t in tag) { 258 if (t.get_name () == "path") { 259 parse_path (t, pl); 260 } 261 262 if (t.get_name () == "g") { 263 layer = new Layer (); 264 parse_layer (t, layer); 265 pl.subgroups.add (layer); 266 } 267 268 if (t.get_name () == "polygon") { 269 parse_polygon (t, pl); 270 } 271 272 if (t.get_name () == "polyline") { 273 parse_polyline (t, pl); 274 } 275 276 if (t.get_name () == "rect") { 277 parse_rect (t, pl); 278 } 279 280 if (t.get_name () == "circle") { 281 parse_circle (t, pl); 282 } 283 284 if (t.get_name () == "ellipse") { 285 parse_ellipse (t, pl); 286 } 287 288 if (t.get_name () == "line") { 289 parse_line (t, pl); 290 } 291 } 292 293 foreach (Attribute attr in tag.get_attributes ()) { 294 if (attr.get_name () == "transform") { 295 transform (attr.get_content (), pl); 296 } 297 } 298 } 299 300 private void transform (string transform_functions, Layer layer) { 301 PathList path_list = new PathList (); 302 303 foreach (SvgBird.Object o in layer.objects) { 304 if (o is PathObject) { 305 path_list.add (((PathObject) o).get_path ()); 306 } 307 } 308 309 transform_paths (transform_functions, path_list); 310 transform_subgroups (transform_functions, layer); 311 } 312 313 private void transform_subgroups (string transform_functions, Layer layer) { 314 foreach (Layer subgroup in layer.subgroups) { 315 transform (transform_functions, subgroup); 316 } 317 } 318 319 private void transform_paths (string transform_functions, PathList pl) { 320 string data = transform_functions.dup (); 321 string[] functions; 322 323 // use only a single space as separator 324 while (data.index_of (" ") > -1) { 325 data = data.replace (" ", " "); 326 } 327 328 return_if_fail (data.index_of (")") > -1); 329 330 // add separator 331 data = data.replace (") ", "|"); 332 data = data.replace (")", "|"); 333 functions = data.split ("|"); 334 335 for (int i = functions.length - 1; i >= 0; i--) { 336 if (functions[i].has_prefix ("translate")) { 337 translate (functions[i], pl); 338 } 339 340 if (functions[i].has_prefix ("scale")) { 341 scale (functions[i], pl); 342 } 343 344 if (functions[i].has_prefix ("matrix")) { 345 matrix (functions[i], pl); 346 } 347 348 // TODO: rotate etc. 349 } 350 } 351 352 /** @param path a path in the cartesian coordinate system 353 * The other parameters are in the SVG coordinate system. 354 */ 355 public static void apply_matrix (Path path, double a, double b, double c, 356 double d, double e, double f){ 357 358 double dx, dy; 359 Font font = BirdFont.get_current_font (); 360 Glyph glyph = MainWindow.get_current_glyph (); 361 362 foreach (EditPoint ep in path.points) { 363 ep.tie_handles = false; 364 ep.reflective_point = false; 365 } 366 367 foreach (EditPoint ep in path.points) { 368 apply_matrix_on_handle (ep.get_right_handle (), a, b, c, d, e, f); 369 apply_matrix_on_handle (ep.get_left_handle (), a, b, c, d, e, f); 370 371 ep.independent_y = font.top_position - ep.independent_y; 372 ep.independent_x -= glyph.left_limit; 373 374 dx = a * ep.independent_x + c * ep.independent_y + e; 375 dy = b * ep.independent_x + d * ep.independent_y + f; 376 377 ep.independent_x = dx; 378 ep.independent_y = dy; 379 380 ep.independent_y = font.top_position - ep.independent_y; 381 ep.independent_x += glyph.left_limit; 382 } 383 } 384 385 public static void apply_matrix_on_handle (EditPointHandle h, 386 double a, double b, double c, 387 double d, double e, double f){ 388 389 double dx, dy; 390 Font font = BirdFont.get_current_font (); 391 Glyph glyph = MainWindow.get_current_glyph (); 392 393 h.y = font.top_position - h.y; 394 h.x -= glyph.left_limit; 395 396 dx = a * h.x + c * h.y + e; 397 dy = b * h.x + d * h.y + f; 398 399 h.x = dx; 400 h.y = dy; 401 402 h.y = font.top_position - h.y; 403 h.x += glyph.left_limit; 404 } 405 406 407 private void matrix (string function, PathList pl) { 408 string parameters = get_transform_parameters (function); 409 string[] p = parameters.split (" "); 410 411 if (p.length != 6) { 412 warning ("Expecting six parameters for matrix transformation."); 413 return; 414 } 415 416 foreach (Path path in pl.paths) { 417 apply_matrix (path, parse_double (p[0]), parse_double (p[1]), 418 parse_double (p[2]), parse_double (p[3]), 419 parse_double (p[4]), parse_double (p[5])); 420 } 421 } 422 423 private void scale (string function, PathList pl) { 424 string parameters = get_transform_parameters (function); 425 string[] p = parameters.split (" "); 426 double x, y; 427 428 x = 1; 429 y = 1; 430 431 if (p.length > 0) { 432 x = parse_double (p[0]); 433 } 434 435 if (p.length > 1) { 436 y = parse_double (p[1]); 437 } 438 439 foreach (Path path in pl.paths) { 440 path.scale (-x, y); 441 } 442 } 443 444 private void translate (string function, PathList pl) { 445 string parameters = get_transform_parameters (function); 446 string[] p = parameters.split (" "); 447 double x, y; 448 449 x = 0; 450 y = 0; 451 452 if (p.length > 0) { 453 x = parse_double (p[0]); 454 } 455 456 if (p.length > 1) { 457 y = parse_double (p[1]); 458 } 459 460 foreach (Path path in pl.paths) { 461 path.move (x, -y); 462 } 463 } 464 465 private string get_transform_parameters (string function) { 466 int i; 467 string param = ""; 468 469 i = function.index_of ("("); 470 return_val_if_fail (i != -1, param); 471 param = function.substring (i); 472 473 param = param.replace ("(", ""); 474 param = param.replace ("\n", " "); 475 param = param.replace ("\t", " "); 476 param = param.replace (",", " "); 477 478 while (param.index_of (" ") > -1) { 479 param.replace (" ", " "); 480 } 481 482 return param.strip(); 483 } 484 485 private void parse_circle (Tag tag, Layer pl) { 486 Path p; 487 double x, y, r; 488 Glyph g; 489 PathList npl; 490 BezierPoints[] bezier_points; 491 SvgStyle style = new SvgStyle (); 492 bool hidden = false; 493 494 npl = new PathList (); 495 496 x = 0; 497 y = 0; 498 r = 0; 499 500 foreach (Attribute attr in tag.get_attributes ()) { 501 if (attr.get_name () == "cx") { 502 x = parse_double (attr.get_content ()); 503 } 504 505 if (attr.get_name () == "cy") { 506 y = -parse_double (attr.get_content ()); 507 } 508 509 if (attr.get_name () == "r") { 510 r = parse_double (attr.get_content ()); 511 } 512 513 if (attr.get_name () == "display" && attr.get_content () == "none") { 514 hidden = true; 515 } 516 } 517 518 style = SvgStyle.parse (null, tag.get_attributes ()); 519 520 if (hidden) { 521 return; 522 } 523 524 bezier_points = new BezierPoints[1]; 525 bezier_points[0] = new BezierPoints (); 526 bezier_points[0].type == 'L'; 527 bezier_points[0].x0 = x; 528 bezier_points[0].y0 = y; 529 530 g = MainWindow.get_current_glyph (); 531 move_and_resize (bezier_points, 1, false, 1, g); 532 533 p = CircleTool.create_circle (bezier_points[0].x0, 534 bezier_points[0].y0, r, PointType.CUBIC); 535 536 npl.add (p); 537 538 foreach (Attribute attr in tag.get_attributes ()) { 539 if (attr.get_name () == "transform") { 540 transform_paths (attr.get_content (), npl); 541 } 542 } 543 544 npl.apply_style (style); 545 append_paths (pl, npl); 546 } 547 548 private void parse_ellipse (Tag tag, Layer pl) { 549 Path p; 550 double x, y, rx, ry; 551 Glyph g; 552 PathList npl; 553 BezierPoints[] bezier_points; 554 SvgStyle style = new SvgStyle (); 555 bool hidden = false; 556 557 npl = new PathList (); 558 559 x = 0; 560 y = 0; 561 rx = 0; 562 ry = 0; 563 564 foreach (Attribute attr in tag.get_attributes ()) { 565 if (attr.get_name () == "cx") { 566 x = parse_double (attr.get_content ()); 567 } 568 569 if (attr.get_name () == "cy") { 570 y = -parse_double (attr.get_content ()); 571 } 572 573 if (attr.get_name () == "rx") { 574 rx = parse_double (attr.get_content ()); 575 } 576 577 if (attr.get_name () == "ry") { 578 ry = parse_double (attr.get_content ()); 579 } 580 581 if (attr.get_name () == "display" && attr.get_content () == "none") { 582 hidden = true; 583 } 584 } 585 586 style = SvgStyle.parse (null, tag.get_attributes ()); 587 588 if (hidden) { 589 return; 590 } 591 592 bezier_points = new BezierPoints[1]; 593 bezier_points[0] = new BezierPoints (); 594 bezier_points[0].type == 'L'; 595 bezier_points[0].x0 = x; 596 bezier_points[0].y0 = y; 597 598 g = MainWindow.get_current_glyph (); 599 move_and_resize (bezier_points, 1, false, 1, g); 600 601 p = CircleTool.create_ellipse (bezier_points[0].x0, 602 bezier_points[0].y0, rx, ry, PointType.CUBIC); 603 604 npl.add (p); 605 606 foreach (Attribute attr in tag.get_attributes ()) { 607 if (attr.get_name () == "transform") { 608 transform_paths (attr.get_content (), npl); 609 } 610 } 611 612 npl.apply_style (style); 613 append_paths (pl, npl); 614 } 615 616 private void parse_line (Tag tag, Layer pl) { 617 Path p; 618 double x1, y1, x2, y2; 619 BezierPoints[] bezier_points; 620 Glyph g; 621 PathList npl = new PathList (); 622 SvgStyle style = new SvgStyle (); 623 bool hidden = false; 624 625 x1 = 0; 626 y1 = 0; 627 x2 = 0; 628 y2 = 0; 629 630 foreach (Attribute attr in tag.get_attributes ()) { 631 if (attr.get_name () == "x1") { 632 x1 = parse_double (attr.get_content ()); 633 } 634 635 if (attr.get_name () == "y1") { 636 y1 = -parse_double (attr.get_content ()); 637 } 638 639 if (attr.get_name () == "x2") { 640 x2 = parse_double (attr.get_content ()); 641 } 642 643 if (attr.get_name () == "xy") { 644 y2 = -parse_double (attr.get_content ()); 645 } 646 647 if (attr.get_name () == "display" && attr.get_content () == "none") { 648 hidden = true; 649 } 650 } 651 652 style = SvgStyle.parse (null, tag.get_attributes ()); 653 654 if (hidden) { 655 return; 656 } 657 658 bezier_points = new BezierPoints[2]; 659 bezier_points[0] = new BezierPoints (); 660 bezier_points[0].type == 'L'; 661 bezier_points[0].x0 = x1; 662 bezier_points[0].y0 = y1; 663 664 bezier_points[1] = new BezierPoints (); 665 bezier_points[1].type == 'L'; 666 bezier_points[1].x0 = x2; 667 bezier_points[1].y0 = y2; 668 669 g = MainWindow.get_current_glyph (); 670 move_and_resize (bezier_points, 4, false, 1, g); 671 672 p = new Path (); 673 674 p.add (bezier_points[0].x0, bezier_points[0].y0); 675 p.add (bezier_points[1].x0, bezier_points[1].y0); 676 677 p.close (); 678 p.create_list (); 679 p.recalculate_linear_handles (); 680 681 npl.add (p); 682 683 foreach (Attribute attr in tag.get_attributes ()) { 684 if (attr.get_name () == "transform") { 685 transform_paths (attr.get_content (), npl); 686 } 687 } 688 689 npl.apply_style (style); 690 append_paths (pl, npl); 691 } 692 693 private void parse_rect (Tag tag, Layer layer) { 694 Path p; 695 double x, y, x2, y2; 696 BezierPoints[] bezier_points; 697 Glyph g; 698 PathList npl = new PathList (); 699 SvgStyle style = new SvgStyle (); 700 bool hidden = false; 701 EditPoint ep; 702 703 x = 0; 704 y = 0; 705 x2 = 0; 706 y2 = 0; 707 708 foreach (Attribute attr in tag.get_attributes ()) { 709 if (attr.get_name () == "x") { 710 x = parse_double (attr.get_content ()); 711 } 712 713 if (attr.get_name () == "y") { 714 y = -parse_double (attr.get_content ()); 715 } 716 717 if (attr.get_name () == "width") { 718 x2 = parse_double (attr.get_content ()); 719 } 720 721 if (attr.get_name () == "height") { 722 y2 = -parse_double (attr.get_content ()); 723 } 724 725 if (attr.get_name () == "display" && attr.get_content () == "none") { 726 hidden = true; 727 } 728 } 729 730 style = SvgStyle.parse (null, tag.get_attributes ()); 731 732 if (hidden) { 733 return; 734 } 735 736 x2 += x; 737 y2 += y; 738 739 bezier_points = new BezierPoints[4]; 740 bezier_points[0] = new BezierPoints (); 741 bezier_points[0].type == 'L'; 742 bezier_points[0].x0 = x; 743 bezier_points[0].y0 = y; 744 745 bezier_points[1] = new BezierPoints (); 746 bezier_points[1].type == 'L'; 747 bezier_points[1].x0 = x2; 748 bezier_points[1].y0 = y; 749 750 bezier_points[2] = new BezierPoints (); 751 bezier_points[2].type == 'L'; 752 bezier_points[2].x0 = x2; 753 bezier_points[2].y0 = y2; 754 755 bezier_points[3] = new BezierPoints (); 756 bezier_points[3].type == 'L'; 757 bezier_points[3].x0 = x; 758 bezier_points[3].y0 = y2; 759 760 g = MainWindow.get_current_glyph (); 761 move_and_resize (bezier_points, 4, false, 1, g); 762 763 p = new Path (); 764 765 ep = p.add (bezier_points[0].x0, bezier_points[0].y0); 766 ep.set_point_type (PointType.CUBIC); 767 768 ep = p.add (bezier_points[1].x0, bezier_points[1].y0); 769 ep.set_point_type (PointType.CUBIC); 770 771 ep = p.add (bezier_points[2].x0, bezier_points[2].y0); 772 ep.set_point_type (PointType.CUBIC); 773 774 ep = p.add (bezier_points[3].x0, bezier_points[3].y0); 775 ep.set_point_type (PointType.CUBIC); 776 777 p.close (); 778 p.create_list (); 779 p.recalculate_linear_handles (); 780 781 npl.add (p); 782 783 // FIXME: right layer for other transforms 784 foreach (Attribute attr in tag.get_attributes ()) { 785 if (attr.get_name () == "transform") { 786 transform_paths (attr.get_content (), npl); 787 } 788 } 789 790 npl.apply_style (style); 791 append_paths (layer, npl); 792 } 793 794 private void parse_polygon (Tag tag, Layer layer) { 795 PathList path_list = get_polyline (tag); 796 797 foreach (Path p in path_list.paths) { 798 p.close (); 799 } 800 801 append_paths (layer, path_list); 802 } 803 804 static void append_paths (Layer layer, PathList pl) { 805 LayerUtils.append_paths (layer, pl); 806 } 807 808 private void parse_polyline (Tag tag, Layer layer) { 809 append_paths (layer, get_polyline (tag)); 810 } 811 812 private PathList get_polyline (Tag tag) { 813 Path p = new Path (); 814 bool hidden = false; 815 PathList path_list = new PathList (); 816 SvgStyle style = new SvgStyle (); 817 818 foreach (Attribute attr in tag.get_attributes ()) { 819 if (attr.get_name () == "points") { 820 p = parse_poly_data (attr.get_content ()); 821 } 822 823 if (attr.get_name () == "display" && attr.get_content () == "none") { 824 hidden = true; 825 } 826 } 827 828 style = SvgStyle.parse (null, tag.get_attributes ()); 829 830 if (hidden) { 831 return path_list; 832 } 833 834 path_list.add (p); 835 path_list.apply_style (style); 836 837 foreach (Attribute attr in tag.get_attributes ()) { 838 if (attr.get_name () == "transform") { 839 transform_paths (attr.get_content (), path_list); 840 } 841 } 842 843 return path_list; 844 } 845 846 private void parse_path (Tag tag, Layer layer) { 847 Glyph glyph = MainWindow.get_current_glyph (); 848 PathList path_list = new PathList (); 849 SvgStyle style = new SvgStyle (); 850 bool hidden = false; 851 852 foreach (Attribute attr in tag.get_attributes ()) { 853 if (attr.get_name () == "d") { 854 path_list = parse_svg_data (attr.get_content (), glyph); 855 } 856 857 if (attr.get_name () == "display" && attr.get_content () == "none") { 858 hidden = true; 859 } 860 861 if (attr.get_name () == "visibility" 862 && (attr.get_content () == "hidden" 863 || attr.get_content () == "collapse")) { 864 hidden = true; 865 } 866 } 867 868 style = SvgStyle.parse (null, tag.get_attributes ()); 869 870 if (hidden) { 871 return; 872 } 873 874 foreach (Path path in path_list.paths) { 875 LayerUtils.add_path (layer, path); 876 } 877 878 path_list.apply_style (style); 879 880 // assume the even odd rule is applied and convert the path 881 // to a path using the non-zero rule 882 int inside_count; 883 bool inside; 884 foreach (SvgBird.Object o1 in layer.objects) { 885 if (o1 is PathObject) { 886 Path p1 = ((PathObject) o1).get_path (); 887 inside_count = 0; 888 889 foreach (SvgBird.Object o2 in layer.objects) { 890 if (o2 is PathObject) { 891 Path p2 = ((PathObject) o2).get_path (); 892 893 if (p1 != p2) { 894 inside = true; 895 896 foreach (EditPoint ep in p1.points) { 897 if (!is_inside (ep, p2)) { 898 inside = false; 899 } 900 } 901 902 if (inside) { 903 inside_count++; 904 } 905 } 906 } 907 } 908 909 if (inside_count % 2 == 0) { 910 p1.force_direction (Direction.CLOCKWISE); 911 } else { 912 p1.force_direction (Direction.COUNTER_CLOCKWISE); 913 } 914 } 915 } 916 917 foreach (Attribute attr in tag.get_attributes ()) { 918 if (attr.get_name () == "transform") { 919 transform_paths (attr.get_content (), path_list); 920 } 921 } 922 } 923 924 public static void create_lines_for_segment (Path path, EditPoint start, EditPoint end, double tolerance) { 925 double x1, x2, x3; 926 double y1, y2, y3; 927 double step_start, step, step_end; 928 929 path.add (start.x, start.y); 930 931 step_start = 0; 932 step = 0.5; 933 step_end = 1; 934 935 while (true) { 936 Path.get_point_for_step (start, end, step_start, out x1, out y1); 937 Path.get_point_for_step (start, end, step, out x2, out y2); 938 Path.get_point_for_step (start, end, step_end, out x3, out y3); 939 940 if (!StrokeTool.is_flat (x1, y1, x2, y2, x3, y3, tolerance) 941 && step_end - step / 2.0 > step_start 942 && step_end - step / 2.0 > 0.1 943 && step > 0.05 944 && Path.distance_to_point (start, end) > 1) { 945 946 step /= 2.0; 947 948 if (step < 0.05) { 949 step = 0.05; 950 } else { 951 step_end = step_start + 2 * step; 952 } 953 } else { 954 path.add (x3, y3); 955 956 if (step_end + step < 1) { 957 step_start = step_end; 958 step_end += step; 959 } else { 960 break; 961 } 962 } 963 } 964 } 965 966 public static Path get_lines (Path p) { 967 EditPoint start; 968 Path path = new Path (); 969 970 if (p.points.size == 0) { 971 return path; 972 } 973 974 // create a set of straight lines 975 start = p.points.get (p.points.size - 1); 976 977 foreach (EditPoint end in p.points) { 978 create_lines_for_segment (path, start, end, 1); 979 start = end; 980 } 981 982 return path; 983 } 984 985 /** Check if a point is inside using the even odd fill rule. 986 * The path should only have straight lines. 987 */ 988 public static bool is_inside (EditPoint point, Path path) { 989 EditPoint prev; 990 bool inside = false; 991 992 if (path.points.size <= 1) { 993 return false; 994 } 995 996 if (!(path.xmin <= point.x <= path.xmax)) { 997 return false; 998 } 999 1000 if (!(path.ymin <= point.y <= path.ymax)) { 1001 return false; 1002 } 1003 1004 prev = path.points.get (path.points.size - 1); 1005 1006 foreach (EditPoint p in path.points) { 1007 if ((p.y > point.y) != (prev.y > point.y) 1008 && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { 1009 inside = !inside; 1010 } 1011 1012 prev = p; 1013 } 1014 1015 return inside; 1016 } 1017 1018 public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { 1019 PathList p = parse_svg_data (d, g, svg_glyph, units); 1020 foreach (Path path in p.paths) { 1021 g.add_path (path); 1022 } 1023 } 1024 1025 /** 1026 * @param d svg data 1027 * @param glyph use lines from this glyph but don't add the generated paths 1028 * @param svg_glyph parse svg glyph with origo in lower left corner 1029 * 1030 * @return the new paths 1031 */ 1032 public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { 1033 Font font; 1034 PathList path_list = new PathList (); 1035 BezierPoints[] bezier_points; 1036 int points; 1037 1038 font = BirdFont.get_current_font (); 1039 SvgFile.get_bezier_points (d, out bezier_points, out points, svg_glyph); 1040 1041 if (points == 0) { 1042 warning ("No points in path."); 1043 return path_list; 1044 } 1045 1046 move_and_resize (bezier_points, points, svg_glyph, units, glyph); 1047 1048 if (format == SvgFormat.ILLUSTRATOR) { 1049 path_list = create_paths_illustrator (bezier_points, points); 1050 } else { 1051 path_list = create_paths_inkscape (bezier_points, points); 1052 } 1053 1054 // TODO: Find out if it is possible to tie handles. 1055 return path_list; 1056 } 1057 1058 void move_and_resize (BezierPoints[] b, int num_b, bool svg_glyph, double units, Glyph glyph) { 1059 Font font = BirdFont.get_current_font (); 1060 1061 for (int i = 0; i < num_b; i++) { 1062 // resize all points 1063 b[i].x0 *= units; 1064 b[i].y0 *= units; 1065 b[i].x1 *= units; 1066 b[i].y1 *= units; 1067 b[i].x2 *= units; 1068 b[i].y2 *= units; 1069 1070 // move all points 1071 if (svg_glyph) { 1072 b[i].x0 += glyph.left_limit; 1073 b[i].y0 += font.base_line; 1074 b[i].x1 += glyph.left_limit; 1075 b[i].y1 += font.base_line; 1076 b[i].x2 += glyph.left_limit; 1077 b[i].y2 += font.base_line; 1078 } else { 1079 b[i].x0 += glyph.left_limit; 1080 b[i].y0 += font.top_position; 1081 b[i].x1 += glyph.left_limit; 1082 b[i].y1 += font.top_position; 1083 b[i].x2 += glyph.left_limit; 1084 b[i].y2 += font.top_position; 1085 } 1086 } 1087 } 1088 1089 void find_last_handle (int start_index, BezierPoints[] b, int num_b, out double left_x, out double left_y, out PointType last_type) { 1090 BezierPoints last = new BezierPoints (); 1091 bool found = false; 1092 1093 left_x = 0; 1094 left_y = 0; 1095 last_type = PointType.NONE; 1096 1097 return_if_fail (b.length != 0); 1098 return_if_fail (b[0].type != 'z'); 1099 return_if_fail (num_b < b.length); 1100 1101 if (num_b == 2) { 1102 left_x = b[0].x0 + (b[1].x0 - b[0].x0) / 3.0; 1103 left_y = b[0].y0 + (b[1].y0 - b[0].y0) / 3.0; 1104 last_type = PointType.LINE_CUBIC; 1105 return; 1106 } 1107 1108 for (int i = start_index; i < num_b; i++) { 1109 switch (b[i].type) { 1110 case 'Q': 1111 break; 1112 case 'C': 1113 break; 1114 case 'z': 1115 found = true; 1116 break; 1117 default: 1118 break; 1119 } 1120 1121 if (found || i + 1 == num_b) { 1122 1123 return_if_fail (i >= 1); 1124 1125 if (b[i - 1].type == 'Q') { 1126 return_if_fail (i >= 1); 1127 left_x = b[i - 1].x0; 1128 left_y = b[i - 1].y0; 1129 last_type = PointType.QUADRATIC; 1130 } else if (b[i - 1].type == 'C') { 1131 return_if_fail (i >= 1); 1132 left_x = b[i - 1].x1; 1133 left_y = b[i - 1].y1; 1134 last_type = PointType.CUBIC; 1135 } else if (b[i - 1].type == 'S') { 1136 return_if_fail (i >= 1); 1137 left_x = b[i - 1].x1; 1138 left_y = b[i - 1].y1; 1139 last_type = PointType.CUBIC; 1140 } else if (b[i - 1].type == 'L' || last.type == 'M') { 1141 return_if_fail (i >= 2); // FIXME: -2 can be C or L 1142 left_x = b[i - 2].x0 + (b[i - 1].x0 - b[i - 2].x0) / 3.0; 1143 left_y = b[i - 2].y0 + (b[i - 1].y0 - b[i - 2].y0) / 3.0; 1144 last_type = PointType.LINE_CUBIC; 1145 } else { 1146 warning (@"Unexpected type. $(b[i - 1])\n"); 1147 } 1148 return; 1149 } 1150 1151 last = b[i]; 1152 } 1153 1154 warning ("Last point not found."); 1155 } 1156 1157 PathList create_paths_inkscape (BezierPoints[] b, int num_b) { 1158 double last_x; 1159 double last_y; 1160 PointType last_type; 1161 Path path; 1162 PathList path_list = new PathList (); 1163 EditPoint ep = new EditPoint (); 1164 Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); 1165 1166 path = new Path (); 1167 1168 if (num_b == 0) { 1169 warning ("No SVG data"); 1170 return path_list; 1171 } 1172 1173 if (b[0].type != 'M') { 1174 warning ("Path must begin with M or m."); 1175 return path_list; 1176 } 1177 1178 find_last_handle (0, b, num_b, out last_x, out last_y, out last_type); 1179 1180 for (int i = 0; i < num_b; i++) { 1181 if (b[i].type == '\0') { 1182 warning ("Parser error."); 1183 return path_list; 1184 } 1185 1186 if (b[i].type == 'z') { 1187 path.close (); 1188 path.create_list (); 1189 path.recalculate_linear_handles (); 1190 path_list.add (path); 1191 path = new Path (); 1192 1193 if (i + 1 >= num_b) { 1194 break; 1195 } else { 1196 find_last_handle (i + 1, b, num_b, out last_x, out last_y, out last_type); 1197 } 1198 } 1199 1200 if (i >= num_b) { 1201 break; 1202 } 1203 1204 if (b[i].type == 'M') { 1205 ep = path.add (b[i].x0, b[i].y0); 1206 ep.set_point_type (PointType.CUBIC); 1207 1208 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1209 1210 if (i == 0 || (b[i - 1].type == 'z')) { 1211 ep.get_left_handle ().set_point_type (last_type); 1212 ep.get_left_handle ().move_to_coordinate (last_x, last_y); 1213 } else { 1214 if (b[i - 1].type == 'C' || b[i - 1].type == 'S') { 1215 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1216 ep.get_left_handle ().move_to_coordinate (b[i + 1].x1, b[i + 1].y1); 1217 } 1218 1219 if (b[i + 1].type == 'C' || b[i - 1].type == 'S') { 1220 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1221 ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 1222 } else if (b[i + 1].type == 'L' || b[i + 1].type == 'M') { 1223 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1224 } 1225 } 1226 } 1227 1228 if (b[i].type == 'L') { 1229 return_val_if_fail (i != 0, path_list); 1230 1231 ep = path.add (b[i].x0, b[i].y0); 1232 ep.set_point_type (PointType.CUBIC); 1233 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1234 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1235 1236 if (b[i + 1].type == 'L' || b[i + 1].type == 'M' || b[i + 1].type == 'z') { 1237 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1238 } 1239 1240 if (b[i -1].type == 'L' || b[i - 1].type == 'M') { 1241 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1242 } 1243 } 1244 1245 if (b[i].type == 'Q') { 1246 return_val_if_fail (i != 0, path_list); 1247 1248 ep.set_point_type (PointType.QUADRATIC); 1249 1250 ep.get_right_handle ().set_point_type (PointType.QUADRATIC); 1251 ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); 1252 1253 if (b[i + 1].type != 'z') { 1254 ep = path.add (b[i].x1, b[i].y1); 1255 1256 ep.get_left_handle ().set_point_type (PointType.QUADRATIC); 1257 ep.get_left_handle ().move_to_coordinate (b[i].x0, b[i].y0); 1258 } 1259 } 1260 1261 if (b[i].type == 'C' || b[i].type == 'S') { 1262 return_val_if_fail (i != 0, path_list); 1263 1264 ep.set_point_type (PointType.CUBIC); 1265 1266 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1267 ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); 1268 1269 if (b[i].type == 'S') { 1270 smooth_points.add (ep); 1271 } 1272 1273 if (b[i + 1].type != 'z') { 1274 ep = path.add (b[i].x2, b[i].y2); 1275 1276 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1277 ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); 1278 } 1279 } 1280 } 1281 1282 foreach (EditPoint e in smooth_points) { 1283 e.set_point_type (PointType.LINE_DOUBLE_CURVE); 1284 e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1285 e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1286 } 1287 1288 foreach (EditPoint e in smooth_points) { 1289 path.recalculate_linear_handles_for_point (e); 1290 } 1291 1292 for (int i = 0; i < 3; i++) { 1293 foreach (EditPoint e in smooth_points) { 1294 e.set_tie_handle (true); 1295 e.process_tied_handle (); 1296 } 1297 } 1298 1299 if (path.points.size > 0) { 1300 path_list.add (path); 1301 } 1302 1303 foreach (Path p in path_list.paths) { 1304 p.remove_points_on_points (); 1305 } 1306 1307 return path_list; 1308 } 1309 1310 PathList create_paths_illustrator (BezierPoints[] b, int num_b) { 1311 Path path; 1312 PathList path_list = new PathList (); 1313 EditPoint ep; 1314 bool first_point = true; 1315 double first_left_x, first_left_y; 1316 Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); 1317 1318 if (num_b > b.length) { 1319 warning ("num_b > b.length: $num_b > $(b.length)"); 1320 return path_list; 1321 } 1322 1323 path = new Path (); 1324 1325 if (num_b <= 1) { 1326 warning ("No SVG data"); 1327 return path_list; 1328 } 1329 1330 first_left_x = 0; 1331 first_left_y = 0; 1332 1333 // FIXME: array boundaries 1334 for (int i = 0; i < num_b; i++) { 1335 if (b[i].type == '\0') { 1336 warning ("Parser error."); 1337 return path_list; 1338 } else if (b[i].type == 'z') { 1339 path.close (); 1340 path.create_list (); 1341 1342 int first_index = 1; 1343 1344 for (int j = i - 1; j >= 1; j--) { 1345 if (b[j].type == 'z') { 1346 first_index = j + 1; // from z to M 1347 } 1348 } 1349 1350 if (b[first_index].type == 'C' || b[first_index].type == 'S') { 1351 return_val_if_fail (path.points.size != 0, path_list); 1352 ep = path.points.get (path.points.size - 1); 1353 1354 if (b[i - 1].type != 'L' ) { 1355 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1356 ep.get_right_handle ().move_to_coordinate (b[first_index].x0, b[first_index].y0); 1357 } 1358 } else if (b[first_index].type == 'L') { 1359 return_val_if_fail (path.points.size != 0, path_list); 1360 ep = path.points.get (path.points.size - 1); 1361 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1362 path.recalculate_linear_handles_for_point (ep); 1363 } else { 1364 warning ("Unexpected type: %s", (!) b[first_index].type.to_string ()); 1365 } 1366 1367 path.recalculate_linear_handles (); 1368 path_list.add (path); 1369 1370 path = new Path (); 1371 first_point = true; 1372 } else if (b[i].type == 'M') { 1373 } else if (b[i].type == 'L') { 1374 1375 if (first_point) { 1376 first_left_x = b[i].x0; 1377 first_left_y = b[i].y0; 1378 } 1379 1380 ep = path.add (b[i].x0, b[i].y0); 1381 ep.set_point_type (PointType.LINE_CUBIC); // TODO: quadratic 1382 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); 1383 1384 if (b[i -1].type == 'L' || first_point) { 1385 // ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 1386 } 1387 1388 if (b[i + 1].type == 'C' || b[i + 1].type == 'S') { 1389 return_val_if_fail (i + 1 < num_b, path_list); 1390 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1391 ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 1392 } 1393 1394 first_point = false; 1395 } else if (b[i].type == 'Q') { 1396 warning ("Illustrator does not support quadratic control points."); 1397 warning (@"$(b[i])\n"); 1398 } else if (b[i].type == 'C' || b[i].type == 'S') { 1399 1400 if (first_point) { 1401 first_left_x = b[i].x0; 1402 first_left_y = b[i].y0; 1403 } 1404 1405 ep = path.add (b[i].x2, b[i].y2); 1406 ep.set_point_type (PointType.CUBIC); 1407 1408 ep.get_right_handle ().set_point_type (PointType.CUBIC); 1409 ep.get_left_handle ().set_point_type (PointType.CUBIC); 1410 1411 ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); 1412 1413 if (b[i].type == 'S') { 1414 smooth_points.add (ep); 1415 } 1416 1417 if (b[i + 1].type != 'z') { 1418 ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 1419 } else { 1420 ep.get_right_handle ().move_to_coordinate (first_left_x, first_left_y); 1421 } 1422 1423 first_point = false; 1424 } else { 1425 warning ("Unknown control point type."); 1426 warning (@"$(b[i])\n"); 1427 } 1428 } 1429 1430 foreach (EditPoint e in smooth_points) { 1431 e.set_point_type (PointType.LINE_DOUBLE_CURVE); 1432 e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1433 e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); 1434 } 1435 1436 foreach (EditPoint e in smooth_points) { 1437 path.recalculate_linear_handles_for_point (e); 1438 } 1439 1440 for (int i = 0; i < 3; i++) { 1441 foreach (EditPoint e in smooth_points) { 1442 e.set_tie_handle (true); 1443 e.process_tied_handle (); 1444 } 1445 } 1446 1447 if (path.points.size > 0) { 1448 path_list.add (path); 1449 } 1450 1451 foreach (Path p in path_list.paths) { 1452 p.remove_points_on_points (); 1453 } 1454 1455 return path_list; 1456 } 1457 1458 public static double parse_double (string? s) { 1459 if (unlikely (is_null (s))) { 1460 warning ("Got null instead of expected string."); 1461 return 0; 1462 } 1463 1464 if (unlikely (!is_point ((!) s))) { 1465 warning (@"Expecting a double got: $((!) s)"); 1466 return 0; 1467 } 1468 1469 return double.parse ((!) s); 1470 } 1471 1472 static bool is_point (string? s) { 1473 if (s == null) { 1474 warning ("s is null"); 1475 return false; 1476 } 1477 1478 return double.try_parse ((!) s); 1479 } 1480 1481 Path parse_poly_data (string polygon_points) { 1482 string data = SvgFile.add_separators (polygon_points); 1483 string[] c = data.split (" "); 1484 Path path; 1485 BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; 1486 int bi; 1487 Glyph g; 1488 EditPoint ep; 1489 1490 bi = 0; 1491 for (int i = 0; i < c.length - 1; i += 2) { 1492 if (i + 1 >= c.length) { 1493 warning ("No y value."); 1494 break; 1495 } 1496 1497 if (bi >= bezier_points.length) { 1498 warning ("End of bezier_points"); 1499 break; 1500 } 1501 1502 bezier_points[bi] = new BezierPoints (); 1503 bezier_points[bi].type = 'L'; 1504 bezier_points[bi].x0 = parse_double (c[i]); 1505 bezier_points[bi].y0 = -parse_double (c[i + 1]); 1506 bi++; 1507 } 1508 1509 g = MainWindow.get_current_glyph (); 1510 move_and_resize (bezier_points, bi, false, 1, g); 1511 1512 path = new Path (); 1513 for (int i = 0; i < bi; i++) { 1514 ep = path.add (bezier_points[i].x0, bezier_points[i].y0); 1515 ep.set_point_type (PointType.LINE_CUBIC); 1516 } 1517 1518 path.create_list (); 1519 path.recalculate_linear_handles (); 1520 1521 return path; 1522 } 1523 1524 public static EmbeddedSvg parse_embedded_svg_file (string path) { 1525 string xml_data; 1526 SvgFile svg_file = new SvgFile (); 1527 1528 try { 1529 FileUtils.get_contents (path, out xml_data); 1530 return parse_embedded_svg_data (xml_data); 1531 } catch (GLib.Error error) { 1532 warning (error.message); 1533 } 1534 1535 SvgDrawing drawing = new SvgDrawing (); 1536 return new EmbeddedSvg (drawing); 1537 } 1538 1539 public static EmbeddedSvg parse_embedded_svg_data (string xml_data) { 1540 XmlParser xmlparser = new XmlParser (xml_data); 1541 SvgDrawing drawing = new SvgDrawing (); 1542 SvgFile svg_file = new SvgFile (); 1543 1544 if (xmlparser.validate ()) { 1545 Tag root = xmlparser.get_root_tag (); 1546 drawing = svg_file.parse_svg_file (root); 1547 EmbeddedSvg svg = new EmbeddedSvg (drawing); 1548 svg.svg_data = xml_data; 1549 return svg; 1550 } else { 1551 warning ("Invalid xml file."); 1552 } 1553 1554 return new EmbeddedSvg (drawing); 1555 } 1556 1557 } 1558 1559 } 1560