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