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