.
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