The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

StrokeTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/StrokeTool.vala.
Cut off corners on strokes
1 /* 2 Copyright (C) 2014 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using Cairo; 16 using Math; 17 18 namespace BirdFont { 19 20 public class StrokeTool : Tool { 21 22 public StrokeTool (string tooltip) { 23 select_action.connect((self) => { 24 stroke_selected_paths (); 25 }); 26 } 27 28 public static void set_stroke_for_selected_paths (double width) { 29 Glyph g = MainWindow.get_current_glyph (); 30 31 foreach (Path p in g.active_paths) { 32 p.set_stroke (width); 33 } 34 35 GlyphCanvas.redraw (); 36 } 37 38 /** Create strokes for the selected outlines. */ 39 void stroke_selected_paths () { 40 Glyph g = MainWindow.get_current_glyph (); 41 PathList paths = new PathList (); 42 43 foreach (Path p in g.active_paths) { 44 paths.append (get_stroke (p, p.stroke)); 45 } 46 47 foreach (Path np in paths.paths) { 48 g.add_path (np); 49 } 50 } 51 52 public static PathList get_stroke (Path path, double thickness) { 53 Path p = path.copy (); 54 PathList pl; 55 56 pl = get_stroke_outline (p, thickness); 57 58 return pl; 59 } 60 61 public static PathList get_stroke_outline (Path p, double thickness) { 62 Path counter, outline, merged; 63 PathList paths = new PathList (); 64 65 if (!p.is_open () && p.is_filled ()) { 66 outline = create_stroke (p, thickness); 67 outline.close (); 68 paths.add (outline); 69 outline.update_region_boundaries (); 70 } else if (!p.is_open () && !p.is_filled ()) { 71 outline = create_stroke (p, thickness); 72 counter = create_stroke (p, -1 * thickness); 73 74 paths.add (outline); 75 paths.add (counter); 76 77 if (p.is_clockwise ()) { 78 outline.force_direction (Direction.CLOCKWISE); 79 } else { 80 outline.force_direction (Direction.COUNTER_CLOCKWISE); 81 } 82 83 if (outline.is_clockwise ()) { 84 counter.force_direction (Direction.COUNTER_CLOCKWISE); 85 } else { 86 counter.force_direction (Direction.CLOCKWISE); 87 } 88 89 outline.update_region_boundaries (); 90 counter.update_region_boundaries (); 91 } else if (p.is_open ()) { 92 outline = create_stroke (p, thickness); 93 counter = create_stroke (p, -1 * thickness); 94 merged = merge_strokes (p, outline, counter, thickness); 95 96 if (p.is_clockwise ()) { 97 merged.force_direction (Direction.CLOCKWISE); 98 } else { 99 merged.force_direction (Direction.COUNTER_CLOCKWISE); 100 } 101 102 merged.update_region_boundaries (); 103 paths.add (merged); 104 } else { 105 warning ("Can not create stroke."); 106 paths.add (p); 107 } 108 109 return paths; 110 } 111 112 /** Create one stroke from the outline and counter stroke and close the 113 * open endings. 114 * 115 * @param path the path to create stroke for 116 * @param stroke for the outline of path 117 * @param stroke for the counter path 118 */ 119 static Path merge_strokes (Path path, Path stroke, Path counter, double thickness) { 120 Path merged; 121 EditPoint corner1, corner2; 122 EditPoint corner3, corner4; 123 EditPoint end; 124 double angle; 125 126 if (path.points.size < 2) { 127 warning ("Missing points."); 128 return stroke; 129 } 130 131 if (stroke.points.size < 4) { 132 warning ("Missing points."); 133 return stroke; 134 } 135 136 if (counter.points.size < 4) { 137 warning ("Missing points."); 138 return stroke; 139 } 140 141 // end of stroke 142 end = path.get_last_visible_point (); 143 corner1 = stroke.get_last_point (); 144 angle = end.get_left_handle ().angle; 145 corner1.x = end.x + cos (angle - PI / 2) * thickness; 146 corner1.y = end.y + sin (angle - PI / 2) * thickness; 147 148 corner2 = counter.get_last_point (); 149 corner2.x = end.x + cos (angle + PI / 2) * thickness; 150 corner2.y = end.y + sin (angle + PI / 2) * thickness; 151 152 // the other end 153 end = path.get_first_point (); 154 corner3 = stroke.get_first_point (); 155 angle = end.get_right_handle ().angle; 156 corner3.x = end.x + cos (angle + PI / 2) * thickness; 157 corner3.y = end.y + sin (angle + PI / 2) * thickness; 158 159 corner4 = counter.get_first_point (); 160 corner4.x = end.x + cos (angle - PI / 2) * thickness; 161 corner4.y = end.y + sin (angle - PI / 2) * thickness; 162 163 corner1.get_left_handle ().convert_to_line (); 164 corner2.get_right_handle ().convert_to_line (); 165 166 corner3.get_left_handle ().convert_to_line (); 167 corner4.get_right_handle ().convert_to_line (); 168 169 counter.reverse (); 170 171 // Append the other part of the stroke 172 merged = stroke.copy (); 173 merged.append_path (counter); 174 corner2 = merged.points.get (merged.points.size - 1); 175 176 merged.close (); 177 merged.create_list (); 178 merged.recalculate_linear_handles (); 179 180 return merged; 181 } 182 183 static Path create_stroke (Path p, double thickness) { 184 Path stroked; 185 186 if (p.points.size >= 2) { 187 stroked = p.copy (); 188 stroked = generate_stroke (stroked, thickness); 189 190 if (!p.is_open ()) { 191 stroked.reverse (); 192 stroked.close (); 193 } 194 } else { 195 // TODO: create stroke for a path with one point 196 warning ("One point."); 197 stroked = new Path (); 198 } 199 200 return stroked; 201 } 202 203 static Path generate_stroke (Path p, double thickness) { 204 Path stroked = new Path (); 205 EditPoint start; 206 EditPoint end; 207 EditPoint previous = new EditPoint (); 208 209 foreach (EditPoint ep in p.points) { 210 start = ep.copy (); 211 end = ep.get_next ().copy (); 212 213 move_segment (start, end, thickness); 214 215 add_corner (stroked, previous, start); 216 217 stroked.add_point (start); 218 stroked.add_point (end); 219 220 // line ends around corner 221 start.get_left_handle ().convert_to_line (); 222 end.get_right_handle ().convert_to_line (); 223 224 previous = end; 225 } 226 stroked.recalculate_linear_handles (); 227 228 return stroked; 229 } 230 231 static void move_segment (EditPoint stroke_start, EditPoint stroke_stop, double thickness) { 232 EditPointHandle r, l; 233 double m, n; 234 double qx, qy; 235 236 stroke_start.set_tie_handle (false); 237 stroke_stop.set_tie_handle (false); 238 239 r = stroke_start.get_right_handle (); 240 l = stroke_stop.get_left_handle (); 241 242 m = cos (r.angle + PI / 2) * thickness; 243 n = sin (r.angle + PI / 2) * thickness; 244 245 stroke_start.get_right_handle ().move_to_coordinate_delta (m, n); 246 stroke_start.get_left_handle ().move_to_coordinate_delta (m, n); 247 248 stroke_start.independent_x += m; 249 stroke_start.independent_y += n; 250 251 qx = cos (l.angle - PI / 2) * thickness; 252 qy = sin (l.angle - PI / 2) * thickness; 253 254 stroke_stop.get_right_handle ().move_to_coordinate_delta (qx, qy); 255 stroke_stop.get_left_handle ().move_to_coordinate_delta (qx, qy); 256 257 stroke_stop.independent_x += qx; 258 stroke_stop.independent_y += qy; 259 } 260 261 static void add_corner (Path stroked, EditPoint previous, EditPoint next) { 262 EditPoint corner; 263 double corner_x, corner_y; 264 EditPointHandle previous_handle; 265 EditPointHandle next_handle; 266 267 previous_handle = previous.get_left_handle (); 268 next_handle = next.get_right_handle (); 269 270 previous_handle.angle += PI; 271 next_handle.angle += PI; 272 273 Path.find_intersection_handle (previous_handle, next_handle, out corner_x, out corner_y); 274 corner = new EditPoint (corner_x, corner_y, PointType.LINE_CUBIC); // FIXME: point type 275 stroked.add_point (corner); 276 277 previous_handle.angle -= PI; 278 next_handle.angle -= PI; 279 } 280 281 282 } 283 284 } 285 286