The Birdfont Source Code


All Repositories / birdfont.git / commitdiff – RSS feed

Move SVG classes to a subdirectory

These changes was commited to the Birdfont repository Tue, 05 Jan 2016 12:06:34 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
[Tue, 05 Jan 2016 12:06:34 +0000]

Updated Files

dodo.py
libbirdfont/Svg/Svg.vala
libbirdfont/Svg/SvgArc.vala
libbirdfont/Svg/SvgFile.vala
libbirdfont/Svg/SvgFont.vala
libbirdfont/Svg/SvgFontFormatWriter.vala
libbirdfont/Svg/SvgParser.vala
libbirdfont/Svg/SvgStyle.vala
libbirdfont/Svg.vala
libbirdfont/SvgArc.vala
libbirdfont/SvgFont.vala
libbirdfont/SvgFontFormatWriter.vala
libbirdfont/SvgParser.vala
libbirdfont/SvgStyle.vala
diff --git a/dodo.py b/dodo.py
--- a/dodo.py +++ b/dodo.py @@ -302,6 +302,7 @@ libbirdfont/*.vala \ libbirdfont/OpenFontFormat/*.vala \ libbirdfont/TextRendering/*.vala \ + libbirdfont/Svg/*.vala \ --pkg """ + config.GEE + """ \ --pkg gio-2.0 \ --pkg cairo \
diff --git libbirdfont/Svg/Svg.vala(new)
--- /dev/null +++ b/libbirdfont/Svg/Svg.vala @@ -1,1 +1,354 @@ + /* + Copyright (C) 2012 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 Cairo; + + namespace BirdFont { + + public class Svg { + + /** Export to svg glyph data. */ + public static string to_svg_glyph (Glyph g) { + StringBuilder svg = new StringBuilder (); + PathList stroke_list; + + foreach (Path p in g.get_visible_paths ()) { + if (p.stroke == 0) { + write_path_as_glyph (p, svg, g); + } else { + stroke_list = p.get_completed_stroke (); + write_paths_as_glyph (stroke_list, svg, g); + } + } + + return svg.str; + } + + /** Export to svg-font data. */ + public static string to_svg_path (Path pl, Glyph g) { + StringBuilder svg = new StringBuilder (); + pl.create_list (); + write_path (pl, svg, g, false); + return svg.str; + } + + private static void write_paths_as_glyph (PathList pl, StringBuilder svg, Glyph g) { + foreach (Path p in pl.paths) { + write_path_as_glyph (p, svg, g); + } + } + + private static void write_path_as_glyph (Path pl, StringBuilder svg, Glyph g) { + write_path (pl, svg, g, true); + } + + private static void write_path (Path p, StringBuilder svg, Glyph g, bool do_glyph) { + int i = 0; + EditPoint? n = null; + EditPoint m; + + if (p.points.size < 2) { + return; + } + + p.create_list (); + + foreach (var e in p.points) { + if (i == 0) { + add_abs_start (e, svg, g, do_glyph); + i++; + n = e; + continue; + } + + m = (!) n; + + add_abs_next (m, e, svg, g, do_glyph); + + n = e; + i++; + } + + if (!p.is_open ()) { + m = p.points.get (0); + add_abs_next ((!) n, m, svg, g, do_glyph); + close_path (svg); + } + } + + private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph) { + if (start.right_handle.type == PointType.LINE_QUADRATIC) { + add_abs_line_to (start, end, svg, g, do_glyph); + } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { + add_abs_line_to (start, end, svg, g, do_glyph); + } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { + add_quadratic_abs_path (start, end, svg, g, do_glyph); + } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) { + add_double_quadratic_abs_path (start, end, svg, g, do_glyph); + } else { + add_cubic_abs_path (start, end, svg, g, do_glyph); + } + } + + private static void add_abs_start (EditPoint ep, StringBuilder svg, Glyph g, bool to_glyph) { + double left = g.left_limit; + double baseline = -BirdFont.get_current_font ().base_line; + double height = g.get_height (); + + svg.append_printf ("M"); + + if (!to_glyph) { + svg.append_printf ("%s ", round (ep.x - left)); + svg.append_printf ("%s ", round (-ep.y + height / 2)); + } else { + svg.append_printf ("%s ", round (ep.x - left)); + svg.append_printf ("%s ", round (ep.y + baseline)); + } + } + + private static void close_path (StringBuilder svg) { + svg.append ("z"); + } + + private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg, Glyph g, bool to_glyph) { + double baseline = -BirdFont.get_current_font ().base_line; + double left = g.left_limit; + double height = g.get_height (); + + double xa, ya, xb, yb; + + Path.get_line_points (start, stop, out xa, out ya, out xb, out yb); + + double center_x = Glyph.xc (); + double center_y = Glyph.yc (); + + svg.append ("L"); + + if (!to_glyph) { + svg.append_printf ("%s ", round (xb - center_x - left)); + svg.append_printf ("%s ", round (yb - center_y + height / 2)); + } else { + svg.append_printf ("%s ", round (xb - center_x - left)); + svg.append_printf ("%s ", round (-yb + center_y + baseline)); + } + } + + private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { + EditPoint middle; + double x, y; + + x = start.get_right_handle ().x + (end.get_left_handle ().x - start.get_right_handle ().x) / 2; + y = start.get_right_handle ().y + (end.get_left_handle ().y - start.get_right_handle ().y) / 2; + + middle = new EditPoint (x, y, PointType.QUADRATIC); + middle.right_handle = end.get_left_handle ().copy (); + + add_quadratic_abs_path (start, middle, svg, g, to_glyph); + add_quadratic_abs_path (middle, end, svg, g, to_glyph); + } + + private static void add_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { + double left = g.left_limit; + double baseline = -BirdFont.get_current_font ().base_line; + double height = g.get_height (); + + double xa, ya, xb, yb, xc, yc, xd, yd; + + Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd); + + double center_x = Glyph.xc (); + double center_y = Glyph.yc (); + + // cubic path + if (!to_glyph) { + svg.append_printf ("Q"); + + svg.append_printf ("%s ", round (xb - center_x - left)); + svg.append_printf ("%s ", round (yb - center_y + height / 2)); + + svg.append_printf ("%s ", round (xd - center_x - left)); + svg.append_printf ("%s ", round (yd - center_y + height / 2)); + + } else { + svg.append_printf ("Q"); + + svg.append_printf ("%s ", round (xb - center_x - left)); + svg.append_printf ("%s ", round (-yb + center_y + baseline)); + + svg.append_printf ("%s ", round (xd - center_x - left)); + svg.append_printf ("%s ", round (-yd + center_y + baseline)); + } + } + + private static void add_cubic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { + double left = g.left_limit; + double baseline = -BirdFont.get_current_font ().base_line; + double height = g.get_height (); + + double xa, ya, xb, yb, xc, yc, xd, yd; + + Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd); + + double center_x = Glyph.xc (); + double center_y = Glyph.yc (); + + // cubic path + if (!to_glyph) { + svg.append_printf ("C"); + + svg.append_printf ("%s ", round (xb - center_x - left)); + svg.append_printf ("%s ", round (yb - center_y + height / 2)); + + svg.append_printf ("%s ", round (xc - center_x - left)); + svg.append_printf ("%s ", round (yc - center_y + height / 2)); + + svg.append_printf ("%s ", round (xd - center_x - left)); + svg.append_printf ("%s ", round (yd - center_y + height / 2)); + + } else { + svg.append_printf ("C"); + + svg.append_printf ("%s ", round (xb - center_x - left)); + svg.append_printf ("%s ", round (-yb + center_y + baseline)); + + svg.append_printf ("%s ", round (xc - center_x - left)); + svg.append_printf ("%s ", round (-yc + center_y + baseline)); + + svg.append_printf ("%s ", round (xd - center_x - left)); + svg.append_printf ("%s ", round (-yd + center_y + baseline)); + } + } + + /** Draw path from svg font data. */ + public static void draw_svg_path (Context cr, string svg, double x, double y) { + double x1, x2, x3; + double y1, y2, y3; + double px, py; + string[] d = svg.split (" "); + + if (d.length == 0) { + return; + } + + px = 0; + py = 0; + + cr.save (); + + cr.set_line_width (0); + + if (svg == "") { + return; + } + + for (int i = 0; i < d.length; i++) { + + // trim off leading white space + while (d[i].index_of (" ") == 0) { + d[i] = d[i].substring (1); // FIXME: maybe no ascii + } + + if (d[i].index_of ("L") == 0) { + x1 = double.parse (d[i].substring (1)) + x; + y1 = -double.parse (d[i+1]) + y; + cr.line_to (x1, y1); + + px = x1; + py = y1; + continue; + } + + if (d[i].index_of ("Q") == 0) { + x1 = double.parse (d[i].substring (1)) + x; + y1 = -double.parse (d[i+1]) + y; + + x2 = double.parse (d[i+2]) + x; + y2 = -double.parse (d[i+3]) + y; + + cr.curve_to ((px + 2 * x1) / 3, (py + 2 * y1) / 3, (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, x2, y2); + + px = x2; + py = y2; + continue; + } + + if (d[i].index_of ("C") == 0) { + x1 = double.parse (d[i].substring (1)) + x; + y1 = -double.parse (d[i+1]) + y; + + x2 = double.parse (d[i+2]) + x; + y2 = -double.parse (d[i+3]) + y; + + x3 = double.parse (d[i+4]) + x; + y3 = -double.parse (d[i+5]) + y; + + cr.curve_to (x1, y1, x2, y2, x3, y3); + + px = x3; + py = y3; + continue; + } + + if (d[i].index_of ("M") == 0) { + x1 = double.parse (d[i].substring (1)) + x; + y1 = -double.parse (d[i+1]) + y; + + cr.move_to (x1, y1); + + px = x1; + py = y1; + continue; + } + + if (d[i].index_of ("zM") == 0) { + cr.close_path (); + + x1 = double.parse (d[i].substring (2)) + x; + y1 = -double.parse (d[i+1]) + y; + + cr.move_to (x1, y1); + + px = x1; + py = y1; + continue; + } + + if (d[i].index_of ("z") == 0) { + cr.close_path (); + continue; + } + + } + + cr.fill (); + cr.restore (); + } + + } + + internal static string round (double p) { + string v = p.to_string (); + char[] c = new char [501]; + + v = p.format (c, "%3.15f"); + + if (v.index_of ("e") != -1) { + return "0.0"; + } + + return v; + } + + }
diff --git libbirdfont/Svg/SvgArc.vala(new)
--- /dev/null +++ b/libbirdfont/Svg/SvgArc.vala @@ -1,1 +1,182 @@ + /* + * BirdFont code from SVG Salamander + * + * Copyright (c) 2004, Mark McKay + * Copyright (c) 2014, Johan Mattsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Mark McKay can be contacted at mark@kitfox.com. Salamander and other + * projects can be found at http://www.kitfox.com + * + * Created on January 26, 2004, 8:40 PM + * Adapded to BirdFont on Juli 2, 2014, 5:01 PM + */ + + using Math; + + namespace BirdFont { + + /** Convert an SVG arc instruction to a Beziér path. */ + static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, double y0, double rx, double ry, double angle, bool largeArcFlag, bool sweepFlag, double x, double y) { + + // + // Elliptical arc implementation based on the SVG specification notes + // + + double dx2, dy2, cosAngle, sinAngle; + double x1, y1, Prx, Pry, Px1, Py1, radiiCheck; + double sign, sq, coef, cx1, cy1; + double sx2, sy2, cx, cy; + double ux, uy, vx, vy, p, n; + double angleStart, angleExtent; + double s, step, theta; + + // Compute the half distance between the current and the final point + dx2 = (x0 - x) / 2.0; + dy2 = (y0 - y) / 2.0; + + // Convert angle from degrees to radians + angle = 2 * PI * ((angle % 360.0) / 360.0); + + cosAngle = cos (angle); + sinAngle = sin (angle); + + // + // Step 1 : Compute (x1, y1) + // + x1 = cosAngle * dx2 + sinAngle * dy2; + y1 = -sinAngle * dx2 + cosAngle * dy2; + + // Ensure radii are large enough + rx = fabs(rx); + ry = fabs(ry); + Prx = rx * rx; + Pry = ry * ry; + Px1 = x1 * x1; + Py1 = y1 * y1; + + + // Check that radii are large enough + radiiCheck = Px1 / Prx + Py1 / Pry; + + if (radiiCheck > 1) { + rx = sqrt (radiiCheck) * rx; + ry = sqrt (radiiCheck) * ry; + Prx = rx * rx; + Pry = ry * ry; + } + + // + // Step 2 : Compute (cx1, cy1) + // + sign = (largeArcFlag == sweepFlag) ? -1 : 1; + sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1)) / ((Prx * Py1) + (Pry * Px1)); + sq = (sq < 0) ? 0 : sq; + coef = (sign * Math.sqrt(sq)); + cx1 = coef * ((rx * y1) / ry); + cy1 = coef * -((ry * x1) / rx); + + // + // Step 3 : Compute (cx, cy) from (cx1, cy1) + // + + sx2 = (x0 + x) / 2.0; + sy2 = (y0 + y) / 2.0; + cx = sx2 - (cosAngle * cx1 - sinAngle * cy1); + cy = sy2 - (sinAngle * cx1 + cosAngle * cy1); + + // + // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) + // + + ux = (x1 - cx1) / rx; + uy = (y1 - cy1) / ry; + vx = (-x1 - cx1) / rx; + vy = (-y1 - cy1) / ry; + + // Compute the angle start + n = sqrt((ux * ux) + (uy * uy)); + p = ux; // (1 * ux) + (0 * uy) + sign = (uy < 0) ? -1d : 1d; + angleStart = sign * acos(p / n); + + // Compute the angle extent + n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); + p = ux * vx + uy * vy; + sign = (ux * vy - uy * vx < 0) ? -1d : 1d; + angleExtent = sign * Math.acos(p / n); + + if(!sweepFlag && angleExtent > 0) { + angleExtent -= 2 *PI; + } else if (sweepFlag && angleExtent < 0) { + angleExtent += 2 *PI; + } + angleExtent %= 2 * PI; + angleStart %= 2 * PI; + + angleExtent *= -1; + angleStart *= -1; + + // Approximate the path with Beziér points + s = (angleExtent > 0) ? 1 : -1; + step = fabs (angleExtent) / (2 * fabs (angleExtent)); + + theta = PI - angleStart - angleExtent; + + bezier_points[bi].type = 'L'; + bezier_points[bi].svg_type = 'a'; + + bezier_points[bi].x0 = cx + rx * cos (theta); + bezier_points[bi].y0 = cy + ry * sin (theta); + + bi++; + + for (double a = 0; a < fabs (angleExtent); a += step) { + theta = PI - angleStart - angleExtent + s * a; + + return_if_fail (0 <= bi < bezier_points.length); + + bezier_points[bi].type = 'S'; + bezier_points[bi].svg_type = 'a'; + + bezier_points[bi].x0 = cx + rx * cos (theta); + bezier_points[bi].y0 = cy + ry * sin (theta); + + bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); + bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); + + bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); + bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); + + bi++; + } + } + + } +
--- /dev/null +++ b/libbirdfont/Svg/SvgFile.vala @@ -1,1 +1,22 @@ + /* + Copyright (C) 2015 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. + */ + + namespace BirdFont { + + public class SvgFile : GLib.Object { + + } + + }
--- /dev/null +++ b/libbirdfont/Svg/SvgFont.vala @@ -1,1 +1,265 @@ + /* + Copyright (C) 2013 2014 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; + + namespace BirdFont { + + class SvgFont : GLib.Object { + Font font; + double units = 1; + double font_advance = 0; + + public SvgFont (Font f) { + this.font = f; + } + + /** Load svg font from file. */ + public void load (string path) { + string data; + XmlParser xml_parser; + try { + FileUtils.get_contents (path, out data); + xml_parser = new XmlParser (data); + parse_svg_font (xml_parser.get_root_tag ()); + } catch (GLib.Error e) { + warning (e.message); + } + } + + void parse_svg_font (Tag tag) { + foreach (Tag t in tag) { + if (t.get_name () == "defs") { + parse_svg_font (t); + } + + if (t.get_name () == "font") { + parse_font_tag (t); + parse_svg_font (t); + } + + if (t.get_name () == "font-face") { + parse_font_limits (t); + } + + if (t.get_name () == "hkern") { + parse_hkern (t); + } + + if (t.get_name () == "glyph") { + parse_glyph (t); + } + } + } + + void parse_hkern (Tag tag) { + string left = ""; + string right = ""; + string left_name = ""; + string right_name = ""; + double kerning = 0; + unichar l, r; + StringBuilder sl, sr; + GlyphRange grr, grl; + KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes (); + + foreach (Attribute attr in tag.get_attributes ()) { + // left + if (attr.get_name () == "u1") { + left = attr.get_content (); + } + + // right + if (attr.get_name () == "u2") { + right = attr.get_content (); + } + + if (attr.get_name () == "g1") { + left_name = attr.get_content (); + } + + if (attr.get_name () == "g2") { + right_name = attr.get_content (); + } + + // kerning + if (attr.get_name () == "k") { + kerning = double.parse (attr.get_content ()) * units; + } + } + + // FIXME: ranges and sequences for u1 & u2 + g1 & g2 + foreach (string lk in left.split (",")) { + foreach (string rk in right.split (",")) { + l = get_unichar (lk); + r = get_unichar (rk); + + sl = new StringBuilder (); + sl.append_unichar (l); + + sr = new StringBuilder (); + sr.append_unichar (r); + + try { + grl = new GlyphRange (); + grl.parse_ranges (sl.str); + + grr = new GlyphRange (); + grr.parse_ranges (sr.str); + + classes.set_kerning (grl, grr, -kerning); + } catch (MarkupError e) { + warning (e.message); + } + } + } + } + + void parse_font_limits (Tag tag) { + double top_limit = 0; + double bottom_limit = 0; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "units-per-em") { + units = 100.0 / double.parse (attr.get_content ()); + } + } + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "ascent") { + top_limit = double.parse (attr.get_content ()); + } + + if (attr.get_name () == "descent") { + bottom_limit = double.parse (attr.get_content ()); + } + } + + top_limit *= units; + bottom_limit *= units; + + font.bottom_limit = bottom_limit; + font.top_limit = top_limit; + } + + void parse_font_tag (Tag tag) { + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "horiz-adv-x") { + font_advance = double.parse (attr.get_content ()); + } + + if (attr.get_name () == "id") { + font.set_name (attr.get_content ()); + } + } + } + + /** Obtain unichar value from either a character, a hex representation of + * a character or series of characters (f, &#x6d; or ffi). + */ + static unichar get_unichar (string val) { + string v = val; + unichar unicode_value; + + if (val == "&") { + return '&'; + } + + // TODO: parse ligatures + if (v.has_prefix ("&")) { + // parse hex value + v = v.substring (0, v.index_of (";")); + v = v.replace ("&#x", "U+"); + v = v.replace (";", ""); + unicode_value = Font.to_unichar (v); + } else { + // obtain unicode value + + if (v.char_count () > 1) { + warning ("font contains ligatures"); + return '\0'; + } + + unicode_value = v.get_char (0); + } + + return unicode_value; + } + + bool is_ligature (string v) { + if (v.has_prefix ("&")) { + return false; + } + + return v.char_count () > 1; + } + + void parse_glyph (Tag tag) { + unichar unicode_value = 0; + string glyph_name = ""; + string svg = ""; + Glyph glyph; + GlyphCollection glyph_collection; + double advance = font_advance; + string ligature = ""; + SvgParser parser = new SvgParser (); + StringBuilder unicode_name; + + parser.set_format (SvgFormat.INKSCAPE); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "unicode") { + unicode_value = get_unichar (attr.get_content ()); + + if (glyph_name == "") { + glyph_name = attr.get_content (); + } + + if (is_ligature (attr.get_content ())) { + ligature = attr.get_content (); + } + } + + // svg data + if (attr.get_name () == "d") { + svg = attr.get_content (); + } + + if (attr.get_name () == "glyph-name") { + glyph_name = attr.get_content (); + } + + if (attr.get_name () == "horiz-adv-x") { + advance = double.parse (attr.get_content ()); + } + } + + unicode_name = new StringBuilder (); + unicode_name.append_unichar (unicode_value); + + glyph = new Glyph (unicode_name.str, unicode_value); + parser.add_path_to_glyph (svg, glyph, true, units); + glyph.right_limit = glyph.left_limit + advance * units; + + // FIXME: add svg font ligatures + + glyph_collection = new GlyphCollection (unicode_value, glyph_name); + glyph_collection.insert_glyph (glyph, true); + + font.add_glyph_collection (glyph_collection); + } + } + + }
--- /dev/null +++ b/libbirdfont/Svg/SvgFontFormatWriter.vala @@ -1,1 +1,133 @@ + /* + Copyright (C) 2012, 2014 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. + */ + + namespace BirdFont { + + class SvgFontFormatWriter : GLib.Object { + + DataOutputStream os; + + public SvgFontFormatWriter () { + } + + public void open (File file) throws Error { + if (file.query_exists ()) { + throw new FileError.EXIST ("SvgFontFormatWriter: file exists."); + } + + os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION)); + } + + public void close () throws Error { + os.close (); + } + + public void write_font_file (Font font) throws Error { + string font_name = font.get_full_name (); + + int units_per_em = 100; + + int ascent = 80; + int descent = -20; + + StringBuilder b; + + Glyph? g; + Glyph glyph; + unichar index = 0; + + string uni; + + KerningClasses classes; + + put ("""<?xml version="1.0" standalone="no"?>"""); + put ("""<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >"""); + put ("""<svg>"""); + + // (metadata goes here) + + put ("<defs>"); + + put (@"<font id=\"$font_name\" horiz-adv-x=\"250\" >"); + put (@"<font-face units-per-em=\"$(to_float (units_per_em))\" ascent=\"$(to_float (ascent))\" descent=\"$(to_float (descent))\" />"); + + // (missing-glyph goes here) + + // regular glyphs + while (true) { + g = font.get_glyph_index (index++); + + if (g == null) { + break; + } + + glyph = (!) g; + + b = new StringBuilder (); + b.append_unichar (glyph.get_unichar ()); + + if (glyph.get_unichar () >= ' ' && b.str.validate ()) { + if (b.str == "\"" || b.str == "&" || b.str == "<" || b.str == ">") { + uni = Font.to_hex_code (glyph.get_unichar ()); + put (@"<glyph unicode=\"&#x$(uni);\" horiz-adv-x=\"$(to_float (glyph.get_width ()))\" d=\"$(glyph.get_svg_data ())\" />"); + } else { + put (@"<glyph unicode=\"$(b.str)\" horiz-adv-x=\"$(to_float (glyph.get_width ()))\" d=\"$(glyph.get_svg_data ())\" />"); + } + } + } + + // FIXME: ligatures + classes = BirdFont.get_current_font ().get_kerning_classes (); + classes.all_pairs ((kerning) => { + string l, r; + + foreach (Kerning k in kerning.kerning) { + try { + if (k.glyph != null) { + l = Font.to_hex_code (kerning.character.unichar_code); + r = Font.to_hex_code (((!)k.glyph).unichar_code); + os.put_string (@"<hkern u1=\"&#x$l;\" u2=\"&#x$r;\" k=\"$(to_float (-k.val))\"/>\n"); + } else { + warning ("No glyph."); + } + } catch (GLib.Error e) { + warning (e.message); + } + } + }); + + put ("</font>"); + put ("</defs>"); + put ("</svg>"); + } + + string to_float (double d) { + string s = @"$d"; + if (s.index_of ("e") != -1) { + return "0".dup (); + } + return s.replace (",", "."); + } + + /** Write a new line */ + private void put (string line) throws Error { + os.put_string (line); + os.put_string ("\n"); + } + + } + + + }
--- /dev/null +++ b/libbirdfont/Svg/SvgParser.vala @@ -1,1 +1,2115 @@ + /* + Copyright (C) 2012 2013 2014 2015 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 Math; + + namespace BirdFont { + + public enum SvgFormat { + NONE, + INKSCAPE, + ILLUSTRATOR + } + + public enum SvgType { + COLOR, + REGULAR + } + + public class SvgParser { + + SvgFormat format = SvgFormat.ILLUSTRATOR; + + public SvgParser () { + } + + public void set_format (SvgFormat f) { + format = f; + } + + public static void import (SvgType type) { + FileChooser fc = new FileChooser (); + fc.file_selected.connect ((p) => { + string path; + + if (p == null) { + return; + } + + path = (!) p; + + if (type == SvgType.REGULAR) { + import_svg (path); + } else if (type == SvgType.COLOR) { + Glyph glyph = MainWindow.get_current_glyph (); + import_color_svg (glyph, path); + } + }); + + fc.add_extension ("svg"); + MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD); + } + + public static void import_color_svg (Glyph glyph, string path) { + try { + string data; + FileUtils.get_contents (path, out data); + glyph.color_svg_data = data; + + if (glyph.color_svg_data != null) { + glyph.svg_x = glyph.left_limit; + glyph.svg_y = BirdFont.get_current_font ().top_position; + } + } catch (GLib.Error e) { + warning (e.message); + } + } + + public static void import_folder (SvgType type) { + FileChooser fc = new FileChooser (); + fc.file_selected.connect ((p) => { + string path; + File svg_folder; + File svg; + bool imported; + FileEnumerator enumerator; + FileInfo? file_info; + string file_name; + Font font; + + if (p == null) { + return; + } + + path = (!) p; + svg_folder = File.new_for_path (path); + font = BirdFont.get_current_font (); + + try { + enumerator = svg_folder.enumerate_children (FileAttribute.STANDARD_NAME, 0); + while ((file_info = enumerator.next_file ()) != null) { + file_name = ((!) file_info).get_name (); + + if (file_name.has_suffix (".svg")) { + svg = get_child (svg_folder, file_name); + imported = import_svg_file (font, svg, type); + + if (!imported) { + warning ("Can't import %s.", (!) svg.get_path ()); + } else { + font.touch (); + } + } + } + } catch (Error e) { + warning (e.message); + } + }); + + MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD | FileChooser.DIRECTORY); + } + + public static void import_svg_data (string xml_data, SvgFormat format = SvgFormat.NONE) { + PathList path_list = new PathList (); + Glyph glyph; + string[] lines = xml_data.split ("\n"); + bool has_format = false; + SvgParser parser = new SvgParser (); + XmlParser xmlparser; + + foreach (string l in lines) { + if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) { + parser.set_format (SvgFormat.ILLUSTRATOR); + has_format = true; + } + + if (l.index_of ("Inkscape") > -1 || l.index_of ("inkscape") > -1) { + parser.set_format (SvgFormat.INKSCAPE); + has_format = true; + } + } + + if (format != SvgFormat.NONE) { + parser.set_format (format); + } + + // parse the file + if (!has_format) { + warn_if_test ("No format identifier found in SVG parser.\n"); + } + + xmlparser = new XmlParser (xml_data); + + if (!xmlparser.validate()) { + warning("Invalid XML in SVG parser."); + } + + path_list = parser.parse_svg_file (xmlparser.get_root_tag ()); + + glyph = MainWindow.get_current_glyph (); + foreach (Path p in path_list.paths) { + FastPath path = new FastPath.for_path (p); + glyph.add_object (path); + glyph.add_active_object (null, path); // FIXME: groups + path.update_region_boundaries (); + } + + glyph.close_path (); + } + + public static string replace (string content, string start, string stop, string replacement) { + int i_tag = content.index_of (start); + int end_tag = content.index_of (stop, i_tag); + string c = ""; + + if (i_tag > -1) { + c = content.substring (0, i_tag) + + replacement + + content.substring (end_tag + stop.length); + } else { + c = content; + } + + return c; + } + + public static void import_svg (string path) { + string svg_data; + try { + FileUtils.get_contents (path, out svg_data); + } catch (GLib.Error e) { + warning (e.message); + } + import_svg_data (svg_data); + } + + private PathList parse_svg_file (Tag tag) { + Layer pl = new Layer (); + + foreach (Tag t in tag) { + + if (t.get_name () == "g") { + parse_layer (t, pl); + } + + if (t.get_name () == "switch") { + parse_layer (t, pl); + } + + if (t.get_name () == "path") { + parse_path (t, pl); + } + + if (t.get_name () == "polygon") { + parse_polygon (t, pl); + } + + if (t.get_name () == "polyline") { + parse_polyline (t, pl); + } + + if (t.get_name () == "circle") { + parse_circle (t, pl); + } + + if (t.get_name () == "ellipse") { + parse_ellipse (t, pl); + } + + if (t.get_name () == "line") { + parse_line (t, pl); + } + } + + return pl.get_all_paths (); + } + + private void parse_layer (Tag tag, Layer pl) { + Layer layer; + 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) { + return; + } + + foreach (Tag t in tag) { + if (t.get_name () == "path") { + parse_path (t, pl); + } + + if (t.get_name () == "g") { + layer = new Layer (); + parse_layer (t, layer); + pl.subgroups.add (layer); + } + + if (t.get_name () == "polygon") { + parse_polygon (t, pl); + } + + if (t.get_name () == "polyline") { + parse_polyline (t, pl); + } + + if (t.get_name () == "rect") { + parse_rect (t, pl); + } + + if (t.get_name () == "circle") { + parse_circle (t, pl); + } + + if (t.get_name () == "ellipse") { + parse_ellipse (t, pl); + } + + if (t.get_name () == "line") { + parse_line (t, pl); + } + } + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform (attr.get_content (), pl); + } + } + } + + private void transform (string transform_functions, Layer layer) { + PathList path_list = new PathList (); + + foreach (Object o in layer.objects) { + if (o is FastPath) { + path_list.add (((FastPath) o).get_path ()); + } + } + + transform_paths (transform_functions, path_list); + transform_subgroups (transform_functions, layer); + } + + private void transform_subgroups (string transform_functions, Layer layer) { + foreach (Layer subgroup in layer.subgroups) { + transform (transform_functions, subgroup); + } + } + + private void transform_paths (string transform_functions, PathList pl) { + string data = transform_functions.dup (); + string[] functions; + + // use only a single space as separator + while (data.index_of (" ") > -1) { + data = data.replace (" ", " "); + } + + return_if_fail (data.index_of (")") > -1); + + // add separator + data = data.replace (") ", "|"); + data = data.replace (")", "|"); + functions = data.split ("|"); + + for (int i = functions.length - 1; i >= 0; i--) { + if (functions[i].has_prefix ("translate")) { + translate (functions[i], pl); + } + + if (functions[i].has_prefix ("scale")) { + scale (functions[i], pl); + } + + if (functions[i].has_prefix ("matrix")) { + matrix (functions[i], pl); + } + + // TODO: rotate etc. + } + } + + /** @param path a path in the cartesian coordinate system + * The other parameters are in the SVG coordinate system. + */ + public static void apply_matrix (Path path, double a, double b, double c, + double d, double e, double f){ + + double dx, dy; + Font font = BirdFont.get_current_font (); + Glyph glyph = MainWindow.get_current_glyph (); + + foreach (EditPoint ep in path.points) { + ep.tie_handles = false; + ep.reflective_point = false; + } + + foreach (EditPoint ep in path.points) { + apply_matrix_on_handle (ep.get_right_handle (), a, b, c, d, e, f); + apply_matrix_on_handle (ep.get_left_handle (), a, b, c, d, e, f); + + ep.independent_y = font.top_position - ep.independent_y; + ep.independent_x -= glyph.left_limit; + + dx = a * ep.independent_x + c * ep.independent_y + e; + dy = b * ep.independent_x + d * ep.independent_y + f; + + ep.independent_x = dx; + ep.independent_y = dy; + + ep.independent_y = font.top_position - ep.independent_y; + ep.independent_x += glyph.left_limit; + } + } + + public static void apply_matrix_on_handle (EditPointHandle h, + double a, double b, double c, + double d, double e, double f){ + + double dx, dy; + Font font = BirdFont.get_current_font (); + Glyph glyph = MainWindow.get_current_glyph (); + + h.y = font.top_position - h.y; + h.x -= glyph.left_limit; + + dx = a * h.x + c * h.y + e; + dy = b * h.x + d * h.y + f; + + h.x = dx; + h.y = dy; + + h.y = font.top_position - h.y; + h.x += glyph.left_limit; + } + + + private void matrix (string function, PathList pl) { + string parameters = get_transform_parameters (function); + string[] p = parameters.split (" "); + + if (p.length != 6) { + warning ("Expecting six parameters for matrix transformation."); + return; + } + + foreach (Path path in pl.paths) { + apply_matrix (path, parse_double (p[0]), parse_double (p[1]), + parse_double (p[2]), parse_double (p[3]), + parse_double (p[4]), parse_double (p[5])); + } + } + + private void scale (string function, PathList pl) { + string parameters = get_transform_parameters (function); + string[] p = parameters.split (" "); + double x, y; + + x = 1; + y = 1; + + if (p.length > 0) { + x = parse_double (p[0]); + } + + if (p.length > 1) { + y = parse_double (p[1]); + } + + foreach (Path path in pl.paths) { + path.scale (-x, y); + } + } + + private void translate (string function, PathList pl) { + string parameters = get_transform_parameters (function); + string[] p = parameters.split (" "); + double x, y; + + x = 0; + y = 0; + + if (p.length > 0) { + x = parse_double (p[0]); + } + + if (p.length > 1) { + y = parse_double (p[1]); + } + + foreach (Path path in pl.paths) { + path.move (x, -y); + } + } + + 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 void parse_circle (Tag tag, Layer pl) { + Path p; + double x, y, r; + Glyph g; + PathList npl; + BezierPoints[] bezier_points; + SvgStyle style = new SvgStyle (); + bool hidden = false; + + npl = new PathList (); + + x = 0; + y = 0; + r = 0; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "cx") { + x = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "cy") { + y = -parse_double (attr.get_content ()); + } + + if (attr.get_name () == "r") { + r = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "display" && attr.get_content () == "none") { + hidden = true; + } + } + + style = SvgStyle.parse (tag.get_attributes ()); + + if (hidden) { + return; + } + + bezier_points = new BezierPoints[1]; + bezier_points[0] = new BezierPoints (); + bezier_points[0].type == 'L'; + bezier_points[0].x0 = x; + bezier_points[0].y0 = y; + + g = MainWindow.get_current_glyph (); + move_and_resize (bezier_points, 1, false, 1, g); + + p = CircleTool.create_circle (bezier_points[0].x0, + bezier_points[0].y0, r, PointType.CUBIC); + + npl.add (p); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform_paths (attr.get_content (), npl); + } + } + + style.apply (npl); + pl.append_paths (npl); + } + + private void parse_ellipse (Tag tag, Layer pl) { + Path p; + double x, y, rx, ry; + Glyph g; + PathList npl; + BezierPoints[] bezier_points; + SvgStyle style = new SvgStyle (); + bool hidden = false; + + npl = new PathList (); + + x = 0; + y = 0; + rx = 0; + ry = 0; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "cx") { + x = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "cy") { + y = -parse_double (attr.get_content ()); + } + + if (attr.get_name () == "rx") { + rx = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "ry") { + ry = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "display" && attr.get_content () == "none") { + hidden = true; + } + } + + style = SvgStyle.parse (tag.get_attributes ()); + + if (hidden) { + return; + } + + bezier_points = new BezierPoints[1]; + bezier_points[0] = new BezierPoints (); + bezier_points[0].type == 'L'; + bezier_points[0].x0 = x; + bezier_points[0].y0 = y; + + g = MainWindow.get_current_glyph (); + move_and_resize (bezier_points, 1, false, 1, g); + + p = CircleTool.create_ellipse (bezier_points[0].x0, + bezier_points[0].y0, rx, ry, PointType.CUBIC); + + npl.add (p); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform_paths (attr.get_content (), npl); + } + } + + style.apply (npl); + pl.append_paths (npl); + } + + private void parse_line (Tag tag, Layer pl) { + Path p; + double x1, y1, x2, y2; + BezierPoints[] bezier_points; + Glyph g; + PathList npl = new PathList (); + SvgStyle style = new SvgStyle (); + bool hidden = false; + + x1 = 0; + y1 = 0; + x2 = 0; + y2 = 0; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "x1") { + x1 = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "y1") { + y1 = -parse_double (attr.get_content ()); + } + + if (attr.get_name () == "x2") { + x2 = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "xy") { + y2 = -parse_double (attr.get_content ()); + } + + if (attr.get_name () == "display" && attr.get_content () == "none") { + hidden = true; + } + } + + style = SvgStyle.parse (tag.get_attributes ()); + + if (hidden) { + return; + } + + bezier_points = new BezierPoints[2]; + bezier_points[0] = new BezierPoints (); + bezier_points[0].type == 'L'; + bezier_points[0].x0 = x1; + bezier_points[0].y0 = y1; + + bezier_points[1] = new BezierPoints (); + bezier_points[1].type == 'L'; + bezier_points[1].x0 = x2; + bezier_points[1].y0 = y2; + + g = MainWindow.get_current_glyph (); + move_and_resize (bezier_points, 4, false, 1, g); + + p = new Path (); + + p.add (bezier_points[0].x0, bezier_points[0].y0); + p.add (bezier_points[1].x0, bezier_points[1].y0); + + p.close (); + p.create_list (); + p.recalculate_linear_handles (); + + npl.add (p); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform_paths (attr.get_content (), npl); + } + } + + style.apply (npl); + pl.append_paths (npl); + } + + private void parse_rect (Tag tag, Layer layer) { + Path p; + double x, y, x2, y2; + BezierPoints[] bezier_points; + Glyph g; + PathList npl = new PathList (); + SvgStyle style = new SvgStyle (); + bool hidden = false; + EditPoint ep; + + x = 0; + y = 0; + x2 = 0; + y2 = 0; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "x") { + x = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "y") { + y = -parse_double (attr.get_content ()); + } + + if (attr.get_name () == "width") { + x2 = parse_double (attr.get_content ()); + } + + if (attr.get_name () == "height") { + y2 = -parse_double (attr.get_content ()); + } + + if (attr.get_name () == "display" && attr.get_content () == "none") { + hidden = true; + } + } + + style = SvgStyle.parse (tag.get_attributes ()); + + if (hidden) { + return; + } + + x2 += x; + y2 += y; + + bezier_points = new BezierPoints[4]; + bezier_points[0] = new BezierPoints (); + bezier_points[0].type == 'L'; + bezier_points[0].x0 = x; + bezier_points[0].y0 = y; + + bezier_points[1] = new BezierPoints (); + bezier_points[1].type == 'L'; + bezier_points[1].x0 = x2; + bezier_points[1].y0 = y; + + bezier_points[2] = new BezierPoints (); + bezier_points[2].type == 'L'; + bezier_points[2].x0 = x2; + bezier_points[2].y0 = y2; + + bezier_points[3] = new BezierPoints (); + bezier_points[3].type == 'L'; + bezier_points[3].x0 = x; + bezier_points[3].y0 = y2; + + g = MainWindow.get_current_glyph (); + move_and_resize (bezier_points, 4, false, 1, g); + + p = new Path (); + + ep = p.add (bezier_points[0].x0, bezier_points[0].y0); + ep.set_point_type (PointType.CUBIC); + + ep = p.add (bezier_points[1].x0, bezier_points[1].y0); + ep.set_point_type (PointType.CUBIC); + + ep = p.add (bezier_points[2].x0, bezier_points[2].y0); + ep.set_point_type (PointType.CUBIC); + + ep = p.add (bezier_points[3].x0, bezier_points[3].y0); + ep.set_point_type (PointType.CUBIC); + + p.close (); + p.create_list (); + p.recalculate_linear_handles (); + + npl.add (p); + + // FIXME: right layer for other transforms + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform_paths (attr.get_content (), npl); + } + } + + style.apply (npl); + layer.append_paths (npl); + } + + private void parse_polygon (Tag tag, Layer layer) { + PathList path_list = get_polyline (tag); + + foreach (Path p in path_list.paths) { + p.close (); + } + + layer.append_paths (path_list); + } + + + private void parse_polyline (Tag tag, Layer layer) { + layer.append_paths (get_polyline (tag)); + } + + private PathList get_polyline (Tag tag) { + Path p = new Path (); + bool hidden = false; + PathList path_list = new PathList (); + SvgStyle style = new SvgStyle (); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "points") { + p = parse_poly_data (attr.get_content ()); + } + + if (attr.get_name () == "display" && attr.get_content () == "none") { + hidden = true; + } + } + + style = SvgStyle.parse (tag.get_attributes ()); + + if (hidden) { + return path_list; + } + + path_list.add (p); + style.apply (path_list); + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform_paths (attr.get_content (), path_list); + } + } + + return path_list; + } + + private void parse_path (Tag tag, Layer layer) { + Glyph glyph = MainWindow.get_current_glyph (); + PathList path_list = new PathList (); + SvgStyle style = new SvgStyle (); + bool hidden = false; + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "d") { + path_list = parse_svg_data (attr.get_content (), glyph); + } + + 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; + } + } + + style = SvgStyle.parse (tag.get_attributes ()); + + if (hidden) { + return; + } + + foreach (Path path in path_list.paths) { + layer.add_path (path); + } + + style.apply (path_list); + + // assume the even odd rule is applied and convert the path + // to a path using the non-zero rule + int inside_count; + bool inside; + foreach (Object o1 in layer.objects) { + if (o1 is FastPath) { + Path p1 = ((FastPath) o1).get_path (); + inside_count = 0; + + foreach (Object o2 in layer.objects) { + if (o2 is FastPath) { + Path p2 = ((FastPath) o2).get_path (); + + if (p1 != p2) { + inside = true; + + foreach (EditPoint ep in p1.points) { + if (!is_inside (ep, p2)) { + inside = false; + } + } + + if (inside) { + inside_count++; + } + } + } + } + + if (inside_count % 2 == 0) { + p1.force_direction (Direction.CLOCKWISE); + } else { + p1.force_direction (Direction.COUNTER_CLOCKWISE); + } + } + } + + foreach (Attribute attr in tag.get_attributes ()) { + if (attr.get_name () == "transform") { + transform_paths (attr.get_content (), path_list); + } + } + } + + public static void create_lines_for_segment (Path path, EditPoint start, EditPoint end, double tolerance) { + double x1, x2, x3; + double y1, y2, y3; + double step_start, step, step_end; + + path.add (start.x, start.y); + + step_start = 0; + step = 0.5; + step_end = 1; + + while (true) { + Path.get_point_for_step (start, end, step_start, out x1, out y1); + Path.get_point_for_step (start, end, step, out x2, out y2); + Path.get_point_for_step (start, end, step_end, out x3, out y3); + + if (!StrokeTool.is_flat (x1, y1, x2, y2, x3, y3, tolerance) + && step_end - step / 2.0 > step_start + && step_end - step / 2.0 > 0.1 + && step > 0.05 + && Path.distance_to_point (start, end) > 1) { + + step /= 2.0; + + if (step < 0.05) { + step = 0.05; + } else { + step_end = step_start + 2 * step; + } + } else { + path.add (x3, y3); + + if (step_end + step < 1) { + step_start = step_end; + step_end += step; + } else { + break; + } + } + } + } + + public static Path get_lines (Path p) { + EditPoint start; + Path path = new Path (); + + if (p.points.size == 0) { + return path; + } + + // create a set of straight lines + start = p.points.get (p.points.size - 1); + + foreach (EditPoint end in p.points) { + create_lines_for_segment (path, start, end, 1); + start = end; + } + + return path; + } + + /** Check if a point is inside using the even odd fill rule. + * The path should only have straight lines. + */ + public static bool is_inside (EditPoint point, Path path) { + EditPoint prev; + bool inside = false; + + if (path.points.size <= 1) { + return false; + } + + if (!(path.xmin <= point.x <= path.xmax)) { + return false; + } + + if (!(path.ymin <= point.y <= path.ymax)) { + return false; + } + + prev = path.points.get (path.points.size - 1); + + foreach (EditPoint p in path.points) { + if ((p.y > point.y) != (prev.y > point.y) + && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { + inside = !inside; + } + + prev = p; + } + + return inside; + } + + /** Add space as separator to svg data. + * @param d svg data + */ + 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; + } + + public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { + PathList p = parse_svg_data (d, g, svg_glyph, units); + foreach (Path path in p.paths) { + g.add_path (path); + } + } + + /** + * @param d svg data + * @param glyph use lines from this glyph but don't add the generated paths + * @param svg_glyph parse svg glyph with origo in lower left corner + * + * @return the new paths + */ + public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { + double px = 0; + double py = 0; + double px2 = 0; + double py2 = 0; + double cx = 0; + double cy = 0; + string data; + Font font; + PathList path_list = new PathList (); + BezierPoints[] bezier_points; + string[] c; + double arc_rx, arc_ry; + double arc_rotation; + int large_arc; + int arc_sweep; + double arc_dest_x, arc_dest_y; + + font = BirdFont.get_current_font (); + + data = add_separators (d); + c = data.split (" "); + bezier_points = new BezierPoints[8 * c.length + 1]; // the arc instruction can use up to eight points + + for (int i = 0; i < 2 * c.length + 1; i++) { + bezier_points[i] = new BezierPoints (); + } + + int bi = 0; + + // 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; // if (svg_glyph) ? + + 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 = parse_double (c[++i]); + 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; + + add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); + + 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 = parse_double (c[++i]); + 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; + + add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); + + 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."); + return path_list; + } + + move_and_resize (bezier_points, bi, svg_glyph, units, glyph); + + if (format == SvgFormat.ILLUSTRATOR) { + path_list = create_paths_illustrator (bezier_points, bi); + } else { + path_list = create_paths_inkscape (bezier_points, bi); + } + + // TODO: Find out if it is possible to tie handles. + return path_list; + } + + void move_and_resize (BezierPoints[] b, int num_b, bool svg_glyph, double units, Glyph glyph) { + Font font = BirdFont.get_current_font (); + + for (int i = 0; i < num_b; i++) { + // resize all points + b[i].x0 *= units; + b[i].y0 *= units; + b[i].x1 *= units; + b[i].y1 *= units; + b[i].x2 *= units; + b[i].y2 *= units; + + // move all points + if (svg_glyph) { + b[i].x0 += glyph.left_limit; + b[i].y0 += font.base_line; + b[i].x1 += glyph.left_limit; + b[i].y1 += font.base_line; + b[i].x2 += glyph.left_limit; + b[i].y2 += font.base_line; + } else { + b[i].x0 += glyph.left_limit; + b[i].y0 += font.top_position; + b[i].x1 += glyph.left_limit; + b[i].y1 += font.top_position; + b[i].x2 += glyph.left_limit; + b[i].y2 += font.top_position; + } + } + } + + void find_last_handle (int start_index, BezierPoints[] b, int num_b, out double left_x, out double left_y, out PointType last_type) { + BezierPoints last = new BezierPoints (); + bool found = false; + + left_x = 0; + left_y = 0; + last_type = PointType.NONE; + + return_if_fail (b.length != 0); + return_if_fail (b[0].type != 'z'); + return_if_fail (num_b < b.length); + + if (num_b == 2) { + left_x = b[0].x0 + (b[1].x0 - b[0].x0) / 3.0; + left_y = b[0].y0 + (b[1].y0 - b[0].y0) / 3.0; + last_type = PointType.LINE_CUBIC; + return; + } + + for (int i = start_index; i < num_b; i++) { + switch (b[i].type) { + case 'Q': + break; + case 'C': + break; + case 'z': + found = true; + break; + default: + break; + } + + if (found || i + 1 == num_b) { + + return_if_fail (i >= 1); + + if (b[i - 1].type == 'Q') { + return_if_fail (i >= 1); + left_x = b[i - 1].x0; + left_y = b[i - 1].y0; + last_type = PointType.QUADRATIC; + } else if (b[i - 1].type == 'C') { + return_if_fail (i >= 1); + left_x = b[i - 1].x1; + left_y = b[i - 1].y1; + last_type = PointType.CUBIC; + } else if (b[i - 1].type == 'S') { + return_if_fail (i >= 1); + left_x = b[i - 1].x1; + left_y = b[i - 1].y1; + last_type = PointType.CUBIC; + } else if (b[i - 1].type == 'L' || last.type == 'M') { + return_if_fail (i >= 2); // FIXME: -2 can be C or L + left_x = b[i - 2].x0 + (b[i - 1].x0 - b[i - 2].x0) / 3.0; + left_y = b[i - 2].y0 + (b[i - 1].y0 - b[i - 2].y0) / 3.0; + last_type = PointType.LINE_CUBIC; + } else { + warning (@"Unexpected type. $(b[i - 1])\n"); + } + return; + } + + last = b[i]; + } + + warning ("Last point not found."); + } + + PathList create_paths_inkscape (BezierPoints[] b, int num_b) { + double last_x; + double last_y; + PointType last_type; + Path path; + PathList path_list = new PathList (); + EditPoint ep = new EditPoint (); + Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); + + path = new Path (); + + if (num_b == 0) { + warning ("No SVG data"); + return path_list; + } + + if (b[0].type != 'M') { + warning ("Path must begin with M or m."); + return path_list; + } + + find_last_handle (0, b, num_b, out last_x, out last_y, out last_type); + + for (int i = 0; i < num_b; i++) { + if (b[i].type == '\0') { + warning ("Parser error."); + return path_list; + } + + if (b[i].type == 'z') { + path.close (); + path.create_list (); + path.recalculate_linear_handles (); + path_list.add (path); + path = new Path (); + + if (i + 1 >= num_b) { + break; + } else { + find_last_handle (i + 1, b, num_b, out last_x, out last_y, out last_type); + } + } + + if (i >= num_b) { + break; + } + + if (b[i].type == 'M') { + ep = path.add (b[i].x0, b[i].y0); + ep.set_point_type (PointType.CUBIC); + + ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); + + if (i == 0 || (b[i - 1].type == 'z')) { + ep.get_left_handle ().set_point_type (last_type); + ep.get_left_handle ().move_to_coordinate (last_x, last_y); + } else { + if (b[i - 1].type == 'C' || b[i - 1].type == 'S') { + ep.get_left_handle ().set_point_type (PointType.CUBIC); + ep.get_left_handle ().move_to_coordinate (b[i + 1].x1, b[i + 1].y1); + } + + if (b[i + 1].type == 'C' || b[i - 1].type == 'S') { + ep.get_right_handle ().set_point_type (PointType.CUBIC); + ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); + } else if (b[i + 1].type == 'L' || b[i + 1].type == 'M') { + ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); + } + } + } + + if (b[i].type == 'L') { + return_val_if_fail (i != 0, path_list); + + ep = path.add (b[i].x0, b[i].y0); + ep.set_point_type (PointType.CUBIC); + ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); + ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); + + if (b[i + 1].type == 'L' || b[i + 1].type == 'M' || b[i + 1].type == 'z') { + ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); + } + + if (b[i -1].type == 'L' || b[i - 1].type == 'M') { + ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); + } + } + + if (b[i].type == 'Q') { + return_val_if_fail (i != 0, path_list); + + ep.set_point_type (PointType.QUADRATIC); + + ep.get_right_handle ().set_point_type (PointType.QUADRATIC); + ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); + + if (b[i + 1].type != 'z') { + ep = path.add (b[i].x1, b[i].y1); + + ep.get_left_handle ().set_point_type (PointType.QUADRATIC); + ep.get_left_handle ().move_to_coordinate (b[i].x0, b[i].y0); + } + } + + if (b[i].type == 'C' || b[i].type == 'S') { + return_val_if_fail (i != 0, path_list); + + ep.set_point_type (PointType.CUBIC); + + ep.get_right_handle ().set_point_type (PointType.CUBIC); + ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); + + if (b[i].type == 'S') { + smooth_points.add (ep); + } + + if (b[i + 1].type != 'z') { + ep = path.add (b[i].x2, b[i].y2); + + ep.get_left_handle ().set_point_type (PointType.CUBIC); + ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); + } + } + } + + foreach (EditPoint e in smooth_points) { + e.set_point_type (PointType.LINE_DOUBLE_CURVE); + e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); + e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); + } + + foreach (EditPoint e in smooth_points) { + path.recalculate_linear_handles_for_point (e); + } + + for (int i = 0; i < 3; i++) { + foreach (EditPoint e in smooth_points) { + e.set_tie_handle (true); + e.process_tied_handle (); + } + } + + if (path.points.size > 0) { + path_list.add (path); + } + + foreach (Path p in path_list.paths) { + p.remove_points_on_points (); + } + + return path_list; + } + + PathList create_paths_illustrator (BezierPoints[] b, int num_b) { + Path path; + PathList path_list = new PathList (); + EditPoint ep; + bool first_point = true; + double first_left_x, first_left_y; + Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); + + if (num_b > b.length) { + warning ("num_b > b.length: $num_b > $(b.length)"); + return path_list; + } + + path = new Path (); + + if (num_b <= 1) { + warning ("No SVG data"); + return path_list; + } + + first_left_x = 0; + first_left_y = 0; + + // FIXME: array boundaries + for (int i = 0; i < num_b; i++) { + if (b[i].type == '\0') { + warning ("Parser error."); + return path_list; + } else if (b[i].type == 'z') { + path.close (); + path.create_list (); + + int first_index = 1; + + for (int j = i - 1; j >= 1; j--) { + if (b[j].type == 'z') { + first_index = j + 1; // from z to M + } + } + + if (b[first_index].type == 'C' || b[first_index].type == 'S') { + return_val_if_fail (path.points.size != 0, path_list); + ep = path.points.get (path.points.size - 1); + + if (b[i - 1].type != 'L' ) { + ep.get_right_handle ().set_point_type (PointType.CUBIC); + ep.get_right_handle ().move_to_coordinate (b[first_index].x0, b[first_index].y0); + } + } else if (b[first_index].type == 'L') { + return_val_if_fail (path.points.size != 0, path_list); + ep = path.points.get (path.points.size - 1); + ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); + path.recalculate_linear_handles_for_point (ep); + } else { + warning ("Unexpected type: %s", (!) b[first_index].type.to_string ()); + } + + path.recalculate_linear_handles (); + path_list.add (path); + + path = new Path (); + first_point = true; + } else if (b[i].type == 'M') { + } else if (b[i].type == 'L') { + + if (first_point) { + first_left_x = b[i].x0; + first_left_y = b[i].y0; + } + + ep = path.add (b[i].x0, b[i].y0); + ep.set_point_type (PointType.LINE_CUBIC); // TODO: quadratic + ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); + + if (b[i -1].type == 'L' || first_point) { + // ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); + } + + if (b[i + 1].type == 'C' || b[i + 1].type == 'S') { + return_val_if_fail (i + 1 < num_b, path_list); + ep.get_right_handle ().set_point_type (PointType.CUBIC); + ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); + } + + first_point = false; + } else if (b[i].type == 'Q') { + warning ("Illustrator does not support quadratic control points."); + warning (@"$(b[i])\n"); + } else if (b[i].type == 'C' || b[i].type == 'S') { + + if (first_point) { + first_left_x = b[i].x0; + first_left_y = b[i].y0; + } + + ep = path.add (b[i].x2, b[i].y2); + ep.set_point_type (PointType.CUBIC); + + ep.get_right_handle ().set_point_type (PointType.CUBIC); + ep.get_left_handle ().set_point_type (PointType.CUBIC); + + ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); + + if (b[i].type == 'S') { + smooth_points.add (ep); + } + + if (b[i + 1].type != 'z') { + ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); + } else { + ep.get_right_handle ().move_to_coordinate (first_left_x, first_left_y); + } + + first_point = false; + } else { + warning ("Unknown control point type."); + warning (@"$(b[i])\n"); + } + } + + foreach (EditPoint e in smooth_points) { + e.set_point_type (PointType.LINE_DOUBLE_CURVE); + e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); + e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); + } + + foreach (EditPoint e in smooth_points) { + path.recalculate_linear_handles_for_point (e); + } + + for (int i = 0; i < 3; i++) { + foreach (EditPoint e in smooth_points) { + e.set_tie_handle (true); + e.process_tied_handle (); + } + } + + if (path.points.size > 0) { + path_list.add (path); + } + + foreach (Path p in path_list.paths) { + p.remove_points_on_points (); + } + + return path_list; + } + + // TODO: implement a default svg parser + + static int parse_int (string? s) { + if (is_null (s)) { + warning ("null instead of string"); + return 0; + } + + if (!is_point ((!) s)) { + warning (@"Expecting an integer got: $((!) s)"); + return 0; + } + + return int.parse ((!) s); + } + + static double parse_double (string? s) { + if (is_null (s)) { + warning ("Got null instead of expected string."); + return 0; + } + + if (!is_point ((!) s)) { + warning (@"Expecting a double got: $((!) s)"); + return 0; + } + + return double.parse ((!) s); + } + + static bool is_point (string? s) { + if (s == null) { + warning ("s is null"); + return false; + } + + return double.try_parse ((!) s); + } + + Path parse_poly_data (string polygon_points) { + string data = add_separators (polygon_points); + string[] c = data.split (" "); + Path path; + BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; + int bi; + Glyph g; + EditPoint ep; + + bi = 0; + for (int i = 0; i < c.length - 1; i += 2) { + if (i + 1 >= c.length) { + warning ("No y value."); + break; + } + + if (bi >= bezier_points.length) { + warning ("End of bezier_points"); + break; + } + + bezier_points[bi] = new BezierPoints (); + bezier_points[bi].type == 'L'; + bezier_points[bi].x0 = parse_double (c[i]); + bezier_points[bi].y0 = -parse_double (c[i + 1]); + bi++; + } + + g = MainWindow.get_current_glyph (); + move_and_resize (bezier_points, bi, false, 1, g); + + path = new Path (); + for (int i = 0; i < bi; i++) { + ep = path.add (bezier_points[i].x0, bezier_points[i].y0); + ep.set_point_type (PointType.LINE_CUBIC); + } + + path.create_list (); + path.recalculate_linear_handles (); + + return path; + } + } + + }
--- /dev/null +++ b/libbirdfont/Svg/SvgStyle.vala @@ -1,1 +1,125 @@ + /* + Copyright (C) 2015 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 Math; + + namespace BirdFont { + + public class SvgStyle { + + Gee.HashMap<string, string> style; + + public SvgStyle () { + style = new Gee.HashMap<string, string> (); + } + + public LineCap get_line_cap () { + string l; + + if (!style.has_key ("stroke-linecap")) { + return LineCap.BUTT; + } + + l = style.get ("stroke-linecap"); + + if (l == "round") { + return LineCap.ROUND; + } else if (l == "square") { + return LineCap.SQUARE; + } + + return LineCap.BUTT; + } + + public bool has_stroke () { + bool s = true; + + if (style.has_key ("stroke")) { + s = style.get ("stroke") != "none"; + } + + return get_stroke_width () > 0 && s; + } + + public double get_stroke_width () { + if (!style.has_key ("stroke-width")) { + return 0; + } + + return double.parse (style.get ("stroke-width")); + } + + + public static SvgStyle parse (Attributes attributes) { + SvgStyle s = new SvgStyle (); + + foreach (Attribute a in attributes) { + if (a.get_name () == "style") { + s.parse_key_value_pairs (a.get_content ()); + } + + if (a.get_name () == "stroke-width") { + s.style.set ("stroke-width", a.get_content ()); + } + + if (a.get_name () == "stroke") { + s.style.set ("stroke", a.get_content ()); + } + + if (a.get_name () == "fill") { + s.style.set ("fill", a.get_content ()); + } + } + + return s; + } + + void parse_key_value_pairs (string svg_style) { + string[] p = svg_style.split (";"); + string[] pair; + string k, v; + + foreach (string kv in p) { + pair = kv.split (":"); + + if (pair.length != 2) { + warning ("pair.length != 2"); + continue; + } + + k = pair[0]; + v = pair[1]; + + style.set (k, v); + } + } + + public void apply (PathList path_list) { + foreach (Path p in path_list.paths) { + if (has_stroke ()) { + p.stroke = get_stroke_width (); + } else { + p.stroke = 0; + } + + p.line_cap = get_line_cap (); + p.reset_stroke (); + p.update_region_boundaries (); + } + } + } + + }
diff --git libbirdfont/Svg.vala(deleted)
--- a/libbirdfont/Svg.vala +++ /dev/null @@ -1,354 +1,1 @@ - /* - Copyright (C) 2012 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 Cairo; - - namespace BirdFont { - - public class Svg { - - /** Export to svg glyph data. */ - public static string to_svg_glyph (Glyph g) { - StringBuilder svg = new StringBuilder (); - PathList stroke_list; - - foreach (Path p in g.get_visible_paths ()) { - if (p.stroke == 0) { - write_path_as_glyph (p, svg, g); - } else { - stroke_list = p.get_completed_stroke (); - write_paths_as_glyph (stroke_list, svg, g); - } - } - - return svg.str; - } - - /** Export to svg-font data. */ - public static string to_svg_path (Path pl, Glyph g) { - StringBuilder svg = new StringBuilder (); - pl.create_list (); - write_path (pl, svg, g, false); - return svg.str; - } - - private static void write_paths_as_glyph (PathList pl, StringBuilder svg, Glyph g) { - foreach (Path p in pl.paths) { - write_path_as_glyph (p, svg, g); - } - } - - private static void write_path_as_glyph (Path pl, StringBuilder svg, Glyph g) { - write_path (pl, svg, g, true); - } - - private static void write_path (Path p, StringBuilder svg, Glyph g, bool do_glyph) { - int i = 0; - EditPoint? n = null; - EditPoint m; - - if (p.points.size < 2) { - return; - } - - p.create_list (); - - foreach (var e in p.points) { - if (i == 0) { - add_abs_start (e, svg, g, do_glyph); - i++; - n = e; - continue; - } - - m = (!) n; - - add_abs_next (m, e, svg, g, do_glyph); - - n = e; - i++; - } - - if (!p.is_open ()) { - m = p.points.get (0); - add_abs_next ((!) n, m, svg, g, do_glyph); - close_path (svg); - } - } - - private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph) { - if (start.right_handle.type == PointType.LINE_QUADRATIC) { - add_abs_line_to (start, end, svg, g, do_glyph); - } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { - add_abs_line_to (start, end, svg, g, do_glyph); - } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { - add_quadratic_abs_path (start, end, svg, g, do_glyph); - } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) { - add_double_quadratic_abs_path (start, end, svg, g, do_glyph); - } else { - add_cubic_abs_path (start, end, svg, g, do_glyph); - } - } - - private static void add_abs_start (EditPoint ep, StringBuilder svg, Glyph g, bool to_glyph) { - double left = g.left_limit; - double baseline = -BirdFont.get_current_font ().base_line; - double height = g.get_height (); - - svg.append_printf ("M"); - - if (!to_glyph) { - svg.append_printf ("%s ", round (ep.x - left)); - svg.append_printf ("%s ", round (-ep.y + height / 2)); - } else { - svg.append_printf ("%s ", round (ep.x - left)); - svg.append_printf ("%s ", round (ep.y + baseline)); - } - } - - private static void close_path (StringBuilder svg) { - svg.append ("z"); - } - - private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg, Glyph g, bool to_glyph) { - double baseline = -BirdFont.get_current_font ().base_line; - double left = g.left_limit; - double height = g.get_height (); - - double xa, ya, xb, yb; - - Path.get_line_points (start, stop, out xa, out ya, out xb, out yb); - - double center_x = Glyph.xc (); - double center_y = Glyph.yc (); - - svg.append ("L"); - - if (!to_glyph) { - svg.append_printf ("%s ", round (xb - center_x - left)); - svg.append_printf ("%s ", round (yb - center_y + height / 2)); - } else { - svg.append_printf ("%s ", round (xb - center_x - left)); - svg.append_printf ("%s ", round (-yb + center_y + baseline)); - } - } - - private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { - EditPoint middle; - double x, y; - - x = start.get_right_handle ().x + (end.get_left_handle ().x - start.get_right_handle ().x) / 2; - y = start.get_right_handle ().y + (end.get_left_handle ().y - start.get_right_handle ().y) / 2; - - middle = new EditPoint (x, y, PointType.QUADRATIC); - middle.right_handle = end.get_left_handle ().copy (); - - add_quadratic_abs_path (start, middle, svg, g, to_glyph); - add_quadratic_abs_path (middle, end, svg, g, to_glyph); - } - - private static void add_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { - double left = g.left_limit; - double baseline = -BirdFont.get_current_font ().base_line; - double height = g.get_height (); - - double xa, ya, xb, yb, xc, yc, xd, yd; - - Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd); - - double center_x = Glyph.xc (); - double center_y = Glyph.yc (); - - // cubic path - if (!to_glyph) { - svg.append_printf ("Q"); - - svg.append_printf ("%s ", round (xb - center_x - left)); - svg.append_printf ("%s ", round (yb - center_y + height / 2)); - - svg.append_printf ("%s ", round (xd - center_x - left)); - svg.append_printf ("%s ", round (yd - center_y + height / 2)); - - } else { - svg.append_printf ("Q"); - - svg.append_printf ("%s ", round (xb - center_x - left)); - svg.append_printf ("%s ", round (-yb + center_y + baseline)); - - svg.append_printf ("%s ", round (xd - center_x - left)); - svg.append_printf ("%s ", round (-yd + center_y + baseline)); - } - } - - private static void add_cubic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) { - double left = g.left_limit; - double baseline = -BirdFont.get_current_font ().base_line; - double height = g.get_height (); - - double xa, ya, xb, yb, xc, yc, xd, yd; - - Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd); - - double center_x = Glyph.xc (); - double center_y = Glyph.yc (); - - // cubic path - if (!to_glyph) { - svg.append_printf ("C"); - - svg.append_printf ("%s ", round (xb - center_x - left)); - svg.append_printf ("%s ", round (yb - center_y + height / 2)); - - svg.append_printf ("%s ", round (xc - center_x - left)); - svg.append_printf ("%s ", round (yc - center_y + height / 2)); - - svg.append_printf ("%s ", round (xd - center_x - left)); - svg.append_printf ("%s ", round (yd - center_y + height / 2)); - - } else { - svg.append_printf ("C"); - - svg.append_printf ("%s ", round (xb - center_x - left)); - svg.append_printf ("%s ", round (-yb + center_y + baseline)); - - svg.append_printf ("%s ", round (xc - center_x - left)); - svg.append_printf ("%s ", round (-yc + center_y + baseline)); - - svg.append_printf ("%s ", round (xd - center_x - left)); - svg.append_printf ("%s ", round (-yd + center_y + baseline)); - } - } - - /** Draw path from svg font data. */ - public static void draw_svg_path (Context cr, string svg, double x, double y) { - double x1, x2, x3; - double y1, y2, y3; - double px, py; - string[] d = svg.split (" "); - - if (d.length == 0) { - return; - } - - px = 0; - py = 0; - - cr.save (); - - cr.set_line_width (0); - - if (svg == "") { - return; - } - - for (int i = 0; i < d.length; i++) { - - // trim off leading white space - while (d[i].index_of (" ") == 0) { - d[i] = d[i].substring (1); // FIXME: maybe no ascii - } - - if (d[i].index_of ("L") == 0) { - x1 = double.parse (d[i].substring (1)) + x; - y1 = -double.parse (d[i+1]) + y; - cr.line_to (x1, y1); - - px = x1; - py = y1; - continue; - } - - if (d[i].index_of ("Q") == 0) { - x1 = double.parse (d[i].substring (1)) + x; - y1 = -double.parse (d[i+1]) + y; - - x2 = double.parse (d[i+2]) + x; - y2 = -double.parse (d[i+3]) + y; - - cr.curve_to ((px + 2 * x1) / 3, (py + 2 * y1) / 3, (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, x2, y2); - - px = x2; - py = y2; - continue; - } - - if (d[i].index_of ("C") == 0) { - x1 = double.parse (d[i].substring (1)) + x; - y1 = -double.parse (d[i+1]) + y; - - x2 = double.parse (d[i+2]) + x; - y2 = -double.parse (d[i+3]) + y; - - x3 = double.parse (d[i+4]) + x; - y3 = -double.parse (d[i+5]) + y; - - cr.curve_to (x1, y1, x2, y2, x3, y3); - - px = x3; - py = y3; - continue; - } - - if (d[i].index_of ("M") == 0) { - x1 = double.parse (d[i].substring (1)) + x; - y1 = -double.parse (d[i+1]) + y; - - cr.move_to (x1, y1); - - px = x1; - py = y1; - continue; - } - - if (d[i].index_of ("zM") == 0) { - cr.close_path (); - - x1 = double.parse (d[i].substring (2)) + x; - y1 = -double.parse (d[i+1]) + y; - - cr.move_to (x1, y1); - - px = x1; - py = y1; - continue; - } - - if (d[i].index_of ("z") == 0) { - cr.close_path (); - continue; - } - - } - - cr.fill (); - cr.restore (); - } - - } - - internal static string round (double p) { - string v = p.to_string (); - char[] c = new char [501]; - - v = p.format (c, "%3.15f"); - - if (v.index_of ("e") != -1) { - return "0.0"; - } - - return v; - } - - }
diff --git libbirdfont/SvgArc.vala(deleted)
--- a/libbirdfont/SvgArc.vala +++ /dev/null @@ -1,182 +1,1 @@ - /* - * BirdFont code from SVG Salamander - * - * Copyright (c) 2004, Mark McKay - * Copyright (c) 2014, Johan Mattsson - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Mark McKay can be contacted at mark@kitfox.com. Salamander and other - * projects can be found at http://www.kitfox.com - * - * Created on January 26, 2004, 8:40 PM - * Adapded to BirdFont on Juli 2, 2014, 5:01 PM - */ - - using Math; - - namespace BirdFont { - - /** Convert an SVG arc instruction to a Beziér path. */ - static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, double y0, double rx, double ry, double angle, bool largeArcFlag, bool sweepFlag, double x, double y) { - - // - // Elliptical arc implementation based on the SVG specification notes - // - - double dx2, dy2, cosAngle, sinAngle; - double x1, y1, Prx, Pry, Px1, Py1, radiiCheck; - double sign, sq, coef, cx1, cy1; - double sx2, sy2, cx, cy; - double ux, uy, vx, vy, p, n; - double angleStart, angleExtent; - double s, step, theta; - - // Compute the half distance between the current and the final point - dx2 = (x0 - x) / 2.0; - dy2 = (y0 - y) / 2.0; - - // Convert angle from degrees to radians - angle = 2 * PI * ((angle % 360.0) / 360.0); - - cosAngle = cos (angle); - sinAngle = sin (angle); - - // - // Step 1 : Compute (x1, y1) - // - x1 = cosAngle * dx2 + sinAngle * dy2; - y1 = -sinAngle * dx2 + cosAngle * dy2; - - // Ensure radii are large enough - rx = fabs(rx); - ry = fabs(ry); - Prx = rx * rx; - Pry = ry * ry; - Px1 = x1 * x1; - Py1 = y1 * y1; - - - // Check that radii are large enough - radiiCheck = Px1 / Prx + Py1 / Pry; - - if (radiiCheck > 1) { - rx = sqrt (radiiCheck) * rx; - ry = sqrt (radiiCheck) * ry; - Prx = rx * rx; - Pry = ry * ry; - } - - // - // Step 2 : Compute (cx1, cy1) - // - sign = (largeArcFlag == sweepFlag) ? -1 : 1; - sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1)) / ((Prx * Py1) + (Pry * Px1)); - sq = (sq < 0) ? 0 : sq; - coef = (sign * Math.sqrt(sq)); - cx1 = coef * ((rx * y1) / ry); - cy1 = coef * -((ry * x1) / rx); - - // - // Step 3 : Compute (cx, cy) from (cx1, cy1) - // - - sx2 = (x0 + x) / 2.0; - sy2 = (y0 + y) / 2.0; - cx = sx2 - (cosAngle * cx1 - sinAngle * cy1); - cy = sy2 - (sinAngle * cx1 + cosAngle * cy1); - - // - // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) - // - - ux = (x1 - cx1) / rx; - uy = (y1 - cy1) / ry; - vx = (-x1 - cx1) / rx; - vy = (-y1 - cy1) / ry; - - // Compute the angle start - n = sqrt((ux * ux) + (uy * uy)); - p = ux; // (1 * ux) + (0 * uy) - sign = (uy < 0) ? -1d : 1d; - angleStart = sign * acos(p / n); - - // Compute the angle extent - n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); - p = ux * vx + uy * vy; - sign = (ux * vy - uy * vx < 0) ? -1d : 1d; - angleExtent = sign * Math.acos(p / n); - - if(!sweepFlag && angleExtent > 0) { - angleExtent -= 2 *PI; - } else if (sweepFlag && angleExtent < 0) { - angleExtent += 2 *PI; - } - angleExtent %= 2 * PI; - angleStart %= 2 * PI; - - angleExtent *= -1; - angleStart *= -1; - - // Approximate the path with Beziér points - s = (angleExtent > 0) ? 1 : -1; - step = fabs (angleExtent) / (2 * fabs (angleExtent)); - - theta = PI - angleStart - angleExtent; - - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'a'; - - bezier_points[bi].x0 = cx + rx * cos (theta); - bezier_points[bi].y0 = cy + ry * sin (theta); - - bi++; - - for (double a = 0; a < fabs (angleExtent); a += step) { - theta = PI - angleStart - angleExtent + s * a; - - return_if_fail (0 <= bi < bezier_points.length); - - bezier_points[bi].type = 'S'; - bezier_points[bi].svg_type = 'a'; - - bezier_points[bi].x0 = cx + rx * cos (theta); - bezier_points[bi].y0 = cy + ry * sin (theta); - - bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); - bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); - - bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); - bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); - - bi++; - } - } - - } -
diff --git libbirdfont/SvgFont.vala(deleted)
--- a/libbirdfont/SvgFont.vala +++ /dev/null @@ -1,265 +1,1 @@ - /* - Copyright (C) 2013 2014 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; - - namespace BirdFont { - - class SvgFont : GLib.Object { - Font font; - double units = 1; - double font_advance = 0; - - public SvgFont (Font f) { - this.font = f; - } - - /** Load svg font from file. */ - public void load (string path) { - string data; - XmlParser xml_parser; - try { - FileUtils.get_contents (path, out data); - xml_parser = new XmlParser (data); - parse_svg_font (xml_parser.get_root_tag ()); - } catch (GLib.Error e) { - warning (e.message); - } - } - - void parse_svg_font (Tag tag) { - foreach (Tag t in tag) { - if (t.get_name () == "defs") { - parse_svg_font (t); - } - - if (t.get_name () == "font") { - parse_font_tag (t); - parse_svg_font (t); - } - - if (t.get_name () == "font-face") { - parse_font_limits (t); - } - - if (t.get_name () == "hkern") { - parse_hkern (t); - } - - if (t.get_name () == "glyph") { - parse_glyph (t); - } - } - } - - void parse_hkern (Tag tag) { - string left = ""; - string right = ""; - string left_name = ""; - string right_name = ""; - double kerning = 0; - unichar l, r; - StringBuilder sl, sr; - GlyphRange grr, grl; - KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes (); - - foreach (Attribute attr in tag.get_attributes ()) { - // left - if (attr.get_name () == "u1") { - left = attr.get_content (); - } - - // right - if (attr.get_name () == "u2") { - right = attr.get_content (); - } - - if (attr.get_name () == "g1") { - left_name = attr.get_content (); - } - - if (attr.get_name () == "g2") { - right_name = attr.get_content (); - } - - // kerning - if (attr.get_name () == "k") { - kerning = double.parse (attr.get_content ()) * units; - } - } - - // FIXME: ranges and sequences for u1 & u2 + g1 & g2 - foreach (string lk in left.split (",")) { - foreach (string rk in right.split (",")) { - l = get_unichar (lk); - r = get_unichar (rk); - - sl = new StringBuilder (); - sl.append_unichar (l); - - sr = new StringBuilder (); - sr.append_unichar (r); - - try { - grl = new GlyphRange (); - grl.parse_ranges (sl.str); - - grr = new GlyphRange (); - grr.parse_ranges (sr.str); - - classes.set_kerning (grl, grr, -kerning); - } catch (MarkupError e) { - warning (e.message); - } - } - } - } - - void parse_font_limits (Tag tag) { - double top_limit = 0; - double bottom_limit = 0; - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "units-per-em") { - units = 100.0 / double.parse (attr.get_content ()); - } - } - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "ascent") { - top_limit = double.parse (attr.get_content ()); - } - - if (attr.get_name () == "descent") { - bottom_limit = double.parse (attr.get_content ()); - } - } - - top_limit *= units; - bottom_limit *= units; - - font.bottom_limit = bottom_limit; - font.top_limit = top_limit; - } - - void parse_font_tag (Tag tag) { - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "horiz-adv-x") { - font_advance = double.parse (attr.get_content ()); - } - - if (attr.get_name () == "id") { - font.set_name (attr.get_content ()); - } - } - } - - /** Obtain unichar value from either a character, a hex representation of - * a character or series of characters (f, &#x6d; or ffi). - */ - static unichar get_unichar (string val) { - string v = val; - unichar unicode_value; - - if (val == "&") { - return '&'; - } - - // TODO: parse ligatures - if (v.has_prefix ("&")) { - // parse hex value - v = v.substring (0, v.index_of (";")); - v = v.replace ("&#x", "U+"); - v = v.replace (";", ""); - unicode_value = Font.to_unichar (v); - } else { - // obtain unicode value - - if (v.char_count () > 1) { - warning ("font contains ligatures"); - return '\0'; - } - - unicode_value = v.get_char (0); - } - - return unicode_value; - } - - bool is_ligature (string v) { - if (v.has_prefix ("&")) { - return false; - } - - return v.char_count () > 1; - } - - void parse_glyph (Tag tag) { - unichar unicode_value = 0; - string glyph_name = ""; - string svg = ""; - Glyph glyph; - GlyphCollection glyph_collection; - double advance = font_advance; - string ligature = ""; - SvgParser parser = new SvgParser (); - StringBuilder unicode_name; - - parser.set_format (SvgFormat.INKSCAPE); - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "unicode") { - unicode_value = get_unichar (attr.get_content ()); - - if (glyph_name == "") { - glyph_name = attr.get_content (); - } - - if (is_ligature (attr.get_content ())) { - ligature = attr.get_content (); - } - } - - // svg data - if (attr.get_name () == "d") { - svg = attr.get_content (); - } - - if (attr.get_name () == "glyph-name") { - glyph_name = attr.get_content (); - } - - if (attr.get_name () == "horiz-adv-x") { - advance = double.parse (attr.get_content ()); - } - } - - unicode_name = new StringBuilder (); - unicode_name.append_unichar (unicode_value); - - glyph = new Glyph (unicode_name.str, unicode_value); - parser.add_path_to_glyph (svg, glyph, true, units); - glyph.right_limit = glyph.left_limit + advance * units; - - // FIXME: add svg font ligatures - - glyph_collection = new GlyphCollection (unicode_value, glyph_name); - glyph_collection.insert_glyph (glyph, true); - - font.add_glyph_collection (glyph_collection); - } - } - - }
--- a/libbirdfont/SvgFontFormatWriter.vala +++ /dev/null @@ -1,133 +1,1 @@ - /* - Copyright (C) 2012, 2014 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. - */ - - namespace BirdFont { - - class SvgFontFormatWriter : GLib.Object { - - DataOutputStream os; - - public SvgFontFormatWriter () { - } - - public void open (File file) throws Error { - if (file.query_exists ()) { - throw new FileError.EXIST ("SvgFontFormatWriter: file exists."); - } - - os = new DataOutputStream (file.create(FileCreateFlags.REPLACE_DESTINATION)); - } - - public void close () throws Error { - os.close (); - } - - public void write_font_file (Font font) throws Error { - string font_name = font.get_full_name (); - - int units_per_em = 100; - - int ascent = 80; - int descent = -20; - - StringBuilder b; - - Glyph? g; - Glyph glyph; - unichar index = 0; - - string uni; - - KerningClasses classes; - - put ("""<?xml version="1.0" standalone="no"?>"""); - put ("""<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >"""); - put ("""<svg>"""); - - // (metadata goes here) - - put ("<defs>"); - - put (@"<font id=\"$font_name\" horiz-adv-x=\"250\" >"); - put (@"<font-face units-per-em=\"$(to_float (units_per_em))\" ascent=\"$(to_float (ascent))\" descent=\"$(to_float (descent))\" />"); - - // (missing-glyph goes here) - - // regular glyphs - while (true) { - g = font.get_glyph_index (index++); - - if (g == null) { - break; - } - - glyph = (!) g; - - b = new StringBuilder (); - b.append_unichar (glyph.get_unichar ()); - - if (glyph.get_unichar () >= ' ' && b.str.validate ()) { - if (b.str == "\"" || b.str == "&" || b.str == "<" || b.str == ">") { - uni = Font.to_hex_code (glyph.get_unichar ()); - put (@"<glyph unicode=\"&#x$(uni);\" horiz-adv-x=\"$(to_float (glyph.get_width ()))\" d=\"$(glyph.get_svg_data ())\" />"); - } else { - put (@"<glyph unicode=\"$(b.str)\" horiz-adv-x=\"$(to_float (glyph.get_width ()))\" d=\"$(glyph.get_svg_data ())\" />"); - } - } - } - - // FIXME: ligatures - classes = BirdFont.get_current_font ().get_kerning_classes (); - classes.all_pairs ((kerning) => { - string l, r; - - foreach (Kerning k in kerning.kerning) { - try { - if (k.glyph != null) { - l = Font.to_hex_code (kerning.character.unichar_code); - r = Font.to_hex_code (((!)k.glyph).unichar_code); - os.put_string (@"<hkern u1=\"&#x$l;\" u2=\"&#x$r;\" k=\"$(to_float (-k.val))\"/>\n"); - } else { - warning ("No glyph."); - } - } catch (GLib.Error e) { - warning (e.message); - } - } - }); - - put ("</font>"); - put ("</defs>"); - put ("</svg>"); - } - - string to_float (double d) { - string s = @"$d"; - if (s.index_of ("e") != -1) { - return "0".dup (); - } - return s.replace (",", "."); - } - - /** Write a new line */ - private void put (string line) throws Error { - os.put_string (line); - os.put_string ("\n"); - } - - } - - - }
diff --git libbirdfont/SvgParser.vala(deleted)
--- a/libbirdfont/SvgParser.vala +++ /dev/null @@ -1,2115 +1,1 @@ - /* - Copyright (C) 2012 2013 2014 2015 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 Math; - - namespace BirdFont { - - public enum SvgFormat { - NONE, - INKSCAPE, - ILLUSTRATOR - } - - public enum SvgType { - COLOR, - REGULAR - } - - public class SvgParser { - - SvgFormat format = SvgFormat.ILLUSTRATOR; - - public SvgParser () { - } - - public void set_format (SvgFormat f) { - format = f; - } - - public static void import (SvgType type) { - FileChooser fc = new FileChooser (); - fc.file_selected.connect ((p) => { - string path; - - if (p == null) { - return; - } - - path = (!) p; - - if (type == SvgType.REGULAR) { - import_svg (path); - } else if (type == SvgType.COLOR) { - Glyph glyph = MainWindow.get_current_glyph (); - import_color_svg (glyph, path); - } - }); - - fc.add_extension ("svg"); - MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD); - } - - public static void import_color_svg (Glyph glyph, string path) { - try { - string data; - FileUtils.get_contents (path, out data); - glyph.color_svg_data = data; - - if (glyph.color_svg_data != null) { - glyph.svg_x = glyph.left_limit; - glyph.svg_y = BirdFont.get_current_font ().top_position; - } - } catch (GLib.Error e) { - warning (e.message); - } - } - - public static void import_folder (SvgType type) { - FileChooser fc = new FileChooser (); - fc.file_selected.connect ((p) => { - string path; - File svg_folder; - File svg; - bool imported; - FileEnumerator enumerator; - FileInfo? file_info; - string file_name; - Font font; - - if (p == null) { - return; - } - - path = (!) p; - svg_folder = File.new_for_path (path); - font = BirdFont.get_current_font (); - - try { - enumerator = svg_folder.enumerate_children (FileAttribute.STANDARD_NAME, 0); - while ((file_info = enumerator.next_file ()) != null) { - file_name = ((!) file_info).get_name (); - - if (file_name.has_suffix (".svg")) { - svg = get_child (svg_folder, file_name); - imported = import_svg_file (font, svg, type); - - if (!imported) { - warning ("Can't import %s.", (!) svg.get_path ()); - } else { - font.touch (); - } - } - } - } catch (Error e) { - warning (e.message); - } - }); - - MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD | FileChooser.DIRECTORY); - } - - public static void import_svg_data (string xml_data, SvgFormat format = SvgFormat.NONE) { - PathList path_list = new PathList (); - Glyph glyph; - string[] lines = xml_data.split ("\n"); - bool has_format = false; - SvgParser parser = new SvgParser (); - XmlParser xmlparser; - - foreach (string l in lines) { - if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) { - parser.set_format (SvgFormat.ILLUSTRATOR); - has_format = true; - } - - if (l.index_of ("Inkscape") > -1 || l.index_of ("inkscape") > -1) { - parser.set_format (SvgFormat.INKSCAPE); - has_format = true; - } - } - - if (format != SvgFormat.NONE) { - parser.set_format (format); - } - - // parse the file - if (!has_format) { - warn_if_test ("No format identifier found in SVG parser.\n"); - } - - xmlparser = new XmlParser (xml_data); - - if (!xmlparser.validate()) { - warning("Invalid XML in SVG parser."); - } - - path_list = parser.parse_svg_file (xmlparser.get_root_tag ()); - - glyph = MainWindow.get_current_glyph (); - foreach (Path p in path_list.paths) { - FastPath path = new FastPath.for_path (p); - glyph.add_object (path); - glyph.add_active_object (null, path); // FIXME: groups - path.update_region_boundaries (); - } - - glyph.close_path (); - } - - public static string replace (string content, string start, string stop, string replacement) { - int i_tag = content.index_of (start); - int end_tag = content.index_of (stop, i_tag); - string c = ""; - - if (i_tag > -1) { - c = content.substring (0, i_tag) - + replacement - + content.substring (end_tag + stop.length); - } else { - c = content; - } - - return c; - } - - public static void import_svg (string path) { - string svg_data; - try { - FileUtils.get_contents (path, out svg_data); - } catch (GLib.Error e) { - warning (e.message); - } - import_svg_data (svg_data); - } - - private PathList parse_svg_file (Tag tag) { - Layer pl = new Layer (); - - foreach (Tag t in tag) { - - if (t.get_name () == "g") { - parse_layer (t, pl); - } - - if (t.get_name () == "switch") { - parse_layer (t, pl); - } - - if (t.get_name () == "path") { - parse_path (t, pl); - } - - if (t.get_name () == "polygon") { - parse_polygon (t, pl); - } - - if (t.get_name () == "polyline") { - parse_polyline (t, pl); - } - - if (t.get_name () == "circle") { - parse_circle (t, pl); - } - - if (t.get_name () == "ellipse") { - parse_ellipse (t, pl); - } - - if (t.get_name () == "line") { - parse_line (t, pl); - } - } - - return pl.get_all_paths (); - } - - private void parse_layer (Tag tag, Layer pl) { - Layer layer; - 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) { - return; - } - - foreach (Tag t in tag) { - if (t.get_name () == "path") { - parse_path (t, pl); - } - - if (t.get_name () == "g") { - layer = new Layer (); - parse_layer (t, layer); - pl.subgroups.add (layer); - } - - if (t.get_name () == "polygon") { - parse_polygon (t, pl); - } - - if (t.get_name () == "polyline") { - parse_polyline (t, pl); - } - - if (t.get_name () == "rect") { - parse_rect (t, pl); - } - - if (t.get_name () == "circle") { - parse_circle (t, pl); - } - - if (t.get_name () == "ellipse") { - parse_ellipse (t, pl); - } - - if (t.get_name () == "line") { - parse_line (t, pl); - } - } - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform (attr.get_content (), pl); - } - } - } - - private void transform (string transform_functions, Layer layer) { - PathList path_list = new PathList (); - - foreach (Object o in layer.objects) { - if (o is FastPath) { - path_list.add (((FastPath) o).get_path ()); - } - } - - transform_paths (transform_functions, path_list); - transform_subgroups (transform_functions, layer); - } - - private void transform_subgroups (string transform_functions, Layer layer) { - foreach (Layer subgroup in layer.subgroups) { - transform (transform_functions, subgroup); - } - } - - private void transform_paths (string transform_functions, PathList pl) { - string data = transform_functions.dup (); - string[] functions; - - // use only a single space as separator - while (data.index_of (" ") > -1) { - data = data.replace (" ", " "); - } - - return_if_fail (data.index_of (")") > -1); - - // add separator - data = data.replace (") ", "|"); - data = data.replace (")", "|"); - functions = data.split ("|"); - - for (int i = functions.length - 1; i >= 0; i--) { - if (functions[i].has_prefix ("translate")) { - translate (functions[i], pl); - } - - if (functions[i].has_prefix ("scale")) { - scale (functions[i], pl); - } - - if (functions[i].has_prefix ("matrix")) { - matrix (functions[i], pl); - } - - // TODO: rotate etc. - } - } - - /** @param path a path in the cartesian coordinate system - * The other parameters are in the SVG coordinate system. - */ - public static void apply_matrix (Path path, double a, double b, double c, - double d, double e, double f){ - - double dx, dy; - Font font = BirdFont.get_current_font (); - Glyph glyph = MainWindow.get_current_glyph (); - - foreach (EditPoint ep in path.points) { - ep.tie_handles = false; - ep.reflective_point = false; - } - - foreach (EditPoint ep in path.points) { - apply_matrix_on_handle (ep.get_right_handle (), a, b, c, d, e, f); - apply_matrix_on_handle (ep.get_left_handle (), a, b, c, d, e, f); - - ep.independent_y = font.top_position - ep.independent_y; - ep.independent_x -= glyph.left_limit; - - dx = a * ep.independent_x + c * ep.independent_y + e; - dy = b * ep.independent_x + d * ep.independent_y + f; - - ep.independent_x = dx; - ep.independent_y = dy; - - ep.independent_y = font.top_position - ep.independent_y; - ep.independent_x += glyph.left_limit; - } - } - - public static void apply_matrix_on_handle (EditPointHandle h, - double a, double b, double c, - double d, double e, double f){ - - double dx, dy; - Font font = BirdFont.get_current_font (); - Glyph glyph = MainWindow.get_current_glyph (); - - h.y = font.top_position - h.y; - h.x -= glyph.left_limit; - - dx = a * h.x + c * h.y + e; - dy = b * h.x + d * h.y + f; - - h.x = dx; - h.y = dy; - - h.y = font.top_position - h.y; - h.x += glyph.left_limit; - } - - - private void matrix (string function, PathList pl) { - string parameters = get_transform_parameters (function); - string[] p = parameters.split (" "); - - if (p.length != 6) { - warning ("Expecting six parameters for matrix transformation."); - return; - } - - foreach (Path path in pl.paths) { - apply_matrix (path, parse_double (p[0]), parse_double (p[1]), - parse_double (p[2]), parse_double (p[3]), - parse_double (p[4]), parse_double (p[5])); - } - } - - private void scale (string function, PathList pl) { - string parameters = get_transform_parameters (function); - string[] p = parameters.split (" "); - double x, y; - - x = 1; - y = 1; - - if (p.length > 0) { - x = parse_double (p[0]); - } - - if (p.length > 1) { - y = parse_double (p[1]); - } - - foreach (Path path in pl.paths) { - path.scale (-x, y); - } - } - - private void translate (string function, PathList pl) { - string parameters = get_transform_parameters (function); - string[] p = parameters.split (" "); - double x, y; - - x = 0; - y = 0; - - if (p.length > 0) { - x = parse_double (p[0]); - } - - if (p.length > 1) { - y = parse_double (p[1]); - } - - foreach (Path path in pl.paths) { - path.move (x, -y); - } - } - - 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 void parse_circle (Tag tag, Layer pl) { - Path p; - double x, y, r; - Glyph g; - PathList npl; - BezierPoints[] bezier_points; - SvgStyle style = new SvgStyle (); - bool hidden = false; - - npl = new PathList (); - - x = 0; - y = 0; - r = 0; - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "cx") { - x = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "cy") { - y = -parse_double (attr.get_content ()); - } - - if (attr.get_name () == "r") { - r = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "display" && attr.get_content () == "none") { - hidden = true; - } - } - - style = SvgStyle.parse (tag.get_attributes ()); - - if (hidden) { - return; - } - - bezier_points = new BezierPoints[1]; - bezier_points[0] = new BezierPoints (); - bezier_points[0].type == 'L'; - bezier_points[0].x0 = x; - bezier_points[0].y0 = y; - - g = MainWindow.get_current_glyph (); - move_and_resize (bezier_points, 1, false, 1, g); - - p = CircleTool.create_circle (bezier_points[0].x0, - bezier_points[0].y0, r, PointType.CUBIC); - - npl.add (p); - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } - - style.apply (npl); - pl.append_paths (npl); - } - - private void parse_ellipse (Tag tag, Layer pl) { - Path p; - double x, y, rx, ry; - Glyph g; - PathList npl; - BezierPoints[] bezier_points; - SvgStyle style = new SvgStyle (); - bool hidden = false; - - npl = new PathList (); - - x = 0; - y = 0; - rx = 0; - ry = 0; - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "cx") { - x = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "cy") { - y = -parse_double (attr.get_content ()); - } - - if (attr.get_name () == "rx") { - rx = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "ry") { - ry = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "display" && attr.get_content () == "none") { - hidden = true; - } - } - - style = SvgStyle.parse (tag.get_attributes ()); - - if (hidden) { - return; - } - - bezier_points = new BezierPoints[1]; - bezier_points[0] = new BezierPoints (); - bezier_points[0].type == 'L'; - bezier_points[0].x0 = x; - bezier_points[0].y0 = y; - - g = MainWindow.get_current_glyph (); - move_and_resize (bezier_points, 1, false, 1, g); - - p = CircleTool.create_ellipse (bezier_points[0].x0, - bezier_points[0].y0, rx, ry, PointType.CUBIC); - - npl.add (p); - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } - - style.apply (npl); - pl.append_paths (npl); - } - - private void parse_line (Tag tag, Layer pl) { - Path p; - double x1, y1, x2, y2; - BezierPoints[] bezier_points; - Glyph g; - PathList npl = new PathList (); - SvgStyle style = new SvgStyle (); - bool hidden = false; - - x1 = 0; - y1 = 0; - x2 = 0; - y2 = 0; - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "x1") { - x1 = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "y1") { - y1 = -parse_double (attr.get_content ()); - } - - if (attr.get_name () == "x2") { - x2 = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "xy") { - y2 = -parse_double (attr.get_content ()); - } - - if (attr.get_name () == "display" && attr.get_content () == "none") { - hidden = true; - } - } - - style = SvgStyle.parse (tag.get_attributes ()); - - if (hidden) { - return; - } - - bezier_points = new BezierPoints[2]; - bezier_points[0] = new BezierPoints (); - bezier_points[0].type == 'L'; - bezier_points[0].x0 = x1; - bezier_points[0].y0 = y1; - - bezier_points[1] = new BezierPoints (); - bezier_points[1].type == 'L'; - bezier_points[1].x0 = x2; - bezier_points[1].y0 = y2; - - g = MainWindow.get_current_glyph (); - move_and_resize (bezier_points, 4, false, 1, g); - - p = new Path (); - - p.add (bezier_points[0].x0, bezier_points[0].y0); - p.add (bezier_points[1].x0, bezier_points[1].y0); - - p.close (); - p.create_list (); - p.recalculate_linear_handles (); - - npl.add (p); - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } - - style.apply (npl); - pl.append_paths (npl); - } - - private void parse_rect (Tag tag, Layer layer) { - Path p; - double x, y, x2, y2; - BezierPoints[] bezier_points; - Glyph g; - PathList npl = new PathList (); - SvgStyle style = new SvgStyle (); - bool hidden = false; - EditPoint ep; - - x = 0; - y = 0; - x2 = 0; - y2 = 0; - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "x") { - x = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "y") { - y = -parse_double (attr.get_content ()); - } - - if (attr.get_name () == "width") { - x2 = parse_double (attr.get_content ()); - } - - if (attr.get_name () == "height") { - y2 = -parse_double (attr.get_content ()); - } - - if (attr.get_name () == "display" && attr.get_content () == "none") { - hidden = true; - } - } - - style = SvgStyle.parse (tag.get_attributes ()); - - if (hidden) { - return; - } - - x2 += x; - y2 += y; - - bezier_points = new BezierPoints[4]; - bezier_points[0] = new BezierPoints (); - bezier_points[0].type == 'L'; - bezier_points[0].x0 = x; - bezier_points[0].y0 = y; - - bezier_points[1] = new BezierPoints (); - bezier_points[1].type == 'L'; - bezier_points[1].x0 = x2; - bezier_points[1].y0 = y; - - bezier_points[2] = new BezierPoints (); - bezier_points[2].type == 'L'; - bezier_points[2].x0 = x2; - bezier_points[2].y0 = y2; - - bezier_points[3] = new BezierPoints (); - bezier_points[3].type == 'L'; - bezier_points[3].x0 = x; - bezier_points[3].y0 = y2; - - g = MainWindow.get_current_glyph (); - move_and_resize (bezier_points, 4, false, 1, g); - - p = new Path (); - - ep = p.add (bezier_points[0].x0, bezier_points[0].y0); - ep.set_point_type (PointType.CUBIC); - - ep = p.add (bezier_points[1].x0, bezier_points[1].y0); - ep.set_point_type (PointType.CUBIC); - - ep = p.add (bezier_points[2].x0, bezier_points[2].y0); - ep.set_point_type (PointType.CUBIC); - - ep = p.add (bezier_points[3].x0, bezier_points[3].y0); - ep.set_point_type (PointType.CUBIC); - - p.close (); - p.create_list (); - p.recalculate_linear_handles (); - - npl.add (p); - - // FIXME: right layer for other transforms - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } - - style.apply (npl); - layer.append_paths (npl); - } - - private void parse_polygon (Tag tag, Layer layer) { - PathList path_list = get_polyline (tag); - - foreach (Path p in path_list.paths) { - p.close (); - } - - layer.append_paths (path_list); - } - - - private void parse_polyline (Tag tag, Layer layer) { - layer.append_paths (get_polyline (tag)); - } - - private PathList get_polyline (Tag tag) { - Path p = new Path (); - bool hidden = false; - PathList path_list = new PathList (); - SvgStyle style = new SvgStyle (); - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "points") { - p = parse_poly_data (attr.get_content ()); - } - - if (attr.get_name () == "display" && attr.get_content () == "none") { - hidden = true; - } - } - - style = SvgStyle.parse (tag.get_attributes ()); - - if (hidden) { - return path_list; - } - - path_list.add (p); - style.apply (path_list); - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), path_list); - } - } - - return path_list; - } - - private void parse_path (Tag tag, Layer layer) { - Glyph glyph = MainWindow.get_current_glyph (); - PathList path_list = new PathList (); - SvgStyle style = new SvgStyle (); - bool hidden = false; - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "d") { - path_list = parse_svg_data (attr.get_content (), glyph); - } - - 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; - } - } - - style = SvgStyle.parse (tag.get_attributes ()); - - if (hidden) { - return; - } - - foreach (Path path in path_list.paths) { - layer.add_path (path); - } - - style.apply (path_list); - - // assume the even odd rule is applied and convert the path - // to a path using the non-zero rule - int inside_count; - bool inside; - foreach (Object o1 in layer.objects) { - if (o1 is FastPath) { - Path p1 = ((FastPath) o1).get_path (); - inside_count = 0; - - foreach (Object o2 in layer.objects) { - if (o2 is FastPath) { - Path p2 = ((FastPath) o2).get_path (); - - if (p1 != p2) { - inside = true; - - foreach (EditPoint ep in p1.points) { - if (!is_inside (ep, p2)) { - inside = false; - } - } - - if (inside) { - inside_count++; - } - } - } - } - - if (inside_count % 2 == 0) { - p1.force_direction (Direction.CLOCKWISE); - } else { - p1.force_direction (Direction.COUNTER_CLOCKWISE); - } - } - } - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), path_list); - } - } - } - - public static void create_lines_for_segment (Path path, EditPoint start, EditPoint end, double tolerance) { - double x1, x2, x3; - double y1, y2, y3; - double step_start, step, step_end; - - path.add (start.x, start.y); - - step_start = 0; - step = 0.5; - step_end = 1; - - while (true) { - Path.get_point_for_step (start, end, step_start, out x1, out y1); - Path.get_point_for_step (start, end, step, out x2, out y2); - Path.get_point_for_step (start, end, step_end, out x3, out y3); - - if (!StrokeTool.is_flat (x1, y1, x2, y2, x3, y3, tolerance) - && step_end - step / 2.0 > step_start - && step_end - step / 2.0 > 0.1 - && step > 0.05 - && Path.distance_to_point (start, end) > 1) { - - step /= 2.0; - - if (step < 0.05) { - step = 0.05; - } else { - step_end = step_start + 2 * step; - } - } else { - path.add (x3, y3); - - if (step_end + step < 1) { - step_start = step_end; - step_end += step; - } else { - break; - } - } - } - } - - public static Path get_lines (Path p) { - EditPoint start; - Path path = new Path (); - - if (p.points.size == 0) { - return path; - } - - // create a set of straight lines - start = p.points.get (p.points.size - 1); - - foreach (EditPoint end in p.points) { - create_lines_for_segment (path, start, end, 1); - start = end; - } - - return path; - } - - /** Check if a point is inside using the even odd fill rule. - * The path should only have straight lines. - */ - public static bool is_inside (EditPoint point, Path path) { - EditPoint prev; - bool inside = false; - - if (path.points.size <= 1) { - return false; - } - - if (!(path.xmin <= point.x <= path.xmax)) { - return false; - } - - if (!(path.ymin <= point.y <= path.ymax)) { - return false; - } - - prev = path.points.get (path.points.size - 1); - - foreach (EditPoint p in path.points) { - if ((p.y > point.y) != (prev.y > point.y) - && point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) { - inside = !inside; - } - - prev = p; - } - - return inside; - } - - /** Add space as separator to svg data. - * @param d svg data - */ - 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; - } - - public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { - PathList p = parse_svg_data (d, g, svg_glyph, units); - foreach (Path path in p.paths) { - g.add_path (path); - } - } - - /** - * @param d svg data - * @param glyph use lines from this glyph but don't add the generated paths - * @param svg_glyph parse svg glyph with origo in lower left corner - * - * @return the new paths - */ - public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { - double px = 0; - double py = 0; - double px2 = 0; - double py2 = 0; - double cx = 0; - double cy = 0; - string data; - Font font; - PathList path_list = new PathList (); - BezierPoints[] bezier_points; - string[] c; - double arc_rx, arc_ry; - double arc_rotation; - int large_arc; - int arc_sweep; - double arc_dest_x, arc_dest_y; - - font = BirdFont.get_current_font (); - - data = add_separators (d); - c = data.split (" "); - bezier_points = new BezierPoints[8 * c.length + 1]; // the arc instruction can use up to eight points - - for (int i = 0; i < 2 * c.length + 1; i++) { - bezier_points[i] = new BezierPoints (); - } - - int bi = 0; - - // 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; // if (svg_glyph) ? - - 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 = parse_double (c[++i]); - 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; - - add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); - - 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 = parse_double (c[++i]); - 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; - - add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); - - 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."); - return path_list; - } - - move_and_resize (bezier_points, bi, svg_glyph, units, glyph); - - if (format == SvgFormat.ILLUSTRATOR) { - path_list = create_paths_illustrator (bezier_points, bi); - } else { - path_list = create_paths_inkscape (bezier_points, bi); - } - - // TODO: Find out if it is possible to tie handles. - return path_list; - } - - void move_and_resize (BezierPoints[] b, int num_b, bool svg_glyph, double units, Glyph glyph) { - Font font = BirdFont.get_current_font (); - - for (int i = 0; i < num_b; i++) { - // resize all points - b[i].x0 *= units; - b[i].y0 *= units; - b[i].x1 *= units; - b[i].y1 *= units; - b[i].x2 *= units; - b[i].y2 *= units; - - // move all points - if (svg_glyph) { - b[i].x0 += glyph.left_limit; - b[i].y0 += font.base_line; - b[i].x1 += glyph.left_limit; - b[i].y1 += font.base_line; - b[i].x2 += glyph.left_limit; - b[i].y2 += font.base_line; - } else { - b[i].x0 += glyph.left_limit; - b[i].y0 += font.top_position; - b[i].x1 += glyph.left_limit; - b[i].y1 += font.top_position; - b[i].x2 += glyph.left_limit; - b[i].y2 += font.top_position; - } - } - } - - void find_last_handle (int start_index, BezierPoints[] b, int num_b, out double left_x, out double left_y, out PointType last_type) { - BezierPoints last = new BezierPoints (); - bool found = false; - - left_x = 0; - left_y = 0; - last_type = PointType.NONE; - - return_if_fail (b.length != 0); - return_if_fail (b[0].type != 'z'); - return_if_fail (num_b < b.length); - - if (num_b == 2) { - left_x = b[0].x0 + (b[1].x0 - b[0].x0) / 3.0; - left_y = b[0].y0 + (b[1].y0 - b[0].y0) / 3.0; - last_type = PointType.LINE_CUBIC; - return; - } - - for (int i = start_index; i < num_b; i++) { - switch (b[i].type) { - case 'Q': - break; - case 'C': - break; - case 'z': - found = true; - break; - default: - break; - } - - if (found || i + 1 == num_b) { - - return_if_fail (i >= 1); - - if (b[i - 1].type == 'Q') { - return_if_fail (i >= 1); - left_x = b[i - 1].x0; - left_y = b[i - 1].y0; - last_type = PointType.QUADRATIC; - } else if (b[i - 1].type == 'C') { - return_if_fail (i >= 1); - left_x = b[i - 1].x1; - left_y = b[i - 1].y1; - last_type = PointType.CUBIC; - } else if (b[i - 1].type == 'S') { - return_if_fail (i >= 1); - left_x = b[i - 1].x1; - left_y = b[i - 1].y1; - last_type = PointType.CUBIC; - } else if (b[i - 1].type == 'L' || last.type == 'M') { - return_if_fail (i >= 2); // FIXME: -2 can be C or L - left_x = b[i - 2].x0 + (b[i - 1].x0 - b[i - 2].x0) / 3.0; - left_y = b[i - 2].y0 + (b[i - 1].y0 - b[i - 2].y0) / 3.0; - last_type = PointType.LINE_CUBIC; - } else { - warning (@"Unexpected type. $(b[i - 1])\n"); - } - return; - } - - last = b[i]; - } - - warning ("Last point not found."); - } - - PathList create_paths_inkscape (BezierPoints[] b, int num_b) { - double last_x; - double last_y; - PointType last_type; - Path path; - PathList path_list = new PathList (); - EditPoint ep = new EditPoint (); - Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); - - path = new Path (); - - if (num_b == 0) { - warning ("No SVG data"); - return path_list; - } - - if (b[0].type != 'M') { - warning ("Path must begin with M or m."); - return path_list; - } - - find_last_handle (0, b, num_b, out last_x, out last_y, out last_type); - - for (int i = 0; i < num_b; i++) { - if (b[i].type == '\0') { - warning ("Parser error."); - return path_list; - } - - if (b[i].type == 'z') { - path.close (); - path.create_list (); - path.recalculate_linear_handles (); - path_list.add (path); - path = new Path (); - - if (i + 1 >= num_b) { - break; - } else { - find_last_handle (i + 1, b, num_b, out last_x, out last_y, out last_type); - } - } - - if (i >= num_b) { - break; - } - - if (b[i].type == 'M') { - ep = path.add (b[i].x0, b[i].y0); - ep.set_point_type (PointType.CUBIC); - - ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - - if (i == 0 || (b[i - 1].type == 'z')) { - ep.get_left_handle ().set_point_type (last_type); - ep.get_left_handle ().move_to_coordinate (last_x, last_y); - } else { - if (b[i - 1].type == 'C' || b[i - 1].type == 'S') { - ep.get_left_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().move_to_coordinate (b[i + 1].x1, b[i + 1].y1); - } - - if (b[i + 1].type == 'C' || b[i - 1].type == 'S') { - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); - } else if (b[i + 1].type == 'L' || b[i + 1].type == 'M') { - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - } - } - } - - if (b[i].type == 'L') { - return_val_if_fail (i != 0, path_list); - - ep = path.add (b[i].x0, b[i].y0); - ep.set_point_type (PointType.CUBIC); - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - - if (b[i + 1].type == 'L' || b[i + 1].type == 'M' || b[i + 1].type == 'z') { - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - } - - if (b[i -1].type == 'L' || b[i - 1].type == 'M') { - ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - } - } - - if (b[i].type == 'Q') { - return_val_if_fail (i != 0, path_list); - - ep.set_point_type (PointType.QUADRATIC); - - ep.get_right_handle ().set_point_type (PointType.QUADRATIC); - ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); - - if (b[i + 1].type != 'z') { - ep = path.add (b[i].x1, b[i].y1); - - ep.get_left_handle ().set_point_type (PointType.QUADRATIC); - ep.get_left_handle ().move_to_coordinate (b[i].x0, b[i].y0); - } - } - - if (b[i].type == 'C' || b[i].type == 'S') { - return_val_if_fail (i != 0, path_list); - - ep.set_point_type (PointType.CUBIC); - - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); - - if (b[i].type == 'S') { - smooth_points.add (ep); - } - - if (b[i + 1].type != 'z') { - ep = path.add (b[i].x2, b[i].y2); - - ep.get_left_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); - } - } - } - - foreach (EditPoint e in smooth_points) { - e.set_point_type (PointType.LINE_DOUBLE_CURVE); - e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); - e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); - } - - foreach (EditPoint e in smooth_points) { - path.recalculate_linear_handles_for_point (e); - } - - for (int i = 0; i < 3; i++) { - foreach (EditPoint e in smooth_points) { - e.set_tie_handle (true); - e.process_tied_handle (); - } - } - - if (path.points.size > 0) { - path_list.add (path); - } - - foreach (Path p in path_list.paths) { - p.remove_points_on_points (); - } - - return path_list; - } - - PathList create_paths_illustrator (BezierPoints[] b, int num_b) { - Path path; - PathList path_list = new PathList (); - EditPoint ep; - bool first_point = true; - double first_left_x, first_left_y; - Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); - - if (num_b > b.length) { - warning ("num_b > b.length: $num_b > $(b.length)"); - return path_list; - } - - path = new Path (); - - if (num_b <= 1) { - warning ("No SVG data"); - return path_list; - } - - first_left_x = 0; - first_left_y = 0; - - // FIXME: array boundaries - for (int i = 0; i < num_b; i++) { - if (b[i].type == '\0') { - warning ("Parser error."); - return path_list; - } else if (b[i].type == 'z') { - path.close (); - path.create_list (); - - int first_index = 1; - - for (int j = i - 1; j >= 1; j--) { - if (b[j].type == 'z') { - first_index = j + 1; // from z to M - } - } - - if (b[first_index].type == 'C' || b[first_index].type == 'S') { - return_val_if_fail (path.points.size != 0, path_list); - ep = path.points.get (path.points.size - 1); - - if (b[i - 1].type != 'L' ) { - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[first_index].x0, b[first_index].y0); - } - } else if (b[first_index].type == 'L') { - return_val_if_fail (path.points.size != 0, path_list); - ep = path.points.get (path.points.size - 1); - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - path.recalculate_linear_handles_for_point (ep); - } else { - warning ("Unexpected type: %s", (!) b[first_index].type.to_string ()); - } - - path.recalculate_linear_handles (); - path_list.add (path); - - path = new Path (); - first_point = true; - } else if (b[i].type == 'M') { - } else if (b[i].type == 'L') { - - if (first_point) { - first_left_x = b[i].x0; - first_left_y = b[i].y0; - } - - ep = path.add (b[i].x0, b[i].y0); - ep.set_point_type (PointType.LINE_CUBIC); // TODO: quadratic - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - - if (b[i -1].type == 'L' || first_point) { - // ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - } - - if (b[i + 1].type == 'C' || b[i + 1].type == 'S') { - return_val_if_fail (i + 1 < num_b, path_list); - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); - } - - first_point = false; - } else if (b[i].type == 'Q') { - warning ("Illustrator does not support quadratic control points."); - warning (@"$(b[i])\n"); - } else if (b[i].type == 'C' || b[i].type == 'S') { - - if (first_point) { - first_left_x = b[i].x0; - first_left_y = b[i].y0; - } - - ep = path.add (b[i].x2, b[i].y2); - ep.set_point_type (PointType.CUBIC); - - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().set_point_type (PointType.CUBIC); - - ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); - - if (b[i].type == 'S') { - smooth_points.add (ep); - } - - if (b[i + 1].type != 'z') { - ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); - } else { - ep.get_right_handle ().move_to_coordinate (first_left_x, first_left_y); - } - - first_point = false; - } else { - warning ("Unknown control point type."); - warning (@"$(b[i])\n"); - } - } - - foreach (EditPoint e in smooth_points) { - e.set_point_type (PointType.LINE_DOUBLE_CURVE); - e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); - e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); - } - - foreach (EditPoint e in smooth_points) { - path.recalculate_linear_handles_for_point (e); - } - - for (int i = 0; i < 3; i++) { - foreach (EditPoint e in smooth_points) { - e.set_tie_handle (true); - e.process_tied_handle (); - } - } - - if (path.points.size > 0) { - path_list.add (path); - } - - foreach (Path p in path_list.paths) { - p.remove_points_on_points (); - } - - return path_list; - } - - // TODO: implement a default svg parser - - static int parse_int (string? s) { - if (is_null (s)) { - warning ("null instead of string"); - return 0; - } - - if (!is_point ((!) s)) { - warning (@"Expecting an integer got: $((!) s)"); - return 0; - } - - return int.parse ((!) s); - } - - static double parse_double (string? s) { - if (is_null (s)) { - warning ("Got null instead of expected string."); - return 0; - } - - if (!is_point ((!) s)) { - warning (@"Expecting a double got: $((!) s)"); - return 0; - } - - return double.parse ((!) s); - } - - static bool is_point (string? s) { - if (s == null) { - warning ("s is null"); - return false; - } - - return double.try_parse ((!) s); - } - - Path parse_poly_data (string polygon_points) { - string data = add_separators (polygon_points); - string[] c = data.split (" "); - Path path; - BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; - int bi; - Glyph g; - EditPoint ep; - - bi = 0; - for (int i = 0; i < c.length - 1; i += 2) { - if (i + 1 >= c.length) { - warning ("No y value."); - break; - } - - if (bi >= bezier_points.length) { - warning ("End of bezier_points"); - break; - } - - bezier_points[bi] = new BezierPoints (); - bezier_points[bi].type == 'L'; - bezier_points[bi].x0 = parse_double (c[i]); - bezier_points[bi].y0 = -parse_double (c[i + 1]); - bi++; - } - - g = MainWindow.get_current_glyph (); - move_and_resize (bezier_points, bi, false, 1, g); - - path = new Path (); - for (int i = 0; i < bi; i++) { - ep = path.add (bezier_points[i].x0, bezier_points[i].y0); - ep.set_point_type (PointType.LINE_CUBIC); - } - - path.create_list (); - path.recalculate_linear_handles (); - - return path; - } - } - - }
diff --git libbirdfont/SvgStyle.vala(deleted)
--- a/libbirdfont/SvgStyle.vala +++ /dev/null @@ -1,125 +1,1 @@ - /* - Copyright (C) 2015 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 Math; - - namespace BirdFont { - - public class SvgStyle { - - Gee.HashMap<string, string> style; - - public SvgStyle () { - style = new Gee.HashMap<string, string> (); - } - - public LineCap get_line_cap () { - string l; - - if (!style.has_key ("stroke-linecap")) { - return LineCap.BUTT; - } - - l = style.get ("stroke-linecap"); - - if (l == "round") { - return LineCap.ROUND; - } else if (l == "square") { - return LineCap.SQUARE; - } - - return LineCap.BUTT; - } - - public bool has_stroke () { - bool s = true; - - if (style.has_key ("stroke")) { - s = style.get ("stroke") != "none"; - } - - return get_stroke_width () > 0 && s; - } - - public double get_stroke_width () { - if (!style.has_key ("stroke-width")) { - return 0; - } - - return double.parse (style.get ("stroke-width")); - } - - - public static SvgStyle parse (Attributes attributes) { - SvgStyle s = new SvgStyle (); - - foreach (Attribute a in attributes) { - if (a.get_name () == "style") { - s.parse_key_value_pairs (a.get_content ()); - } - - if (a.get_name () == "stroke-width") { - s.style.set ("stroke-width", a.get_content ()); - } - - if (a.get_name () == "stroke") { - s.style.set ("stroke", a.get_content ()); - } - - if (a.get_name () == "fill") { - s.style.set ("fill", a.get_content ()); - } - } - - return s; - } - - void parse_key_value_pairs (string svg_style) { - string[] p = svg_style.split (";"); - string[] pair; - string k, v; - - foreach (string kv in p) { - pair = kv.split (":"); - - if (pair.length != 2) { - warning ("pair.length != 2"); - continue; - } - - k = pair[0]; - v = pair[1]; - - style.set (k, v); - } - } - - public void apply (PathList path_list) { - foreach (Path p in path_list.paths) { - if (has_stroke ()) { - p.stroke = get_stroke_width (); - } else { - p.stroke = 0; - } - - p.line_cap = get_line_cap (); - p.reset_stroke (); - p.update_region_boundaries (); - } - } - } - - }