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