Commit bb5c3f9
quadratic control points
Changed files (10)
modifiedlibbirdfont/EditPoint.valamodifiedlibbirdfont/EditPointHandle.valamodifiedlibbirdfont/Font.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/ImportSvg.valamodifiedlibbirdfont/Path.valamodifiedlibbirdfont/PenTool.valamodifiedlibbirdfont/Svg.valamodifiedlibbirdfont/SvgFont.valamodifiedlibbirdfont/TestCases.vala
Diff
@@ -24,6 +24,7 @@ public enum PointType { NORMAL, LINE, CURVE,+ QUADRATIC, DOUBLE_CURVE, END, FLOATING@@ -111,7 +111,7 @@ public class EditPointHandle { return p; }- + public void move_to_coordinate (double x, double y) { double a, b, c; @@ -1378,7 +1378,8 @@ public class Font : GLib.Object { bool r; if (unlikely (unicode.index_of ("U+") != 0)) {- warning ("All unicode values must begin with U+");+ warning (@"All unicode values must begin with U+ ($unicode");+ return '\0'; } while (r = unicode.get_next_char (ref index, out c)) {@@ -1275,8 +1275,6 @@ public class Glyph : FontDisplay { public void draw_path (Context cr) { double left, baseline;-- // plot_outline (cr); baseline = get_line ("baseline").pos; left = get_line ("left").pos;@@ -168,13 +168,18 @@ public class ImportSvg { 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", " ");@@ -348,7 +353,7 @@ public class ImportSvg { py = cy; } }- + if (c[i] == "c") { while (is_point (c[i + 1])) { command[ci++] = "C";@@ -406,7 +411,10 @@ public class ImportSvg { } else { cy = -parse_double (c[++i]); }- ++ px2 = cx;+ py2 = cy;+ p[pi++] = cx; p[pi++] = cy; @@ -437,6 +445,135 @@ public class ImportSvg { } } + // quadratic+ if (c[i] == "q") {+ while (is_point (c[i + 1])) {+ command[ci++] = "Q";+ + cx = px + parse_double (c[++i]);+ + if (svg_glyph) {+ cy = py + parse_double (c[++i]);+ } else {+ cy = py + -parse_double (c[++i]);+ }+ + p[pi++] = cx;+ p[pi++] = 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]);+ }+ + p[pi++] = cx;+ p[pi++] = cy;+ + px = cx;+ py = cy;+ }+ }+ + if (c[i] == "Q") {++ while (is_point (c[i + 1])) {+ command[ci++] = "Q";+ + cx = parse_double (c[++i]);+ + if (svg_glyph) {+ cy = parse_double (c[++i]);+ } else {+ cy = -parse_double (c[++i]);+ }+ + p[pi++] = cx;+ p[pi++] = cy;++ px2 = cx;+ py2 = cy;+ + cx = parse_double (c[++i]);+ + if (svg_glyph) {+ cy = parse_double (c[++i]);+ } else {+ cy = -parse_double (c[++i]);+ }+ + p[pi++] = cx;+ p[pi++] = cy;+ + px = cx;+ py = cy; + } + }++ if (c[i] == "t") {+ while (is_point (c[i + 1])) {+ command[ci++] = "Q";+ + // the first point is the reflection+ cx = 2 * px - px2;+ cy = 2 * py - py2; // if (svg_glyph) ?+ + p[pi++] = cx;+ p[pi++] = 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;+ + p[pi++] = px;+ p[pi++] = py;+ }+ }++ if (c[i] == "T") {+ while (is_point (c[i + 1])) {+ command[ci++] = "Q";+ + // the reflection+ cx = 2 * px - px2;+ cy = 2 * py - py2; // if (svg_glyph) ?+ p[pi++] = cx;+ p[pi++] = 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;+ + p[pi++] = px;+ p[pi++] = py; + }+ }+ if (c[i] == "s") { while (is_point (c[i + 1])) { command[ci++] = "C";@@ -529,13 +666,17 @@ public class ImportSvg { Path path = new Path (); - // move+ // move and resize if (svg_glyph) {+ // resize all points+ for (int i = 0; i < pi; i++) {+ p[i] *= units;+ }+ + // move only y for (int i = 0; i < pi; i += 2) {- p[i] *= units; // TODO: test it. p[i] += glyph.left_limit;- //p[i+1] += font.top_position;- p[i+1] -= font.base_line; // FIXME: is the font at the baseline?+ p[i+1] -= font.base_line; } } else { for (int i = 0; i < pi; i += 2) {@@ -563,7 +704,31 @@ public class ImportSvg { if (command[i] == "L") { path.add (p[ic++], p[ic++]); }- + + if (command[i] == "Q") {+ x0 = p[ic++];+ y0 = p[ic++];+ x1 = p[ic++];+ y1 = p[ic++];++ if (is_null (path.points.last ().data)) {+ warning ("Paths must begin with M");+ return;+ }+ + ep1 = path.points.last ().data;+ ep1.recalculate_linear_handles ();+ ep1.get_right_handle ().type = PointType.QUADRATIC;+ ep1.get_right_handle ().move_to_coordinate (x0, y0); ++ path.add (x1, y1);++ ep2 = path.points.last ().data;+ ep2.recalculate_linear_handles ();+ ep2.get_left_handle ().type = PointType.QUADRATIC;+ ep2.get_left_handle ().move_to_coordinate (x0, y0); + }+ if (command[i] == "C") { x0 = p[ic++]; y0 = p[ic++];@@ -210,7 +210,11 @@ public class Path { cr.line_to (xa, ya); // this point makes sense only if it is the first or last position, the other points are meaning less don't export them - cr.curve_to (xb, yb, xc, yc, xd, yd);+ if (en.get_right_handle ().type == PointType.QUADRATIC) {+ cr.curve_to ((xa + 2 * xb) / 3, (ya + 2 * yb) / 3, (xd + 2 * xb) / 3, (yd + 2 * yb) / 3, xd, yd); + } else {+ cr.curve_to (xb, yb, xc, yc, xd, yd);+ } } /** Curve relative to window center. */@@ -299,7 +303,7 @@ public class Path { draw_img_center (cr, img_right, e.get_right_handle ().x (), e.get_right_handle ().y ()); } - if (!(is_open () && e == points.first ().data)) {+ if (!(is_open () && e == points.first ().data) && e.get_left_handle ().type != PointType.QUADRATIC) { draw_line (handle_left, e, cr, 0.15); draw_img_center (cr, img_left, e.get_left_handle ().x (), e.get_left_handle ().y ()); }@@ -966,7 +970,7 @@ public class Path { eh.length = 0; eh = ((!)quadratic_path.points.last ().prev).data.get_right_handle ();- eh.set_point_type (PointType.CURVE);+ eh.set_point_type (PointType.QUADRATIC); eh.length *= 1.6; return quadratic_path;@@ -986,7 +990,7 @@ public class Path { eh.length = 0; eh = start.get_right_handle ();- eh.set_point_type (PointType.CURVE);+ eh.set_point_type (PointType.QUADRATIC); eh.move_to_coordinate (curve_x, curve_y); } @@ -171,14 +171,16 @@ public class PenTool : Tool { // move curve handles if (move_selected_handle) {+ if (selected_handle.type == PointType.LINE) {+ selected_handle.set_point_type (PointType.CURVE); // TODO: quadratic+ }+ if (GridTool.is_visible ()) { coordinate_x = Glyph.path_coordinate_x (x); coordinate_y = Glyph.path_coordinate_y (y); GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y);- selected_handle.set_point_type (PointType.CURVE); selected_handle.move_to_coordinate (coordinate_x, coordinate_y); } else {- selected_handle.set_point_type (PointType.CURVE); selected_handle.move_delta ((x - last_point_x) * precision, (y - last_point_y) * precision); } @@ -78,13 +78,19 @@ class Svg { } private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph) {+ if (end.get_left_handle ().type == PointType.QUADRATIC) {+ add_quadratic_abs_path (end, start, svg, g, do_glyph);+ }+ if (start.right_handle.type == PointType.LINE && end.left_handle.type == PointType.LINE) { add_abs_line_to (end, start, svg, g, do_glyph);- } else if (end.get_left_handle ().type == PointType.NONE) {- add_quadratic_abs_path (end, start, svg, g, do_glyph);- } else {+ } else if (end.get_left_handle ().type == PointType.CURVE) { add_cubic_abs_path (end, start, svg, g, do_glyph); }++ if (end.get_left_handle ().type == PointType.NONE) {+ warning ("No point type.");+ } } private static void add_abs_start (EditPoint ep, StringBuilder svg, Glyph g, bool to_glyph) { @@ -142,6 +148,8 @@ class Svg { double center_x = Glyph.xc (); double center_y = Glyph.yc ();++ // FIXME xc or xd, right or left handle?: // cubic path if (!to_glyph) {@@ -150,8 +158,8 @@ class Svg { svg.append_printf ("%s ", round (xb - center_x - left)); svg.append_printf ("%s ", round (yb - center_y - baseline + height)); - svg.append_printf ("%s ", round (xc - center_x - left));- svg.append_printf ("%s ", round (yc - center_y - baseline + height));+ svg.append_printf ("%s ", round (xd - center_x - left));+ svg.append_printf ("%s ", round (yd - center_y - baseline + height)); } else { svg.append_printf ("Q");@@ -159,8 +167,8 @@ class Svg { 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)); } } @@ -207,13 +215,16 @@ class Svg { 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 (1);@@ -227,13 +238,16 @@ class Svg { // trim off leading white space while (d[i].index_of (" ") == 0) { - d[i] = d[i].substring (1);+ 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; } @@ -243,8 +257,11 @@ class Svg { x2 = double.parse (d[i+2]) + x; y2 = -double.parse (d[i+3]) + y;- - cr.curve_to (x1, y1, x2, y2, x2, y2);+ + 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; } @@ -259,6 +276,9 @@ class Svg { y3 = -double.parse (d[i+5]) + y; cr.curve_to (x1, y1, x2, y2, x3, y3);+ + px = x3;+ py = y3; continue; } @@ -267,6 +287,9 @@ class Svg { y1 = -double.parse (d[i+1]) + y; cr.move_to (x1, y1);++ px = x1;+ py = y1; continue; } @@ -277,6 +300,9 @@ class Svg { y1 = -double.parse (d[i+1]) + y; cr.move_to (x1, y1);++ px = x1;+ py = y1; continue; } @@ -22,7 +22,8 @@ namespace Supplement { class SvgFont : GLib.Object { Font font; double units = 1;- + double font_advance = 0;+ public SvgFont (Font f) { this.font = f; }@@ -43,16 +44,13 @@ class SvgFont : GLib.Object { } void parse_svg_font (Xml.Node* node) {- print ("parse_svg_font" + node->name + "\n"); for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {- print (iter->name + "\n");- if (iter->name == "defs") { parse_svg_font (iter); } if (iter->name == "font") {- parse_font_name (iter);+ parse_font_tag (iter); parse_svg_font (iter); } @@ -73,6 +71,8 @@ class SvgFont : GLib.Object { void parse_hkern (Xml.Node* node) { string left = ""; string right = "";+ string left_name = "";+ string right_name = ""; double kerning = 0; unichar l, r; StringBuilder sl, sr;@@ -83,8 +83,6 @@ class SvgFont : GLib.Object { attr_name = prop->name; attr_content = prop->children->content; - print (attr_name + "\n");- // left if (attr_name == "u1") { left = attr_content;@@ -94,29 +92,37 @@ class SvgFont : GLib.Object { if (attr_name == "u2") { right = attr_content; }++ if (attr_name == "g1") {+ left_name = attr_content;+ } + if (attr_name == "g2") {+ right_name = attr_content;+ }+ // kerning if (attr_name == "k") { kerning = double.parse (attr_content); }- - // TODO: ranges and sequences for u1 & u2 + g1 & g2 } - print ("L " + left + "\n");- - l = get_unichar (left);- r = get_unichar (right);- - sl = new StringBuilder ();- sl.append_unichar (l);- - print ("L " + sl.str + "\n"); - sr = new StringBuilder ();- sr.append_unichar (r);- - font.set_kerning (sl.str, sr.str, -kerning);+ // TODO: 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);+ + font.set_kerning (sl.str, sr.str, -kerning); + }+ } } void parse_font_limits (Xml.Node* node) {@@ -149,7 +155,7 @@ class SvgFont : GLib.Object { font.top_limit = top_limit; } - void parse_font_name (Xml.Node* node) {+ void parse_font_tag (Xml.Node* node) { string attr_name = ""; string attr_content; @@ -157,6 +163,10 @@ class SvgFont : GLib.Object { attr_name = prop->name; attr_content = prop->children->content; + if (attr_name == "horiz-adv-x") {+ font_advance = double.parse (attr_content);+ }+ if (attr_name == "id") { font.set_name (attr_content); }@@ -170,6 +180,10 @@ class SvgFont : GLib.Object { string v = val; unichar unicode_value; + if (val == "&") {+ return '&';+ }+ // TODO: parse ligatures if (v.has_prefix ("&")) { // parse hex value@@ -192,7 +206,7 @@ class SvgFont : GLib.Object { string glyph_name = ""; string svg = ""; Glyph glyph;- double advance = 100;+ double advance = font_advance; for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) { attr_name = prop->name;@@ -200,10 +214,7 @@ class SvgFont : GLib.Object { if (attr_name == "unicode") { unicode_value = get_unichar (attr_content);- - if (glyph_name == "") {- glyph_name = attr_content;- }+ glyph_name = attr_content; } // svg data@@ -212,12 +223,14 @@ class SvgFont : GLib.Object { } if (attr_name == "glyph-name") {- glyph_name = attr_content;+ // use glyph value as name until ligatures is properly implemented+ if (glyph_name == "") {+ glyph_name = attr_content;+ } } if (attr_name == "horiz-adv-x") { advance = double.parse (attr_content);- print (@"horiz-adv-x $advance\n"); } } @@ -45,6 +45,15 @@ class TestCases { add (test_spin_button, "Spin button"); add (test_inkscape_import, "Inkscape import"); add (test_illustrator_import, "Illustrator import");+ add (test_parse_quadratic_paths, "Quadratic paths");+ }++ public static void test_parse_quadratic_paths () {+ Glyph g;+ Tool.test_open_next_glyph ();+ g = MainWindow.get_current_glyph ();+ ImportSvg.parse_svg_data ("M20,300 Q400,50 600,300 T1000,300Q1200 50 1400 300Q1600 50 1800 600 L 1800 700 L 200 700 z", g);+ Toolbox.select_tool_by_name ("full_glyph"); } public static void test_illustrator_import () {