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