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