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.
Use trusty in travis
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 224 /** Flip handles if next point on path is in the other direction. 225 * Used to recalculate handles after new point is inserted on a path. 226 */ 227 public void recalculate_handles (double px, double py) { 228 double dr, dl; 229 EditPointHandle t; 230 231 if (next == null || get_next ().next != null) { 232 return; 233 } 234 235 if (unlikely (reflective_point || tie_handles)) { 236 warning ("Points on lines can't have tied handles."); 237 return; 238 } 239 240 px = get_next ().get_next ().x; 241 py = get_next ().get_next ().y; 242 243 dr = Math.sqrt (Math.pow (px - right_handle.x, 2) + Math.pow (py - right_handle.y, 2)); 244 dl = Math.sqrt (Math.pow (px - left_handle.x, 2) + Math.pow (py - left_handle.y, 2)); 245 246 // flip handles 247 if (dl < dr) { 248 t = right_handle; 249 right_handle = left_handle; 250 left_handle = t; 251 } 252 } 253 254 public bool is_clockwise () { 255 return get_direction () >= 0; 256 } 257 258 public double get_direction () { 259 if (prev == null) { 260 return 0; 261 } 262 263 return (x - get_prev ().x) * (y + get_prev ().y); 264 } 265 266 public void set_tie_handle (bool tie) { 267 tie_handles = tie; 268 } 269 270 public void process_symmetrical_handles () { 271 process_tied_handle (); 272 right_handle.process_symmetrical_handle (); 273 left_handle.process_symmetrical_handle (); 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 /** This can only be performed if the path has been closed. */ 298 public void process_tied_handle () 299 requires (next != null && prev != null) { 300 double a, b, c, length, angle; 301 EditPointHandle eh; 302 EditPointHandle prev_rh, next_lh; 303 304 eh = right_handle; 305 306 a = left_handle.x - right_handle.x; 307 b = left_handle.y - right_handle.y; 308 c = a * a + b * b; 309 310 if (c == 0) { 311 return; 312 } 313 314 length = sqrt (fabs (c)); 315 316 if (right_handle.y < left_handle.y) { 317 angle = acos (a / length) + PI; 318 } else { 319 angle = -acos (a / length) + PI; 320 } 321 322 prev_rh = get_prev ().get_right_handle (); 323 next_lh = get_next ().get_left_handle (); 324 325 convert_from_line_to_curve (next_lh); 326 convert_from_line_to_curve (prev_rh); 327 convert_from_line_to_curve (left_handle); 328 convert_from_line_to_curve (right_handle); 329 330 right_handle.angle = angle; 331 left_handle.angle = angle - PI; 332 333 set_tie_handle (true); 334 eh.move_to_coordinate (right_handle.x, right_handle.y); 335 } 336 337 public EditPoint copy () { 338 EditPoint new_point = new EditPoint (); 339 340 new_point.x = x; 341 new_point.y = y; 342 343 new_point.type = type; 344 new_point.flags = flags; 345 346 new_point.right_handle.angle = right_handle.angle; 347 new_point.right_handle.length = right_handle.length; 348 new_point.right_handle.type = right_handle.type; 349 350 new_point.left_handle.angle = left_handle.angle; 351 new_point.left_handle.length = left_handle.length; 352 new_point.left_handle.type = left_handle.type; 353 354 new_point.color = color; 355 356 return new_point; 357 } 358 359 public double get_distance (double x, double y) { 360 return Path.distance (this.x, x, this.y, y); 361 } 362 363 public unowned EditPointHandle get_left_handle () { 364 if (unlikely (is_null (left_handle))) { 365 warning ("EditPoint.left_handle is null"); 366 } 367 368 return left_handle; 369 } 370 371 public unowned EditPointHandle get_right_handle () { 372 if (unlikely (is_null (right_handle))) { 373 warning ("EditPoint.right_handle is null"); 374 } 375 376 return right_handle; 377 } 378 379 public unowned EditPoint get_prev () { 380 if (unlikely (prev == null)) { 381 warning ("EditPoint.prev is null"); 382 } 383 384 return (!) prev; 385 } 386 387 public unowned EditPoint get_next () { 388 if (unlikely (next == null)) { 389 warning ("EditPoint.next is null"); 390 } 391 392 return (!) next; 393 } 394 395 public unowned EditPoint get_link_item () { 396 return this; 397 } 398 399 public void set_independet_position (double tx, double ty) { 400 double rx, ry, lx, ly; 401 402 rx = right_handle.x; 403 ry = right_handle.y; 404 405 lx = left_handle.x; 406 ly = left_handle.y; 407 408 set_position (tx, ty); 409 410 left_handle.move_to_coordinate (lx, ly); 411 right_handle.move_to_coordinate (rx, ry); 412 } 413 414 public void set_position (double tx, double ty) { 415 EditPoint p, n; 416 417 x = tx; 418 y = ty; 419 420 if (unlikely (tx.is_nan () || ty.is_nan ())) { 421 warning (@"Invalid point at ($tx,$ty)."); 422 x = 0; 423 y = 0; 424 } 425 426 // move connected quadratic handle 427 if (right_handle.type == PointType.QUADRATIC) { 428 if (next != null) { 429 n = get_next (); 430 n.set_tie_handle (false); 431 n.set_reflective_handles (false); 432 n.left_handle.move_to_coordinate_internal (right_handle.x, right_handle.y); 433 } 434 } 435 436 if (left_handle.type == PointType.QUADRATIC) { 437 if (prev != null && !get_prev ().is_selected ()) { 438 p = get_prev (); 439 p.set_tie_handle (false); 440 p.set_reflective_handles (false); 441 p.right_handle.move_to_coordinate (left_handle.x, left_handle.y); 442 } 443 } 444 } 445 446 public static void to_coordinate (ref double x, ref double y) { 447 double xc, yc, xt, yt, ivz; 448 Glyph g = MainWindow.get_current_glyph (); 449 450 ivz = 1 / g.view_zoom; 451 452 xc = g.allocation.width / 2.0; 453 yc = g.allocation.height / 2.0; 454 455 x *= ivz; 456 y *= ivz; 457 458 xt = x - xc + g.view_offset_x; 459 yt = yc - y - g.view_offset_y; 460 461 x = xt; 462 y = yt; 463 } 464 465 public bool is_selected () { 466 return selected_point; 467 } 468 469 public void set_selected (bool s) { 470 selected_point = s; 471 } 472 473 public bool set_active (bool active) { 474 bool update = (this.active_point != active); 475 476 if (update) { 477 this.active_point = active; 478 } 479 480 return update; 481 } 482 483 public void convert_to_line () { 484 left_handle.convert_to_line (); 485 right_handle.convert_to_line (); 486 } 487 488 public void convert_to_curve () { 489 left_handle.convert_to_curve (); 490 right_handle.convert_to_curve (); 491 } 492 493 public string to_string () { 494 StringBuilder s = new StringBuilder (); 495 496 if (deleted) { 497 s.append (@"Deleted "); 498 } 499 500 s.append (@"Control point: $x, $y\n"); 501 s.append (@"Left handle: angle: $(left_handle.angle) l: $(left_handle.length)\n"); 502 s.append (@"Right handle: angle: $(right_handle.angle) l: $(right_handle.length)\n"); 503 s.append (@"Type: $type Left: $(left_handle.type) Right: $(right_handle.type)\n".replace ("BIRD_FONT_POINT_TYPE_", "")); 504 s.append (@"Flags $(flags)\n"); 505 506 return s.str; 507 } 508 } 509 510 } 511