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