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 in Beziér tool
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_path.recalculate_linear_handles_for_point (current_point); 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_path.recalculate_linear_handles_for_point (current_point); 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_path.recalculate_linear_handles_for_point (current_point); 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 convert_zero_length_handles_to_lines (); 260 corner_node = false; 261 262 // ignore double clicks 263 if ((GLib.get_real_time () - last_release_time) / 1000000.0 < 0.2) { 264 last_release_time = GLib.get_real_time (); 265 return; 266 } 267 last_release_time = GLib.get_real_time (); 268 269 px = Glyph.path_coordinate_x (x); 270 py = Glyph.path_coordinate_y (y); 271 272 if (GridTool.is_visible ()) { 273 GridTool.tie_coordinate (ref px, ref py); 274 } 275 276 g = MainWindow.get_current_glyph (); 277 278 if (state == MOVE_HANDLES) { 279 current_point = current_path.add (px, py); 280 current_path.hide_end_handle = true; 281 current_point.get_left_handle ().convert_to_line (); 282 current_path.recalculate_linear_handles_for_point (current_point); 283 set_point_type (); 284 g.clear_active_paths (); 285 g.add_active_path (null, current_path); 286 287 GlyphCanvas.redraw (); 288 state = MOVE_POINT; 289 } else if (state == MOVE_LAST_HANDLE_LEFT || state == MOVE_LAST_HANDLE_RIGHT) { 290 current_path.update_region_boundaries (); 291 g.close_path (); 292 MainWindow.set_cursor (NativeWindow.VISIBLE); 293 294 if (Path.is_counter (g.get_visible_path_list (), current_path)) { 295 current_path.force_direction (Direction.COUNTER_CLOCKWISE); 296 } else { 297 current_path.force_direction (Direction.CLOCKWISE); 298 } 299 300 current_path.reset_stroke (); 301 302 state = NONE; 303 } 304 305 convert_zero_length_handles_to_lines (); 306 } 307 308 void convert_zero_length_handles_to_lines () { 309 EditPoint first, last; 310 311 first = current_path.get_first_point (); 312 313 if (first.get_left_handle ().length == 0) { 314 first.get_left_handle ().convert_to_line (); 315 } 316 317 if (first.get_right_handle ().length == 0) { 318 first.get_right_handle ().convert_to_line (); 319 } 320 321 last = current_path.get_last_point (); 322 323 if (last.get_left_handle ().length == 0) { 324 last.get_left_handle ().convert_to_line (); 325 } 326 327 if (last.get_right_handle ().length == 0) { 328 last.get_right_handle ().convert_to_line (); 329 } 330 } 331 332 public void move (int x, int y) { 333 double px, py; 334 335 last_x = x; 336 last_y = y; 337 338 px = Glyph.path_coordinate_x (x); 339 py = Glyph.path_coordinate_y (y); 340 341 if (GridTool.is_visible ()) { 342 GridTool.tie_coordinate (ref px, ref py); 343 } 344 345 if (state == MOVE_POINT) { 346 current_point.x = px; 347 current_point.y = py; 348 current_path.hide_end_handle = true; 349 current_path.recalculate_linear_handles_for_point (current_point); 350 current_path.reset_stroke (); 351 352 if (current_point.type == PointType.QUADRATIC) { 353 current_path.create_list (); 354 current_point.get_prev ().get_right_handle ().process_connected_handle (); 355 } 356 357 GlyphCanvas.redraw (); 358 } else if (state == MOVE_HANDLES 359 || state == MOVE_LAST_HANDLE_LEFT 360 || state == MOVE_LAST_HANDLE_RIGHT) { 361 362 current_path.hide_end_handle = false; 363 364 if (!corner_node) { 365 current_point.set_reflective_handles (true); 366 current_point.convert_to_curve (); 367 } 368 369 if (state == MOVE_LAST_HANDLE_LEFT) { 370 current_point.get_left_handle ().move_to_coordinate (px, py); 371 } else { 372 current_point.get_right_handle ().move_to_coordinate (px, py); 373 } 374 375 current_path.reset_stroke (); 376 GlyphCanvas.redraw (); 377 } else if (state == MOVE_HANDLE_ON_AXIS) { 378 EditPointHandle h = current_point.get_right_handle (); 379 380 current_path.hide_end_handle = false; 381 current_point.set_reflective_handles (true); 382 current_point.convert_to_curve (); 383 384 double tied_x = 0; 385 double tied_y = 0; 386 387 PointTool.tie_angle (h.parent.x, h.parent.y, 388 px, py, out tied_x, out tied_y); 389 390 h.x = tied_x; 391 h.y = tied_y; 392 393 current_path.reset_stroke (); 394 GlyphCanvas.redraw (); 395 } 396 397 if (current_path.points.size > 0) { 398 current_path.get_first_point ().set_reflective_handles (false); 399 current_path.get_last_point ().set_reflective_handles (false); 400 } 401 402 convert_zero_length_handles_to_lines (); 403 } 404 405 public void switch_to_line_mode () { 406 int s = current_path.points.size; 407 EditPoint p; 408 409 if (s > 2) { 410 p = current_path.points.get (s - 2); 411 p.set_tie_handle (false); 412 p.set_reflective_handles (false); 413 p.get_right_handle ().convert_to_line (); 414 current_point.get_left_handle ().convert_to_line (); 415 current_path.recalculate_linear_handles_for_point (p); 416 current_path.recalculate_linear_handles_for_point (current_point); 417 current_path.reset_stroke (); 418 GlyphCanvas.redraw (); 419 420 state = MOVE_POINT; 421 } 422 } 423 424 public void stop_drawing () { 425 if (state == MOVE_POINT 426 && current_path.points.size > 0 427 && current_path.is_open ()) { 428 429 current_path.delete_last_point (); 430 current_path.reset_stroke (); 431 current_path.create_full_stroke (); // cache better stroke 432 } 433 434 state = NONE; 435 } 436 437 public override void before_undo () { 438 } 439 440 public override void after_undo () { 441 if (state != NONE) { 442 MainWindow.set_cursor (NativeWindow.VISIBLE); 443 state = NONE; 444 } 445 } 446 447 public void create_corner () { 448 Glyph g = MainWindow.get_current_glyph (); 449 450 corner_node = true; 451 g.open_path (); 452 453 if (current_path.is_open ()) { 454 current_path.delete_last_point (); 455 current_path.reset_stroke (); 456 current_point = current_path.get_last_point (); 457 current_point.set_tie_handle (false); 458 current_point.set_reflective_handles (false); 459 state = MOVE_HANDLES; 460 } else { 461 state = swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT; 462 } 463 464 current_point.set_reflective_handles (false); 465 current_point.get_right_handle ().convert_to_curve (); 466 } 467 468 public void move_handle_on_axis () { 469 state = MOVE_HANDLE_ON_AXIS; 470 } 471 } 472 473 } 474