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