.
1 /*
2 Copyright (C) 2013 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 ImageSurface? resize_handle;
30
31 static double selection_box_width = 0;
32 static double selection_box_height = 0;
33 static double selection_box_center_x = 0;
34 static double selection_box_center_y = 0;
35
36 static bool rotate_path = false;
37 static double last_rotate_y;
38 static double rotation = 0;
39 static double last_rotate = 0;
40
41 public double last_skew = 0;
42
43 public signal void objects_rotated (double angle);
44 public signal void objects_resized (double width, double height);
45
46 public ResizeTool (string n) {
47 base (n, t_("Resize and rotate paths"));
48
49 resize_handle = Icons.get_icon ("resize_handle.png");
50
51 select_action.connect((self) => {
52 });
53
54 deselect_action.connect((self) => {
55 });
56
57 press_action.connect((self, b, x, y) => {
58 Path last_path;
59 Glyph glyph;
60
61 glyph = MainWindow.get_current_glyph ();
62 glyph.store_undo_state ();
63
64 foreach (Path p in glyph.active_paths) {
65 if (is_over_resize_handle (p, x, y)) {
66 resize_path = true;
67 resized_path = p;
68 last_resize_x = x;
69 last_resize_y = y;
70 return;
71 }
72 }
73
74 if (resized_path != null) {
75 if (is_over_resize_handle ((!) resized_path, x, y)) {
76 resize_path = true;
77 last_resize_x = x;
78 last_resize_y = y;
79 return;
80 }
81 }
82
83 foreach (Path p in glyph.active_paths) {
84 if (is_over_rotate_handle (p, x, y)) {
85 rotate_path = true;
86 return;
87 }
88 }
89
90 if (glyph.active_paths.size > 0) {
91 last_path = glyph.active_paths.get (glyph.active_paths.size - 1);
92 last_rotate = last_path.rotation ;
93 }
94
95 rotation = last_rotate;
96 last_resize_x = x;
97 last_rotate_y = y;
98
99 MoveTool.press (b, x, y);
100
101 move_paths = true;
102 });
103
104 release_action.connect((self, b, x, y) => {
105 resize_path = false;
106 rotate_path = false;
107 move_paths = false;
108 DrawingTools.move_tool.release (b, x, y);
109 });
110
111 move_action.connect ((self, x, y) => {
112 if (resize_path && can_resize (x, y)) {
113 resize (x, y);
114 }
115
116 if (rotate_path) {
117 rotate (x, y);
118 }
119
120 if (!rotate_path) {
121 MoveTool.update_boundaries_for_selection ();
122 MoveTool.get_selection_box_boundaries (out selection_box_center_x,
123 out selection_box_center_y, out selection_box_width,
124 out selection_box_height);
125 }
126
127 if (move_paths) {
128 GlyphCanvas.redraw ();
129 }
130
131 DrawingTools.move_tool.move (x, y);
132 });
133
134 draw_action.connect ((self, cr, glyph) => {
135 Glyph g = MainWindow.get_current_glyph ();
136 ImageSurface resize_img = (!) resize_handle;
137
138 foreach (Path p in g.active_paths) {
139 cr.set_source_surface (resize_img, Glyph.reverse_path_coordinate_x (p.xmax) - 10, Glyph.reverse_path_coordinate_y (p.ymax) - 10);
140 cr.paint ();
141 }
142
143 if (g.active_paths.size > 0) {
144 draw_rotate_handle (cr);
145 }
146
147 MoveTool.draw_actions (cr);
148 });
149
150 key_press_action.connect ((self, keyval) => {
151 DrawingTools.move_tool.key_press (keyval);
152 });
153
154 }
155
156 public void signal_objects_rotated () {
157 objects_rotated (rotation * (180 / PI));
158 }
159
160 public void rotate_selected_paths (double angle, double cx, double cy) {
161 Glyph glyph = MainWindow.get_current_glyph ();
162 double dx, dy, xc2, yc2, w, h;
163 Path last_path;
164 foreach (Path p in glyph.active_paths) {
165 p.rotate (angle, cx, cy);
166 }
167
168 MoveTool.get_selection_box_boundaries (out xc2, out yc2, out w, out h);
169
170 dx = -(xc2 - cx);
171 dy = -(yc2 - cy);
172
173 foreach (Path p in glyph.active_paths) {
174 p.move (dx, dy);
175 }
176
177 last_rotate = rotation;
178
179 MoveTool.update_selection_boundaries ();
180
181 if (glyph.active_paths.size > 0) {
182 last_path = glyph.active_paths.get (glyph.active_paths.size - 1);
183 rotation = last_path.rotation;
184
185 if (rotation > PI) {
186 rotation -= 2 * PI;
187 }
188
189 last_rotate = rotation;
190 signal_objects_rotated ();
191 }
192 }
193
194 /** Move rotate handle to pixel x,y. */
195 void rotate (double x, double y) {
196 double cx, cy, xc, yc, a, b;
197
198 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x);
199 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y);
200 xc = selection_box_center_x;
201 yc = selection_box_center_y;
202
203 a = x - cx;
204 b = y - cy;
205
206 rotation = atan (b / a);
207
208 if (a < 0) {
209 rotation += PI;
210 }
211
212 rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y);
213 }
214
215 static bool is_over_rotate_handle (Path p, double x, double y) {
216 double cx, cy, hx, hy;
217 double size = 10;
218 bool inx, iny;
219
220 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x);
221 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y);
222
223 hx = cos (rotation) * 75;
224 hy = sin (rotation) * 75;
225
226 inx = x - size * MainWindow.units <= cx + hx - 2.5 <= x + size * MainWindow.units;
227 iny = y - size * MainWindow.units <= cy + hy - 2.5 <= y + size * MainWindow.units;
228
229 return inx && iny;
230 }
231
232 static void draw_rotate_handle (Context cr) {
233 double cx, cy, hx, hy;
234
235 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x);
236 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y);
237
238 cr.save ();
239 Theme.color (cr, "Highlight 1");
240 cr.rectangle (cx - 2.5, cy - 2.5, 5, 5);
241 cr.fill ();
242
243 hx = cos (rotation) * 75;
244 hy = sin (rotation) * 75;
245
246 cr.set_line_width (1);
247 cr.move_to (cx, cy);
248 cr.line_to (cx + hx, cy + hy);
249 cr.stroke ();
250
251 Theme.color (cr, "Highlight 1");
252 cr.rectangle (cx + hx - 2.5, cy + hy - 2.5, 5, 5);
253 cr.fill ();
254
255 cr.restore ();
256 }
257
258 double get_resize_ratio (double px, double py) {
259 double ratio, x, y, w, h;
260
261 Glyph glyph = MainWindow.get_current_glyph ();
262 glyph.selection_boundaries (out x, out y, out w, out h);
263
264 ratio = 1;
265
266 if (Math.fabs (last_resize_y - py) > Math.fabs (last_resize_x - px)) {
267 ratio = 1 + (Glyph.path_coordinate_y (py)
268 - Glyph.path_coordinate_y (last_resize_y)) / h;
269 } else {
270 ratio = 1 + (Glyph.path_coordinate_x (px)
271 - Glyph.path_coordinate_x (last_resize_x)) / w;
272 }
273
274 return ratio;
275 }
276
277 public void resize_selected_paths (double ratio) {
278 double resize_pos_x = 0;
279 double resize_pos_y = 0;
280 Glyph glyph = MainWindow.get_current_glyph ();
281 double selection_minx, selection_miny, dx, dy;
282
283 get_selection_min (out resize_pos_x, out resize_pos_y);
284
285 // resize paths
286 foreach (Path selected_path in glyph.active_paths) {
287 selected_path.resize (ratio);
288 }
289
290 // move paths relative to the updated xmin and xmax
291 get_selection_min (out selection_minx, out selection_miny);
292 dx = resize_pos_x - selection_minx;
293 dy = resize_pos_y - selection_miny;
294 foreach (Path selected_path in glyph.active_paths) {
295 selected_path.move (dx, dy);
296 }
297
298 if (glyph.active_paths.size > 0) {
299 MoveTool.get_selection_box_boundaries (out selection_box_center_x,
300 out selection_box_center_y, out selection_box_width,
301 out selection_box_height);
302 objects_resized (selection_box_width, selection_box_height);
303 }
304 }
305
306 /** Move resize handle to pixel x,y. */
307 void resize (double px, double py) {
308 double ratio;
309
310 ratio = get_resize_ratio (px, py);
311
312 if (ratio != 1) {
313 resize_selected_paths (ratio);
314 last_resize_x = px;
315 last_resize_y = py;
316 }
317 }
318
319 void get_selection_min (out double x, out double y) {
320 Glyph glyph = MainWindow.get_current_glyph ();
321 x = double.MAX;
322 y = double.MAX;
323 foreach (Path p in glyph.active_paths) {
324 if (p.xmin < x) {
325 x = p.xmin;
326 }
327
328 if (p.ymin < y) {
329 y = p.ymin;
330 }
331 }
332 }
333
334 bool can_resize (double x, double y) {
335 Glyph glyph = MainWindow.get_current_glyph ();
336 double h, w;
337 double ratio = get_resize_ratio (x, y);
338
339 foreach (Path selected_path in glyph.active_paths) {
340 h = selected_path.ymax - selected_path.ymin;
341 w = selected_path.xmax - selected_path.xmin;
342
343 if (selected_path.points.size <= 1) {
344 continue;
345 }
346
347 if (h * ratio < 1 || w * ratio < 1) {
348 return false;
349 }
350 }
351
352 return true;
353 }
354
355 bool is_over_resize_handle (Path p, double x, double y) {
356 double handle_x = Math.fabs (Glyph.reverse_path_coordinate_x (p.xmax));
357 double handle_y = Math.fabs (Glyph.reverse_path_coordinate_y (p.ymax));
358 return fabs (handle_x - x + 10) < 20 * MainWindow.units && fabs (handle_y - y + 10) < 20 * MainWindow.units;
359 }
360
361 public void skew (double skew) {
362 Glyph glyph = MainWindow.get_current_glyph ();
363 double dx, nx, nw, dw, x, y, w, h;
364 double s = (skew - last_skew) / 100.0;
365
366 glyph.selection_boundaries (out x, out y, out w, out h);
367
368 foreach (Path path in glyph.active_paths) {
369 SvgParser.apply_matrix (path, 1, 0, s, 1, 0, 0);
370 path.skew = skew;
371 path.update_region_boundaries ();
372 }
373
374 glyph.selection_boundaries (out nx, out y, out nw, out h);
375
376 dx = -(nx - x);
377
378 foreach (Path p in glyph.active_paths) {
379 p.move (dx, 0);
380 }
381
382 last_skew = skew;
383
384 dw = (nw - w);
385 glyph.right_limit += dw;
386 glyph.remove_lines ();
387 glyph.add_help_lines ();
388 }
389 }
390
391 }
392