API  0.9.10
CGPath.j
Go to the documentation of this file.
1 /*
2  * CGPath.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @typedef CGPath
25 
31 
34 
44 {
45  return { count:0, start:CGPointMake(0, 0), current:CGPointMake(0, 0), elements:[] };
46 }
47 
52 function CGPathCreateMutableCopy(aPath)
53 {
54  var path = CGPathCreateMutable();
55 
56  CGPathAddPath(path, aPath);
57 
58  return path;
59 }
60 
65 function CGPathCreateCopy(aPath)
66 {
67  return CGPathCreateMutableCopy(aPath);
68 }
69 
70 function CGPathRelease(aPath)
71 {
72 }
73 
74 function CGPathRetain(aPath)
75 {
76  return aPath;
77 }
78 
79 function CGPathAddArc(aPath, aTransform, x, y, aRadius, aStartAngle, anEndAngle, isClockwise)
80 {
81  if (aTransform && !CGAffineTransformIsIdentity(aTransform))
82  {
83  var center = CGPointMake(x, y),
84  end = CGPointMake(COS(anEndAngle), SIN(anEndAngle)),
85  start = CGPointMake(COS(aStartAngle), SIN(aStartAngle));
86 
87  end = CGPointApplyAffineTransform(end, aTransform);
89  center = CGPointApplyAffineTransform(center, aTransform);
90 
91  x = center.x;
92  y = center.y;
93 
94  var oldEndAngle = anEndAngle,
95  oldStartAngle = aStartAngle;
96 
97  anEndAngle = ATAN2(end.y - aTransform.ty, end.x - aTransform.tx);
98  aStartAngle = ATAN2(start.y - aTransform.ty, start.x - aTransform.tx);
99 
100  // Angles that equal "modulo" 2 pi return as equal after transforming them,
101  // so we have to make sure to make them different again if they were different
102  // to start out with. It's the difference between no circle and a full circle.
103  if (anEndAngle === aStartAngle && oldEndAngle !== oldStartAngle)
104  if (oldStartAngle > oldEndAngle)
105  anEndAngle = anEndAngle - PI2;
106  else
107  aStartAngle = aStartAngle - PI2;
108 
109  aRadius = CGSizeMake(aRadius, 0);
110  aRadius = CGSizeApplyAffineTransform(aRadius, aTransform);
111  aRadius = SQRT(aRadius.width * aRadius.width + aRadius.height * aRadius.height);
112  }
113 
114  /*
115  From the Cocoa docs:
116 
117  If the specified path already contains a subpath, Quartz implicitly adds a line connecting the subpath’s current point to the beginning of the arc. If the path is empty, Quartz creates a new subpath with a starting point set to the starting point of the arc.
118 
119  The ending point of the arc becomes the new current point of the path.
120  */
121  var arcEndX = x + aRadius * COS(anEndAngle),
122  arcEndY = y + aRadius * SIN(anEndAngle),
123  arcStartX = x + aRadius * COS(aStartAngle),
124  arcStartY = y + aRadius * SIN(aStartAngle);
125 
126  if (aPath.count)
127  {
128  if (aPath.current.x !== x || aPath.current.y !== y)
129  CGPathAddLineToPoint(aPath, aTransform, arcStartX, arcStartY);
130  }
131  else
132  {
133  var arcStartX = x + aRadius * COS(aStartAngle),
134  arcStartY = y + aRadius * SIN(aStartAngle);
135 
136  aPath.start = CGPointMake(arcStartX, arcStartY);
137  }
138 
139  aPath.current = CGPointMake(arcEndX, arcEndY);
140  aPath.elements[aPath.count++] = { type:kCGPathElementAddArc, x:x, y:y, radius:aRadius, startAngle:aStartAngle, endAngle:anEndAngle, isClockwise:isClockwise };
141 }
142 
143 function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
144 {
145  var p1 = CGPointMake(x1, y1),
146  p2 = CGPointMake(x2, y2);
147 
148  if (aTransform)
149  {
150  p1 = CGPointApplyAffineTransform(p1, aTransform);
151  p2 = CGPointApplyAffineTransform(p2, aTransform);
152  }
153 
154  /*
155  From the Cocoa docs:
156 
157  If the current point and the first tangent point of the arc (the starting point) are not equal, Quartz appends a straight line segment from the current point to the first tangent point.
158 
159  The ending point of the arc becomes the new current point of the path.
160  */
161  if (aPath.count)
162  {
163  if (aPath.current.x !== p1.x || aPath.current.y !== p1.y)
164  CGPathAddLineToPoint(aPath, aTransform, p1.x, p1.y);
165  }
166  else
167  aPath.start = p1;
168 
169  aPath.current = p2;
170  aPath.elements[aPath.count++] = { type:kCGPathElementAddArcToPoint, p1x:p1.x, p1y:p1.y, p2x:p2.x, p2y:p2.y, radius:aRadius };
171 }
172 
173 function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
174 {
175  var cp1 = CGPointMake(cp1x, cp1y),
176  cp2 = CGPointMake(cp2x, cp2y),
177  end = CGPointMake(x, y);
178 
179  if (aTransform)
180  {
181  cp1 = CGPointApplyAffineTransform(cp1, aTransform);
182  cp2 = CGPointApplyAffineTransform(cp2, aTransform);
183  end = CGPointApplyAffineTransform(end, aTransform);
184  }
185 
186  aPath.current = end;
187  aPath.elements[aPath.count++] = { type:kCGPathElementAddCurveToPoint, cp1x:cp1.x, cp1y:cp1.y, cp2x:cp2.x, cp2y:cp2.y, x:end.x, y:end.y };
188 }
189 
190 function CGPathAddLines(aPath, aTransform, points, count)
191 {
192  if (count === null || count === undefined)
193  count = points.length;
194 
195  if (!aPath || count < 1)
196  return;
197 
198  CGPathMoveToPoint(aPath, aTransform, points[0].x, points[0].y);
199 
200  for (var i = 1; i < count; ++i)
201  CGPathAddLineToPoint(aPath, aTransform, points[i].x, points[i].y);
202 }
203 
204 function CGPathAddLineToPoint(aPath, aTransform, x, y)
205 {
206  var point = CGPointMake(x, y);
207 
208  if (aTransform !== NULL)
209  point = CGPointApplyAffineTransform(point, aTransform);
210 
211  aPath.elements[aPath.count++] = { type: kCGPathElementAddLineToPoint, x:point.x, y:point.y };
212  aPath.current = point;
213 }
214 
215 function CGPathAddPath(aPath, aTransform, anotherPath)
216 {
217  for (var i = 0, count = anotherPath.count; i < count; ++i)
218  {
219  var element = anotherPath.elements[i];
220 
221  switch (element.type)
222  {
224  CGPathAddLineToPoint(aPath, aTransform, element.x, element.y);
225  break;
226 
228  CGPathAddCurveToPoint(aPath, aTransform,
229  element.cp1x, element.cp1y,
230  element.cp2x, element.cp2y,
231  element.x, element.y);
232  break;
233 
235  CGPathAddArc(aPath, aTransform, element.x, element.y,
236  element.radius, element.startAngle,
237  element.endAngle, element.isClockwise);
238  break;
239 
241  CGPathAddArcToPoint(aPath, aTransform,
242  element.p1x, element.p1y,
243  element.p2x, element.p2y,
244  element.radius);
245  break;
246 
248  CGPathAddQuadCurveToPoint(aPath, aTransform,
249  element.cpx, element.cpy,
250  element.x, element.y);
251  break;
252 
254  CGPathMoveToPoint(aPath, aTransform, element.x, element.y);
255  break;
256 
258  CGPathCloseSubpath(aPath);
259  break;
260  }
261  }
262 }
263 
264 function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
265 {
266  var cp = CGPointMake(cpx, cpy),
267  end = CGPointMake(x, y);
268 
269  if (aTransform)
270  {
271  cp = CGPointApplyAffineTransform(cp, aTransform);
272  end = CGPointApplyAffineTransform(end, aTransform);
273  }
274 
275  aPath.elements[aPath.count++] = { type:kCGPathElementAddQuadCurveToPoint, cpx:cp.x, cpy:cp.y, x:end.x, y:end.y }
276  aPath.current = end;
277 }
278 
279 function CGPathAddRect(aPath, aTransform, aRect)
280 {
281  CGPathAddRects(aPath, aTransform, [aRect], 1);
282 }
283 
284 function CGPathAddRects(aPath, aTransform, rects, count)
285 {
286  var i = 0;
287 
288  if (count === NULL)
289  var count = rects.length;
290 
291  for (; i < count; ++i)
292  {
293  var rect = rects[i];
294 
295  CGPathMoveToPoint(aPath, aTransform, CGRectGetMinX(rect), CGRectGetMinY(rect));
296  CGPathAddLineToPoint(aPath, aTransform, CGRectGetMaxX(rect), CGRectGetMinY(rect));
297  CGPathAddLineToPoint(aPath, aTransform, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
298  CGPathAddLineToPoint(aPath, aTransform, CGRectGetMinX(rect), CGRectGetMaxY(rect));
299 
300  CGPathCloseSubpath(aPath);
301  }
302 }
303 
304 function CGPathMoveToPoint(aPath, aTransform, x, y)
305 {
306  var point = CGPointMake(x, y);
307 
308  if (aTransform !== NULL)
309  point = CGPointApplyAffineTransform(point, aTransform);
310 
311  aPath.start = aPath.current = point;
312 
313  // If the previous op was a move, just update that point
314  if (aPath.count)
315  {
316  var previous = aPath.elements[aPath.count - 1];
317 
318  if (previous.type === kCGPathElementMoveToPoint)
319  {
320  previous.x = point.x;
321  previous.y = point.y;
322  return;
323  }
324  }
325 
326  aPath.elements[aPath.count++] = { type:kCGPathElementMoveToPoint, x:point.x, y:point.y };
327 }
328 
329 var KAPPA = 4.0 * ((SQRT2 - 1.0) / 3.0);
330 
331 function CGPathWithEllipseInRect(aRect)
332 {
333  var path = CGPathCreateMutable();
334 
335  if (CGRectGetWidth(aRect) === CGRectGetHeight(aRect))
336  CGPathAddArc(path, nil, CGRectGetMidX(aRect), CGRectGetMidY(aRect), CGRectGetWidth(aRect) / 2.0, 0.0, 2 * PI, YES);
337  else
338  {
339  var axis = CGSizeMake(CGRectGetWidth(aRect) / 2.0, CGRectGetHeight(aRect) / 2.0),
340  center = CGPointMake(CGRectGetMinX(aRect) + axis.width, CGRectGetMinY(aRect) + axis.height);
341 
342  CGPathMoveToPoint(path, nil, center.x, center.y - axis.height);
343 
344  CGPathAddCurveToPoint(path, nil, center.x + (KAPPA * axis.width), center.y - axis.height, center.x + axis.width, center.y - (KAPPA * axis.height), center.x + axis.width, center.y);
345  CGPathAddCurveToPoint(path, nil, center.x + axis.width, center.y + (KAPPA * axis.height), center.x + (KAPPA * axis.width), center.y + axis.height, center.x, center.y + axis.height);
346  CGPathAddCurveToPoint(path, nil, center.x - (KAPPA * axis.width), center.y + axis.height, center.x - axis.width, center.y + (KAPPA * axis.height), center.x - axis.width, center.y);
347  CGPathAddCurveToPoint(path, nil, center.x - axis.width, center.y - (KAPPA * axis.height), center.x - (KAPPA * axis.width), center.y - axis.height, center.x, center.y - axis.height);
348  }
349 
350  CGPathCloseSubpath(path);
351 
352  return path;
353 }
354 
355 function CGPathWithRoundedRectangleInRect(aRect, xRadius, yRadius/*not currently supported*/, ne, se, sw, nw)
356 {
357  var path = CGPathCreateMutable(),
358  xMin = CGRectGetMinX(aRect),
359  xMax = CGRectGetMaxX(aRect),
360  yMin = CGRectGetMinY(aRect),
361  yMax = CGRectGetMaxY(aRect);
362 
363  CGPathMoveToPoint(path, nil, xMin + xRadius, yMin);
364 
365  if (ne)
366  {
367  CGPathAddLineToPoint(path, nil, xMax - xRadius, yMin);
368  CGPathAddCurveToPoint(path, nil, xMax - xRadius, yMin, xMax, yMin, xMax, yMin + xRadius);
369  }
370  else
371  CGPathAddLineToPoint(path, nil, xMax, yMin);
372 
373  if (se)
374  {
375  CGPathAddLineToPoint(path, nil, xMax, yMax - xRadius);
376  CGPathAddCurveToPoint(path, nil, xMax, yMax - xRadius, xMax, yMax, xMax - xRadius, yMax);
377  }
378  else
379  CGPathAddLineToPoint(path, nil, xMax, yMax);
380 
381  if (sw)
382  {
383  CGPathAddLineToPoint(path, nil, xMin + xRadius, yMax);
384  CGPathAddCurveToPoint(path, nil, xMin + xRadius, yMax, xMin, yMax, xMin, yMax - xRadius);
385  }
386  else
387  CGPathAddLineToPoint(path, nil, xMin, yMax);
388 
389  if (nw)
390  {
391  CGPathAddLineToPoint(path, nil, xMin, yMin + xRadius);
392  CGPathAddCurveToPoint(path, nil, xMin, yMin + xRadius, xMin, yMin, xMin + xRadius, yMin);
393  }
394  else
395  CGPathAddLineToPoint(path, nil, xMin, yMin);
396 
397  CGPathCloseSubpath(path);
398 
399  return path;
400 }
401 
402 function CGPathCloseSubpath(aPath)
403 {
404  var count = aPath.count;
405 
406  // Don't bother closing this subpath if there aren't any current elements, or the last element already closed the subpath.
407  if (count === 0 || aPath.elements[count - 1].type === kCGPathElementCloseSubpath)
408  return;
409 
410  // After closing, the current point is the previous path's starting point
411  aPath.current = CGPointCreateCopy(aPath.start);
412  aPath.elements[aPath.count++] = { type:kCGPathElementCloseSubpath, start:aPath.start };
413 }
414 
415 function CGPathEqualToPath(aPath, anotherPath)
416 {
417  if (aPath === anotherPath)
418  return YES;
419 
420  if (aPath.count !== anotherPath.count || !CGPointEqualToPoint(aPath.start, anotherPath.start) || !CGPointEqualToPoint(aPath.current, anotherPath.current))
421  return NO;
422 
423  var i = 0,
424  count = aPath.count;
425 
426  for (; i < count; ++i)
427  {
428  var element = aPath[i],
429  anotherElement = anotherPath[i];
430 
431  if (element.type !== anotherElement.type)
432  return NO;
433 
434  switch (element.type)
435  {
437  if (element.x !== anotherElement.x ||
438  element.y !== anotherElement.y ||
439  element.radius !== anotherElement.radius ||
440  element.startAngle !== anotherElement.startAngle ||
441  element.endAngle !== anotherElement.endAngle ||
442  element.isClockwise !== anotherElement.isClockwise)
443  {
444  return NO;
445  }
446  break;
447 
449  if (element.p1x !== anotherElement.p1x ||
450  element.p1y !== anotherElement.p1y ||
451  element.p2x !== anotherElement.p2x ||
452  element.p2y !== anotherElement.p2y ||
453  element.radius !== anotherElement.radius)
454  {
455  return NO;
456  }
457  break;
458 
460  if (element.cp1x !== anotherElement.cp1x ||
461  element.cp1y !== anotherElement.cp1y ||
462  element.cp2x !== anotherElement.cp2x ||
463  element.cp2y !== anotherElement.cp2y ||
464  element.x !== anotherElement.x ||
465  element.y !== anotherElement.y)
466  {
467  return NO;
468  }
469  break;
470 
473  if (element.x !== anotherElement.x ||
474  element.y !== anotherElement.y)
475  {
476  return NO;
477  }
478  break;
479 
481  if (element.cpx !== anotherElement.cpx ||
482  element.cpy !== anotherElement.cpy ||
483  element.x !== anotherElement.x ||
484  element.y !== anotherElement.y)
485  {
486  return NO;
487  }
488  break;
489 
491  if (!CGPointEqualToPoint(element.start, anotherElement.start))
492  return NO;
493  break;
494  }
495  }
496 
497  return YES;
498 }
499 
500 function CGPathGetCurrentPoint(aPath)
501 {
502  return CGPointCreateCopy(aPath.current);
503 }
504 
505 function CGPathIsEmpty(aPath)
506 {
507  return !aPath || aPath.count === 0;
508 }
509 
513 function CGPathGetBoundingBox(aPath)
514 {
515  if (!aPath || !aPath.count)
516  return CGRectMakeZero();
517 
518  var ox = 0,
519  oy = 0,
520  rx = 0,
521  ry = 0,
522  movePoint = nil;
523 
524  function addPoint(x, y)
525  {
526  ox = MIN(ox, x);
527  oy = MIN(oy, y);
528  rx = MAX(rx, x);
529  ry = MAX(ry, y);
530  }
531 
532  for (var i = 0, count = aPath.count; i < count; ++i)
533  {
534  var element = aPath.elements[i];
535 
536  // Just enclose all the control points. The curves must be inside of the control points.
537  // This won't work for CGPathGetBoundingBox.
538  switch (element.type)
539  {
541  if (movePoint)
542  {
543  addPoint(movePoint.x, movePoint.y);
544  movePoint = nil;
545  }
546 
547  addPoint(element.x, element.y);
548  break;
549 
551  if (movePoint)
552  {
553  addPoint(movePoint.x, movePoint.y);
554  movePoint = nil;
555  }
556 
557  addPoint(element.cp1x, element.cp1y);
558  addPoint(element.cp2x, element.cp2y);
559  addPoint(element.x, element.y);
560  break;
561 
563  if (movePoint)
564  {
565  addPoint(movePoint.x, movePoint.y);
566  movePoint = nil;
567  }
568 
569  addPoint(element.x, element.y);
570  break;
571 
573  if (movePoint)
574  {
575  addPoint(movePoint.x, movePoint.y);
576  movePoint = nil;
577  }
578 
579  addPoint(element.p1x, element.p1y);
580  addPoint(element.p2x, element.p2y);
581  break;
582 
584  if (movePoint)
585  {
586  addPoint(movePoint.x, movePoint.y);
587  movePoint = nil;
588  }
589 
590  addPoint(element.cpx, element.cpy);
591  addPoint(element.x, element.y);
592  break;
593 
595  movePoint = CGPointMake(element.x, element.y);
596  break;
597 
599  if (movePoint)
600  {
601  addPoint(movePoint.x, movePoint.y);
602  movePoint = nil;
603  }
604 
605  break;
606  }
607  }
608 
609  return CGRectMake(ox, oy, rx - ox, ry - oy);
610 }
611 
612 function CGPathContainsPoint(aPath, aTransform, point, eoFill)
613 {
614  if (!aPath.count)
615  return NO;
616 
617  if (aTransform)
618  point = CGPointApplyAffineTransform(point, aTransform);
619 
620  var context = CGBitmapGraphicsContextCreate();
621 
622  CGContextBeginPath(context);
623  CGContextAddPath(context, aPath);
624  CGContextClosePath(context);
625 
626  return context.isPointInPath(point.x, point.y);
627 }
628 
function CGPathWithRoundedRectangleInRect(aRect, xRadius, yRadius, ne, se, sw, nw)
Definition: CGPath.j:355
CGPath kCGPathElementMoveToPoint
Definition: CGPath.j:26
function CGPathGetBoundingBox(aPath)
Definition: CGPath.j:513
function CGPathCloseSubpath(aPath)
Definition: CGPath.j:402
function CGPathEqualToPath(aPath, anotherPath)
Definition: CGPath.j:415
function CGAffineTransformIsIdentity(aTransform)
function CGPathAddRects(aPath, aTransform, rects, count)
Definition: CGPath.j:284
function CGPathCreateCopy(aPath)
Definition: CGPath.j:65
kCGPathElementAddLineToPoint
Definition: CGPath.j:27
function CGContextClosePath(aContext)
Definition: CGContext.j:322
function CGPathMoveToPoint(aPath, aTransform, x, y)
Definition: CGPath.j:304
kCGPathElementAddArcToPoint
Definition: CGPath.j:33
function CGPathAddLines(aPath, aTransform, points, count)
Definition: CGPath.j:190
function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
Definition: CGPath.j:173
function CGContextAddPath(aContext, aPath)
Definition: CGContext.j:258
function CGPathWithEllipseInRect(aRect)
Definition: CGPath.j:331
function CGPathIsEmpty(aPath)
Definition: CGPath.j:505
var KAPPA
Definition: CGPath.j:329
function CGPathAddArc(aPath, aTransform, x, y, aRadius, aStartAngle, anEndAngle, isClockwise)
Definition: CGPath.j:79
kCGPathElementAddArc
Definition: CGPath.j:32
kCGPathElementCloseSubpath
Definition: CGPath.j:30
function CGContextBeginPath(aContext)
Definition: CGContext.j:311
function CGPathCreateMutable()
Definition: CGPath.j:43
function CGPathAddRect(aPath, aTransform, aRect)
Definition: CGPath.j:279
function CGSizeApplyAffineTransform(aSize, aTransform)
function CGPathCreateMutableCopy(aPath)
Definition: CGPath.j:52
function CGPathGetCurrentPoint(aPath)
Definition: CGPath.j:500
function CGPathRetain(aPath)
Definition: CGPath.j:74
function CGPathRelease(aPath)
Definition: CGPath.j:70
kCGPathElementAddCurveToPoint
Definition: CGPath.j:29
FrameUpdater prototype start
function CGPathAddLineToPoint(aPath, aTransform, x, y)
Definition: CGPath.j:204
kCGPathElementAddQuadCurveToPoint
Definition: CGPath.j:28
function CGPointApplyAffineTransform(aPoint, aTransform)
function CGPathContainsPoint(aPath, aTransform, point, eoFill)
Definition: CGPath.j:612
function CGBitmapGraphicsContextCreate()
Definition: CGContext.j:136
function CGPathAddPath(aPath, aTransform, anotherPath)
Definition: CGPath.j:215
function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
Definition: CGPath.j:143
function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
Definition: CGPath.j:264