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