The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

SvgFile.vala in libbirdfont/Svg

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/Svg/SvgFile.vala.
Move the SVG parser and the SVG rendering code to libsvgbird
1 /* 2 Copyright (C) 2016 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using B; 16 using Cairo; 17 18 namespace BirdFont { 19 20 public class SvgFile : GLib.Object { 21 22 SvgDrawing drawing; 23 24 public SvgFile () { 25 } 26 27 public EmbeddedSvg parse (string path) { 28 string xml_data; 29 30 try { 31 FileUtils.get_contents (path, out xml_data); 32 return parse_data (xml_data); 33 } catch (GLib.Error error) { 34 warning (error.message); 35 } 36 37 SvgDrawing drawing = new SvgDrawing (); 38 return new EmbeddedSvg (drawing); 39 } 40 41 public EmbeddedSvg parse_data (string xml_data) { 42 XmlParser xmlparser = new XmlParser (xml_data); 43 SvgDrawing drawing = new SvgDrawing (); 44 45 if (xmlparser.validate ()) { 46 Tag root = xmlparser.get_root_tag (); 47 drawing = parse_svg_file (root); 48 EmbeddedSvg svg = new EmbeddedSvg (drawing); 49 svg.svg_data = xml_data; 50 return svg; 51 } else { 52 warning ("Invalid xml file."); 53 } 54 55 return new EmbeddedSvg (drawing); 56 } 57 58 private SvgDrawing parse_svg_file (Tag tag) { 59 drawing = new SvgDrawing (); 60 61 foreach (Attribute attr in tag.get_attributes ()) { 62 if (attr.get_name () == "width") { 63 drawing.width = parse_number (attr.get_content ()); 64 } 65 66 if (attr.get_name () == "height") { 67 drawing.height = parse_number (attr.get_content ()); 68 } 69 } 70 71 foreach (Tag t in tag) { 72 string name = t.get_name (); 73 74 if (name == "g") { 75 parse_layer (drawing.root_layer, t); 76 } 77 78 if (name == "defs") { 79 parse_defs (drawing, t); 80 } 81 82 parse_object (drawing.root_layer, t); 83 } 84 85 return drawing; 86 } 87 88 private void parse_layer (Layer layer, Tag tag) { 89 bool hidden = false; 90 91 foreach (Attribute attr in tag.get_attributes ()) { 92 if (attr.get_name () == "display" && attr.get_content () == "none") { 93 hidden = true; 94 } 95 96 if (attr.get_name () == "visibility" 97 && (attr.get_content () == "hidden" 98 || attr.get_content () == "collapse")) { 99 hidden = true; 100 } 101 } 102 103 if (hidden) { 104 layer.visible = !hidden; 105 } 106 107 foreach (Tag t in tag) { 108 string name = t.get_name (); 109 110 if (name == "g") { 111 Layer sublayer = new Layer (); 112 parse_layer (layer, t); 113 layer.subgroups.add (sublayer); 114 } 115 116 parse_object (layer, t); 117 } 118 119 foreach (Attribute attr in tag.get_attributes ()) { 120 if (attr.get_name () == "transform") { 121 layer.transforms = parse_transform (attr.get_content ()); 122 } 123 } 124 } 125 126 void parse_defs (SvgDrawing drawing, Tag tag) { 127 foreach (Tag t in tag) { 128 // FIXME: radial 129 string name = t.get_name (); 130 131 if (name == "linearGradient") { 132 parse_linear_gradient (drawing, t); 133 } 134 } 135 136 foreach (Gradient gradient in drawing.defs.gradients) { 137 if (gradient.href != null) { 138 Gradient? referenced; 139 referenced = drawing.defs.get_gradient_for_id ((!) gradient.href); 140 141 if (referenced != null) { 142 gradient.copy_stops ((!) referenced); 143 } 144 145 gradient.href = null; 146 } 147 } 148 } 149 150 void parse_linear_gradient (SvgDrawing drawing, Tag tag) { 151 Gradient gradient = new Gradient (); 152 153 drawing.defs.add (gradient); 154 155 foreach (Attribute attr in tag.get_attributes ()) { 156 string name = attr.get_name (); 157 158 // FIXME: gradientUnits 159 160 if (name == "gradientTransform") { 161 gradient.transforms = parse_transform (attr.get_content ()); 162 } 163 164 if (name == "href") { 165 gradient.href = attr.get_content (); 166 } 167 168 if (name == "x1") { 169 gradient.x1 = parse_number (attr.get_content ()); 170 } 171 172 if (name == "y1") { 173 gradient.y1 = parse_number (attr.get_content ()); 174 } 175 176 if (name == "x2") { 177 gradient.x2 = parse_number (attr.get_content ()); 178 } 179 180 if (name == "y2") { 181 gradient.y2 = parse_number (attr.get_content ()); 182 } 183 184 if (name == "id") { 185 gradient.id = attr.get_content (); 186 } 187 } 188 189 foreach (Tag t in tag) { 190 // FIXME: radial 191 string name = t.get_name (); 192 193 if (name == "stop") { 194 parse_stop (gradient, t); 195 } 196 } 197 } 198 199 void parse_stop (Gradient gradient, Tag tag) { 200 SvgStyle style = SvgStyle.parse (drawing.defs, tag.get_attributes ()); 201 Stop stop = new Stop (); 202 203 gradient.stops.add (stop); 204 205 foreach (Attribute attr in tag.get_attributes ()) { 206 string name = attr.get_name (); 207 208 if (name == "offset") { 209 string stop_offset = attr.get_content (); 210 211 if (stop_offset.index_of ("%") > -1) { 212 stop_offset = stop_offset.replace ("%", ""); 213 stop.offset = parse_number (stop_offset) / 100.0; 214 } else { 215 stop.offset = parse_number (stop_offset); 216 } 217 } 218 } 219 220 string? stop_color = style.style.get ("stop-color"); 221 string? stop_opacity = style.style.get ("stop-opacity"); 222 Color? color = Color.black (); 223 224 if (stop_color != null) { 225 color = Color.parse (stop_color); 226 227 if (color != null) { 228 stop.color = (!) color; 229 } 230 } 231 232 if (stop_opacity != null && color != null) { 233 ((!) color).a = parse_number (stop_opacity); 234 } 235 } 236 237 void parse_object (Layer layer, Tag tag) { 238 string name = tag.get_name (); 239 240 if (name == "path") { 241 parse_path (layer, tag); 242 } 243 244 if (name == "polygon") { 245 parse_polygon (layer, tag); 246 } 247 248 if (name == "polyline") { 249 parse_polyline (layer, tag); 250 } 251 252 if (name == "rect") { 253 parse_rect (layer, tag); 254 } 255 256 if (name == "circle") { 257 parse_circle (layer, tag); 258 } 259 260 if (name == "ellipse") { 261 parse_ellipse (layer, tag); 262 } 263 264 if (name == "line") { 265 parse_line (layer, tag); 266 } 267 } 268 269 private void parse_polygon (Layer layer, Tag tag) { 270 } 271 272 private void parse_polyline (Layer layer, Tag tag) { 273 } 274 275 private void parse_rect (Layer layer, Tag tag) { 276 Rectangle rectangle = new Rectangle (); 277 278 foreach (Attribute attr in tag.get_attributes ()) { 279 string attribute = attr.get_name (); 280 281 if (attribute == "x") { 282 rectangle.x = parse_number (attr.get_content ()); 283 } 284 285 if (attribute == "y") { 286 rectangle.y = parse_number (attr.get_content ()); 287 } 288 289 if (attribute == "width") { 290 rectangle.width = parse_number (attr.get_content ()); 291 } 292 293 if (attribute == "height") { 294 rectangle.height = parse_number (attr.get_content ()); 295 } 296 297 if (attribute == "rx") { 298 rectangle.rx = parse_number (attr.get_content ()); 299 } 300 301 if (attribute == "ry") { 302 rectangle.ry = parse_number (attr.get_content ()); 303 } 304 } 305 306 rectangle.transforms = get_transform (tag.get_attributes ()); 307 rectangle.style = SvgStyle.parse (drawing.defs,tag.get_attributes ()); 308 rectangle.visible = is_visible (tag); 309 310 layer.add_object (rectangle); 311 } 312 313 private void parse_circle (Layer layer, Tag tag) { 314 } 315 316 private void parse_ellipse (Layer layer, Tag tag) { 317 } 318 319 private void parse_line (Layer layer, Tag tag) { 320 } 321 322 // FIXME: reverse order? 323 public SvgTransforms parse_transform (string transforms) { 324 string[] functions; 325 string transform = transforms; 326 SvgTransforms transform_functions; 327 328 transform_functions = new SvgTransforms (); 329 330 transform = transform.replace ("\t", " "); 331 transform = transform.replace ("\n", " "); 332 transform = transform.replace ("\r", " "); 333 334 // use only a single space as separator 335 while (transform.index_of (" ") > -1) { 336 transform = transform.replace (" ", " "); 337 } 338 339 if (unlikely (transform.index_of (")") == -1)) { 340 warning ("No parenthesis in transform function."); 341 return transform_functions; 342 } 343 344 // add separator 345 transform = transform.replace (") ", "|"); 346 transform = transform.replace (")", "|"); 347 functions = transform.split ("|"); 348 349 for (int i = 0; i < functions.length; i++) { 350 if (functions[i].has_prefix ("translate")) { 351 transform_functions.add (translate (functions[i])); 352 } 353 354 if (functions[i].has_prefix ("scale")) { 355 transform_functions.add (scale (functions[i])); 356 } 357 358 if (functions[i].has_prefix ("matrix")) { 359 transform_functions.add (matrix (functions[i])); 360 } 361 362 // TODO: rotate etc. 363 } 364 365 return transform_functions; 366 } 367 368 private SvgTransform matrix (string function) { 369 string parameters = get_transform_parameters (function); 370 string[] p = parameters.split (" "); 371 SvgTransform transform = new SvgTransform (); 372 transform.type = TransformType.MATRIX; 373 374 if (unlikely (p.length != 6)) { 375 warning ("Expecting six parameters for matrix transformation."); 376 return transform; 377 } 378 379 for (int i = 0; i < 6; i++) { 380 double argument = SvgParser.parse_double (p[i]); 381 transform.arguments.add (argument); 382 } 383 384 return transform; 385 } 386 387 private static string remove_unit (string d) { 388 string s = d.replace ("pt", ""); 389 s = s.replace ("pc", ""); 390 s = s.replace ("mm", ""); 391 s = s.replace ("cm", ""); 392 s = s.replace ("in", ""); 393 s = s.replace ("px", ""); 394 return s; 395 } 396 397 public static double parse_number (string? number_with_unit) { 398 if (number_with_unit == null) { 399 return 0; 400 } 401 402 string d = (!) number_with_unit; 403 string s = remove_unit (d); 404 double n = SvgParser.parse_double (s); 405 406 if (d.has_suffix ("pt")) { 407 n *= 1.25; 408 } else if (d.has_suffix ("pc")) { 409 n *= 15; 410 } else if (d.has_suffix ("mm")) { 411 n *= 3.543307; 412 } else if (d.has_suffix ("cm")) { 413 n *= 35.43307; 414 } else if (d.has_suffix ("in")) { 415 n *= 90; 416 } 417 418 return n; 419 } 420 421 private SvgTransform scale (string function) { 422 string parameters = get_transform_parameters (function); 423 string[] p = parameters.split (" "); 424 SvgTransform transform = new SvgTransform (); 425 transform.type = TransformType.SCALE; 426 427 if (p.length > 0) { 428 transform.arguments.add (SvgParser.parse_double (p[0])); 429 } 430 431 if (p.length > 1) { 432 transform.arguments.add (SvgParser.parse_double (p[1])); 433 } 434 435 return transform; 436 } 437 438 private SvgTransform translate (string function) { 439 string parameters = get_transform_parameters (function); 440 string[] p = parameters.split (" "); 441 SvgTransform transform = new SvgTransform (); 442 transform.type = TransformType.TRANSLATE; 443 444 if (p.length > 0) { 445 transform.arguments.add (SvgParser.parse_double (p[0])); 446 } 447 448 if (p.length > 1) { 449 transform.arguments.add (SvgParser.parse_double (p[1])); 450 } 451 452 return transform; 453 } 454 455 private string get_transform_parameters (string function) { 456 int i; 457 string param = ""; 458 459 i = function.index_of ("("); 460 return_val_if_fail (i != -1, param); 461 param = function.substring (i); 462 463 param = param.replace ("(", ""); 464 param = param.replace ("\n", " "); 465 param = param.replace ("\t", " "); 466 param = param.replace (",", " "); 467 468 while (param.index_of (" ") > -1) { 469 param.replace (" ", " "); 470 } 471 472 return param.strip(); 473 } 474 475 private bool is_visible (Tag tag) { 476 bool hidden = false; 477 478 foreach (Attribute attr in tag.get_attributes ()) { 479 if (attr.get_name () == "display" && attr.get_content () == "none") { 480 hidden = true; 481 } 482 483 if (attr.get_name () == "visibility" 484 && (attr.get_content () == "hidden" 485 || attr.get_content () == "collapse")) { 486 hidden = true; 487 } 488 } 489 490 return !hidden; 491 } 492 493 private SvgTransforms get_transform (Attributes attributes) { 494 foreach (Attribute attr in attributes) { 495 if (attr.get_name () == "transform") { 496 return parse_transform (attr.get_content ()); 497 } 498 } 499 500 return new SvgTransforms (); 501 } 502 503 private void parse_path (Layer layer, Tag tag) { 504 SvgPath path = new SvgPath (); 505 506 foreach (Attribute attr in tag.get_attributes ()) { 507 if (attr.get_name () == "d") { 508 path.points = parse_points (attr.get_content ()); 509 } 510 } 511 512 path.transforms = get_transform (tag.get_attributes ()); 513 path.style = SvgStyle.parse (drawing.defs, tag.get_attributes ()); 514 path.visible = is_visible (tag); 515 516 layer.add_object (path); 517 } 518 519 public Gee.ArrayList<Points> parse_points (string data) { 520 Gee.ArrayList<Points> path_data = new Gee.ArrayList<Points> (); 521 Points points = new Points (); 522 BezierPoints[] bezier_points; 523 int points_size; 524 525 SvgParser.get_bezier_points (data, out bezier_points, out points_size, true); 526 527 for (int i = 0; i < points_size; i++) { 528 // FIXME: add more types 529 if (bezier_points[i].type == 'M') { 530 points.x = bezier_points[i].x0; 531 points.y = bezier_points[i].y0; 532 } else if (bezier_points[i].type == 'C') { 533 points.add (bezier_points[i].x0); 534 points.add (bezier_points[i].y0); 535 points.add (bezier_points[i].x1); 536 points.add (bezier_points[i].y1); 537 points.add (bezier_points[i].x2); 538 points.add (bezier_points[i].y2); 539 } else if (bezier_points[i].type == 'L') { 540 points.add (bezier_points[i].x0); 541 points.add (bezier_points[i].y0); 542 points.add (bezier_points[i].x0); 543 points.add (bezier_points[i].y0); 544 points.add (bezier_points[i].x0); 545 points.add (bezier_points[i].y0); 546 } else if (bezier_points[i].type == 'z') { 547 points.closed = true; 548 path_data.add (points); 549 points = new Points (); 550 } else { 551 string type = (!) bezier_points[i].type.to_string (); 552 warning (@"SVG conversion not implemented for $type"); 553 } 554 } 555 556 if (points.point_data.size > 0) { 557 path_data.add (points); 558 } 559 560 return path_data; 561 } 562 } 563 564 } 565