The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

ResizeTool.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/ResizeTool.vala.
Rotate paths
1 /* 2 Copyright (C) 2013 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 using SvgBird; 18 19 namespace BirdFont { 20 21 public class ResizeTool : Tool { 22 bool resize_path_proportional = false; 23 bool resize_width = false; 24 25 SvgBird.Object? resized_path = null; 26 double last_resize_y; 27 double last_resize_x; 28 29 bool move_paths = false; 30 31 static double selection_box_width = 0; 32 static double selection_box_height = 0; 33 static double selection_box_center_x = 0; 34 static double selection_box_center_y = 0; 35 static double selection_box_left = 0; 36 static double selection_box_top = 0; 37 38 static bool rotate_path = false; 39 static double last_rotate_y; 40 static double rotation = 0; 41 static double last_rotate = 0; 42 43 public double last_skew = 0; 44 45 public signal void objects_rotated (double angle); 46 public signal void objects_resized (double width, double height); 47 48 Text proportional_handle; 49 Text horizontal_handle; 50 51 public ResizeTool (string n) { 52 base (n, t_("Resize and rotate paths")); 53 54 proportional_handle = new Text ("resize_handle", 60); 55 proportional_handle.load_font ("icons.bf"); 56 Theme.text_color (proportional_handle, "Highlighted 1"); 57 58 horizontal_handle = new Text ("resize_handle_horizontal", 60); 59 horizontal_handle.load_font ("icons.bf"); 60 Theme.text_color (horizontal_handle, "Highlighted 1"); 61 62 select_action.connect((self) => { 63 }); 64 65 deselect_action.connect((self) => { 66 }); 67 68 press_action.connect((self, b, x, y) => { 69 SvgBird.Object last_path; 70 Glyph glyph; 71 72 glyph = MainWindow.get_current_glyph (); 73 glyph.store_undo_state (); 74 75 foreach (SvgBird.Object p in glyph.active_paths) { 76 if (is_over_resize_handle (p, x, y)) { 77 resize_path_proportional = true; 78 resized_path = p; 79 last_resize_x = x; 80 last_resize_y = y; 81 return; 82 } 83 84 if (is_over_horizontal_resize_handle (p, x, y)) { 85 resize_width = true; 86 resized_path = p; 87 last_resize_x = x; 88 last_resize_y = y; 89 return; 90 } 91 } 92 93 foreach (SvgBird.Object p in glyph.active_paths) { 94 if (is_over_rotate_handle (p, x, y)) { 95 rotate_path = true; 96 return; 97 } 98 } 99 100 if (glyph.active_paths.size > 0) { 101 last_path = glyph.active_paths.get (glyph.active_paths.size - 1); 102 last_rotate = last_path.transforms.rotation; 103 } 104 105 rotation = last_rotate; 106 last_resize_x = x; 107 last_rotate_y = y; 108 109 if (!resize_path_proportional && !resize_width && !rotate_path) { 110 DrawingTools.move_tool.press (b, x, y); 111 } 112 113 move_paths = true; 114 115 update_selection_box (); 116 }); 117 118 release_action.connect((self, b, x, y) => { 119 resize_path_proportional = false; 120 resize_width = false; 121 rotate_path = false; 122 move_paths = false; 123 DrawingTools.move_tool.release (b, x, y); 124 update_selection_box (); 125 GlyphCanvas.redraw (); 126 127 foreach (SvgBird.Object object in MainWindow.get_current_glyph ().active_paths) { 128 if (object is PathObject) { 129 PathObject path = (PathObject) object; 130 Path p = path.get_path (); 131 132 /* 133 Matrix matrix = Matrix.identity (); 134 matrix.scale (1, -1); 135 matrix.multiply (matrix, object.transforms.get_matrix ()); 136 matrix.invert (); 137 p.transform (matrix); 138 object.transforms.clear (); 139 */ 140 141 p.create_full_stroke (); 142 } else { 143 object.transforms.collapse_transforms (); 144 } 145 } 146 }); 147 148 move_action.connect ((self, x, y) => { 149 Glyph glyph; 150 151 if (resize_path_proportional && can_resize (x, y)) { 152 resize_proportional (x, y); 153 update_selection_box (); 154 } 155 156 if (resize_width && can_resize (x, y)) { 157 resize_horizontal (x, y); 158 update_selection_box (); 159 } 160 161 if (rotate_path) { 162 rotate (x, y); 163 } 164 165 if (move_paths 166 || rotate_path 167 || resize_path_proportional 168 || resize_width) { 169 170 glyph = MainWindow.get_current_glyph (); 171 172 GlyphCanvas.redraw (); 173 } 174 175 DrawingTools.move_tool.move (x, y); 176 }); 177 178 draw_action.connect ((self, cr, glyph) => { 179 Text handle; 180 Glyph g = MainWindow.get_current_glyph (); 181 182 if (!move_paths) { 183 if (!rotate_path) { 184 if (!resize_width) { 185 handle = proportional_handle; 186 get_resize_handle_position (out handle.widget_x, out handle.widget_y); 187 188 handle.widget_x -= handle.get_sidebearing_extent () / 2; 189 handle.widget_y -= handle.get_height () / 2; 190 191 handle.draw (cr); 192 } 193 194 if (!resize_path_proportional) { 195 handle = horizontal_handle; 196 197 get_horizontal_reseize_handle_position (out handle.widget_x, 198 out handle.widget_y); 199 200 handle.widget_x -= handle.get_sidebearing_extent () / 2; 201 handle.widget_y -= handle.get_height () / 2; 202 203 handle.draw (cr); 204 } 205 } 206 207 if (!resize_path_proportional && !resize_width 208 && g.active_paths.size > 0) { 209 210 draw_rotate_handle (cr); 211 } 212 } 213 214 MoveTool.draw_actions (cr); 215 }); 216 217 key_press_action.connect ((self, keyval) => { 218 DrawingTools.move_tool.key_down (keyval); 219 }); 220 } 221 222 public static void get_resize_handle_position (out double px, out double py) { 223 px = Glyph.reverse_path_coordinate_x (selection_box_center_x + selection_box_width / 2); 224 py = Glyph.reverse_path_coordinate_y (selection_box_center_y + selection_box_height / 2); 225 } 226 227 public static void get_horizontal_reseize_handle_position (out double px, out double py) { 228 px = Glyph.reverse_path_coordinate_x (selection_box_center_x + selection_box_width / 2); 229 px += 40; 230 py = Glyph.reverse_path_coordinate_y (selection_box_center_y); 231 } 232 233 public static double get_rotated_handle_length () { 234 double s, hx, hy; 235 double d; 236 237 s = fmin (selection_box_width, selection_box_height) * 1.1; 238 d = (s / Glyph.ivz ()) / 2; 239 240 hx = cos (rotation) * d; 241 hy = sin (rotation) * d; 242 243 return d; 244 } 245 246 public void signal_objects_rotated () { 247 objects_rotated (rotation * (180 / PI)); 248 } 249 250 public void rotate_selected_paths (double angle, double cx, double cy) { 251 Glyph glyph = MainWindow.get_current_glyph (); 252 SvgBird.Object last_path; 253 double x, y; 254 255 glyph.layers.update_boundaries_for_object (); 256 257 foreach (SvgBird.Object p in glyph.active_paths) { 258 if (p is EmbeddedSvg) { 259 EmbeddedSvg svg = (EmbeddedSvg) p; 260 x = selection_box_left - svg.x + selection_box_width / 2; 261 y = selection_box_top + svg.y + selection_box_height / 2; 262 p.transforms.rotate (angle, x, y); 263 } else if (p is PathObject) { 264 Path path = ((PathObject) p).get_path (); 265 SvgTransforms transform = new SvgTransforms (); 266 transform.rotate (-angle, selection_box_center_x, selection_box_center_y); 267 Matrix matrix = transform.get_matrix (); 268 path.transform (matrix); 269 } 270 } 271 272 last_rotate = rotation; 273 274 if (glyph.active_paths.size > 0) { 275 last_path = glyph.active_paths.get (glyph.active_paths.size - 1); 276 277 if (rotation > PI) { 278 rotation -= 2 * PI; 279 } 280 281 if (last_rotate > PI) { 282 last_rotate -= 2 * PI; 283 } 284 285 signal_objects_rotated (); 286 } 287 } 288 289 /** Move rotate handle to pixel x,y. */ 290 void rotate (double x, double y) { 291 double a, b; 292 293 a = Glyph.path_coordinate_x (x) - selection_box_center_x; 294 b = selection_box_center_y - Glyph.path_coordinate_y (y); 295 296 rotation = atan2 (b, a); 297 298 if (a == 0) { 299 rotation = b > 0 ? PI / 2 : -PI / 2; 300 } 301 302 rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y); 303 } 304 305 static bool is_over_rotate_handle (SvgBird.Object p, double x, double y) { 306 double cx, cy, hx, hy; 307 double size = 10; 308 bool inx, iny; 309 310 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 311 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 312 313 hx = cos (rotation) * get_rotated_handle_length (); 314 hy = sin (rotation) * get_rotated_handle_length (); 315 316 inx = x - size * MainWindow.units <= cx + hx - 2.5 <= x + size * MainWindow.units; 317 iny = y - size * MainWindow.units <= cy + hy - 2.5 <= y + size * MainWindow.units; 318 319 return inx && iny; 320 } 321 322 static void draw_rotate_handle (Context cr) { 323 double cx, cy, hx, hy; 324 325 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 326 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 327 328 cr.save (); 329 Theme.color (cr, "Highlighted 1"); 330 cr.rectangle (cx - 2.5, cy - 2.5, 5, 5); 331 cr.fill (); 332 333 hx = cos (last_rotate) * get_rotated_handle_length (); 334 hy = sin (last_rotate) * get_rotated_handle_length (); 335 336 cr.set_line_width (1); 337 cr.move_to (cx, cy); 338 cr.line_to (cx + hx, cy + hy); 339 cr.stroke (); 340 341 Theme.color (cr, "Highlighted 1"); 342 cr.rectangle (cx + hx - 2.5, cy + hy - 2.5, 5, 5); 343 cr.fill (); 344 345 cr.restore (); 346 } 347 348 double get_resize_ratio (double px, double py) { 349 double ratio, x, y, w, h; 350 351 Glyph glyph = MainWindow.get_current_glyph (); 352 glyph.selection_boundaries (out x, out y, out w, out h); 353 354 ratio = 1; 355 356 if (Math.fabs (last_resize_y - py) > Math.fabs (last_resize_x - px)) { 357 ratio = 1 + (Glyph.path_coordinate_y (py) 358 - Glyph.path_coordinate_y (last_resize_y)) / h; 359 } else { 360 ratio = 1 + (Glyph.path_coordinate_x (px) 361 - Glyph.path_coordinate_x (last_resize_x)) / w; 362 } 363 364 return ratio; 365 } 366 367 public void resize_selected_paths (double ratio_x, double ratio_y) { 368 Glyph g = MainWindow.get_current_glyph (); 369 resize_glyph (g, ratio_x, ratio_y, true); 370 } 371 372 public void resize_glyph (Glyph glyph, double ratio_x, 373 double ratio_y, bool selected = true) { 374 375 double resize_pos_x = 0; 376 double resize_pos_y = 0; 377 double selection_minx, selection_miny, dx, dy; 378 379 if (!selected) { 380 glyph.clear_active_paths (); 381 382 foreach (SvgBird.Object path in glyph.get_visible_objects ()) { 383 glyph.add_active_object (path); 384 } 385 } 386 387 get_selection_min (out resize_pos_x, out resize_pos_y); 388 389 // resize paths 390 foreach (SvgBird.Object selected_path in glyph.active_paths) { 391 selected_path.transforms.resize (ratio_x, ratio_y); 392 } 393 394 // move paths relative to the updated xmin and xmax 395 get_selection_min (out selection_minx, out selection_miny); 396 dx = resize_pos_x - selection_minx; 397 dy = resize_pos_y - selection_miny; 398 399 foreach (SvgBird.Object selected_path in glyph.active_paths) { 400 selected_path.move (dx, dy); 401 } 402 403 if (glyph.active_paths.size > 0) { 404 update_selection_box (); 405 objects_resized (selection_box_width, selection_box_height); 406 } 407 408 if (!selected) { 409 double w; 410 w = (ratio_x * glyph.get_width () - glyph.get_width ()) / 2.0; 411 glyph.left_limit -= w; 412 glyph.right_limit += w; 413 glyph.clear_active_paths (); 414 glyph.remove_lines (); 415 glyph.add_help_lines (); 416 } 417 } 418 419 public static void update_selection_box () { 420 MoveTool.update_boundaries_for_selection (); 421 MoveTool.get_selection_box_boundaries (out selection_box_center_x, 422 out selection_box_center_y, out selection_box_width, 423 out selection_box_height, out selection_box_left, out selection_box_top); 424 } 425 426 /** Move resize handle to pixel x,y. */ 427 void resize_proportional (double px, double py) { 428 double ratio; 429 430 ratio = get_resize_ratio (px, py); 431 432 if (ratio != 1) { 433 resize_selected_paths (ratio, ratio); 434 last_resize_x = px; 435 last_resize_y = py; 436 } 437 } 438 439 /** Move resize handle to pixel x,y. */ 440 void resize_horizontal (double px, double py) { 441 double ratio, x, y, w, h; 442 443 Glyph glyph = MainWindow.get_current_glyph (); 444 glyph.selection_boundaries (out x, out y, out w, out h); 445 446 ratio = 1 + (Glyph.path_coordinate_x (px) 447 - Glyph.path_coordinate_x (last_resize_x)) / w; 448 449 if (ratio != 1) { 450 resize_selected_paths (ratio, 1); 451 last_resize_x = px; 452 last_resize_y = py; 453 } 454 } 455 456 void get_selection_min (out double x, out double y) { 457 Glyph glyph = MainWindow.get_current_glyph (); 458 x = double.MAX; 459 y = double.MAX; 460 foreach (SvgBird.Object p in glyph.active_paths) { 461 if (p.xmin < x) { 462 x = p.xmin; 463 } 464 465 if (p.ymin < y) { 466 y = p.ymin; 467 } 468 } 469 } 470 471 bool can_resize (double x, double y) { 472 Glyph glyph = MainWindow.get_current_glyph (); 473 double h, w; 474 double ratio = get_resize_ratio (x, y); 475 476 foreach (SvgBird.Object selected_path in glyph.active_paths) { 477 h = selected_path.ymax - selected_path.ymin; 478 w = selected_path.xmax - selected_path.xmin; 479 480 if (selected_path.is_empty ()) { // FIXME: test with one point 481 continue; 482 } 483 484 if (h * ratio < 1 || w * ratio < 1) { 485 return false; 486 } 487 } 488 489 return true; 490 } 491 492 bool is_over_resize_handle (SvgBird.Object p, double x, double y) { 493 double handle_x, handle_y; 494 get_resize_handle_position (out handle_x, out handle_y); 495 return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; 496 } 497 498 bool is_over_horizontal_resize_handle (SvgBird.Object p, double x, double y) { 499 double handle_x, handle_y; 500 get_horizontal_reseize_handle_position (out handle_x, out handle_y); 501 return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; 502 } 503 504 public void skew (double skew) { 505 Glyph glyph = MainWindow.get_current_glyph (); 506 skew_glyph (glyph, skew, last_skew, true); 507 last_skew = skew; 508 } 509 510 public void skew_glyph (Glyph glyph, double skew, double last_skew, 511 bool selected_paths) { 512 513 double dx, nx, nw, dw, x, y, w, h; 514 double s = (skew - last_skew) / 100.0; 515 516 if (!selected_paths) { 517 glyph.clear_active_paths (); 518 519 foreach (SvgBird.Object path in glyph.get_visible_objects ()) { 520 glyph.add_active_object (path); 521 } 522 } 523 524 glyph.selection_boundaries (out x, out y, out w, out h); 525 526 foreach (SvgBird.Object path in glyph.active_paths) { 527 if (path is PathObject) { // FIXME: other objects 528 Path p = ((PathObject) path).get_path (); 529 SvgParser.apply_matrix (p, 1, 0, s, 1, 0, 0); 530 p.skew = skew; 531 path.update_boundaries_for_object (); 532 } 533 } 534 535 glyph.selection_boundaries (out nx, out y, out nw, out h); 536 537 dx = -(nx - x); 538 539 foreach (SvgBird.Object p in glyph.active_paths) { 540 p.move (dx, 0); 541 } 542 543 dw = (nw - w); 544 glyph.right_limit += dw; 545 glyph.remove_lines (); 546 glyph.add_help_lines (); 547 548 if (!selected_paths) { 549 glyph.clear_active_paths (); 550 } 551 } 552 } 553 554 } 555