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