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 branch 'master' of github.com:johanmattssonm/birdfont
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, t_ ("Create Beziér curves")); 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 void press (int b, int x, int y) { 91 Glyph g = MainWindow.get_current_glyph (); 92 double px, py; 93 Path? p; 94 Path path; 95 96 if (button_down) { 97 warning ("Discarding event."); 98 return; 99 } 100 101 button_down = true; 102 103 if (state == MOVE_HANDLES 104 || state == MOVE_LAST_HANDLE_RIGHT 105 || state == MOVE_LAST_HANDLE_LEFT) { 106 return; 107 } 108 109 if (b == 2) { 110 if (g.is_open ()) { 111 stop_drawing (); 112 g.close_path (); 113 } else { 114 g.open_path (); 115 } 116 117 MainWindow.set_cursor (NativeWindow.VISIBLE); 118 state = NONE; 119 120 return; 121 } 122 123 // ignore double clicks 124 if ((GLib.get_real_time () - last_press_time) / 1000000.0 < 0.2) { 125 last_press_time = GLib.get_real_time (); 126 return; 127 } 128 last_press_time = GLib.get_real_time (); 129 130 g.store_undo_state (); 131 132 PenTool.update_orientation (); 133 134 MainWindow.set_cursor (NativeWindow.HIDDEN); 135 g.open_path (); 136 137 px = Glyph.path_coordinate_x (x); 138 py = Glyph.path_coordinate_y (y); 139 140 if (GridTool.is_visible ()) { 141 GridTool.tie_coordinate (ref px, ref py); 142 } 143 144 if (state == MOVE_HANDLE_ON_AXIS) { 145 current_point = current_path.add (px, py); 146 current_path.hide_end_handle = true; 147 current_point.get_left_handle ().convert_to_line (); 148 current_point.recalculate_linear_handles (); 149 set_point_type (); 150 corner_node = false; 151 state = MOVE_POINT; 152 } else if (corner_node) { 153 if (current_path.is_open ()) { 154 current_point = current_path.add (px, py); 155 current_path.hide_end_handle = true; 156 current_point.get_left_handle ().convert_to_line (); 157 current_point.recalculate_linear_handles (); 158 set_point_type (); 159 g.clear_active_paths (); 160 g.add_active_path (null, current_path); 161 GlyphCanvas.redraw (); 162 state = MOVE_POINT; 163 } else { 164 state = NONE; 165 } 166 } else if (state == NONE) { 167 g.open_path (); 168 current_path = new Path (); 169 current_path.reopen (); 170 current_path.hide_end_handle = true; 171 current_point = current_path.add (px, py); 172 current_point.get_left_handle ().convert_to_line (); 173 current_point.recalculate_linear_handles (); 174 g.add_path (current_path); 175 176 set_point_type (); 177 178 if (StrokeTool.add_stroke) { 179 current_path.stroke = StrokeTool.stroke_width; 180 current_path.line_cap = StrokeTool.line_cap; 181 } 182 183 BirdFont.get_current_font ().touch (); 184 185 GlyphCanvas.redraw (); 186 state = MOVE_POINT; 187 } else if (state == MOVE_POINT) { 188 if (PenTool.can_join (current_point)) { 189 EditPoint first = current_path.get_first_point (); 190 191 p = PenTool.join_paths (current_point); 192 return_if_fail (p != null); 193 path = (!) p; 194 swap = path.get_first_point () != first; 195 196 if (current_path.points.size == 1) { 197 return_if_fail (path.is_open ()); 198 current_path = path; 199 current_point = path.get_last_point (); 200 state = MOVE_POINT; 201 } else { 202 g.open_path (); 203 current_path = path; 204 current_point = !swap ? path.get_first_point () : path.get_last_point (); 205 state = !swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT; 206 } 207 } else { 208 state = MOVE_HANDLES; 209 } 210 } 211 } 212 213 void set_point_type () { 214 PointType pt; 215 216 pt = DrawingTools.get_selected_point_type (); 217 218 current_point.type = pt; 219 current_point.get_left_handle ().type = pt; 220 current_point.get_right_handle ().type = pt; 221 current_point.get_left_handle ().convert_to_line (); 222 223 current_point.get_right_handle ().convert_to_line (); 224 } 225 226 public void release (int b, int x, int y) { 227 double px, py; 228 Glyph g; 229 230 if (!button_down) { 231 warning ("Discarding event."); 232 return; 233 } 234 235 button_down = false; 236 237 if (state == NONE || state == MOVE_POINT) { 238 return; 239 } 240 241 corner_node = false; 242 243 // ignore double clicks 244 if ((GLib.get_real_time () - last_release_time) / 1000000.0 < 0.2) { 245 last_release_time = GLib.get_real_time (); 246 return; 247 } 248 last_release_time = GLib.get_real_time (); 249 250 px = Glyph.path_coordinate_x (x); 251 py = Glyph.path_coordinate_y (y); 252 253 if (GridTool.is_visible ()) { 254 GridTool.tie_coordinate (ref px, ref py); 255 } 256 257 g = MainWindow.get_current_glyph (); 258 259 if (state == MOVE_HANDLES) { 260 current_point = current_path.add (px, py); 261 current_path.hide_end_handle = true; 262 current_point.get_left_handle ().convert_to_line (); 263 current_point.recalculate_linear_handles (); 264 set_point_type (); 265 g.clear_active_paths (); 266 g.add_active_path (null, current_path); 267 268 GlyphCanvas.redraw (); 269 state = MOVE_POINT; 270 } else if (state == MOVE_LAST_HANDLE_LEFT || state == MOVE_LAST_HANDLE_RIGHT) { 271 current_path.update_region_boundaries (); 272 g.close_path (); 273 MainWindow.set_cursor (NativeWindow.VISIBLE); 274 275 if (Path.is_counter (g.get_visible_path_list (), current_path)) { 276 current_path.force_direction (Direction.COUNTER_CLOCKWISE); 277 } else { 278 current_path.force_direction (Direction.CLOCKWISE); 279 } 280 281 current_path.reset_stroke (); 282 283 state = NONE; 284 } 285 } 286 287 public void move (int x, int y) { 288 double px, py; 289 290 last_x = x; 291 last_y = y; 292 293 px = Glyph.path_coordinate_x (x); 294 py = Glyph.path_coordinate_y (y); 295 296 if (GridTool.is_visible ()) { 297 GridTool.tie_coordinate (ref px, ref py); 298 } 299 300 if (state == MOVE_POINT) { 301 current_point.x = px; 302 current_point.y = py; 303 current_path.hide_end_handle = true; 304 current_point.recalculate_linear_handles (); 305 current_path.reset_stroke (); 306 307 if (current_point.type == PointType.QUADRATIC) { 308 current_path.create_list (); 309 current_point.get_prev ().get_right_handle ().process_connected_handle (); 310 } 311 312 GlyphCanvas.redraw (); 313 } else if (state == MOVE_HANDLES 314 || state == MOVE_LAST_HANDLE_LEFT 315 || state == MOVE_LAST_HANDLE_RIGHT) { 316 317 current_path.hide_end_handle = false; 318 319 if (!corner_node) { 320 current_point.set_reflective_handles (true); 321 current_point.convert_to_curve (); 322 } 323 324 if (state == MOVE_LAST_HANDLE_LEFT) { 325 current_point.get_left_handle ().move_to_coordinate (px, py); 326 } else { 327 current_point.get_right_handle ().move_to_coordinate (px, py); 328 } 329 330 current_path.reset_stroke (); 331 GlyphCanvas.redraw (); 332 } else if (state == MOVE_HANDLE_ON_AXIS) { 333 EditPointHandle h = current_point.get_right_handle (); 334 double horizontal, vertical; 335 336 vertical = Path.distance (px, h.parent.x, py, py); 337 horizontal = Path.distance (h.parent.y, py, py, py); 338 339 current_path.hide_end_handle = false; 340 current_point.set_reflective_handles (true); 341 current_point.convert_to_curve (); 342 343 if (horizontal < vertical) { 344 h.move_to_coordinate (px, current_point.y); 345 } else { 346 h.move_to_coordinate (current_point.x, py); 347 } 348 349 GlyphCanvas.redraw (); 350 } 351 352 if (current_path.points.size > 0) { 353 current_path.get_first_point ().set_reflective_handles (false); 354 current_path.get_last_point ().set_reflective_handles (false); 355 } 356 } 357 358 public void switch_to_line_mode () { 359 int s = current_path.points.size; 360 EditPoint p; 361 362 if (s > 2) { 363 p = current_path.points.get (s - 2); 364 p.get_right_handle ().convert_to_line (); 365 current_point.get_left_handle ().convert_to_line (); 366 p.recalculate_linear_handles (); 367 current_point.recalculate_linear_handles (); 368 current_path.reset_stroke (); 369 GlyphCanvas.redraw (); 370 371 state = MOVE_POINT; 372 } 373 } 374 375 public void stop_drawing () { 376 if (state == MOVE_POINT 377 && current_path.points.size > 0 378 && current_path.is_open ()) { 379 380 current_path.delete_last_point (); 381 current_path.reset_stroke (); 382 current_path.create_full_stroke (); // cache better stroke 383 } 384 385 state = NONE; 386 } 387 388 public override void before_undo () { 389 } 390 391 public override void after_undo () { 392 if (state != NONE) { 393 MainWindow.set_cursor (NativeWindow.VISIBLE); 394 state = NONE; 395 } 396 } 397 398 public void create_corner () { 399 Glyph g = MainWindow.get_current_glyph (); 400 401 corner_node = true; 402 g.open_path (); 403 404 if (current_path.is_open ()) { 405 current_path.delete_last_point (); 406 current_path.reset_stroke (); 407 current_point = current_path.get_last_point (); 408 state = MOVE_HANDLES; 409 } else { 410 state = swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT; 411 } 412 413 current_point.set_reflective_handles (false); 414 current_point.get_right_handle ().convert_to_curve (); 415 } 416 417 public void move_handle_on_axis () { 418 state = MOVE_HANDLE_ON_AXIS; 419 } 420 } 421 422 } 423