SvgFile.vala
/*
Copyright (C) 2016 Johan Mattsson
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/
using B;
using Cairo;
namespace BirdFont {
public class SvgFile : GLib.Object {
SvgDrawing drawing;
public SvgFile () {
}
public EmbeddedSvg parse (string path) {
string xml_data;
try {
FileUtils.get_contents (path, out xml_data);
return parse_data (xml_data);
} catch (GLib.Error error) {
warning (error.message);
}
SvgDrawing drawing = new SvgDrawing ();
return new EmbeddedSvg (drawing);
}
public EmbeddedSvg parse_data (string xml_data) {
XmlParser xmlparser = new XmlParser (xml_data);
SvgDrawing drawing = new SvgDrawing ();
if (xmlparser.validate ()) {
Tag root = xmlparser.get_root_tag ();
drawing = parse_svg_file (root);
EmbeddedSvg svg = new EmbeddedSvg (drawing);
svg.svg_data = xml_data;
return svg;
} else {
warning ("Invalid xml file.");
}
return new EmbeddedSvg (drawing);
}
private SvgDrawing parse_svg_file (Tag tag) {
drawing = new SvgDrawing ();
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "width") {
drawing.width = parse_number (attr.get_content ());
}
if (attr.get_name () == "height") {
drawing.height = parse_number (attr.get_content ());
}
}
foreach (Tag t in tag) {
string name = t.get_name ();
if (name == "g") {
parse_layer (drawing.root_layer, t);
}
if (name == "defs") {
parse_defs (drawing, t);
}
parse_object (drawing.root_layer, t);
}
return drawing;
}
private void parse_layer (Layer layer, Tag tag) {
bool hidden = false;
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "display" && attr.get_content () == "none") {
hidden = true;
}
if (attr.get_name () == "visibility"
&& (attr.get_content () == "hidden"
|| attr.get_content () == "collapse")) {
hidden = true;
}
}
if (hidden) {
layer.visible = !hidden;
}
foreach (Tag t in tag) {
string name = t.get_name ();
if (name == "g") {
Layer sublayer = new Layer ();
parse_layer (layer, t);
layer.subgroups.add (sublayer);
}
parse_object (layer, t);
}
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "transform") {
layer.transforms = parse_transform (attr.get_content ());
}
}
}
void parse_defs (SvgDrawing drawing, Tag tag) {
foreach (Tag t in tag) {
// FIXME: radial
string name = t.get_name ();
if (name == "linearGradient") {
parse_linear_gradient (drawing, t);
}
}
foreach (Gradient gradient in drawing.defs.gradients) {
if (gradient.href != null) {
Gradient? referenced;
referenced = drawing.defs.get_gradient_for_id ((!) gradient.href);
if (referenced != null) {
gradient.copy_stops ((!) referenced);
}
gradient.href = null;
}
}
}
void parse_linear_gradient (SvgDrawing drawing, Tag tag) {
Gradient gradient = new Gradient ();
drawing.defs.add (gradient);
foreach (Attribute attr in tag.get_attributes ()) {
string name = attr.get_name ();
// FIXME: gradientUnits
if (name == "gradientTransform") {
gradient.transforms = parse_transform (attr.get_content ());
}
if (name == "href") {
gradient.href = attr.get_content ();
}
if (name == "x1") {
gradient.x1 = parse_number (attr.get_content ());
}
if (name == "y1") {
gradient.y1 = parse_number (attr.get_content ());
}
if (name == "x2") {
gradient.x2 = parse_number (attr.get_content ());
}
if (name == "y2") {
gradient.y2 = parse_number (attr.get_content ());
}
if (name == "id") {
gradient.id = attr.get_content ();
}
}
foreach (Tag t in tag) {
// FIXME: radial
string name = t.get_name ();
if (name == "stop") {
parse_stop (gradient, t);
}
}
}
void parse_stop (Gradient gradient, Tag tag) {
SvgStyle style = SvgStyle.parse (drawing.defs, tag.get_attributes ());
Stop stop = new Stop ();
gradient.stops.add (stop);
foreach (Attribute attr in tag.get_attributes ()) {
string name = attr.get_name ();
if (name == "offset") {
string stop_offset = attr.get_content ();
if (stop_offset.index_of ("%") > -1) {
stop_offset = stop_offset.replace ("%", "");
stop.offset = parse_number (stop_offset) / 100.0;
} else {
stop.offset = parse_number (stop_offset);
}
}
}
string? stop_color = style.style.get ("stop-color");
string? stop_opacity = style.style.get ("stop-opacity");
Color? color = Color.black ();
if (stop_color != null) {
color = Color.parse (stop_color);
if (color != null) {
stop.color = (!) color;
}
}
if (stop_opacity != null && color != null) {
((!) color).a = parse_number (stop_opacity);
}
}
void parse_object (Layer layer, Tag tag) {
string name = tag.get_name ();
if (name == "path") {
parse_path (layer, tag);
}
if (name == "polygon") {
parse_polygon (layer, tag);
}
if (name == "polyline") {
parse_polyline (layer, tag);
}
if (name == "rect") {
parse_rect (layer, tag);
}
if (name == "circle") {
parse_circle (layer, tag);
}
if (name == "ellipse") {
parse_ellipse (layer, tag);
}
if (name == "line") {
parse_line (layer, tag);
}
}
private void parse_polygon (Layer layer, Tag tag) {
}
private void parse_polyline (Layer layer, Tag tag) {
}
private void parse_rect (Layer layer, Tag tag) {
Rectangle rectangle = new Rectangle ();
foreach (Attribute attr in tag.get_attributes ()) {
string attribute = attr.get_name ();
if (attribute == "x") {
rectangle.x = parse_number (attr.get_content ());
}
if (attribute == "y") {
rectangle.y = parse_number (attr.get_content ());
}
if (attribute == "width") {
rectangle.width = parse_number (attr.get_content ());
}
if (attribute == "height") {
rectangle.height = parse_number (attr.get_content ());
}
if (attribute == "rx") {
rectangle.rx = parse_number (attr.get_content ());
}
if (attribute == "ry") {
rectangle.ry = parse_number (attr.get_content ());
}
}
rectangle.transforms = get_transform (tag.get_attributes ());
rectangle.style = SvgStyle.parse (drawing.defs,tag.get_attributes ());
rectangle.visible = is_visible (tag);
layer.add_object (rectangle);
}
private void parse_circle (Layer layer, Tag tag) {
}
private void parse_ellipse (Layer layer, Tag tag) {
}
private void parse_line (Layer layer, Tag tag) {
}
// FIXME: reverse order?
public SvgTransforms parse_transform (string transforms) {
string[] functions;
string transform = transforms;
SvgTransforms transform_functions;
transform_functions = new SvgTransforms ();
transform = transform.replace ("\t", " ");
transform = transform.replace ("\n", " ");
transform = transform.replace ("\r", " ");
// use only a single space as separator
while (transform.index_of (" ") > -1) {
transform = transform.replace (" ", " ");
}
if (unlikely (transform.index_of (")") == -1)) {
warning ("No parenthesis in transform function.");
return transform_functions;
}
// add separator
transform = transform.replace (") ", "|");
transform = transform.replace (")", "|");
functions = transform.split ("|");
for (int i = 0; i < functions.length; i++) {
if (functions[i].has_prefix ("translate")) {
transform_functions.add (translate (functions[i]));
}
if (functions[i].has_prefix ("scale")) {
transform_functions.add (scale (functions[i]));
}
if (functions[i].has_prefix ("matrix")) {
transform_functions.add (matrix (functions[i]));
}
// TODO: rotate etc.
}
return transform_functions;
}
private SvgTransform matrix (string function) {
string parameters = get_transform_parameters (function);
string[] p = parameters.split (" ");
SvgTransform transform = new SvgTransform ();
transform.type = TransformType.MATRIX;
if (unlikely (p.length != 6)) {
warning ("Expecting six parameters for matrix transformation.");
return transform;
}
for (int i = 0; i < 6; i++) {
double argument = SvgParser.parse_double (p[i]);
transform.arguments.add (argument);
}
return transform;
}
private static string remove_unit (string d) {
string s = d.replace ("pt", "");
s = s.replace ("pc", "");
s = s.replace ("mm", "");
s = s.replace ("cm", "");
s = s.replace ("in", "");
s = s.replace ("px", "");
return s;
}
public static double parse_number (string? number_with_unit) {
if (number_with_unit == null) {
return 0;
}
string d = (!) number_with_unit;
string s = remove_unit (d);
double n = SvgParser.parse_double (s);
if (d.has_suffix ("pt")) {
n *= 1.25;
} else if (d.has_suffix ("pc")) {
n *= 15;
} else if (d.has_suffix ("mm")) {
n *= 3.543307;
} else if (d.has_suffix ("cm")) {
n *= 35.43307;
} else if (d.has_suffix ("in")) {
n *= 90;
}
return n;
}
private SvgTransform scale (string function) {
string parameters = get_transform_parameters (function);
string[] p = parameters.split (" ");
SvgTransform transform = new SvgTransform ();
transform.type = TransformType.SCALE;
if (p.length > 0) {
transform.arguments.add (SvgParser.parse_double (p[0]));
}
if (p.length > 1) {
transform.arguments.add (SvgParser.parse_double (p[1]));
}
return transform;
}
private SvgTransform translate (string function) {
string parameters = get_transform_parameters (function);
string[] p = parameters.split (" ");
SvgTransform transform = new SvgTransform ();
transform.type = TransformType.TRANSLATE;
if (p.length > 0) {
transform.arguments.add (SvgParser.parse_double (p[0]));
}
if (p.length > 1) {
transform.arguments.add (SvgParser.parse_double (p[1]));
}
return transform;
}
private string get_transform_parameters (string function) {
int i;
string param = "";
i = function.index_of ("(");
return_val_if_fail (i != -1, param);
param = function.substring (i);
param = param.replace ("(", "");
param = param.replace ("\n", " ");
param = param.replace ("\t", " ");
param = param.replace (",", " ");
while (param.index_of (" ") > -1) {
param.replace (" ", " ");
}
return param.strip();
}
private bool is_visible (Tag tag) {
bool hidden = false;
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "display" && attr.get_content () == "none") {
hidden = true;
}
if (attr.get_name () == "visibility"
&& (attr.get_content () == "hidden"
|| attr.get_content () == "collapse")) {
hidden = true;
}
}
return !hidden;
}
private SvgTransforms get_transform (Attributes attributes) {
foreach (Attribute attr in attributes) {
if (attr.get_name () == "transform") {
return parse_transform (attr.get_content ());
}
}
return new SvgTransforms ();
}
private void parse_path (Layer layer, Tag tag) {
SvgPath path = new SvgPath ();
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "d") {
path.points = parse_points (attr.get_content ());
}
}
path.transforms = get_transform (tag.get_attributes ());
path.style = SvgStyle.parse (drawing.defs, tag.get_attributes ());
path.visible = is_visible (tag);
layer.add_object (path);
}
public Gee.ArrayList<Points> parse_points (string data) {
Gee.ArrayList<Points> path_data = new Gee.ArrayList<Points> ();
Points points = new Points ();
BezierPoints[] bezier_points;
int points_size;
SvgParser.get_bezier_points (data, out bezier_points, out points_size, true);
for (int i = 0; i < points_size; i++) {
// FIXME: add more types
if (bezier_points[i].type == 'M') {
points.x = bezier_points[i].x0;
points.y = bezier_points[i].y0;
} else if (bezier_points[i].type == 'C') {
points.add (bezier_points[i].x0);
points.add (bezier_points[i].y0);
points.add (bezier_points[i].x1);
points.add (bezier_points[i].y1);
points.add (bezier_points[i].x2);
points.add (bezier_points[i].y2);
} else if (bezier_points[i].type == 'L') {
points.add (bezier_points[i].x0);
points.add (bezier_points[i].y0);
points.add (bezier_points[i].x0);
points.add (bezier_points[i].y0);
points.add (bezier_points[i].x0);
points.add (bezier_points[i].y0);
} else if (bezier_points[i].type == 'z') {
points.closed = true;
path_data.add (points);
points = new Points ();
} else {
string type = (!) bezier_points[i].type.to_string ();
warning (@"SVG conversion not implemented for $type");
}
}
if (points.point_data.size > 0) {
path_data.add (points);
}
return path_data;
}
}
}