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