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