Clone in a terminal:
Path.vala
/*
    Copyright (C) 2012 Johan Mattsson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using Cairo;
using Math;

namespace Supplement {

public enum Direction {
	CLOCKWISE,
	COUNTER_CLOCKWISE
}

public class Path {
	
	public List<EditPoint> points = new List<EditPoint> ();

	EditPoint? last_point = null;
	EditPoint? second_last_point = null;

	/** Region boundries */
	public double xmax = double.MIN;
	public double xmin = double.MAX;
	public double ymax = double.MIN;
	public double ymin = double.MAX;

	bool edit = true;
	bool open = true;
	bool direction_is_set = false;

	bool no_derived_direction = false;
	bool clockwise_direction = true;

	public delegate bool RasterIterator (double x, double y, double step); // iterate over each pixel at a given zoom level
	
	bool selected = false;
	
	private static ImageSurface? edit_point_image = null;
	private static ImageSurface? active_edit_point_image = null;
	
	private static ImageSurface? edit_point_handle_image = null;
	private static ImageSurface? active_edit_point_handle_image = null;

	private static ImageSurface? selected_edit_point_image = null;
	private static ImageSurface? active_selected_edit_point_image = null;
	
	Path quadratic_path; // quadratic points for ttf export
	
	public Path () {
		if (edit_point_image == null) {
			edit_point_image = Icons.get_icon ("edit_point.png");
			active_edit_point_image = Icons.get_icon ("active_edit_point.png");
			edit_point_handle_image = Icons.get_icon ("edit_point_handle.png");
			active_edit_point_handle_image = Icons.get_icon ("active_edit_point_handle.png");
			selected_edit_point_image  = Icons.get_icon ("selected_edit_point.png");
			active_selected_edit_point_image = Icons.get_icon ("active_selected_edit_point.png");
		}
	}

	public bool has_direction () {
		return direction_is_set;
	}

	public bool empty () {
		return points.length () == 0;
	}

	public void draw_outline (Context cr, Allocation allocation, double view_zoom) {
		unowned List<EditPoint> ep = points;
		
		unowned EditPoint? n = null;
		unowned EditPoint en;
		unowned EditPoint em;
		
		if (ep.length () < 2) {
			return;
		}
		
		cr.new_path ();
		
		// draw lines
		foreach (EditPoint e in ep) {
			if (n != null) {
				en = (!) n;
				draw_next (e, en, cr);
			}
			
			n = e;
		}

		// close path
		if (!is_open () && n != null) {
			en = (!) n;
			em = ep.first ().data;
			
			draw_next (em, en, cr);
		}

		cr.stroke ();
	}
	
	public void draw_edit_points (Context cr, Allocation allocation, double view_zoom) {
		unowned List<EditPoint> ep = points;
		
		if (is_editable ()) {
			// control points for curvature
			foreach (EditPoint e in ep) {
				if (e.selected || e.selected_handle > 0)
					draw_edit_point_handles (e, cr);
			}
						
			// control points
			foreach (EditPoint e in ep) {
				draw_edit_point (e, cr);
			}
		}
	}

	public void fill_path (Context cr, Allocation allocation, double view_zoom) {
		unowned List<EditPoint> ep = points;
		
		unowned EditPoint? n = null;
		unowned EditPoint en;
		unowned EditPoint em;
		
		cr.new_path ();
					
		// draw lines
		foreach (EditPoint e in ep) {
			if (n != null) {
				en = (!) n;
				draw_next (e, en, cr);
			}
			
			n = e;
		}
		
		// close path
		if (!is_open () && ep.length () >= 2 && n != null) {
			en = (!) n;
			em = ep.first ().data;
			draw_next (em, en, cr);
		}

		// fill path
		cr.close_path ();
			
		if (is_clockwise ()) {
			cr.set_source_rgba (80/255.0, 95/255.0, 137/255.0, 0.5);
		} else {
			cr.set_source_rgba (144/255.0, 145/255.0, 236/255.0, 0.5);
		}
		
		cr.fill ();
	}

	private void draw_next (EditPoint e, EditPoint en, Context cr) {
		draw_curve (e, en, cr);
	}
	
	private static void draw_curve (EditPoint e, EditPoint en, Context cr, double alpha = 1) {
		Glyph g = MainWindow.get_current_glyph ();
		double xa, ya, xb, yb, xc, yc, xd, yd;
		
		get_bezier_points (e, en, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);

		cr.set_source_rgba (0, 0, 0, alpha);
		cr.set_line_width (1 / g.view_zoom);
		
		cr.line_to (xa, ya); // this point makes sense only if it is the first or last position, the other points are meaning less don't export them

		cr.curve_to (xb, yb, xc, yc, xd, yd);
	}
	
	/** Curve relative to window center. */
	public static void get_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {
		Glyph g = MainWindow.get_current_glyph ();
		
		double center_x, center_y;
		
		center_x = g.allocation.width / 2.0;
		center_y = g.allocation.height / 2.0;
				
		xa = center_x + en.x;
		ya = center_y - en.y;

		xb = center_x + en.get_right_handle ().x ();
		yb = center_y - en.get_right_handle ().y ();
		
		xc = center_x + e.get_left_handle ().x ();
		yc = center_y - e.get_left_handle ().y ();
		
		xd = center_x + e.x;
		yd = center_y - e.y;		
	}

	/** Curve absolute glyph data. */
	public static void get_abs_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {
		xa =  + en.x;
		ya =  - en.y;

		xb =  + en.get_right_handle ().x ();
		yb =  - en.get_right_handle ().y ();
		
		xc =  + e.get_left_handle ().x ();
		yc =  - e.get_left_handle ().y ();
		
		xd =  + e.x;
		yd =  - e.y;		
	}
		
	/** Line points relative to center. */
	public static void get_line_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb) {
		double xc = Glyph.xc ();
		double yc = Glyph.yc ();
		
		xa = xc + en.x;
		ya = yc - en.y;
		
		xb = xc + e.x;
		yb = yc - e.y;
	}
		
	public void draw_line (EditPoint e, EditPoint en, Context cr, double alpha = 1) { 
		Glyph g = MainWindow.get_current_glyph ();
		double ax, ay, bx, by;

		get_line_points (e, en, out ax, out ay, out bx, out by);
	
		cr.set_source_rgba (0/255.0, 0/255.0, 0/255.0, 1);
		cr.set_line_width (1.7 * (1/g.view_zoom));

		cr.line_to (ax, ay);
		cr.line_to (bx, by);
		
		cr.stroke ();
	}
	
	public void draw_edit_point  (EditPoint e, Context cr) {
		draw_edit_point_center (e, cr);
	}
	
	public void draw_edit_point_handles (EditPoint e, Context cr)
		requires (active_edit_point_handle_image != null && edit_point_handle_image != null)
	{
		ImageSurface img_right, img_left;

		EditPoint handle_right = e.get_right_handle ().get_point ();
		EditPoint handle_left = e.get_left_handle ().get_point ();

		cr.stroke ();
		
		img_right = (e.get_right_handle ().active) ? (!) active_edit_point_handle_image : (!) edit_point_handle_image;
		img_left = (e.get_left_handle ().active) ? (!) active_edit_point_handle_image : (!) edit_point_handle_image;
		
		if (!(is_open () && e == points.last ().data)) {
			draw_line (handle_right, e, cr, 0.15);
			draw_img_center (cr, img_right, e.get_right_handle ().x (), e.get_right_handle ().y ());
		}
		
		if (!(is_open () && e == points.first ().data)) {
			draw_line (handle_left, e, cr, 0.15);
			draw_img_center (cr, img_left, e.get_left_handle ().x (), e.get_left_handle ().y ());
		}
	}

	public static void draw_edit_point_center (EditPoint e, Context cr) 
		requires (active_edit_point_image != null && edit_point_image != null)
	{	
		ImageSurface img;
		
		if (e.is_selected ()) {
			img = (e.active) ? (!) active_selected_edit_point_image : (!) selected_edit_point_image;
		} else {
			img = (e.active) ? (!) active_edit_point_image : (!) edit_point_image;
		}
		
		draw_img_center (cr, img, e.x, e.y);
	}
	
	public static void draw_img_center (Context cr, ImageSurface img, double x, double y) {
		Glyph g = MainWindow.get_current_glyph ();
		
		double ivz = 1 / g.view_zoom;

		double xc = g.allocation.width / 2.0;
		double yc = g.allocation.height / 2.0;

		cr.save ();

		cr.scale (ivz, ivz);
		
		x = xc + x - (img.get_width () / 2.0) * ivz; 
		y = yc - y - (img.get_height () / 2.0) * ivz;
		
		cr.set_source_surface (img, x * g.view_zoom, y * g.view_zoom);
		cr.paint ();
		
		cr.restore ();		
	}
	
	/** Returns true if there is an edit point at (p.x, p.y). */
	private bool has_edit_point (EditPoint p) {
		foreach (var t in points) {
			if (p.x == t.x && p.y == t.y) {
				return true;
			}
		}
		return false;
	}

	private EditPoint? get_edit_point_at (double x, double y) {
		foreach (var p in points) {
			if (p.x == x && p.y == y) return p;
		}
				
		return null;
	}
		
	private void remove_all_edit_points () {
		while (points.length () > 0) {
			points.remove_link (points.first ());
		}
	}
	
	/** Replace edit points in this path with @param new_path.points. */
	public void replace_path (Path new_path) {
		while (points.length () > 0) {
			points.remove_link (points.first ());
		}
		
		foreach (var np in new_path.points) {
			add (np.x, np.y);
		}
		
		close ();
	}
	
	/** Returns true if this path can be merged with @param union */ 
	private bool is_valid_union (Path union) {
		if (union == this) {
			warning ("Path can't union with it self.");
			return false;
		}
		
		if (union.points.length () <= 2 || points.length () <= 2) {
			return false;
		}

		if (!has_overlapping_boundry (union)) {
			return false;
		}

		return true;
	}
	
	/** Set direction for this path to clockwise for outline and 
	 * counter clockwise for inline paths.
	 */
	public bool force_direction (Direction direction) {
		bool c = (direction == Direction.CLOCKWISE);
		direction_is_set = true;
		
		if (c != is_clockwise ()) {
			this.reverse ();
		}
		
		if (unlikely (is_clockwise () != c)) {
			warning ("Failed to set direction for path in force_direction.");
			return true;
		}
				
		return false;
	}

	/** Switch direction from clockwise path to counter clockwise path or vise versa. */
	public void reverse () {
		bool direction = is_clockwise ();
		
		if (no_derived_direction) {
			clockwise_direction = !clockwise_direction;
		}
		
		reverse_points ();
		
		if (unlikely (direction == is_clockwise ())) {
			stderr.printf ("Error: Direction did not change after reversing path.\n");
			stderr.printf (@"Length: $(points.length ())\n");
			stderr.printf (@"No particular direction can be derived: $no_derived_direction \n");
			warning ("Path.reverse () failed.\n");
		}
	}

	private void reverse_points () {
		points.reverse ();
		
		EditPointHandle t;
		foreach (var e in points) {
			t = e.right_handle;
			e.right_handle = e.left_handle;
			e.left_handle = t;
		}
		
		create_list ();
	}

	public void print_all_points () {
		int i = 0;
		foreach (var p in points) {
			++i;
			string t = (p.type == PointType.END) ? " endpoint" : "";
			stdout.printf (@"Point $i at ($(p.x), $(p.y)) $t \n");
		}
	}
	
	private bool validate_list () {
		unowned List<EditPoint> ep = points.first ();
		
		if (points.length () < 3) {
			warning ("points.length () < 3");
		}
		
		if (!is_open ()) {
			for (int i = 0; i < points.length (); i++) {
				if (ep.data.next == null) {
					return false;
				}

				if (ep.data.prev == null) {
					return false;
				}
								
				ep = (!) ep.data.next;
			}
		}

		if (is_open ()) {
			for (int i = 1; i < points.length () - 1; i++) {
				if (ep.data.next == null) {
					return false;
				}

				if (ep.data.prev == null) {
					return false;
				}
								
				ep = (!) ep.data.next;
			}
			
			if (ep.data.next != null) {
				return false;
			}
		}
		
		return true;
	}
	
	private double clockwise_sum () {
		return_if_fail (points.length () >= 3);
		
		double sum = 0;
		EditPoint prev = points.last ().data;
		foreach (EditPoint e in points) {
			sum += (e.x - prev.x) * (e.y + prev.y);
			prev = e;
		}
		
		return sum;
	}
	
	public bool is_clockwise () {
		double s;
		
		if (unlikely (points.length () <= 2)) {
			no_derived_direction = true;
			return clockwise_direction;
		}
		
		s = clockwise_sum ();
		
		if (s == 0) {
			no_derived_direction = true;
			return clockwise_direction;	
		}
		
		return s > 0;
	}
	
	public bool is_editable () {
		return edit;
	}

	/** Show control points on outline path. */
	public void set_editable (bool e) {
		edit = e;
	}
		
	public bool is_open () {
		return open;
	}
	
	private bool direction_x (EditPoint start, EditPoint stop) {
		return (start.x < stop.x);
	}

	private bool direction_y (EditPoint start, EditPoint stop) {
		return (start.y < stop.y);
	}
	
	public void thinner (double ratio) 
		requires (points.length () > 2)
	{		
		update_region_boundries ();
		
		double w = Math.fabs(xmax - xmin);
		double h = Math.fabs(ymax - ymin);
		double cx = w / 2 + xmin; 
		double cy = h / 2 + ymin;
		
		ratio = 1 - ratio;

		unowned List<EditPoint> i = points.first ();
		unowned List<EditPoint> prev = points.last ();
		
		bool dirx = true;
		bool ldirx = direction_x (prev.data, i.data);
		bool ndirx;

		bool diry = true;
		bool ldiry = direction_y (prev.data, i.data);
		bool ndiry;
		
		while (true) {
			ndirx = direction_x (prev.data, i.data);
			
			if (ndirx != ldirx) {
				dirx = !dirx;
			}
			
			ldirx = ndirx;

			ndiry = direction_y (prev.data, i.data);
			
			if (ndiry != ldiry) {
				diry = !diry;
			}
			
			ldiry = ndiry;
			
			EditPoint p = i.data;
			
			if (dirx) {
				if (p.x < cx)
					p.x += (cx - p.x) * ratio;
				else
					p.x -= (p.x - cx) * ratio;
			} else {
				if (p.x < cx)
					p.x += (cx - p.x) * ratio * 2;
				else
					p.x -= (p.x - cx) * ratio * 2;
			}

			// err borde vara -y
			if (p.y < cy && diry || p.y > cy && ! diry)
				p.y += (cy - p.y) * ratio;
			else
				p.y -= (p.y - cy) * ratio;	
			
			if (i == points.last ()) {
				break;
			}
			
			prev = i;
			i = i.next;
		}
	}
	
	public void resize (double ratio) {
		double h = ymax - ymin;
		double px, py, nx, ny;

		px = xmin;
		py = ymin;
		
		if (ratio < 1 && h < 2) {
			return;
		}
		
		foreach (var p in points) {
			p.x *= ratio;
			p.y *= ratio;
			p.right_handle.length *= ratio;
			p.left_handle.length *= ratio;
		}
		
		xmin *= ratio;	
		xmax *= ratio;
		ymin *= ratio;
		ymax *= ratio;

		nx = xmin;
		ny = ymin;
		
		move (px - nx, py - ny);
	}
	
	public Path copy () {
		Path new_path = new Path ();
		
		foreach (var ep in points) {
			new_path.add_point (ep.copy ());
		}
		
		new_path.edit = edit;
		new_path.open = open;
	
		new_path.create_list ();
		
		return new_path;
	}	
	
	public bool is_over (double x, double y) {
		Glyph g = MainWindow.get_current_glyph ();
		
		x = x * Glyph.ivz () + g.view_offset_x - Glyph.xc ();
		y = y * Glyph.ivz () + g.view_offset_y - Glyph.yc ();

		y *= -1;
		
		return is_over_coordinate (x, y);
	}
	
	public bool is_over_coordinate (double x, double y) {
		return is_over_coordinate_var (x, y, 0.1);
	}
	
	public static double distance (double ax, double bx, double ay, double by) {
		return Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));
	}
	
	/** Estimate length of path. */
	private double get_length () {
		double len = 0;
		
		return_if_fail (points.length () > 2);
		
		EditPoint prev = points.last ().data;
		foreach (EditPoint p in points) {
			len += get_length_from (prev, p);
			prev = p;
		}
		
		return len;
	}
	
	private static double get_length_from (EditPoint a, EditPoint b) {
		double x, y;
		
		x = Math.fabs (a.x - a.get_right_handle ().x ());
		x += Math.fabs (a.get_right_handle ().x () - b.get_left_handle ().x ());
		x += Math.fabs (b.get_left_handle ().x () - b.x);

		y = Math.fabs (a.y - a.get_right_handle ().y ());
		y += Math.fabs (a.get_right_handle ().y () - b.get_left_handle ().y ());
		y += Math.fabs (b.get_left_handle ().y () - b.y);
		
		return Math.fabs (Math.sqrt (x * x + y * y));
	} 
	
	/** Variable precision */
	public bool is_over_coordinate_var (double x, double y, double tolerance) {
		List<EditPoint> ycoordinates = new List<EditPoint> ();
		double last = 0;
		bool on_edge = false;
		double last_x = 0;

		if (points.length () < 3) {
			return false;
		}
		
		if (!is_over_boundry_precision (x, y, tolerance)) {
			return false;
		}

		foreach (EditPoint e in points) {
			if (distance (e.x, x, e.y, y) < tolerance) {
				return true;
			}
		}

		all_of_path ((cx, cy, ct) => {
			double distance = Math.fabs (Math.sqrt (Math.pow (cx - x, 2) + Math.pow (cy - y, 2)));
			
			if (distance < tolerance) {
				on_edge = true;
				return false;
			}
			
			if (Math.fabs (cx - x) < tolerance && Math.fabs (last - cy) > 2 * tolerance) {
				ycoordinates.append (new EditPoint (cx, cy));
				last = cy;
			}
			
			last_x = cx;
			return true;
		});

		if (on_edge) {
			return true;
		}

		if (ycoordinates.length () == 0) {
			warning ("No ycoordinates is empty");
			return true;
		}

		ycoordinates.sort ((a, b) => {
			return (a.y < b.y) ? 1 : -1;
		});
		
		for (int i = 0; i < ycoordinates.length () - 1; i++) {
			if (Math.fabs (ycoordinates.nth (i).data.y - ycoordinates.nth (i + 1).data.y) < 4 * tolerance) {
				ycoordinates.remove_link (ycoordinates.nth (i));
			}
		}
		
		if (unlikely (ycoordinates.length () % 2 != 0)) {
			warning (@"not an even number of coordinates ($(ycoordinates.length ()))");
			stderr.printf (@"(ymin <= y <= ymax) && (xmin <= x <= xmax);\n");
			stderr.printf (@"($ymin <= $y <= $ymax) && ($xmin <= $x <= $xmax);\n");
		
			stderr.printf (@"tolerance: $(tolerance)\n");
			
			stderr.printf ("ycoordinates:\n");
			foreach (EditPoint e in ycoordinates) {
				stderr.printf (@"$(e.y)\n");
			}

			ycoordinates.append (ycoordinates.last ().data.copy ());
			
			return true;
		}
		
		for (unowned List<EditPoint> e = ycoordinates.first (); true; e = e.next) {
			return_val_if_fail (!is_null (e), true);
			
			if (y <= e.data.y + tolerance) {
				return_val_if_fail (!is_null (e.next), true);
				
				e = e.next;

				if (y >= e.data.y - tolerance) {
					return true;
				}
			}
			
			if (e == ycoordinates.last ()) {
				break;
			}
		}

		return false;
	}
	
	public bool is_over_boundry_precision (double x, double y, double p) {
		if (unlikely (ymin == double.MAX || ymin == 10000)) {
			warning (@"no bounding box. length ($(points.length ()))");
			update_region_boundries ();
		}
		
		return (ymin - p <= y <= ymax + p) && (xmin - p <= x <= xmax + p);
	}
	
	public bool is_over_boundry (double x, double y) {
		if (unlikely (ymin == double.MAX)) {
			warning ("bounding box is not calculated, run update_region_boundries first.");
		}

		return (ymin <= y <= ymax) && (xmin <= x <= xmax);
	}

	public bool has_overlapping_boundry (Path p) {
		return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax);
	}
	
	public void add (double x, double y) {
		add_after (x, y, points.last ());
	}

	public void add_point (EditPoint p) {
		add_point_after (p, points.last ());
	}

	/** Insert a new point after @param previous_point and return a reference 
	 * to the new item in list.
	 */
	public unowned List<EditPoint> add_after (double x, double y, List<EditPoint>? previous_point) {
		EditPoint p = new EditPoint (x, y, PointType.LINE);	
		return add_point_after (p, previous_point);
	}
	
	public unowned List<EditPoint> add_point_after (EditPoint p, List<EditPoint>? previous_point) {
		unowned List<EditPoint> np;
		int prev_index;

		if (points.length () > 0 && previous_point != null && ((!)previous_point).data.type == PointType.END) {
			points.delete_link ((!) previous_point);
		}

		if (unlikely (previous_point == null && points.length () != 0)) warning ("previous_point == null");

		if (points.length () == 0) {
			points.append (p);
			np = points.last ();
			p.prev = points.last ();
			p.next = points.last ();
		} else {
			p.prev = (!) previous_point;
			p.next = ((!) previous_point).next;

			points.insert_before (((!) previous_point).next, p);
			
			prev_index = points.position ((!) previous_point);
			np = points.nth (prev_index + 1);
		}
		
		second_last_point = last_point;
		last_point = p;
		
		PenTool.set_default_handle_positions ();
		
		return np;
	}

	public void close () {
		open = false;
		edit = false;

		create_list ();
		
		if (points.length () > 2) {
			points.first ().data.recalculate_linear_handles ();
			points.last ().data.recalculate_linear_handles ();
		}
	}
	
	public void reopen () {
		open = true;
		edit = true;
	}
	
	public EditPoint? get_last_point () {
		return last_point;
	}

	public EditPoint? get_second_last_point () {
		return second_last_point;
	}
	
	/** Move path. */
	public void move (double delta_x, double delta_y) {
		foreach (var ep in points) {
			ep.x += delta_x;
			ep.y += delta_y; // kanske -y
		}
		
		update_region_boundries ();
	}

	public void update_region_boundries () {
		if (points.length () == 0) {
			xmax = 0;
			xmin = 0;
			ymax = 0;
			ymin = 0;			
		}

		// inside and outside in vala lambda functions reveals a tricky problem
		// (look at c code). that's the reason for the !new_val expression

		xmax = -10000;
		xmin = 10000;
		ymax = -10000;
		ymin = 10000;

		double txmax = -10000;
		double txmin = 10000;
		double tymax = -10000;
		double tymin = 10000;

		bool new_val = false;
			
		all_of_path ((cx, cy) => {	
			if (!new_val) {
				txmax = cx;
				txmin = cx;
				tymax = cy;
				tymin = cy;
				new_val = true;
			}
			
			if (cx < txmin) {
				txmin = cx;
			}

			if (cx > txmax) {
				txmax = cx;
			}
			
			if (cy < tymin) {
				tymin = cy;
			}

			if (cy > tymax) {
				tymax = cy;
			}

			return true;
		});
				
		xmax = txmax;
		xmin = txmin;
		ymax = tymax;
		ymin = tymin;

		if (unlikely (!new_val)) {
			// only one point
		} else if (unlikely (!got_region_boundries ())) {
			warning (@"No new region boundries.\nPoints.length: $(points.length ())");
			print_boundries ();
		}
	}
		
	/** Test if @param path is a valid outline for this object. */	
	public bool test_is_outline (Path path) {
		assert (false);
		return this.test_is_outline_of_path (path) && path.test_is_outline_of_path (this);
	}
	
	private bool test_is_outline_of_path (Path outline)
		requires (outline.points.length () >= 2 || points.length () >= 2)
	{	
		// rather slow use it for testing, only
		unowned List<EditPoint> i = outline.points.first ();
		unowned List<EditPoint> prev = i.last ();

		double tolerance = 1;
		bool g = false;
		
		EditPoint ep = new EditPoint (0, 0);
		double min = double.MAX;
		
		while (true) {
			min = 10000;
			 
			all_of (prev.data, i.data, (cx, cy) => {
					get_closest_point_on_path (ep, cx, cy);

					double n = pow (ep.x - cx, 2) + pow (cy - ep.y, 2);
					
					if (n < min) min = n;
					
					if (n < tolerance) {
						g = true;
						return false;
					}

					return true;
				});
			
			if (!g) {
				critical (@"this path does not seem to be the outline. (min $min)");
			}
			
			g = false;
			
			if (i == i.last ()) {
				break;
			}
				
			i = i.next;
		}
		
		return true;
	}

	/** Convert quadratic bezier points to cubic representation of the glyph
	 * suitable for ttf-export.
	 */ 
	public Path get_quadratic_points () {
		EditPoint middle = new EditPoint ();
		unowned List<EditPoint> e;
		EditPointHandle eh;
		
		quadratic_path = copy ();
		quadratic_path.close ();
				
		if (quadratic_path.points.length () < 2) {
			return quadratic_path;
		}

		// split all curves in as many regions as we need
		split_cubic_in_parts (quadratic_path);	

		// estimate quatdratic form
		middle.prev = quadratic_path.points.last ();
		middle.next = quadratic_path.points.first ();

		convert_to_quadratic (quadratic_path, middle);
		
		e = (!) quadratic_path.points.first ();
		while (e != quadratic_path.points.last ().prev) {
			middle.prev = e;
			middle.next = e.next;
							
			convert_to_quadratic (quadratic_path, middle);	
			
			e = (!) e.next;
		}
		
		eh = ((!)quadratic_path.points.last ().prev).data.get_left_handle ();
		eh.set_point_type (PointType.NONE);
		eh.length = 0;

		eh = ((!)quadratic_path.points.last ().prev).data.get_right_handle ();
		eh.set_point_type (PointType.CURVE);
		eh.length *= 1.6;

		return quadratic_path;
	}

	void convert_to_quadratic (Path cubic_path, EditPoint middle) {
		double curve_x, curve_y;	
		EditPointHandle eh;
		
		EditPoint start = middle.get_prev ().data;
		EditPoint stop = middle.get_next ().data;

		estimate_quadratic (start, stop, out curve_x, out curve_y);
		
		eh = start.get_left_handle ();
		eh.set_point_type (PointType.NONE);
		eh.length = 0;
		
		eh = start.get_right_handle ();
		eh.set_point_type (PointType.CURVE);
		eh.move_to_coordinate (curve_x, curve_y);
	}

	public void split_cubic_in_parts (Path cubic_path) {
		int j = 0;
		while (split_all_cubic_in_half (cubic_path)) {
			j++;
			
			if (j > 5) {
				warning ("too many iterations in split path");
				break;
			}
		}	
	}

	public bool split_all_cubic_in_half (Path cubic_path) {
		EditPoint middle = new EditPoint ();
		unowned List<EditPoint> e;
		bool need_split = false;
		uint len = cubic_path.points.length ();
	
		if (len < 2) {
			return false;
		}

		middle.prev = cubic_path.points.last ();
		middle.next = cubic_path.points.first ();
		
		if (split_cubic_in_half (cubic_path, middle)) {
			need_split = true;
		}
			
		e = (!) cubic_path.points.first ();
		for (uint i = 0; i < len - 1; i++) {
			middle.prev = e;
			middle.next = e.next;
			
			middle.get_prev ().data.recalculate_linear_handles ();
			middle.get_next ().data.recalculate_linear_handles ();
			
			if (split_cubic_in_half (cubic_path, middle)) {
				e = (!) e.next.next;
				need_split = true;
			} else {
				e = (!) e.next;
			}
		}
				
		return need_split;	
	}

	public void estimate_quadratic (EditPoint start, EditPoint stop, out double curve_x, out double curve_y) {
		curve_x = -0.25 * start.x + 0.75 *  stop.get_left_handle ().x () + 0.75 * start.get_right_handle ().x () - 0.25 * stop.x;
		curve_y = -0.25 * start.y + 0.75 *  stop.get_left_handle ().y () + 0.75 * start.get_right_handle ().y () - 0.25 * stop.y;		
	}

	bool split_cubic_in_half (Path cubic_path, EditPoint middle) {
		double curve_x, curve_y, cx, cy, qx, qy, nx, ny, distance;	
		
		EditPoint start = middle.get_prev ().data;
		EditPoint stop = middle.get_next ().data;
		
		estimate_quadratic (start, stop, out curve_x, out curve_y);
	
		qx = quadratic_bezier_path (0.75, start.x, curve_x, stop.x);
		qy = quadratic_bezier_path (0.75, start.y, curve_y, stop.y);

		cx = bezier_path (0.75, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);
		cy = bezier_path (0.75, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);

		distance = Math.sqrt (Math.pow (cx - qx, 2) + Math.pow (cy - qy, 2));

		nx = bezier_path (0.5, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);
		ny = bezier_path (0.5, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);

		if (Math.fabs (distance) > 0.01) {
			EditPoint new_edit_point = new EditPoint (nx, ny, PointType.CURVE);

			new_edit_point.next = middle.get_next ();
			new_edit_point.prev = middle.get_prev ();
			cubic_path.insert_new_point_on_path (new_edit_point);
			cubic_path.create_list ();

			return true;	
		}
		
		return false;
	}

	double distance_to_path (EditPoint start, EditPoint stop, double x, double y) {
		double min = double.MAX;
		
		all_of (start, stop, (cx, cy, t) => {
			double n = pow (x - cx, 2) + pow (y - cy, 2);
			
			if (n < min) {
				min = n;
			}
			
			return true;
		});
		
		return min;
	}

	public void insert_new_point_on_path (EditPoint? epp) {
		EditPoint start, stop;
		double x0, x1, y0, y1;
		double px, py;
		
		double position, t, d, min;
		double steps = 500;
		
		EditPoint ep;

		if (epp == null) {
			return;
		}

		ep = (!) epp;

		start = ep.get_prev ().data;
		stop = ep.get_next ().data;

		add_point_after (ep, ep.get_prev ());

		min = double.MAX;

		position = 0.5;

		for (int i = 0; i < steps; i++) {
			t = i / steps;
			
			px = bezier_path (t, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);
			py = bezier_path (t, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);
			
			d = Math.sqrt (Math.pow (ep.x - px, 2) + Math.pow (ep.y - py, 2));
			
			if (d < min) {
				min = d;
				position = t;
			}
		}
			
		bezier_vector (position, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out x0, out x1);
		bezier_vector (position, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out y0, out y1);

		ep.get_left_handle ().set_point_type (PointType.CURVE);
		ep.get_left_handle ().move_to_coordinate (x0, y0);
		ep.get_left_handle ().parent = ep;
		
		ep.get_right_handle ().set_point_type (PointType.CURVE);
		ep.get_right_handle ().move_to_coordinate (x1, y1);
		ep.get_right_handle ().parent = ep;

		stop.get_left_handle ().length *= 1 - position;
		start.get_right_handle ().length *= position;
	}
			
	/** Get a point on the this path closest to x and y coordinates. */
	public void get_closest_point_on_path (EditPoint edit_point, double x, double y) {
		return_if_fail (points.length () != 0);
		
		double min = double.MAX;
		double n = 0;
		bool g = false;
		
		double ox = 0;
		double oy = 0;
		
		double handle_x0, handle_x1;
		double handle_y0, handle_y1;
		
		unowned List<EditPoint> i = points.first ();
		unowned List<EditPoint> prev = i.next;

		bool done = false;
		bool exit = false;
		
		unowned List<EditPoint>? previous_point = null;
		unowned List<EditPoint>? next_point = null;

		EditPoint previous;
		EditPoint next;
		double step = 0;

		if (points.length () == 1) {
			edit_point.x = i.data.x;
			edit_point.y = i.data.y;
			
			edit_point.prev = i;
			edit_point.next = i;
			
			exit = true;
			
			return;
		}
		
		if (points.length () != 1) {
			edit_point.x = i.data.x;
			edit_point.y = i.data.y;
		}
		
		while (!exit) {
			
			if (i == points.last ()) {
				done = true;
			}
			
			if (!done) {
				i = i.next;
				prev = i.prev;
			}	else if (done && !is_open ()) {
				i = points.first ();
				prev = points.last ();
				exit = true;
			} else {
				break;
			}
			
			all_of (prev.data, i.data, (cx, cy, t) => {
				n = pow (x - cx, 2) + pow (y - cy, 2);
				
				if (n < min) {
					min = n;
					
					ox = cx;
					oy = cy;
				
					previous_point = prev;
					next_point = i;
					
					step = t;
					
					g = true;
				}
				
				return true;
			});
		}

		if (previous_point == null) {
			warning (@"previous_point == null, points.length (): $(points.length ())");
			return;
		}
		
		if (next_point == null) {
			warning ("next_point != null");
			return;
		}

		previous = ((!) previous_point).data;
		next = ((!) next_point).data;

		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);

		// position 
		edit_point.set_position (ox, oy);
		edit_point.prev = previous_point;
		edit_point.next = next_point;

		// curve (angle)
		edit_point.get_right_handle ().move_to_coordinate (handle_x0, handle_y0);
		edit_point.get_left_handle ().move_to_coordinate (handle_x1, handle_y1);
		// FIXA: maybe just edit_point.set_tie_handle (true);

		if (unlikely (!g)) {
			warning (@"Error: Got no coordinates for point on path. Num points $(points.length ())\n");
		}
	}

	public static void all_of (EditPoint start, EditPoint stop, RasterIterator iter, int steps = -1) {
		if (steps == -1) {
			steps = (int) (10 * get_length_from (start, stop));
		}
		
		all_of_curve (start.x, start.y, start.get_right_handle ().x (), start.get_right_handle ().y (), stop.get_left_handle ().x (), stop.get_left_handle ().y (), stop.x, stop.y, iter, steps);
	}

	private static void all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, RasterIterator iter, double steps = 400) {
		double px = x1;
		double py = y1;
		
		double t;
		
		for (int i = 0; i < steps; i++) {
			t = i / steps;
			
			px = bezier_path (t, x0, x1, x2, x3);
			py = bezier_path (t, y0, y1, y2, y3);
			
			if (!iter (px, py, t)) {
				return;
			}
			
		}	
	}
	
	void point_on_bezier_path (double t, EditPoint start, EditPoint stop, out double x, out double y) {		
		double x0 = start.x;
		double y0 = start.y; 
		double x1 = start.get_right_handle ().x ();
		double y1 = start.get_right_handle ().y ();
		double x2 = stop.get_left_handle ().x ();
		double y2 = stop.get_left_handle ().y (); 
		double x3 = stop.x;
		double y3 = stop.y;

		x = bezier_path (t, x0, x1, x2, x3);
		y = bezier_path (t, y0, y1, y2, y3);
	}

	private void all_of_path (RasterIterator iter) {
		unowned List<EditPoint> i, next;
		
		if (points.length () < 2) {
			return;
		}

		i = points.first ();
		next = i.next;

		while (i != points.last ()) {
			all_of (i.data, next.data, iter);
			i = i.next;
			next = i.next;
		}
		
		if (!is_open ()) {
			all_of (points.last ().data, points.first ().data, iter);
		}
	}

	public static double bezier_path (double step, double p0, double p1, double p2, double p3) {
		double q0, q1, q2;
		double r0, r1;

		q0 = step * (p1 - p0) + p0;
		q1 = step * (p2 - p1) + p1;
		q2 = step * (p3 - p2) + p2;

		r0 = step * (q1 - q0) + q0;
		r1 = step * (q2 - q1) + q1;

		return step * (r1 - r0) + r0;
	}

	public static void bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) {
		double q0, q1, q2;

		q0 = step * (p1 - p0) + p0;
		q1 = step * (p2 - p1) + p1;
		q2 = step * (p3 - p2) + p2;

		a0 = step * (q1 - q0) + q0;
		a1 = step * (q2 - q1) + q1;
	}

	public static double quadratic_bezier_path (double step, double p0, double p1, double p2) {
		double q0 = step * (p1 - p0) + p0;
		double q1 = step * (p2 - p1) + p1;
		
		return step * (q1 - q0) + q0;
	}
			
	public void plot (Context cr, Allocation allocation, double view_zoom) {
			double px = 0, py = 0;
			double xc = allocation.width / 2.0;
			double yc = allocation.height / 2.0;

			cr.save ();
			
			all_of_path ((x, y) => {
				//cr.set_source_rgba (0.3, 0.3, 0.3, 1);
				cr.move_to (px + xc, -py + yc);
				cr.line_to (x + xc, -y + yc);
				
				px = x;
				py = y;
				
				return true;
			});

			cr.stroke ();
			cr.restore ();
	}
	
	public void print_boundries () {
		stderr.printf (@"xmax $xmax \n");
		stderr.printf (@"xmin $xmin \n");
		stderr.printf (@"ymax $ymax \n");
		stderr.printf (@"ymin $ymin \n");		
	}
	
	public bool got_region_boundries () {
		return !(xmax == -10000 || xmin ==  10000 || ymax == -10000 || ymin ==  10000);
	}
	
	public void create_list () {
		unowned List<EditPoint> ep = points.first ();
		unowned List<EditPoint> prev = ep;
		
		if (points.length () == 0) {
			return;
		}
		
		if (points.length () == 1) {
			ep.data.next = null;
			ep.data.prev = null;
			return;
		}
		
		ep.data.next = ep.next;
		ep.data.prev = points.last ();
		prev = ep;
		
		assert (ep.data.next != null);
		assert (ep.data.prev != null);
		
		while (ep != ep.last ()) {
			ep.data.next = ep.next;
			ep.data.prev = prev;
			prev = ep;
			
			assert (ep.data.next != null);
			assert (ep.data.prev != null);
		
			ep = ep.next;
		}
		
		if (is_open ()) {
			ep.data.next = points.first ();
		} else {
			ep.data.next = points.first ();
			assert (ep.data.next != null);
		}
		
		ep.data.prev = prev;
		assert (ep.data.prev != null);
	}

	public void delete_edit_point (EditPoint ep) 
		requires (points.length () > 0)
	{
			unowned List<EditPoint>? pl = null;
			EditPoint p;
			
			if (ep.prev != null) {
				set_new_start (ep);
			}
			
			p = points.first ().data;
			
			for (uint i = 0; i < points.length (); i++) {
				p = points.nth (i).data;
				
				if (p == ep) {
					p.prev = null;
					p.next = null;
					
					pl = points.nth (i);
					break;
				}
			}

			if (pl != null) {
				points.delete_link ((!) pl);
				reopen ();
				MainWindow.get_current_glyph ().add_active_path (this);
				create_list ();
			}
	}
		
	public void set_new_start (EditPoint ep) {
		List<EditPoint> list = new List<EditPoint> ();
		uint len = points.length ();
		unowned List<EditPoint> iter = points.first ();
		unowned List<EditPoint>? ni = null;
		bool found = false;
		
		foreach (EditPoint it in points) {
			if (it == ep) {
				found = true;
				break;
			}
			
			iter = iter.next;
			ni = (!) iter;
		}
		
		if (unlikely (!found)) {
			warning ("Could not find edit point.");
		}
		
		if (ni == null) {
			return;			
		}
		
		iter = (!) ni;
		
		for (uint i = 0; i < len; i++) {
			list.append (iter.data);
			
			if (iter == iter.last ())
				iter = iter.first ();
			else
				iter = iter.next;
		
		}
		
		while (points.length () > 0) {
			points.remove_link (points.first ());
		}
		
		foreach (EditPoint p in list) {
			points.append (p);
		}
		
	}
	
	public static PathList merge (Path p0, Path p1) {
		bool done;
		PathList path_list;
		
		done = try_merge (p0.copy(), p1.copy(), out path_list);
		
		if (!done) {
			warning ("failed to merge paths");
		}
		
		return path_list;
	}
	
	private static bool try_merge (Path p0, Path p1, out PathList path_list) {
		EditPoint e;
		IntersectionList il;
		int i;
		Path np;
		
		unowned List<Path> pi;
		
		il = IntersectionList.create_intersection_list (p0, p1);
		
		path_list = new PathList ();
		
		if (p0 == p1) {
			return false;
		}
		
		// add editpoints on intersections 
		p0.update_region_boundries ();
		p1.update_region_boundries ();
		foreach (Intersection inter in il.points) {
			e = new EditPoint ();
			p0.get_closest_point_on_path (e, inter.x, inter.y);
			inter.editpoint_a = e;
			
			e = inter.editpoint_a;
			if (!p0.has_edit_point (e)) {
				p0.insert_new_point_on_path (e);
			}

			e = new EditPoint ();
			p1.get_closest_point_on_path (e, inter.x, inter.y);
			inter.editpoint_b = e;
			
			inter.editpoint_b.x = inter.editpoint_a.x;
			inter.editpoint_b.y = inter.editpoint_a.y;
			
			e = inter.editpoint_b;
			if (!p1.has_edit_point (e)) {
				p1.insert_new_point_on_path (e);
			}
		}
			
		if (il.points.length () < 2) {
			return false;
		}
		
		//path_list.paths.append (p0);
		//path_list.paths.append (p1);
		//return false;
		
		// get all parts
		foreach (Intersection inter in il.points) {
			p0.set_new_start (inter.editpoint_a);
			get_merge_part (il, p0, p1, out np);
			path_list.paths.append (np);
		}
		
		foreach (Path pp in path_list.paths) {
			pp.update_region_boundries ();
		}

		// remove duplicate paths
		for (i = 0; i < path_list.paths.length (); i++) {
			pi = path_list.paths.nth (i);

			if (is_duplicated (path_list, pi.data)) {
				path_list.paths.remove_link (pi);
				--i;			
			}
		}
		
		// remove paths contained in other paths
		for (i = 0; i < path_list.paths.length (); i++) {
			pi = path_list.paths.nth (i);
			
			if (pi.data.is_clockwise () && is_clasped (path_list, pi.data)) {
				path_list.paths.remove_link (pi);
				i--;
			}
		}
	
		return true;
	}
	
	private static bool is_duplicated (PathList pl, Path p) {
		bool duplicate = false;
		foreach (Path pd in pl.paths) {
			if (pd == p) {
				continue;
			}
			
			if (is_duplicated_path (p, pd)) {
				duplicate = true;
			}
		}
		
		return duplicate;
	}
	
	private static bool is_duplicated_path (Path p0, Path p1) {
		bool eq;
		
		assert (p1 != p0);	
		
		foreach (EditPoint ep in p0.points) {
			eq = false;
			
			foreach (EditPoint e in p1.points) {
				eq = (Math.fabs (ep.x - e.x) < 0.04 && Math.fabs (ep.y - e.y) < 0.04);
				
				if (eq) {
					break;
				}
			}
			
			if (eq) {
				continue;
			} else {
				return false;
			}
		}
		
		return true;
	}
	
	private static bool is_clasped (PathList pl, Path p) {
		foreach (Path o in pl.paths) {
			if (o == p) {
				continue;
			}
			
			if (is_clasped_path (o, p)) {
				return true;
			}
		}
		
		return false;
	}

	private static bool is_clasped_path (Path outside, Path inside) {
		bool i = true;
		foreach (EditPoint e in inside.points) {
			if (!outside.is_over_coordinate_var (e.x, e.y, 0.5)) { // high tolerance since point may be off curve in both paths
				i = false;
				break;
			}
		}
		return i;
	}
	
	private static bool get_merge_part (IntersectionList il, Path p0, Path p1, out Path new_path) {
		EditPoint ex;
		EditPoint ix;
		EditPoint en;
		uint offset_i = 0;
		uint offset_j;
		uint len_i;
		int i, j;
		uint len_j;
		Path np = new Path ();
		Intersection s = new Intersection (0, 0, 1);
		
		ex = p0.points.last ().data;
		ix = p0.points.last ().data;
		len_i = p0.points.length ();
		
		for (i = 0; i < p0.points.length (); i++) {
			ex = p0.points.nth ((i + offset_i) % len_i).data;

			if (ex == p0.points.first ().data && i != 0) {	
				s = (!) il.get_intersection (ex);	
				break;
			}

			// add new point for path a
			if (np.has_edit_point (ex)) {
				// SPLIT
				warning ("Merged path need split");
				np.close ();
				new_path = np;
				
				return false;
			} else {
				en = ex.copy ();
				np.add_point (en);
				en.recalculate_linear_handles ();
			}
			
			// swap paths
			if (il.has_edit_point (ex)) {
				s = (!) il.get_intersection (ex);
			
				en.type = PointType.CURVE;
				en.right_handle.type = PointType.CURVE;
				en.right_handle.angle  = s.editpoint_b.right_handle.angle;
				en.right_handle.length = s.editpoint_b.right_handle.length;
							
				// read until we find ex
				for (j = 0; j < p1.points.length (); j++) {
					ix = p1.points.nth (j).data;
					
					if (ix == s.editpoint_b) {
						break;
					}
				}
				
				offset_j = j + 1;
				len_j = p1.points.length ();
				for (j = 0; j < p1.points.length (); j++) {
					
					ix = p1.points.nth ((j + offset_j) % len_j).data;
					
					// add
					if (np.has_edit_point (ix)) {
						// SPLIT
						warning ("Merged path need split");
						np.close ();
						new_path = np;
						
						break;
					} else {
						en = ix.copy ();
						np.add_point (en);
						ix.recalculate_linear_handles ();
					}
					
					if (il.has_edit_point (ix)) {
						s = (!) il.get_intersection (ix);
						break;
					}
				}

				en.type = PointType.CURVE;
				en.right_handle.type = PointType.CURVE;
				en.right_handle.angle  = s.editpoint_a.right_handle.angle;
				en.right_handle.length = s.editpoint_a.right_handle.length;
								
				if (j == p0.points.length ()) {
					np.close ();
					new_path = np;
					return true;
				}

				// skip to next intersection
				int k;
				for (k = 0; k < p0.points.length (); k++) {
					ix = p0.points.nth (k).data; 

					if (ix == s.editpoint_a) {
						break;
					}
				}
				
				if (k == p0.points.length ()) {
					new_path = np;
					return true;
				}
				
				offset_i = 0;
				i = k;
			}
		}

		new_path = np;
		
		return true;	
	}

}

public class PathList : GLib.Object {
	public List<Path> paths = new List<Path> ();
	
	public PathList () {
	}
	
	public void clear () {
		while (paths.length () > 0) {
			paths.remove_link (paths.first ());
		}
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112