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