StrokeTool.vala
/*
Copyright (C) 2014 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 enum LineCap {
BUTT,
SQUARE,
ROUND
}
public class StrokeTool : GLib.Object {
public static double stroke_width = 0;
public static bool add_stroke = false;
public static bool show_stroke_tools = false;
public static bool convert_stroke = false;
public static LineCap line_cap = LineCap.BUTT;
StrokeTask task;
public StrokeTool () {
task = new StrokeTask.none ();
}
public StrokeTool.with_task (StrokeTask cancelable_task) {
task = cancelable_task;
}
/** Create strokes for the selected outlines. */
public void stroke_selected_paths () {
Glyph g = MainWindow.get_current_glyph ();
PathList paths = new PathList ();
convert_stroke = true;
g.store_undo_state ();
foreach (Path p in g.active_paths) {
if (p.stroke > 0) {
paths.append (p.get_completed_stroke ());
}
}
if (paths.paths.size > 0) {
foreach (Path p in g.active_paths) {
g.layers.remove_path (p);
}
g.active_paths.clear ();
foreach (Path np in paths.paths) {
g.add_path (np);
g.active_paths.add (np);
}
PenTool.update_orientation ();
GlyphCanvas.redraw ();
}
PenTool.update_orientation ();
convert_stroke = false;
}
public PathList get_stroke_fast (Path path, double thickness) {
PathList o;
Path stroke;
StrokeTool s = new StrokeTool ();
stroke = path.copy ();
stroke.remove_points_on_points (0.1);
o = s.create_stroke (stroke, thickness);
return o;
}
public PathList get_stroke (Path path, double thickness) {
PathList o, m;
Path stroke;
if (task.is_cancelled ()) {
return new PathList ();
}
stroke = path.copy ();
stroke.remove_points_on_points (0.1);
o = create_stroke (stroke, thickness);
o = get_all_parts (o);
o = remove_intersection_paths (o);
o = merge (o);
m = new PathList ();
foreach (Path p in o.paths) {
m.add (simplify_stroke (p));
}
return m;
}
void reset_flags (PathList o) {
foreach (Path p in o.paths) {
foreach (EditPoint ep in p.points) {
ep.flags &= ~(EditPoint.INTERSECTION
| EditPoint.COPIED
| EditPoint.NEW_CORNER
| EditPoint.SELF_INTERSECTION);
}
p.update_region_boundaries ();
}
}
public void merge_selected_paths () {
Glyph g = MainWindow.get_current_glyph ();
PathList o = new PathList ();
PathList r;
PathList new_paths = new PathList ();
bool error = false;
g.store_undo_state ();
foreach (Path p in g.active_paths) {
if (p.stroke == 0) {
o.add (p);
} else {
o.append (p.get_completed_stroke ());
}
}
foreach (Path p in o.paths) {
p.close ();
remove_single_point_intersections (p);
}
o = remove_overlap (o, out error);
if (error) {
warning ("merge_selected_paths failed.");
return;
}
reset_flags (o);
new_paths.append (o);
for (int merge = 0; merge < 2; merge++) {
for (int i = 0; i < o.paths.size; i++) {
for (int j = 0; j < o.paths.size; j++) {
Path p1, p2;
if (task.is_cancelled ()) {
return;
}
p1 = o.paths.get (i);
p2 = o.paths.get (j);
if (merge == 0) {
if (p1.is_clockwise () == p2.is_clockwise ()) {
continue;
}
}
if (merge == 1) {
if (p1.is_clockwise () != p2.is_clockwise ()) {
continue;
}
}
if (i == j) {
continue;
}
r = merge_selected (p1, p2, false, out error);
if (error) {
warning ("Can't merge selected paths.");
return;
}
remove_merged_curve_parts (r);
if (r.paths.size > 0) {
reset_flags (r);
new_paths.append (r);
new_paths.remove (p1);
new_paths.remove (p2);
o.remove (p1);
o.remove (p2);
o.append (r);
i = 0;
j = 0;
}
}
}
}
if (task.is_cancelled ()) {
return;
}
foreach (Path p in g.active_paths) {
g.delete_path (p);
}
g.clear_active_paths ();
remove_merged_curve_parts (new_paths);
foreach (Path p in new_paths.paths) {
g.add_path (p);
g.add_active_path (null, p);
}
PenTool.update_orientation ();
GlyphCanvas.redraw ();
}
void remove_single_point_intersections (Path p) {
PointSelection ps;
p.remove_points_on_points ();
for (int i = 0; i < p.points.size; i++) {
EditPoint ep = p.points.get (i);
EditPoint next = p.points.get ((i + 1) % p.points.size);
if (fabs (ep.get_right_handle ().angle - ep.get_left_handle ().angle) % (2 * PI) < 0.01) {
if (ep.get_right_handle ().length > 0 && ep.get_left_handle ().length > 0) {
ps = new PointSelection (ep, p);
PenTool.remove_point_simplify (ps);
}
} else if (Path.distance_to_point (ep, next) < 0.01) {
ps = new PointSelection (ep, p);
PenTool.remove_point_simplify (ps);
}
}
}
PathList remove_overlap (PathList pl, out bool error) {
PathList r = new PathList ();
error = false;
foreach (Path p in pl.paths) {
PathList m = merge_selected (p, new Path (), true, out error);
if (error) {
warning ("Can't remove overlap.");
return pl;
}
if (m.paths.size > 0) {
r.append (m);
} else {
r.add (p);
}
}
return r;
}
void remove_merged_curve_parts (PathList r) {
Gee.ArrayList<Path> remove = new Gee.ArrayList<Path> ();
PathList flat = new PathList ();
foreach (Path p in r.paths) {
p.update_region_boundaries ();
flat.add (p.flatten ());
}
foreach (Path p in r.paths) {
PathList pl = get_insides (flat, p);
int counters = 0;
int clockwise = 0;
foreach (Path i in pl.paths) {
if (i.is_clockwise ()) {
clockwise++;
} else {
counters++;
}
}
if (p.is_clockwise ()) {
if (clockwise - 1 > counters) {
remove.add (p);
break;
}
} else {
if (clockwise < counters - 1) {
remove.add (p);
break;
}
}
}
foreach (Path p in remove) {
r.paths.remove (p);
remove_merged_curve_parts (r);
return;
}
}
public PathList merge_selected (Path path1, Path path2,
bool self_intersection, out bool error) {
PathList flat = new PathList ();
PathList o = new PathList ();
PathList pl = new PathList ();
PathList r = new PathList ();
pl.add (path1);
pl.add (path2);
error = false;
if (!self_intersection) {
if (!path1.boundaries_intersecting (path2)) {
return r;
}
}
foreach (Path p in pl.paths) {
if (p.stroke == 0) {
o.add (p);
flat.add (p.copy ().flatten (50));
}
}
flat = merge (flat);
foreach (Path pp in o.paths) {
pp.remove_points_on_points (0.1);
}
bool has_split_point = false;
foreach (Path p in flat.paths) {
foreach (EditPoint ep in p.points) {
if ((ep.flags & EditPoint.SPLIT_POINT) > 0) {
foreach (Path pp in o.paths) {
EditPoint lep = new EditPoint ();
if (pp.points.size > 1) {
pp.get_closest_point_on_path (lep, ep.x, ep.y, null, null);
if (Path.distance_to_point (ep, lep) < 0.1) {
EditPoint lep2 = new EditPoint ();
pp.get_closest_point_on_path (lep2, ep.x, ep.y, lep.prev, lep.next);
if (lep.prev != null) {
lep.get_left_handle ().type = lep.get_prev ().get_right_handle ().type;
} else {
lep.get_left_handle ().type = pp.get_last_point ().get_right_handle ().type;
}
if (lep.next != null) {
lep.get_right_handle ().type = lep.get_next ().get_left_handle ().type;
} else {
lep.get_left_handle ().type = pp.get_first_point ().get_right_handle ().type;
}
if (lep2.prev != null) {
lep2.get_left_handle ().type = lep2.get_prev ().get_right_handle ().type;
} else {
lep2.get_left_handle ().type = pp.get_first_point ().get_right_handle ().type;
}
if (lep2.next != null) {
lep2.get_right_handle ().type = lep2.get_next ().get_left_handle ().type;
} else {
lep2.get_left_handle ().type = pp.get_last_point ().get_right_handle ().type;
}
// self intersection
if (Path.distance_to_point (ep, lep2) < 0.1
&& Path.distance_to_point (ep, lep) < 0.1) {
if (Path.distance_to_point (lep, (!) lep.prev) < 0.001) {
continue;
}
if (Path.distance_to_point (lep, (!) lep.next) < 0.001) {
continue;
}
add_double_point_at_intersection (pp, lep, ep);
add_double_point_at_intersection (pp, lep2, ep);
pp.insert_new_point_on_path (lep);
pp.insert_new_point_on_path (lep2);
lep.flags |= EditPoint.SELF_INTERSECTION;
lep2.flags |= EditPoint.SELF_INTERSECTION;
lep.tie_handles = false;
lep.reflective_point = false;
lep2.tie_handles = false;
lep2.reflective_point = false;
} else {
if (lep.prev != null && Path.distance_to_point (lep, (!) lep.prev) < 0.00000001) {
lep.get_prev ().flags |= EditPoint.INTERSECTION;
lep.get_prev ().tie_handles = false;
lep.get_prev ().reflective_point = false;
continue;
}
if (lep.next != null && Path.distance_to_point (lep, (!) lep.next) < 0.00000001) {
lep.get_next ().flags |= EditPoint.INTERSECTION;
lep.get_next ().tie_handles = false;
lep.get_next ().reflective_point = false;
continue;
}
add_double_point_at_intersection (pp, lep, ep);
pp.insert_new_point_on_path (lep);
lep.flags |= EditPoint.INTERSECTION;
lep.tie_handles = false;
lep.reflective_point = false;
}
has_split_point = true;
}
}
}
}
}
}
if (!has_split_point) {
return r;
}
// remove double intersection points
EditPoint prev = new EditPoint ();
foreach (Path pp in o.paths) {
foreach (EditPoint ep in pp.points) {
if (((prev.flags & EditPoint.SELF_INTERSECTION) > 0 || (prev.flags & EditPoint.INTERSECTION) > 0)
&& ((ep.flags & EditPoint.SELF_INTERSECTION) > 0 || (ep.flags & EditPoint.INTERSECTION) > 0)
&& fabs (ep.x - prev.x) < 0.2
&& fabs (ep.y - prev.y) < 0.2) {
prev.deleted = true;
}
prev = ep;
}
}
foreach (Path pp in o.paths) {
pp.remove_deleted_points ();
}
foreach (Path p in o.paths) {
foreach (EditPoint ep in p.points) {
ep.flags &= ~EditPoint.COPIED;
}
}
return_val_if_fail (o.paths.size == 2, r);
Path p1, p2;
p1 = o.paths.get (0);
p2 = o.paths.get (1);
PathList parts = new PathList ();
if (self_intersection) {
// remove overlap
PathList self_parts;
self_parts = remove_self_intersections (p1, out error);
if (error) {
warning ("remove_self_intersections failed");
return new PathList ();
}
parts.append (self_parts);
} else {
// merge two path
PathList merged_paths = merge_paths_with_curves (p1, p2);
if (merged_paths.paths.size > 0) {
parts.append (merged_paths);
} else {
parts.add (p1);
parts.add (p2);
}
}
foreach (Path p in parts.paths) {
reset_intersections (p);
}
reset_intersections (path1);
reset_intersections (path2);
return parts;
}
/** Add hidden double points to make sure that the path does not
* change when new points are added to a 2x2 path.
*/
void add_double_point_at_intersection (Path pp, EditPoint lep, EditPoint ep) {
EditPoint prev;
EditPoint next;
EditPoint hidden;
double px, py;
if (lep.get_right_handle ().type == PointType.DOUBLE_CURVE
|| lep.get_right_handle ().type == PointType.LINE_DOUBLE_CURVE) {
return_if_fail (lep.prev != null);
return_if_fail (lep.next != null);
prev = lep.get_prev ();
next = lep.get_next ();
hidden = new EditPoint (0, 0, PointType.QUADRATIC);
px = (next.get_left_handle ().x + prev.get_right_handle ().x) / 2.0;
py = (next.get_left_handle ().y + prev.get_right_handle ().y) / 2.0;
hidden.independent_x = px;
hidden.independent_y = py;
hidden.get_right_handle ().x = next.get_left_handle ().x;
hidden.get_right_handle ().y = next.get_left_handle ().y;
hidden.get_left_handle ().x = prev.get_right_handle ().x;
hidden.get_left_handle ().y = prev.get_right_handle ().y;
pp.add_point_after (hidden, prev);
hidden.get_right_handle ().type = PointType.QUADRATIC;
hidden.get_left_handle ().type = PointType.QUADRATIC;
prev.get_right_handle ().type = PointType.QUADRATIC;
next.get_left_handle ().type = PointType.QUADRATIC;
prev.type = PointType.QUADRATIC;
next.type = PointType.QUADRATIC;
pp.get_closest_point_on_path (lep, ep.x, ep.y, null, null);
}
}
PathList remove_self_intersections (Path original, out bool error) {
Path merged = new Path ();
IntersectionList intersections = new IntersectionList ();
EditPoint ep1, ep2, found;
double d;
double min_d;
Path current;
bool found_intersection;
PathList parts;
int i = 0;
Path path = original.copy ();
error = false;
path.remove_points_on_points ();
parts = new PathList ();
if (path.points.size <= 1) {
return parts;
}
// reset copied points
foreach (EditPoint n in path.points) {
n.flags &= ~EditPoint.COPIED;
}
// build list of intersection points
for (i = 0; i < path.points.size; i++) {
ep1 = path.points.get (i);
if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0
&& (ep1.flags & EditPoint.COPIED) == 0) {
ep1.flags |= EditPoint.COPIED;
found = new EditPoint ();
min_d = double.MAX;
found_intersection = false;
for (int j = 0; j < path.points.size; j++) {
ep2 = path.points.get (j);
d = Path.distance_to_point (ep1, ep2);
if ((ep2.flags & EditPoint.COPIED) == 0
&& (ep2.flags & EditPoint.SELF_INTERSECTION) > 0) {
if (d < min_d) {
min_d = d;
found_intersection = true;
found = ep2;
}
}
}
if (!found_intersection) {
warning (@"No self intersection:\n$(ep1)");
return parts;
}
ep1.tie_handles = false;
ep1.reflective_point = false;
found.tie_handles = false;
found.reflective_point = false;
found.flags |= EditPoint.COPIED;
Intersection intersection = new Intersection (ep1, path, found, path);
intersection.self_intersection = true;
intersections.points.add (intersection);
}
}
// reset copy flag
foreach (EditPoint n in path.points) {
n.flags &= ~EditPoint.COPIED;
}
if (intersections.points.size == 0) {
warning ("No intersection points.");
error = true;
return parts;
}
current = path;
current.reverse ();
while (true) {
EditPoint modified;
i = 0;
Intersection new_start = new Intersection.empty ();
ep1 = current.points.get (i);
current = path;
modified = ep1.copy ();
for (i = 0; i < current.points.size; i++) {
ep1 = current.points.get (i);
modified = ep1.copy ();
if ((ep1.flags & EditPoint.COPIED) == 0
&& (ep1.flags & EditPoint.SELF_INTERSECTION) == 0) {
break;
}
}
if (i >= current.points.size || (ep1.flags & EditPoint.COPIED) > 0) {
// all points have been copied
break;
}
while (true) {
if ((ep1.flags & EditPoint.SELF_INTERSECTION) > 0) {
bool other;
EditPointHandle handle;
handle = ep1.get_left_handle ();
new_start = intersections.get_point (ep1, out other);
i = index_of (current, other ? new_start.point : new_start.other_point);
if (!(0 <= i < current.points.size)) {
warning (@"Index out of bounds. ($i)");
return parts;
}
ep1 = current.points.get (i);
modified = ep1.copy ();
modified.left_handle.move_to_coordinate (handle.x, handle.y);
}
if ((ep1.flags & EditPoint.COPIED) > 0) {
merged.close ();
merged.close ();
merged.create_list ();
parts.add (merged);
foreach (EditPoint n in merged.points) {
n.flags &= ~EditPoint.SELF_INTERSECTION;
}
merged.reverse ();
merged = new Path ();
break;
}
// adjust the other handle
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
ep1.left_handle.convert_to_curve ();
ep1.right_handle.convert_to_curve ();
ep1.tie_handles = false;
ep1.reflective_point = false;
}
// add point to path
ep1.flags |= EditPoint.COPIED;
merged.add_point (modified.copy ());
i++;
ep1 = current.points.get (i % current.points.size);
modified = ep1.copy ();
}
ep1.flags |= EditPoint.COPIED;
}
return parts;
}
PathList merge_paths_with_curves (Path path1, Path path2) {
PathList r = new PathList ();
IntersectionList intersections = new IntersectionList ();
EditPoint ep1, ep2, found;
double d;
double min_d;
Path current;
bool found_intersection;
Path flat1, flat2;
if (path1.points.size <= 1 || path2.points.size <= 1) {
return r;
}
flat1 = path1.flatten ();
flat2 = path2.flatten ();
// reset copied points
foreach (EditPoint n in path2.points) {
n.flags &= ~EditPoint.COPIED;
}
// build list of intersection points
for (int i = 0; i < path1.points.size; i++) {
ep1 = path1.points.get (i);
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
found = new EditPoint ();
min_d = double.MAX;
found_intersection = false;
for (int j = 0; j < path2.points.size; j++) {
ep2 = path2.points.get (j);
d = Path.distance_to_point (ep1, ep2);
if ((ep2.flags & EditPoint.COPIED) == 0
&& (ep2.flags & EditPoint.INTERSECTION) > 0) {
if (d < min_d && d < 0.1) {
min_d = d;
found_intersection = true;
found = ep2;
}
}
}
if (!found_intersection) {
warning (@"No intersection for:\n $(ep1)");
continue;
}
found.flags |= EditPoint.COPIED;
ep1.tie_handles = false;
ep1.reflective_point = false;
found.tie_handles = false;
found.reflective_point = false;
Intersection intersection = new Intersection (ep1, path1, found, path2);
intersections.points.add (intersection);
}
}
// reset copy flag
foreach (EditPoint n in path1.points) {
n.flags &= ~EditPoint.COPIED;
}
foreach (EditPoint n in path2.points) {
n.flags &= ~EditPoint.COPIED;
}
if (intersections.points.size == 0) {
warning ("No intersection points.");
return r;
}
Path new_path = new Path ();
current = path1;
while (true) {
// find a beginning of a new part
bool find_parts = false;
Intersection new_start = new Intersection.empty ();
foreach (Intersection inter in intersections.points) {
if (!inter.done && !find_parts) {
find_parts = true;
new_start = inter;
current = new_start.path;
}
}
if (new_path.points.size > 0) {
new_path.close ();
new_path.recalculate_linear_handles ();
new_path.update_region_boundaries ();
r.add (new_path);
}
if (!find_parts) { // no more parts
break;
}
if ((new_start.get_point (current).flags & EditPoint.COPIED) > 0) {
current = new_start.get_other_path (current);
}
int i = index_of (current, new_start.get_point (current));
if (i < 0) {
warning ("i < 0");
return r;
}
EditPoint previous = new EditPoint ();
new_path = new Path ();
ep1 = current.points.get (i);
current = new_start.get_other_path (current); // swap at first iteration
bool first = true;
while (true) {
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
bool other;
previous = ep1;
if (likely (intersections.has_point (ep1))) {
new_start = intersections.get_point (ep1, out other);
current = new_start.get_other_path (current);
i = index_of (current, new_start.get_point (current));
if (!(0 <= i < current.points.size)) {
warning (@"Index out of bounds. ($i)");
return r;
}
ep1 = current.points.get (i);
ep2 = current.points.get ((i + 1) % current.points.size);
double px, py;
Path.get_point_for_step (ep1, ep2, 0.5, out px, out py);
bool inside = (current == path1 && flat2.is_over_coordinate (px, py))
|| (current == path2 && flat1.is_over_coordinate (px, py));
bool other_inside = (current != path1 && flat2.is_over_coordinate (px, py))
|| (current != path2 && flat1.is_over_coordinate (px, py));
if (inside && !other_inside) {
current = new_start.get_other_path (current);
i = index_of (current, new_start.get_point (current));
if (!(0 <= i < current.points.size)) {
warning (@"Index out of bounds. ($i >= $(current.points.size)) ");
return r;
}
new_start.done = true;
ep1 = current.points.get (i);
}
inside = (current == path1 && flat2.is_over_coordinate (px, py))
|| (current == path2 && flat1.is_over_coordinate (px, py));
if (first) {
Path c = new_start.get_other_path (current);
if (c.points.size >= 1) {
previous = c.get_first_point ();
}
first = false;
}
}
}
if ((ep1.flags & EditPoint.COPIED) > 0) {
new_path.close ();
if (new_path.points.size >= 1) {
EditPoint first_point = new_path.get_first_point ();
EditPointHandle h;
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
first_point.left_handle.move_to_coordinate (previous.left_handle.x, previous.left_handle.y);
if (first_point.next != null) {
h = first_point.get_next ().get_left_handle ();
h.process_connected_handle ();
}
}
}
break;
}
// adjust the other handle
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
ep1.left_handle.convert_to_curve ();
ep1.right_handle.convert_to_curve ();
}
// add point to path
ep1.flags |= EditPoint.COPIED;
new_path.add_point (ep1.copy ());
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
new_path.get_last_point ().left_handle.move_to_coordinate (previous.left_handle.x, previous.left_handle.y);
}
i++;
ep1 = current.points.get (i % current.points.size);
}
ep1.flags |= EditPoint.COPIED;
if (!new_start.done) {
new_start.done = (new_start.get_other_point (current).flags & EditPoint.COPIED) > 0;
}
}
return r;
}
Path simplify_stroke (Path p) {
Path simplified = new Path ();
Path segment, added_segment;
EditPoint ep, ep_start, last, first, segment_last;
int start, stop;
int j;
EditPointHandle last_handle;
last_handle = new EditPointHandle.empty ();
segment_last = new EditPoint ();
last = new EditPoint ();
p.remove_points_on_points ();
foreach (EditPoint e in p.points) {
PenTool.convert_point_type (e, PointType.CUBIC);
}
bool has_curve_start = true;
foreach (EditPoint e in p.points) {
e.flags &= ~EditPoint.NEW_CORNER;
if ((e.flags & EditPoint.CURVE) == 0) {
p.set_new_start (e);
has_curve_start = false;
break;
}
}
if (has_curve_start) {
warning ("Curve start");
}
for (int i = 0; i < p.points.size; i++) {
ep = p.points.get (i);
if ((ep.flags & EditPoint.CURVE) > 0) {
start = i;
for (j = start + 1; j < p.points.size; j++) {
ep = p.points.get (j);
if ((ep.flags & EditPoint.CURVE) == 0) {
break;
}
}
if (task.is_cancelled ()) {
return new Path ();
}
stop = j;
start -= 1;
if (start < 0) {
warning ("start < 0");
start = 0;
}
if (stop >= p.points.size) {
warning ("stop >= p.points.size");
stop = p.points.size - 1;
}
ep_start = p.points.get (start);
ep = p.points.get (stop);
double l = Path.distance_to_point (ep_start, ep);
segment = fit_bezier_path (p, start, stop, 0.00001 * l * l);
added_segment = segment.copy ();
if (simplified.points.size > 0) {
last = simplified.get_last_point ();
}
if (added_segment.points.size > 0) {
segment_last = added_segment.get_last_point ();
first = added_segment.get_first_point ();
segment_last.right_handle = ep_start.get_right_handle ().copy ();
if (simplified.points.size > 1) {
last = simplified.delete_last_point ();
}
first.set_tie_handle (false);
last.set_tie_handle (false);
last.get_right_handle ().x = first.get_right_handle ().x;
last.get_right_handle ().y = first.get_right_handle ().y;
first.get_left_handle ().convert_to_curve ();
first.get_left_handle ().x = last.get_left_handle ().x;
first.get_left_handle ().y = last.get_left_handle ().y;
last = added_segment.get_last_point ();
last.right_handle = ep.get_right_handle ().copy ();
added_segment.recalculate_linear_handles_for_point (last);
simplified.append_path (added_segment);
segment_last.right_handle = ep.get_right_handle ().copy ();
if (added_segment.points.size > 0) {
if (ep_start.get_right_handle ().is_line ()) {
first = added_segment.get_first_point ();
simplified.recalculate_linear_handles_for_point (first);
}
}
last_handle = last.get_left_handle ();
} else {
warning ("No points in segment.");
}
i = stop;
} else {
simplified.add_point (ep.copy ());
}
}
simplified.recalculate_linear_handles ();
simplified.close ();
remove_single_point_intersections (simplified);
return simplified;
}
public static Path fit_bezier_path (Path p, int start, int stop, double error) {
int index, size;
Path simplified;
double[] lines;
double[] result;
EditPoint ep;
simplified = new Path ();
return_val_if_fail (0 <= start < p.points.size, simplified);
return_val_if_fail (0 <= stop < p.points.size, simplified);
size = stop - start + 1;
lines = new double[2 * size];
index = 0;
for (int i = start; i <= stop; i++) {
ep = p.points.get (i);
lines[index] = ep.x;
index++;
lines[index] = ep.y;
index++;
}
return_val_if_fail (2 * size == index, new Path ());
Gems.fit_bezier_curve_to_line (lines, error, out result);
return_val_if_fail (!is_null (result), simplified);
for (int i = 0; i + 7 < result.length; i += 8) {
simplified.add_cubic_bezier_points (
result[i], result[i + 1],
result[i + 2], result[i + 3],
result[i + 4], result[i + 5],
result[i + 6], result[i + 7]);
}
return simplified;
}
PathList remove_intersection_paths (PathList pl) {
PathList r = new PathList ();
foreach (Path p in pl.paths) {
if (p.points.size > 7) {
r.add (p);
} else if (!has_new_corner (p)) {
r.add (p);
} else if (counters (pl, p) == 0) {
r.add (p);
}
}
return r;
}
bool has_new_corner (Path p) {
foreach (EditPoint ep in p.points) {
if ((ep.flags & EditPoint.NEW_CORNER) > 0) {
return true;
}
}
return false;
}
void add_line_cap (Path path, Path stroke1, Path stroke2, bool last_cap) {
if (path.line_cap == LineCap.SQUARE) {
add_square_cap (path, stroke1, stroke2, last_cap);
} else if (path.line_cap == LineCap.ROUND) {
add_round_cap (path, stroke1, stroke2, last_cap);
}
}
void add_round_cap (Path path, Path stroke1, Path stroke2, bool last_cap) {
double px, py;
double step, start_angle, stop_angle;
double radius;
EditPoint n, nstart, nend;
Path cap = new Path ();
EditPoint start, end;
EditPointHandle last_handle;
EditPoint first, last;
stroke1.remove_points_on_points ();
stroke2.remove_points_on_points ();
last_handle = path.get_first_point ().get_right_handle ();
start = stroke1.get_last_point ();
end = stroke2.get_first_point ();
start_angle = last_handle.angle + PI / 2;
stop_angle = start_angle + PI;
nstart = cap.add (start.x, start.y);
radius = Path.distance_to_point (start, end) / 2;
step = PI / 5;
for (int j = 0; j < 5; j++) {
double angle = start_angle + step * j;
px = radius * cos (angle) + last_handle.parent.x;
py = radius * sin (angle) + last_handle.parent.y;
n = cap.add (px, py);
n.type = PointType.LINE_CUBIC;
n.get_right_handle ().type = PointType.LINE_CUBIC;
n.get_left_handle ().type = PointType.LINE_CUBIC;
}
nend = cap.add (end.x, end.y);
for (int i = 0; i < cap.points.size; i++) {
cap.recalculate_linear_handles_for_point (cap.points.get (i));
}
int size = cap.points.size;
for (int i = 1; i < size; i++) {
n = cap.points.get (i);
n.convert_to_curve ();
n.set_tie_handle (true);
n.process_tied_handle ();
}
int f = stroke1.points.size - 1;
for (int i = 2; i < cap.points.size - 1; i++) {
n = cap.points.get (i).copy ();
stroke1.add_point (n);
}
cap.remove_points_on_points ();
return_if_fail (0 < f < stroke1.points.size);
first = stroke1.points.get (f);
last = stroke1.get_last_point ();
last.convert_to_curve ();
last = stroke1.add_point (stroke2.get_first_point ());
stroke2.delete_first_point ();
last.convert_to_line ();
stroke1.recalculate_linear_handles_for_point (last);
last.next = stroke1.add_point (stroke2.get_first_point ()).get_link_item ();
stroke2.delete_first_point ();
last.get_left_handle ().convert_to_curve ();
last.get_left_handle ().angle = last.get_right_handle ().angle + PI;
last.flags = EditPoint.CURVE_KEEP;
double a;
double l;
return_if_fail (cap.points.size > 1);
a = (first.get_left_handle ().angle + PI) % (2 * PI);
l = cap.points.get (1).get_right_handle ().length;
first.get_right_handle ().convert_to_curve ();
first.get_right_handle ().angle = a;
first.get_right_handle ().length = l;
a = (first.get_left_handle ().angle + PI) % (2 * PI);
last.get_left_handle ().convert_to_curve ();
last.get_left_handle ().angle = a;
last.get_left_handle ().length = l;
}
void add_square_cap (Path path, Path stroke1, Path stroke2, bool last_cap) {
EditPointHandle last_handle;
EditPoint start;
EditPoint end;
EditPoint n;
double x, y;
double stroke_width = path.stroke / 2;
last_handle = path.get_first_point ().get_right_handle ();
start = stroke1.get_last_point ();
end = stroke2.get_first_point ();
y = sin (last_handle.angle - PI) * stroke_width;
x = cos (last_handle.angle - PI) * stroke_width;
n = stroke1.add (start.x + x, start.y + y);
n.type = PointType.CUBIC;
n.get_right_handle ().type = PointType.CUBIC;
n.get_left_handle ().type = PointType.CUBIC;
n.convert_to_line ();
n = stroke1.add (end.x + x, end.y + y);
n.type = PointType.CUBIC;
n.get_right_handle ().type = PointType.CUBIC;
n.get_left_handle ().type = PointType.CUBIC;
n.convert_to_line ();
}
/** Create one stroke from the outline and counter stroke and close the
* open endings.
*
* @param path the path to create stroke for
* @param stroke for the outline of path
* @param stroke for the counter path
*/
Path merge_strokes (Path path, Path stroke, Path counter) {
Path merged;
EditPoint last_counter, first;
merged = stroke.copy ();
merged.reverse ();
last_counter = new EditPoint ();
first = new EditPoint ();
add_line_cap (path, merged, counter, true);
path.reverse ();
add_line_cap (path, counter, merged, true);
path.reverse ();
merged.append_path (counter);
merged.close ();
merged.create_list ();
merged.recalculate_linear_handles ();
return merged;
}
public static void move_segment (EditPoint stroke_start, EditPoint stroke_stop, double thickness) {
EditPointHandle r, l;
double m, n;
double qx, qy;
stroke_start.set_tie_handle (false);
stroke_stop.set_tie_handle (false);
r = stroke_start.get_right_handle ();
l = stroke_stop.get_left_handle ();
m = cos (r.angle + PI / 2) * thickness;
n = sin (r.angle + PI / 2) * thickness;
stroke_start.get_right_handle ().move_to_coordinate_delta (m, n);
stroke_start.get_left_handle ().move_to_coordinate_delta (m, n);
stroke_start.independent_x += m;
stroke_start.independent_y += n;
qx = cos (l.angle - PI / 2) * thickness;
qy = sin (l.angle - PI / 2) * thickness;
stroke_stop.get_right_handle ().move_to_coordinate_delta (qx, qy);
stroke_stop.get_left_handle ().move_to_coordinate_delta (qx, qy);
stroke_stop.independent_x += qx;
stroke_stop.independent_y += qy;
}
void add_corner (Path stroked, EditPoint previous, EditPoint next,
EditPoint original, double stroke_width) {
double ratio;
double distance;
EditPoint corner;
double corner_x, corner_y;
EditPointHandle previous_handle;
EditPointHandle next_handle;
EditPoint cutoff1, cutoff2;
double adjusted_stroke = (stroke_width * 0.999999) / 2.0;
bool d1, d2;
previous_handle = previous.get_left_handle ();
next_handle = next.get_right_handle ();
previous_handle.convert_to_line ();
next_handle.convert_to_line ();
previous_handle.angle += PI;
next_handle.angle += PI;
Path.find_intersection_handle (previous_handle, next_handle, out corner_x, out corner_y);
corner = new EditPoint (corner_x, corner_y, PointType.CUBIC);
corner.convert_to_line ();
previous_handle.angle -= PI;
next_handle.angle -= PI;
distance = Path.distance_to_point (corner, original);
ratio = 1.5 * fabs (adjusted_stroke) / distance;
double or = original.get_right_handle ().angle;
double ol = original.get_left_handle ().angle;
if (previous.prev == null) { // FIXME: first point
warning ("Point before corner.");
d1 = false;
d2 = false;
} else {
d1 = corner.x - previous.x >= 0 == previous.x - previous.get_prev ().x >= 0;
d2 = corner.y - previous.y >= 0 == previous.y - previous.get_prev ().y >= 0;
}
if (ratio > 1) {
if (!d1 && !d2) {
return;
} else {
stroked.add_point (corner);
}
} else {
cutoff1 = new EditPoint ();
cutoff1.set_point_type (previous.type);
cutoff1.convert_to_line ();
cutoff2 = new EditPoint ();
cutoff2.set_point_type (previous.type);
cutoff2.convert_to_line ();
if (fabs (or - ol) < 0.001) {
cutoff1.x = previous.x + 1.5 * fabs (adjusted_stroke) * -cos (or);
cutoff1.y = previous.y + 1.5 * fabs (adjusted_stroke) * -sin (or);
cutoff2.x = next.x + 1.5 * fabs (adjusted_stroke) * -cos (or);
cutoff2.y = next.y + 1.5 * fabs (adjusted_stroke) * -sin (or);
} else {
cutoff1.x = previous.x + (corner.x - previous.x) * ratio;
cutoff1.y = previous.y + (corner.y - previous.y) * ratio;
cutoff2.x = next.x + (corner.x - next.x) * ratio;
cutoff2.y = next.y + (corner.y - next.y) * ratio;
}
if (!cutoff1.is_valid () || cutoff2.is_valid ()) {
cutoff1 = stroked.add_point (cutoff1);
cutoff2 = stroked.add_point (cutoff2);
}
stroked.recalculate_linear_handles_for_point (cutoff1);
stroked.recalculate_linear_handles_for_point (cutoff2);
// self intersection
if (!d1 && !d2) {
cutoff1.deleted = true;
cutoff2.deleted = true;
stroked.remove_deleted_points ();
return;
}
if (distance > 4 * stroke_width) {
previous.flags = EditPoint.NONE;
next.flags = EditPoint.NONE;
} else {
previous.flags |= EditPoint.NEW_CORNER;
next.flags |= EditPoint.NEW_CORNER;
}
}
}
PathList get_parts (Path path) {
PathList intersections;
PathList r;
r = get_parts_self (path);
intersections = new PathList ();
foreach (Path p in r.paths) {
intersections.add (p);
}
return intersections;
}
/*
bool split_corner (PathList pl) {
EditPoint p1, p2;
EditPoint a1, a2;
PathList result;
bool split;
foreach (Path p in pl.paths) {
if (p.points.size == 0) {
continue;
}
for (int index = 1; index < p.points.size + 2; index++) {
p1 = p.points.get ((index - 1) % p.points.size);
p2 = p.points.get (index % p.points.size);
a1 = p.points.get ((index + 3) % p.points.size); // two points ahead
a2 = p.points.get ((index + 4) % p.points.size);
if ((p1.flags & EditPoint.STROKE_OFFSET) > 0
|| (p2.flags & EditPoint.STROKE_OFFSET) > 0
|| (a1.flags & EditPoint.STROKE_OFFSET) > 0
|| (a2.flags & EditPoint.STROKE_OFFSET) > 0) {
split = split_segment (p, a1, a2, p1, p2, out result);
if (split) {
pl.append (result);
pl.paths.remove (p);
split_corner (pl);
return true;
} else {
p1 = p.points.get ((index - 1) % p.points.size);
p2 = p.points.get (index % p.points.size);
a1 = p.points.get ((index + 2) % p.points.size); // one point ahead
a2 = p.points.get ((index + 3) % p.points.size);
split = split_segment (p, a1, a2, p1, p2, out result);
if (split) {
pl.append (result);
pl.paths.remove (p);
split_corner (pl);
return true;
} else {
p1 = p.points.get ((index - 1) % p.points.size);
p2 = p.points.get (index % p.points.size);
a1 = p.points.get ((index + 3) % p.points.size); // two points ahead
a2 = p.points.get ((index + 4) % p.points.size);
if ((p1.flags & EditPoint.STROKE_OFFSET) > 0
&& (a1.flags & EditPoint.STROKE_OFFSET) > 0) {
p1.flags = EditPoint.COUNTER_TO_OUTLINE;
a1.flags = EditPoint.COUNTER_TO_OUTLINE;
}
}
}
}
}
}
return false;
}
*/
/*
bool split_segment (Path p, EditPoint first, EditPoint next, EditPoint p1, EditPoint p2, out PathList result) {
double ix, iy;
bool intersection;
int i;
result = new PathList ();
intersection = segments_intersects (first, next, p1, p2, out ix, out iy, true);
if (intersection) {
add_intersection (p, first, next, ix, iy);
add_intersection (p, p1, p2, ix, iy);
i = mark_intersection_as_deleted (p);
return_val_if_fail (i == 2, false);
result.append (get_remaining_points (p.copy ()));
}
return intersection;
}
*/
PathList get_parts_self (Path path, PathList? paths = null) {
PathList pl;
PathList r;
if (task.is_cancelled ()) {
return new PathList ();
}
r = paths == null ? new PathList () : (!) paths;
pl = split (path);
foreach (Path part in pl.paths) {
if (!has_self_intersection (part)) {
r.add (part);
} else {
get_parts_self (part, r);
}
}
if (r.paths.size == 0) {
warning ("No parts in path");
}
return r;
}
bool has_intersection_points (Path path) {
foreach (EditPoint p in path.points) {
if ((p.flags & EditPoint.INTERSECTION) > 0) {
return true;
}
}
return false;
}
/** Split one path at intersection points in two parts. */
PathList split (Path path) {
Path new_path;
PathList pl;
int i;
if (!has_intersection_points (path)) {
add_self_intersection_points (path);
} else {
warning ("points already created.");
}
foreach (EditPoint p in path.points) {
if (insides (p, path) == 1) {
path.set_new_start (p);
path.close ();
break;
}
}
i = mark_intersection_as_deleted (path);
if (!(i == 0 || i == 2)) {
warning (@"Split should only create two parts, $i points will be deleted.");
}
new_path = path.copy ();
pl = get_remaining_points (new_path);
return pl;
}
PathList process_deleted_control_points (Path path) {
PathList paths, nl, pl, rl, result;
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);
}
}
result = new PathList ();
foreach (Path p in rl.paths) {
pl = process_deleted_control_points (p);
if (pl.paths.size > 0) {
result.append (pl);
} else {
result.add (p);
}
}
for (int i = 1; i < result.paths.size; i++) {
result.paths.get (i).reverse ();
}
paths.append (result);
return paths;
}
PathList get_remaining_points (Path old_path) {
PathList pl;
old_path.close ();
pl = process_deleted_control_points (old_path);
if (pl.paths.size == 0) {
pl.add (old_path);
}
foreach (Path pn in pl.paths) {
pn.close ();
}
return pl;
}
bool has_self_intersection (Path path) {
bool intersection = false;
path.all_segments ((ep1, ep2) => {
double ix, iy;
EditPoint p1, p2;
if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true)) {
intersection = true;
return false;
}
return true;
});
return intersection;
}
bool add_self_intersection_points (Path path, bool only_offsets = false) {
bool intersection = false;
path.all_segments ((ep1, ep2) => {
double ix, iy;
EditPoint p1, p2;
if (segment_intersects (path, ep1, ep2, out ix, out iy, out p1, out p2, true, only_offsets)) {
add_intersection (path, ep1, ep2, ix, iy);
add_intersection (path, p1, p2, ix, iy);
intersection = true;
return false;
}
return true;
});
return intersection;
}
EditPoint 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 ();
double d;
if (next == path.get_first_point ()) {
ep1.prev = null;
} else {
ep1.prev = prev;
}
ep1.prev = prev;
ep1.next = ep2;
ep1.flags |= EditPoint.NEW_CORNER | EditPoint.SPLIT_POINT;
ep1.type = prev.type;
ep1.x = px;
ep1.y = py;
ep1.color = c;
n.add (ep1);
ep2.prev = ep1;
ep2.next = ep3;
ep2.flags |= EditPoint.INTERSECTION | EditPoint.SPLIT_POINT;
ep2.type = prev.type;
ep2.x = px;
ep2.y = py;
ep2.color = c;
n.add (ep2);
ep3.prev = ep2;
ep3.next = next;
ep3.flags |= EditPoint.NEW_CORNER | EditPoint.SPLIT_POINT;
ep3.type = prev.type;
ep3.x = px;
ep3.y = py;
ep3.color = c;
n.add (ep3);
next.get_left_handle ().convert_to_line ();
foreach (EditPoint np in n) {
np = path.add_point_after (np, np.prev);
path.create_list ();
}
PenTool.convert_point_to_line (ep1, true);
PenTool.convert_point_to_line (ep2, true);
PenTool.convert_point_to_line (ep3, true);
path.recalculate_linear_handles_for_point (ep1);
path.recalculate_linear_handles_for_point (ep2);
path.recalculate_linear_handles_for_point (ep3);
d = Path.distance_to_point (prev, next);
prev.get_right_handle ().length *= Path.distance_to_point (prev, ep1) / d;
next.get_left_handle ().length *= Path.distance_to_point (ep3, next) / d;
path.recalculate_linear_handles_for_point (next);
return ep2;
}
bool segments_intersects (EditPoint p1, EditPoint p2, EditPoint ep, EditPoint next,
out double ix, out double iy,
bool skip_points_on_points = false) {
double cross_x, cross_y;
ix = 0;
iy = 0;
if (is_line (ep.x, ep.y, p1.x, p1.y, next.x, next.y)) {
ix = p1.x;
iy = p1.y;
return true;
}
if (is_line (ep.x, ep.y, p2.x, p2.y, next.x, next.y)) {
ix = p2.x;
iy = p2.y;
return true;
}
if (is_line (p1.x, p1.y, ep.x, ep.y, p2.x, p2.y)) {
ix = ep.x;
iy = ep.y;
return true;
}
if (is_line (p1.x, p1.y, next.x, next.y, p2.x, p2.y)) {
ix = next.x;
iy = next.y;
return true;
}
Path.find_intersection_point (ep, next, p1, p2, out cross_x, out cross_y);
if (fmin (ep.x, next.x) - 0.00001 <= cross_x <= fmax (ep.x, next.x) + 0.00001
&& fmin (ep.y, next.y) - 0.00001 <= cross_y <= fmax (ep.y, next.y) + 0.00001) {
// iterate to find intersection.
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)) {
ix = cross_x;
iy = cross_y;
return true;
}
}
return false;
}
bool segment_intersects (Path path, EditPoint ep, EditPoint next,
out double ix, out double iy,
out EditPoint ia, out EditPoint ib,
bool skip_points_on_points = false,
bool only_offsets = false) {
EditPoint p1, p2;
bool intersection, inter;
double iix, iiy;
double distance, min_distance;
intersection = false;
ix = 0;
iy = 0;
iix = 0;
iiy = 0;
ia = new EditPoint ();
ib = new EditPoint ();
if (path.points.size == 0) {
return false;
}
min_distance = double.MAX;
p1 = path.points.get (path.points.size - 1);
for (int i = 0; i < path.points.size; i++) {
p2 = path.points.get (i);
bool is_offset = ((p1.flags & EditPoint.STROKE_OFFSET) > 0)
&& ((p2.flags & EditPoint.STROKE_OFFSET) > 0)
&& ((ep.flags & EditPoint.STROKE_OFFSET) > 0)
&& ((next.flags & EditPoint.STROKE_OFFSET) > 0);
if (!only_offsets || is_offset) {
if (p1 != ep && p2 != next) {
inter = segments_intersects (p1, p2, ep, next, out iix, out iiy,
skip_points_on_points);
if (inter) {
distance = Path.distance (ep.x, iix, ep.y, iiy);
if (distance < min_distance) {
ia = p1;
ib = p2;
ix = iix;
iy = iiy;
intersection = true;
min_distance = distance;
}
}
}
}
p1 = p2;
}
return intersection;
}
/** @return true if p2 is on the line p1 to p3 */
bool is_line (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.01) {
return fmin (x1, x3) - 0.00001 <= x2 && x2 <= fmax (x1, x3) + 0.00001
&& fmin (y1, y3) - 0.00001 <= y2 && y2 <= fmax (y1, y3) + 0.00001
&& is_flat (x1, y1, x2, y2, x3, y3, tolerance);
}
public static bool is_flat (double x1, double y1, double x2, double y2, double x3, double y3, double tolerance = 0.001) {
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)) / ds;
double y = fabs ((y3 - y1) * p - (y2 - y1)) / ds;
double d = fabs (ds - (d1 + d2)) / ds;
return ds > 0.001 && d1 > 0.001 && d2 > 0.001
&& d < tolerance && x < tolerance && y < tolerance;
}
// indside becomes outside in some paths
/*
void remove_points_in_stroke (PathList pl) {
PathList r;
foreach (Path p in pl.paths) {
if (remove_points_in_stroke_for_path (p, pl, out r)) {
pl.append (r);
remove_points_in_stroke (pl);
return;
}
}
}
*/
/*
bool remove_points_in_stroke_for_path (Path p, PathList pl, out PathList result) {
EditPoint start_ep;
EditPoint start_next;
EditPoint start_prev;
EditPoint end_ep = new EditPoint ();
EditPoint end_next;
EditPoint end_prev;
result = new PathList ();
for (int i = 1; i < p.points.size + 1; i++) {
start_prev = p.points.get ((i - 1) % p.points.size);
start_ep = p.points.get (i % p.points.size);
start_next = p.points.get ((i + 1) % p.points.size);
if ((start_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) {
for (int j = i; j < p.points.size + i; j++) {
end_prev = p.points.get ((j - 1) % p.points.size);
end_ep = p.points.get (j % p.points.size);
end_next = p.points.get ((j + 1) % p.points.size);
if ((end_ep.flags & EditPoint.COUNTER_TO_OUTLINE) > 0) {
start_ep.flags = EditPoint.NONE;
end_ep.flags = EditPoint.NONE;
if (merge_segments (pl, p, start_prev, start_ep, end_ep, end_next, out result)) {
return true;
}
}
}
}
start_ep.flags = EditPoint.NONE;
end_ep.flags = EditPoint.NONE;
}
return false;
}
*/
/*
bool merge_segments (PathList pl,
Path path1, EditPoint start1, EditPoint stop1,
EditPoint start2, EditPoint stop2,
out PathList result) {
result = new PathList ();
PathList r1;
PathList r2;
foreach (Path path2 in pl.paths) {
if (path2 != path1) {
reset_intersections (path1);
reset_intersections (path2);
if (add_merge_intersection_point (path1, path2, start1, stop1)) {
if (add_merge_intersection_point (path1, path2, start2, stop2)) {
r1 = get_remaining_points (path1.copy ());
r2 = get_remaining_points (path2.copy ());
if (r1.paths.size != 2) {
warning (@"Expecting two paths in r1 found $(r1.paths.size)\n");
reset_intersections (path1);
reset_intersections (path2);
return true;
}
if (r2.paths.size != 2) {
warning (@"Expecting two paths in r2 found $(r2.paths.size)\n");
reset_intersections (path1);
reset_intersections (path2);
return true;
}
pl.paths.remove (path1);
pl.paths.remove (path2);
double d1 = Path.point_distance (r1.paths.get (0).get_first_point (),
r2.paths.get (0).get_first_point ());
double d2 = Path.point_distance (r1.paths.get (0).get_first_point (),
r2.paths.get (1).get_first_point ());
Path m1, m2;
if (d1 > d2) {
m1 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (0));
m2 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (1));
} else {
m1 = PenTool.merge_open_paths (r1.paths.get (1), r2.paths.get (0));
m2 = PenTool.merge_open_paths (r1.paths.get (0), r2.paths.get (1));
}
result.add (m1);
result.add (m2);
return true;
} else {
reset_intersections (path1);
reset_intersections (path2);
}
} else {
reset_intersections (path1);
reset_intersections (path2);
}
}
}
return false;
}
*/
void reset_intersections (Path p) {
foreach (EditPoint ep in p.points) {
ep.flags &= ~EditPoint.INTERSECTION;
ep.flags &= ~EditPoint.COPIED;
ep.flags &= ~EditPoint.SELF_INTERSECTION;
ep.deleted = false;
}
p.remove_points_on_points ();
}
/*
bool add_merge_intersection_point (Path path1, Path path2, EditPoint first, EditPoint next) {
double ix, iy;
bool intersection;
intersection = false;
ix = 0;
iy = 0;
path2.all_segments ((p1, p2) => {
int i;
intersection = segments_intersects (first, next, p1, p2, out ix, out iy);
if (intersection) {
add_intersection (path1, first, next, ix, iy);
add_intersection (path2, p1, p2, ix, iy);
i = mark_intersection_as_deleted (path1);
i = mark_intersection_as_deleted (path2);
}
return !intersection;
});
return intersection;
}
*/
bool is_inside_of_path (PointSelection ps, PathList pl, out Path outline) {
outline = new Path ();
foreach (Path p in pl.paths) {
if (p != ps.path) {
if (is_inside (ps.point, p)) {
outline = p;
return true;
}
}
}
return false;
}
PathList get_all_parts (PathList pl) {
PathList m;
bool intersects = false;
PathList r = new PathList ();
foreach (Path p in pl.paths) {
if (has_self_intersection (p)) {
m = get_parts (p);
r.append (m);
intersects = true;
} else {
r.add (p);
}
}
foreach (Path p in r.paths) {
p.close ();
p.update_region_boundaries ();
}
if (intersects) {
return get_all_parts (r);
}
return r;
}
void remove_single_points (PathList pl) {
PathList r = new PathList ();
foreach (Path p in pl.paths) {
p.update_region_boundaries ();
if (p.points.size < 10
|| p.xmax - p.xmin < 0.01
|| p.ymax - p.ymin < 0.01) {
r.add (p);
}
}
foreach (Path p in r.paths) {
pl.remove (p);
}
}
public PathList merge (PathList pl) {
bool error = false;
PathList m;
PathList r = pl;
Path p1, p2;
r = get_all_parts (r);
remove_single_points (r);
while (paths_has_intersection (r, out p1, out p2)) {
if (task.is_cancelled ()) {
return new PathList ();
}
if (merge_path (p1, p2, out m, out error)) {
r.paths.remove (p1);
r.paths.remove (p2);
foreach (Path np in m.paths) {
np.remove_points_on_points ();
r.add (np);
}
if (task.is_cancelled ()) {
return new PathList ();
}
r = get_all_parts (r);
remove_single_points (r);
if (paths_has_intersection (m, out p1, out p2)) {
warning ("Paths are not merged.");
error = true;
}
} else {
warning ("Not merged.");
error = true;
}
if (error) {
warning ("Merge error");
break;
}
}
if (!error) {
remove_merged_parts (r);
}
foreach (Path p in r.paths) {
p.close ();
p.recalculate_linear_handles ();
}
if (task.is_cancelled ()) {
return new PathList ();
}
return r;
}
void remove_merged_parts (PathList r) {
Gee.ArrayList<Path> remove = new Gee.ArrayList<Path> ();
int c;
foreach (Path p in r.paths) {
p.update_region_boundaries ();
}
foreach (Path p in r.paths) {
c = counters (r, p);
if (c % 2 == 0) {
if (!p.is_clockwise ()) {
remove.add (p);
}
} else {
if (p.is_clockwise ()) {
remove.add (p);
}
}
}
foreach (Path p in remove) {
r.paths.remove (p);
}
}
public PathList get_insides (PathList pl, Path path) {
bool inside = false;
PathList insides = new PathList ();
foreach (Path p in pl.paths) {
if (p.points.size > 1
&& p != path
&& path.boundaries_intersecting (p)) {
inside = true;
foreach (EditPoint ep in path.points) {
if (!is_inside (ep, p)) {
inside = false;
break;
}
}
if (inside) {
insides.add (p); // add the flat inside to the list
}
}
}
return insides;
}
public int counters (PathList pl, Path path) {
int inside_count = 0;
bool inside;
foreach (Path p in pl.paths) {
inside = true;
if (p.points.size > 1
&& p != path
&& path.boundaries_intersecting (p)) {
foreach (EditPoint ep in path.points) {
if (!is_inside (ep, p)) {
inside = false;
break;
}
}
if (inside) {
inside_count++;
}
}
}
return inside_count;
}
public static bool is_inside (EditPoint point, Path path) {
EditPoint prev;
bool inside = false;
if (path.points.size <= 1) {
return false;
}
prev = path.points.get (path.points.size - 1);
foreach (EditPoint p in path.points) {
if ((fabs (p.x - point.x) < 0.1 && fabs (p.y - point.y) < 0.1)
|| (fabs (prev.x - point.x) < 0.1 && fabs (prev.y - point.y) < 0.1)) {
return true;
} else if ((p.y > point.y) != (prev.y > point.y)
&& point.x < (prev.x - p.x) * (point.y - p.y) / (prev.y - p.y) + p.x) {
inside = !inside;
}
prev = p;
}
return inside;
}
public int insides (EditPoint point, Path path) {
EditPoint prev;
int inside = 0;
if (path.points.size <= 1) {
return 0;
}
prev = path.get_last_point ();
foreach (EditPoint start in path.points) {
if (start.x == point.x && point.y == start.y) {
inside++;
} else if ((start.y > point.y) != (prev.y > point.y)
&& point.x < (prev.x - start.x) * (point.y - start.y) / (prev.y - start.y) + start.x) {
inside++;
}
prev = start;
}
return inside;
}
bool merge_path (Path path1, Path path2, out PathList merged_paths, out bool error) {
IntersectionList intersections;
EditPoint ep1, next, p1, p2, pp1, pp2;
Path path, other, merged;
PathList r, other_paths, result;
bool intersects;
int s, i;
double ix, iy, iix, iiy;
bool merge = false;
EditPoint intersection_point, other_intersection_point;
bool path1_direction, path2_direction;
error = false;
merged_paths = new PathList ();
intersections = new IntersectionList ();
if (path1.points.size == 0) {
return false;
}
if (path2.points.size == 0) {
return false;
}
reset_intersections (path1);
reset_intersections (path2);
iix = 0;
iiy = 0;
result = new PathList ();
if (path1.points.size == 0 || path2.points.size == 0) {
warning ("No points in path.");
error = true;
return false;
}
if (!path1.boundaries_intersecting (path2)) {
return false;
}
Path original_path1 = path1.copy ();
Path original_path2 = path2.copy ();
s = 0;
foreach (EditPoint e in original_path1.points) {
if (!is_inside (e, original_path2)
&& insides (e, original_path1) == 1) { // FIXME: later as well
break;
}
s++;
}
if (s >= path1.points.size) {
Path t;
t = original_path1;
original_path1 = original_path2;
original_path2 = t;
s = 0;
foreach (EditPoint e in original_path1.points) {
if (!is_inside (e, original_path2)) {
break;
}
s++;
}
}
if (s >= original_path1.points.size) {
warning ("No start point found.");
error = true;
return false;
}
path = original_path1;
other = original_path2;
other_paths = new PathList ();
r = new PathList ();
other_paths.add (path);
other_paths.add (other);
intersects = false;
p1 = new EditPoint ();
p2 = new EditPoint ();
pp1 = new EditPoint ();
pp2 = new EditPoint ();
ix = 0;
iy = 0;
i = s;
merged = new Path ();
path1_direction = is_clockwise (original_path1);
path2_direction = is_clockwise (original_path1);
while (true) {
ep1 = path.points.get (i % path.points.size);
next = path.points.get ((i + 1) % path.points.size);
if ((ep1.flags & EditPoint.COPIED) > 0) {
if (merge) {
merged.close ();
result.add (merged.copy ());
}
merged = new Path ();
bool find_parts = false;
Intersection new_start = new Intersection.empty ();
foreach (Intersection inter in intersections.points) {
if (!inter.done && !find_parts) {
find_parts = true;
new_start = inter;
break;
}
}
if (task.is_cancelled ()) {
return false;
}
if (!find_parts) {
break; // done, no more parts to merge
} else {
// set start point for next part
path = new_start.other_path;
if (path.points.size == 0) {
warning ("No points.");
error = true;
return false;
}
i = index_of (path, new_start.get_point (path));
if (i < 0) {
warning ("Start point not found.");
error = true;
return false;
}
ep1 = path.points.get (i % path.points.size);
next = path.points.get ((i + 1) % path.points.size);
if ((ep1.flags & EditPoint.INTERSECTION) == 0) {
warning ("Not starting on an intersection point.");
error = true;
return false;
}
new_start.done = true;
}
}
intersects = false;
double dm;
double d;
if ((ep1.flags & EditPoint.INTERSECTION) > 0) {
Intersection current_intersection;
bool continue_on_other_path;
current_intersection = intersections.get_point (ep1, out continue_on_other_path);
current_intersection.done = true;
// take the other part of an intersection
if ((next.flags & EditPoint.COPIED) != 0) {
bool next_is_intersection = false;
bool next_continue_on_other_path;
next_is_intersection = ((next.flags & EditPoint.INTERSECTION) > 0);
if (next_is_intersection) {
Intersection next_intersection = intersections.get_point (next, out next_continue_on_other_path);
ep1.flags |= EditPoint.COPIED;
merged.add_point (ep1.copy ());
if (!next_intersection.done) {
EditPoint new_start_point;
path = next_continue_on_other_path
? next_intersection.other_path : next_intersection.path;
new_start_point = next_continue_on_other_path
? next_intersection.other_point : next_intersection.point;
i = index_of (path, new_start_point);
if (i < 0) {
warning ("Point not found in path.\n");
error = true;
return false;
}
ep1 = path.points.get (i % path.points.size);
next = path.points.get ((i + 1) % path.points.size);
} else {
warning ("Part is already created.\n");
error = true;
return false;
}
} else {
ep1.flags |= EditPoint.COPIED;
merged.add_point (ep1.copy ());
EditPoint new_start_point;
if ((next.flags & EditPoint.COPIED) > 0) {
path = current_intersection.get_other_path (path);
new_start_point = current_intersection.get_point (path);
i = index_of (path, new_start_point);
if (i < 0) {
warning ("Point not found in path.");
error = true;
return false;
}
ep1 = path.points.get (i % path.points.size);
next = path.points.get ((i + 1) % path.points.size);
if ((next.flags & EditPoint.INTERSECTION) > 0) {
warning ("Wrong type.");
error = true;
return false;
}
if ((next.flags & EditPoint.COPIED) > 0) {
merged.add_point (ep1.copy ());
continue;
}
} else {
ep1.flags |= EditPoint.COPIED;
merged.add_point (ep1.copy ());
}
}
} else {
ep1.flags |= EditPoint.COPIED;
if (path1_direction == path2_direction) {
if (!is_inside (ep1, original_path1)) {
merged.add_point (ep1.copy ());
}
} else {
merged.add_point (ep1.copy ());
}
}
current_intersection.done = true;
} else {
// find new intersection
dm = double.MAX;
foreach (Path o in other_paths.paths) {
bool inter = segment_intersects (o, ep1, next, out iix, out iiy,
out pp1, out pp2, false, false);
d = Path.distance (ep1.x, iix, ep1.y, iiy);
if (d < dm && inter) {
other = o;
dm = d;
intersects = true;
p1 = pp1;
p2 = pp2;
ix = iix;
iy = iiy;
}
if (d < 0.0001) {
intersects = false;
}
}
if (intersects) {
merged.add_point (ep1.copy ());
ep1.flags |= EditPoint.COPIED;
intersection_point = add_intersection (path, ep1, next, ix, iy);
other_intersection_point = add_intersection (other, p1, p2, ix, iy);
bool g = false;
foreach (Intersection old_intersection in intersections.points) {
if (old_intersection.point == intersection_point
|| old_intersection.other_point == other_intersection_point) {
old_intersection.done = true;
g = true;
}
}
if (!g) {
Intersection ip = new Intersection (intersection_point, path, other_intersection_point, other);
intersections.points.add (ip);
}
merged.add_point (intersection_point.copy ());
intersection_point.flags |= EditPoint.COPIED;
i = index_of (other, other_intersection_point);
if (i < 0) {
warning (@"Point not found ($i).");
break;
}
path = other;
merge = true;
} else {
ep1.flags |= EditPoint.COPIED;
merged.add_point (ep1.copy ());
PointSelection ps;
Path outline;
// remove points inside of path
if (is_clockwise (original_path2)) {
ps = new PointSelection (ep1, merged);
if (is_inside_of_path (ps, result, out outline)) {
ep1.deleted = true;
}
}
}
}
i++;
}
if (merge) {
original_path1.remove_points_on_points ();
original_path2.remove_points_on_points ();
original_path1.remove_deleted_points ();
original_path2.remove_deleted_points ();
foreach (Path np in result.paths) {
Path p = np.copy ();
bool has_direction = true;
p.remove_points_on_points ();
if (has_direction) {
p.close ();
reset_intersections (p);
merged_paths.append (get_parts (p));
p.update_region_boundaries ();
p.recalculate_linear_handles ();
}
}
}
return merge && !error;
}
int index_of (Path p, EditPoint ep) {
int i = 0;
foreach (EditPoint e in p.points) {
if (e == ep) {
return i;
}
i++;
}
return -1;
}
public int counters_in_point_in_path (Path p, EditPoint ep) {
int inside_count = 0;
bool inside;
if (p.points.size > 1) {
inside = true;
if (!is_inside (ep, p)) {
inside = false;
}
if (inside) {
inside_count++;
}
}
return inside_count;
}
int mark_intersection_as_deleted (Path path) {
int i = 0;
foreach (EditPoint p in path.points) {
if ((p.flags & EditPoint.INTERSECTION) > 0) {
p.deleted = true;
i++;
}
}
return i;
}
/** @param n number of interrsections to find per path. */
bool has_intersection (Path path1, Path path2) {
bool intersection = false;
if (!path1.boundaries_intersecting (path2)) {
return false;
}
path1.all_segments ((ep1, ep2) => {
double ix, iy;
EditPoint p1, p2;
bool i;
i = segment_intersects (path2, ep1, ep2, out ix, out iy,
out p1, out p2, true);
if (i) {
intersection = true;
}
return !intersection;
});
return intersection;
}
bool paths_has_intersection (PathList r, out Path path1, out Path path2) {
int i, j;
path1 = new Path ();
path2 = new Path ();
i = 0;
foreach (Path p1 in r.paths) {
j = 0;
foreach (Path p2 in r.paths) {
if (p1 != p2) {
if (has_intersection (p1, p2)) {
path1 = p1;
path2 = p2;
return true;
}
}
j++;
}
i++;
}
return false;
}
public bool has_points_outside (PathList pl, Path p) {
if (pl.paths.size != 2) {
warning ("Stroke should only create two parts.");
return false;
}
foreach (Path path in pl.paths) {
if (path != p) {
foreach (EditPoint ep in p.points) {
if (!is_inside (ep, path)) {
return true;
}
}
}
}
return false;
}
bool is_clockwise (Path p) {
double sum = 0;
EditPoint p1, p2;
EditPointHandle l, r;
p.recalculate_linear_handles ();
if (p.points.size < 3) {
return true;
}
for (int i = 0; i < p.points.size; i++) {
p1 = p.points.get (i);
p2 = p.points.get ((i + 1) % p.points.size);
l = p1.get_left_handle ();
r = p1.get_right_handle ();
if (!(fabs (l.angle - r.angle) < 0.0001 && l.length > 0.01 && r.length > 0.01)) {
sum += (p2.x - p1.x) * (p2.y + p1.y);
}
}
return sum > 0;
}
public PathList create_stroke (Path original_path, double thickness) {
PathList pl;
EditPoint p1, p2, p3;
EditPoint previous, previous_inside, start, start_inside;
Path side1, side2;
double x, y, x2, y2, x3, y3;
int size;
bool flat, f_next, f_bigger;
int i;
double tolerance;
double step_increment;
double step_size;
EditPoint corner1, corner1_inside;
double step;
double min_increment;
EditPointHandle l, r;
Path path = original_path.copy ();
int keep;
bool on_curve;
pl = new PathList ();
size = path.is_open () ? path.points.size - 1 : path.points.size;
side1 = new Path ();
side2 = new Path ();
foreach (EditPoint ph in path.points) {
if (ph.type == PointType.HIDDEN) {
ph.type = PointType.CUBIC;
}
}
path.remove_deleted_points ();
if (path.points.size < 2) {
return pl;
}
previous = new EditPoint ();
previous_inside = new EditPoint ();
corner1 = new EditPoint ();
corner1_inside = new EditPoint ();
if (path.is_open ()) {
p1 = path.points.get (0);
p2 = path.points.get (1 % path.points.size);
get_segment (thickness, 0, 0.00001, p1, p2, out start);
get_segment (-thickness, 0, 0.00001, p1, p2, out start_inside);
previous = start.copy ();
previous_inside = start_inside.copy ();
previous.flags |= EditPoint.CURVE_KEEP;
previous_inside.flags |= EditPoint.CURVE_KEEP;
side1.add_point (previous);
side2.add_point (previous_inside);
}
min_increment = 0.02; // 0.013
for (i = 0; i < size; i++) {
p1 = path.points.get (i % path.points.size);
p2 = path.points.get ((i + 1) % path.points.size);
p3 = path.points.get ((i + 2) % path.points.size);
if (unlikely (task.is_cancelled ())) {
return new PathList ();
}
tolerance = 0.01;
step_increment = 1.05;
step_size = 0.039;
corner1 = new EditPoint ();
if (p1.type == PointType.HIDDEN
|| p2.type == PointType.HIDDEN) {
continue;
}
get_segment (thickness, 0, 0.00001, p1, p2, out start);
get_segment (-thickness, 0, 0.00001, p1, p2, out start_inside);
previous = start.copy ();
previous_inside = start_inside.copy ();
previous.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END;
previous_inside.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END;
side1.add_point (previous);
side2.add_point (previous_inside);
step = step_size;
keep = 0;
step_size = 0.05;
while (step < 1 - 2 * step_size) {
Path.get_point_for_step (p1, p2, step, out x, out y);
Path.get_point_for_step (p1, p2, step + step_size, out x2, out y2);
Path.get_point_for_step (p1, p2, step + 2 * step_size, out x3, out y3);
flat = is_flat (x, y, x2, y2, x3, y3, tolerance);
Path.get_point_for_step (p1, p2, step, out x, out y);
Path.get_point_for_step (p1, p2, step + step_size / step_increment, out x2, out y2);
Path.get_point_for_step (p1, p2, step + 2 * step_size / step_increment, out x3, out y3);
f_next = is_flat (x, y, x2, y2, x3, y3, tolerance);
Path.get_point_for_step (p1, p2, step, out x, out y);
Path.get_point_for_step (p1, p2, step + step_size * step_increment, out x2, out y2);
Path.get_point_for_step (p1, p2, step + 2 * step_size * step_increment, out x3, out y3);
f_bigger = is_flat (x, y, x2, y2, x3, y3, tolerance);
if (!flat && !f_next && step_size > min_increment) {
step_size /= step_increment;
continue;
}
if (flat && f_bigger && step_size < 0.1) {
step_size *= step_increment;
continue;
}
get_segment (thickness, step, step_size, p1, p2, out corner1);
get_segment (-thickness, step, step_size, p1, p2, out corner1_inside);
previous.get_right_handle ().length *= step_size;
corner1.get_left_handle ().length *= step_size;
previous_inside.get_right_handle ().length *= step_size;
corner1_inside.get_left_handle ().length *= step_size;
previous = corner1.copy ();
previous_inside = corner1_inside.copy ();
if (keep == 0 && step > 0.3) { // keep two points per segment
on_curve = true;
keep++;
} else if (keep == 1 && step > 0.6) {
on_curve = true;
keep++;
} else {
on_curve = false;
}
if (!on_curve) {
previous.flags |= EditPoint.CURVE;
previous_inside.flags |= EditPoint.CURVE;
} else {
previous.flags |= EditPoint.CURVE_KEEP;
previous_inside.flags |= EditPoint.CURVE_KEEP;
}
side1.add_point (previous);
side2.add_point (previous_inside);
step += step_size;
}
previous.get_right_handle ().length *= step_size;
corner1.get_left_handle ().length *= step_size;
previous_inside.get_right_handle ().length *= step_size;
corner1_inside.get_left_handle ().length *= step_size;
get_segment (thickness, 1 - 0.00001, 0.00001, p1, p2, out corner1);
get_segment (-thickness, 1 - 0.00001, 0.00001, p1, p2, out corner1_inside);
previous = corner1.copy ();
previous_inside = corner1_inside.copy ();
previous.get_right_handle ().length *= step_size;
previous.get_left_handle ().length *= step_size;
previous_inside.get_right_handle ().length *= step_size;
previous_inside.get_left_handle ().length *= step_size;
previous.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END;
previous_inside.flags |= EditPoint.CURVE | EditPoint.SEGMENT_END;
side1.add_point (previous);
side2.add_point (previous_inside);
l = p2.get_left_handle ();
r = p2.get_right_handle ();
if (fabs ((l.angle + r.angle + PI) % (2 * PI) - PI) > 0.005) {
if (!path.is_open () || i < size - 1) {
get_segment (thickness, 0, 0.00001, p2, p3, out start);
add_corner (side1, previous, start, p2.copy (), thickness);
get_segment (-thickness, 0, 0.00001, p2, p3, out start);
add_corner (side2, previous_inside, start, p2.copy (), thickness);
}
}
}
side1.remove_points_on_points ();
side2.remove_points_on_points ();
convert_to_curve (side1);
convert_to_curve (side2);
side2.reverse ();
pl = merge_stroke_parts (path, side1, side2);
return pl;
}
void convert_to_curve (Path path) {
if (path.is_open ()) {
path.get_first_point ().flags &= ~EditPoint.CURVE;
path.get_last_point ().flags &= ~EditPoint.CURVE;
}
path.recalculate_linear_handles ();
foreach (EditPoint ep in path.points) {
if ((ep.flags & EditPoint.SEGMENT_END) == 0) {
if ((ep.flags & EditPoint.CURVE) > 0 || (ep.flags & EditPoint.CURVE_KEEP) > 0) {
ep.convert_to_curve ();
}
}
}
if (task.is_cancelled ()) {
return;
}
foreach (EditPoint ep in path.points) {
if ((ep.flags & EditPoint.SEGMENT_END) == 0) {
if ((ep.flags & EditPoint.CURVE) > 0 || (ep.flags & EditPoint.CURVE_KEEP) > 0) {
ep.set_tie_handle (true);
}
}
}
if (task.is_cancelled ()) {
return;
}
foreach (EditPoint ep in path.points) {
if ((ep.flags & EditPoint.SEGMENT_END) == 0) {
if ((ep.flags & EditPoint.CURVE) > 0 || (ep.flags & EditPoint.CURVE_KEEP) > 0) {
ep.process_tied_handle ();
}
}
}
}
public void get_segment (double stroke_thickness, double step, double step_size,
EditPoint p1, EditPoint p2, out EditPoint ep1) {
double thickness = stroke_thickness / 2;
Path overlay;
double x, y, x2, y2, x3, y3;
EditPoint corner1, corner2, corner3;
PointType type;
Path.get_point_for_step (p1, p2, step, out x, out y);
Path.get_point_for_step (p1, p2, step + step_size, out x2, out y2);
Path.get_point_for_step (p1, p2, step + 2 * step_size, out x3, out y3);
overlay = new Path ();
type = p1.get_right_handle ().type;
corner1 = new EditPoint (x, y, type);
corner2 = new EditPoint (x2, y2, type);
corner3 = new EditPoint (x3, y3, type);
corner2.convert_to_line ();
overlay.add_point (corner1);
overlay.add_point (corner2);
overlay.add_point (corner3);
overlay.close ();
overlay.recalculate_linear_handles ();
move_segment (corner1, corner2, thickness);
ep1 = corner2;
}
public PathList merge_stroke_parts (Path p, Path side1, Path side2) {
Path merged = new Path ();
PathList paths = new PathList ();
if (!p.is_open ()) {
side1.update_region_boundaries ();
paths.add (side1);
side2.update_region_boundaries ();
paths.add (side2);
side1.close ();
side2.close ();
} else if (p.is_open ()) {
side2.reverse ();
merged = merge_strokes (p, side1, side2);
merged.close ();
merged.update_region_boundaries ();
paths.add (merged);
merged.reverse ();
} else {
warning ("Can not create stroke.");
paths.add (p);
}
return paths;
}
public static Path change_weight_fast (Path path, double weight, bool counter) {
StrokeTool tool = new StrokeTool ();
PathList pl;
pl = tool.get_stroke_fast (path, fabs(weight));
return_val_if_fail (pl.paths.size == 2, new Path ());
if (counter == !pl.paths.get (0).is_clockwise ()) {
return pl.paths.get (0);
}
return pl.paths.get (1);
}
public static Path change_weight (Path path, bool counter, double weight) {
StrokeTool tool = new StrokeTool ();
Path o = path.copy ();
Path interpolated = new Path();
o.force_direction (Direction.CLOCKWISE);
double default_weight = 5;
PathList pl = tool.get_stroke (o, default_weight);
Gee.ArrayList<PointSelection> deleted;
deleted = new Gee.ArrayList<PointSelection> ();
return_val_if_fail (pl.paths.size > 0, new Path ());
if (weight < 0) {
counter = !counter;
}
foreach (Path sp in pl.paths) {
if (sp.points.size > interpolated.points.size
&& counter == !sp.is_clockwise ()) {
interpolated = sp;
}
}
return interpolated;
}
}
}