The Birdfont Source Code


All Repositories / birdfont.git / commit – RSS feed

Create stroke for curves

These changes was commited to the Birdfont repository Fri, 10 Apr 2015 22:21:54 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
author Johan Mattsson <johan.mattsson.m@gmail.com>
Fri, 10 Apr 2015 22:21:54 +0000 (00:21 +0200)
committer Johan Mattsson <johan.mattsson.m@gmail.com>
Fri, 10 Apr 2015 22:21:54 +0000 (00:21 +0200)
commit 2aec262ee3cad57dbbc15f03c893d2ed812f2b22
tree ba9cf07ee6c167c422cf14a438547b4c4b9bc84a
parent 42c2f216dbd802b976bac13bd6cd7e1eb35e22e8
Create stroke for curves

libbirdfont/EditPoint.vala
libbirdfont/Intersection.vala [new ]
libbirdfont/Path.vala
libbirdfont/StrokeTool.vala
--- a/libbirdfont/EditPoint.vala +++ b/libbirdfont/EditPoint.vala @@ -180,10 +180,14 @@ } } - public static bool is_valid (double x, double y) { + public bool is_valid () { + return is_valid_position (x, y); + } + + public static bool is_valid_position (double x, double y) { return likely (x.is_finite () && y.is_finite () - && x > -100000 && x < 100000 - && y > -100000 && y < 100000); + && x > Glyph.CANVAS_MIN && x < Glyph.CANVAS_MAX + && y > Glyph.CANVAS_MIN && y < Glyph.CANVAS_MAX); } public void set_point_type (PointType t) {
--- /dev/null +++ b/libbirdfont/Intersection.vala @@ -1,1 +1,91 @@ + /* + Copyright (C) 2015 Johan Mattsson + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + public class Intersection : GLib.Object { + public bool done = false; + public EditPoint point; + public EditPoint other_point; + public Path path; + public Path other_path; + + public Intersection (EditPoint point, Path path, + EditPoint other_point, Path other_path) { + + this.point = point; + this.path = path; + this.other_point = other_point; + this.other_path = other_path; + } + + public Intersection.empty () { + this.point = new EditPoint (); + this.path = new Path (); + this.other_point = new EditPoint (); + this.other_path = new Path (); + } + + public Path get_other_path (Path p) { + if (p == path) { + return other_path; + } + + if (p == other_path) { + return path; + } + + warning ("Wrong intersection."); + return new Path (); + } + + public EditPoint get_point (Path p) { + if (p == path) { + return point; + } + + if (p == other_path) { + return other_point; + } + + warning ("Wrong intersection."); + return new EditPoint (); + } + } + + public class IntersectionList : GLib.Object { + public Gee.ArrayList<Intersection> points = new Gee.ArrayList<Intersection> (); + + public IntersectionList () { + } + + public Intersection get_point (EditPoint ep, out bool other) { + other = false; + foreach (Intersection i in points) { + if (i.other_point == ep || i.point == ep) { + other = (i.other_point == ep); + return i; + } + } + + warning ("No intersection found for point."); + return new Intersection.empty (); + } + } + + }
--- a/libbirdfont/Path.vala +++ b/libbirdfont/Path.vala @@ -535,29 +535,6 @@ cr.close_path (); cr.fill (); - cr.restore (); - } - - public static void draw_image (Context cr, ImageSurface img, double x, double y) { - Glyph g = MainWindow.get_current_glyph (); - double r = 1.0 / 10.0; - - double width = Math.sqrt (stroke_width); - - double ivz = 1 / g.view_zoom; - double ivs = 1 / width; - - double xc = g.allocation.width / 2.0; - double yc = g.allocation.height / 2.0; - - cr.save (); - cr.scale (ivz * width * r, ivz * width * r); - - x = xc + x - (width * r * img.get_width () / 2.0) * ivz; - y = yc - y - (width * r * img.get_height () / 2.0) * ivz; - - cr.set_source_surface (img, x * g.view_zoom * ivs * 1/r, y * g.view_zoom * ivs * 1/r); - cr.paint (); cr.restore (); } @@ -1415,9 +1392,23 @@ } public static void get_point_for_step (EditPoint start, EditPoint stop, double step, out double x, out double y) { - // FIXME: Types - x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); - y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); + PointType right = start.type; + PointType left = stop.type; + + if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) { + x = double_bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); + y = double_bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); + } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) { + x = quadratic_bezier_path (step, start.x, start.get_right_handle ().x, stop.x); + y = quadratic_bezier_path (step, start.y, start.get_right_handle ().y, stop.y); + } else if (right == PointType.CUBIC && left == PointType.CUBIC) { + x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); + y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); + } else { + warning (@"Mixed point types"); + x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x); + y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); + } } private static bool all_of_double (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3,
--- a/libbirdfont/StrokeTool.vala +++ b/libbirdfont/StrokeTool.vala @@ -67,12 +67,6 @@ if (h) { PathList n = new PathList (); - - // FIXME: DELETE - print (@"event\n"); - foreach (Path p in g.active_paths) { - print (@"clockwise $(is_clockwise (p))\n"); - } foreach (Path p in g.active_paths) { if (p.stroke == 0) { @@ -99,33 +93,29 @@ GlyphCanvas.redraw (); } } - - public static PathList get_stroke (Path path, double thickness) { PathList n; - PathList o = new PathList (); - Path original = path.copy (); + Path stroke = path.copy (); - original.remove_points_on_points (0.3); - - n = get_stroke_outline (original, thickness); + stroke.remove_points_on_points (0.3); + + flatten (stroke, thickness); + n = get_stroke_outline (stroke, thickness); foreach (Path p in n.paths) { p.remove_points_on_points (0.3); } - - o = n; // o = split_corners (n); // o = get_all_parts (n); // Changing orientation causes a problem with self intersecting corners // but it solves the problem where the original path is self intersection. - // According to the fill rule should such intersection be counter paths but + // According to the fill rule should intersection be counter paths but // it is better to try to merge paths with filled intersections. // - // TODO: Implement merging with the other fill rule. + // TODO: Implement merging with other fill rules. // this works for glyphs like 8 but not for other intersections /* @@ -137,12 +127,12 @@ } }*/ - o = merge (o); + n = merge (n); - remove_self_intersecting_corners (o); - o = remove_remaining_corners (o); + remove_self_intersecting_corners (n); + n = remove_remaining_corners (n); - return o; + return n; } static PathList remove_remaining_corners (PathList pl) { @@ -235,6 +225,7 @@ if (is_corner_self_intersection (p)) { parts = get_parts (p); if (parts.paths.size > 1) { + pl.paths.remove (p); pl.append (parts); remove_self_intersecting_corners (pl); return; @@ -388,14 +379,23 @@ start.flags |= EditPoint.STROKE_OFFSET; end.flags |= EditPoint.STROKE_OFFSET; - if (!p.is_open () || (i != 0 && i != p.points.size - 1)) { - // add a new corner but remove small bumpts on the line later. - add_corner (stroked, previous, start, ep.copy (), thickness); + bool flat = (Path.distance_to_point (previous, start) < 0.05 * thickness); + + if (!flat && !p.is_open () || (i != 0 && i != p.points.size - 1)) { + if (!ep.tie_handles) { + add_corner (stroked, previous, start, ep.copy (), thickness); + } else { + warning ("Tied handles."); + } + } + + if (start.is_valid () && end.is_valid ()) { + stroked.add_point (start); + stroked.add_point (end); + } else { + warning ("Bad point in stroke."); } - stroked.add_point (start); - stroked.add_point (end); - // open ends around corner start.get_left_handle ().convert_to_line (); @@ -474,28 +474,20 @@ return; } - if (distance == stroke_width) { + if (!corner.is_valid ()) { + warning ("Invalid corner."); return; } - if (Path.distance_to_point (previous, next) < 0.5 * adjusted_stroke) { - previous.flags = EditPoint.NONE; - next.flags = EditPoint.NONE; - previous.color = Color.pink (); - next.color = Color.pink (); + previous.color = Color.red (); + next.color = Color.blue (); + + if (Path.distance_to_point (previous, next) < fabs (0.2 * stroke_width)) { return; } - - if (Path.distance_to_point (previous, corner) < 0.5 * adjusted_stroke - || Path.distance_to_point (corner, next) < 0.5 * adjusted_stroke) { - previous.flags = EditPoint.NONE; - next.flags = EditPoint.NONE; - previous.color = Color.pink (); - next.color = Color.pink (); - corner.color = Color.pink (); - return; - } - + + corner.color = Color.pink (); + if (distance < stroke_width) { previous.flags |= EditPoint.NEW_CORNER; next.flags |= EditPoint.NEW_CORNER; @@ -530,7 +522,6 @@ previous.flags |= EditPoint.NEW_CORNER; next.flags |= EditPoint.NEW_CORNER; } - } } } @@ -546,13 +537,7 @@ intersections.add (p); } - // return split_paths (intersections); return intersections; - } - - static PathList split_corners (PathList result) { - split_corner (result); - return result; } public static bool is_counter_to_outline (Path p) { @@ -589,14 +574,8 @@ static bool split_corner (PathList pl) { EditPoint p1, p2; EditPoint a1, a2; - EditPoint ep1, ep2; - EditPoint previous; PathList result; bool split; - EditPointHandle l, r; - double corner_x, corner_y; - bool intersection; - double ix, iy; foreach (Path p in pl.paths) { if (p.points.size == 0) { @@ -829,7 +808,6 @@ static PathList get_remaining_points (Path old_path) { PathList pl; - bool first = true; old_path.close (); pl = process_deleted_control_points (old_path); @@ -1036,12 +1014,14 @@ return intersection; } - static bool same (EditPoint a, EditPoint b) { - return a.x == b.x && a.y == b.y; + /** @return true if p2 is on the line p1 to p3 */ + static bool is_line (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.01) { + return fmin (x1, x3) <= x2 && x2 <= fmax (x1, x3) + && fmin (y1, y3) <= y2 && y2 <= fmax (y1, y3) + && is_flat (x1, y1, x2, y2, x3, y3, tolerance); } - /** @return true if p2 is on the line p1 to p3 */ - static bool is_line (double x1, double y1, double x2, double y2, double x3, double y3) { + static bool is_flat (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.01) { double ds = Path.distance (x1, x3, y1, y3); double d1 = Path.distance (x1, x2, y1, y2); double d2 = Path.distance (x2, x3, y2, y3); @@ -1050,48 +1030,13 @@ double y = fabs ((y3 - y1) * p - (y2 - y1)); double d = fabs (ds - (d1 + d2)); - return ds > 0.01 && d1 > 0.01 && d2 > 0.01 - && d < 0.01 && x < 0.01 && y < 0.01 - && fmin (x1, x3) <= x2 && x2 <= fmax (x1, x3) - && fmin (y1, y3) <= y2 && y2 <= fmax (y1, y3); - } - - static Path get_outline (Path path) { - PathList pl = get_parts (path); - Path outline = new Path (); - int inside; - int min_inside = int.MAX; - int points = 0; - int i = 0; - - foreach (Path p in pl.paths) { - inside = Path.counters (pl, p); - - if (inside < min_inside) { - outline = p; - min_inside = inside; - } - - i++; - } - - if (min_inside == 0) { - foreach (Path p in pl.paths) { - if (p.points.size > points) { - outline = p; - points = p.points.size; - } - } - } - - return outline; + return ds > 0.01 && d1 > 0.01 && d2 > 0.01 + && d < tolerance && x < tolerance && y < tolerance; } // indside becomes outside in some paths static void remove_points_in_stroke (PathList pl) { - PathList result; PathList r; - PathList parts; foreach (Path p in pl.paths) { if (remove_points_in_stroke_for_path (p, pl, out r)) { @@ -1103,15 +1048,12 @@ } static bool remove_points_in_stroke_for_path (Path p, PathList pl, out PathList result) { - bool remove = false; EditPoint start_ep; EditPoint start_next; EditPoint start_prev; EditPoint end_ep = new EditPoint (); EditPoint end_next; EditPoint end_prev; - Path path2; - EditPoint found = new EditPoint (); result = new PathList (); @@ -1224,16 +1166,6 @@ ep.deleted = false; } p.remove_points_on_points (); - } - - static bool has_counter_to_outline (Path p) { - foreach (EditPoint ep in p.points) { - if ((ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) { - return true; - } - } - - return false; } static bool add_merge_intersection_point (Path path1, Path path2, EditPoint first, EditPoint next) { @@ -1280,7 +1212,6 @@ PathList m; bool intersects = false; PathList r = new PathList (); - bool counter; foreach (Path p in pl.paths) { if (has_self_intersection (p)) { @@ -1316,18 +1247,8 @@ PathList r = pl; Path p1, p2; - print (@"enter\n"); - foreach (Path p in pl.paths) { - print (@"clockwise $(is_clockwise (p))\n"); - } - r = get_all_parts (r); - print (@"first part\n"); - foreach (Path p in r.paths) { - print (@"clockwise $(is_clockwise (p))\n"); - } - foreach (Path p in r.paths) { if (stroke_selected) { // FIXME: DELETE ((!) BirdFont.get_current_font ().get_glyph ("e")).add_path (p); @@ -1352,20 +1273,7 @@ ((!) BirdFont.get_current_font ().get_glyph ("g")).add_path (mm); } - print (@"Before\n"); - print (@"clockwise $(is_clockwise (p1))\n"); - print (@"clockwise $(is_clockwise (p2))\n"); - - print (@"\n"); - foreach (Path mm in m.paths) - print (@"clockwise $(is_clockwise (mm))\n"); - - r = get_all_parts (r); - - print (@"\n"); - foreach (Path mm in m.paths) - print (@"clockwise $(is_clockwise (mm))\n"); - + r = get_all_parts (r); } else { warning ("Not merged."); } @@ -1418,76 +1326,6 @@ if (stroke_selected) { // FIXME: DELETE foreach (Path mm in r.paths) ((!) BirdFont.get_current_font ().get_glyph ("j")).add_path (mm); - } - } - - public class Intersection : GLib.Object { - public bool done = false; - public EditPoint point; - public EditPoint other_point; - public Path path; - public Path other_path; - - public Intersection (EditPoint point, Path path, - EditPoint other_point, Path other_path) { - - this.point = point; - this.path = path; - this.other_point = other_point; - this.other_path = other_path; - } - - public Intersection.empty () { - this.point = new EditPoint (); - this.path = new Path (); - this.other_point = new EditPoint (); - this.other_path = new Path (); - } - - public Path get_other_path (Path p) { - if (p == path) { - return other_path; - } - - if (p == other_path) { - return path; - } - - warning ("Wrong intersection."); - return new Path (); - } - - public EditPoint get_point (Path p) { - if (p == path) { - return point; - } - - if (p == other_path) { - return other_point; - } - - warning ("Wrong intersection."); - return new EditPoint (); - } - } - - public class IntersectionList : GLib.Object { - public Gee.ArrayList<Intersection> points = new Gee.ArrayList<Intersection> (); - - public IntersectionList () { - } - - public Intersection get_point (EditPoint ep, out bool other) { - other = false; - foreach (Intersection i in points) { - if (i.other_point == ep || i.point == ep) { - other = (i.other_point == ep); - return i; - } - } - - warning ("No intersection found for point."); - return new Intersection.empty (); } } @@ -1495,13 +1333,12 @@ IntersectionList intersections; EditPoint ep1, next, p1, p2, pp1, pp2; Path path, other, merged; - PathList pl1, pl2, r, other_paths, result; + PathList r, other_paths, result; bool intersects; int s, i; double ix, iy, iix, iiy; bool merge = false; EditPoint intersection_point, other_intersection_point; - Intersection intersection; bool path1_direction, path2_direction; error = false; @@ -1809,24 +1646,6 @@ original_path2.remove_points_on_points (); original_path1.remove_deleted_points (); original_path2.remove_deleted_points (); - - // FIXME: delete - foreach (EditPoint ep in original_path1.points) { - if ((ep.flags & EditPoint.COPIED) == 0) { - warning (@"Points left in original_path1 ($(original_path1.points.size) ).\n"); - } - } - - foreach (EditPoint ep in original_path2.points) { - if ((ep.flags & EditPoint.COPIED) == 0) { - warning (@"Points left original_path2 ($(original_path2.points.size)).\n"); - ep.color = new Color (0,1,0,1); - - if (stroke_selected) { // FIXME: DELETE - ((!) BirdFont.get_current_font ().get_glyph ("h")).add_path (original_path2); - } - } - } int counter; foreach (Path np in result.paths) { @@ -1835,11 +1654,10 @@ p.remove_points_on_points (); counter = Path.counters (result, p); - print (@"counter: $counter\n"); + if (counter == 0) { has_direction = !merged.force_direction (Direction.CLOCKWISE); } else { - print (@"original_path1.is_clockwise () != original_path2.is_clockwise () $(is_clockwise (original_path1)) != $(is_clockwise (original_path2))\n"); if (is_clockwise (original_path1) != is_clockwise (original_path2)) { has_direction = !merged.force_direction (Direction.COUNTER_CLOCKWISE); } else { @@ -1870,23 +1688,6 @@ } return -1; - } - - static Path get_next_part (PathList pl, EditPoint ep) { - double d, m; - Path r; - - r = new Path (); - m = double.MAX; - foreach (Path p in pl.paths) { - d = Path.distance_to_point (p.get_last_point (), ep); - if (d < m) { - m = d; - r = p; - } - } - - return r; } public static int counters_in_point_in_path (Path p, EditPoint ep) { @@ -2000,8 +1801,6 @@ } public static bool has_points_outside (PathList pl, Path p) { - bool inside = false; - if (pl.paths.size != 2) { warning ("Stroke should only create two parts."); return false; @@ -2032,8 +1831,49 @@ } return sum > 0; + } + + static void flatten (Path path, double stroke_width) { + EditPoint start, end, new_point; + double px, py; + int size, i, added_points; + double step = 0.51; + + size = path.is_open () ? path.points.size - 1 : path.points.size; + + i = 0; + added_points = 0; + while (i < path.points.size) { + start = path.points.get (i); + end = path.points.get ((i + 1) % path.points.size); + + Path.get_point_for_step (start, end, step, out px, out py); + + if (unlikely (added_points > 4)) { + warning ("More than four points added in stroke."); + added_points = 0; + i++; + } else if (!is_flat (start.x, start.y, px, py, end.x, end.y, 0.05 * stroke_width) + && Path.distance (start.x, px, start.y, py) > 2 * stroke_width + && Path.distance (end.x, px, end.y, py) > 2 * stroke_width) { + new_point = new EditPoint (px, py); + new_point.prev = start; + new_point.next = end; + path.insert_new_point_on_path (new_point, step); + added_points++; + } else { + added_points = 0; + i++; + } + } + + path.remove_points_on_points (); + + if (stroke_selected) {// FIXME: DELETE + ((!) BirdFont.get_current_font ().get_glyph ("c")).add_path (path); + } } } }