.
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 print (@"n_points: $(n_points)\n");
192 }
193
194 ~EditPoint () {
195 n_points--;
196 }
197
198 public bool is_valid () {
199 return is_valid_position (x, y);
200 }
201
202 public static bool is_valid_position (double x, double y) {
203 return likely (x.is_finite () && y.is_finite ()
204 && x > Glyph.CANVAS_MIN && x < Glyph.CANVAS_MAX
205 && y > Glyph.CANVAS_MIN && y < Glyph.CANVAS_MAX);
206 }
207
208 public void set_point_type (PointType t) {
209 type = t;
210 }
211
212 public bool equals (EditPoint e) {
213 return e.x == x
214 && e.y == y
215 && get_right_handle ().x == e.get_right_handle ().x
216 && get_right_handle ().y == e.get_right_handle ().y
217 && get_left_handle ().x == e.get_left_handle ().x
218 && get_left_handle ().y == e.get_left_handle ().y;
219 }
220
221 /** Make handles symmetrical. */
222 public void set_reflective_handles (bool symmetrical) {
223 reflective_point = symmetrical;
224 }
225
226 /** Flip handles if next point on path is in the other direction.
227 * Used to recalculate handles after new point is inserted on a path.
228 */
229 public void recalculate_handles (double px, double py) {
230 double dr, dl;
231 EditPointHandle t;
232
233 if (next == null || get_next ().next != null) {
234 return;
235 }
236
237 if (unlikely (reflective_point || tie_handles)) {
238 warning ("Points on lines can't have tied handles.");
239 return;
240 }
241
242 px = get_next ().get_next ().x;
243 py = get_next ().get_next ().y;
244
245 dr = Math.sqrt (Math.pow (px - right_handle.x, 2) + Math.pow (py - right_handle.y, 2));
246 dl = Math.sqrt (Math.pow (px - left_handle.x, 2) + Math.pow (py - left_handle.y, 2));
247
248 // flip handles
249 if (dl < dr) {
250 t = right_handle;
251 right_handle = left_handle;
252 left_handle = t;
253 }
254 }
255
256 /** Set bezier points for linear paths. */
257 public void recalculate_linear_handles () {
258 unowned EditPointHandle h;
259 unowned EditPoint n;
260 double nx, ny;
261
262 return_if_fail (!is_null (right_handle) && !is_null (left_handle));
263
264 if (prev == null && next != null) {
265 // FIXME: prev = get_next ().last ();
266 }
267
268 // left handle
269 if (prev != null) {
270 n = get_prev ();
271 h = get_left_handle ();
272
273 return_if_fail (!is_null (n) && !is_null (h));
274
275 if (h.type == PointType.LINE_CUBIC) {
276 nx = x + ((n.x - x) / 3);
277 ny = y + ((n.y - y) / 3);
278 h.move_to_coordinate (nx, ny);
279 }
280
281 if (h.type == PointType.LINE_DOUBLE_CURVE) {
282 nx = x + ((n.x - x) / 4);
283 ny = y + ((n.y - y) / 4);
284 h.move_to_coordinate (nx, ny);
285 }
286
287 if (h.type == PointType.LINE_QUADRATIC) {
288 nx = x + ((n.x - x) / 2);
289 ny = y + ((n.y - y) / 2);
290 h.move_to_coordinate (nx, ny);
291 }
292
293 // the other side
294 h = n.get_right_handle ();
295 return_if_fail (!is_null (h) && !is_null (h));
296
297 if (h.type == PointType.LINE_DOUBLE_CURVE) {
298 nx = n.x + ((x - n.x) / 4);
299 ny = n.y + ((y - n.y) / 4);
300 h.move_to_coordinate (nx, ny);
301 }
302
303 if (h.type == PointType.LINE_CUBIC) {
304 nx = n.x + ((x - n.x) / 3);
305 ny = n.y + ((y - n.y) / 3);
306 h.move_to_coordinate (nx, ny);
307 }
308
309 if (h.type == PointType.LINE_QUADRATIC) {
310 nx = n.x + ((x - n.x) / 2);
311 ny = n.y + ((y - n.y) / 2);
312 h.move_to_coordinate (nx, ny);
313 }
314 }
315
316 // right handle
317 if (next != null) {
318 n = get_next ();
319 h = get_right_handle ();
320
321 return_if_fail (!is_null (n) && !is_null (h));
322
323 if (h.type == PointType.LINE_CUBIC) {
324 nx = x + ((n.x - x) / 3);
325 ny = y + ((n.y - y) / 3);
326
327 h.move_to_coordinate (nx, ny);
328 }
329
330 if (h.type == PointType.LINE_DOUBLE_CURVE) {
331 nx = x + ((n.x - x) / 4);
332 ny = y + ((n.y - y) / 4);
333
334 h.move_to_coordinate (nx, ny);
335 }
336
337 if (h.type == PointType.LINE_QUADRATIC) {
338 nx = x + ((n.x - x) / 2);
339 ny = y + ((n.y - y) / 2);
340
341 h.move_to_coordinate (nx, ny);
342 }
343
344 h = n.get_left_handle ();
345 return_if_fail (!is_null (h));
346
347 if (h.type == PointType.LINE_CUBIC) {
348 nx = n.x + ((x - n.x) / 3);
349 ny = n.y + ((y - n.y) / 3);
350
351 h.move_to_coordinate (nx, ny);
352 }
353
354 if (h.type == PointType.LINE_DOUBLE_CURVE) {
355 nx = n.x + ((x - n.x) / 4);
356 ny = n.y + ((y - n.y) / 4);
357
358 h.move_to_coordinate (nx, ny);
359 }
360
361 if (h.type == PointType.LINE_QUADRATIC) {
362 nx = n.x + ((x - n.x) / 2);
363 ny = n.y + ((y - n.y) / 2);
364
365 h.move_to_coordinate (nx, ny);
366 }
367 }
368 }
369
370 public bool is_clockwise () {
371 return get_direction () >= 0;
372 }
373
374 public double get_direction () {
375 if (prev == null) {
376 return 0;
377 }
378
379 return (x - get_prev ().x) * (y + get_prev ().y);
380 }
381
382 public void set_tie_handle (bool tie) {
383 tie_handles = tie;
384 }
385
386 public void process_symmetrical_handles () {
387 process_tied_handle ();
388 right_handle.process_symmetrical_handle ();
389 left_handle.process_symmetrical_handle ();
390 }
391
392 public static void convert_from_line_to_curve (EditPointHandle h) {
393 switch (h.type) {
394 case PointType.LINE_QUADRATIC:
395 h.type = PointType.QUADRATIC;
396 break;
397 case PointType.LINE_DOUBLE_CURVE:
398 h.type = PointType.DOUBLE_CURVE;
399 break;
400 case PointType.LINE_CUBIC:
401 h.type = PointType.CUBIC;
402 break;
403 default:
404 break;
405 }
406 }
407
408 /** This can only be performed if the path has been closed. */
409 public void process_tied_handle ()
410 requires (next != null && prev != null) {
411 double a, b, c, length, angle;
412 EditPointHandle eh;
413 EditPointHandle prev_rh, next_lh;
414
415 eh = right_handle;
416
417 a = left_handle.x - right_handle.x;
418 b = left_handle.y - right_handle.y;
419 c = a * a + b * b;
420
421 if (c == 0) {
422 return;
423 }
424
425 length = sqrt (fabs (c));
426
427 if (right_handle.y < left_handle.y) {
428 angle = acos (a / length) + PI;
429 } else {
430 angle = -acos (a / length) + PI;
431 }
432
433 prev_rh = get_prev ().get_right_handle ();
434 next_lh = get_next ().get_left_handle ();
435
436 convert_from_line_to_curve (next_lh);
437 convert_from_line_to_curve (prev_rh);
438 convert_from_line_to_curve (left_handle);
439 convert_from_line_to_curve (right_handle);
440
441 right_handle.angle = angle;
442 left_handle.angle = angle - PI;
443
444 set_tie_handle (true);
445 eh.move_to_coordinate (right_handle.x, right_handle.y);
446 }
447
448 public EditPoint copy () {
449 EditPoint new_point = new EditPoint ();
450
451 new_point.x = x;
452 new_point.y = y;
453
454 new_point.type = type;
455 new_point.flags = flags;
456
457 new_point.right_handle.angle = right_handle.angle;
458 new_point.right_handle.length = right_handle.length;
459 new_point.right_handle.type = right_handle.type;
460
461 new_point.left_handle.angle = left_handle.angle;
462 new_point.left_handle.length = left_handle.length;
463 new_point.left_handle.type = left_handle.type;
464
465 new_point.color = color;
466
467 return new_point;
468 }
469
470 public double get_distance (double x, double y) {
471 return Path.distance (this.x, x, this.y, y);
472 }
473
474 public unowned EditPointHandle get_left_handle () {
475 if (unlikely (is_null (left_handle))) {
476 warning ("EditPoint.left_handle is null");
477 }
478
479 return left_handle;
480 }
481
482 public unowned EditPointHandle get_right_handle () {
483 if (unlikely (is_null (right_handle))) {
484 warning ("EditPoint.right_handle is null");
485 }
486
487 return right_handle;
488 }
489
490 public unowned EditPoint get_prev () {
491 if (unlikely (prev == null)) {
492 warning ("EditPoint.prev is null");
493 }
494
495 return (!) prev;
496 }
497
498 public unowned EditPoint get_next () {
499 if (unlikely (next == null)) {
500 warning ("EditPoint.next is null");
501 }
502
503 return (!) next;
504 }
505
506 public unowned EditPoint get_link_item () {
507 return this;
508 }
509
510 public void set_independet_position (double tx, double ty) {
511 double rx, ry, lx, ly;
512
513 rx = right_handle.x;
514 ry = right_handle.y;
515
516 lx = left_handle.x;
517 ly = left_handle.y;
518
519 set_position (tx, ty);
520
521 left_handle.move_to_coordinate (lx, ly);
522 right_handle.move_to_coordinate (rx, ry);
523 }
524
525 public void set_position (double tx, double ty) {
526 EditPoint p, n;
527
528 x = tx;
529 y = ty;
530
531 if (unlikely (tx.is_nan () || ty.is_nan ())) {
532 warning (@"Invalid point at ($tx,$ty).");
533 x = 0;
534 y = 0;
535 }
536
537 // move connected quadratic handle
538 if (right_handle.type == PointType.QUADRATIC) {
539 if (next != null) {
540 n = get_next ();
541 n.set_tie_handle (false);
542 n.set_reflective_handles (false);
543 n.left_handle.move_to_coordinate_internal (right_handle.x, right_handle.y);
544 }
545 }
546
547 if (left_handle.type == PointType.QUADRATIC) {
548 if (prev != null && !get_prev ().is_selected ()) {
549 p = get_prev ();
550 p.set_tie_handle (false);
551 p.set_reflective_handles (false);
552 p.right_handle.move_to_coordinate (left_handle.x, left_handle.y);
553 }
554 }
555 }
556
557 public static void to_coordinate (ref double x, ref double y) {
558 double xc, yc, xt, yt, ivz;
559 Glyph g = MainWindow.get_current_glyph ();
560
561 ivz = 1 / g.view_zoom;
562
563 xc = g.allocation.width / 2.0;
564 yc = g.allocation.height / 2.0;
565
566 x *= ivz;
567 y *= ivz;
568
569 xt = x - xc + g.view_offset_x;
570 yt = yc - y - g.view_offset_y;
571
572 x = xt;
573 y = yt;
574 }
575
576 public bool is_selected () {
577 return selected_point;
578 }
579
580 public void set_selected (bool s) {
581 selected_point = s;
582 }
583
584 public bool set_active (bool active) {
585 bool update = (this.active_point != active);
586
587 if (update) {
588 this.active_point = active;
589 }
590
591 return update;
592 }
593
594 public void convert_to_line () {
595 left_handle.convert_to_line ();
596 right_handle.convert_to_line ();
597 }
598
599 public void convert_to_curve () {
600 left_handle.convert_to_curve ();
601 right_handle.convert_to_curve ();
602 }
603
604 public string to_string () {
605 StringBuilder s = new StringBuilder ();
606
607 if (deleted) {
608 s.append (@"Deleted ");
609 }
610
611 s.append (@"Control point: $x, $y\n");
612 s.append (@"Left handle: angle: $(left_handle.angle) l: $(left_handle.length)\n");
613 s.append (@"Right handle: angle: $(right_handle.angle) l: $(right_handle.length)\n");
614 s.append (@"Type: $type Left: $(left_handle.type) Right: $(right_handle.type)\n".replace ("BIRD_FONT_POINT_TYPE_", ""));
615
616 return s.str;
617 }
618
619 public double max_x () {
620 double mx = x;
621
622 if (get_right_handle ().x > mx) {
623 mx = get_right_handle ().x;
624 }
625
626 if (get_left_handle ().x > mx) {
627 mx = get_left_handle ().x;
628 }
629
630 return mx;
631 }
632
633 public double min_x () {
634 double mx = x;
635
636 if (get_right_handle ().x < mx) {
637 mx = get_right_handle ().x;
638 }
639
640 if (get_left_handle ().x < mx) {
641 mx = get_left_handle ().x;
642 }
643
644 return mx;
645 }
646
647 public double max_y () {
648 double my = y;
649
650 if (get_right_handle ().y > my) {
651 my = get_right_handle ().y;
652 }
653
654 if (get_left_handle ().y > my) {
655 my = get_left_handle ().y;
656 }
657
658 return my;
659 }
660
661 public double min_y () {
662 double my = y;
663
664 if (get_right_handle ().y < my) {
665 my = get_right_handle ().y;
666 }
667
668 if (get_left_handle ().y < my) {
669 my = get_left_handle ().y;
670 }
671
672 return my;
673 }
674 }
675
676 }
677