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