The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

EditPoint.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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