Commit 4f7c8c8
Stroke This commit adds code for generating a stroke from a path.
Changed files (9)
modifiedlibbirdfont/BirdFontFile.valamodifiedlibbirdfont/DrawingTools.valamodifiedlibbirdfont/EditPoint.valamodifiedlibbirdfont/EditPointHandle.valamodifiedlibbirdfont/FileTab.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/Path.valaaddedlibbirdfont/StrokeTool.valamodifiedscripts/bavala.py
Diff
@@ -69,7 +69,6 @@ class BirdFontFile { private bool load_xml (TextReader tr) { bool ok = true;- int parsed; Xml.Node* root; tr.read ();@@ -69,6 +69,9 @@ public class DrawingTools : ToolCollection { Tool resize_tool = new ResizeTool ("resize"); draw_tools.add_tool (resize_tool);++ Tool stroke_tool = new StrokeTool ("stroke");+ draw_tools.add_tool (stroke_tool); // quadratic Bézier points Tool quadratic_points = new Tool ("quadratic_points", t_("Create quadratic Bézier curves"));@@ -71,6 +71,41 @@ public class EditPoint { } } + public double get_corner_angle () {+ double l, r, a;+ + l = get_left_handle ().angle;+ r = get_right_handle ().angle;+ a = fabs (l - r);++ if (l > r) {+ return 2 * PI - a;+ }+ + return a;+ }++ public double get_angle () {+ double a = fabs ((right_handle.angle - PI) - left_handle.angle);+ while (a >= 2 * PI - 0.01) {+ a -= 2 * PI;+ }+ + return fabs (a);+ }++ public bool is_corner () {+ return !(get_angle () <= 0.01);+ }+ + public bool is_narrow () {+ return !(get_angle () < PI / 2 - 0.01);+ }++ public bool is_wide () {+ return !(get_angle () > PI / 2 - 0.01);+ }+ public bool equals (EditPoint e) { return e.x == x && e.y == y @@ -216,6 +251,18 @@ public class EditPoint { } } + public bool is_clockwise () {+ return get_direction () >= 0;+ }+ + public double get_direction () {+ if (is_null (prev)) {+ return 0;+ }+ + return (x - ((!) prev).data.x) * (y + ((!) prev).data.y);+ }+ public void set_tie_handle (bool t) { tie_handles = t; }@@ -93,7 +93,7 @@ public class EditPointHandle { return sin (angle) * length + parent.y; } - void print_position () {+ internal void print_position () { warning (@"\nEdit point handle at position $(px ()),$(py ()) is not valid.\n" + @"Type: $(parent.type), " + @"Index: $(parent.get_index ()) of $(parent.get_list ().length ())\n"@@ -235,7 +235,6 @@ public class FileTab : FontDisplay { } private void draw_file_row (WidgetAllocation allocation, Context cr, Font font, bool color, double y) {- File thumbnail; string fn = (!) font.font_file; fn = fn.substring (fn.replace ("\\", "/").last_index_of ("/") + 1);@@ -1321,6 +1321,7 @@ public class Glyph : FontDisplay { public void draw_path (Context cr) { double left, baseline;+ Path stroke; baseline = get_line ("baseline").pos; left = get_line ("left").pos;@@ -1355,6 +1356,13 @@ public class Glyph : FontDisplay { } } }+ + // draw stroke+ foreach (unowned Path p in path_list) {+ stroke = StrokeTool.get_stroke (p, 3);+ stroke.close ();+ stroke.draw_outline (cr, allocation, view_zoom);+ } } private void draw_zoom_area(Context cr) {@@ -42,7 +42,16 @@ public class Path { 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+ // Iterate over each pixel in a path+ public delegate bool RasterIterator (double x, double y, double step);+ + // Iterate over all points in a rasterized path and compute a vector + // for each point+ public delegate bool VectorIterator (EditPoint start, EditPoint stop, + double x, double y, double hx0, double hx1, double dy0, double dy1, + double step);+ + public delegate bool SegmentIterator (EditPoint start, EditPoint stop); private static ImageSurface? edit_point_image = null; private static ImageSurface? active_edit_point_image = null;@@ -531,13 +540,12 @@ public class Path { } private double clockwise_sum () {+ double sum = 0;+ return_val_if_fail (points.length () >= 3, 0); - double sum = 0;- EditPoint prev = points.last ().data; foreach (EditPoint e in points) {- sum += (e.x - prev.x) * (e.y + prev.y);- prev = e;+ sum += e.get_direction (); } return sum;@@ -1471,6 +1479,12 @@ public class Path { } } + public static void get_point_for_step (EditPoint start, EditPoint stop, double step, out double x, out double y) {+ // FIXME: Types+ x = bezier_path (step, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+ y = bezier_path (step, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y); + }+ private static void all_of_double (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;@@ -1549,7 +1563,7 @@ public class Path { } } - private void all_of_path (RasterIterator iter) {+ private void all_segments (SegmentIterator iter) { unowned List<EditPoint> i, next; if (points.length () < 2) {@@ -1560,16 +1574,49 @@ public class Path { next = i.next; while (i != points.last ()) {- all_of (i.data, next.data, iter);+ iter (i.data, next.data); i = i.next; next = i.next; } if (!is_open ()) {- all_of (points.last ().data, points.first ().data, iter);- }+ iter (points.last ().data, points.first ().data);+ } } + private void all_of_path (RasterIterator iter, int steps = -1) {+ all_segments ((start, stop) => {+ all_of (start, stop, iter, steps);+ return true;+ });+ }++ public void all_vectors (VectorIterator iter, int steps = -1) {+ all_segments ((start, stop) => {+ all_of (start, stop, (x, y, s) => {+ double handle_x0, handle_x1, handle_y0, handle_y1;+ + handle_x0 = 0;+ handle_x1 = 0;+ handle_y0 = 0;+ handle_y1 = 0;+ + //FIXME:+ //double_bezier_vector (s, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out handle_x0, out handle_x1);+ //double_bezier_vector (s, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out handle_y0, out handle_y1);++ print (@"s: $s\n");++ bezier_vector (s, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x, out handle_x0, out handle_x1);+ bezier_vector (s, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y, out handle_y0, out handle_y1);+ + iter (start, stop, x, y, handle_x0, handle_x1, handle_y0, handle_y1, s);+ return true;+ }, steps);+ return true;+ }); + }+ public static double bezier_path (double step, double p0, double p1, double p2, double p3) { double q0, q1, q2; double r0, r1;@@ -1616,7 +1663,7 @@ public class Path { return quadratic_bezier_path (2 * (step - 0.5), middle, p2, p3); } - private void double_bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) {+ public static void double_bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) { double b0, b1, c0, c1, d0, d1; // set angle@@ -1633,7 +1680,7 @@ public class Path { a0 = d0; a1 = d1; }- + public void plot (Context cr, WidgetAllocation allocation, double view_zoom) { double px = 0, py = 0; double xc = allocation.width / 2.0;new file mode 100644@@ -0,0 +1,312 @@+/*+ Copyright (C) 2014 Johan Mattsson++ This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version.++ This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.+*/++using Cairo;+using Math;++namespace BirdFont {++public class StrokeTool : Tool {+ + public StrokeTool (string tool_tip) {+ select_action.connect((self) => {+ stroke_selected_paths ();+ });+ }++ void stroke_selected_paths () {+ Glyph g = MainWindow.get_current_glyph ();+ List<Path> paths = new List<Path> ();+ + foreach (Path p in g.path_list) {+ paths.append (get_stroke (p, 4));+ paths.append (get_stroke (p, -4));+ }+ + foreach (Path np in paths) {+ g.add_path (np);+ }+ }+ + public static Path get_stroke (Path p, double thickness) {+ Path new_path = new Path ();+ Path stroked = new Path ();+ + bool clockwise = p.is_clockwise ();+ + p.create_list ();+ p.all_vectors ((start, stop, px, py, handle_x0, handle_x1, handle_y0, handle_y1, position) => {+ EditPoint ep;+ PointType right, left;+ + ep = new EditPoint (px, py);+ + right = start.get_right_handle ().type;+ left = stop.get_right_handle ().type;+ + if (right == PointType.LINE_QUADRATIC && left == PointType.LINE_QUADRATIC) {+ ep.get_right_handle ().set_point_type (PointType.LINE_QUADRATIC);+ ep.get_left_handle ().set_point_type (PointType.LINE_QUADRATIC);+ ep.type = PointType.QUADRATIC;+ } else if (right == PointType.LINE_CUBIC && left == PointType.LINE_CUBIC) {+ ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC);+ ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC);+ ep.type = PointType.LINE_CUBIC;+ } else if (right == PointType.LINE_DOUBLE_CURVE && left == PointType.LINE_DOUBLE_CURVE) {+ ep.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE);+ ep.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE);+ ep.type = PointType.DOUBLE_CURVE;+ + if (position == 0) {+ ep.left_handle = start.left_handle.copy ();+ ep.right_handle = start.right_handle.copy ();+ + ep.get_left_handle ().length *= 0.833;+ ep.get_right_handle ().length *= 0.833;++ } else {+ ep.get_left_handle ().set_point_type (PointType.CUBIC);+ ep.get_right_handle ().set_point_type (PointType.CUBIC);+ + ep.get_left_handle ().parent = ep;+ ep.get_right_handle ().parent = ep;+ + ep.get_left_handle ().move_to_coordinate (handle_x0, handle_y0);+ ep.get_right_handle ().move_to_coordinate (handle_x1, handle_y1);+ }+ + } else if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) {+ ep.get_left_handle ().move_to_coordinate (handle_x1, handle_y1);+ ep.get_right_handle ().move_to_coordinate (handle_x0, handle_y0);++ ep.get_left_handle ().set_point_type (PointType.DOUBLE_CURVE); + ep.get_right_handle ().set_point_type (PointType.DOUBLE_CURVE);+ + ep.type = PointType.DOUBLE_CURVE;+ } else if (right == PointType.QUADRATIC) { + ep.get_right_handle ().move_to_coordinate (handle_x0, handle_y0);+ + ep.get_left_handle ().set_point_type (PointType.QUADRATIC); + ep.get_right_handle ().set_point_type (PointType.QUADRATIC);+ + ep.get_left_handle ().move_to_coordinate_internal (0, 0);+ + ep.type = PointType.QUADRATIC; + } else {+ + if (position == 0) {+ ep.left_handle = start.left_handle.copy ();+ ep.right_handle = start.right_handle.copy ();+ + ep.get_left_handle ().length *= 0.833;+ ep.get_right_handle ().length *= 0.833;++ } else {+ ep.get_left_handle ().set_point_type (PointType.CUBIC);+ ep.get_right_handle ().set_point_type (PointType.CUBIC);+ + ep.get_left_handle ().parent = ep;+ ep.get_right_handle ().parent = ep;+ + ep.get_left_handle ().move_to_coordinate (handle_x0, handle_y0);+ ep.get_right_handle ().move_to_coordinate (handle_x1, handle_y1);+ }++ ep.type = PointType.LINE_CUBIC;+ }+ ep.get_left_handle ().parent = ep;+ ep.get_right_handle ().parent = ep;+ + new_path.add_point (ep);+ + return true;+ }, 3);+ + new_path.create_list ();++ foreach (EditPoint e in new_path.points) { + e.get_left_handle ().length /= 2; + e.get_right_handle ().length /= 2;+ }+ + bool end_point; // first and last point+ uint k;+ uint npoints;+ + end_point = true;+ k = 0;+ npoints = new_path.points.length ();+ new_path.create_list ();+ foreach (EditPoint e in new_path.points) { + double nx, ny; + double stroke_thickness;+ double space_left, space_right;+ + get_new_position (e, clockwise, thickness, out nx, out ny);+ + EditPoint ep = e.copy ();++ end_point = (k == 0 || k == npoints - 1);+ k++;++ ep.x = nx;+ ep.y = ny;++ if (e.is_corner ()) {+ EditPoint corner1, corner2, corner3, swap_corner;+ + // add new points in order to preserve the stroke+ corner1 = e.copy ();+ stroked.add_point (corner1);+ + corner2 = e.copy ();+ stroked.get_closest_point_on_path (corner2, nx, ny);++ if (end_point) {+ swap_corner = corner1;+ corner1 = corner2;+ corner2 = swap_corner;+ }++ if (end_point) {+ stroked.insert_new_point_on_path (corner1);+ } else {+ stroked.insert_new_point_on_path (corner2);+ }+ + + corner1.x += cos (e.get_right_handle ().angle + PI / 2) * 2 * thickness;+ corner1.y += sin (e.get_right_handle ().angle + PI / 2) * 2 * thickness;++ corner2.x += cos (e.get_left_handle ().angle - PI / 2) * 2 * thickness;+ corner2.y += sin (e.get_left_handle ().angle - PI / 2) * 2 * thickness;++ // fill the gap+ double middle_x, middle_y;+ double corner_thickness_left, corner_thickness_right;+ corner3 = e.copy ();+ middle_x = corner1.x + (corner2.x - corner1.x) / 2;+ middle_y = corner1.y + (corner2.y - corner1.y) / 2;+ stroked.get_closest_point_on_path (corner3, middle_x, middle_y);+ stroked.insert_new_point_on_path (corner3);+ + corner_thickness_right = thickness * ((e.get_right_handle ().angle - PI) / (2 * PI));+ corner_thickness_left = thickness * ((e.get_left_handle ().angle) / (2 * PI));+ + double ncf;++ ncf = 2 * thickness;+ ncf *= 1 / sin (e.get_corner_angle ());+ + corner3.x = e.x;+ corner3.y = e.y;+ + corner3.x += ncf * cos (e.get_right_handle ().angle - PI);+ corner3.y += ncf * sin (e.get_right_handle ().angle - PI);++ corner3.x += ncf * cos (e.get_left_handle ().angle - PI);+ corner3.y += ncf * sin (e.get_left_handle ().angle - PI);+ + } else {+ stroke_thickness = fabs (thickness) * 0.04;++ space_left = get_space_difference (e, e.get_prev ().data, thickness, clockwise);+ space_right = get_space_difference (e, e.get_next ().data, thickness, clockwise);++ ep.get_left_handle ().length += space_left / 3;+ ep.get_right_handle ().length += space_right / 3;+ + stroked.add_point (ep);+ }+ }+ + foreach (EditPoint ep in stroked.points) {+ ep.recalculate_linear_handles ();+ }+ + stroked.reverse ();+ return stroked;+ }++ public static void get_new_position (EditPoint e, bool clockwise, double thickness, out double strokex, out double strokey) {+ double sx, sy;+ get_new_position_delta (e, clockwise, thickness, out sx, out sy);+ strokex = sx + e.x;+ strokey = sy + e.y; + }++ public static void get_new_position_delta (EditPoint e, bool clockwise, double thickness, out double strokex, out double strokey) {+ double ra, la;+ double len_tot;+ double len_r, len_l;+ + double avg_angle;+ double angle;+ + e.recalculate_linear_handles ();+ + ra = e.get_right_handle ().angle;+ la = e.get_left_handle ().angle;+ + len_r = e.get_right_handle ().length;+ len_l = e.get_left_handle ().length;+ + len_tot = len_r + len_l;+ + avg_angle = (la + ra + PI) / 2.0;++ while (avg_angle > 2 * PI) {+ avg_angle -= 2 * PI;+ }+ + angle = avg_angle - PI / 2;+ + ldnx = 0;+ ldny = 0;+ + double m, n, o, p;++ m = cos (ra + PI / 2) * thickness;+ n = sin (ra + PI / 2) * thickness;+ + o = cos (la - PI / 2) * thickness;+ p = sin (la - PI / 2) * thickness;++ ldnx = m + o;+ ldny = n + p;+ + strokex = ldnx;+ strokey = ldny;+ }+ + static double get_space_difference (EditPoint ep0, EditPoint ep1, double stroke, bool clockwise) {+ double p0x, p0y, p1x, p1y;+ double d1, d2;+ + get_new_position (ep0, clockwise, stroke, out p0x, out p0y);+ get_new_position (ep1, clockwise, stroke, out p1x, out p1y);+ + d1 = Path.distance (ep0.x, ep1.x, ep0.y, ep1.y);+ d2 = Path.distance (p0x, p1x, p0y, p1y);+ + return d2 - d1;+ }++ public static double length_adjustment (double lx, double ldx, double ly, double ldy, double l) {+ double a = sqrt (pow (ldx, 2) + pow (ldy, 2)) / fabs (l); + return a;+ }+}++}@@ -85,11 +85,8 @@ class Vala(object): self.so_link = join(build, src) + '.so' self.so_link_name = src + '.so' self.so_version = so_version-<<<<<<< HEAD- self.so_name = 'lib' + library + '.so.' + so_version;-=======- self.so_name = library + '.so.' + so_version;->>>>>>> e8a3b18d3b7fd2193801a61a9d8b1d1c306dfd0c+ self.so_name = 'lib' + library + '.so.' + so_version+ def gen_c(self, opts): """translate code from vala to C and create .vapi"""