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