API  0.9.6
 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:NULL, current:NULL, 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  aPath.current = _CGPointMake(x + aRadius * COS(anEndAngle), y + aRadius * SIN(anEndAngle));
114  aPath.elements[aPath.count++] = { type:kCGPathElementAddArc, x:x, y:y, radius:aRadius, startAngle:aStartAngle, endAngle:anEndAngle };
115 }
116 
117 function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
118 {
119  var p1 = _CGPointMake(x1, y1),
120  p2 = _CGPointMake(x2, y2);
121 
122  if (aTransform)
123  {
124  p1 = _CGPointApplyAffineTransform(p1, aTransform);
125  p2 = _CGPointApplyAffineTransform(p2, aTransform);
126  }
127 
128  aPath.current = p2;
129  aPath.elements[aPath.count++] = { type:kCGPathElementAddArcToPoint, p1x:p1.x, p1y:p1.y, p2x:p2.x, p2y:p2.y, radius:aRadius };
130 }
131 
132 function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
133 {
134  var cp1 = _CGPointMake(cp1x, cp1y),
135  cp2 = _CGPointMake(cp2x, cp2y),
136  end = _CGPointMake(x, y);
137 
138  if (aTransform)
139  {
140  cp1 = _CGPointApplyAffineTransform(cp1, aTransform);
141  cp2 = _CGPointApplyAffineTransform(cp2, aTransform);
142  end = _CGPointApplyAffineTransform(end, aTransform);
143  }
144 
145  aPath.current = end;
146  aPath.elements[aPath.count++] = { type:kCGPathElementAddCurveToPoint, cp1x:cp1.x, cp1y:cp1.y, cp2x:cp2.x, cp2y:cp2.y, x:end.x, y:end.y };
147 }
148 
149 function CGPathAddLines(aPath, aTransform, points, count)
150 {
151  var i = 1;
152 
153  if (count === NULL)
154  var count = points.length;
155 
156  if (!aPath || count < 2)
157  return;
158 
159  CGPathMoveToPoint(aPath, aTransform, points[0].x, points[0].y);
160 
161  for (; i < count; ++i)
162  CGPathAddLineToPoint(aPath, aTransform, points[i].x, points[i].y);
163 }
164 
165 function CGPathAddLineToPoint(aPath, aTransform, x, y)
166 {
167  var point = _CGPointMake(x, y);
168 
169  if (aTransform != NULL)
170  point = _CGPointApplyAffineTransform(point, aTransform);
171 
172  aPath.elements[aPath.count++] = { type: kCGPathElementAddLineToPoint, x:point.x, y:point.y };
173  aPath.current = point;
174 }
175 
176 function CGPathAddPath(aPath, aTransform, anotherPath)
177 {
178  for (var i = 0, count = anotherPath.count; i < count; ++i)
179  {
180  var element = anotherPath.elements[i];
181 
182  switch (element.type)
183  {
184  case kCGPathElementAddLineToPoint: CGPathAddLineToPoint(aPath, aTransform, element.x, element.y);
185  break;
186 
188  element.cp1x, element.cp1y,
189  element.cp2x, element.cp2y,
190  element.x, element.y);
191  break;
192 
193  case kCGPathElementAddArc: CGPathAddArc(aPath, aTransform, element.x, element.y,
194  element.radius, element.startAngle,
195  element.endAngle, element.isClockwise);
196  break;
197 
198  case kCGPathElementAddArcToPoint: CGPathAddArcToPoint(aPath, aTransform,
199  element.p1x, element.p1y,
200  element.p2x, element.p2y,
201  element.radius);
202  break;
203 
205  element.cpx, element.cpy,
206  element.x, element.y);
207  break;
208 
209  case kCGPathElementMoveToPoint: CGPathMoveToPoint(aPath, aTransform, element.x, element.y);
210  break;
211 
213  break;
214  }
215  }
216 }
217 
218 function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
219 {
220  var cp = _CGPointMake(cpx, cpy),
221  end = _CGPointMake(x, y);
222 
223  if (aTransform)
224  {
225  cp = _CGPointApplyAffineTransform(cp, aTransform);
226  end = _CGPointApplyAffineTransform(end, aTransform);
227  }
228 
229  aPath.elements[aPath.count++] = { type:kCGPathElementAddQuadCurveToPoint, cpx:cp.x, cpy:cp.y, x:end.x, y:end.y }
230  aPath.current = end;
231 }
232 
233 function CGPathAddRect(aPath, aTransform, aRect)
234 {
235  CGPathAddRects(aPath, aTransform, [aRect], 1);
236 }
237 
238 function CGPathAddRects(aPath, aTransform, rects, count)
239 {
240  var i = 0;
241 
242  if (count === NULL)
243  var count = rects.length;
244 
245  for (; i < count; ++i)
246  {
247  var rect = rects[i];
248 
249  CGPathMoveToPoint(aPath, aTransform, _CGRectGetMinX(rect), _CGRectGetMinY(rect));
250  CGPathAddLineToPoint(aPath, aTransform, _CGRectGetMaxX(rect), _CGRectGetMinY(rect));
251  CGPathAddLineToPoint(aPath, aTransform, _CGRectGetMaxX(rect), _CGRectGetMaxY(rect));
252  CGPathAddLineToPoint(aPath, aTransform, _CGRectGetMinX(rect), _CGRectGetMaxY(rect));
253 
254  CGPathCloseSubpath(aPath);
255  }
256 }
257 
258 function CGPathMoveToPoint(aPath, aTransform, x, y)
259 {
260  var point = _CGPointMake(x, y),
261  count = aPath.count;
262 
263  if (aTransform != NULL)
264  point = _CGPointApplyAffineTransform(point, aTransform);
265 
266  aPath.start = point;
267  aPath.current = point;
268 
269  var previous = aPath.elements[count - 1];
270 
271  if (count != 0 && previous.type == kCGPathElementMoveToPoint)
272  {
273  previous.x = point.x;
274  previous.y = point.y;
275  }
276  else
277  aPath.elements[aPath.count++] = { type:kCGPathElementMoveToPoint, x:point.x, y:point.y };
278 }
279 
280 var KAPPA = 4.0 * ((SQRT2 - 1.0) / 3.0);
281 
282 function CGPathWithEllipseInRect(aRect)
283 {
284  var path = CGPathCreateMutable();
285 
286  if (_CGRectGetWidth(aRect) == _CGRectGetHeight(aRect))
287  CGPathAddArc(path, nil, _CGRectGetMidX(aRect), _CGRectGetMidY(aRect), _CGRectGetWidth(aRect) / 2.0, 0.0, 2 * PI, YES);
288  else
289  {
290  var axis = _CGSizeMake(_CGRectGetWidth(aRect) / 2.0, _CGRectGetHeight(aRect) / 2.0),
291  center = _CGPointMake(_CGRectGetMinX(aRect) + axis.width, _CGRectGetMinY(aRect) + axis.height);
292 
293  CGPathMoveToPoint(path, nil, center.x, center.y - axis.height);
294 
295  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);
296  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);
297  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);
298  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);
299  }
300 
301  CGPathCloseSubpath(path);
302 
303  return path;
304 }
305 
306 function CGPathWithRoundedRectangleInRect(aRect, xRadius, yRadius/*not currently supported*/, ne, se, sw, nw)
307 {
308  var path = CGPathCreateMutable(),
309  xMin = _CGRectGetMinX(aRect),
310  xMax = _CGRectGetMaxX(aRect),
311  yMin = _CGRectGetMinY(aRect),
312  yMax = _CGRectGetMaxY(aRect);
313 
314  CGPathMoveToPoint(path, nil, xMin + xRadius, yMin);
315 
316  if (ne)
317  {
318  CGPathAddLineToPoint(path, nil, xMax - xRadius, yMin);
319  CGPathAddCurveToPoint(path, nil, xMax - xRadius, yMin, xMax, yMin, xMax, yMin + xRadius);
320  }
321  else
322  CGPathAddLineToPoint(path, nil, xMax, yMin);
323 
324  if (se)
325  {
326  CGPathAddLineToPoint(path, nil, xMax, yMax - xRadius);
327  CGPathAddCurveToPoint(path, nil, xMax, yMax - xRadius, xMax, yMax, xMax - xRadius, yMax);
328  }
329  else
330  CGPathAddLineToPoint(path, nil, xMax, yMax);
331 
332  if (sw)
333  {
334  CGPathAddLineToPoint(path, nil, xMin + xRadius, yMax);
335  CGPathAddCurveToPoint(path, nil, xMin + xRadius, yMax, xMin, yMax, xMin, yMax - xRadius);
336  }
337  else
338  CGPathAddLineToPoint(path, nil, xMin, yMax);
339 
340  if (nw)
341  {
342  CGPathAddLineToPoint(path, nil, xMin, yMin + xRadius);
343  CGPathAddCurveToPoint(path, nil, xMin, yMin + xRadius, xMin, yMin, xMin + xRadius, yMin);
344  }
345  else
346  CGPathAddLineToPoint(path, nil, xMin, yMin);
347 
348  CGPathCloseSubpath(path);
349 
350  return path;
351 }
352 
353 function CGPathCloseSubpath(aPath)
354 {
355  var count = aPath.count;
356 
357  // Don't bother closing this subpath if there aren't any current elements, or the last element already closed the subpath.
358  if (count == 0 || aPath.elements[count - 1].type == kCGPathElementCloseSubpath)
359  return;
360 
361  aPath.elements[aPath.count++] = { type:kCGPathElementCloseSubpath, points:[aPath.start] };
362 }
363 
364 function CGPathEqualToPath(aPath, anotherPath)
365 {
366  if (aPath == anotherPath)
367  return YES;
368 
369  if (aPath.count != anotherPath.count || !_CGPointEqualToPoint(aPath.start, anotherPath.start) || !_CGPointEqualToPoint(aPath.current, anotherPath.current))
370  return NO;
371 
372  var i = 0,
373  count = aPath.count;
374 
375  for (; i < count; ++i)
376  {
377  var element = aPath[i],
378  anotherElement = anotherPath[i];
379 
380  if (element.type != anotherElement.type)
381  return NO;
382 
383  if ((element.type == kCGPathElementAddArc || element.type == kCGPathElementAddArcToPoint) &&
384  element.radius != anotherElement.radius)
385  return NO;
386 
387  var j = element.points.length;
388 
389  while (j--)
390  if (!_CGPointEqualToPoint(element.points[j], anotherElement.points[j]))
391  return NO;
392  }
393 
394  return YES;
395 }
396 
397 function CGPathGetCurrentPoint(aPath)
398 {
399  return _CGPointCreateCopy(aPath.current);
400 }
401 
402 function CGPathIsEmpty(aPath)
403 {
404  return !aPath || aPath.count == 0;
405 }
406 
410 function CGPathGetBoundingBox(aPath)
411 {
412  if (!aPath || !aPath.count)
413  return _CGRectMakeZero();
414 
415  var ox = 0,
416  oy = 0,
417  rx = 0,
418  ry = 0,
419  movePoint = nil;
420 
421  function addPoint(x, y)
422  {
423  ox = MIN(ox, x);
424  oy = MIN(oy, y);
425  rx = MAX(rx, x);
426  ry = MAX(ry, y);
427  }
428 
429  for (var i = 0, count = aPath.count; i < count; ++i)
430  {
431  var element = aPath.elements[i];
432 
433  // Just enclose all the control points. The curves must be inside of the control points.
434  // This won't work for CGPathGetPathBoundingBox.
435  switch (element.type)
436  {
438  if (movePoint)
439  {
440  addPoint(movePoint.x, movePoint.y);
441  movePoint = nil;
442  }
443 
444  addPoint(element.x, element.y);
445  break;
446 
448  if (movePoint)
449  {
450  addPoint(movePoint.x, movePoint.y);
451  movePoint = nil;
452  }
453 
454  addPoint(element.cp1x, element.cp1y);
455  addPoint(element.cp2x, element.cp2y);
456  addPoint(element.x, element.y);
457  break;
458 
460  if (movePoint)
461  {
462  addPoint(movePoint.x, movePoint.y);
463  movePoint = nil;
464  }
465 
466  addPoint(element.x, element.y);
467  break;
468 
470  if (movePoint)
471  {
472  addPoint(movePoint.x, movePoint.y);
473  movePoint = nil;
474  }
475 
476  addPoint(element.p1x, element.p1y);
477  addPoint(element.p2x, element.p2y);
478  break;
479 
481  if (movePoint)
482  {
483  addPoint(movePoint.x, movePoint.y);
484  movePoint = nil;
485  }
486 
487  addPoint(element.cpx, element.cpy);
488  addPoint(element.x, element.y);
489  break;
490 
492  movePoint = _CGPointMake(element.x, element.y);
493  break;
494 
496  if (movePoint)
497  {
498  addPoint(movePoint.x, movePoint.y);
499  movePoint = nil;
500  }
501 
502  break;
503  }
504  }
505 
506  return _CGRectMake(ox, oy, rx - ox, ry - oy);
507 }
508