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.
Merge branch '2.10'
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