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.
Merge ../birdfont-2.x
1 /* 2 Copyright (C) 2015 2016 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 (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 (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 if (first.get_right_handle ().length == 0) { 313 first.convert_to_line (); 314 } 315 316 last = current_path.get_last_point (); 317 if (last.get_left_handle ().length == 0) { 318 last.convert_to_line (); 319 } 320 } 321 322 public void move (int x, int y) { 323 double px, py; 324 325 last_x = x; 326 last_y = y; 327 328 px = Glyph.path_coordinate_x (x); 329 py = Glyph.path_coordinate_y (y); 330 331 if (GridTool.is_visible ()) { 332 GridTool.tie_coordinate (ref px, ref py); 333 } 334 335 if (state == MOVE_POINT) { 336 current_point.x = px; 337 current_point.y = py; 338 current_path.hide_end_handle = true; 339 current_path.recalculate_linear_handles_for_point (current_point); 340 current_path.reset_stroke (); 341 342 if (current_point.type == PointType.QUADRATIC) { 343 current_path.create_list (); 344 current_point.get_prev ().get_right_handle ().process_connected_handle (); 345 } 346 347 GlyphCanvas.redraw (); 348 } else if (state == MOVE_HANDLES 349 || state == MOVE_LAST_HANDLE_LEFT 350 || state == MOVE_LAST_HANDLE_RIGHT) { 351 352 current_path.hide_end_handle = false; 353 354 if (!corner_node) { 355 current_point.set_reflective_handles (true); 356 current_point.convert_to_curve (); 357 } 358 359 if (state == MOVE_LAST_HANDLE_LEFT) { 360 current_point.get_left_handle ().move_to_coordinate (px, py); 361 } else { 362 current_point.get_right_handle ().move_to_coordinate (px, py); 363 } 364 365 current_path.reset_stroke (); 366 GlyphCanvas.redraw (); 367 } else if (state == MOVE_HANDLE_ON_AXIS) { 368 EditPointHandle h = current_point.get_right_handle (); 369 370 current_path.hide_end_handle = false; 371 current_point.set_reflective_handles (true); 372 current_point.convert_to_curve (); 373 374 double tied_x = 0; 375 double tied_y = 0; 376 377 PointTool.tie_angle (h.parent.x, h.parent.y, 378 px, py, out tied_x, out tied_y); 379 380 h.x = tied_x; 381 h.y = tied_y; 382 383 current_path.reset_stroke (); 384 GlyphCanvas.redraw (); 385 } 386 387 if (current_path.points.size > 0) { 388 current_path.get_first_point ().set_reflective_handles (false); 389 current_path.get_last_point ().set_reflective_handles (false); 390 } 391 392 convert_zero_length_handles_to_lines (); 393 } 394 395 public void switch_to_line_mode () { 396 int s = current_path.points.size; 397 EditPoint p; 398 399 if (s > 2) { 400 p = current_path.points.get (s - 2); 401 p.get_right_handle ().convert_to_line (); 402 current_point.get_left_handle ().convert_to_line (); 403 current_path.recalculate_linear_handles_for_point (p); 404 current_path.recalculate_linear_handles_for_point (current_point); 405 current_path.reset_stroke (); 406 GlyphCanvas.redraw (); 407 408 state = MOVE_POINT; 409 } 410 } 411 412 public void stop_drawing () { 413 if (state == MOVE_POINT 414 && current_path.points.size > 0 415 && current_path.is_open ()) { 416 417 current_path.delete_last_point (); 418 current_path.reset_stroke (); 419 current_path.create_full_stroke (); // cache better stroke 420 } 421 422 state = NONE; 423 } 424 425 public override void before_undo () { 426 } 427 428 public override void after_undo () { 429 if (state != NONE) { 430 MainWindow.set_cursor (NativeWindow.VISIBLE); 431 state = NONE; 432 } 433 } 434 435 public void create_corner () { 436 Glyph g = MainWindow.get_current_glyph (); 437 438 corner_node = true; 439 g.open_path (); 440 441 if (current_path.is_open ()) { 442 current_path.delete_last_point (); 443 current_path.reset_stroke (); 444 current_point = current_path.get_last_point (); 445 state = MOVE_HANDLES; 446 } else { 447 state = swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT; 448 } 449 450 current_point.set_reflective_handles (false); 451 current_point.get_right_handle ().convert_to_curve (); 452 } 453 454 public void move_handle_on_axis () { 455 state = MOVE_HANDLE_ON_AXIS; 456 } 457 } 458 459 } 460