Commit b18c4dd
Merge branch 'master' of github.com:johanmattssonm/birdfont Conflicts: libbirdfont/StrokeTool.vala
Changed files (12)
modifiedlibbirdfont/ClickMap.valamodifiedlibbirdfont/EditPoint.valamodifiedlibbirdfont/Font.valamodifiedlibbirdfont/OpenFontFormat/GlyfData.valamodifiedlibbirdfont/OpenFontFormat/GlyfTable.valamodifiedlibbirdfont/OpenFontFormat/HmtxTable.valamodifiedlibbirdfont/OpenFontFormat/OpenFontFormatReader.valamodifiedlibbirdfont/Path.valamodifiedlibbirdfont/PenTool.valamodifiedlibbirdfont/PointConverter.valamodifiedlibbirdfont/StrokeTool.valamodifiedscripts/version.py
Diff
@@ -17,52 +17,102 @@ using Cairo; namespace BirdFont { public class ClickMap : GLib.Object {- ImageSurface map;- int width;+ public ImageSurface map;+ public int width; + public double xmax;+ public double ymax;+ public double xmin;+ public double ymin;+ public ClickMap (int width) { this.width = width; map = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, width);+ xmax = 0;+ ymax = 0;+ xmin = 0;+ ymin = 0; } public bool get_value (int x, int y) { unowned uchar[] d = map.get_data (); bool transparent;++ Context c;+ + c = new Context (map);+ c.set_fill_rule (Cairo.FillRule.WINDING); if (unlikely (!(0 <= x < width && 0 <= y < width))) {- warning ("Array index out of bounds.");+ warning (@"Array index out of bounds. x: $x y: $y size: $width"); return true; } transparent = d[y * map.get_stride () + 4 * x + 3] == 0;- + return !transparent; }- - public void create_click_map (Path path) {+ + bool add_point (Context c, double cx, double cy) {+ int px = (int) (width * ((cx - xmin) / (xmax - xmin)));+ int py = (int) (width * ((cy - ymin) / (ymax - ymin)));+ c.line_to (px, py);+ return true;+ }+ + public void create_click_map (Path path, Path? counter = null) { // FIXME: clean up+ Path p; Context c;- + c = new Context (map); - c.save ();+ if (counter == null) {+ xmax = path.xmax;+ ymax = path.ymax;+ xmin = path.xmin;+ ymin = path.ymin;+ } else {+ p = (!) counter;+ + xmax = fmax (p.xmax, path.xmax);+ ymax = fmax (p.ymax, path.ymax);+ xmin = fmin (p.xmin, path.xmin);+ ymin = fmin (p.ymin, path.ymin);+ } + c.save ();+ c.set_fill_rule (FillRule.EVEN_ODD); c.set_source_rgba (0, 0, 0, 1); c.new_path ();-- path.all_of_path ((cx, cy, ct) => {- int px = (int) (width * ((cx - path.xmin) / (path.xmax - path.xmin)));- int py = (int) (width * ((cy - path.ymin) / (path.ymax - path.ymin)));- - c.line_to (px, py);- + path.all_of_path ((cx, cy) => {+ add_point (c, cx, cy); return true; }, 2 * width);+ c.close_path (); - c.close_path ();- c.fill ();+ if (counter != null) {+ c.new_path ();+ + p = (!) counter;+ p.all_of_path ((cx, cy) => {+ add_point (c, cx, cy);+ return true;+ }, 2 * width);+ + c.close_path ();+ } + c.fill (); c.restore ();- }+ } } +static double fmin (double a, double b) {+ return a < b ? a : b;+}++static double fmax (double a, double b) {+ return a > b ? a : b;+}+ }@@ -44,8 +44,8 @@ public class EditPoint : GLib.Object { public static uint DELETED = 1 << 2; public static uint TIE = 1 << 3; public static uint REFLECTIVE = 1 << 4;- public static uint CORNER = 1 << 5;- public static uint INTERSECTION = 1 << 6;+ public static uint INTERSECTION = 1 << 5;+ public static uint NEW_CORNER = 1 << 6; public uint flags = NONE; @@ -151,6 +151,8 @@ public class EditPoint : GLib.Object { } } + public Color? color = null;+ public EditPoint (double nx = 0, double ny = 0, PointType nt = PointType.NONE) { x = nx; y = ny;@@ -436,7 +438,9 @@ public class EditPoint : GLib.Object { new_point.left_handle.angle = left_handle.angle; new_point.left_handle.length = left_handle.length; new_point.left_handle.type = left_handle.type;- + + new_point.color = color;+ return new_point; } @@ -485,18 +485,7 @@ public class Font : GLib.Object { /** Obtain all versions and alterntes for this glyph. */ public GlyphCollection? get_glyph_collection (string glyph) {- GlyphCollection? gc = get_cached_glyph_collection (glyph);- Glyph? g;- - if (gc == null && otf_font) {- // load it from otf file if we need to- g = otf.read_glyph (glyph);- - if (g != null) {- return get_cached_glyph_collection (glyph);- }- }- + GlyphCollection? gc = get_cached_glyph_collection (glyph); return gc; } @@ -36,6 +36,7 @@ public class CoordinateFlags { /** Data for one entry in the glyf table. */ public class GlyfData : GLib.Object { public Gee.ArrayList<Path> paths = new Gee.ArrayList<Path> ();+ public Gee.ArrayList<EditPoint> points = new Gee.ArrayList<EditPoint> (); public Gee.ArrayList<uint16> end_points = new Gee.ArrayList<uint16> (); public Gee.ArrayList<uint8> flags = new Gee.ArrayList<uint8> (); public Gee.ArrayList<int16> coordinate_x = new Gee.ArrayList<int16> ();@@ -55,26 +56,48 @@ public class GlyfData : GLib.Object { get { return HeadTable.UNITS; } } - public GlyfData (Glyph g) { + public GlyfData (Glyph g) {+ bool process;+ PathList qp = g.get_quadratic_paths (); + glyph = g; - foreach (Path p in g.get_quadratic_paths ().paths) {+ foreach (Path p in qp.paths) { if (p.points.size > 0) { if (!is_empty (p)) { // Add points at extrema p.add_extrema ();- paths.add (p); } } } - if (paths.size > 0) {+ process = true;+ + while (process) {+ points.clear ();+ paths.clear ();+ foreach (Path p in qp.paths) {+ if (!is_empty (p)) {+ paths.add (p);+ foreach (EditPoint ep in p.points) {+ points.add (ep);+ }+ }+ }+ + if (paths.size == 0) {+ break;+ }+ process_end_points (); process_flags (); process_x ();- process_y ();+ + // error checking is done here+ process = !process_y (); + process_bounding_box ();- }+ } } bool is_empty (Path p) {@@ -112,6 +135,7 @@ public class GlyfData : GLib.Object { uint16 last_end_point = 0; PointType type; + end_points.clear (); end_point = 0; foreach (Path quadratic in paths) {@@ -187,6 +211,7 @@ public class GlyfData : GLib.Object { double x; PointType type; + coordinate_x.clear (); foreach (Path p in paths) { foreach (EditPoint e in p.points) { x = rint (e.x * UNITS - prev - glyph.left_limit * UNITS);@@ -205,17 +230,31 @@ public class GlyfData : GLib.Object { } } - void process_y () {+ bool process_y () { double prev = 0; double y; Font font = OpenFontFormatWriter.get_current_font (); PointType type;+ int epi = 0;+ + coordinate_y.clear (); foreach (Path p in paths) { foreach (EditPoint e in p.points) { y = rint (e.y * UNITS - prev - font.base_line * UNITS); coordinate_y.add ((int16) y); + if ((int16) y == 0 && (int16) coordinate_x.get (coordinate_y.size - 1) == 0) {+ warning (@"Point on point in TTF. Index $(coordinate_y.size - 1)");+ + if (BirdFont.has_argument ("--test")) {+ print (glyph.get_name () + "\n");+ print (points.get (epi).to_string ());+ PenTool.remove_point_simplify (new PointSelection (points.get (epi), p));+ return false;+ }+ }+ prev = rint (e.y * UNITS - font.base_line * UNITS); type = e.get_right_handle ().type;@@ -223,10 +262,24 @@ public class GlyfData : GLib.Object { // off curve y = rint (e.get_right_handle ().y * UNITS - prev - font.base_line * UNITS); coordinate_y.add ((int16) y);- ++ if ((int16) y == 0 && (int16) coordinate_x.get (coordinate_y.size - 1) == 0) {+ warning (@"Point on point in TTF (off curve) Index: $(coordinate_y.size - 1) ");+ if (BirdFont.has_argument ("--test")) {+ print (glyph.get_name () + "\n");+ print (points.get (epi).to_string ());+ + PenTool.remove_point_simplify (new PointSelection (points.get (epi), p));+ return false;+ }+ }+ prev = rint (e.get_right_handle ().y * UNITS - font.base_line * UNITS);+ epi++; } }+ + return true; } void process_bounding_box () {@@ -283,3 +336,4 @@ public class GlyfData : GLib.Object { } }+@@ -319,20 +319,6 @@ public class GlyfTable : OtfTable { } printd (@"length after padding: $(fd.length ())\n"); }-- public Glyph? read_glyph (string name) throws GLib.Error {- Glyph? glyph;- int i;- - i = post_table.get_gid (name);- - if (i == -1) {- return null;- }- - glyph = parse_index (i, dis, loca_table, hmtx_table, head_table, post_table);- return glyph;- } public new void parse (FontData dis, CmapTable cmap_table, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table, KernTable kern_table) throws GLib.Error { this.cmap_table = cmap_table;@@ -344,56 +330,6 @@ public class GlyfTable : OtfTable { this.dis = dis; } - Glyph parse_index (int index, FontData dis, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table) throws GLib.Error {--// FIXME: DELETE parse with freetype- Glyph glyph = new Glyph ("");-/*- double xmin, xmax;- double units_per_em = head_table.get_units_per_em ();- unichar character = 0;- string name;- - character = cmap_table.get_char (index);- name = post_table.get_name (index);- - if (name == "") {- StringBuilder name_c = new StringBuilder ();- name_c.append_unichar (character);- name = name_c.str;- }-- printd (@"name: $(name)\n");-- if (!loca.is_empty (index)) { - glyph = parse_next_glyf (dis, character, index, out xmin, out xmax, units_per_em);- - glyph.left_limit = xmin - hmtx_table.get_lsb (index);- glyph.left_limit = 0;- glyph.right_limit = glyph.left_limit + hmtx_table.get_advance (index);- } else {- // add empty glyph- glyph = new Glyph (name, character);- glyph.left_limit = -hmtx_table.get_lsb (index);- glyph.right_limit = hmtx_table.get_advance (index) - hmtx_table.get_lsb (index); - }- - glyph.name = name;-- if (character == 0) {- glyph.set_unassigned (true);- }- - if (character == 0 && name != "") {- stderr.printf (@"Got null character\n");- stderr.printf (@"gid: $index\n");- stderr.printf (@"char: $((uint) character)\n");- stderr.printf (@"name: $(name)\n");- }-*/- return glyph;- }- Glyph parse_next_composite_glyf (FontData dis, unichar character, int pgid) throws Error { uint16 component_flags = 0; uint16 glyph_index;@@ -173,7 +173,9 @@ public class HmtxTable : OtfTable { font_data = fd; - warn_if_fail (max_advance != 0);+ if (max_advance != 0) {+ warning ("max_advance is zero");+ } } public int16 get_average_width () {@@ -60,16 +60,6 @@ public class OpenFontFormatReader : Object { dis.close (); } - public Glyph? read_glyph (string name) {- try {- return directory_table.glyf_table.read_glyph (name);- } catch (GLib.Error e) {- warning (e.message);- }- - return null;- }- public void parse_index (string file_name) throws Error { file = File.new_for_path (file_name); if (!file.query_exists ()) {@@ -410,8 +410,8 @@ public class Path { } public void draw_edit_point_handles (EditPoint e, Context cr) {- string color_left = "Control Point Handle";- string color_right = "Control Point Handle";+ Color color_left = Theme.get_color ("Control Point Handle");+ Color color_right = Theme.get_color ("Control Point Handle"); EditPoint handle_right = e.get_right_handle ().get_point (); EditPoint handle_left = e.get_left_handle ().get_point (); @@ -419,19 +419,19 @@ public class Path { if (e.type != PointType.HIDDEN) { if (e.get_right_handle ().selected) {- color_right = "Selected Control Point Handle";+ color_right = Theme.get_color ("Selected Control Point Handle"); } else if (e.get_right_handle ().active) {- color_right = "Active Control Point Handle";+ color_right = Theme.get_color ("Active Control Point Handle"); } else {- color_right = "Control Point Handle";+ color_right = Theme.get_color ("Control Point Handle"); } if (e.get_left_handle ().selected) {- color_left = "Selected Control Point Handle";+ color_left = Theme.get_color ("Selected Control Point Handle"); } else if (e.get_left_handle ().active) {- color_left = "Active Control Point Handle";+ color_left = Theme.get_color ("Active Control Point Handle"); } else {- color_left = "Control Point Handle";+ color_left = Theme.get_color ("Control Point Handle"); } if (!hide_end_handle || !(is_open () && e == points.get (points.size - 1))) {@@ -447,40 +447,76 @@ public class Path { } public static void draw_edit_point_center (EditPoint e, Context cr) {+ Color c;+ if (e.type != PointType.HIDDEN) { if (e.type == PointType.CUBIC || e.type == PointType.LINE_CUBIC) { if (e.is_selected ()) { if (e.active_point) {- draw_control_point (cr, e.x, e.y, "Selected Active Cubic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Selected Active Cubic Control Point");+ } } else {- draw_control_point (cr, e.x, e.y, "Selected Cubic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Selected Cubic Control Point");+ } } } else { if (e.active_point) {- draw_control_point (cr, e.x, e.y, "Active Cubic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Active Cubic Control Point");+ } } else {- draw_control_point (cr, e.x, e.y, "Cubic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Cubic Control Point");+ } } } } else { if (e.is_selected ()) { if (e.active_point) {- draw_control_point (cr, e.x, e.y, "Selected Active Quadratic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Selected Active Quadratic Control Point");+ } } else {- draw_control_point (cr, e.x, e.y, "Selected Quadratic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Selected Quadratic Control Point");+ } } } else { if (e.active_point) {- draw_control_point (cr, e.x, e.y, "Active Quadratic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Active Quadratic Control Point");+ } } else {- draw_control_point (cr, e.x, e.y, "Quadratic Control Point");+ if (e.color != null) {+ c = (!) e.color;+ } else {+ c = Theme.get_color ("Quadratic Control Point");+ } } } }+ + draw_control_point (cr, e.x, e.y, c); } } - public static void draw_control_point (Context cr, double x, double y, string color, double size = 3.5) {+ public static void draw_control_point (Context cr, double x, double y, Color color, double size = 3.5) { Glyph g = MainWindow.get_current_glyph (); double ivz = 1 / g.view_zoom; double width = size * Math.sqrt (stroke_width) * ivz;@@ -492,7 +528,7 @@ public class Path { x = xc + x - (width / 2.0) * ivz; y = yc - y - (width / 2.0) * ivz; - Theme.color (cr, color);+ cr.set_source_rgba (color.r, color.g, color.b, color.a); cr.move_to (x, y); cr.arc (x, y, width, 0, 2 * Math.PI);@@ -764,8 +800,7 @@ public class Path { if (!is_over_boundry (x, y)) { return false; }- - + // generate a rasterized image of the object width = 160; click_map = new ClickMap (width);@@ -1667,7 +1702,7 @@ public class Path { int i; int index = 0; - if (!has_deleted_point ()) { + if (!has_deleted_point ()) { return path_list; } @@ -1676,7 +1711,7 @@ public class Path { return path_list; } - // set start position to the point that will be removed + // set start position to a point that will be removed for (i = 0; i < points.size; i++) { p = points.get (i); @@ -1959,6 +1994,7 @@ public class Path { find_intersection (h1.parent.x, h1.parent.y, h1.x, h1.y, h2.parent.x, h2.parent.y, h2.x, h2.y, out point_x, out point_y); } + /** Finx intersection point for two straight lines. */ public static void find_intersection_point (EditPoint p1, EditPoint p2, EditPoint q1, EditPoint q2, out double point_x, out double point_y) { find_intersection (p1.x, p1.y, p2.x, p2.y, q1.x, q1.y, q2.x, q2.y, out point_x, out point_y); }@@ -1209,16 +1209,16 @@ public class PenTool : Tool { if (active_handle.active) { Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),- Glyph.path_coordinate_y (begin_action_y), "Control Point Handle"); + Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Control Point Handle")); } else if (selected_points.size > 0) { ps = selected_points.get (selected_points.size - 1); if (ps.point.type == PointType.CUBIC) { Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),- Glyph.path_coordinate_y (begin_action_y), "Selected Cubic Control Point");+ Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Cubic Control Point")); } else { Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),- Glyph.path_coordinate_y (begin_action_y), "Selected Quadratic Control Point"); + Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Quadratic Control Point")); } } }@@ -38,8 +38,6 @@ public class PointConverter { warning ("Too many points in segment."); } - quadratic_path.remove_points_on_points ();- if (quadratic_path.points.size < 2) { return new Path (); }@@ -19,9 +19,16 @@ namespace BirdFont { public class StrokeTool : Tool { + static bool stroke_selected = false;+ static int iterations = 0;+ public StrokeTool (string tooltip) {+ iterations = 10; select_action.connect((self) => {+ stroke_selected = true;+ iterations++; stroke_selected_paths ();+ stroke_selected = false; }); } @@ -50,29 +57,39 @@ public class StrokeTool : Tool { } public static PathList get_stroke (Path path, double thickness) {- Path p = path.copy ();- PathList pl;+ PathList pl = new PathList ();+ StrokeParts parts;+ + parts = get_parts (path.copy (), thickness, path); - pl = get_stroke_outline (p, thickness); + foreach (Path p in parts.get_all ()) {+ p.get_first_point ().color = new Color (0, 1, 0, 1);+ pl.append (get_stroke_outline (p, thickness));+ } return pl; } public static PathList get_stroke_outline (Path p, double thickness) {- Path counter, outline, merged;+ Path counter, outline;+ Path merged; PathList paths = new PathList ();- + StrokeParts parts;+ if (!p.is_open () && p.is_filled ()) { outline = create_stroke (p, thickness); outline.close ();- paths.add (outline);- outline.update_region_boundaries ();+ + parts = remove_intersections (outline, thickness, p);+ + foreach (Path sp in parts.get_all ()) {+ paths.add (sp);+ sp.update_region_boundaries ();+ } } 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 {@@ -85,8 +102,19 @@ public class StrokeTool : Tool { counter.force_direction (Direction.CLOCKWISE); } - outline.update_region_boundaries ();- counter.update_region_boundaries ();+ parts = remove_intersections (outline, thickness, p);+ + foreach (Path sp in parts.get_all ()) {+ paths.add (sp);+ sp.update_region_boundaries ();+ }++ parts = remove_intersections (counter, thickness, p);+ + foreach (Path sp in parts.get_all ()) {+ paths.add (sp);+ sp.update_region_boundaries ();+ } } else if (p.is_open ()) { outline = create_stroke (p, thickness); counter = create_stroke (p, -1 * thickness);@@ -97,9 +125,13 @@ public class StrokeTool : Tool { } else { merged.force_direction (Direction.COUNTER_CLOCKWISE); }++ parts = remove_intersections (merged, thickness, p); - merged.update_region_boundaries ();- paths.add (merged);+ foreach (Path sp in parts.get_all ()) {+ paths.add (sp);+ sp.update_region_boundaries ();+ } } else { warning ("Can not create stroke."); paths.add (p);@@ -139,10 +171,12 @@ public class StrokeTool : Tool { static Path create_stroke (Path p, double thickness) { Path stroked;+ Path path; if (p.points.size >= 2) {- stroked = p.copy ();- stroked = generate_stroke (stroked, thickness);+ path = p.copy ();+ path.remove_points_on_points ();+ stroked = generate_stroke (path, thickness); if (!p.is_open ()) { stroked.reverse ();@@ -153,9 +187,7 @@ public class StrokeTool : Tool { warning ("One point."); stroked = new Path (); }- - remove_self_intersections (stroked);- + return stroked; } @@ -166,7 +198,7 @@ public class StrokeTool : Tool { EditPoint previous; int i; - previous = p.get_last_point ().copy ();+ previous = p.get_first_point ().copy (); move_segment (start, previous, thickness); i = 0;@@ -176,10 +208,12 @@ public class StrokeTool : Tool { move_segment (start, end, thickness); - if (end.get_left_handle ().length > 0 && end.get_right_handle ().length > 0) {- if (!p.is_open () || (i != 0 && i != p.points.size - 1)) { // FIXME: first point i=0- add_corner (stroked, previous, start, ep.copy (), thickness);- }+ if (start == p.get_last_point ()) {+ end = p.get_first_point ();+ }+ + if (!p.is_open () || (i != 0 && i != p.points.size - 1)) {+ add_corner (stroked, previous, start, ep.copy (), thickness); } stroked.add_point (start);@@ -253,10 +287,16 @@ public class StrokeTool : Tool { corner = new EditPoint (corner_x, corner_y, previous.type); corner.convert_to_line (); + previous_handle.angle -= PI;+ next_handle.angle -= PI;+ distance = Path.distance_to_point (corner, original);+ ratio = fabs (stroke_width) / distance; // FIXME: cutoff parameter - ratio = fabs (stroke_width) / distance;- + double r = original.get_right_handle ().angle;+ double l = original.get_left_handle ().angle;+ double angle = atan2 (sin (r - l), cos (r - l));+ if (ratio > 1) { stroked.add_point (corner); } else {@@ -273,74 +313,200 @@ public class StrokeTool : Tool { cutoff2.x = next.x + (corner.x - next.x) * ratio; cutoff2.y = next.y + (corner.y - next.y) * ratio;++ cutoff1 = stroked.add_point (cutoff1);+ cutoff2 = stroked.add_point (cutoff2); - stroked.add_point (cutoff1);- stroked.add_point (cutoff2);- }+ cutoff1.recalculate_linear_handles ();+ cutoff2.recalculate_linear_handles ();+ } + } - previous_handle.angle -= PI;- next_handle.angle -= PI;+ static StrokeParts remove_intersections (Path path, double thickness, Path original) {+ StrokeParts parts;+ + parts = get_parts (path, thickness, original);+ delete_intersection_parts (original, parts.parts, thickness);+ + return parts;+ }+ + static StrokeParts get_parts (Path path, double thickness, Path original) {+ StrokeParts sp;+ PathList pl = new PathList ();+ StrokeParts parts = new StrokeParts ();+ Path remaining_points = path;+ + while (add_self_intersection_points (remaining_points)) {+ foreach (EditPoint p in remaining_points.points) {+ if ((p.flags & EditPoint.INTERSECTION) > 0) {+ p.deleted = true;+ // FIXME: delete p.color = new Color (1, 1, 0, 1);+ } + }+ + remaining_points = get_remaining_points (remaining_points, parts);+ }+ + foreach (Path p in parts.parts.paths) {+ p.close ();+ sp = get_parts (p, thickness, original);+ pl.add (sp.path);+ pl.append (sp.parts);+ }+ + parts.parts.clear ();+ + foreach (Path p in pl.paths) {+ parts.parts.add (p);+ }+ + parts.path = remaining_points;+ + return parts; } - static void remove_self_intersections (Path p) {- bool keep = true;+ static PathList process_deleted_control_points (Path path) {+ PathList paths, nl, pl, rl; - add_self_intersection_points (p);- - // FIXME: set start on non intersecting point- foreach (EditPoint ep in p.points) {- if ((ep.flags & EditPoint.INTERSECTION) > 0) {- print (@"Inter $(ep)");- keep = !keep;+ paths = new PathList ();+ rl = new PathList ();+ pl = new PathList ();+ nl = new PathList ();+ + if (!path.has_deleted_point ()) {+ return pl;+ }+ + pl.add (path);+ + foreach (Path p in pl.paths) {+ nl = p.process_deleted_points ();+ + if (nl.paths.size > 0) {+ rl.append (nl);+ rl.paths.remove (p); }+ }+ + foreach (Path p in rl.paths) {+ pl = process_deleted_control_points (p); - if (!keep && (ep.flags & EditPoint.INTERSECTION) == 0) {- ep.deleted = true;- ep.type = PointType.CUBIC;+ if (pl.paths.size > 0) {+ paths.append (pl);+ } else {+ paths.add (p); } } - p.remove_deleted_points ();+ return paths; } - static void add_self_intersection_points (Path path) {- Gee.ArrayList<EditPoint> n = new Gee.ArrayList<EditPoint> ();+ static Path get_remaining_points (Path old_path, StrokeParts parts) {+ Path new_path;+ PathList pl;++ old_path.close ();+ pl = process_deleted_control_points (old_path);+ + if (pl.paths.size == 0) {+ return old_path;+ }++ new_path = new Path ();+ foreach (Path pn in pl.paths) {+ if (pn.points.size > new_path.points.size) {+ new_path = pn;+ }+ + if (stroke_selected) // FIXME: DELETE+ ((!) BirdFont.get_current_font ().get_glyph ("a")).add_path (pn); + }+ + new_path.reopen ();+ + foreach (Path pn in pl.paths) {+ if (pn != new_path) {+ parts.parts.add (pn);+ }+ }+ + if (new_path.has_deleted_point ()) {+ warning ("Points left.");+ }+ + return get_remaining_points (new_path, parts);+ }++ static bool add_self_intersection_points (Path path) {+ bool intersection = false; + path.get_first_point ().color = new Color (0, 1, 0, 1);+ path.all_segments ((ep1, ep2) => { double ix, iy;- EditPoint nep;- EditPoint nep2;+ EditPoint p1; EditPoint p2; - if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2)) {- nep = new EditPoint ();- nep.prev = ep1;- nep.next = ep2;- - nep.x = ix;- nep.y = iy;- - n.add (nep);- - nep2 = new EditPoint ();- nep2.prev = p1;- nep2.next = p2;- - nep2.x = ix;- nep2.y = iy;- - n.add (nep2);+ if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2)) { + add_intersection (path, ep1, ep2, ix, iy);+ add_intersection (path, p1, p2, ix, iy); + intersection = true;+ return false; } return true; }); + return intersection;+ }+ + static void add_intersection (Path path, EditPoint prev, EditPoint next, double px, double py, Color? c = null) {+ Gee.ArrayList<EditPoint> n = new Gee.ArrayList<EditPoint> ();+ EditPoint ep1 = new EditPoint ();+ EditPoint ep2 = new EditPoint ();+ EditPoint ep3 = new EditPoint ();+ + if (prev == path.get_last_point ()) { // FIXME: double check+ ep1.prev = null;+ } else {+ ep1.prev = prev;+ }+ + ep1.prev = prev;+ ep1.next = ep2;+ ep1.flags |= EditPoint.NEW_CORNER;+ ep1.type = PointType.CUBIC;+ ep1.x = px;+ ep1.y = py;+ ep1.color = c;+ n.add (ep1);++ ep2.prev = ep1;+ ep2.next = ep3;+ ep2.flags |= EditPoint.INTERSECTION;+ ep2.type = PointType.QUADRATIC;+ ep2.x = px;+ ep2.y = py;+ ep2.color = c;+ n.add (ep2);++ ep3.prev = ep2;+ ep3.next = next;+ ep3.flags |= EditPoint.NEW_CORNER;+ ep3.type = PointType.CUBIC;+ ep3.x = px;+ ep3.y = py;+ ep3.color = c;+ n.add (ep3);+ foreach (EditPoint np in n) {- path.insert_new_point_on_path (np, -1, true);- np.type = PointType.QUADRATIC;- np.flags |= EditPoint.INTERSECTION;+ np = path.add_point_after (np, np.prev);+ path.create_list (); }+ + path.recalculate_linear_handles (); } static bool segment_intersects (Path path, EditPoint ep, EditPoint next,@@ -360,31 +526,114 @@ public class StrokeTool : Tool { } // FIXME: last to first- for (int i = 1; i < path.points.size - 2; i++) {+ for (int i = 2; i < path.points.size - 2; i++) { p1 = path.points.get (i - 1); p2 = path.points.get (i); Path.find_intersection_point (ep, next, p1, p2, out cross_x, out cross_y);- - if ((p1.x < cross_x < p2.x || p1.x > cross_x > p2.x)- && (p1.y < cross_y < p2.y || p1.y > cross_y > p2.y)- && (ep.x < cross_x < next.x || ep.x > cross_x > next.x)- && (ep.y < cross_y < next.y || ep.y > cross_y > next.y)) {+ + if (Glyph.CANVAS_MIN < cross_x < Glyph.CANVAS_MAX+ && Glyph.CANVAS_MIN < cross_y < Glyph.CANVAS_MAX) {+ // iterate to find intersection.++ if (!((ep.x == cross_x && ep.y == cross_y)+ || (next.x == cross_x && next.y == cross_y)+ || (p1.x == cross_x && p1.y == cross_y) + || (p2.x == cross_x && p2.y == cross_y))) {+ + if (is_line (ep.x, ep.y, cross_x, cross_y, next.x, next.y)+ && is_line (p1.x, p1.y, cross_x, cross_y, p2.x, p2.y)) { - // iterate to find intersection. - ix = cross_x;- iy = cross_y;- - ia = p1;- ib = p2;- - return true;- }+ // FIXME: delete ep.color = new Color (1, 0, 0, 1);+ next.color = new Color (0.5, 0, 0, 1);+ + // FIXME: delete p1.color = new Color (0, 0, 1, 1);+ // FIXME: delete p2.color = new Color (0, 0, 0.5, 1);+ + ix = cross_x;+ iy = cross_y;+ + ia = p1;+ ib = p2;+ + return true;+ }+ } + } } return false; }-}+ + /** @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 ds = Path.distance (x1, x3, y1, y3);+ double d1 = Path.distance (x1, x2, y1, y2);+ double d2 = Path.distance (x2, x3, y2, y3);+ double p = d1 / ds;+ double x = fabs ((x3 - x1) * p - (x2 - x1));+ double y = fabs ((y3 - y1) * p - (y2 - y1));+ double d = fabs (ds - (d1 + d2));+ + return ds > 0.001 && d1 > 0.001 && d2 > 0.001 + && d < 0.001 && x < 0.001 && y < 0.001+ && fmin (x1, x3) < x2 && x2 < fmax (x1, x3) + && fmin (y1, y3) < y2 && y2 < fmax (y1, y3);+ }++ static void delete_intersection_parts (Path original, PathList parts, double stroke_width) {+ PathList remove = new PathList ();+ + foreach (Path p in parts.paths) {+ if (is_stroke (original, p, stroke_width)) {+ remove.add (p);+ }+ }+ + foreach (Path p in remove.paths) {+ parts.paths.remove (p);+ } + }+ + static bool is_stroke (Path original, Path part, double stroke_width) {+ double stroke_size = fabs (stroke_width);+ bool stroke = false;+ + original.all_of_path ((cx, cy, ct) => {+ foreach (EditPoint p in part.points) {+ if (Path.distance (cx, p.x, cy, p.y) < stroke_size - 0.1) {+ if (48 < p.x < 50) print (@"D: $(Path.distance (cx, p.x, cy, p.y)) < $(stroke_size) \n");+ + p.color = new Color (1, 0, 1, 1); // FIXME: DELETE+ stroke = true;+ return false;+ } else {+ p.color = new Color (1, 1, 1, 1); // FIXME: DELETE+ }+ }+ + return true;+ }, 12);+ + return stroke;+ } + class StrokeParts : GLib.Object {+ public PathList parts = new PathList ();+ public Path path = new Path ();+ + public Gee.ArrayList<Path> get_all () {+ Gee.ArrayList<Path> pl = new Gee.ArrayList<Path> ();+ + pl.add (path);+ + foreach (Path p in parts.paths) {+ pl.add (p);+ }+ + return pl;+ }+ } } +}@@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. """ -VERSION = '2.4.3'+VERSION = '2.4.4' SO_VERSION_MAJOR = '36' SO_VERSION_MINOR = '0' SO_VERSION = SO_VERSION_MAJOR + '.' + SO_VERSION_MINOR