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