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.
Parse more style attributes in SVG files
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 foreach (Path p in glyph.active_paths) { 196 p.rotate (angle, cx, cy); 197 } 198 199 MoveTool.get_selection_box_boundaries (out xc2, out yc2, out w, out h); 200 201 dx = -(xc2 - cx); 202 dy = -(yc2 - cy); 203 204 foreach (Path p in glyph.active_paths) { 205 p.move (dx, dy); 206 } 207 208 last_rotate = rotation; 209 210 MoveTool.update_selection_boundaries (); 211 212 if (glyph.active_paths.size > 0) { 213 last_path = glyph.active_paths.get (glyph.active_paths.size - 1); 214 rotation = last_path.rotation; 215 216 if (rotation > PI) { 217 rotation -= 2 * PI; 218 } 219 220 last_rotate = rotation; 221 signal_objects_rotated (); 222 } 223 } 224 225 /** Move rotate handle to pixel x,y. */ 226 void rotate (double x, double y) { 227 double cx, cy, xc, yc, a, b; 228 229 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 230 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 231 xc = selection_box_center_x; 232 yc = selection_box_center_y; 233 234 a = x - cx; 235 b = y - cy; 236 237 rotation = atan (b / a); 238 239 if (a < 0) { 240 rotation += PI; 241 } 242 243 rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y); 244 } 245 246 static bool is_over_rotate_handle (Path p, double x, double y) { 247 double cx, cy, hx, hy; 248 double size = 10; 249 bool inx, iny; 250 251 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 252 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 253 254 hx = cos (rotation) * get_rotated_handle_length (); 255 hy = sin (rotation) * get_rotated_handle_length (); 256 257 inx = x - size * MainWindow.units <= cx + hx - 2.5 <= x + size * MainWindow.units; 258 iny = y - size * MainWindow.units <= cy + hy - 2.5 <= y + size * MainWindow.units; 259 260 return inx && iny; 261 } 262 263 static void draw_rotate_handle (Context cr) { 264 double cx, cy, hx, hy; 265 266 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 267 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 268 269 cr.save (); 270 Theme.color (cr, "Highlighted 1"); 271 cr.rectangle (cx - 2.5, cy - 2.5, 5, 5); 272 cr.fill (); 273 274 hx = cos (rotation) * get_rotated_handle_length (); 275 hy = sin (rotation) * get_rotated_handle_length (); 276 277 cr.set_line_width (1); 278 cr.move_to (cx, cy); 279 cr.line_to (cx + hx, cy + hy); 280 cr.stroke (); 281 282 Theme.color (cr, "Highlighted 1"); 283 cr.rectangle (cx + hx - 2.5, cy + hy - 2.5, 5, 5); 284 cr.fill (); 285 286 cr.restore (); 287 } 288 289 double get_resize_ratio (double px, double py) { 290 double ratio, x, y, w, h; 291 292 Glyph glyph = MainWindow.get_current_glyph (); 293 glyph.selection_boundaries (out x, out y, out w, out h); 294 295 ratio = 1; 296 297 if (Math.fabs (last_resize_y - py) > Math.fabs (last_resize_x - px)) { 298 ratio = 1 + (Glyph.path_coordinate_y (py) 299 - Glyph.path_coordinate_y (last_resize_y)) / h; 300 } else { 301 ratio = 1 + (Glyph.path_coordinate_x (px) 302 - Glyph.path_coordinate_x (last_resize_x)) / w; 303 } 304 305 return ratio; 306 } 307 308 public void resize_selected_paths (double ratio) { 309 Glyph g = MainWindow.get_current_glyph (); 310 resize_glyph (g, ratio, true); 311 } 312 313 public void resize_glyph (Glyph glyph, double ratio, bool selected = true) { 314 double resize_pos_x = 0; 315 double resize_pos_y = 0; 316 double selection_minx, selection_miny, dx, dy; 317 318 if (!selected) { 319 glyph.clear_active_paths (); 320 321 foreach (Path path in glyph.get_visible_paths ()) { 322 glyph.add_active_path (null, path); 323 } 324 } 325 326 get_selection_min (out resize_pos_x, out resize_pos_y); 327 328 // resize paths 329 foreach (Path selected_path in glyph.active_paths) { 330 selected_path.resize (ratio); 331 selected_path.reset_stroke (); 332 } 333 334 // move paths relative to the updated xmin and xmax 335 get_selection_min (out selection_minx, out selection_miny); 336 dx = resize_pos_x - selection_minx; 337 dy = resize_pos_y - selection_miny; 338 foreach (Path selected_path in glyph.active_paths) { 339 selected_path.move (dx, dy); 340 } 341 342 if (glyph.active_paths.size > 0) { 343 update_selection_box (); 344 objects_resized (selection_box_width, selection_box_height); 345 } 346 347 if (!selected) { 348 double w; 349 w = (ratio * glyph.get_width () - glyph.get_width ()) / 2.0; 350 glyph.left_limit -= w; 351 glyph.right_limit += w; 352 glyph.clear_active_paths (); 353 glyph.remove_lines (); 354 glyph.add_help_lines (); 355 } 356 } 357 358 void update_selection_box () { 359 MoveTool.update_boundaries_for_selection (); 360 MoveTool.get_selection_box_boundaries (out selection_box_center_x, 361 out selection_box_center_y, out selection_box_width, 362 out selection_box_height); 363 } 364 365 /** Move resize handle to pixel x,y. */ 366 void resize (double px, double py) { 367 double ratio; 368 369 ratio = get_resize_ratio (px, py); 370 371 if (ratio != 1) { 372 resize_selected_paths (ratio); 373 last_resize_x = px; 374 last_resize_y = py; 375 } 376 } 377 378 void get_selection_min (out double x, out double y) { 379 Glyph glyph = MainWindow.get_current_glyph (); 380 x = double.MAX; 381 y = double.MAX; 382 foreach (Path p in glyph.active_paths) { 383 if (p.xmin < x) { 384 x = p.xmin; 385 } 386 387 if (p.ymin < y) { 388 y = p.ymin; 389 } 390 } 391 } 392 393 bool can_resize (double x, double y) { 394 Glyph glyph = MainWindow.get_current_glyph (); 395 double h, w; 396 double ratio = get_resize_ratio (x, y); 397 398 foreach (Path selected_path in glyph.active_paths) { 399 h = selected_path.ymax - selected_path.ymin; 400 w = selected_path.xmax - selected_path.xmin; 401 402 if (selected_path.points.size <= 1) { 403 continue; 404 } 405 406 if (h * ratio < 1 || w * ratio < 1) { 407 return false; 408 } 409 } 410 411 return true; 412 } 413 414 bool is_over_resize_handle (Path p, double x, double y) { 415 double handle_x, handle_y; 416 get_reseize_handle_position (out handle_x, out handle_y); 417 return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; 418 } 419 420 public void skew (double skew) { 421 Glyph glyph = MainWindow.get_current_glyph (); 422 skew_glyph (glyph, skew, last_skew, true); 423 last_skew = skew; 424 } 425 426 public void skew_glyph (Glyph glyph, double skew, double last_skew, 427 bool selected_paths) { 428 429 double dx, nx, nw, dw, x, y, w, h; 430 double s = (skew - last_skew) / 100.0; 431 432 if (!selected_paths) { 433 glyph.clear_active_paths (); 434 435 foreach (Path path in glyph.get_visible_paths ()) { 436 glyph.add_active_path (null, path); 437 } 438 } 439 440 glyph.selection_boundaries (out x, out y, out w, out h); 441 442 foreach (Path path in glyph.active_paths) { 443 SvgParser.apply_matrix (path, 1, 0, s, 1, 0, 0); 444 path.skew = skew; 445 path.update_region_boundaries (); 446 } 447 448 glyph.selection_boundaries (out nx, out y, out nw, out h); 449 450 dx = -(nx - x); 451 452 foreach (Path p in glyph.active_paths) { 453 p.move (dx, 0); 454 p.reset_stroke (); 455 } 456 457 dw = (nw - w); 458 glyph.right_limit += dw; 459 glyph.remove_lines (); 460 glyph.add_help_lines (); 461 462 if (!selected_paths) { 463 glyph.clear_active_paths (); 464 } 465 } 466 } 467 468 } 469