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