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