.
1 /*
2 Copyright (C) 2012, 2013, 2014 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
17 namespace BirdFont {
18
19 public enum PointType {
20 NONE,
21 LINE_QUADRATIC, // line with quadratic handle
22 LINE_DOUBLE_CURVE, // line with two quadratic handles
23 LINE_CUBIC, // line with cubic handles
24 CUBIC,
25 DOUBLE_CURVE, // two quadratic points with a hidden point half way between the two line handles
26 QUADRATIC,
27 HIDDEN,
28 FLOATING,
29 END
30 }
31
32 public class EditPoint : GLib.Object {
33
34 public double x;
35 public double y;
36 public PointType type;
37
38 public unowned EditPoint? prev = null;
39 public unowned EditPoint? next = null;
40
41 public static uint NONE = 0;
42 public static uint ACTIVE = 1;
43 public static uint SELECTED = 1 << 1;
44 public static uint DELETED = 1 << 2;
45 public static uint TIE = 1 << 3;
46 public static uint REFLECTIVE = 1 << 4;
47
48 public static uint INTERSECTION = 1 << 5;
49 public static uint NEW_CORNER = 1 << 6;
50 public static uint STROKE_OFFSET = 1 << 7;
51 public static uint COUNTER_TO_OUTLINE = 1 << 8;
52 public static uint COPIED = 1 << 9;
53 public static uint REMOVE_PART = 1 << 10;
54 public static uint OVERLAY = 1 << 11;
55 public static uint CURVE = 1 << 12;
56 public static uint CURVE_KEEP = 1 << 13;
57 public static uint SEGMENT_END = 1 << 14;
58
59 public static uint ALL = 0xFFFFFF;
60
61 public uint flags = NONE;
62
63 public bool active_point {
64 get {
65 return (flags & ACTIVE) > 0;
66 }
67
68 set {
69 if (value) {
70 flags |= ACTIVE;
71 } else {
72 flags &= uint.MAX ^ ACTIVE;
73 }
74 }
75 }
76
77 public bool selected_point {
78 get {
79 return (flags & SELECTED) > 0;
80 }
81
82 set {
83 if (value) {
84 flags |= SELECTED;
85 } else {
86 flags &= uint.MAX ^ SELECTED;
87 }
88 }
89 }
90
91 public bool deleted {
92 get {
93 return (flags & DELETED) > 0;
94 }
95
96 set {
97 if (value) {
98 flags |= DELETED;
99 } else {
100 flags &= uint.MAX ^ DELETED;
101 }
102 }
103 }
104
105 public bool tie_handles {
106 get {
107 return (flags & TIE) > 0;
108 }
109
110 set {
111 if (value) {
112 flags |= TIE;
113 } else {
114 flags &= uint.MAX ^ TIE;
115 }
116 }
117 }
118
119 public bool reflective_point {
120 get {
121 return (flags & REFLECTIVE) > 0;
122 }
123
124 set {
125 if (value) {
126 flags |= REFLECTIVE;
127 } else {
128 flags &= uint.MAX ^ REFLECTIVE;
129 }
130 }
131 }
132
133 public int selected_handle = 0;
134
135 public EditPointHandle right_handle;
136 public EditPointHandle left_handle;
137
138 /** Set new position for control point without moving handles. */
139 public double independent_x {
140 get {
141 return x;
142 }
143
144 set {
145 double d = value - x;
146 x = value;
147 right_handle.x -= d;
148 left_handle.x -= d;
149 }
150 }
151
152 public double independent_y {
153 get {
154 return y;
155 }
156
157 set {
158 double d = value - y;
159 y = value;
160 right_handle.y -= d;
161 left_handle.y -= d;
162 }
163 }
164
165 public Color? color = null;
166
167 static int n_points = 0;
168
169 public EditPoint (double nx = 0, double ny = 0, PointType nt = PointType.NONE) {
170 x = nx;
171 y = ny;
172 type = nt;
173 active_point = false;
174
175 set_active (true);
176
177 if (nt == PointType.FLOATING) {
178 active_point = false;
179 }
180
181 right_handle = new EditPointHandle (this, 0, 7);
182 left_handle = new EditPointHandle (this, PI, 7);
183
184 if (unlikely (nx.is_nan () || ny.is_nan ())) {
185 warning (@"Invalid point at ($nx,$ny).");
186 x = 0;
187 y = 0;
188 }
189
190 n_points++;
191 }
192
193 ~EditPoint () {
194 n_points--;
195 }
196
197 public bool is_valid () {
198 return is_valid_position (x, y);
199 }
200
201 public static bool is_valid_position (double x, double y) {
202 return likely (x.is_finite () && y.is_finite ()
203 && x > Glyph.CANVAS_MIN && x < Glyph.CANVAS_MAX
204 && y > Glyph.CANVAS_MIN && y < Glyph.CANVAS_MAX);
205 }
206
207 public void set_point_type (PointType t) {
208 type = t;
209 }
210
211 public bool equals (EditPoint e) {
212 return e.x == x
213 && e.y == y
214 && get_right_handle ().x == e.get_right_handle ().x
215 && get_right_handle ().y == e.get_right_handle ().y
216 && get_left_handle ().x == e.get_left_handle ().x
217 && get_left_handle ().y == e.get_left_handle ().y;
218 }
219
220 /** Make handles symmetrical. */
221 public void set_reflective_handles (bool symmetrical) {
222 reflective_point = symmetrical;
223 }
224
225 /** Flip handles if next point on path is in the other direction.
226 * Used to recalculate handles after new point is inserted on a path.
227 */
228 public void recalculate_handles (double px, double py) {
229 double dr, dl;
230 EditPointHandle t;
231
232 if (next == null || get_next ().next != null) {
233 return;
234 }
235
236 if (unlikely (reflective_point || tie_handles)) {
237 warning ("Points on lines can't have tied handles.");
238 return;
239 }
240
241 px = get_next ().get_next ().x;
242 py = get_next ().get_next ().y;
243
244 dr = Math.sqrt (Math.pow (px - right_handle.x, 2) + Math.pow (py - right_handle.y, 2));
245 dl = Math.sqrt (Math.pow (px - left_handle.x, 2) + Math.pow (py - left_handle.y, 2));
246
247 // flip handles
248 if (dl < dr) {
249 t = right_handle;
250 right_handle = left_handle;
251 left_handle = t;
252 }
253 }
254
255 /** Set bezier points for linear paths. */
256 public void recalculate_linear_handles () {
257 unowned EditPointHandle h;
258 unowned EditPoint n;
259 double nx, ny;
260
261 return_if_fail (!is_null (right_handle) && !is_null (left_handle));
262
263 if (prev == null && next != null) {
264 // FIXME: prev = get_next ().last ();
265 }
266
267 // left handle
268 if (prev != null) {
269 n = get_prev ();
270 h = get_left_handle ();
271
272 return_if_fail (!is_null (n) && !is_null (h));
273
274 if (h.type == PointType.LINE_CUBIC) {
275 nx = x + ((n.x - x) / 3);
276 ny = y + ((n.y - y) / 3);
277 h.move_to_coordinate (nx, ny);
278 }
279
280 if (h.type == PointType.LINE_DOUBLE_CURVE) {
281 nx = x + ((n.x - x) / 4);
282 ny = y + ((n.y - y) / 4);
283 h.move_to_coordinate (nx, ny);
284 }
285
286 if (h.type == PointType.LINE_QUADRATIC) {
287 nx = x + ((n.x - x) / 2);
288 ny = y + ((n.y - y) / 2);
289 h.move_to_coordinate (nx, ny);
290 }
291
292 // the other side
293 h = n.get_right_handle ();
294 return_if_fail (!is_null (h) && !is_null (h));
295
296 if (h.type == PointType.LINE_DOUBLE_CURVE) {
297 nx = n.x + ((x - n.x) / 4);
298 ny = n.y + ((y - n.y) / 4);
299 h.move_to_coordinate (nx, ny);
300 }
301
302 if (h.type == PointType.LINE_CUBIC) {
303 nx = n.x + ((x - n.x) / 3);
304 ny = n.y + ((y - n.y) / 3);
305 h.move_to_coordinate (nx, ny);
306 }
307
308 if (h.type == PointType.LINE_QUADRATIC) {
309 nx = n.x + ((x - n.x) / 2);
310 ny = n.y + ((y - n.y) / 2);
311 h.move_to_coordinate (nx, ny);
312 }
313 }
314
315 // right handle
316 if (next != null) {
317 n = get_next ();
318 h = get_right_handle ();
319
320 return_if_fail (!is_null (n) && !is_null (h));
321
322 if (h.type == PointType.LINE_CUBIC) {
323 nx = x + ((n.x - x) / 3);
324 ny = y + ((n.y - y) / 3);
325
326 h.move_to_coordinate (nx, ny);
327 }
328
329 if (h.type == PointType.LINE_DOUBLE_CURVE) {
330 nx = x + ((n.x - x) / 4);
331 ny = y + ((n.y - y) / 4);
332
333 h.move_to_coordinate (nx, ny);
334 }
335
336 if (h.type == PointType.LINE_QUADRATIC) {
337 nx = x + ((n.x - x) / 2);
338 ny = y + ((n.y - y) / 2);
339
340 h.move_to_coordinate (nx, ny);
341 }
342
343 h = n.get_left_handle ();
344 return_if_fail (!is_null (h));
345
346 if (h.type == PointType.LINE_CUBIC) {
347 nx = n.x + ((x - n.x) / 3);
348 ny = n.y + ((y - n.y) / 3);
349
350 h.move_to_coordinate (nx, ny);
351 }
352
353 if (h.type == PointType.LINE_DOUBLE_CURVE) {
354 nx = n.x + ((x - n.x) / 4);
355 ny = n.y + ((y - n.y) / 4);
356
357 h.move_to_coordinate (nx, ny);
358 }
359
360 if (h.type == PointType.LINE_QUADRATIC) {
361 nx = n.x + ((x - n.x) / 2);
362 ny = n.y + ((y - n.y) / 2);
363
364 h.move_to_coordinate (nx, ny);
365 }
366 }
367 }
368
369 public bool is_clockwise () {
370 return get_direction () >= 0;
371 }
372
373 public double get_direction () {
374 if (prev == null) {
375 return 0;
376 }
377
378 return (x - get_prev ().x) * (y + get_prev ().y);
379 }
380
381 public void set_tie_handle (bool tie) {
382 tie_handles = tie;
383 }
384
385 public void process_symmetrical_handles () {
386 process_tied_handle ();
387 right_handle.process_symmetrical_handle ();
388 left_handle.process_symmetrical_handle ();
389 }
390
391 public static void convert_from_line_to_curve (EditPointHandle h) {
392 switch (h.type) {
393 case PointType.LINE_QUADRATIC:
394 h.type = PointType.QUADRATIC;
395 break;
396 case PointType.LINE_DOUBLE_CURVE:
397 h.type = PointType.DOUBLE_CURVE;
398 break;
399 case PointType.LINE_CUBIC:
400 h.type = PointType.CUBIC;
401 break;
402 default:
403 break;
404 }
405 }
406
407 /** This can only be performed if the path has been closed. */
408 public void process_tied_handle ()
409 requires (next != null && prev != null) {
410 double a, b, c, length, angle;
411 EditPointHandle eh;
412 EditPointHandle prev_rh, next_lh;
413
414 eh = right_handle;
415
416 a = left_handle.x - right_handle.x;
417 b = left_handle.y - right_handle.y;
418 c = a * a + b * b;
419
420 if (c == 0) {
421 return;
422 }
423
424 length = sqrt (fabs (c));
425
426 if (right_handle.y < left_handle.y) {
427 angle = acos (a / length) + PI;
428 } else {
429 angle = -acos (a / length) + PI;
430 }
431
432 prev_rh = get_prev ().get_right_handle ();
433 next_lh = get_next ().get_left_handle ();
434
435 convert_from_line_to_curve (next_lh);
436 convert_from_line_to_curve (prev_rh);
437 convert_from_line_to_curve (left_handle);
438 convert_from_line_to_curve (right_handle);
439
440 right_handle.angle = angle;
441 left_handle.angle = angle - PI;
442
443 set_tie_handle (true);
444 eh.move_to_coordinate (right_handle.x, right_handle.y);
445 }
446
447 public EditPoint copy () {
448 EditPoint new_point = new EditPoint ();
449
450 new_point.x = x;
451 new_point.y = y;
452
453 new_point.type = type;
454 new_point.flags = flags;
455
456 new_point.right_handle.angle = right_handle.angle;
457 new_point.right_handle.length = right_handle.length;
458 new_point.right_handle.type = right_handle.type;
459
460 new_point.left_handle.angle = left_handle.angle;
461 new_point.left_handle.length = left_handle.length;
462 new_point.left_handle.type = left_handle.type;
463
464 new_point.color = color;
465
466 return new_point;
467 }
468
469 public double get_distance (double x, double y) {
470 return Path.distance (this.x, x, this.y, y);
471 }
472
473 public unowned EditPointHandle get_left_handle () {
474 if (unlikely (is_null (left_handle))) {
475 warning ("EditPoint.left_handle is null");
476 }
477
478 return left_handle;
479 }
480
481 public unowned EditPointHandle get_right_handle () {
482 if (unlikely (is_null (right_handle))) {
483 warning ("EditPoint.right_handle is null");
484 }
485
486 return right_handle;
487 }
488
489 public unowned EditPoint get_prev () {
490 if (unlikely (prev == null)) {
491 warning ("EditPoint.prev is null");
492 }
493
494 return (!) prev;
495 }
496
497 public unowned EditPoint get_next () {
498 if (unlikely (next == null)) {
499 warning ("EditPoint.next is null");
500 }
501
502 return (!) next;
503 }
504
505 public unowned EditPoint get_link_item () {
506 return this;
507 }
508
509 public void set_independet_position (double tx, double ty) {
510 double rx, ry, lx, ly;
511
512 rx = right_handle.x;
513 ry = right_handle.y;
514
515 lx = left_handle.x;
516 ly = left_handle.y;
517
518 set_position (tx, ty);
519
520 left_handle.move_to_coordinate (lx, ly);
521 right_handle.move_to_coordinate (rx, ry);
522 }
523
524 public void set_position (double tx, double ty) {
525 EditPoint p, n;
526
527 x = tx;
528 y = ty;
529
530 if (unlikely (tx.is_nan () || ty.is_nan ())) {
531 warning (@"Invalid point at ($tx,$ty).");
532 x = 0;
533 y = 0;
534 }
535
536 // move connected quadratic handle
537 if (right_handle.type == PointType.QUADRATIC) {
538 if (next != null) {
539 n = get_next ();
540 n.set_tie_handle (false);
541 n.set_reflective_handles (false);
542 n.left_handle.move_to_coordinate_internal (right_handle.x, right_handle.y);
543 }
544 }
545
546 if (left_handle.type == PointType.QUADRATIC) {
547 if (prev != null && !get_prev ().is_selected ()) {
548 p = get_prev ();
549 p.set_tie_handle (false);
550 p.set_reflective_handles (false);
551 p.right_handle.move_to_coordinate (left_handle.x, left_handle.y);
552 }
553 }
554 }
555
556 public static void to_coordinate (ref double x, ref double y) {
557 double xc, yc, xt, yt, ivz;
558 Glyph g = MainWindow.get_current_glyph ();
559
560 ivz = 1 / g.view_zoom;
561
562 xc = g.allocation.width / 2.0;
563 yc = g.allocation.height / 2.0;
564
565 x *= ivz;
566 y *= ivz;
567
568 xt = x - xc + g.view_offset_x;
569 yt = yc - y - g.view_offset_y;
570
571 x = xt;
572 y = yt;
573 }
574
575 public bool is_selected () {
576 return selected_point;
577 }
578
579 public void set_selected (bool s) {
580 selected_point = s;
581 }
582
583 public bool set_active (bool active) {
584 bool update = (this.active_point != active);
585
586 if (update) {
587 this.active_point = active;
588 }
589
590 return update;
591 }
592
593 public void convert_to_line () {
594 left_handle.convert_to_line ();
595 right_handle.convert_to_line ();
596 }
597
598 public void convert_to_curve () {
599 left_handle.convert_to_curve ();
600 right_handle.convert_to_curve ();
601 }
602
603 public string to_string () {
604 StringBuilder s = new StringBuilder ();
605
606 if (deleted) {
607 s.append (@"Deleted ");
608 }
609
610 s.append (@"Control point: $x, $y\n");
611 s.append (@"Left handle: angle: $(left_handle.angle) l: $(left_handle.length)\n");
612 s.append (@"Right handle: angle: $(right_handle.angle) l: $(right_handle.length)\n");
613 s.append (@"Type: $type Left: $(left_handle.type) Right: $(right_handle.type)\n".replace ("BIRD_FONT_POINT_TYPE_", ""));
614
615 return s.str;
616 }
617
618 public double max_x () {
619 double mx = x;
620
621 if (get_right_handle ().x > mx) {
622 mx = get_right_handle ().x;
623 }
624
625 if (get_left_handle ().x > mx) {
626 mx = get_left_handle ().x;
627 }
628
629 return mx;
630 }
631
632 public double min_x () {
633 double mx = x;
634
635 if (get_right_handle ().x < mx) {
636 mx = get_right_handle ().x;
637 }
638
639 if (get_left_handle ().x < mx) {
640 mx = get_left_handle ().x;
641 }
642
643 return mx;
644 }
645
646 public double max_y () {
647 double my = y;
648
649 if (get_right_handle ().y > my) {
650 my = get_right_handle ().y;
651 }
652
653 if (get_left_handle ().y > my) {
654 my = get_left_handle ().y;
655 }
656
657 return my;
658 }
659
660 public double min_y () {
661 double my = y;
662
663 if (get_right_handle ().y < my) {
664 my = get_right_handle ().y;
665 }
666
667 if (get_left_handle ().y < my) {
668 my = get_left_handle ().y;
669 }
670
671 return my;
672 }
673 }
674
675 }
676