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