Commit d5305e3
merge paths
Changed files (12)
modifiedsrc/Argument.valamodifiedsrc/ExportTool.valamodifiedsrc/Glyph.valamodifiedsrc/IntersectionList.valamodifiedsrc/OverView.valamodifiedsrc/Path.valamodifiedsrc/PenTool.valamodifiedsrc/Supplement.valamodifiedsrc/TestCases.valamodifiedsrc/TestSupplement.valamodifiedsrc/Tool.valamodifiedsrc/Toolbox.vala
Diff
@@ -60,7 +60,7 @@ class Argument : GLib.Object { } // valid parameter- if (a == "--autosave" || a == "--exit" || a == "--slow" || a == "--help" || a == "--test" || a == "--fatal-warning") {+ if (a == "--autosave" || a == "--exit" || a == "--slow" || a == "--help" || a == "--test" || a == "--fatal-warning" || a == "--show-coordinates") { prev = a; i++; continue;@@ -180,6 +180,8 @@ class Argument : GLib.Object { switch (p.get_char (1)) { case 'a': return "--autosave";+ case 'c':+ return "--show-coordinates"; case 'e': return "--exit"; case 'f': @@ -231,6 +233,7 @@ class Argument : GLib.Object { print_padded ("-h, --help", "show this message"); print_padded ("-s, --slow", "sleep between each command in test suite"); print_padded ("-t, --test [TEST]", "run test case");+ print_padded ("-c --show-coordinates", "show coordinate in glyph view"); stdout.printf ("\n"); }@@ -383,16 +383,6 @@ os.put_string (""" ta.show_text (@"Wrote $file_name"); return true; }- - public override bool test () {- this.yield ();- return true;- }-- public void test_view_result () {- test ();- view_result ();- } } }@@ -93,6 +93,9 @@ class Glyph : FontDisplay { public List<Kerning> kerning = new List<Kerning> (); + double motion_x = 0;+ double motion_y = 0;+ public Glyph (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code;@@ -198,7 +201,7 @@ class Glyph : FontDisplay { if (p.ymax > y2) y2 = p.ymax; } }-+ /** @return centrum pixel for x coordinates. */ public static double xc () { return MainWindow.get_current_glyph ().allocation.width / 2.0;@@ -532,7 +535,12 @@ class Glyph : FontDisplay { t = MainWindow.get_toolbox ().get_current_tool (); t.move_action (t, (int) e.x, (int) e.y);- ++ if (Supplement.show_coordinates) {+ motion_x = e.x * ivz () - xc () + view_offset_x;+ motion_y = yc () - e.y * ivz () - view_offset_y;+ }+ help_line_event (e); } @@ -1350,6 +1358,14 @@ class Glyph : FontDisplay { } } + private void draw_coordinate (Context cr) {+ cr.set_source_rgba (0.5, 0.5, 0.5, 1);+ cr.set_font_size (12);+ cr.move_to (0, 10);+ cr.show_text (@"($motion_x, $motion_y)");+ cr.stroke (); + }+ private void draw_path (Context cr) { double left, baseline; @@ -1395,14 +1411,11 @@ class Glyph : FontDisplay { } private void draw_help_lines (Context cr) {- - // lines foreach (Line line in get_all_help_lines ()) { cr.save (); line.draw (cr, allocation); cr.restore (); }- } public override void draw (Allocation allocation, Context cr) {@@ -1420,6 +1433,10 @@ class Glyph : FontDisplay { draw_background_color (cr, 1); cr.restore (); + if (Supplement.show_coordinates) {+ draw_coordinate (cmp);+ }+ if (show_help_lines) { cmp.save (); cmp.scale (view_zoom, view_zoom);@@ -25,7 +25,8 @@ class IntersectionList { public void add (Intersection i) { double d; Intersection l;- + bool close = false;+ if (points.length () == 0) { points.append (i); return;@@ -35,20 +36,34 @@ class IntersectionList { return; } - l = ((!) points.last ()).data;- d = Math.fabs (Math.sqrt (Math.pow (i.x - l.x, 2) + Math.pow (i.y - l.y, 2)));+ for (unowned List<Intersection> pl = points.first (); ; pl = pl.next) {+ l = pl.data;+ d = Math.fabs (Math.sqrt (Math.pow (i.x - l.x, 2) + Math.pow (i.y - l.y, 2))); - if (l.distance >= i.distance) {- points.remove_link (points.last ());+ if (l.distance >= i.distance) {+ close = true;+ + if (d < 0.3) {+ points.remove_link (points.last ());+ points.append (i);+ return;+ }+ }+ + if (pl == points.last ()) {+ break;+ }+ }+ + if (!close) { points.append (i);- return; }-- if (d < 0.3) {- return;+ }+ + public void clear () {+ while (points.length () > 0) {+ points.remove_link (points.first ()); }-- points.append (i); } public void remove_point (EditPoint e) {@@ -70,15 +85,19 @@ class IntersectionList { } bool has_point (Intersection i) {+ return has_point_at (i.x, i.y);+ }++ bool has_point_at (double x, double y) { foreach (Intersection n in points) {- if (i.x == n.x && i.y == n.y) {+ if (x == n.x && y == n.y) { return true; } } return false; }- + public int get_point_index (EditPoint e) { int i = 0; foreach (Intersection n in points) {@@ -105,13 +124,14 @@ class IntersectionList { } public Intersection? get_intersection (EditPoint e) {+ Intersection? p = null; foreach (Intersection n in points) { if (n.editpoint_a == e || n.editpoint_b == e) {- return n;+ assert (p == null);+ p = n; } }- - return null;+ return p; } public void append (IntersectionList i) {@@ -133,6 +153,7 @@ class IntersectionList { return il; } + // find crossing paths a_start = p1.points.first (); for (int i = 0; i < p1.points.length (); i++) { @@ -150,27 +171,15 @@ class IntersectionList { } else { b_stop = b_start.data.get_next (); }- - //if (probably_intersecting (a_start.data, a_stop.data, b_start.data, b_stop.data)) {- il.append (find_intersections (a_start.data, a_stop.data, b_start.data, b_stop.data));- //}- ++ il.append (find_intersections (a_start.data, a_stop.data, b_start.data, b_stop.data));+ b_start = b_stop; } a_start = a_stop;- } -/*- foreach (Intersection inter in il.points) {- e = new EditPoint ();- p1.get_closest_point_on_path (e, inter.x, inter.y);- inter.editpoint_a = e;-- e = new EditPoint ();- p2.get_closest_point_on_path (e, inter.x, inter.y);- inter.editpoint_b = e;- } - */+ }+ return il; } @@ -189,7 +198,7 @@ class IntersectionList { double d = Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2))); if (d < 0.2) {- il.add (new Intersection (ax, ay, 0));+ il.add (new Intersection (ax, ay, d)); } if (d < mind) mind = d;@@ -333,11 +333,6 @@ class OverView : FontDisplay { character_string = glyph_range.get_char ((uint32) index); } - if (character_string == "") {- warning (@"Got null character as name for glyph. Index: $index");- // break;- }- cr.save (); draw_thumbnail (cr, character_string, x, y); cr.restore ();@@ -386,7 +386,7 @@ class Path { next = ((!)ed).data; - if (is_over (ep.x, ep.y)) {+ if (is_over_coordinate (ep.x, ep.y)) { union.reverse (); return false; }@@ -742,37 +742,80 @@ class Path { } public bool is_over_coordinate (double x, double y) {+ return is_over_coordinate_var (x, y, 0.1);+ }+ + public double distance (double ax, double bx, double ay, double by) {+ return Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));+ }+ + /** Variable precision */+ public bool is_over_coordinate_var (double x, double y, double precision) { List<EditPoint> ycoordinates = new List<EditPoint> (); double last = 0;- - if (!is_over_boundry (x, y)) {+ bool on_edge = false;+ double last_x = 0;+ + if (points.length () == 0) {+ return false;+ }+ + if (!is_over_boundry_precision (x, y, precision)) { return false; } + foreach (EditPoint e in points) {+ if (distance (e.x, x, e.y, y) < precision) {+ return true;+ }+ }+ all_of_path ((cx, cy, ct) => {- if (Math.fabs (cx - x) < 0.2 && Math.fabs (last - cy) > 1) {+ double distance = Math.fabs (Math.sqrt (Math.pow (cx - x, 2) + Math.pow (cy - y, 2)));+ + if (distance < precision) {+ on_edge = true;+ return false;+ }+ + if (Math.fabs (cx - x) < precision && Math.fabs (last - cy) > 1) { ycoordinates.append (new EditPoint (cx, cy)); last = cy; }+ + last_x = cx; return true; }); + if (on_edge) {+ return true;+ }+ ycoordinates.sort ((a, b) => { return (a.y < b.y) ? 1 : -1; });-- return_val_if_fail (ycoordinates.length () >= 2, true); if (unlikely (ycoordinates.length () % 2 != 0)) { warning (@"not an even number of coordinates ($(ycoordinates.length ()))");- }+ stderr.printf (@"(ymin <= y <= ymax) && (xmin <= x <= xmax);\n");+ stderr.printf (@"($ymin <= $y <= $ymax) && ($xmin <= $x <= $xmax);\n");+ + stderr.printf ("ycoordinates:\n");+ foreach (EditPoint e in ycoordinates) {+ stderr.printf (@"$(e.y)\n");+ } + ycoordinates.append (ycoordinates.last ().data.copy ());+ + return true;+ }+ for (unowned List<EditPoint> e = ycoordinates.first (); true; e = e.next) {- if (y < e.data.y) {+ if (y <= e.data.y + precision) { return_if_fail ((void*) e.next != null); e = e.next; - if (y > e.data.y) {+ if (y >= e.data.y - precision) { return true; } }@@ -785,12 +828,24 @@ class Path { return false; } + public bool is_over_boundry_precision (double x, double y, double p) {+ if (unlikely (ymin == double.MAX)) {+ warning ("no bounding box");+ }+ + return (ymin - p <= y <= ymax + p) && (xmin - p <= x <= xmax + p);+ }+ public bool is_over_boundry (double x, double y) {+ if (unlikely (ymin == double.MAX)) {+ warning ("bounding box is not calculated, run update_region_boundries first.");+ }+ return (ymin <= y <= ymax) && (xmin <= x <= xmax); } public bool has_overlapping_boundry (Path p) {- return !(xmax < p.xmin || ymax < p.ymin) || (xmin > p.xmax || ymin > p.ymax);+ return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax); } public void add (double x, double y) {@@ -1570,11 +1625,7 @@ class Path { bool done; PathList path_list; - done = try_merge (p1.copy(), p0.copy(), out path_list);- - if (!done) {- done = try_merge (p0.copy(), p1.copy(), out path_list);- }+ done = try_merge (p0.copy(), p1.copy(), out path_list); if (!done) { warning ("failed to merge paths");@@ -1585,7 +1636,7 @@ class Path { private static bool try_merge (Path p0, Path p1, out PathList path_list) { EditPoint e;- IntersectionList il = IntersectionList.create_intersection_list (p0, p1);+ IntersectionList il; EditPoint ex; EditPoint ix;@@ -1599,7 +1650,11 @@ class Path { bool over; Path np; Path np_counter;- + + unowned List<Path> pi;+ + il = IntersectionList.create_intersection_list (p0, p1);+ path_list = new PathList (); if (p0 == p1) {@@ -1607,6 +1662,8 @@ class Path { } // add editpoints points on intersections + p0.update_region_boundries ();+ p1.update_region_boundries (); foreach (Intersection inter in il.points) { e = new EditPoint (); p0.get_closest_point_on_path (e, inter.x, inter.y);@@ -1621,72 +1678,239 @@ class Path { p1.get_closest_point_on_path (e, inter.x, inter.y); inter.editpoint_b = e; + inter.editpoint_b.x = inter.editpoint_a.x;+ inter.editpoint_b.y = inter.editpoint_a.y;+ e = inter.editpoint_b; if (!p1.has_edit_point (e)) { p1.insert_new_point_on_path (e); }+ }+ + if (il.points.length () < 2) {+ return false;+ }+ + //path_list.paths.append (p0);+ //path_list.paths.append (p1);+ //return false;+ + // get all parts+ foreach (Intersection inter in il.points) {+ p0.set_new_start (inter.editpoint_a);+ get_merge_part (il, p0, p1, out np);+ path_list.paths.append (np);+ }+ + foreach (Path pp in path_list.paths) {+ pp.update_region_boundries ();+ } + // remove duplicate paths+ for (i = 0; i < path_list.paths.length (); i++) {+ pi = path_list.paths.nth (i);+ + if (is_duplicated (path_list, pi.data)) {+ path_list.paths.remove_link (pi);+ --i; + } } - if (il.points.length () == 0) {- return false;+ // remova paths contained in other paths+ for (i = 0; i < path_list.paths.length (); i++) {+ pi = path_list.paths.nth (i);+ + if (pi.data.is_clockwise () && is_clasped (path_list, pi.data)) {+ path_list.paths.remove_link (pi);+ i--;+ } } - // begin outside intersection- ex = p0.points.first ().data;- prev = p0.points.first ().data;- for (i = 0; i < p0.points.length (); i++) {- ex = prev.get_next ().data;+ return true;+ }+ + private static bool is_duplicated (PathList pl, Path p) {+ bool duplicate = false;+ foreach (Path pd in pl.paths) {+ if (pd == p) {+ continue;+ } - if (i != 0 && !p1.is_over_coordinate (ex.x, ex.y) && !p1.is_over_coordinate (prev.x, prev.y) && !il.has_edit_point (prev) && !il.has_edit_point (ex)) {- p0.set_new_start (ex);- break;+ if (is_duplicated_path (pd, p)) {+ duplicate = true; }+ }+ + return duplicate;+ }+ + private static bool is_duplicated_path (Path p0, Path p1) {+ bool eq;+ + assert (p1 != p0); + + foreach (EditPoint ep in p0.points) {+ eq = false; - prev = ex;+ foreach (EditPoint e in p1.points) {+ eq = (Math.fabs (ep.x - e.x) < 0.04 && Math.fabs (ep.y - e.y) < 0.04);+ + if (eq) {+ break;+ }+ }+ + if (eq) {+ continue;+ } else {+ return false;+ } }-- if (i == p0.points.length ()) {- warning ("no point outside path.");+ + return true;+ }+ + private static bool is_clasped (PathList pl, Path p) {+ foreach (Path o in pl.paths) {+ if (o == p) {+ continue;+ }+ + if (is_clasped_path (o, p)) {+ return true;+ } }+ + return false;+ } - print (@"il.points.length (): $(il.points.length ())\n");-- //path_list.paths.append (p0);- //path_list.paths.append (p1);- //return false;- - // create a new path - if (!create_merged_path (il, p0, p1, out np)) {- return false;+ private static bool is_clasped_path (Path outside, Path inside) {+ bool i = true;+ foreach (EditPoint e in inside.points) {+ if (!outside.is_over_coordinate_var (e.x, e.y, 0.5)) { // high tolerance since point may be off curve in both paths+ i = false;+ } }+ return i;+ }+ + private static bool get_merge_part (IntersectionList il, Path p0, Path p1, out Path new_path) {+ EditPoint ex;+ EditPoint ix;+ EditPoint en;+ uint offset_i = 0;+ uint offset_j;+ uint len_i;+ int i, j;+ uint len_j;+ bool over;+ Path np = new Path ();+ Intersection s = new Intersection (0, 0, 1); - path_list.paths.append (np);+ ex = p0.points.last ().data;+ ix = p0.points.last ().data;+ len_i = p0.points.length (); - if (!np.is_clockwise ()) {- warning ("Outline is counter clockwise after merge");- return false;- }+ for (i = 0; i < p0.points.length (); i++) {+ ex = p0.points.nth ((i + offset_i) % len_i).data; - // find counter path if we have more intersections- if (il.points.length () > 0) { - ex = p0.points.first ().data;- p0.set_new_start (il.points.first ().data.editpoint_a);+ if (ex == p0.points.first ().data && i != 0) { + s = (!) il.get_intersection (ex); + break;+ } - if (!create_merged_path (il, p0, p1, out np_counter)) {- warning ("Failed to merge counter");+ // add new point for path a+ if (np.has_edit_point (ex)) {+ // SPLIT+ warning ("Merged path need split");+ np.close ();+ new_path = np;+ + return false;+ } else {+ en = ex.copy ();+ np.add_point (en);+ en.recalculate_linear_handles (); } - path_list.paths.append (np_counter);+ // swap paths+ if (il.has_edit_point (ex)) {+ s = (!) il.get_intersection (ex); - if (np_counter.is_clockwise ()) {- warning ("Counter is clockwise after merge");- return false;+ en.type = PointType.CURVE;+ en.right_handle.type = PointType.CURVE;+ en.right_handle.angle = s.editpoint_b.right_handle.angle;+ en.right_handle.length = s.editpoint_b.right_handle.length;+ + // read until we find ex+ for (j = 0; j < p1.points.length (); j++) {+ ix = p1.points.nth (j).data;+ + if (ix == s.editpoint_b) {+ break;+ }+ }+ + offset_j = j + 1;+ len_j = p1.points.length ();+ for (j = 0; j < p1.points.length (); j++) {+ + ix = p1.points.nth ((j + offset_j) % len_j).data;+ + // add+ if (np.has_edit_point (ix)) {+ // SPLIT+ warning ("Merged path need split");+ np.close ();+ new_path = np;+ + break;+ } else {+ en = ix.copy ();+ np.add_point (en);+ ix.recalculate_linear_handles ();+ }+ + if (il.has_edit_point (ix)) {+ s = (!) il.get_intersection (ix);+ break;+ }+ }++ en.type = PointType.CURVE;+ en.right_handle.type = PointType.CURVE;+ en.right_handle.angle = s.editpoint_a.right_handle.angle;+ en.right_handle.length = s.editpoint_a.right_handle.length;+ + if (j == p0.points.length ()) {+ np.close ();+ new_path = np;+ return true;+ }++ // skip to next intersection+ int k;+ for (k = 0; k < p0.points.length (); k++) {+ ix = p0.points.nth (k).data; ++ if (ix == s.editpoint_a) {+ break;+ }+ }+ + if (k == p0.points.length ()) {+ new_path = np;+ return true;+ }+ + offset_i = 0;+ i = k; }- }++ new_path = np; - return true;+ return true; } private static bool create_merged_path (IntersectionList il, Path p0, Path p1, out Path new_path) {@@ -1716,6 +1940,9 @@ class Path { if (np.has_edit_point (ex)) { // SPLIT warning ("Merged path need split");+ np.close ();+ new_path = np;+ return false; } else { np.add_point (ex); ex.recalculate_linear_handles ();@@ -1752,6 +1979,9 @@ class Path { if (np.has_edit_point (ix)) { // SPLIT warning ("Merged path need split");+ np.close ();+ new_path = np;+ return false; } else { np.add_point (ix); ix.recalculate_linear_handles ();@@ -586,6 +586,7 @@ class PenTool : Tool { // merge it + this.yield (); return true;@@ -22,6 +22,8 @@ namespace Supplement { class Supplement { public static Argument args; public static bool experimental; + public static bool show_coordinates; + public static bool fatal_wanings; static Font current_font; @@ -105,6 +107,8 @@ class Supplement { stdout.printf ("birdfont version %s\n", VERSION); experimental = args.has_argument ("--test"); + show_coordinates = args.has_argument ("--show-coordinates"); + fatal_wanings = has_argument ("--fatal-warning"); Preferences preferences = new Preferences (); preferences.load (); @@ -124,7 +128,7 @@ class Supplement { MainWindow window = new MainWindow ("Birdfont"); window.show_all (); - if (has_argument ("--fatal-warning")) { + if (fatal_wanings) { LogLevelFlags levels = LogLevelFlags.LEVEL_ERROR | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_WARNING; Log.set_handler (null, levels, fatal_warning); } @@ -35,10 +35,85 @@ class TestCases { add (test_coordinates, "Coordinates"); add (test_drawing, "Pen tool"); add (test_delete_points, "Delete edit points");- add (test_view_result, "View result in web browser"); add (test_save_backup, "Save backup"); add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path"); add (test_notdef, "Notdef");+ add (test_merge, "Merge");+ add (test_over_path, "Over path");+ }++ public static void test_over_path () {+ Glyph g;+ Path p = new Path ();+ Path p2 = new Path ();+ Tool pen_tool;+ + pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool");+ pen_tool.test_select_action ();+ Tool.test_open_next_glyph ();+ + g = MainWindow.get_current_glyph ();++ pen_tool.test_click_action (1, 10, 10);+ pen_tool.test_click_action (1, 10, 10);+ pen_tool.test_click_action (1, 100, 10);+ pen_tool.test_click_action (1, 100, 100);+ pen_tool.test_click_action (1, 10, 100);+ pen_tool.test_click_action (3, 0, 0);++ warn_if_fail (g.active_paths.length () == 0);++ g.select_path (50, 50);+ + warn_if_fail (g.active_paths.length () == 1);+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.update_region_boundries ();+ p.close ();+ g.add_path (p);++ if (!p.is_over_coordinate (0, 0)) {+ warning ("Coordinate 0, 0 is not in path.");+ }+ + if (!p.is_over_coordinate (-10, 10)) {+ warning ("Corner corrdinate -10, 10 is not in path.");+ }+ + warn_if_fail (!p.is_over_coordinate (-20, -20)); + }++ public static void test_merge () {+ Glyph g;+ Path p = new Path ();+ Path p2 = new Path ();+ + Tool.test_open_next_glyph ();+ g = MainWindow.get_current_glyph ();+ + p.add (-10, 10);+ p.add (10, 10);+ p.add (10, -10);+ p.add (-10, -10);+ p.close ();+ g.add_path (p);++ p2.add (10, 10);+ p2.add (10, -10); + p2.add (20, -10);+ p2.add (20, 10);+ p2.reverse ();+ p2.close ();+ g.add_path (p2);+ + g.add_active_path (p);+ g.add_active_path (p);+ + g.add_active_path (p);+ g.merge_all (); } public static void test_notdef () {@@ -349,11 +424,6 @@ class TestCases { tool.test_coordinates (); } - public static void test_view_result () {- ExportTool tool = (ExportTool) MainWindow.get_toolbox ().get_tool ("export");- tool.test_view_result ();- }- public static void test_reverse_random_paths () { PenTool tool = (PenTool) MainWindow.get_toolbox ().get_tool ("pen_tool"); tool.test_reverse_random_triangles (); @@ -134,6 +134,8 @@ class TestSupplement : GLib.Object { stderr.printf ("%s\n\n", message); get_singleton ().has_failed = true; + + assert (!Supplement.fatal_wanings); } public static void @continue () { @@ -265,9 +265,11 @@ class Tool : GLib.Object { public static void test_open_next_glyph () { Tool.yield ();+ Glyph g; OverView o = MainWindow.get_overview (); MainWindow.get_tab_bar ().select_overview ();+ MainWindow.get_toolbox ().select_tool_by_name ("utf_8"); o.select_next_glyph (); Tool.yield ();@@ -146,10 +146,8 @@ class Toolbox : DrawingArea { draw_tool_modifiers.add_tool (erase_tool); // path tools- if (Supplement.experimental) {- Tool union_paths_tool = new MergeTool ("union_paths");- path_tool_modifiers.add_tool (union_paths_tool);- }+ Tool union_paths_tool = new MergeTool ("union_paths");+ path_tool_modifiers.add_tool (union_paths_tool); Tool reverse_path_tool = new Tool ("reverse_path", "Create counter from outline", 'r'); reverse_path_tool.select_action.connect((self) => {