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