API  1.0.0
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  aPath.start = CGPointMake(arcStartX, arcStartY);
134  }
135 
136  aPath.current = CGPointMake(arcEndX, arcEndY);
137  aPath.elements[aPath.count++] = { type:kCGPathElementAddArc, x:x, y:y, radius:aRadius, startAngle:aStartAngle, endAngle:anEndAngle, isClockwise:isClockwise };
138 }
139 
140 function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
141 {
142  var p1 = CGPointMake(x1, y1),
143  p2 = CGPointMake(x2, y2);
144 
145  if (aTransform)
146  {
147  p1 = CGPointApplyAffineTransform(p1, aTransform);
148  p2 = CGPointApplyAffineTransform(p2, aTransform);
149  }
150 
151  /*
152  From the Cocoa docs:
153 
154  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.
155 
156  The ending point of the arc becomes the new current point of the path.
157  */
158  if (aPath.count)
159  {
160  if (aPath.current.x !== p1.x || aPath.current.y !== p1.y)
161  CGPathAddLineToPoint(aPath, aTransform, p1.x, p1.y);
162  }
163  else
164  aPath.start = p1;
165 
166  aPath.current = p2;
167  aPath.elements[aPath.count++] = { type:kCGPathElementAddArcToPoint, p1x:p1.x, p1y:p1.y, p2x:p2.x, p2y:p2.y, radius:aRadius };
168 }
169 
170 function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
171 {
172  var cp1 = CGPointMake(cp1x, cp1y),
173  cp2 = CGPointMake(cp2x, cp2y),
174  end = CGPointMake(x, y);
175 
176  if (aTransform)
177  {
178  cp1 = CGPointApplyAffineTransform(cp1, aTransform);
179  cp2 = CGPointApplyAffineTransform(cp2, aTransform);
180  end = CGPointApplyAffineTransform(end, aTransform);
181  }
182 
183  aPath.current = end;
184  aPath.elements[aPath.count++] = { type:kCGPathElementAddCurveToPoint, cp1x:cp1.x, cp1y:cp1.y, cp2x:cp2.x, cp2y:cp2.y, x:end.x, y:end.y };
185 }
186 
187 function CGPathAddLines(aPath, aTransform, points, count)
188 {
189  if (count === null || count === undefined)
190  count = points.length;
191 
192  if (!aPath || count < 1)
193  return;
194 
195  CGPathMoveToPoint(aPath, aTransform, points[0].x, points[0].y);
196 
197  for (var i = 1; i < count; ++i)
198  CGPathAddLineToPoint(aPath, aTransform, points[i].x, points[i].y);
199 }
200 
201 function CGPathAddLineToPoint(aPath, aTransform, x, y)
202 {
203  var point = CGPointMake(x, y);
204 
205  if (aTransform !== NULL)
206  point = CGPointApplyAffineTransform(point, aTransform);
207 
208  aPath.elements[aPath.count++] = { type: kCGPathElementAddLineToPoint, x:point.x, y:point.y };
209  aPath.current = point;
210 }
211 
212 function CGPathAddPath(aPath, aTransform, anotherPath)
213 {
214  for (var i = 0, count = anotherPath.count; i < count; ++i)
215  {
216  var element = anotherPath.elements[i];
217 
218  switch (element.type)
219  {
221  CGPathAddLineToPoint(aPath, aTransform, element.x, element.y);
222  break;
223 
225  CGPathAddCurveToPoint(aPath, aTransform,
226  element.cp1x, element.cp1y,
227  element.cp2x, element.cp2y,
228  element.x, element.y);
229  break;
230 
232  CGPathAddArc(aPath, aTransform, element.x, element.y,
233  element.radius, element.startAngle,
234  element.endAngle, element.isClockwise);
235  break;
236 
238  CGPathAddArcToPoint(aPath, aTransform,
239  element.p1x, element.p1y,
240  element.p2x, element.p2y,
241  element.radius);
242  break;
243 
245  CGPathAddQuadCurveToPoint(aPath, aTransform,
246  element.cpx, element.cpy,
247  element.x, element.y);
248  break;
249 
251  CGPathMoveToPoint(aPath, aTransform, element.x, element.y);
252  break;
253 
255  CGPathCloseSubpath(aPath);
256  break;
257  }
258  }
259 }
260 
261 function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
262 {
263  var cp = CGPointMake(cpx, cpy),
264  end = CGPointMake(x, y);
265 
266  if (aTransform)
267  {
268  cp = CGPointApplyAffineTransform(cp, aTransform);
269  end = CGPointApplyAffineTransform(end, aTransform);
270  }
271 
272  aPath.elements[aPath.count++] = { type:kCGPathElementAddQuadCurveToPoint, cpx:cp.x, cpy:cp.y, x:end.x, y:end.y };
273  aPath.current = end;
274 }
275 
276 function CGPathAddRect(aPath, aTransform, aRect)
277 {
278  CGPathAddRects(aPath, aTransform, [aRect], 1);
279 }
280 
281 function CGPathAddRects(aPath, aTransform, rects, count)
282 {
283  var i = 0;
284 
285  if (count === NULL)
286  var count = rects.length;
287 
288  for (; i < count; ++i)
289  {
290  var rect = rects[i];
291 
292  CGPathMoveToPoint(aPath, aTransform, CGRectGetMinX(rect), CGRectGetMinY(rect));
293  CGPathAddLineToPoint(aPath, aTransform, CGRectGetMaxX(rect), CGRectGetMinY(rect));
294  CGPathAddLineToPoint(aPath, aTransform, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
295  CGPathAddLineToPoint(aPath, aTransform, CGRectGetMinX(rect), CGRectGetMaxY(rect));
296 
297  CGPathCloseSubpath(aPath);
298  }
299 }
300 
301 function CGPathMoveToPoint(aPath, aTransform, x, y)
302 {
303  var point = CGPointMake(x, y);
304 
305  if (aTransform !== NULL)
306  point = CGPointApplyAffineTransform(point, aTransform);
307 
308  aPath.start = aPath.current = point;
309 
310  // If the previous op was a move, just update that point
311  if (aPath.count)
312  {
313  var previous = aPath.elements[aPath.count - 1];
314 
315  if (previous.type === kCGPathElementMoveToPoint)
316  {
317  previous.x = point.x;
318  previous.y = point.y;
319  return;
320  }
321  }
322 
323  aPath.elements[aPath.count++] = { type:kCGPathElementMoveToPoint, x:point.x, y:point.y };
324 }
325 
326 var KAPPA = 4.0 * ((SQRT2 - 1.0) / 3.0);
327 
328 function CGPathWithEllipseInRect(aRect)
329 {
330  var path = CGPathCreateMutable();
331 
332  if (CGRectGetWidth(aRect) === CGRectGetHeight(aRect))
333  CGPathAddArc(path, nil, CGRectGetMidX(aRect), CGRectGetMidY(aRect), CGRectGetWidth(aRect) / 2.0, 0.0, 2 * PI, YES);
334  else
335  {
336  var axis = CGSizeMake(CGRectGetWidth(aRect) / 2.0, CGRectGetHeight(aRect) / 2.0),
337  center = CGPointMake(CGRectGetMinX(aRect) + axis.width, CGRectGetMinY(aRect) + axis.height);
338 
339  CGPathMoveToPoint(path, nil, center.x, center.y - axis.height);
340 
341  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);
342  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);
343  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);
344  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);
345  }
346 
347  CGPathCloseSubpath(path);
348 
349  return path;
350 }
351 
352 function CGPathWithRoundedRectangleInRect(aRect, xRadius, yRadius/*not currently supported*/, ne, se, sw, nw)
353 {
354  var path = CGPathCreateMutable(),
355  xMin = CGRectGetMinX(aRect),
356  xMax = CGRectGetMaxX(aRect),
357  yMin = CGRectGetMinY(aRect),
358  yMax = CGRectGetMaxY(aRect);
359 
360  CGPathMoveToPoint(path, nil, xMin + xRadius, yMin);
361 
362  if (ne)
363  {
364  CGPathAddLineToPoint(path, nil, xMax - xRadius, yMin);
365  CGPathAddCurveToPoint(path, nil, xMax - xRadius, yMin, xMax, yMin, xMax, yMin + xRadius);
366  }
367  else
368  CGPathAddLineToPoint(path, nil, xMax, yMin);
369 
370  if (se)
371  {
372  CGPathAddLineToPoint(path, nil, xMax, yMax - xRadius);
373  CGPathAddCurveToPoint(path, nil, xMax, yMax - xRadius, xMax, yMax, xMax - xRadius, yMax);
374  }
375  else
376  CGPathAddLineToPoint(path, nil, xMax, yMax);
377 
378  if (sw)
379  {
380  CGPathAddLineToPoint(path, nil, xMin + xRadius, yMax);
381  CGPathAddCurveToPoint(path, nil, xMin + xRadius, yMax, xMin, yMax, xMin, yMax - xRadius);
382  }
383  else
384  CGPathAddLineToPoint(path, nil, xMin, yMax);
385 
386  if (nw)
387  {
388  CGPathAddLineToPoint(path, nil, xMin, yMin + xRadius);
389  CGPathAddCurveToPoint(path, nil, xMin, yMin + xRadius, xMin, yMin, xMin + xRadius, yMin);
390  }
391  else
392  CGPathAddLineToPoint(path, nil, xMin, yMin);
393 
394  CGPathCloseSubpath(path);
395 
396  return path;
397 }
398 
399 function CGPathCloseSubpath(aPath)
400 {
401  var count = aPath.count;
402 
403  // Don't bother closing this subpath if there aren't any current elements, or the last element already closed the subpath.
404  if (count === 0 || aPath.elements[count - 1].type === kCGPathElementCloseSubpath)
405  return;
406 
407  // After closing, the current point is the previous path's starting point
408  aPath.current = CGPointCreateCopy(aPath.start);
409  aPath.elements[aPath.count++] = { type:kCGPathElementCloseSubpath, start:aPath.start };
410 }
411 
412 function CGPathEqualToPath(aPath, anotherPath)
413 {
414  if (aPath === anotherPath)
415  return YES;
416 
417  if (aPath.count !== anotherPath.count || !CGPointEqualToPoint(aPath.start, anotherPath.start) || !CGPointEqualToPoint(aPath.current, anotherPath.current))
418  return NO;
419 
420  var i = 0,
421  count = aPath.count;
422 
423  for (; i < count; ++i)
424  {
425  var element = aPath[i],
426  anotherElement = anotherPath[i];
427 
428  if (element.type !== anotherElement.type)
429  return NO;
430 
431  switch (element.type)
432  {
434  if (element.x !== anotherElement.x ||
435  element.y !== anotherElement.y ||
436  element.radius !== anotherElement.radius ||
437  element.startAngle !== anotherElement.startAngle ||
438  element.endAngle !== anotherElement.endAngle ||
439  element.isClockwise !== anotherElement.isClockwise)
440  {
441  return NO;
442  }
443  break;
444 
446  if (element.p1x !== anotherElement.p1x ||
447  element.p1y !== anotherElement.p1y ||
448  element.p2x !== anotherElement.p2x ||
449  element.p2y !== anotherElement.p2y ||
450  element.radius !== anotherElement.radius)
451  {
452  return NO;
453  }
454  break;
455 
457  if (element.cp1x !== anotherElement.cp1x ||
458  element.cp1y !== anotherElement.cp1y ||
459  element.cp2x !== anotherElement.cp2x ||
460  element.cp2y !== anotherElement.cp2y ||
461  element.x !== anotherElement.x ||
462  element.y !== anotherElement.y)
463  {
464  return NO;
465  }
466  break;
467 
470  if (element.x !== anotherElement.x ||
471  element.y !== anotherElement.y)
472  {
473  return NO;
474  }
475  break;
476 
478  if (element.cpx !== anotherElement.cpx ||
479  element.cpy !== anotherElement.cpy ||
480  element.x !== anotherElement.x ||
481  element.y !== anotherElement.y)
482  {
483  return NO;
484  }
485  break;
486 
488  if (!CGPointEqualToPoint(element.start, anotherElement.start))
489  return NO;
490  break;
491  }
492  }
493 
494  return YES;
495 }
496 
497 function CGPathGetCurrentPoint(aPath)
498 {
499  return CGPointCreateCopy(aPath.current);
500 }
501 
502 function CGPathIsEmpty(aPath)
503 {
504  return !aPath || aPath.count === 0;
505 }
506 
510 function CGPathGetBoundingBox(aPath)
511 {
512  if (!aPath || !aPath.count)
513  return CGRectMakeZero();
514 
515  var ox = 0,
516  oy = 0,
517  rx = 0,
518  ry = 0,
519  movePoint = nil;
520 
521  function addPoint(x, y)
522  {
523  ox = MIN(ox, x);
524  oy = MIN(oy, y);
525  rx = MAX(rx, x);
526  ry = MAX(ry, y);
527  }
528 
529  for (var i = 0, count = aPath.count; i < count; ++i)
530  {
531  var element = aPath.elements[i];
532 
533  // Just enclose all the control points. The curves must be inside of the control points.
534  // This won't work for CGPathGetBoundingBox.
535  switch (element.type)
536  {
538  if (movePoint)
539  {
540  addPoint(movePoint.x, movePoint.y);
541  movePoint = nil;
542  }
543 
544  addPoint(element.x, element.y);
545  break;
546 
548  if (movePoint)
549  {
550  addPoint(movePoint.x, movePoint.y);
551  movePoint = nil;
552  }
553 
554  addPoint(element.cp1x, element.cp1y);
555  addPoint(element.cp2x, element.cp2y);
556  addPoint(element.x, element.y);
557  break;
558 
560  if (movePoint)
561  {
562  addPoint(movePoint.x, movePoint.y);
563  movePoint = nil;
564  }
565 
566  addPoint(element.x, element.y);
567  break;
568 
570  if (movePoint)
571  {
572  addPoint(movePoint.x, movePoint.y);
573  movePoint = nil;
574  }
575 
576  addPoint(element.p1x, element.p1y);
577  addPoint(element.p2x, element.p2y);
578  break;
579 
581  if (movePoint)
582  {
583  addPoint(movePoint.x, movePoint.y);
584  movePoint = nil;
585  }
586 
587  addPoint(element.cpx, element.cpy);
588  addPoint(element.x, element.y);
589  break;
590 
592  movePoint = CGPointMake(element.x, element.y);
593  break;
594 
596  if (movePoint)
597  {
598  addPoint(movePoint.x, movePoint.y);
599  movePoint = nil;
600  }
601 
602  break;
603  }
604  }
605 
606  return CGRectMake(ox, oy, rx - ox, ry - oy);
607 }
608 
609 function CGPathContainsPoint(aPath, aTransform, point, eoFill)
610 {
611  if (!aPath.count)
612  return NO;
613 
614  if (aTransform)
615  point = CGPointApplyAffineTransform(point, aTransform);
616 
617  var context = CGBitmapGraphicsContextCreate();
618 
619  CGContextBeginPath(context);
620  CGContextAddPath(context, aPath);
621  CGContextClosePath(context);
622 
623  return context.isPointInPath(point.x, point.y);
624 }
625 
function CGPathWithRoundedRectangleInRect(aRect, xRadius, yRadius, ne, se, sw, nw)
Definition: CGPath.j:352
CGPath kCGPathElementMoveToPoint
Definition: CGPath.j:26
function CGPathGetBoundingBox(aPath)
Definition: CGPath.j:510
function CGPathCloseSubpath(aPath)
Definition: CGPath.j:399
function CGPathEqualToPath(aPath, anotherPath)
Definition: CGPath.j:412
function CGAffineTransformIsIdentity(aTransform)
function CGPathAddRects(aPath, aTransform, rects, count)
Definition: CGPath.j:281
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:301
kCGPathElementAddArcToPoint
Definition: CGPath.j:33
function CGPathAddLines(aPath, aTransform, points, count)
Definition: CGPath.j:187
function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
Definition: CGPath.j:170
function CGContextAddPath(aContext, aPath)
Definition: CGContext.j:258
function CGPathWithEllipseInRect(aRect)
Definition: CGPath.j:328
function CGPathIsEmpty(aPath)
Definition: CGPath.j:502
var KAPPA
Definition: CGPath.j:326
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:276
function CGSizeApplyAffineTransform(aSize, aTransform)
function CGPathCreateMutableCopy(aPath)
Definition: CGPath.j:52
function CGPathGetCurrentPoint(aPath)
Definition: CGPath.j:497
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:201
kCGPathElementAddQuadCurveToPoint
Definition: CGPath.j:28
function CGPointApplyAffineTransform(aPoint, aTransform)
function CGPathContainsPoint(aPath, aTransform, point, eoFill)
Definition: CGPath.j:609
function CGBitmapGraphicsContextCreate()
Definition: CGContext.j:136
function CGPathAddPath(aPath, aTransform, anotherPath)
Definition: CGPath.j:212
function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
Definition: CGPath.j:140
function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
Definition: CGPath.j:261