Commit da8a53e
Save stroke width in bf files
Changed files (12)
modifiedlibbirdfont/BirdFontFile.valamodifiedlibbirdfont/DrawingTools.valamodifiedlibbirdfont/EditPoint.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/MoveTool.valamodifiedlibbirdfont/OverView.valamodifiedlibbirdfont/Path.valamodifiedlibbirdfont/PathList.valamodifiedlibbirdfont/PenTool.valamodifiedlibbirdfont/StrokeTool.valamodifiedlibbirdfont/Svg.valamodifiedlibbirdfont/TrackTool.vala
Diff
@@ -56,9 +56,6 @@ class BirdFontFile { font.font_file = "typeface.bf"; - printd ("Load data:\n");- printd (xml_data);- tr = new TextReader.for_doc (xml_data, ""); ok = load_xml (tr); @@ -304,6 +301,7 @@ class BirdFontFile { if (pl.is_open ()) { data.append (" O"); }+ return data.str; } @@ -317,6 +315,7 @@ class BirdFontFile { if (pl.is_open ()) { data.append (" O"); }+ return data.str; } @@ -435,7 +434,7 @@ class BirdFontFile { foreach (Path p in g.path_list) { data = get_point_data (p); if (data != "") {- os.put_string (@"\t\t<path data=\"$(data)\" />\n");+ os.put_string (@"\t\t<path stroke=\"$(p.stroke)\" data=\"$(data)\" />\n"); } } @@ -955,18 +954,30 @@ class BirdFontFile { private Path parse_path (Xml.Node* node) { string attr_name; string attr_content;+ Path path = new Path (); for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) { attr_name = prop->name; attr_content = prop->children->content; if (attr_name == "data") {- return parse_path_data (attr_content);+ path = parse_path_data (attr_content);+ }+ }++ for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ attr_name = prop->name;+ attr_content = prop->children->content;++ if (attr_name == "stroke") {+ path.set_stroke (double.parse (attr_content)); }+ } + if (path.points.length () == 0) {+ warning ("Empty path"); } - warning ("No path");- return new Path (); + return path; } private static void line (Path path, string px, string py) {@@ -1230,6 +1241,10 @@ class BirdFontFile { close (path); } else { path.points.remove_link (path.points.last ());+ + if (!path.is_open ()) {+ warning ("Closed path.");+ } } path.update_region_boundaries ();@@ -1308,7 +1323,9 @@ class BirdFontFile { } } - /** Parse visual objects and paths */+ /** Parse visual objects and paths + * @deprecated ffi format use bf+ */ private void parse_content (Glyph g, Xml.Node* node) { Xml.Node* i; return_if_fail (node != null);@@ -37,6 +37,7 @@ public class DrawingTools : ToolCollection { public static SpinButton object_stroke; public static Tool move_tool;+ public static PenTool pen_tool; public DrawingTools (GlyphCanvas main_glyph_canvas) { glyph_canvas = main_glyph_canvas;@@ -59,7 +60,7 @@ public class DrawingTools : ToolCollection { grid_expander = grid; // Draw tools- PenTool pen_tool = new PenTool ("pen_tool");+ pen_tool = new PenTool ("pen_tool"); draw_tools.add_tool (pen_tool); PointTool point_tool = new PointTool ("point_tool");@@ -89,27 +89,6 @@ public class EditPoint { return a; }-- public double get_angle () {- double a = fabs ((right_handle.angle - PI) - left_handle.angle);- while (a >= 2 * PI - 0.01) {- a -= 2 * PI;- }- - return fabs (a);- }-- public bool is_corner () {- return !(get_angle () <= 0.01);- }- - public bool is_narrow () {- return !(get_angle () < PI / 2 - 0.01);- }-- public bool is_wide () {- return !(get_angle () > PI / 2 - 0.01);- } public bool equals (EditPoint e) { return e.x == x @@ -962,8 +962,12 @@ public class Glyph : FontDisplay { } else { np = new Path (); path_list.append (np);- np.add (xt, yt); - } + np.add (xt, yt);+ + if (DrawingTools.pen_tool.is_selected ()) {+ np.set_stroke (PenTool.path_stroke_width);+ }+ } clear_active_paths (); add_active_path (np);@@ -1034,9 +1038,11 @@ public class Glyph : FontDisplay { } foreach (Path path in active_paths) {- EditPoint? pt = path.get_second_last_point ();- if (pt != null) {- EditPoint p = (!) pt;+ EditPoint p;+ EditPoint pl = path.get_last_point ();+ + if (pl.prev != null) {+ p = pl.get_prev ().data; px = p.x + xc; py = p.y - xc;@@ -1076,15 +1082,13 @@ public class Glyph : FontDisplay { /** Close all editable paths and return false if no path have been closed. */ public bool close_path () { bool r = false;- + foreach (var p in path_list) { if (p.is_editable ()) { r = true;- p.close ();+ p.set_editable (false); } }-- print ("CLOSE\n"); // FIXME: DELETE redraw_area (0, 0, allocation.width, allocation.height); open = false;@@ -1327,7 +1331,6 @@ public class Glyph : FontDisplay { public void draw_path (Context cr) { double left, baseline;- Path stroke; baseline = get_line ("baseline").pos; left = get_line ("left").pos;@@ -1335,7 +1338,9 @@ public class Glyph : FontDisplay { if (!is_open ()) { cr.save (); cr.set_source_rgba (0, 0, 0, 1);+ Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () + baseline);+ cr.restore (); } @@ -1351,27 +1356,37 @@ public class Glyph : FontDisplay { if (is_open ()) { foreach (unowned Path p in path_list) { p.draw_outline (cr, allocation, view_zoom);- p.draw_edit_points (cr, allocation, view_zoom);+ p.draw_edit_points (cr, allocation, view_zoom); ++ if (p.stroke > 0) { + draw_outline_for_paths (StrokeTool.get_stroke (p, p.stroke), cr);+ } } } if (!is_open ()) { foreach (unowned Path p in active_paths) {- if (!p.is_editable ()) {+ if (p.stroke == 0) { p.fill_path (cr, allocation, view_zoom);+ } else {+ fill_paths (StrokeTool.get_stroke (p, p.stroke), cr); } } }- - // draw stroke- foreach (unowned Path p in path_list) {- if (p.stroke > 0) { - stroke = StrokeTool.get_stroke (p, p.stroke);- stroke.draw_outline (cr, allocation, view_zoom);- }- } } + private void draw_outline_for_paths (PathList pl, Context cr) {+ foreach (Path p in pl.paths) {+ p.draw_outline (cr, allocation, view_zoom);+ }+ } + + private void fill_paths (PathList pl, Context cr) {+ foreach (Path p in pl.paths) {+ p.fill_path (cr, allocation, view_zoom);+ }+ } + private void draw_zoom_area(Context cr) { cr.save (); cr.set_line_width (2.0);@@ -1663,7 +1678,6 @@ public class Glyph : FontDisplay { merge_path (p0); } - //open_path (); close_path (); foreach (Path p in path_list) {@@ -37,6 +37,8 @@ class MoveTool : Tool { base (n, t_("Move paths"), 'm', CTRL); select_action.connect((self) => {+ Glyph glyph = MainWindow.get_current_glyph ();+ glyph.close_path (); }); deselect_action.connect((self) => {@@ -66,7 +66,6 @@ public class OverView : FontDisplay { tabs.add_tab (g); canvas.set_current_glyph (g); set_initial_zoom ();- g.close_path (); } }); @@ -96,7 +95,6 @@ public class OverView : FontDisplay { canvas = MainWindow.get_glyph_canvas (); canvas.set_current_glyph (glyph); - glyph.close_path (); set_initial_zoom (); } });@@ -35,8 +35,11 @@ public class Path { public double ymax = double.MIN; public double ymin = double.MAX; - /** The width of a stroked outline. */+ /** Stroke width */ public double stroke = 0;+ + /** Fill property for closed paths with stroke. */+ public bool fill = false; bool edit = true; bool open = true;@@ -94,7 +97,7 @@ public class Path { public static double stroke_width = 1; public static bool show_all_line_handles = true; public static bool fill_open_path = false;- + public Path () { string width; points = new List<EditPoint> ();@@ -135,6 +138,14 @@ public class Path { } } + public bool is_filled () {+ return fill;+ }++ public void set_fill (bool f) {+ fill = f;+ }+ public EditPoint get_first_point () { if (unlikely (points.length () == 0)) { warning ("No point");@@ -162,7 +173,7 @@ public class Path { } public void set_stroke (double width) {- stroke = width;+ stroke = width; } public void draw_boundaries (Context cr, WidgetAllocation allocation, double view_zoom) {@@ -714,6 +725,8 @@ public class Path { new_path.edit = edit; new_path.open = open;+ new_path.stroke = stroke;+ new_path.fill = fill; new_path.direction_is_set = direction_is_set; new_path.create_list (); @@ -979,11 +992,6 @@ public class Path { open = true; edit = true; }-- // FIXME: delete this:- public EditPoint? get_second_last_point () {- return second_last_point;- } /** Move path. */ public void move (double delta_x, double delta_y) {@@ -1513,6 +1521,7 @@ public class Path { previous = ((!) previous_point).data; next = ((!) next_point).data; + // FIXME: delete bezier_vector (step, previous.x, previous.get_right_handle ().x (), next.get_left_handle ().x (), next.x, out handle_x0, out handle_x1); bezier_vector (step, previous.y, previous.get_right_handle ().y (), next.get_left_handle ().y (), next.y, out handle_y0, out handle_y1); @@ -1650,46 +1659,6 @@ public class Path { return true; }); }-- // FIXME: DELETE- public void all_vectors (VectorIterator iter, int steps = -1) {- all_segments ((start, stop) => {- all_of (start, stop, (x, y, s) => {- double handle_x0, handle_x1, handle_y0, handle_y1;- PointType pt;- - handle_x0 = 0;- handle_x1 = 0;- handle_y0 = 0;- handle_y1 = 0;- - pt = PenTool.to_curve (start.type);- - if (unlikely (PenTool.is_line (pt))) {- warning (@"Wrong type: $(start.type) ($pt)");- }- - switch (pt) {- case PointType.QUADRATIC: - handle_x0 = quadratic_bezier_vector (s, start.x, start.get_right_handle ().x (), stop.x);- handle_y0 = quadratic_bezier_vector (s, start.y, start.get_right_handle ().y (), stop.y);- break;- case PointType.DOUBLE_CURVE: - double_bezier_vector (s, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out handle_x0, out handle_x1);- double_bezier_vector (s, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out handle_y0, out handle_y1);- break;- default:- bezier_vector (s, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out handle_x0, out handle_x1);- bezier_vector (s, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out handle_y0, out handle_y1);- break;- }- - iter (start, stop, x, y, handle_x0, handle_x1, handle_y0, handle_y1, s);- return true;- }, steps);- return true;- }); - } public static double bezier_path (double step, double p0, double p1, double p2, double p3) { double q0, q1, q2;@@ -21,6 +21,10 @@ public class PathList : GLib.Object { paths = new List<Path> (); } + public void add (Path p) {+ paths.append (p);+ }+ public void append (PathList pl) { foreach (Path p in pl.paths) { paths.append (p);@@ -71,7 +71,7 @@ public class PenTool : Tool { static List<Path> clockwise; static List<Path> counter_clockwise; - static double path_stroke_width = 0;+ public static double path_stroke_width = 0; public PenTool (string name) { string click_to_add_points;@@ -104,11 +104,7 @@ public class PenTool : Tool { }); deselect_action.connect ((self) => {- Glyph glyph = MainWindow.get_current_glyph ();- - force_direction ();- glyph.close_path ();- + force_direction (); move_point_on_path = false; }); @@ -1107,8 +1103,6 @@ public class PenTool : Tool { set_point_type (selected_point); set_default_handle_positions (); - new_point.path.set_stroke (path_stroke_width);- return new_point; } @@ -27,7 +27,8 @@ public class StrokeTool : Tool { public static void set_stroke_for_selected_paths (double width) { Glyph g = MainWindow.get_current_glyph ();- + + warning ("set_stroke_for_selected_paths"); foreach (Path p in g.active_paths) { p.set_stroke (width); }@@ -38,57 +39,92 @@ public class StrokeTool : Tool { /** Create strokes for the selected outlines. */ void stroke_selected_paths () { Glyph g = MainWindow.get_current_glyph ();- List<Path> paths = new List<Path> ();+ PathList paths = new PathList (); foreach (Path p in g.active_paths) { paths.append (get_stroke (p, p.stroke));- paths.append (get_stroke (p, -1 * p.stroke)); } - foreach (Path np in paths) {+ foreach (Path np in paths.paths) { g.add_path (np); } } - public static Path get_stroke (Path p, double thickness) {+ public static PathList get_stroke (Path p, double thickness) { Path counter, outline, merged;+ PathList paths = new PathList (); - if (!p.is_open ()) {- merged = create_stroke (p, thickness);- } else {+ if (!p.is_open () && p.is_filled ()) {+ outline = create_stroke (p, thickness);+ outline.close ();+ paths.add (outline);+ } else if (!p.is_open () && !p.is_filled ()) {+ outline = create_stroke (p, thickness);+ counter = create_stroke (p, -1 * thickness);+ paths.add (outline);+ paths.add (counter);+ + if (p.is_clockwise ()) {+ outline.force_direction (Direction.CLOCKWISE);+ } else {+ outline.force_direction (Direction.COUNTER_CLOCKWISE);+ }+ + if (outline.is_clockwise ()) {+ counter.force_direction (Direction.COUNTER_CLOCKWISE);+ } else {+ counter.force_direction (Direction.CLOCKWISE);+ }+ + } else if (p.is_open ()) { outline = create_stroke (p, thickness); counter = create_stroke (p, -1 * thickness); merged = merge_strokes (outline, counter);+ + if (p.is_clockwise ()) {+ merged.force_direction (Direction.CLOCKWISE);+ } else {+ merged.force_direction (Direction.COUNTER_CLOCKWISE);+ }+ + paths.add (merged);+ } else {+ warning ("Can not create stroke.");+ paths.add (p); } - return merged;+ return paths; } /** Create one stroke from the outline and counter stroke and close the * open endings. */ static Path merge_strokes (Path stroke, Path counter) {- Glyph g;- Path merged = stroke.copy ();+ Path merged; if (stroke.points.length () < 3) { warning ("Missing points.");- return merged;+ return stroke; } if (counter.points.length () < 3) { warning ("Missing points.");- return merged;+ return stroke; } counter.reverse (); + counter.points.remove_link (counter.points.first ());+ counter.points.remove_link (counter.points.last ());++ merged = stroke.copy ();+ counter.get_last_point ().get_right_handle ().convert_to_line (); counter.get_first_point ().get_left_handle ().convert_to_line (); merged.get_last_point ().get_right_handle ().convert_to_line (); merged.get_first_point ().get_left_handle ().convert_to_line ();- + merged.append_path (counter); merged.close (); merged.recalculate_linear_handles ();@@ -99,8 +135,8 @@ public class StrokeTool : Tool { static Path create_stroke (Path p, double thickness) { Path new_path; Path stroked;-- new_path = add_tangent_points (p);+ + new_path = tangents (p); if (new_path.points.length () >= 2) { stroked = change_stroke_width (new_path, thickness);@@ -113,6 +149,7 @@ public class StrokeTool : Tool { } } else { // TODO: create stroke for path with one point+ warning ("One point."); stroked = new Path (); } @@ -128,13 +165,21 @@ public class StrokeTool : Tool { stroked.points.remove_link (stroked.points.last ()); stroked.points.remove_link (stroked.points.last ()); - //stroked.points.remove_link (stroked.points.first ()); stroked.points.remove_link (stroked.points.first ());+ stroked.points.remove_link (stroked.points.first ());+ }++ static Path tangents (Path p) {+ Path pn = p;+ for (int i = 0; i < 3; i++) {+ pn = add_tangent_points (pn);+ }+ return pn; } static Path add_tangent_points (Path p) { Path path = p.copy ();- int steps = 4;+ int steps = 2; List<EditPoint> new_points = new List<EditPoint> (); EditPoint nep;@@ -142,16 +187,23 @@ public class StrokeTool : Tool { path.all_segments ((start, stop) => { EditPoint? prev = null;+ + if (start.type == PointType.DOUBLE_CURVE) {+ steps = 1;+ } else {+ steps = 2;+ }+ Path.all_of (start, stop, (x, y, step) => { EditPoint ep = new EditPoint (); if (0 < step < 1) {- //new_path.get_closest_point_on_path (ep, x, y); ep.set_position (x, y); // Stop processing when the first hidden point is found. if (start.type == PointType.HIDDEN || stop.type == PointType.HIDDEN) {- return false;+ // FIXME: Rename tracker points+ return true; } return_val_if_fail (prev != null, false);@@ -159,6 +211,8 @@ public class StrokeTool : Tool { ep.prev = ((!) prev).get_link_item (); ep.next = start.get_next (); + ep.type = PenTool.to_curve (start.type);+ if (unlikely (ep.next == null || ep.prev == null)) { warning ("No points on segment."); return false;@@ -190,7 +244,7 @@ public class StrokeTool : Tool { nep.prev = ((!) previous).get_link_item (); } - if (!path.has_point (nep)) {+ if (!path.has_point (nep)) { path.insert_new_point_on_path (nep); return_if_fail (nep.prev != null); }@@ -212,19 +266,15 @@ public class StrokeTool : Tool { /** Shrink or expand the outline. */ static Path change_stroke_width (Path new_path, double thickness) {- double nx, ny; - double stroke_thickness;+ double px, py, nx, ny, dnx, dny, counter_dnx, counter_dny; double space_left, space_right; Path stroked = new Path (); - EditPoint ep, corner1, corner2, corner3, swap_corner;+ EditPoint ep; bool clockwise; bool end_point; // first and last point uint k; uint npoints;-- double middle_x, middle_y;- double corner_thickness_left, corner_thickness_right;- double middle_point_position;+ double distance_to_counter, distance_to_path; clockwise = new_path.is_clockwise (); @@ -240,77 +290,48 @@ public class StrokeTool : Tool { foreach (EditPoint e in new_path.points) { ep = e.copy (); - get_new_position (e, clockwise, thickness, out nx, out ny);-+ //get_new_position (e, clockwise, thickness, out nx, out ny);+ get_new_position_delta (e, clockwise, thickness, out dnx, out dny);+ get_new_position_delta (e, clockwise, thickness, out counter_dnx, out counter_dny);+ end_point = (k == 0 || k == npoints - 1); k++; + px = ep.x;+ py = ep.y;+ if (ep.type != PointType.HIDDEN) {- ep.x = nx;- ep.y = ny;+ ep.x += dnx;+ ep.y += dny; } else { break; } - if (e.is_corner ()) {- // add new points in order to preserve the stroke- corner1 = e.copy ();- stroked.add_point (corner1);- - corner2 = e.copy ();- stroked.get_closest_point_on_path (corner2, nx, ny); // FIXME: optimize-- if (end_point) {- swap_corner = corner1;- corner1 = corner2;- corner2 = swap_corner;- }-- if (end_point) {- stroked.insert_new_point_on_path (corner1);- } else {- stroked.insert_new_point_on_path (corner2);- }- - corner1.x += cos (e.get_right_handle ().angle + PI / 2) * 2 * thickness;- corner1.y += sin (e.get_right_handle ().angle + PI / 2) * 2 * thickness;-- corner2.x += cos (e.get_left_handle ().angle - PI / 2) * 2 * thickness;- corner2.y += sin (e.get_left_handle ().angle - PI / 2) * 2 * thickness;-- // fill the gap-- corner3 = e.copy ();- middle_x = corner1.x + (corner2.x - corner1.x) / 2;- middle_y = corner1.y + (corner2.y - corner1.y) / 2;- stroked.get_closest_point_on_path (corner3, middle_x, middle_y);- stroked.insert_new_point_on_path (corner3);- - corner_thickness_right = thickness * ((e.get_right_handle ().angle - PI) / (2 * PI));- corner_thickness_left = thickness * ((e.get_left_handle ().angle) / (2 * PI));-- middle_point_position = 2 * thickness;- middle_point_position *= 1 / sin (e.get_corner_angle ());- - corner3.x = e.x;- corner3.y = e.y;- - corner3.x += middle_point_position * cos (e.get_right_handle ().angle - PI);- corner3.y += middle_point_position * sin (e.get_right_handle ().angle - PI);-- corner3.x += middle_point_position * cos (e.get_left_handle ().angle - PI);- corner3.y += middle_point_position * sin (e.get_left_handle ().angle - PI);+ nx = ep.x;+ ny = ep.y;+ + space_left = get_space_difference (e, e.get_prev ().data, thickness, clockwise);+ space_right = get_space_difference (e, e.get_next ().data, thickness, clockwise);+ + distance_to_counter = Path.distance (nx, px + counter_dnx, ny, py + counter_dny);+ distance_to_path = Path.distance (px, nx, py, ny); + //print (@"$k: $distance_to_path < $thickness\n");+ //print (@"$k space_left + space_right $(space_left + space_right) $space_left + $space_right\n");+ + if (distance_to_path >= fabs (2 * thickness) - 0.1) {+ ep.get_left_handle ().length += space_left / 3; + ep.get_right_handle ().length += space_right / 3;+ stroked.add_point (ep);+ } else if (distance_to_path < fabs (2 * thickness) && (space_left + space_right) > 0) {+ add_corner_nodes (stroked, e, clockwise, thickness, end_point, nx, ny);+ } else if (distance_to_path < fabs (2 * thickness) && (space_left + space_right) < 0) {+ corner_position (e, clockwise, thickness, stroked); } else {- stroke_thickness = fabs (thickness) * 0.04; // FIXME: 0.04-- space_left = get_space_difference (e, e.get_prev ().data, thickness, clockwise);- space_right = get_space_difference (e, e.get_next ().data, thickness, clockwise);-- ep.get_left_handle ().length += space_left / 3;+ ep.get_left_handle ().length += space_left / 3; ep.get_right_handle ().length += space_right / 3;- stroked.add_point (ep);+ warning ("Stroke might become distorted"); } } @@ -321,6 +342,105 @@ public class StrokeTool : Tool { return stroked; } + /** Find the point where two lines intersect. */+ public static void find_intersection (double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4,+ out double point_x, out double point_y) {+ point_x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));+ point_y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));+ }+ + /** Add a corner node for sharp corners. */+ static void corner_position (EditPoint ep, bool clockwise, double thickness, Path p) {+ EditPoint corner = ep.copy ();+ double x1, x3;+ double y1, y3;+ double lx1, lx2, lx3, lx4;+ double ly1, ly2, ly3, ly4;+ double k1, k2;+ double n1, n2;+ double posx, posy;+ EditPointHandle h1, h2;++ h1 = ep.get_right_handle ();+ h2 = ep.get_left_handle ();+ + x1 = ep.x + cos (h1.angle + PI / 2) * 2 * thickness;+ y1 = ep.y + sin (h1.angle + PI / 2) * 2 * thickness;++ x3 = ep.x + cos (h2.angle - PI / 2) * 2 * thickness;+ y3 = ep.y + sin (h2.angle - PI / 2) * 2 * thickness;++ k1 = sin (h1.angle) / cos (h1.angle);+ n1 = y1;++ k2 = sin (h2.angle) / cos (h2.angle);+ n2 = y3;+ + lx1 = -50 + x1;+ ly1 = k1 * -50 + n1;+ lx2 = 100 + x1;+ ly2 = k1 * 100 + n1;+ lx3 = -50 + x3;+ ly3 = k2 * -50 + n2;+ lx4 = 100 + x3;+ ly4 = k2 * 100 + n2;++ find_intersection (lx1, ly1, lx2, ly2, lx3, ly3, lx4, ly4, out posx, out posy);+ + corner.x = posx;+ corner.y = posy;++ if (Path.distance (ep.x, posx, ep.y, posy) > fabs (10 * thickness)) {+ add_corner_end (ep, p, x3, y3);+ } else {+ p.add_point (corner);+ }+ }++ // TODO: elaborated stroke endings+ static void add_corner_end (EditPoint e, Path path, double next_x, double next_y) {+ double px = e.x + (e.x - next_x) / 2;+ double py = e.y + (e.y - next_y) / 2;+ path.add (px, py);+ }++ /** Sharp corner that need two extra points to be built. */+ public static void add_corner_nodes (Path stroked, EditPoint e, bool clockwise, double thickness, bool end_point, double nx, double ny) {+ double middle_x, middle_y;+ double corner_thickness_left, corner_thickness_right;+ double middle_point_position;+ EditPoint corner1, corner2, corner3, swap_corner;+ + // add new points in order to preserve the stroke+ corner1 = e.copy ();+ stroked.add_point (corner1);+ + corner2 = e.copy ();+ stroked.get_closest_point_on_path (corner2, nx, ny); // FIXME: optimize++ if (end_point) {+ swap_corner = corner1;+ corner1 = corner2;+ corner2 = swap_corner;+ }++ if (end_point) {+ stroked.insert_new_point_on_path (corner1);+ } else {+ stroked.insert_new_point_on_path (corner2);+ }+ + corner1.x += cos (e.get_right_handle ().angle + PI / 2) * 2 * thickness;+ corner1.y += sin (e.get_right_handle ().angle + PI / 2) * 2 * thickness;++ corner2.x += cos (e.get_left_handle ().angle - PI / 2) * 2 * thickness;+ corner2.y += sin (e.get_left_handle ().angle - PI / 2) * 2 * thickness;++ // fill the gap+ // FIXME: this should move the point not create a new point+ corner_position (e, clockwise, thickness, stroked);+ }+ public static void get_new_position (EditPoint e, bool clockwise, double thickness, out double strokex, out double strokey) { double sx, sy; get_new_position_delta (e, clockwise, thickness, out sx, out sy);@@ -328,7 +448,7 @@ public class StrokeTool : Tool { strokey = sy + e.y; } - public static void get_new_position_delta (EditPoint e, bool clockwise, double thickness, out double strokex, out double strokey) {+ static void get_new_position_delta (EditPoint e, bool clockwise, double thickness, out double strokex, out double strokey) { double ra, la; double avg_angle; double angle;@@ -21,9 +21,15 @@ class Svg { /** Export to svg glyph data. */ public static string to_svg_glyph (Glyph g) { StringBuilder svg = new StringBuilder ();-+ PathList stroke_list;+ foreach (Path pl in g.path_list) {- write_path_as_glyph (pl, svg, g);+ if (pl.stroke == 0) {+ write_path_as_glyph (pl, svg, g);+ } else {+ stroke_list = StrokeTool.get_stroke (pl, pl.stroke);+ write_paths_as_glyph (stroke_list, svg, g);+ } } return svg.str;@@ -37,22 +43,28 @@ class Svg { 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 pl, StringBuilder svg, Glyph g, bool do_glyph) {+ private static void write_path (Path p, StringBuilder svg, Glyph g, bool do_glyph) { int i = 0; EditPoint? n = null; EditPoint m; - if (pl.points.length () < 2) {+ if (p.points.length () < 2) { return; }- - pl.create_list ();++ p.create_list (); - foreach (var e in pl.points) {+ foreach (var e in p.points) { if (i == 0) { add_abs_start (e, svg, g, do_glyph); i++;@@ -68,7 +80,7 @@ class Svg { i++; } - m = pl.points.first ().data; + m = p.points.first ().data; add_abs_next ((!) n, m, svg, g, do_glyph); close_path (svg);@@ -53,13 +53,21 @@ public class TrackTool : Tool { Toolbox.set_object_stroke (stroke_width); }); - press_action.connect ((self, b, x, y) => {+ press_action.connect ((self, button, x, y) => { Glyph glyph = MainWindow.get_current_glyph (); Path p; PointSelection? ps; PointSelection end_point;++ if (button == 3) {+ glyph.clear_active_paths ();+ }+ + if (button == 2) {+ glyph.close_path ();+ } - if (b == 1) {+ if (button == 1) { draw_free_hand = true; last_x = x;@@ -71,6 +79,7 @@ public class TrackTool : Tool { ps = get_path_with_end_point (x, y); if (unlikely (ps == null)) { warning ("No end point.");+ return; } end_point = (!) ps; if (end_point.is_first ()) {@@ -78,6 +87,8 @@ public class TrackTool : Tool { } glyph.set_active_path (end_point.path); add_corner (x, y);+ + set_stroke_width (end_point.path.stroke); } else { p = new Path (); glyph.add_path (p);@@ -98,7 +109,7 @@ public class TrackTool : Tool { }); release_action.connect ((self, button, x, y) => {- if (button == 1) {+ if (button == 1 && draw_free_hand) { add_endpoint_and_merge (x, y); } });@@ -142,6 +153,7 @@ public class TrackTool : Tool { string w = SpinButton.convert_to_string (width); Preferences.set ("free_hand_stroke_width", w); stroke_width = width;+ warning (@"W: $width\n"); } void add_endpoint_and_merge (int x, int y) {@@ -167,8 +179,8 @@ public class TrackTool : Tool { if (joined_path.path == p) { delete_last_points_at (x, y);- p.points.remove_link (p.points.first ()); glyph.close_path ();+ p.close (); } else { p = merge_paths (p, joined_path); if (!p.is_open ()) {@@ -190,8 +202,6 @@ public class TrackTool : Tool { ep.set_tie_handle (false); ep.recalculate_linear_handles (); - //p.delete_last_point ();- p.create_list (); if (glyph.path_list.length () < 2) {@@ -358,7 +368,7 @@ public class TrackTool : Tool { update_cycles = 0; } - if (update_cycles > 5) { // cycles of 100 ms+ if (update_cycles > 4) { // cycles of 100 ms convert_points_to_line (); last_update = get_current_time (); add_corner (last_x, last_y);