The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BezierTool.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/BezierTool.vala.
Fix zero length handles
1 /* 2 Copyright (C) 2015 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 Math; 16 using Cairo; 17 18 namespace BirdFont { 19 20 /** Create Beziér curves. */ 21 public class BezierTool : Tool { 22 23 public const uint NONE = 0; 24 public const uint MOVE_POINT = 1; 25 public const uint MOVE_HANDLES = 2; 26 public const uint MOVE_LAST_HANDLE_RIGHT = 3; 27 public const uint MOVE_LAST_HANDLE_LEFT = 4; 28 public const uint MOVE_FIRST_HANDLE = 5; 29 public const uint MOVE_HANDLE_ON_AXIS = 6; 30 31 uint state = NONE; 32 33 Path current_path = new Path (); 34 EditPoint current_point = new EditPoint (); 35 36 int last_x = 0; 37 int last_y = 0; 38 39 double last_release_time = 0; 40 double last_press_time = 0; 41 bool button_down = false; 42 43 /** Create a corner instead of a control point with reflective handles. */ 44 bool corner_node = false; 45 46 /** Swap right and left handles if orientation changes */ 47 bool swap = false; 48 49 public BezierTool (string name) { 50 base (name, ""); 51 52 select_action.connect ((self) => { 53 state = NONE; 54 MainWindow.set_cursor (NativeWindow.VISIBLE); 55 }); 56 57 deselect_action.connect ((self) => { 58 state = NONE; 59 MainWindow.set_cursor (NativeWindow.VISIBLE); 60 }); 61 62 press_action.connect ((self, b, x, y) => { 63 press (b, x, y); 64 }); 65 66 double_click_action.connect ((self, b, x, y) => { 67 }); 68 69 release_action.connect ((self, b, x, y) => { 70 release (b, x, y); 71 }); 72 73 move_action.connect ((self, x, y) => { 74 move (x, y); 75 }); 76 77 key_press_action.connect ((self, keyval) => { 78 }); 79 80 key_release_action.connect ((self, keyval) => { 81 }); 82 83 draw_action.connect ((tool, cairo_context, glyph) => { 84 if (PenTool.can_join (current_point)) { 85 PenTool.draw_join_icon (cairo_context, last_x, last_y); 86 } 87 }); 88 } 89 90 public override string get_tip () { 91 string tip = t_ ("Create Beziér curves") + "\n"; 92 93 tip += HiddenTools.bezier_line.get_key_binding (); 94 tip += " - "; 95 tip += t_ ("line") + "\n"; 96 97 tip += HiddenTools.bezier_corner.get_key_binding (); 98 tip += " - "; 99 tip += t_ ("corner") + "\n"; 100 101 tip += HiddenTools.move_along_axis.get_key_binding (); 102 tip += " - "; 103 tip += t_ ("on axis") + "\n"; 104 105 return tip; 106 } 107 108 public void press (int b, int x, int y) { 109 Glyph g = MainWindow.get_current_glyph (); 110 double px, py; 111 Path? p; 112 Path path; 113 114 if (button_down) { 115 warning ("Discarding event."); 116 return; 117 } 118 119 button_down = true; 120 121 if (state == MOVE_HANDLES 122 || state == MOVE_LAST_HANDLE_RIGHT 123 || state == MOVE_LAST_HANDLE_LEFT) { 124 return; 125 } 126 127 if (b == 2) { 128 if (g.is_open ()) { 129 stop_drawing (); 130 g.close_path (); 131 } else { 132 g.open_path (); 133 } 134 135 MainWindow.set_cursor (NativeWindow.VISIBLE); 136 state = NONE; 137 138 return; 139 } 140 141 // ignore double clicks 142 if ((GLib.get_real_time () - last_press_time) / 1000000.0 < 0.2) { 143 last_press_time = GLib.get_real_time (); 144 return; 145 } 146 last_press_time = GLib.get_real_time (); 147 148 g.store_undo_state (); 149 150 PenTool.update_orientation (); 151 152 MainWindow.set_cursor (NativeWindow.HIDDEN); 153 g.open_path (); 154 155 px = Glyph.path_coordinate_x (x); 156 py = Glyph.path_coordinate_y (y); 157 158 if (GridTool.is_visible ()) { 159 GridTool.tie_coordinate (ref px, ref py); 160 } 161 162 if (state == MOVE_HANDLE_ON_AXIS) { 163 current_point = current_path.add (px, py); 164 current_path.hide_end_handle = true; 165 current_point.get_left_handle ().convert_to_line (); 166 current_point.recalculate_linear_handles (); 167 set_point_type (); 168 corner_node = false; 169 state = MOVE_POINT; 170 } else if (corner_node) { 171 if (current_path.is_open ()) { 172 current_point = current_path.add (px, py); 173 current_path.hide_end_handle = true; 174 current_point.get_left_handle ().convert_to_line (); 175 current_point.recalculate_linear_handles (); 176 set_point_type (); 177 g.clear_active_paths (); 178 g.add_active_path (null, current_path); 179 GlyphCanvas.redraw (); 180 state = MOVE_POINT; 181 } else { 182 state = NONE; 183 } 184 } else if (state == NONE) { 185 g.open_path (); 186 current_path = new Path (); 187 current_path.reopen (); 188 current_path.hide_end_handle = true; 189 current_point = current_path.add (px, py); 190 current_point.get_left_handle ().convert_to_line (); 191 current_point.recalculate_linear_handles (); 192 g.add_path (current_path); 193 194 set_point_type (); 195 196 if (StrokeTool.add_stroke) { 197 current_path.stroke = StrokeTool.stroke_width; 198 current_path.line_cap = StrokeTool.line_cap; 199 } 200 201 BirdFont.get_current_font ().touch (); 202 203 GlyphCanvas.redraw (); 204 state = MOVE_POINT; 205 } else if (state == MOVE_POINT) { 206 if (PenTool.can_join (current_point)) { 207 EditPoint first = current_path.get_first_point (); 208 209 p = PenTool.join_paths (current_point); 210 return_if_fail (p != null); 211 path = (!) p; 212 swap = path.get_first_point () != first; 213 214 if (current_path.points.size == 1) { 215 return_if_fail (path.is_open ()); 216 current_path = path; 217 current_point = path.get_last_point (); 218 state = MOVE_POINT; 219 } else { 220 g.open_path (); 221 current_path = path; 222 current_point = !swap ? path.get_first_point () : path.get_last_point (); 223 state = !swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT; 224 } 225 } else { 226 state = MOVE_HANDLES; 227 } 228 } 229 } 230 231 void set_point_type () { 232 PointType pt; 233 234 pt = DrawingTools.get_selected_point_type (); 235 236 current_point.type = pt; 237 current_point.get_left_handle ().type = pt; 238 current_point.get_right_handle ().type = pt; 239 current_point.get_left_handle ().convert_to_line (); 240 241 current_point.get_right_handle ().convert_to_line (); 242 } 243 244 public void release (int b, int x, int y) { 245 double px, py; 246 Glyph g; 247 248 if (!button_down) { 249 warning ("Discarding event."); 250 return; 251 } 252 253 button_down = false; 254 255 if (state == NONE || state == MOVE_POINT) { 256 return; 257 } 258 259 corner_node = false; 260 261 // ignore double clicks 262 if ((GLib.get_real_time () - last_release_time) / 1000000.0 < 0.2) { 263 last_release_time = GLib.get_real_time (); 264 return; 265 } 266 last_release_time = GLib.get_real_time (); 267 268 px = Glyph.path_coordinate_x (x); 269 py = Glyph.path_coordinate_y (y); 270 271 if (GridTool.is_visible ()) { 272 GridTool.tie_coordinate (ref px, ref py); 273 } 274 275 g = MainWindow.get_current_glyph (); 276 277 if (state == MOVE_HANDLES) { 278 current_point = current_path.add (px, py); 279 current_path.hide_end_handle = true; 280 current_point.get_left_handle ().convert_to_line (); 281 current_point.recalculate_linear_handles (); 282 set_point_type (); 283 g.clear_active_paths (); 284 g.add_active_path (null, current_path); 285 286 GlyphCanvas.redraw (); 287 state = MOVE_POINT; 288 } else if (state == MOVE_LAST_HANDLE_LEFT || state == MOVE_LAST_HANDLE_RIGHT) { 289 current_path.update_region_boundaries (); 290 g.close_path (); 291 MainWindow.set_cursor (NativeWindow.VISIBLE); 292 293 if (Path.is_counter (g.get_visible_path_list (), current_path)) { 294 current_path.force_direction (Direction.COUNTER_CLOCKWISE); 295 } else { 296 current_path.force_direction (Direction.CLOCKWISE); 297 } 298 299 current_path.reset_stroke (); 300 301 state = NONE; 302 } 303 304 convert_zero_length_handles_to_lines (); 305 } 306 307 void convert_zero_length_handles_to_lines () { 308 EditPoint first, last; 309 310 first = current_path.get_first_point (); 311 if (first.get_right_handle ().length == 0) { 312 first.convert_to_line (); 313 } 314 315 last = current_path.get_last_point (); 316 if (last.get_left_handle ().length == 0) { 317 last.convert_to_line (); 318 } 319 } 320 321 public void move (int x, int y) { 322 double px, py; 323 324 last_x = x; 325 last_y = y; 326 327 px = Glyph.path_coordinate_x (x); 328 py = Glyph.path_coordinate_y (y); 329 330 if (GridTool.is_visible ()) { 331 GridTool.tie_coordinate (ref px, ref py); 332 } 333 334 if (state == MOVE_POINT) { 335 current_point.x = px; 336 current_point.y = py; 337 current_path.hide_end_handle = true; 338 current_point.recalculate_linear_handles (); 339 current_path.reset_stroke (); 340 341 if (current_point.type == PointType.QUADRATIC) { 342 current_path.create_list (); 343 current_point.get_prev ().get_right_handle ().process_connected_handle (); 344 } 345 346 GlyphCanvas.redraw (); 347 } else if (state == MOVE_HANDLES 348 || state == MOVE_LAST_HANDLE_LEFT 349 || state == MOVE_LAST_HANDLE_RIGHT) { 350 351 current_path.hide_end_handle = false; 352 353 if (!corner_node) { 354 current_point.set_reflective_handles (true); 355 current_point.convert_to_curve (); 356 } 357 358 if (state == MOVE_LAST_HANDLE_LEFT) { 359 current_point.get_left_handle ().move_to_coordinate (px, py); 360 } else { 361 current_point.get_right_handle ().move_to_coordinate (px, py); 362 } 363 364 current_path.reset_stroke (); 365 GlyphCanvas.redraw (); 366 } else if (state == MOVE_HANDLE_ON_AXIS) { 367 EditPointHandle h = current_point.get_right_handle (); 368 double horizontal, vertical; 369 370 vertical = Path.distance (px, h.parent.x, py, py); 371 horizontal = Path.distance (h.parent.y, py, py, py); 372 373 current_path.hide_end_handle = false; 374 current_point.set_reflective_handles (true); 375 current_point.convert_to_curve (); 376 377 if (horizontal < vertical) { 378 h.move_to_coordinate (px, current_point.y); 379 } else { 380 h.move_to_coordinate (current_point.x, py); 381 } 382 383 GlyphCanvas.redraw (); 384 } 385 386 if (current_path.points.size > 0) { 387 current_path.get_first_point ().set_reflective_handles (false); 388 current_path.get_last_point ().set_reflective_handles (false); 389 } 390 } 391 392 public void switch_to_line_mode () { 393 int s = current_path.points.size; 394 EditPoint p; 395 396 if (s > 2) { 397 p = current_path.points.get (s - 2); 398 p.get_right_handle ().convert_to_line (); 399 current_point.get_left_handle ().convert_to_line (); 400 p.recalculate_linear_handles (); 401 current_point.recalculate_linear_handles (); 402 current_path.reset_stroke (); 403 GlyphCanvas.redraw (); 404 405 state = MOVE_POINT; 406 } 407 } 408 409 public void stop_drawing () { 410 if (state == MOVE_POINT 411 && current_path.points.size > 0 412 && current_path.is_open ()) { 413 414 current_path.delete_last_point (); 415 current_path.reset_stroke (); 416 current_path.create_full_stroke (); // cache better stroke 417 } 418 419 state = NONE; 420 } 421 422 public override void before_undo () { 423 } 424 425 public override void after_undo () { 426 if (state != NONE) { 427 MainWindow.set_cursor (NativeWindow.VISIBLE); 428 state = NONE; 429 } 430 } 431 432 public void create_corner () { 433 Glyph g = MainWindow.get_current_glyph (); 434 435 corner_node = true; 436 g.open_path (); 437 438 if (current_path.is_open ()) { 439 current_path.delete_last_point (); 440 current_path.reset_stroke (); 441 current_point = current_path.get_last_point (); 442 state = MOVE_HANDLES; 443 } else { 444 state = swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT; 445 } 446 447 current_point.set_reflective_handles (false); 448 current_point.get_right_handle ().convert_to_curve (); 449 } 450 451 public void move_handle_on_axis () { 452 state = MOVE_HANDLE_ON_AXIS; 453 } 454 } 455 456 } 457