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;
using Math;
namespace SvgBird {
public enum SvgFormat {
NONE,
INKSCAPE,
ILLUSTRATOR
}
public class SvgFile : GLib.Object {
SvgDrawing drawing;
SvgFormat format = SvgFormat.ILLUSTRATOR;
public SvgFile () {
}
public SvgDrawing parse_svg_data (string xml_data) {
XmlTree tree = new XmlTree (xml_data);
if (xml_data.index_of ("Illustrator") > -1 || xml_data.index_of ("illustrator") > -1) {
format = SvgFormat.ILLUSTRATOR;
} else if (xml_data.index_of ("Inkscape") > -1 || xml_data.index_of ("inkscape") > -1) {
format = SvgFormat.INKSCAPE;
}
return parse_svg_file (tree.get_root (), format);
}
public SvgDrawing parse_svg_file (XmlElement svg_tag, SvgFormat format) {
drawing = new SvgDrawing ();
this.format = format;
SvgStyle style = new SvgStyle ();
SvgStyle.parse (drawing.defs, style, svg_tag, null);
foreach (Attribute attr in svg_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 ());
}
if (attr.get_name () == "viewBox") {
drawing.view_box = parse_view_box (svg_tag);
}
}
foreach (XmlElement t in svg_tag) {
string name = t.get_name ();
if (name == "g") {
parse_layer (drawing.root_layer, style, t);
}
if (name == "svg") {
SvgDrawing embedded = parse_svg_file (t, format);
drawing.root_layer.add_object (embedded);
}
if (name == "defs") {
parse_defs (drawing, t);
}
if (name == "a") {
parse_link (drawing.root_layer, style, svg_tag);
}
parse_object (drawing.root_layer, style, t);
}
set_object_properties (drawing, new SvgStyle (), svg_tag);
drawing.update_boundaries_for_object ();
return drawing;
}
public static ViewBox? parse_view_box (XmlElement tag) {
string arguments;
string parameters = "";
string aspect_ratio = "";
bool slice = true;
bool preserve_aspect_ratio = true;
string default_unit = "px";
if (tag.get_name () != "svg") {
warning (@"viewBox is not the svg tag: $(tag.get_name ())");
return null;
}
foreach (Attribute attribute in tag.get_attributes ()) {
if (attribute.get_name () == "viewBox") {
parameters = attribute.get_content ();
}
if (attribute.get_name () == "preserveAspectRatio") {
aspect_ratio = attribute.get_content ();
string[] preserveSettings = aspect_ratio.split (" ");
if (preserveSettings.length >= 1) {
aspect_ratio = preserveSettings[0];
}
if (preserveSettings.length >= 2) {
slice = preserveSettings[1] == "slice";
}
preserve_aspect_ratio = false;
}
if (attribute.get_name () == "width") {
string val = attribute.get_content ();
default_unit = get_unit (val);
}
if (attribute.get_name () == "height") {
string val = attribute.get_content ();
default_unit = get_unit (val);
}
}
arguments = parameters.replace (",", " ");
while (arguments.index_of (" ") > -1) {
arguments = arguments.replace (" ", " ");
}
string[] view_box_parameters = arguments.split (" ");
if (unlikely (view_box_parameters.length != 4)) {
warning ("Expecting four arguments in view box.");
warning (@"Content: $arguments ($parameters)");
return null;
}
double minx = parse_number (view_box_parameters[0], default_unit);
double miny = parse_number (view_box_parameters[1], default_unit);
double width = parse_number (view_box_parameters[2], default_unit);
double height = parse_number (view_box_parameters[3], default_unit);
uint alignment = ViewBox.XMID_YMID;
aspect_ratio = aspect_ratio.up ();
if (aspect_ratio == "NONE") {
alignment = ViewBox.NONE;
} else if (aspect_ratio == "XMINYMIN") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMINYMIN") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMAXYMIN") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMINYMID") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMIDYMID") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMAXYMID") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMINYMAX") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMIDYMAX") {
alignment = ViewBox.XMIN_YMIN;
} else if (aspect_ratio == "XMAXYMAX") {
alignment = ViewBox.XMIN_YMIN;
}
return new ViewBox (minx, miny, width, height, alignment, slice, preserve_aspect_ratio);
}
private void parse_layer (Layer layer, SvgStyle parent_style, XmlElement tag) {
foreach (XmlElement t in tag) {
string name = t.get_name ();
if (name == "g") {
Layer sublayer = new Layer ();
parse_layer (sublayer, parent_style, t);
layer.objects.add (sublayer);
}
if (name == "a") {
parse_link (layer, parent_style, t);
}
parse_object (layer, parent_style, t);
}
set_object_properties (layer, parent_style, tag);
}
void parse_clip_path (SvgDrawing drawing, XmlElement tag) {
ClipPath clip_path;
Layer layer = new Layer ();
parse_layer (layer, new SvgStyle (), tag);
clip_path = new ClipPath (layer);
drawing.defs.clip_paths.add (clip_path);
}
void parse_defs (SvgDrawing drawing, XmlElement tag) {
foreach (XmlElement t in tag) {
string name = t.get_name ();
if (name == "linearGradient") {
parse_linear_gradient (drawing, t);
} else if (name == "radialGradient") {
parse_radial_gradient (drawing, t);
} else if (name == "clipPath") {
parse_clip_path (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;
}
}
foreach (XmlElement t in tag) {
// FIXME: radial
string name = t.get_name ();
if (name == "style") {
drawing.defs.style_sheet = StyleSheet.parse (drawing.defs, t);
}
}
}
void parse_radial_gradient (SvgDrawing drawing, XmlElement tag) {
RadialGradient gradient = new RadialGradient ();
drawing.defs.add_radial_gradient (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 == "cx") {
gradient.cx = parse_number (attr.get_content ());
}
if (name == "cy") {
gradient.cy = parse_number (attr.get_content ());
}
if (name == "fx") {
gradient.fx = parse_number (attr.get_content ());
}
if (name == "fy") {
gradient.fy = parse_number (attr.get_content ());
}
if (name == "r") {
gradient.r = parse_number (attr.get_content ());
}
if (name == "id") {
gradient.id = attr.get_content ();
}
}
foreach (XmlElement t in tag) {
string name = t.get_name ();
if (name == "stop") {
parse_stop (gradient, t);
}
}
}
void parse_linear_gradient (SvgDrawing drawing, XmlElement tag) {
LinearGradient gradient = new LinearGradient ();
drawing.defs.add_linear_gradient (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 (XmlElement t in tag) {
string name = t.get_name ();
if (name == "stop") {
parse_stop (gradient, t);
}
}
}
void parse_stop (Gradient gradient, XmlElement tag) {
SvgStyle parent_style = new SvgStyle (); // not inherited
SvgStyle style = SvgStyle.parse (drawing.defs, parent_style, tag, null);
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 = new Color (0, 0, 0, 1);
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);
}
}
// links are ignored, add the content to the layer
void parse_link (Layer layer, SvgStyle parent_style, XmlElement tag) {
parse_layer (layer, parent_style, tag);
}
void parse_object (Layer layer, SvgStyle parent_style, XmlElement tag) {
string name = tag.get_name ();
if (name == "path") {
parse_path (layer, parent_style, tag);
}
if (name == "polygon") {
parse_polygon (layer, parent_style, tag);
}
if (name == "polyline") {
parse_polyline (layer, parent_style, tag);
}
if (name == "rect") {
parse_rect (layer, parent_style, tag);
}
if (name == "circle") {
parse_circle (layer, parent_style, tag);
}
if (name == "ellipse") {
parse_ellipse (layer, parent_style, tag);
}
if (name == "line") {
parse_line (layer, parent_style, tag);
}
if (name == "text") {
parse_text (layer, parent_style, tag);
}
if (name == "svg") {
SvgDrawing embedded = parse_svg_file (tag, format);
layer.add_object (embedded);
}
}
private void parse_polygon (Layer layer, SvgStyle parent_style, XmlElement tag) {
Polygon polygon = new Polygon ();
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "points") {
string data = add_separators (attr.get_content ());
string[] point_data = data.split (" ");
for (int i = 0; i < point_data.length - 1; i++) {
string number_x = point_data[i];
string number_y = point_data[i + 1];
polygon.points.add_type (POINT_LINE);
polygon.points.add (parse_number (number_x));
polygon.points.add (parse_number (number_y));
polygon.points.add (0);
polygon.points.add (0);
polygon.points.add (0);
polygon.points.add (0);
polygon.points.add (0);
}
}
}
set_object_properties (polygon, parent_style, tag);
layer.add_object (polygon);
}
void set_object_properties (Object object, SvgStyle parent_style, XmlElement tag) {
Attributes attributes = tag.get_attributes ();
foreach (Attribute attribute in attributes) {
string name = attribute.get_name ();
if (name == "id") {
object.id = attribute.get_content ();
} else if (name == "class") {
object.css_class = attribute.get_content ();
}
}
object.clip_path = get_clip_path (attributes);
object.transforms = get_transform (attributes);
object.style = SvgStyle.parse (drawing.defs, parent_style, tag, null);
object.visible = is_visible (tag);
}
ClipPath? get_clip_path (Attributes attributes) {
foreach (Attribute attribute in attributes) {
if (attribute.get_name () == "clip-path") {
return drawing.defs.get_clip_path_for_url (attribute.get_content ());
}
}
return null;
}
private void parse_polyline (Layer layer, SvgStyle parent_style, XmlElement tag) {
Polyline polyline = new Polyline ();
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "points") {
string data = add_separators (attr.get_content ());
string[] point_data = data.split (" ");
foreach (string number in point_data) {
polyline.points.add (parse_number (number));
}
}
}
set_object_properties (polyline, parent_style, tag);
layer.add_object (polyline);
}
private void parse_rect (Layer layer, SvgStyle parent_style, XmlElement 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 ());
}
}
set_object_properties (rectangle, parent_style, tag);
layer.add_object (rectangle);
}
private void parse_circle (Layer layer, SvgStyle parent_style, XmlElement tag) {
Circle circle = new Circle ();
foreach (Attribute attr in tag.get_attributes ()) {
string name = attr.get_name ();
if (name == "cx") {
circle.cx = parse_number (attr.get_content ());
}
if (name == "cy") {
circle.cy = parse_number (attr.get_content ());
}
if (name == "r") {
circle.r = parse_number (attr.get_content ());
}
}
set_object_properties (circle, parent_style, tag);
layer.add_object (circle);
}
private void parse_ellipse (Layer layer, SvgStyle parent_style, XmlElement tag) {
Ellipse ellipse = new Ellipse ();
foreach (Attribute attr in tag.get_attributes ()) {
string name = attr.get_name ();
if (name == "cx") {
ellipse.cx = parse_number (attr.get_content ());
}
if (name == "cy") {
ellipse.cy = parse_number (attr.get_content ());
}
if (name == "rx") {
ellipse.rx = parse_number (attr.get_content ());
}
if (name == "ry") {
ellipse.ry = parse_number (attr.get_content ());
}
}
set_object_properties (ellipse, parent_style, tag);
layer.add_object (ellipse);
}
private void parse_line (Layer layer, SvgStyle parent_style, XmlElement tag) {
Line line = new Line ();
foreach (Attribute attr in tag.get_attributes ()) {
string name = attr.get_name ();
if (name == "x1") {
line.x1 = parse_number (attr.get_content ());
}
if (name == "y1") {
line.y1 = parse_number (attr.get_content ());
}
if (name == "x2") {
line.x2 = parse_number (attr.get_content ());
}
if (name == "y2") {
line.y2 = parse_number (attr.get_content ());
}
}
set_object_properties (line, parent_style, tag);
layer.add_object (line);
}
private void parse_text (Layer layer, SvgStyle parent_style, XmlElement tag) {
Text text = new Text ();
foreach (Attribute attr in tag.get_attributes ()) {
string name = attr.get_name ();
if (name == "font-size") {
text.set_font_size ((int) parse_number (attr.get_content ()));
}
if (name == "font-family") {
text.set_font (attr.get_content ());
}
if (name == "x") {
text.x = parse_number (attr.get_content ());
}
if (name == "y") {
text.y = parse_number (attr.get_content ());
}
}
text.set_text (tag.get_content ());
set_object_properties (text, parent_style, tag);
layer.add_object (text);
}
// FIXME: reverse order?
public static SvgTransforms parse_transform (string transforms) {
string[] functions;
string transform = transforms;
SvgTransforms transform_functions;
transform_functions = new SvgTransforms ();
if (transforms == "") {
return transform_functions;
}
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: $transform");
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]));
}
if (functions[i].has_prefix ("rotate")) {
transform_functions.add (rotate (functions[i]));
}
// TODO: rotate etc.
}
return transform_functions;
}
private static 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 = 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 string get_unit (string number_with_unit) {
if (number_with_unit.has_suffix ("pt")) {
return "pt";
} else if (number_with_unit.has_suffix ("pc")) {
return "pc";
} else if (number_with_unit.has_suffix ("mm")) {
return "mm";
} else if (number_with_unit.has_suffix ("cm")) {
return "cm";
} else if (number_with_unit.has_suffix ("in")) {
return "in";
}
return "";
}
public static double parse_number (string? number_with_unit, string default_unit = "") {
if (number_with_unit == null) {
return 0;
}
string d = (!) number_with_unit;
string s = remove_unit (d);
double n = parse_double (s);
string current_unit = get_unit (d);
if (current_unit == "") {
current_unit = default_unit;
}
if (current_unit == "pt") {
n *= 1.25;
} else if (current_unit == "pc") {
n *= 15;
} else if (current_unit == "mm") {
n *= 3.543307;
} else if (current_unit == "cm") {
n *= 35.43307;
} else if (current_unit == "in") {
n *= 90;
}
return n;
}
private static SvgTransform rotate (string function) {
string parameters = get_transform_parameters (function);
string[] p = parameters.split (" ");
SvgTransform transform = new SvgTransform ();
transform.type = TransformType.ROTATE;
if (p.length > 0) {
transform.arguments.add (parse_double (p[0]));
}
return transform;
}
private static 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 (parse_double (p[0]));
}
if (p.length > 1) {
transform.arguments.add (parse_double (p[1]));
}
return transform;
}
private static 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 (parse_double (p[0]));
}
if (p.length > 1) {
transform.arguments.add (parse_double (p[1]));
}
return transform;
}
private static 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 = param.replace (" ", " ");
}
return param.strip();
}
private bool is_visible (XmlElement 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;
}
public static 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, SvgStyle parent_style, XmlElement tag) {
SvgPath path = new SvgPath ();
foreach (Attribute attr in tag.get_attributes ()) {
if (attr.get_name () == "d") {
path.points = parse_points (attr.get_content (), format);
}
}
set_object_properties (path, parent_style, tag);
layer.add_object (path);
}
public static Gee.ArrayList<Points> parse_points (string data, SvgFormat format) {
Gee.ArrayList<Points> path_data = new Gee.ArrayList<Points> ();
Points points = new Points ();
BezierPoints[] bezier_points;
int points_size;
get_bezier_points (data, out bezier_points, out points_size, true);
// all instructions are padded
double first_x = 0;
double first_y = 0;
double last_x = 0;
double last_y = 0;
if (points_size > 0) {
first_x = bezier_points[0].x0;
first_y = bezier_points[0].y0;
}
for (int i = 0; i < points_size; i++) {
// FIXME: add more types
if (bezier_points[i].type == 'M') {
points.add_type (POINT_LINE);
points.add (bezier_points[i].x0);
points.add (bezier_points[i].y0);
points.add (0);
points.add (0);
points.add (0);
points.add (0);
points.add (0);
last_x = bezier_points[i].x0;
last_y = bezier_points[i].y0;
} else if (bezier_points[i].type == 'C') {
points.add_type (POINT_CUBIC);
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);
points.add (0);
last_x = bezier_points[i].x2;
last_y = bezier_points[i].y2;
} else if (bezier_points[i].type == 'L') {
points.add_type (POINT_LINE);
points.add (bezier_points[i].x0);
points.add (bezier_points[i].y0);
points.add (0);
points.add (0);
points.add (0);
points.add (0);
points.add (0);
last_x = bezier_points[i].x0;
last_y = bezier_points[i].y0;
} else if (bezier_points[i].type == 'A') {
BezierPoints b = bezier_points[i];
points.add_type (POINT_ARC);
points.add (b.rx);
points.add (b.ry);
points.add (b.rotation);
points.add (b.large_arc ? 1 : 0);
points.add (b.sweep ? 1 : 0);
points.add (b.x1);
points.add (b.y1);
last_x = bezier_points[i].x1;
last_y = bezier_points[i].y1;
} else if (bezier_points[i].type == 'z') {
points.closed = true;
if (fabs (first_x - last_x) > 0.0001 && fabs (first_y - last_y) > 0.0001) {
points.add_type (POINT_LINE);
points.add (first_x);
points.add (first_y);
points.add (0);
points.add (0);
points.add (0);
points.add (0);
points.add (0);
}
path_data.add (points);
points = new Points ();
if (i + 1 < points_size) {
first_x = bezier_points[i + 1].x0;
first_y = bezier_points[i + 1].y0;
}
} else {
string type = (!) bezier_points[i].type.to_string ();
warning (@"SVG conversion not implemented for $type");
}
}
if (points.size > 0) {
path_data.add (points);
}
if (format == SvgFormat.ILLUSTRATOR) {
Gee.ArrayList<Points> illustrator_path_data = new Gee.ArrayList<Points> ();
foreach (Points p in path_data) {
return_val_if_fail (p.point_data.size % 8 == 0, path_data);
if (p.point_data.size > 8) {
Points illustrator_points = new Points ();
if (p.point_data.get_point_type (p.point_data.size - 8) == POINT_CUBIC) {
illustrator_points.insert_type (0, POINT_LINE);
illustrator_points.insert (1, p.point_data.get_double (p.point_data.size - 3));
illustrator_points.insert (2, p.point_data.get_double (p.point_data.size - 2));
illustrator_points.insert (3, 0);
illustrator_points.insert (4, 0);
illustrator_points.insert (5, 0);
illustrator_points.insert (6, 0);
illustrator_points.insert (7, 0);
} else {
illustrator_points.insert_type (0, POINT_LINE);
illustrator_points.insert (1, p.point_data.get_double (p.point_data.size - 7));
illustrator_points.insert (2, p.point_data.get_double (p.point_data.size - 6));
illustrator_points.insert (3, 0);
illustrator_points.insert (4, 0);
illustrator_points.insert (5, 0);
illustrator_points.insert (6, 0);
illustrator_points.insert (7, 0);
}
int start = 0;
for (int i = start; i < p.point_data.size; i += 1) {
illustrator_points.point_data.add (p.point_data.get_double (i));
}
illustrator_points.closed = p.closed;
illustrator_path_data.add (illustrator_points);
}
}
path_data = illustrator_path_data;
}
return path_data;
}
public static double parse_double (string? s) {
if (unlikely (s == null)) {
warning ("number is null");
return 0;
}
if (unlikely (!double.try_parse ((!) s))) {
warning (@"Expecting a double got: $((!) s)");
return 0;
}
return double.parse ((!) s);
}
public static void get_bezier_points (string point_data,
out BezierPoints[] bezier_points, out int points, bool svg_glyph) {
double px = 0;
double py = 0;
double px2 = 0;
double py2 = 0;
double cx = 0;
double cy = 0;
string[] c;
double arc_rx, arc_ry;
double arc_rotation;
int large_arc;
int arc_sweep;
double arc_dest_x, arc_dest_y;
int bi = 0;
string data = add_separators (point_data);
c = data.split (" ");
// the arc instruction can use up to eight points
int bezier_points_length = 8 * c.length + 1;
bezier_points = new BezierPoints[bezier_points_length];
for (int i = 0; i < bezier_points_length; i++) {
bezier_points[i] = new BezierPoints ();
}
// parse path
int i = -1;
while (++i < c.length && bi < bezier_points.length) {
if (c[i] == "m") {
while (i + 2 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'M';
bezier_points[bi].svg_type = 'm';
px += parse_double (c[++i]);
if (svg_glyph) {
py += parse_double (c[++i]);
} else {
py += -parse_double (c[++i]);
}
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bi++;
}
} else if (c[i] == "M") {
while (i + 2 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'M';
bezier_points[bi].svg_type = 'M';
px = parse_double (c[++i]);
if (svg_glyph) {
py = parse_double (c[++i]);
} else {
py = -parse_double (c[++i]);
}
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bi++;
}
} else if (c[i] == "h") {
while (i + 1 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'L';
bezier_points[bi].svg_type = 'h';
px += parse_double (c[++i]);
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bi++;
}
} else if (i + 1 < c.length && c[i] == "H") {
while (is_point (c[i + 1])) {
bezier_points[bi].type = 'L';
bezier_points[bi].svg_type = 'H';
px = parse_double (c[++i]);
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bi++;
}
} else if (c[i] == "v") {
while (i + 1 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'L';
bezier_points[bi].svg_type = 'v';
if (svg_glyph) {
py = py + parse_double (c[++i]);
} else {
py = py - parse_double (c[++i]);
}
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bi++;
}
} else if (i + 1 < c.length && c[i] == "V") {
while (is_point (c[i + 1])) {
bezier_points[bi].type = 'L';
bezier_points[bi].svg_type = 'V';
if (svg_glyph) {
py = parse_double (c[++i]);
} else {
py = -parse_double (c[++i]);
}
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bi++;
}
} else if (c[i] == "l") {
while (i + 2 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'L';
bezier_points[bi].svg_type = 'l';
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
px = cx;
py = cy;
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
bi++;
}
} else if (c[i] == "L") {
while (i + 2 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'L';
bezier_points[bi].svg_type = 'L';
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
px = cx;
py = cy;
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
bi++;
}
} else if (c[i] == "c") {
while (i + 6 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'C';
bezier_points[bi].svg_type = 'C';
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
px2 = cx;
py2 = cy;
bezier_points[bi].x1 = px2;
bezier_points[bi].y1 = py2;
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py + -parse_double (c[++i]);
}
bezier_points[bi].x2 = cx;
bezier_points[bi].y2 = cy;
px = cx;
py = cy;
bi++;
}
} else if (c[i] == "C") {
while (i + 6 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'C';
bezier_points[bi].svg_type = 'C';
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
px2 = cx;
py2 = cy;
bezier_points[bi].x1 = cx;
bezier_points[bi].y1 = cy;
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
bezier_points[bi].x2 = cx;
bezier_points[bi].y2 = cy;
px = cx;
py = cy;
bi++;
}
} else if (c[i] == "q") {
while (i + 4 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'Q';
bezier_points[bi].svg_type = 'q';
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
px2 = cx;
py2 = cy;
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
bezier_points[bi].x1 = cx;
bezier_points[bi].y1 = cy;
px = cx;
py = cy;
bi++;
}
} else if (c[i] == "Q") {
while (i + 4 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'Q';
bezier_points[bi].svg_type = 'Q';
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
px2 = cx;
py2 = cy;
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
px = cx;
py = cy;
bezier_points[bi].x1 = cx;
bezier_points[bi].y1 = cy;
bi++;
}
} else if (c[i] == "t") {
while (i + 2 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'Q';
bezier_points[bi].svg_type = 't';
// the first point is the reflection
cx = 2 * px - px2;
cy = 2 * py - py2; // if (svg_glyph) ?
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
px2 = cx;
py2 = cy;
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
px = cx;
py = cy;
bezier_points[bi].x1 = px;
bezier_points[bi].y1 = py;
bi++;
}
} else if (c[i] == "T") {
while (i + 2 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'Q';
bezier_points[bi].svg_type = 'T';
// the reflection
cx = 2 * px - px2;
cy = 2 * py - py2;
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
px2 = cx;
py2 = cy;
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
px = cx;
py = cy;
bezier_points[bi].x1 = px;
bezier_points[bi].y1 = py;
bi++;
}
} else if (c[i] == "s") {
while (i + 4 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'C';
bezier_points[bi].svg_type = 's';
// the first point is the reflection
cx = 2 * px - px2;
cy = 2 * py - py2; // if (svg_glyph) ?
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
px2 = cx;
py2 = cy;
bezier_points[bi].x1 = px2;
bezier_points[bi].y1 = py2;
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
bezier_points[bi].x2 = cx;
bezier_points[bi].y2 = cy;
px = cx;
py = cy;
bi++;
}
} else if (c[i] == "S") {
while (i + 4 < c.length && is_point (c[i + 1])) {
bezier_points[bi].type = 'C';
bezier_points[bi].svg_type = 'S';
// the reflection
cx = 2 * px - px2;
cy = 2 * py - py2; // if (svg_glyph) ?
bezier_points[bi].x0 = cx;
bezier_points[bi].y0 = cy;
// the other two are regular cubic points
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
px2 = cx;
py2 = cy;
bezier_points[bi].x1 = px2;
bezier_points[bi].y1 = py2;
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
bezier_points[bi].x2 = cx;
bezier_points[bi].y2 = cy;
px = cx;
py = cy;
bi++;
}
} else if (c[i] == "a") {
while (i + 7 < c.length && is_point (c[i + 1])) {
arc_rx = parse_double (c[++i]);
arc_ry = parse_double (c[++i]);
arc_rotation = PI * (parse_double (c[++i]) / 180.0);
large_arc = parse_int (c[++i]);
arc_sweep = parse_int (c[++i]);
cx = px + parse_double (c[++i]);
if (svg_glyph) {
cy = py + parse_double (c[++i]);
} else {
cy = py - parse_double (c[++i]);
}
arc_dest_x = cx;
arc_dest_y = cy;
bezier_points[bi].type = 'A';
bezier_points[bi].svg_type = 'a';
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bezier_points[bi].x1 = cx;
bezier_points[bi].y1 = cy;
bezier_points[bi].rx = arc_rx;
bezier_points[bi].ry = arc_ry;
bezier_points[bi].rotation = arc_rotation;
bezier_points[bi].large_arc = large_arc == 1;
bezier_points[bi].sweep = arc_sweep == 1;
bi++;
px = cx;
py = cy;
}
} else if (i + 7 < c.length && c[i] == "A") {
while (is_point (c[i + 1])) {
arc_rx = parse_double (c[++i]);
arc_ry = parse_double (c[++i]);
arc_rotation = PI * (parse_double (c[++i]) / 180.0);
large_arc = parse_int (c[++i]);
arc_sweep = parse_int (c[++i]);
cx = parse_double (c[++i]);
if (svg_glyph) {
cy = parse_double (c[++i]);
} else {
cy = -parse_double (c[++i]);
}
arc_dest_x = cx;
arc_dest_y = cy;
bezier_points[bi].type = 'A';
bezier_points[bi].svg_type = 'A';
bezier_points[bi].x0 = px;
bezier_points[bi].y0 = py;
bezier_points[bi].x1 = cx;
bezier_points[bi].y1 = cy;
bezier_points[bi].rx = arc_rx;
bezier_points[bi].ry = arc_ry;
bezier_points[bi].rotation = arc_rotation;
bezier_points[bi].large_arc = large_arc == 1;
bezier_points[bi].sweep = arc_sweep == 1;
bi++;
px = cx;
py = cy;
}
} else if (c[i] == "z") {
bezier_points[bi].type = 'z';
bezier_points[bi].svg_type = 'z';
bi++;
} else if (c[i] == "Z") {
bezier_points[bi].type = 'z';
bezier_points[bi].svg_type = 'z';
bi++;
} else if (c[i] == "") {
} else if (c[i] == " ") {
} else {
warning (@"Unknown instruction: $(c[i])");
}
}
if (bi == 0) {
warning ("No points in path.");
}
points = bi;
}
static int parse_int (string? s) {
if (unlikely (s == null)) {
warning ("null instead of string");
return 0;
}
if (unlikely (!int64.try_parse ((!) s))) {
warning (@"Expecting an integer: $((!) s)");
return 0;
}
return int.parse ((!) s);
}
static bool is_point (string? s) {
if (unlikely (s == null)) {
warning ("s is null");
return false;
}
return double.try_parse ((!) s);
}
/** Add space as separator to svg data.
* @param d svg data
*/
public static string add_separators (string d) {
string data = d;
data = data.replace (",", " ");
data = data.replace ("a", " a ");
data = data.replace ("A", " A ");
data = data.replace ("m", " m ");
data = data.replace ("M", " M ");
data = data.replace ("h", " h ");
data = data.replace ("H", " H ");
data = data.replace ("v", " v ");
data = data.replace ("V", " V ");
data = data.replace ("l", " l ");
data = data.replace ("L", " L ");
data = data.replace ("q", " q ");
data = data.replace ("Q", " Q ");
data = data.replace ("c", " c ");
data = data.replace ("C", " C ");
data = data.replace ("t", " t ");
data = data.replace ("T", " T ");
data = data.replace ("s", " s ");
data = data.replace ("S", " S ");
data = data.replace ("zM", " z M ");
data = data.replace ("zm", " z m ");
data = data.replace ("z", " z ");
data = data.replace ("Z", " Z ");
data = data.replace ("-", " -");
data = data.replace ("e -", "e-"); // minus can be either separator or a negative exponent
data = data.replace ("\t", " ");
data = data.replace ("\r\n", " ");
data = data.replace ("\n", " ");
// use only a single space as separator
while (data.index_of (" ") > -1) {
data = data.replace (" ", " ");
}
return data;
}
}
}