Commit 454247c
Draw outline of paths by tracking mouse movements This commits adds a new pen tool that lets the user draw shapes on free hand in contrast to the pencil tool.
Changed files (27)
addedicons/zoom_boundaries.pngdeletedicons/zoom_boundries.pngmodifiedlibbirdfont/BirdFontFile.valamodifiedlibbirdfont/CircleTool.valamodifiedlibbirdfont/ClipTool.valamodifiedlibbirdfont/DrawingTools.valamodifiedlibbirdfont/EditPoint.valamodifiedlibbirdfont/EditPointHandle.valamodifiedlibbirdfont/FileTab.valamodifiedlibbirdfont/Font.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/MoveTool.valamodifiedlibbirdfont/NativeWindow.valamodifiedlibbirdfont/OpenFontFormat/GlyfTable.valamodifiedlibbirdfont/OpenFontFormat/HeadTable.valamodifiedlibbirdfont/OpenFontFormat/HmtxTable.valamodifiedlibbirdfont/OpenFontFormat/OpenFontFormatReader.valamodifiedlibbirdfont/OpenFontFormat/PostTable.valamodifiedlibbirdfont/OverViewItem.valamodifiedlibbirdfont/Path.valamodifiedlibbirdfont/PenTool.valamodifiedlibbirdfont/PointSelection.valamodifiedlibbirdfont/Preferences.valamodifiedlibbirdfont/ResizeTool.valamodifiedlibbirdfont/SpinButton.valamodifiedlibbirdfont/TestCases.valaaddedlibbirdfont/TrackTool.vala
Diff
new file mode 100644Binary files /dev/null and b/icons/zoom_boundaries.png differdeleted file mode 100644Binary files a/icons/zoom_boundries.png and /dev/null differ@@ -494,7 +494,7 @@ class BirdFontFile { } if (iter->name == "lines") {- parse_font_boundries (iter);+ parse_font_boundaries (iter); } if (iter->name == "grid") {@@ -807,7 +807,7 @@ class BirdFontFile { } } - private void parse_font_boundries (Xml.Node* node) {+ private void parse_font_boundaries (Xml.Node* node) { for (Xml.Node* iter = node->children; iter != null; iter = iter->next) { if (iter->name == "top_limit" && "" != iter->children->content) { font.top_limit = parse_double_from_node (iter);@@ -1195,7 +1195,7 @@ class BirdFontFile { path.points.remove_link (path.points.last ()); } - path.update_region_boundries ();+ path.update_region_boundaries (); return path; }@@ -61,7 +61,7 @@ public class CircleTool : Tool { } if (resize_circle) {- circle.update_region_boundries ();+ circle.update_region_boundaries (); diameter = circle.xmax - circle.xmin; cx = circle.xmin + diameter / 2; cy = circle.ymin + diameter / 2;@@ -77,7 +77,7 @@ public class CircleTool : Tool { nx = circle.xmin + diameter / 2; ny = circle.ymin + diameter / 2; - circle.update_region_boundries ();+ circle.update_region_boundaries (); circle.move (cx - nx, cy - ny); last_radius = radius;@@ -43,7 +43,7 @@ public class ClipTool : Tool { paste_paths (); - g.selection_boundries (out x, out y, out w, out h);+ g.selection_boundaries (out x, out y, out w, out h); dx = g.motion_x - x - w / 2.0; dy = g.motion_y - y + h / 2.0;@@ -191,7 +191,7 @@ public class ClipTool : Tool { if (path.points.length () > 0) { glyph.add_path (path); glyph.active_paths.append (path);- path.update_region_boundries ();+ path.update_region_boundaries (); } PenTool.remove_all_selected_points ();@@ -72,8 +72,11 @@ public class DrawingTools : ToolCollection { Tool resize_tool = new ResizeTool ("resize"); draw_tools.add_tool (resize_tool); - Tool stroke_tool = new StrokeTool ("stroke");+ Tool stroke_tool = new StrokeTool ("stroke"); // create outline from path draw_tools.add_tool (stroke_tool);++ Tool track_tool = new TrackTool ("track"); // create outline from path+ draw_tools.add_tool (track_tool); // quadratic Bézier points Tool quadratic_points = new Tool ("quadratic_points", t_("Create quadratic Bézier curves"));@@ -136,7 +139,7 @@ public class DrawingTools : ToolCollection { p.set_tie_handle (tie); - PenTool.handle_selection.path.update_region_boundries ();+ PenTool.handle_selection.path.update_region_boundaries (); } else { foreach (PointSelection ep in PenTool.selected_points) { tie = !ep.point.tie_handles;@@ -147,7 +150,7 @@ public class DrawingTools : ToolCollection { } ep.point.set_tie_handle (tie);- ep.path.update_region_boundries ();+ ep.path.update_region_boundaries (); } } @@ -171,7 +174,7 @@ public class DrawingTools : ToolCollection { ep.point.set_tie_handle (false); } - p.path.update_region_boundries ();+ p.path.update_region_boundaries (); } MainWindow.get_current_glyph ().update_view (); }@@ -405,12 +408,12 @@ public class DrawingTools : ToolCollection { }); view_tools.add_tool (full_glyph); - Tool zoom_boundries = new Tool ("zoom_boundries", t_("Zoom in on region boundries"), 'v');- zoom_boundries.select_action.connect((self) => {+ Tool zoom_boundaries = new Tool ("zoom_boundaries", t_("Zoom in on region boundaries"), 'v');+ zoom_boundaries.select_action.connect((self) => { zoom_tool.store_current_view (); glyph_canvas.get_current_display ().zoom_max (); });- view_tools.add_tool (zoom_boundries);+ view_tools.add_tool (zoom_boundaries); Tool zoom_bg = new Tool ("zoom_background_image", t_("Zoom in on background image")); zoom_bg.select_action.connect((self) => {@@ -344,11 +344,10 @@ public class EditPoint { } right_handle.angle = angle;- left_handle.angle = angle;+ left_handle.angle = angle - PI; set_tie_handle (true); eh.move_to_coordinate (right_handle.x (), right_handle.y ());- } public EditPoint copy () {@@ -57,6 +57,22 @@ public class EditPointHandle { return n; } + public void convert_to_line () {+ switch (type) {+ case PointType.QUADRATIC:+ type = PointType.LINE_QUADRATIC;+ break;+ case PointType.DOUBLE_CURVE:+ type = PointType.LINE_DOUBLE_CURVE;+ break;+ case PointType.CUBIC:+ type = PointType.LINE_CUBIC;+ break;+ default:+ break;+ }+ }+ public void set_point_type (PointType point_type) { type = point_type; }@@ -113,8 +113,12 @@ public class FileTab : FontDisplay { } public override void button_release (int button, double ex, double ey) {- int r = (int) rint ((ey - 17) / row_height) + scroll;- int i = scroll; + int r, i;+ + return_if_fail (!is_null(this));+ + r = (int) rint ((ey - 17) / row_height) + scroll;+ i = scroll; if (scroll == 0) { i += 2; // heading@@ -358,6 +358,7 @@ public class Font : GLib.Object { uint i = 0; Glyph? gl; Glyph g;+ StringBuilder sb; // FIXME: this is too slow@@ -65,7 +65,7 @@ public class Glyph : FontDisplay { List<Line> all_lines = new List<Line> (); // vertical, horzontal and grid lines bool show_help_lines = true; bool xheight_lines_visible = false;- bool margin_boundries_visible = false;+ bool margin_boundaries_visible = false; bool unassigned = false; public unichar unichar_code = 0;@@ -188,7 +188,7 @@ public class Glyph : FontDisplay { return unassigned; } - public void boundries (out double x1, out double y1, out double x2, out double y2) {+ public void boundaries (out double x1, out double y1, out double x2, out double y2) { if (path_list.length () == 0) { x1 = 0; y1 = 0;@@ -203,7 +203,7 @@ public class Glyph : FontDisplay { y2 = path_list.first ().data.ymax; foreach (Path p in path_list) {- p.update_region_boundries ();+ p.update_region_boundaries (); if (p.xmin < x1) x1 = p.xmin; if (p.xmax > x2) x2 = p.xmax;@@ -212,7 +212,7 @@ public class Glyph : FontDisplay { } } - public void selection_boundries (out double x, out double y, out double w, out double h) {+ public void selection_boundaries (out double x, out double y, out double w, out double h) { double px, py, px2, py2; px = 10000;@@ -417,7 +417,7 @@ public class Glyph : FontDisplay { // top to bottom add_line (top_margin_line);- top_margin_line.set_visible (margin_boundries_visible);+ top_margin_line.set_visible (margin_boundaries_visible); add_line (top_line); top_line.set_visible (glyph_has_top || xheight_lines_visible);@@ -431,7 +431,7 @@ public class Glyph : FontDisplay { bottom_line.set_visible (CharDatabase.has_descender (unichar_code) || xheight_lines_visible); add_line (bottom_margin_line);- bottom_margin_line.set_visible (margin_boundries_visible);+ bottom_margin_line.set_visible (margin_boundaries_visible); } bool has_top_line () {@@ -450,12 +450,12 @@ public class Glyph : FontDisplay { } public void set_margin_lines_visible (bool m) {- margin_boundries_visible = m;+ margin_boundaries_visible = m; add_help_lines (); } public bool get_margin_lines_visible () {- return margin_boundries_visible;+ return margin_boundaries_visible; } public void remove_empty_paths () {@@ -1009,6 +1009,16 @@ public class Glyph : FontDisplay { move_selected_edit_point_coordinates (selected_point, xt, yt); } + public void redraw_segment (EditPoint a, EditPoint b) {+ double margin = 10;+ double x = Math.fmin (reverse_path_coordinate_x (a.x), reverse_path_coordinate_x (b.x)) - margin;+ double y = Math.fmin (reverse_path_coordinate_y (a.y), reverse_path_coordinate_y(b.y)) - margin;+ double w = Math.fabs (reverse_path_coordinate_x (a.x) - reverse_path_coordinate_x(b.x)) + 2 * margin;+ double h = Math.fabs (reverse_path_coordinate_y (a.y) - reverse_path_coordinate_y (b.y)) + 2 * margin;+ + redraw_area ((int)x, (int)y, (int)w, (int)h);+ }+ private void redraw_last_stroke (double x, double y) { // redraw line, if we have more than one new point on path double px = 0;@@ -1096,7 +1106,7 @@ public class Glyph : FontDisplay { public void redraw_path_region (Path p) { int x, y, w, h; - p.update_region_boundries ();+ p.update_region_boundaries (); x = reverse_path_coordinate_x (p.xmin); y = reverse_path_coordinate_x (p.xmin);@@ -1152,7 +1162,7 @@ public class Glyph : FontDisplay { reset_zoom (); foreach (var p in path_list) {- p.update_region_boundries ();+ p.update_region_boundaries (); if (p.points.length () > 2) { if (p.xmin < ax) ax = p.xmin;@@ -1419,9 +1429,9 @@ public class Glyph : FontDisplay { ((!)background_image).draw (cr, allocation, view_offset_x, view_offset_y, view_zoom); } - if (unlikely (Preferences.draw_boundries)) {+ if (unlikely (Preferences.draw_boundaries)) { foreach (unowned Path p in path_list) {- p.draw_boundries (cr, allocation, view_zoom);+ p.draw_boundaries (cr, allocation, view_zoom); } } @@ -1565,7 +1575,7 @@ public class Glyph : FontDisplay { foreach (Path p in g.path_list) { add_path (p);- p.update_region_boundries ();+ p.update_region_boundaries (); } remove_lines ();@@ -1595,7 +1605,7 @@ public class Glyph : FontDisplay { Font font = BirdFont.get_current_font (); remove_empty_paths ();- boundries (out x1, out y1, out x2, out y2);+ boundaries (out x1, out y1, out x2, out y2); if (x2 - x1 < 1 || y2 - y1 < 1) { // create an empty thumbnail img = new ImageSurface (Format.ARGB32, 100, 100);@@ -1667,7 +1677,7 @@ public class Glyph : FontDisplay { close_path (); foreach (Path p in path_list) {- p.update_region_boundries ();+ p.update_region_boundaries (); } redraw_area (0, 0, allocation.width, allocation.height);@@ -147,7 +147,7 @@ class MoveTool : Tool { move_path = true; - update_selection_boundries ();+ update_selection_boundaries (); last_x = x; last_y = y;@@ -158,7 +158,7 @@ class MoveTool : Tool { selection_y = y; } - update_boundries_for_selection (); + update_boundaries_for_selection (); } static void select_group () {@@ -179,8 +179,8 @@ class MoveTool : Tool { } } - public static void update_selection_boundries () {- get_selection_box_boundries (out selection_box_center_x,+ public static void update_selection_boundaries () {+ get_selection_box_boundaries (out selection_box_center_x, out selection_box_center_y, out selection_box_width, out selection_box_height); }@@ -202,7 +202,7 @@ class MoveTool : Tool { cr.restore (); } - public static void get_selection_box_boundries (out double x, out double y, out double w, out double h) {+ public static void get_selection_box_boundaries (out double x, out double y, out double w, out double h) { double px, py, px2, py2; Glyph glyph = MainWindow.get_current_glyph (); @@ -300,10 +300,10 @@ class MoveTool : Tool { } } - public static void update_boundries_for_selection () {+ public static void update_boundaries_for_selection () { Glyph glyph = MainWindow.get_current_glyph (); foreach (Path p in glyph.active_paths) {- p.update_region_boundries ();+ p.update_region_boundaries (); } } @@ -333,7 +333,7 @@ class MoveTool : Tool { p.reverse (); } - get_selection_box_boundries (out xc2, out yc2, out w, out h); + get_selection_box_boundaries (out xc2, out yc2, out w, out h); dx = -(xc2 - xc); dy = -(yc2 - yc);@@ -342,7 +342,7 @@ class MoveTool : Tool { p.move (dx, dy); } - update_selection_boundries ();+ update_selection_boundaries (); BirdFont.get_current_font ().touch (); }@@ -1,5 +1,5 @@ /*- Copyright (C) 2012, 2013 Johan Mattsson+ Copyright (C) 2012, 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 @@ -476,7 +476,7 @@ public class GlyfTable : Table { int npoints = 0; int16 ncontours;- int16 ixmin; // set boundries+ int16 ixmin; // set boundaries int16 iymin; int16 ixmax; int16 iymax;@@ -778,12 +778,12 @@ public class GlyfTable : Table { Path ps = ((!) glyph.path_list.first ()).data; - ps.update_region_boundries ();+ ps.update_region_boundaries (); xmin = ps.xmin; xmax = ps.xmax; foreach (Path p in glyph.path_list) {- p.update_region_boundries ();+ p.update_region_boundaries (); if (p.xmin < xmin) { xmin = p.xmin;@@ -114,7 +114,7 @@ public class HeadTable : Table { xmax = dis.read_short (); ymax = dis.read_short (); - printd (@"font boundries:\n");+ printd (@"font boundaries:\n"); printd (@"xmin: $xmin\n"); printd (@"ymin: $ymin\n"); printd (@"xmax: $xmax\n");@@ -183,7 +183,7 @@ public class HeadTable : Table { xmax = glyf_table.xmax; ymax = glyf_table.ymax; - printd (@"font boundries:\n");+ printd (@"font boundaries:\n"); printd (@"xmin: $xmin\n"); printd (@"ymin: $ymin\n"); printd (@"xmax: $xmax\n");@@ -104,7 +104,7 @@ public class HmtxTable : Table { // advance and lsb foreach (Glyph g in glyf_table.glyphs) {- g.boundries (out xmin, out ymin, out xmax, out ymax);+ g.boundaries (out xmin, out ymin, out xmax, out ymax); lsb = (int16) (-1 * (g.left_limit - xmin) * HeadTable.UNITS); advance = (int16) ((g.right_limit - g.left_limit) * HeadTable.UNITS); extent = (int16) (lsb + (xmax - xmin) * HeadTable.UNITS);@@ -101,7 +101,7 @@ public class OpenFontFormatReader : Object { directory_table.parse (font_data, this); } - public void parse_all_tables () {+ public void parse_all_tables () throws Error { directory_table.parse_all_tables (font_data, this); if (!directory_table.validate_tables (font_data, file)) {@@ -109,15 +109,15 @@ public class OpenFontFormatReader : Object { } } - public void parse_kern_table () {+ public void parse_kern_table () throws Error { directory_table.parse_kern_table (font_data); } - public void parse_cmap_table () {+ public void parse_cmap_table () throws Error { directory_table.parse_cmap_table (font_data); } - public void parse_head_table () {+ public void parse_head_table () throws Error { directory_table.parse_head_table (font_data); } @@ -1194,7 +1194,7 @@ public class PostTable : Table { fd.add_ulong (0); // non zero for monospaced font - // mem boundries may be omitted+ // mem boundaries may be omitted fd.add_ulong (0); // min mem for type 42 fd.add_ulong (0); // max mem for type 42 @@ -121,7 +121,7 @@ public class OverViewItem : GLib.Object { } g = ((!) gl).get_current ();- g.boundries (out x1, out y1, out x2, out y2);+ g.boundaries (out x1, out y1, out x2, out y2); gx = (width - (x2 - x1)) / 2*scale; @@ -29,7 +29,7 @@ public class Path { EditPoint? last_point = null; EditPoint? second_last_point = null; - /** Region boundries */+ /** Region boundaries */ public double xmax = double.MIN; public double xmin = double.MAX; public double ymax = double.MIN;@@ -135,6 +135,15 @@ public class Path { } } + public EditPoint get_last_point () {+ if (points.length () == 0) {+ warning ("No point");+ return new EditPoint ();+ }+ + return points.last ().data;+ }+ public bool has_direction () { return direction_is_set; }@@ -147,7 +156,7 @@ public class Path { stroke = width; } - public void draw_boundries (Context cr, WidgetAllocation allocation, double view_zoom) {+ public void draw_boundaries (Context cr, WidgetAllocation allocation, double view_zoom) { double x = Glyph.reverse_path_coordinate_x (xmin); double y = Glyph.reverse_path_coordinate_y (ymin); double x2 = Glyph.reverse_path_coordinate_x (xmax);@@ -601,7 +610,7 @@ public class Path { public void thinner (double ratio) requires (points.length () > 2) { - update_region_boundries ();+ update_region_boundaries (); double w = Math.fabs(xmax - xmin); double h = Math.fabs(ymax - ymin);@@ -838,7 +847,7 @@ public class Path { public bool is_over_boundry_precision (double x, double y, double p) { if (unlikely (ymin == double.MAX || ymin == 10000)) {- update_region_boundries ();+ update_region_boundaries (); } return (ymin - p <= y <= ymax + p) && (xmin - p <= x <= xmax + p);@@ -846,7 +855,7 @@ public class Path { 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.");+ warning ("bounding box is not calculated, run update_region_boundaries first."); } return (ymin <= y <= ymax) && (xmin <= x <= xmax);@@ -856,12 +865,38 @@ public class Path { return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax); } - public void add (double x, double y) {- add_after (x, y, points.last ());+ public EditPoint delete_last_point () {+ unowned List<EditPoint> p;+ EditPoint r;+ uint len;+ + len = points.length ();+ if (unlikely (len == 0)) {+ warning ("No points in path.");+ return new EditPoint ();+ }+ + p = points.last ();+ r = p.data;+ points.remove_link (p);+ + if (len > 1) {+ r.get_prev ().data.next = null;+ + if (r.next != null) {+ r.get_next ().data.prev = null;+ }+ }+ + return r;+ }+ + public EditPoint add (double x, double y) {+ return add_after (x, y, points.last ()).data; } - public void add_point (EditPoint p) {- add_point_after (p, points.last ());+ public EditPoint add_point (EditPoint p) {+ return add_point_after (p, points.last ()).data; } /** Insert a new point after @param previous_point and return a reference @@ -872,6 +907,7 @@ public class Path { return add_point_after (p, previous_point); } + /** @return a list item pointing to the new point */ public unowned List<EditPoint> add_point_after (EditPoint p, List<EditPoint>? previous_point) { unowned List<EditPoint> np; int prev_index;@@ -931,11 +967,8 @@ public class Path { open = true; edit = true; }- - public EditPoint? get_last_point () {- return last_point;- } + // FIXME: delete this: public EditPoint? get_second_last_point () { return second_last_point; }@@ -947,10 +980,10 @@ public class Path { ep.y += delta_y; } - update_region_boundries ();+ update_region_boundaries (); } - private void update_region_boundries_for_point (EditPoint p) {+ private void update_region_boundaries_for_point (EditPoint p) { EditPointHandle left_handle; EditPointHandle right_handle; @@ -973,11 +1006,11 @@ public class Path { ymin = p.y; } - update_region_boundries_for_handle (left_handle);- update_region_boundries_for_handle (right_handle);+ update_region_boundaries_for_handle (left_handle);+ update_region_boundaries_for_handle (right_handle); } - private void update_region_boundries_for_handle (EditPointHandle h) {+ private void update_region_boundaries_for_handle (EditPointHandle h) { if (h.x () > xmax) { xmax = h.x (); }@@ -995,7 +1028,7 @@ public class Path { } } - public void update_region_boundries () {+ public void update_region_boundaries () { xmax = -10000; xmin = 10000; ymax = -10000;@@ -1009,7 +1042,7 @@ public class Path { } foreach (EditPoint p in points) {- update_region_boundries_for_point (p);+ update_region_boundaries_for_point (p); } } @@ -1709,14 +1742,14 @@ public class Path { cr.restore (); } - public void print_boundries () {+ public void print_boundaries () { stderr.printf (@"xmax $xmax \n"); stderr.printf (@"xmin $xmin \n"); stderr.printf (@"ymax $ymax \n"); stderr.printf (@"ymin $ymin \n"); } - public bool has_region_boundries () {+ public bool has_region_boundaries () { return !(xmax == -10000 || xmin == 10000 || ymax == -10000 || ymin == 10000); } @@ -1888,7 +1921,7 @@ public class Path { } foreach (Path path in path_list.paths) {- path.update_region_boundries ();+ path.update_region_boundaries (); } return path_list;@@ -1971,8 +2004,8 @@ public class Path { } // add editpoints on intersections - p0.update_region_boundries ();- p1.update_region_boundries ();+ p0.update_region_boundaries ();+ p1.update_region_boundaries (); foreach (Intersection inter in il.points) { e = new EditPoint (); p0.get_closest_point_on_path (e, inter.x, inter.y);@@ -2014,7 +2047,7 @@ public class Path { } foreach (Path pp in path_list.paths) {- pp.update_region_boundries ();+ pp.update_region_boundaries (); } // remove duplicate paths@@ -2241,7 +2274,7 @@ public class Path { path.points.first ().data.recalculate_linear_handles (); points.last ().data.recalculate_linear_handles (); - path.points.remove_link (path.points.first ());+ // FIXME: path.points.remove_link (path.points.first ()); // copy remaining points foreach (EditPoint p in path.points) {@@ -2253,7 +2286,7 @@ public class Path { path.points.remove_link (path.points.first ()); } - close ();+ //close (); } /** Roatate around coordinate xc, xc. */@@ -2284,7 +2317,7 @@ public class Path { } } - update_region_boundries ();+ update_region_boundaries (); } public void flip_vertical () {@@ -2305,7 +2338,7 @@ public class Path { hl.move_to_coordinate_internal (lx, -1 * ly); } - update_region_boundries ();+ update_region_boundaries (); } public void flip_horizontal () {@@ -2326,7 +2359,7 @@ public class Path { hl.move_to_coordinate_internal (-1 * lx, ly); } - update_region_boundries ();+ update_region_boundaries (); } public void init_point_type () {@@ -374,7 +374,7 @@ public class PenTool : Tool { selected_handle.move_delta ((x - last_point_x) * precision, (y - last_point_y) * precision); } - handle_selection.path.update_region_boundries ();+ handle_selection.path.update_region_boundaries (); // FIXME: redraw line only glyph.redraw_area (0, 0, glyph.allocation.width, glyph.allocation.height);@@ -413,7 +413,7 @@ public class PenTool : Tool { glyph.move_selected_edit_point_delta (p, (x - last_point_x) * precision, (y - last_point_y) * precision); } p.recalculate_linear_handles ();- ps.path.update_region_boundries ();+ ps.path.update_region_boundaries (); } } @@ -747,7 +747,7 @@ public class PenTool : Tool { } foreach (Path merge in glyph.path_list) {- // don't join path with it self here+ // don't join path with itself here if (path == merge) { continue; }@@ -805,7 +805,7 @@ public class PenTool : Tool { } /** Merge paths if ends are close. */- private static bool is_close_to_point (EditPoint ep, double x, double y) {+ public static bool is_close_to_point (EditPoint ep, double x, double y) { double px, py, distance; px = Glyph.reverse_path_coordinate_x (ep.x);@@ -866,20 +866,23 @@ public class PenTool : Tool { cr.restore (); } - void draw_merge_icon (Context cr) {+ public static void draw_join_icon (Context cr, double x, double y) { ImageSurface img;- double x, y;- return_if_fail (tie_icon != null); img = (!) tie_icon; cr.save ();- get_tie_position (out x, out y); cr.set_source_surface (img, x - img.get_width () / 2, y - img.get_height () / 2); cr.paint (); cr.restore (); }++ void draw_merge_icon (Context cr) {+ double x, y;+ get_tie_position (out x, out y);+ draw_join_icon (cr, x, y);+ } /** Obtain the position where to ends meet. */ void get_tie_position (out double x, out double y) {@@ -1071,21 +1074,34 @@ public class PenTool : Tool { remove_all_selected_points (); - new_point = glyph.add_new_edit_point (x, y);+ new_point = add_new_edit_point (x, y); new_point.point.set_selected (true);- new_point.path.update_region_boundries (); selected_point = new_point.point; active_edit_point = new_point.point; add_selected_point (selected_point, glyph.active_paths.last ().data); move_selected = true;+ }+ + public static PointSelection add_new_edit_point (int x, int y) {+ Glyph glyph;+ PointSelection new_point;+ glyph = MainWindow.get_current_glyph (); + new_point = glyph.add_new_edit_point (x, y);+ new_point.path.update_region_boundaries ();++ selected_point = new_point.point;+ active_edit_point = new_point.point; + set_point_type (selected_point); set_default_handle_positions ();+ + return new_point; } - void set_point_type (EditPoint p) {+ static void set_point_type (EditPoint p) { if (p.prev != null && p.get_prev ().data.right_handle.type == PointType.QUADRATIC) { p.left_handle.type = PointType.QUADRATIC; p.right_handle.type = PointType.LINE_QUADRATIC;@@ -1151,10 +1167,10 @@ public class PenTool : Tool { double x = Glyph.path_coordinate_x (event_x); double y = Glyph.path_coordinate_y (event_y); - if (unlikely (!p.has_region_boundries ())) {+ if (unlikely (!p.has_region_boundaries ())) { if (p.points.length () > 0) { warning (@"No bounding box. $(p.points.length ())");- p.update_region_boundries ();+ p.update_region_boundaries (); } } @@ -1457,7 +1473,8 @@ public class PenTool : Tool { ep.set_reflective_handles (false); if (ep.next == null) {- warning ("Next is null.");+ // FIXME: add a new function for this case+ // warning ("Next is null."); } if (ep.prev == null) {@@ -17,6 +17,7 @@ using Cairo; namespace BirdFont { +/** One selected point and its path. */ public class PointSelection : GLib.Object { public EditPointHandle handle;@@ -40,6 +41,12 @@ public class PointSelection : GLib.Object { point = new EditPoint (); handle = new EditPointHandle.empty (); }+ + /** @return true if this point is the first point in the path. */+ public bool is_first () {+ return_val_if_fail (path.points.length () > 0, false);+ return path.points.first ().data == point;+ } } }@@ -19,7 +19,7 @@ using Gee; public class Preferences { static Gee.HashMap<string, string> data;- public static bool draw_boundries = false;+ public static bool draw_boundaries = false; public Preferences () { data = new Gee.HashMap<string, string> ();@@ -99,8 +99,8 @@ class ResizeTool : Tool { } if (!rotate_path) {- MoveTool.update_boundries_for_selection ();- MoveTool.get_selection_box_boundries (out selection_box_center_x,+ MoveTool.update_boundaries_for_selection ();+ MoveTool.get_selection_box_boundaries (out selection_box_center_x, out selection_box_center_y, out selection_box_width, out selection_box_height); }@@ -150,7 +150,7 @@ class ResizeTool : Tool { p.rotate (rotation - last_rotate, selection_box_center_x, selection_box_center_y); } - MoveTool.get_selection_box_boundries (out xc2, out yc2, out w, out h); + MoveTool.get_selection_box_boundaries (out xc2, out yc2, out w, out h); dx = -(xc2 - xc); dy = -(yc2 - yc);@@ -161,7 +161,7 @@ class ResizeTool : Tool { last_rotate = rotation; - MoveTool.update_selection_boundries ();+ MoveTool.update_selection_boundaries (); } static bool is_over_rotate_handle (Path p, double x, double y) {@@ -197,7 +197,7 @@ public class SpinButton : Tool { return (int8) v; } - public void set_value (string new_value, bool check_boundries = true, bool emit_signal = true) {+ public void set_value (string new_value, bool check_boundaries = true, bool emit_signal = true) { string v = new_value.replace (",", "."); while (v.char_count () < 5) {@@ -217,12 +217,12 @@ public class SpinButton : Tool { centi = (int8) int.parse (v.substring (v.index_of_nth_char (3), 1)); milli = (int8) int.parse (v.substring (v.index_of_nth_char (4), 1)); - if (check_boundries && get_value () > max / 1000.0) {+ if (check_boundaries && get_value () > max / 1000.0) { warning (@"Out of bounds ($new_value > $max)."); set_value_round (max, false); } - if (check_boundries && get_value () < min / 1000.0) {+ if (check_boundaries && get_value () < min / 1000.0) { warning (@"Out of bounds ($new_value < $min)."); set_value_round (min, false); }@@ -232,8 +232,8 @@ public class SpinButton : Tool { } } - public void set_value_round (double v, bool check_boundries = true, bool emit_signal = true) {- set_value (@"$v".replace (",", "."), check_boundries, emit_signal);+ public void set_value_round (double v, bool check_boundaries = true, bool emit_signal = true) {+ set_value (@"$v".replace (",", "."), check_boundaries, emit_signal); } public double get_value () {@@ -43,7 +43,7 @@ class TestCases { add (test_freetype, "Freetype"); add (test_preview, "Preview"); add (test_kerning, "Kerning");- add (test_boundries, "Boundries");+ add (test_boundaries, "Boundaries"); } private void add (Callback callback, string name) {@@ -449,7 +449,7 @@ class TestCases { p.add (10, 10); p.add (10, -10); p.add (-10, -10);- p.update_region_boundries ();+ p.update_region_boundaries(); g.add_path (p); g.close_path (); @@ -1367,8 +1367,8 @@ class TestCases { } } - private static void test_boundries () {- Preferences.draw_boundries = true;+ private static void test_boundaries () {+ Preferences.draw_boundaries = true; } } new file mode 100644@@ -0,0 +1,435 @@+/*+ Copyright (C) 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 Cairo;+using Math;++namespace BirdFont {++/** A tool that lets the user draw fonts with the mouse + * instead of adding bezér points one by one.+ */+public class TrackTool : Tool {+ bool draw_free_hand = false;++ int added_points = 0;+ double last_update = 0;++ int last_x = 0;+ int last_y = 0;++ int last_timer_x = 0;+ int last_timer_y = 0;+ int update_cycles = 0;++ int join_x = -1;+ int join_y = -1;+ bool join_paths = false;++ public TrackTool (string tool_tip) {+ press_action.connect ((self, b, x, y) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ Path p;+ PointSelection? ps;+ PointSelection end_point;+ + if (b == 1) {+ draw_free_hand = true;+ + last_x = x;+ last_y = y;+ + glyph.store_undo_state ();+ + if (join_paths) {+ ps = get_path_with_end_point (x, y);+ return_if_fail (ps != null);+ end_point = (!) ps;+ if (end_point.is_first ()) {+ end_point.path.reverse ();+ }+ glyph.set_active_path (end_point.path);+ add_corner (x, y);+ } else {+ p = new Path ();+ glyph.add_path (p);+ glyph.open_path ();+ PenTool.add_new_edit_point (x, y).point;+ record_new_position (x, y);+ }++ glyph.update_view (); + added_points = 0;+ last_update = get_current_time ();+ start_update_timer ();+ }+ });+ + double_click_action.connect ((self, b, x, y) => {+ });++ release_action.connect ((self, b, ix, iy) => {+ if (b == 1) {+ add_endpoint_and_merge (ix, iy);+ }+ });++ move_action.connect ((self, x, y) => {+ PointSelection? open_path = get_path_with_end_point (x, y);+ PointSelection p;+ bool join;+ + join = (open_path != null);+ + if (join != join_paths) {+ MainWindow.get_current_glyph ().update_view ();++ }+ + join_paths = join;+ + if (open_path != null) {+ p = (!) open_path;+ join_x = Glyph.reverse_path_coordinate_x (p.point.x);+ join_y = Glyph.reverse_path_coordinate_y (p.point.y);+ }+ + if (draw_free_hand) {+ record_new_position (x, y);+ convert_on_timeout ();+ last_x = x;+ last_y = y;+ }+ });+ + draw_action.connect ((tool, cairo_context, glyph) => {+ if (join_paths) {+ PenTool.draw_join_icon (cairo_context, join_x, join_y);+ }+ });+ }++ void add_endpoint_and_merge (int x, int y) {+ Glyph glyph;+ Path p;+ PointSelection? open_path = get_path_with_end_point (x, y);+ PointSelection joined_path;+ glyph = MainWindow.get_current_glyph ();+ + if (glyph.path_list.length () == 0) {+ warning ("No path.");+ return;+ }+ + p = glyph.path_list.last ().data;+ draw_free_hand = false;+ + convert_points_to_line ();++ if (join_paths && open_path != null) {+ joined_path = (!) open_path;+ + if (joined_path.path == p) {+ p.close (); + } else {+ merge_paths (p, joined_path);+ }+ } else {+ add_corner (x, y);+ }++ + if (p.points.length () == 0) {+ warning ("No point.");+ return;+ }+ + p.points.first ().data.set_tie_handle (false);+ p.points.last ().data.set_tie_handle (false);+ + p.create_list ();+ + if (glyph.path_list.length () < 2) {+ warning ("Points have been deleted");+ return;+ }++ if (DrawingTools.get_selected_point_type () == PointType.QUADRATIC) {+ foreach (EditPoint e in p.points) {+ e.set_tie_handle (true);+ e.process_tied_handle (); + }+ }+ + glyph.update_view ();+ }++ public static void merge_paths (Path a, PointSelection b) {+ Glyph g;+ Path merged = a.copy ();+ + if (!b.is_first ()) {+ b.path.close ();+ b.path.reverse ();+ b.path.reopen ();+ }+ + merged.append_path (b.path);+ + g = MainWindow.get_current_glyph ();+ + g.add_path (merged);+ + g.delete_path (a);+ g.delete_path (b.path);+ + merged.create_list ();+ merged.update_region_boundaries ();+ merged.recalculate_linear_handles ();+ merged.reopen ();+ + if (PenTool.is_counter_path (merged)) {+ merged.force_direction (Direction.COUNTER_CLOCKWISE);+ } else {+ merged.force_direction (Direction.CLOCKWISE);+ }+ }++ PointSelection? get_path_with_end_point (int x, int y) {+ Glyph glyph = MainWindow.get_current_glyph ();+ EditPoint e;+ EditPoint current_end = new EditPoint ();++ // exclude the end point on the path we are adding points to+ if (draw_free_hand) {+ current_end = get_active_path ().get_last_point ();+ }++ foreach (Path p in glyph.path_list) {+ if (p.is_open () && p.points.length () > 2) {+ e = p.points.first ().data;+ if (PenTool.is_close_to_point (e, x, y)) {+ return new PointSelection (e, p);+ }++ e = p.points.last ().data;+ if (current_end != e && PenTool.is_close_to_point (e, x, y)) {+ return new PointSelection (e, p); + }+ }+ }+ + return null;+ }+ + void record_new_position (int x, int y) {+ Glyph glyph;+ Path p;+ EditPoint new_point;+ double px, py;+ + glyph = MainWindow.get_current_glyph ();+ + if (glyph.path_list.length () == 0) {+ warning ("No path.");+ return;+ }+ + p = glyph.path_list.last ().data;+ p.reopen ();+ px = Glyph.path_coordinate_x (x);+ py = Glyph.path_coordinate_y (y);+ new_point = p.add (px, py);+ added_points++;++ PenTool.convert_point_to_line (new_point, false);+ + if (p.points.length () > 1) {+ glyph.redraw_segment (new_point, new_point.get_prev ().data);+ }+ + glyph.update_view ();+ + last_x = x;+ last_y = y;+ }+ + void start_update_timer () {+ TimeoutSource timer = new TimeoutSource (100);++ timer.set_callback (() => {+ + if (draw_free_hand) {+ record_new_position (last_x, last_y);+ convert_on_timeout ();+ }+ + return draw_free_hand;+ });++ timer.attach (null);+ }++ /** @returns true while the mounse pointer is moving. */+ bool is_moving (int x, int y) {+ return Path.distance (x, last_x, y, last_y) >= 1;+ }+ + /** Add a new point if the update period has ended. */+ void convert_on_timeout () {+ if (!is_moving (last_timer_x, last_timer_y)) {+ update_cycles++;+ } else {+ last_timer_x = last_x;+ last_timer_y = last_y;+ update_cycles = 0;+ }+ + if (update_cycles > 10) {+ convert_points_to_line ();+ last_update = get_current_time ();+ add_corner (last_x, last_y);+ added_points = 0;+ update_cycles = 0;+ }+ + if (added_points > 40) {+ last_update = get_current_time ();+ convert_points_to_line ();+ }+ }+ + void add_corner (int px, int py) {+ PointSelection p;+ delete_last_points_at (px, py);+ p = PenTool.add_new_edit_point (px, py);+ p.point.set_tie_handle (false);+ p.point.get_left_handle ().convert_to_line ();+ p.point.get_right_handle ().convert_to_line ();+ p.point.recalculate_linear_handles ();+ last_update = get_current_time ();+ MainWindow.get_current_glyph ().update_view ();+ }++ Path get_active_path () {+ Glyph glyph = MainWindow.get_current_glyph ();+ + if (glyph.path_list.length () == 0) {+ warning ("No path.");+ return new Path ();+ }+ + return glyph.path_list.last ().data; + }+ + void delete_last_points_at (int x, int y) {+ double px, py;+ Path p;+ + p = get_active_path ();++ if (unlikely (p.points.length () == 0)) {+ warning ("Missing point.");+ return;+ }++ px = Glyph.path_coordinate_x (x);+ py = Glyph.path_coordinate_y (y);++ while (p.points.length () > 0 && is_close (p.points.last ().data, px, py)) {+ p.delete_last_point ();+ }+ }+ + /** @return true if the new point point is closer than a few pixels EditPoint. */+ bool is_close (EditPoint p, double x, double y) {+ Glyph glyph = MainWindow.get_current_glyph ();+ return glyph.view_zoom * Path.distance (p.x, x, p.y, y) < 5;+ }+ + /** @return the last removed point. */+ EditPoint convert_points_to_line () {+ EditPoint ep, last_point;+ double sum_x, sum_y, sum_angle, nx, ny;+ int px, py;+ EditPoint average;+ Path p;+ Glyph glyph;+ + if (added_points == 0) {+ warning ("No points to add.");+ return new EditPoint ();+ }+ + glyph = MainWindow.get_current_glyph ();+ + if (glyph.path_list.length () == 0) {+ warning ("No path.");+ return new EditPoint ();+ }+ + p = glyph.path_list.last ().data;++ if (unlikely (p.points.length () < added_points)) {+ warning ("Missing point.");+ return new EditPoint ();+ }++ sum_x = 0;+ sum_y = 0;+ sum_angle = 0;+ + List<EditPoint> points = new List<EditPoint> ();+ + last_point = p.points.last ().data;+ + for (int i = 0; i < added_points; i++) {+ ep = p.delete_last_point ();+ sum_x += ep.x;+ sum_y += ep.y; + points.append (ep);+ }+ + nx = sum_x / added_points;+ ny = sum_y / added_points;++ px = Glyph.reverse_path_coordinate_x (nx);+ py = Glyph.reverse_path_coordinate_y (ny);+ average = PenTool.add_new_edit_point (px, py).point;+ average.set_tie_handle (true);+ + if (unlikely (p.points.length () == 0)) {+ warning ("No points.");+ return new EditPoint ();+ }+ + if (average.prev != null && average.get_prev ().data.tie_handles) {+ PenTool.convert_point_to_line (average.get_prev ().data, true);+ average.get_prev ().data.process_tied_handle ();+ }++ added_points = 0;+ last_update = get_current_time ();+ glyph.update_view ();+ + return last_point;+ }+ + /** @return current time in milli seconds. */+ static double get_current_time () {+ return GLib.get_real_time () / 1000.0;+ }+}++}