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.
Fix self intersecting stroke
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 111 move_action.connect ((self, x, y) => { 112 Glyph glyph; 113 114 if (resize_path && can_resize (x, y)) { 115 resize (x, y); 116 } 117 118 if (rotate_path) { 119 rotate (x, y); 120 } 121 122 if (move_paths || rotate_path || resize_path) { 123 glyph = MainWindow.get_current_glyph (); 124 125 foreach (Path selected_path in glyph.active_paths) { 126 selected_path.reset_stroke (); 127 } 128 129 update_selection_box (); 130 GlyphCanvas.redraw (); 131 } 132 133 DrawingTools.move_tool.move (x, y); 134 }); 135 136 draw_action.connect ((self, cr, glyph) => { 137 Text handle; 138 Glyph g = MainWindow.get_current_glyph (); 139 140 if (!rotate_path) { 141 handle = new Text ("resize_handle", 60 * MainWindow.units); 142 handle.load_font ("icons.bf"); 143 144 get_reseize_handle_position (out handle.widget_x, out handle.widget_y); 145 146 handle.widget_x -= handle.get_sidebearing_extent () / 2; 147 handle.widget_y -= handle.get_height () / 2; 148 149 Theme.text_color (handle, "Highlighted 1"); 150 handle.draw (cr); 151 } 152 153 if (!resize_path && g.active_paths.size > 0) { 154 draw_rotate_handle (cr); 155 } 156 157 MoveTool.draw_actions (cr); 158 }); 159 160 key_press_action.connect ((self, keyval) => { 161 DrawingTools.move_tool.key_down (keyval); 162 }); 163 } 164 165 public static void get_reseize_handle_position (out double px, out double py) { 166 px = Glyph.reverse_path_coordinate_x (selection_box_center_x + selection_box_width / 2); 167 py = Glyph.reverse_path_coordinate_y (selection_box_center_y + selection_box_height / 2); 168 } 169 170 public static double get_rotated_handle_length () { 171 double s, hx, hy; 172 double d; 173 174 s = fmin (selection_box_width, selection_box_height) * 1.1; 175 d = (s / Glyph.ivz ()) / 2; 176 177 hx = cos (rotation) * d; 178 hy = sin (rotation) * d; 179 180 return d; 181 } 182 183 public void signal_objects_rotated () { 184 objects_rotated (rotation * (180 / PI)); 185 } 186 187 public void rotate_selected_paths (double angle, double cx, double cy) { 188 Glyph glyph = MainWindow.get_current_glyph (); 189 double dx, dy, xc2, yc2, w, h; 190 Path last_path; 191 foreach (Path p in glyph.active_paths) { 192 p.rotate (angle, cx, cy); 193 } 194 195 MoveTool.get_selection_box_boundaries (out xc2, out yc2, out w, out h); 196 197 dx = -(xc2 - cx); 198 dy = -(yc2 - cy); 199 200 foreach (Path p in glyph.active_paths) { 201 p.move (dx, dy); 202 } 203 204 last_rotate = rotation; 205 206 MoveTool.update_selection_boundaries (); 207 208 if (glyph.active_paths.size > 0) { 209 last_path = glyph.active_paths.get (glyph.active_paths.size - 1); 210 rotation = last_path.rotation; 211 212 if (rotation > PI) { 213 rotation -= 2 * PI; 214 } 215 216 last_rotate = rotation; 217 signal_objects_rotated (); 218 } 219 } 220 221 /** Move rotate handle to pixel x,y. */ 222 void rotate (double x, double y) { 223 double cx, cy, xc, yc, a, b; 224 225 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 226 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 227 xc = selection_box_center_x; 228 yc = selection_box_center_y; 229 230 a = x - cx; 231 b = y - cy; 232 233 rotation = atan (b / a); 234 235 if (a < 0) { 236 rotation += PI; 237 } 238 239 rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y); 240 } 241 242 static bool is_over_rotate_handle (Path p, double x, double y) { 243 double cx, cy, hx, hy; 244 double size = 10; 245 bool inx, iny; 246 247 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 248 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 249 250 hx = cos (rotation) * get_rotated_handle_length (); 251 hy = sin (rotation) * get_rotated_handle_length (); 252 253 inx = x - size * MainWindow.units <= cx + hx - 2.5 <= x + size * MainWindow.units; 254 iny = y - size * MainWindow.units <= cy + hy - 2.5 <= y + size * MainWindow.units; 255 256 return inx && iny; 257 } 258 259 static void draw_rotate_handle (Context cr) { 260 double cx, cy, hx, hy; 261 262 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 263 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 264 265 cr.save (); 266 Theme.color (cr, "Highlighted 1"); 267 cr.rectangle (cx - 2.5, cy - 2.5, 5, 5); 268 cr.fill (); 269 270 hx = cos (rotation) * get_rotated_handle_length (); 271 hy = sin (rotation) * get_rotated_handle_length (); 272 273 cr.set_line_width (1); 274 cr.move_to (cx, cy); 275 cr.line_to (cx + hx, cy + hy); 276 cr.stroke (); 277 278 Theme.color (cr, "Highlighted 1"); 279 cr.rectangle (cx + hx - 2.5, cy + hy - 2.5, 5, 5); 280 cr.fill (); 281 282 cr.restore (); 283 } 284 285 double get_resize_ratio (double px, double py) { 286 double ratio, x, y, w, h; 287 288 Glyph glyph = MainWindow.get_current_glyph (); 289 glyph.selection_boundaries (out x, out y, out w, out h); 290 291 ratio = 1; 292 293 if (Math.fabs (last_resize_y - py) > Math.fabs (last_resize_x - px)) { 294 ratio = 1 + (Glyph.path_coordinate_y (py) 295 - Glyph.path_coordinate_y (last_resize_y)) / h; 296 } else { 297 ratio = 1 + (Glyph.path_coordinate_x (px) 298 - Glyph.path_coordinate_x (last_resize_x)) / w; 299 } 300 301 return ratio; 302 } 303 304 public void resize_selected_paths (double ratio) { 305 double resize_pos_x = 0; 306 double resize_pos_y = 0; 307 Glyph glyph = MainWindow.get_current_glyph (); 308 double selection_minx, selection_miny, dx, dy; 309 310 get_selection_min (out resize_pos_x, out resize_pos_y); 311 312 // resize paths 313 foreach (Path selected_path in glyph.active_paths) { 314 selected_path.resize (ratio); 315 } 316 317 // move paths relative to the updated xmin and xmax 318 get_selection_min (out selection_minx, out selection_miny); 319 dx = resize_pos_x - selection_minx; 320 dy = resize_pos_y - selection_miny; 321 foreach (Path selected_path in glyph.active_paths) { 322 selected_path.move (dx, dy); 323 } 324 325 if (glyph.active_paths.size > 0) { 326 update_selection_box (); 327 objects_resized (selection_box_width, selection_box_height); 328 } 329 } 330 331 void update_selection_box () { 332 MoveTool.update_boundaries_for_selection (); 333 MoveTool.get_selection_box_boundaries (out selection_box_center_x, 334 out selection_box_center_y, out selection_box_width, 335 out selection_box_height); 336 } 337 338 /** Move resize handle to pixel x,y. */ 339 void resize (double px, double py) { 340 double ratio; 341 342 ratio = get_resize_ratio (px, py); 343 344 if (ratio != 1) { 345 resize_selected_paths (ratio); 346 last_resize_x = px; 347 last_resize_y = py; 348 } 349 } 350 351 void get_selection_min (out double x, out double y) { 352 Glyph glyph = MainWindow.get_current_glyph (); 353 x = double.MAX; 354 y = double.MAX; 355 foreach (Path p in glyph.active_paths) { 356 if (p.xmin < x) { 357 x = p.xmin; 358 } 359 360 if (p.ymin < y) { 361 y = p.ymin; 362 } 363 } 364 } 365 366 bool can_resize (double x, double y) { 367 Glyph glyph = MainWindow.get_current_glyph (); 368 double h, w; 369 double ratio = get_resize_ratio (x, y); 370 371 foreach (Path selected_path in glyph.active_paths) { 372 h = selected_path.ymax - selected_path.ymin; 373 w = selected_path.xmax - selected_path.xmin; 374 375 if (selected_path.points.size <= 1) { 376 continue; 377 } 378 379 if (h * ratio < 1 || w * ratio < 1) { 380 return false; 381 } 382 } 383 384 return true; 385 } 386 387 bool is_over_resize_handle (Path p, double x, double y) { 388 double handle_x, handle_y; 389 get_reseize_handle_position (out handle_x, out handle_y); 390 return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; 391 } 392 393 public void skew (double skew) { 394 Glyph glyph = MainWindow.get_current_glyph (); 395 double dx, nx, nw, dw, x, y, w, h; 396 double s = (skew - last_skew) / 100.0; 397 398 glyph.selection_boundaries (out x, out y, out w, out h); 399 400 foreach (Path path in glyph.active_paths) { 401 SvgParser.apply_matrix (path, 1, 0, s, 1, 0, 0); 402 path.skew = skew; 403 path.update_region_boundaries (); 404 } 405 406 glyph.selection_boundaries (out nx, out y, out nw, out h); 407 408 dx = -(nx - x); 409 410 foreach (Path p in glyph.active_paths) { 411 p.move (dx, 0); 412 } 413 414 last_skew = skew; 415 416 dw = (nw - w); 417 glyph.right_limit += dw; 418 glyph.remove_lines (); 419 glyph.add_help_lines (); 420 } 421 } 422 423 } 424