.
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