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.
Transform paths in SVG to BF conversion
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 warn_if_test ("No format identifier found in SVG parser.\n"); 166 } 167 168 XmlTree xml_tree = new XmlTree (xml_data); 169 path_list = parser.parse_svg_file (xml_tree.get_root ()); 170 171 foreach (Path p in path_list.paths) { 172 PathObject path = new PathObject.for_path (p); 173 glyph.add_object (path); 174 glyph.add_active_object (path); // FIXME: groups 175 path.update_boundaries_for_object (); 176 } 177 178 glyph.close_path (); 179 180 return path_list; 181 } 182 183 public static string replace (string content, string start, string stop, string replacement) { 184 int i_tag = content.index_of (start); 185 int end_tag = content.index_of (stop, i_tag); 186 string c = ""; 187 188 if (i_tag > -1) { 189 c = content.substring (0, i_tag) 190 + replacement 191 + content.substring (end_tag + stop.length); 192 } else { 193 c = content; 194 } 195 196 return c; 197 } 198 199 public static void import_svg (string path) { 200 string svg_data; 201 try { 202 FileUtils.get_contents (path, out svg_data); 203 } catch (GLib.Error e) { 204 warning (e.message); 205 } 206 import_svg_data (svg_data); 207 } 208 209 private PathList parse_svg_file (XmlElement tag) { 210 Layer layer = new Layer (); 211 return parse_svg_tag (tag, layer); 212 } 213 214 private PathList parse_svg_tag (XmlElement tag, Layer pl) { 215 double width = 0; 216 double height = 0; 217 218 foreach (Attribute attribute in tag.get_attributes ()) { 219 if (attribute.get_name () == "width") { 220 width = parse_double (attribute.get_content ()); 221 } 222 223 if (attribute.get_name () == "height") { 224 height = parse_double (attribute.get_content ()); 225 } 226 } 227 228 foreach (XmlElement t in tag) { 229 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 273 ViewBox? box = SvgFile.parse_view_box (tag); 274 if (box != null) { 275 ViewBox view_box = (!) box; 276 Cairo.Matrix matrix = Cairo.Matrix.identity (); 277 Cairo.Matrix view_box_matrix = view_box.get_matrix (width, height); 278 view_box_matrix.multiply (view_box_matrix, matrix); 279 SvgTransform t = new SvgTransform.for_matrix (view_box_matrix); 280 SvgTransforms transforms = new SvgTransforms (); 281 transforms.add (t); 282 transform_paths (transforms, paths); 283 } 284 285 return paths; 286 } 287 288 private void parse_layer (XmlElement tag, Layer pl) { 289 Layer layer; 290 bool hidden = false; 291 292 foreach (Attribute attr in tag.get_attributes ()) { 293 if (attr.get_name () == "display" && attr.get_content () == "none") { 294 hidden = true; 295 } 296 297 if (attr.get_name () == "visibility" 298 && (attr.get_content () == "hidden" 299 || attr.get_content () == "collapse")) { 300 hidden = true; 301 } 302 } 303 304 if (hidden) { 305 return; 306 } 307 308 foreach (XmlElement t in tag) { 309 if (t.get_name () == "path") { 310 parse_path (t, pl); 311 } 312 313 if (t.get_name () == "g") { 314 layer = new Layer (); 315 parse_layer (t, layer); 316 pl.objects.add (layer); 317 } 318 319 if (t.get_name () == "svg") { 320 layer = new Layer (); 321 parse_svg_tag (t, layer); 322 pl.objects.add (layer); 323 } 324 325 if (t.get_name () == "polygon") { 326 parse_polygon (t, pl); 327 } 328 329 if (t.get_name () == "polyline") { 330 parse_polyline (t, pl); 331 } 332 333 if (t.get_name () == "rect") { 334 parse_rect (t, pl); 335 } 336 337 if (t.get_name () == "circle") { 338 parse_circle (t, pl); 339 } 340 341 if (t.get_name () == "ellipse") { 342 parse_ellipse (t, pl); 343 } 344 345 if (t.get_name () == "line") { 346 parse_line (t, pl); 347 } 348 } 349 350 PathList paths = new PathList (); 351 352 foreach (SvgBird.Object object in pl.objects) { 353 if (object is Path) { 354 paths.add ((Path) object); 355 } 356 } 357 358 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 359 transform_paths (transforms, paths); 360 } 361 362 private void transform_paths (SvgTransforms transforms, PathList pl) { 363 foreach (Path p in pl.paths) { 364 transform_path (transforms, p); 365 } 366 } 367 368 private void transform_path (SvgTransforms transforms, Path path) { 369 Font font = BirdFont.get_current_font (); 370 Glyph glyph = MainWindow.get_current_glyph (); 371 372 foreach (EditPoint ep in path.points) { 373 ep.tie_handles = false; 374 ep.reflective_point = false; 375 } 376 377 Matrix matrix = transforms.get_matrix (); 378 379 foreach (EditPoint ep in path.points) { 380 double x, y; 381 382 x = ep.independent_x - glyph.left_limit; 383 y = font.top_limit - ep.independent_y; 384 385 matrix.transform_point (ref x, ref y); 386 387 ep.independent_x = glyph.left_limit + x; 388 ep.independent_y = font.top_limit - y; 389 390 x = ep.get_right_handle ().x - glyph.left_limit; 391 y = font.top_limit - ep.get_right_handle ().y; 392 393 matrix.transform_point (ref x, ref y); 394 395 ep.get_right_handle ().x = glyph.left_limit + x; 396 ep.get_right_handle ().y = font.top_limit - y; 397 398 x = ep.get_left_handle ().x - glyph.left_limit; 399 y = font.top_limit - ep.get_left_handle ().y; 400 401 matrix.transform_point (ref x, ref y); 402 403 ep.get_left_handle ().x = glyph.left_limit + x; 404 ep.get_left_handle ().y = font.top_limit - y; 405 } 406 407 double stroke_x = path.stroke; 408 double stroke_y = path.stroke; 409 410 matrix.transform_distance (ref stroke_x, ref stroke_y); 411 412 path.stroke = stroke_x; 413 } 414 415 /** @param path a path in the cartesian coordinate system 416 * The other parameters are in the SVG coordinate system. 417 */ 418 public static void apply_matrix (Path path, double a, double b, double c, 419 double d, double e, double f){ 420 421 double dx, dy; 422 Font font = BirdFont.get_current_font (); 423 Glyph glyph = MainWindow.get_current_glyph (); 424 425 foreach (EditPoint ep in path.points) { 426 ep.tie_handles = false; 427 ep.reflective_point = false; 428 } 429 430 foreach (EditPoint ep in path.points) { 431 apply_matrix_on_handle (ep.get_right_handle (), a, b, c, d, e, f); 432 433 EditPointHandle left = ep.get_left_handle (); 434 if (left.type == PointType.QUADRATIC || left.type == PointType.LINE_QUADRATIC) { 435 ep.get_right_handle ().process_connected_handle (); 436 } else { 437 apply_matrix_on_handle (left, a, b, c, d, e, f); 438 } 439 440 ep.independent_y = font.top_limit - ep.independent_y; 441 ep.independent_x -= glyph.left_limit; 442 443 dx = a * ep.independent_x + c * ep.independent_y + e; 444 dy = b * ep.independent_x + d * ep.independent_y + f; 445 446 ep.independent_x = dx; 447 ep.independent_y = dy; 448 449 ep.independent_y = font.top_limit - ep.independent_y; 450 ep.independent_x += glyph.left_limit; 451 } 452 } 453 454 public static void apply_matrix_on_handle (EditPointHandle h, 455 double a, double b, double c, 456 double d, double e, double f){ 457 458 double dx, dy; 459 Font font = BirdFont.get_current_font (); 460 Glyph glyph = MainWindow.get_current_glyph (); 461 462 h.y = font.top_limit - h.y; 463 h.x -= glyph.left_limit; 464 465 dx = a * h.x + c * h.y + e; 466 dy = b * h.x + d * h.y + f; 467 468 h.x = dx; 469 h.y = dy; 470 471 h.y = font.top_limit - h.y; 472 h.x += glyph.left_limit; 473 } 474 475 476 private void matrix (string function, PathList pl) { 477 string parameters = get_transform_parameters (function); 478 string[] p = parameters.split (" "); 479 480 if (p.length != 6) { 481 warning ("Expecting six parameters for matrix transformation."); 482 return; 483 } 484 485 foreach (Path path in pl.paths) { 486 apply_matrix (path, parse_double (p[0]), parse_double (p[1]), 487 parse_double (p[2]), parse_double (p[3]), 488 parse_double (p[4]), parse_double (p[5])); 489 } 490 } 491 492 private void scale (string function, PathList pl) { 493 string parameters = get_transform_parameters (function); 494 string[] p = parameters.split (" "); 495 double x, y; 496 497 x = 1; 498 y = 1; 499 500 if (p.length > 0) { 501 x = parse_double (p[0]); 502 } 503 504 if (p.length > 1) { 505 y = parse_double (p[1]); 506 } 507 508 foreach (Path path in pl.paths) { 509 path.scale (-x, y); 510 } 511 } 512 513 private void translate (string function, PathList pl) { 514 string parameters = get_transform_parameters (function); 515 string[] p = parameters.split (" "); 516 double x, y; 517 518 x = 0; 519 y = 0; 520 521 if (p.length > 0) { 522 x = parse_double (p[0]); 523 } 524 525 if (p.length > 1) { 526 y = parse_double (p[1]); 527 } 528 529 foreach (Path path in pl.paths) { 530 path.move (x, -y); 531 } 532 } 533 534 private string get_transform_parameters (string function) { 535 int i; 536 string param = ""; 537 538 i = function.index_of ("("); 539 return_val_if_fail (i != -1, param); 540 param = function.substring (i); 541 542 param = param.replace ("(", ""); 543 param = param.replace ("\n", " "); 544 param = param.replace ("\t", " "); 545 param = param.replace (",", " "); 546 547 while (param.index_of (" ") > -1) { 548 param = param.replace (" ", " "); 549 } 550 551 return param.strip(); 552 } 553 554 private void parse_circle (XmlElement tag, Layer pl) { 555 Path p; 556 double x, y, r; 557 Glyph g; 558 PathList npl; 559 BezierPoints[] bezier_points; 560 SvgStyle style = new SvgStyle (); 561 bool hidden = false; 562 563 npl = new PathList (); 564 565 x = 0; 566 y = 0; 567 r = 0; 568 569 foreach (Attribute attr in tag.get_attributes ()) { 570 if (attr.get_name () == "cx") { 571 x = parse_double (attr.get_content ()); 572 } 573 574 if (attr.get_name () == "cy") { 575 y = -parse_double (attr.get_content ()); 576 } 577 578 if (attr.get_name () == "r") { 579 r = parse_double (attr.get_content ()); 580 } 581 582 if (attr.get_name () == "display" && attr.get_content () == "none") { 583 hidden = true; 584 } 585 } 586 587 style = SvgStyle.parse (null, style, tag, null); 588 589 if (hidden) { 590 return; 591 } 592 593 bezier_points = new BezierPoints[1]; 594 bezier_points[0] = new BezierPoints (); 595 bezier_points[0].type == 'L'; 596 bezier_points[0].x0 = x; 597 bezier_points[0].y0 = y; 598 599 g = MainWindow.get_current_glyph (); 600 move_and_resize (bezier_points, 1, false, 1, g); 601 602 p = CircleTool.create_circle (bezier_points[0].x0, 603 bezier_points[0].y0, r, PointType.CUBIC); 604 605 npl.add (p); 606 607 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 608 transform_paths (transforms, npl); 609 610 npl.apply_style (style); 611 append_paths (pl, npl); 612 } 613 614 private void parse_ellipse (XmlElement tag, Layer pl) { 615 Path p; 616 double x, y, rx, ry; 617 Glyph g; 618 PathList npl; 619 BezierPoints[] bezier_points; 620 SvgStyle style = new SvgStyle (); 621 bool hidden = false; 622 623 npl = new PathList (); 624 625 x = 0; 626 y = 0; 627 rx = 0; 628 ry = 0; 629 630 foreach (Attribute attr in tag.get_attributes ()) { 631 if (attr.get_name () == "cx") { 632 x = parse_double (attr.get_content ()); 633 } 634 635 if (attr.get_name () == "cy") { 636 y = -parse_double (attr.get_content ()); 637 } 638 639 if (attr.get_name () == "rx") { 640 rx = parse_double (attr.get_content ()); 641 } 642 643 if (attr.get_name () == "ry") { 644 ry = 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, style, tag, null); 653 654 if (hidden) { 655 return; 656 } 657 658 bezier_points = new BezierPoints[1]; 659 bezier_points[0] = new BezierPoints (); 660 bezier_points[0].type == 'L'; 661 bezier_points[0].x0 = x; 662 bezier_points[0].y0 = y; 663 664 g = MainWindow.get_current_glyph (); 665 move_and_resize (bezier_points, 1, false, 1, g); 666 667 p = CircleTool.create_ellipse (bezier_points[0].x0, 668 bezier_points[0].y0, rx, ry, PointType.CUBIC); 669 670 npl.add (p); 671 672 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 673 transform_paths (transforms, npl); 674 675 npl.apply_style (style); 676 append_paths (pl, npl); 677 } 678 679 private void parse_line (XmlElement tag, Layer pl) { 680 Path p; 681 double x1, y1, x2, y2; 682 BezierPoints[] bezier_points; 683 Glyph g; 684 PathList npl = new PathList (); 685 SvgStyle style = new SvgStyle (); 686 bool hidden = false; 687 688 x1 = 0; 689 y1 = 0; 690 x2 = 0; 691 y2 = 0; 692 693 foreach (Attribute attr in tag.get_attributes ()) { 694 if (attr.get_name () == "x1") { 695 x1 = parse_double (attr.get_content ()); 696 } 697 698 if (attr.get_name () == "y1") { 699 y1 = -parse_double (attr.get_content ()); 700 } 701 702 if (attr.get_name () == "x2") { 703 x2 = parse_double (attr.get_content ()); 704 } 705 706 if (attr.get_name () == "xy") { 707 y2 = -parse_double (attr.get_content ()); 708 } 709 710 if (attr.get_name () == "display" && attr.get_content () == "none") { 711 hidden = true; 712 } 713 } 714 715 style = SvgStyle.parse (null, style, tag, null); 716 717 if (hidden) { 718 return; 719 } 720 721 bezier_points = new BezierPoints[2]; 722 bezier_points[0] = new BezierPoints (); 723 bezier_points[0].type == 'L'; 724 bezier_points[0].x0 = x1; 725 bezier_points[0].y0 = y1; 726 727 bezier_points[1] = new BezierPoints (); 728 bezier_points[1].type == 'L'; 729 bezier_points[1].x0 = x2; 730 bezier_points[1].y0 = y2; 731 732 g = MainWindow.get_current_glyph (); 733 move_and_resize (bezier_points, 2, false, 1, g); 734 735 p = new Path (); 736 737 p.add (bezier_points[0].x0, bezier_points[0].y0); 738 p.add (bezier_points[1].x0, bezier_points[1].y0); 739 740 p.close (); 741 p.create_list (); 742 p.recalculate_linear_handles (); 743 744 npl.add (p); 745 746 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 747 transform_paths (transforms, npl); 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 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 845 transform_paths (transforms, npl); 846 847 npl.apply_style (style); 848 append_paths (layer, npl); 849 } 850 851 private void parse_polygon (XmlElement tag, Layer layer) { 852 PathList path_list = get_polyline (tag); 853 854 foreach (Path p in path_list.paths) { 855 p.close (); 856 } 857 858 append_paths (layer, path_list); 859 } 860 861 static void append_paths (Layer layer, PathList pl) { 862 LayerUtils.append_paths (layer, pl); 863 } 864 865 private void parse_polyline (XmlElement tag, Layer layer) { 866 append_paths (layer, get_polyline (tag)); 867 } 868 869 private PathList get_polyline (XmlElement tag) { 870 Path p = new Path (); 871 bool hidden = false; 872 PathList path_list = new PathList (); 873 SvgStyle style = new SvgStyle (); 874 875 foreach (Attribute attr in tag.get_attributes ()) { 876 if (attr.get_name () == "points") { 877 p = parse_poly_data (attr.get_content ()); 878 } 879 880 if (attr.get_name () == "display" && attr.get_content () == "none") { 881 hidden = true; 882 } 883 } 884 885 style = SvgStyle.parse (null, style, tag, null); 886 887 if (hidden) { 888 return path_list; 889 } 890 891 path_list.add (p); 892 path_list.apply_style (style); 893 894 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 895 transform_paths (transforms, path_list); 896 897 return path_list; 898 } 899 900 private void parse_path (XmlElement tag, Layer layer) { 901 Glyph glyph = MainWindow.get_current_glyph (); 902 PathList path_list = new PathList (); 903 SvgStyle style = new SvgStyle (); 904 bool hidden = false; 905 906 foreach (Attribute attr in tag.get_attributes ()) { 907 if (attr.get_name () == "d") { 908 path_list = parse_svg_data (attr.get_content (), glyph); 909 } 910 911 if (attr.get_name () == "display" && attr.get_content () == "none") { 912 hidden = true; 913 } 914 915 if (attr.get_name () == "visibility" 916 && (attr.get_content () == "hidden" 917 || attr.get_content () == "collapse")) { 918 hidden = true; 919 } 920 } 921 922 style = SvgStyle.parse (null, style, tag, null); 923 924 if (hidden) { 925 return; 926 } 927 928 foreach (Path path in path_list.paths) { 929 LayerUtils.add_path (layer, path); 930 } 931 932 path_list.apply_style (style); 933 934 // assume the even odd rule is applied and convert the path 935 // to a path using the non-zero rule 936 int inside_count; 937 bool inside; 938 foreach (SvgBird.Object o1 in layer.objects) { 939 if (o1 is PathObject) { 940 Path p1 = ((PathObject) o1).get_path (); 941 inside_count = 0; 942 943 foreach (SvgBird.Object o2 in layer.objects) { 944 if (o2 is PathObject) { 945 Path p2 = ((PathObject) o2).get_path (); 946 947 if (p1 != p2) { 948 inside = true; 949 950 foreach (EditPoint ep in p1.points) { 951 if (!is_inside (ep, p2)) { 952 inside = false; 953 } 954 } 955 956 if (inside) { 957 inside_count++; 958 } 959 } 960 } 961 } 962 963 if (inside_count % 2 == 0) { 964 p1.force_direction (Direction.CLOCKWISE); 965 } else { 966 p1.force_direction (Direction.COUNTER_CLOCKWISE); 967 } 968 } 969 } 970 971 SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); 972 transform_paths (transforms, path_list); 973 } 974 975 public static void create_lines_for_segment (Path path, EditPoint start, EditPoint end, double tolerance) { 976 double x1, x2, x3; 977 double y1, y2, y3; 978 double step_start, step, step_end; 979 980 path.add (start.x, start.y); 981 982 step_start = 0; 983 step = 0.5; 984 step_end = 1; 985 986 while (true) { 987 Path.get_point_for_step (start, end, step_start, out x1, out y1); 988 Path.get_point_for_step (start, end, step, out x2, out y2); 989 Path.get_point_for_step (start, end, step_end, out x3, out y3); 990 991 if (!StrokeTool.is_flat (x1, y1, x2, y2, x3, y3, tolerance) 992 && step_end - step / 2.0 > step_start 993 && step_end - step / 2.0 > 0.1 994 && step > 0.05 995 && Path.distance_to_point (start, end) > 1) { 996 997 step /= 2.0; 998 999 if (step < 0.05) { 1000 step = 0.05; 1001 } else { 1002 step_end = step_start + 2 * step; 1003 } 1004 } else { 1005 path.add (x3, y3); 1006 1007 if (step_end + step < 1) { 1008 step_start = step_end; 1009 step_end += step; 1010 } else { 1011 break; 1012 } 1013 } 1014 } 1015 } 1016 1017 public static Path get_lines (Path p) { 1018 EditPoint start; 1019 Path path = new Path (); 1020 1021 if (p.points.size == 0) { 1022 return path; 1023 } 1024 1025 // create a set of straight lines 1026 start = p.points.get (p.points.size - 1); 1027 1028 foreach (EditPoint end in p.points) { 1029 create_lines_for_segment (path, start, end, 1); 1030 start = end; 1031 } 1032 1033 return path; 1034 } 1035 1036 /** Check if a point is inside using the even odd fill rule. 1037 * The path should only have straight lines. 1038 */ 1039 public static bool is_inside (EditPoint point, Path path) { 1040 EditPoint prev; 1041 bool inside = false; 1042 1043 if (path.points.size <= 1) { 1044 return false; 1045 } 1046 1047 if (!(path.xmin <= point.x <= path.xmax)) { 1048 return false; 1049 } 1050 1051 if (!(path.ymin <= point.y <= path.ymax)) { 1052 return false; 1053 } 1054 1055 prev = path.points.get (path.points.size - 1); 1056 1057 foreach (EditPoint p in path.points) { 1058 if ((p.y > point.y) != (prev.y > point.y) 1059 && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { 1060 inside = !inside; 1061 } 1062 1063 prev = p; 1064 } 1065 1066 return inside; 1067 } 1068 1069 public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { 1070 PathList p = parse_svg_data (d, g, svg_glyph, units); 1071 foreach (Path path in p.paths) { 1072 g.add_path (path); 1073 } 1074 } 1075 1076 public void get_bezier_points (string svg_data, out BezierPoints[] instructions, out int points, bool svg_glyph) { 1077 SvgFile.get_bezier_points (svg_data, out instructions, out points, svg_glyph); 1078 Gee.ArrayList<BezierPoints> bezier_points = new Gee.ArrayList<BezierPoints> (); 1079 BezierPoints[] arc_data = new BezierPoints[8]; 1080 1081 for (int i = 0; i < points; i++) { 1082 if (instructions[i].type == 'A') { 1083 int arc_index = 0; 1084 1085 add_arc_points (arc_data, ref arc_index, 1086 instructions[i].x0, instructions[i].y0, 1087 instructions[i].rx, instructions[i].ry, 1088 instructions[i].rotation, 1089 instructions[i].large_arc, 1090 instructions[i].sweep, 1091 instructions[i].x1, instructions[i].y1); 1092 1093 for (int j = 0; j < arc_index; j++) { 1094 bezier_points.add (instructions[j]); 1095 } 1096 } 1097 1098 bezier_points.add (instructions[i]); 1099 } 1100 1101 instructions = new BezierPoints[bezier_points.size]; 1102 for (int i = 0; i < bezier_points.size; i++) { 1103 instructions[i] = bezier_points.get (i); 1104 } 1105 } 1106 /** 1107 * @param d svg data 1108 * @param glyph use lines from this glyph but don't add the generated paths 1109 * @param svg_glyph parse svg glyph with origo in lower left corner 1110 * 1111 * @return the new paths 1112 */ 1113 public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { 1114 Font font; 1115 PathList path_list = new PathList (); 1116 BezierPoints[] bezier_points; 1117 int points; 1118 1119 font = BirdFont.get_current_font (); 1120 SvgFile.get_bezier_points (d, out bezier_points, out points, svg_glyph); 1121 1122 if (points == 0) { 1123 warning ("No points in path."); 1124 return path_list; 1125 } 1126 1127 move_and_resize (bezier_points, points, svg_glyph, units, glyph); 1128 path_list = create_svg_paths (d); 1129 1130 // TODO: Find out if it is possible to tie handles. 1131 return path_list; 1132 } 1133 1134 public PathList create_svg_paths (string path_data) { 1135 Gee.ArrayList<Points> points_set = SvgFile.parse_points (path_data, format); 1136 PathList path_list = new PathList (); 1137 1138 foreach (Points p in points_set) { 1139 Path path = new Path (); 1140 1141 PointValue* points = p.point_data.data; 1142 EditPoint next = new EditPoint (); 1143 EditPointHandle handle; 1144 1145 if (p.size == 0) { 1146 warning ("No points in path."); 1147 return path_list; 1148 } 1149 1150 if (p.size % 8 != 0) { 1151 warning ("Points not padded."); 1152 return path_list; 1153 } 1154 1155 for (int i = 0; i < p.size; i += 8) { 1156 1157 switch (points[i].type) { 1158 case POINT_ARC: 1159 // FIXME: 1160 /* 1161 draw_arc (cr, , points[i + 2].value, 1162 points[i + 3].value, points[i + 4].value, 1163 points[i + 5].value, points[i + 6].value, 1164 points[i + 7].value); 1165 */ 1166 break; 1167 case POINT_CUBIC: 1168 handle = next.get_right_handle (); 1169 handle.x = points[i + 1].value; 1170 handle.y = -points[i + 2].value; 1171 handle.type = PointType.CUBIC; 1172 1173 next = new EditPoint (points[i + 5].value, -points[i + 6].value, PointType.CUBIC); 1174 path.add_point (next); 1175 1176 handle = next.get_left_handle (); 1177 handle.x = points[i + 3].value; 1178 handle.y = -points[i + 4].value; 1179 handle.type = PointType.CUBIC; 1180 1181 break; 1182 case POINT_LINE: 1183 handle = next.get_right_handle (); 1184 handle.type = PointType.LINE_CUBIC; 1185 1186 next = new EditPoint (points[i + 1].value, -points[i + 2].value, PointType.CUBIC); 1187 path.add_point (next); 1188 1189 handle = next.get_left_handle (); 1190 handle.type = PointType.LINE_CUBIC; 1191 1192 break; 1193 } 1194 } 1195 1196 if (p.closed) { 1197 path.close (); 1198 } 1199 1200 Font font = BirdFont.get_current_font (); 1201 Glyph glyph = MainWindow.get_current_glyph (); 1202 1203 foreach (EditPoint e in path.points) { 1204 e.independent_x += glyph.left_limit; 1205 e.independent_y += font.top_limit; 1206 e.get_right_handle ().independent_x += glyph.left_limit; 1207 e.get_right_handle ().independent_y += font.top_limit; 1208 e.get_left_handle ().independent_x += glyph.left_limit; 1209 e.get_left_handle ().independent_y += font.top_limit; 1210 } 1211 1212 path.recalculate_linear_handles (); 1213 1214 path.remove_points_on_points (); 1215 path_list.add (path); 1216 } 1217 1218 return path_list; 1219 } 1220 1221 void move_and_resize (BezierPoints[] b, int num_b, bool svg_glyph, double units, Glyph glyph) { 1222 Font font = BirdFont.get_current_font (); 1223 1224 for (int i = 0; i < num_b; i++) { 1225 // resize all points 1226 b[i].x0 *= units; 1227 b[i].y0 *= units; 1228 b[i].x1 *= units; 1229 b[i].y1 *= units; 1230 b[i].x2 *= units; 1231 b[i].y2 *= units; 1232 1233 // move all points 1234 if (svg_glyph) { 1235 b[i].x0 += glyph.left_limit; 1236 b[i].y0 += font.base_line; 1237 b[i].x1 += glyph.left_limit; 1238 b[i].y1 += font.base_line; 1239 b[i].x2 += glyph.left_limit; 1240 b[i].y2 += font.base_line; 1241 } else { 1242 b[i].x0 += glyph.left_limit; 1243 b[i].y0 += font.top_limit; 1244 b[i].x1 += glyph.left_limit; 1245 b[i].y1 += font.top_limit; 1246 b[i].x2 += glyph.left_limit; 1247 b[i].y2 += font.top_limit; 1248 } 1249 } 1250 } 1251 1252 public static double parse_double (string? s) { 1253 if (unlikely (is_null (s))) { 1254 warning ("Got null instead of expected string."); 1255 return 0; 1256 } 1257 1258 if (unlikely (!is_point ((!) s))) { 1259 warning (@"Expecting a double got: $((!) s)"); 1260 return 0; 1261 } 1262 1263 string d = (!) s; 1264 d = d.replace ("px", ""); 1265 1266 return double.parse (d); 1267 } 1268 1269 static bool is_point (string? s) { 1270 if (s == null) { 1271 warning ("s is null"); 1272 return false; 1273 } 1274 1275 return double.try_parse ((!) s); 1276 } 1277 1278 Path parse_poly_data (string polygon_points) { 1279 string data = SvgFile.add_separators (polygon_points); 1280 string[] c = data.split (" "); 1281 Path path; 1282 BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; 1283 int bi; 1284 Glyph g; 1285 EditPoint ep; 1286 1287 bi = 0; 1288 for (int i = 0; i < c.length - 1; i += 2) { 1289 if (i + 1 >= c.length) { 1290 warning ("No y value."); 1291 break; 1292 } 1293 1294 if (bi >= bezier_points.length) { 1295 warning ("End of bezier_points"); 1296 break; 1297 } 1298 1299 bezier_points[bi] = new BezierPoints (); 1300 bezier_points[bi].type = 'L'; 1301 bezier_points[bi].x0 = parse_double (c[i]); 1302 bezier_points[bi].y0 = -parse_double (c[i + 1]); 1303 bi++; 1304 } 1305 1306 g = MainWindow.get_current_glyph (); 1307 move_and_resize (bezier_points, bi, false, 1, g); 1308 1309 path = new Path (); 1310 for (int i = 0; i < bi; i++) { 1311 ep = path.add (bezier_points[i].x0, bezier_points[i].y0); 1312 ep.set_point_type (PointType.LINE_CUBIC); 1313 } 1314 1315 path.create_list (); 1316 path.recalculate_linear_handles (); 1317 1318 return path; 1319 } 1320 1321 public static EmbeddedSvg parse_embedded_svg_file (string path) { 1322 string xml_data; 1323 1324 try { 1325 FileUtils.get_contents (path, out xml_data); 1326 return parse_embedded_svg_data (xml_data); 1327 } catch (GLib.Error error) { 1328 warning (error.message); 1329 } 1330 1331 SvgDrawing drawing = new SvgDrawing (); 1332 return new EmbeddedSvg (drawing); 1333 } 1334 1335 public static EmbeddedSvg parse_embedded_svg_data (string xml_data) { 1336 SvgDrawing drawing = new SvgDrawing (); 1337 SvgFile svg_file = new SvgFile (); 1338 drawing = svg_file.parse_svg_data (xml_data); 1339 EmbeddedSvg svg = new EmbeddedSvg (drawing); 1340 svg.svg_data = xml_data; 1341 return svg; 1342 } 1343 1344 /** Convert an SVG arc instruction to a Beziér path. */ 1345 public static void add_arc_points (BezierPoints[] bezier_points, ref int bi, 1346 double x0, double y0, double rx, double ry, double angle, 1347 bool largeArcFlag, bool sweepFlag, double x, double y) { 1348 1349 double angleStart, angleExtent; 1350 double s, step, theta; 1351 double cx, cy; 1352 1353 cx = 0; 1354 cy = 0; 1355 1356 // Approximate the path with Beziér points 1357 SvgBird.get_arc_arguments (x0, y0, rx, ry, angle, largeArcFlag, sweepFlag, x, y, 1358 out angleStart, out angleExtent, out cx, out cx); 1359 1360 s = (angleExtent > 0) ? 1 : -1; 1361 step = fabs (angleExtent) / (2 * fabs (angleExtent)); 1362 1363 theta = PI - angleStart - angleExtent; 1364 1365 bezier_points[bi].type = 'C'; 1366 bezier_points[bi].svg_type = 'a'; 1367 1368 bezier_points[bi].x0 = cx + rx * cos (theta); 1369 bezier_points[bi].y0 = cy + ry * sin (theta); 1370 1371 bi++; 1372 1373 for (double a = 0; a < fabs (angleExtent); a += step) { 1374 theta = PI - angleStart - angleExtent + s * a; 1375 1376 return_if_fail (0 <= bi < bezier_points.length); 1377 1378 bezier_points[bi].type = 'S'; 1379 bezier_points[bi].svg_type = 'a'; 1380 1381 bezier_points[bi].x0 = cx + rx * cos (theta); 1382 bezier_points[bi].y0 = cy + ry * sin (theta); 1383 1384 bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); 1385 bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); 1386 1387 bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); 1388 bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); 1389 1390 bi++; 1391 } 1392 } 1393 } 1394 1395 } 1396