xref: /haiku/src/libs/icon/shape/VectorPath.cpp (revision a1163de83ea633463a79de234b8742ee106531b2)
1 /*
2  * Copyright 2006, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 
9 #include "VectorPath.h"
10 
11 #include <malloc.h>
12 #include <stdio.h>
13 #include <string.h>
14 
15 #include <agg_basics.h>
16 #include <agg_bounding_rect.h>
17 #include <agg_conv_curve.h>
18 #include <agg_curves.h>
19 #include <agg_math.h>
20 
21 #ifdef ICON_O_MATIC
22 #include <debugger.h>
23 #include <typeinfo>
24 #endif // ICON_O_MATIC
25 
26 #include <Message.h>
27 #include <TypeConstants.h>
28 
29 #ifdef ICON_O_MATIC
30 # include "support.h"
31 
32 # include "CommonPropertyIDs.h"
33 # include "IconProperty.h"
34 # include "Icons.h"
35 # include "Property.h"
36 # include "PropertyObject.h"
37 #endif // ICON_O_MATIC
38 
39 #include "Transformable.h"
40 
41 #define obj_new(type, n)		((type *)malloc ((n) * sizeof(type)))
42 #define obj_renew(p, type, n)	((type *)realloc (p, (n) * sizeof(type)))
43 #define obj_free				free
44 
45 #define ALLOC_CHUNKS 20
46 
47 // get_path_storage
48 bool
49 get_path_storage(agg::path_storage& path,
50 				 const control_point* points, int32 count, bool closed)
51 {
52 	if (count > 1) {
53 		path.move_to(points[0].point.x,
54 					 points[0].point.y);
55 
56 		for (int32 i = 1; i < count; i++) {
57 			path.curve4(points[i - 1].point_out.x,
58 						points[i - 1].point_out.y,
59 						points[i].point_in.x,
60 						points[i].point_in.y,
61 						points[i].point.x,
62 						points[i].point.y);
63 		}
64 		if (closed) {
65 			// curve from last to first control point
66 			path.curve4(points[count - 1].point_out.x,
67 						points[count - 1].point_out.y,
68 						points[0].point_in.x,
69 						points[0].point_in.y,
70 						points[0].point.x,
71 						points[0].point.y);
72 			path.close_polygon();
73 		}
74 
75 		return true;
76 	}
77 	return false;
78 }
79 
80 // #pragma mark -
81 
82 #ifdef ICON_O_MATIC
83 PathListener::PathListener() {}
84 PathListener::~PathListener() {}
85 #endif
86 
87 // #pragma mark -
88 
89 // constructor
90 VectorPath::VectorPath()
91 #ifdef ICON_O_MATIC
92 	: BArchivable(),
93 	  IconObject("<path>"),
94 	  fListeners(20),
95 #else
96 	:
97 #endif
98 	  fPath(NULL),
99 	  fClosed(false),
100 	  fPointCount(0),
101 	  fAllocCount(0),
102 	  fCachedBounds(0.0, 0.0, -1.0, -1.0)
103 {
104 }
105 
106 // constructor
107 VectorPath::VectorPath(const VectorPath& from)
108 #ifdef ICON_O_MATIC
109 	: BArchivable(),
110 	  IconObject(from),
111 	  fListeners(20),
112 #else
113 	:
114 #endif
115 	  fPath(NULL),
116 	  fClosed(false),
117 	  fPointCount(0),
118 	  fAllocCount(0),
119 	  fCachedBounds(0.0, 0.0, -1.0, -1.0)
120 {
121 	*this = from;
122 }
123 
124 // constructor
125 VectorPath::VectorPath(BMessage* archive)
126 #ifdef ICON_O_MATIC
127 	: BArchivable(),
128 	  IconObject(archive),
129 	  fListeners(20),
130 #else
131 	:
132 #endif
133 	  fPath(NULL),
134 	  fClosed(false),
135 	  fPointCount(0),
136 	  fAllocCount(0),
137 	  fCachedBounds(0.0, 0.0, -1.0, -1.0)
138 {
139 	if (!archive)
140 		return;
141 
142 	type_code typeFound;
143 	int32 countFound;
144 	if (archive->GetInfo("point", &typeFound, &countFound) >= B_OK
145 		&& typeFound == B_POINT_TYPE
146 		&& _SetPointCount(countFound)) {
147 
148 		memset(fPath, 0, fAllocCount * sizeof(control_point));
149 
150 		BPoint point;
151 		BPoint pointIn;
152 		BPoint pointOut;
153 		bool connected;
154 		for (int32 i = 0; i < fPointCount
155 						  && archive->FindPoint("point", i, &point) >= B_OK
156 						  && archive->FindPoint("point in", i, &pointIn) >= B_OK
157 						  && archive->FindPoint("point out", i, &pointOut) >= B_OK
158 						  && archive->FindBool("connected", i, &connected) >= B_OK; i++) {
159 			fPath[i].point = point;
160 			fPath[i].point_in = pointIn;
161 			fPath[i].point_out = pointOut;
162 			fPath[i].connected = connected;
163 		}
164 	}
165 	if (archive->FindBool("path closed", &fClosed) < B_OK)
166 		fClosed = false;
167 
168 }
169 
170 // destructor
171 VectorPath::~VectorPath()
172 {
173 	if (fPath)
174 		obj_free(fPath);
175 
176 #ifdef ICON_O_MATIC
177 	if (fListeners.CountItems() > 0) {
178 		PathListener* listener = (PathListener*)fListeners.ItemAt(0);
179 		char message[512];
180 		sprintf(message, "VectorPath::~VectorPath() - "
181 				 "there are still listeners attached! %p/%s",
182 				 listener, typeid(*listener).name());
183 		debugger(message);
184 	}
185 #endif
186 }
187 
188 // #pragma mark -
189 
190 #ifdef ICON_O_MATIC
191 
192 // MakePropertyObject
193 PropertyObject*
194 VectorPath::MakePropertyObject() const
195 {
196 	PropertyObject* object = IconObject::MakePropertyObject();
197 	if (!object)
198 		return NULL;
199 
200 	// closed
201 	object->AddProperty(new BoolProperty(PROPERTY_CLOSED, fClosed));
202 
203 	// archived path
204 	BMessage* archive = new BMessage();
205 	if (Archive(archive) == B_OK) {
206 		object->AddProperty(new IconProperty(PROPERTY_PATH,
207 											 kPathPropertyIconBits,
208 											 kPathPropertyIconWidth,
209 											 kPathPropertyIconHeight,
210 											 kPathPropertyIconFormat,
211 											 archive));
212 	}
213 
214 	return object;
215 }
216 
217 // SetToPropertyObject
218 bool
219 VectorPath::SetToPropertyObject(const PropertyObject* object)
220 {
221 	AutoNotificationSuspender _(this);
222 	IconObject::SetToPropertyObject(object);
223 
224 	// closed
225 	SetClosed(object->Value(PROPERTY_CLOSED, fClosed));
226 
227 	// archived path
228 	IconProperty* pathProperty = dynamic_cast<IconProperty*>(
229 		object->FindProperty(PROPERTY_PATH));
230 	if (pathProperty && pathProperty->Message()) {
231 		VectorPath archivedPath(pathProperty->Message());
232 		*this = archivedPath;
233 	}
234 
235 	return HasPendingNotifications();
236 }
237 
238 // Archive
239 status_t
240 VectorPath::Archive(BMessage* into, bool deep) const
241 {
242 	status_t ret = IconObject::Archive(into, deep);
243 	if (ret < B_OK)
244 		return ret;
245 
246 	if (fPointCount > 0) {
247 		// improve BMessage efficency by preallocating storage for all points
248 		// with the first call
249 		ret = into->AddData("point", B_POINT_TYPE, &fPath[0].point,
250 							sizeof(BPoint), true, fPointCount);
251 		if (ret >= B_OK)
252 			ret = into->AddData("point in", B_POINT_TYPE, &fPath[0].point_in,
253 								sizeof(BPoint), true, fPointCount);
254 		if (ret >= B_OK)
255 			ret = into->AddData("point out", B_POINT_TYPE, &fPath[0].point_out,
256 								sizeof(BPoint), true, fPointCount);
257 		if (ret >= B_OK)
258 			ret = into->AddData("connected", B_BOOL_TYPE, &fPath[0].connected,
259 								sizeof(bool), true, fPointCount);
260 		// add the rest of the points
261 		for (int32 i = 1; i < fPointCount && ret >= B_OK; i++) {
262 			ret = into->AddData("point", B_POINT_TYPE, &fPath[i].point, sizeof(BPoint));
263 			if (ret >= B_OK)
264 				ret = into->AddData("point in", B_POINT_TYPE, &fPath[i].point_in, sizeof(BPoint));
265 			if (ret >= B_OK)
266 				ret = into->AddData("point out", B_POINT_TYPE, &fPath[i].point_out, sizeof(BPoint));
267 			if (ret >= B_OK)
268 				ret = into->AddData("connected", B_BOOL_TYPE, &fPath[i].connected, sizeof(bool));
269 		}
270 	}
271 
272 	if (ret >= B_OK) {
273 		ret = into->AddBool("path closed", fClosed);
274 	} else {
275 		fprintf(stderr, "failed adding points!\n");
276 	}
277 	if (ret < B_OK) {
278 		fprintf(stderr, "failed adding close!\n");
279 	}
280 	// finish off
281 	if (ret < B_OK) {
282 		ret = into->AddString("class", "VectorPath");
283 	}
284 
285 	return ret;
286 }
287 
288 #endif // ICON_O_MATIC
289 
290 // #pragma mark -
291 
292 // operator=
293 VectorPath&
294 VectorPath::operator=(const VectorPath& from)
295 {
296 	_SetPointCount(from.fPointCount);
297 	fClosed = from.fClosed;
298 	if (fPath) {
299 		memcpy(fPath, from.fPath, fPointCount * sizeof(control_point));
300 		fCachedBounds = from.fCachedBounds;
301 	} else {
302 		fprintf(stderr, "VectorPath() -> allocation failed in operator=!\n");
303 		fAllocCount = 0;
304 		fPointCount = 0;
305 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
306 	}
307 	Notify();
308 
309 	return *this;
310 }
311 
312 // MakeEmpty
313 void
314 VectorPath::MakeEmpty()
315 {
316 	_SetPointCount(0);
317 }
318 
319 // #pragma mark -
320 
321 // AddPoint
322 bool
323 VectorPath::AddPoint(BPoint point)
324 {
325 	int32 index = fPointCount;
326 
327 	if (_SetPointCount(fPointCount + 1)) {
328 		_SetPoint(index, point);
329 		_NotifyPointAdded(index);
330 		return true;
331 	}
332 
333 	return false;
334 }
335 
336 // AddPoint
337 bool
338 VectorPath::AddPoint(const BPoint& point,
339 					 const BPoint& pointIn,
340 					 const BPoint& pointOut,
341 					 bool connected)
342 {
343 	int32 index = fPointCount;
344 
345 	if (_SetPointCount(fPointCount + 1)) {
346 		_SetPoint(index, point, pointIn, pointOut, connected);
347 		_NotifyPointAdded(index);
348 		return true;
349 	}
350 
351 	return false;
352 }
353 
354 // AddPoint
355 bool
356 VectorPath::AddPoint(BPoint point, int32 index)
357 {
358 	if (index < 0)
359 		index = 0;
360 	if (index > fPointCount)
361 		index = fPointCount;
362 
363 	if (_SetPointCount(fPointCount + 1)) {
364 		// handle insert
365 		if (index < fPointCount - 1) {
366 			for (int32 i = fPointCount; i > index; i--) {
367 				fPath[i].point = fPath[i - 1].point;
368 				fPath[i].point_in = fPath[i - 1].point_in;
369 				fPath[i].point_out = fPath[i - 1].point_out;
370 				fPath[i].connected = fPath[i - 1].connected;
371 			}
372 		}
373 		_SetPoint(index, point);
374 		_NotifyPointAdded(index);
375 		return true;
376 	}
377 	return false;
378 }
379 
380 // RemovePoint
381 bool
382 VectorPath::RemovePoint(int32 index)
383 {
384 	if (index >= 0 && index < fPointCount) {
385 
386 		if (index < fPointCount - 1) {
387 			// move points
388 			for (int32 i = index; i < fPointCount - 1; i++) {
389 				fPath[i].point = fPath[i + 1].point;
390 				fPath[i].point_in = fPath[i + 1].point_in;
391 				fPath[i].point_out = fPath[i + 1].point_out;
392 				fPath[i].connected = fPath[i + 1].connected;
393 			}
394 		}
395 		fPointCount -= 1;
396 
397 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
398 
399 		_NotifyPointRemoved(index);
400 		return true;
401 	}
402 	return false;
403 }
404 
405 // SetPoint
406 bool
407 VectorPath::SetPoint(int32 index, BPoint point)
408 {
409 	if (index == fPointCount)
410 		index = 0;
411 	if (index >= 0 && index < fPointCount) {
412 		BPoint offset = point - fPath[index].point;
413 		fPath[index].point = point;
414 		fPath[index].point_in += offset;
415 		fPath[index].point_out += offset;
416 
417 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
418 
419 		_NotifyPointChanged(index);
420 		return true;
421 	}
422 	return false;
423 }
424 
425 // SetPoint
426 bool
427 VectorPath::SetPoint(int32 index, BPoint point,
428 								  BPoint pointIn, BPoint pointOut,
429 								  bool connected)
430 {
431 	if (index == fPointCount)
432 		index = 0;
433 	if (index >= 0 && index < fPointCount) {
434 		fPath[index].point = point;
435 		fPath[index].point_in = pointIn;
436 		fPath[index].point_out = pointOut;
437 		fPath[index].connected = connected;
438 
439 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
440 
441 		_NotifyPointChanged(index);
442 		return true;
443 	}
444 	return false;
445 }
446 
447 // SetPointIn
448 bool
449 VectorPath::SetPointIn(int32 i, BPoint point)
450 {
451 	if (i == fPointCount)
452 		i = 0;
453 	if (i >= 0 && i < fPointCount) {
454 		// first, set the "in" point
455 		fPath[i].point_in = point;
456 		// now see what to do about the "out" point
457 		if (fPath[i].connected) {
458 			// keep all three points in one line
459 			BPoint v = fPath[i].point - fPath[i].point_in;
460 			float distIn = sqrtf(v.x * v.x + v.y * v.y);
461 			if (distIn > 0.0) {
462 				float distOut = agg::calc_distance(fPath[i].point.x, fPath[i].point.y,
463 										fPath[i].point_out.x, fPath[i].point_out.y);
464 				float scale = (distIn + distOut) / distIn;
465 				v.x *= scale;
466 				v.y *= scale;
467 				fPath[i].point_out = fPath[i].point_in + v;
468 			}
469 		}
470 
471 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
472 
473 		_NotifyPointChanged(i);
474 		return true;
475 	}
476 	return false;
477 }
478 
479 // SetPointOut
480 bool
481 VectorPath::SetPointOut(int32 i, BPoint point, bool mirrorDist)
482 {
483 	if (i == fPointCount)
484 		i = 0;
485 	if (i >= 0 && i < fPointCount) {
486 		// first, set the "out" point
487 		fPath[i].point_out = point;
488 		// now see what to do about the "out" point
489 		if (mirrorDist) {
490 			// mirror "in" point around main control point
491 			BPoint v = fPath[i].point - fPath[i].point_out;
492 			fPath[i].point_in = fPath[i].point + v;
493 		} else if (fPath[i].connected) {
494 			// keep all three points in one line
495 			BPoint v = fPath[i].point - fPath[i].point_out;
496 			float distOut = sqrtf(v.x * v.x + v.y * v.y);
497 			if (distOut > 0.0) {
498 				float distIn = agg::calc_distance(fPath[i].point.x, fPath[i].point.y,
499 										fPath[i].point_in.x, fPath[i].point_in.y);
500 				float scale = (distIn + distOut) / distOut;
501 				v.x *= scale;
502 				v.y *= scale;
503 				fPath[i].point_in = fPath[i].point_out + v;
504 			}
505 		}
506 
507 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
508 
509 		_NotifyPointChanged(i);
510 		return true;
511 	}
512 	return false;
513 }
514 
515 // SetInOutConnected
516 bool
517 VectorPath::SetInOutConnected(int32 index, bool connected)
518 {
519 	if (index >= 0 && index < fPointCount) {
520 		fPath[index].connected = connected;
521 		_NotifyPointChanged(index);
522 		return true;
523 	}
524 	return false;
525 }
526 
527 // #pragma mark -
528 
529 // GetPointAt
530 bool
531 VectorPath::GetPointAt(int32 index, BPoint& point) const
532 {
533 	if (index == fPointCount)
534 		index = 0;
535 	if (index >= 0 && index < fPointCount) {
536 		point = fPath[index].point;
537 		return true;
538 	}
539 	return false;
540 }
541 
542 // GetPointInAt
543 bool
544 VectorPath::GetPointInAt(int32 index, BPoint& point) const
545 {
546 	if (index == fPointCount)
547 		index = 0;
548 	if (index >= 0 && index < fPointCount) {
549 		point = fPath[index].point_in;
550 		return true;
551 	}
552 	return false;
553 }
554 
555 // GetPointOutAt
556 bool
557 VectorPath::GetPointOutAt(int32 index, BPoint& point) const
558 {
559 	if (index == fPointCount)
560 		index = 0;
561 	if (index >= 0 && index < fPointCount) {
562 		point = fPath[index].point_out;
563 		return true;
564 	}
565 	return false;
566 }
567 
568 // GetPointsAt
569 bool
570 VectorPath::GetPointsAt(int32 index, BPoint& point,
571 						BPoint& pointIn, BPoint& pointOut, bool* connected) const
572 {
573 	if (index >= 0 && index < fPointCount) {
574 		point = fPath[index].point;
575 		pointIn = fPath[index].point_in;
576 		pointOut = fPath[index].point_out;
577 
578 		if (connected)
579 			*connected = fPath[index].connected;
580 
581 		return true;
582 	}
583 	return false;
584 }
585 
586 // CountPoints
587 int32
588 VectorPath::CountPoints() const
589 {
590 	return fPointCount;
591 }
592 
593 // #pragma mark -
594 
595 #ifdef ICON_O_MATIC
596 
597 // distance_to_curve
598 static float
599 distance_to_curve(const BPoint& p, const BPoint& a, const BPoint& aOut, const BPoint& bIn, const BPoint& b)
600 {
601 	agg::curve4_inc curve(a.x, a.y, aOut.x, aOut.y,
602 						  bIn.x, bIn.y, b.x, b.y);
603 
604 	float segDist = FLT_MAX;
605 	double x1, y1, x2, y2;
606 	unsigned cmd = curve.vertex(&x1, &y1);
607 	while (!agg::is_stop(cmd)) {
608 		cmd = curve.vertex(&x2, &y2);
609 		// first figure out if point is between segment start and end points
610 		double a = agg::calc_distance(p.x, p.y, x2, y2);
611 		double b = agg::calc_distance(p.x, p.y, x1, y1);
612 
613 		float currentDist = min_c(a, b);
614 
615 		if (a > 0.0 && b > 0.0) {
616 			double c = agg::calc_distance(x1, y1, x2, y2);
617 
618 			double alpha = acos((b*b + c*c - a*a) / (2*b*c));
619 			double beta = acos((a*a + c*c - b*b) / (2*a*c));
620 
621 			if (alpha <= PI2 && beta <= PI2) {
622 				currentDist = fabs(agg::calc_line_point_distance(
623 											x1, y1, x2, y2, p.x, p.y));
624 			}
625 		}
626 
627 		if (currentDist < segDist) {
628 			segDist = currentDist;
629 		}
630 		x1 = x2;
631 		y1 = y2;
632 	}
633 	return segDist;
634 }
635 
636 // GetDistance
637 bool
638 VectorPath::GetDistance(BPoint p, float* distance, int32* index) const
639 {
640 	if (fPointCount > 1) {
641 		// generate a curve for each segment of the path
642 		// then	iterate over the segments of the curve measuring the distance
643 		*distance = FLT_MAX;
644 
645 		for (int32 i = 0; i < fPointCount - 1; i++) {
646 			float segDist = distance_to_curve(p,
647 											  fPath[i].point,
648 											  fPath[i].point_out,
649 											  fPath[i + 1].point_in,
650 											  fPath[i + 1].point);
651 			if (segDist < *distance) {
652 				*distance = segDist;
653 				*index = i + 1;
654 			}
655 		}
656 		if (fClosed) {
657 			float segDist = distance_to_curve(p,
658 											  fPath[fPointCount - 1].point,
659 											  fPath[fPointCount - 1].point_out,
660 											  fPath[0].point_in,
661 											  fPath[0].point);
662 			if (segDist < *distance) {
663 				*distance = segDist;
664 				*index = fPointCount;
665 			}
666 		}
667 		return true;
668 	}
669 	return false;
670 }
671 
672 // FindBezierScale
673 bool
674 VectorPath::FindBezierScale(int32 index, BPoint point, double* scale) const
675 {
676 	if (index >= 0 && index < fPointCount && scale) {
677 
678 		int maxStep = 1000;
679 
680 		double t = 0.0;
681 		double dt = 1.0 / maxStep;
682 
683 		*scale = 0.0;
684 		double min = FLT_MAX;
685 
686 		BPoint curvePoint;
687 		for (int step = 1; step < maxStep; step++) {
688 			t += dt;
689 
690 			GetPoint(index, t, curvePoint);
691 			double d = agg::calc_distance(curvePoint.x, curvePoint.y,
692 								point.x, point.y);
693 
694 			if (d < min) {
695 				min = d;
696 				*scale = t;
697 			}
698 		}
699 		return true;
700 	}
701 	return false;
702 }
703 
704 // GetPoint
705 bool
706 VectorPath::GetPoint(int32 index, double t, BPoint& point) const
707 {
708 	if (index >= 0 && index < fPointCount) {
709 
710 		double t1 = (1 - t) * (1 - t) * (1 - t);
711 		double t2 = (1 - t) * (1 - t) * t * 3;
712 		double t3 = (1 - t) * t * t * 3;
713 		double t4 = t * t * t;
714 
715 		if (index < fPointCount - 1) {
716 			point.x = fPath[index].point.x * t1 +
717 	   				  fPath[index].point_out.x * t2 +
718 	   				  fPath[index + 1].point_in.x * t3 +
719 	   				  fPath[index + 1].point.x * t4;
720 
721 			point.y = fPath[index].point.y * t1 +
722 					  fPath[index].point_out.y * t2 +
723 					  fPath[index + 1].point_in.y * t3 +
724 					  fPath[index + 1].point.y * t4;
725 		} else if (fClosed) {
726 			point.x = fPath[fPointCount - 1].point.x * t1 +
727 	   				  fPath[fPointCount - 1].point_out.x * t2 +
728 	   				  fPath[0].point_in.x * t3 +
729 	   				  fPath[0].point.x * t4;
730 
731 			point.y = fPath[fPointCount - 1].point.y * t1 +
732 					  fPath[fPointCount - 1].point_out.y * t2 +
733 					  fPath[0].point_in.y * t3 +
734 					  fPath[0].point.y * t4;
735 		}
736 
737 		return true;
738 	}
739 	return false;
740 }
741 
742 #endif // ICON_O_MATIC
743 
744 // SetClosed
745 void
746 VectorPath::SetClosed(bool closed)
747 {
748 	if (fClosed != closed) {
749 		fClosed = closed;
750 		_NotifyClosedChanged();
751 		Notify();
752 	}
753 }
754 
755 // Bounds
756 BRect
757 VectorPath::Bounds() const
758 {
759 	// the bounds of the actual curves, not the control points!
760 	if (!fCachedBounds.IsValid())
761 		 fCachedBounds = _Bounds();
762 	return fCachedBounds;
763 }
764 
765 // Bounds
766 BRect
767 VectorPath::_Bounds() const
768 {
769 	agg::path_storage path;
770 
771 	BRect b;
772 	if (get_path_storage(path, fPath, fPointCount, fClosed)) {
773 
774 		agg::conv_curve<agg::path_storage> curve(path);
775 
776 		uint32 pathID[1];
777 		pathID[0] = 0;
778 		double left, top, right, bottom;
779 
780 		agg::bounding_rect(curve, pathID, 0, 1, &left, &top, &right, &bottom);
781 
782 		b.Set(left, top, right, bottom);
783 	} else if (fPointCount == 1) {
784 		b.Set(fPath[0].point.x, fPath[0].point.y, fPath[0].point.x, fPath[0].point.y);
785 	} else {
786 		b.Set(0.0, 0.0, -1.0, -1.0);
787 	}
788 	return b;
789 }
790 
791 // ControlPointBounds
792 BRect
793 VectorPath::ControlPointBounds() const
794 {
795 	if (fPointCount > 0) {
796 		BRect r(fPath[0].point, fPath[0].point);
797 		for (int32 i = 0; i < fPointCount; i++) {
798 			// include point
799 			r.left = min_c(r.left, fPath[i].point.x);
800 			r.top = min_c(r.top, fPath[i].point.y);
801 			r.right = max_c(r.right, fPath[i].point.x);
802 			r.bottom = max_c(r.bottom, fPath[i].point.y);
803 			// include "in" point
804 			r.left = min_c(r.left, fPath[i].point_in.x);
805 			r.top = min_c(r.top, fPath[i].point_in.y);
806 			r.right = max_c(r.right, fPath[i].point_in.x);
807 			r.bottom = max_c(r.bottom, fPath[i].point_in.y);
808 			// include "out" point
809 			r.left = min_c(r.left, fPath[i].point_out.x);
810 			r.top = min_c(r.top, fPath[i].point_out.y);
811 			r.right = max_c(r.right, fPath[i].point_out.x);
812 			r.bottom = max_c(r.bottom, fPath[i].point_out.y);
813 		}
814 		return r;
815 	}
816 	return BRect(0.0, 0.0, -1.0, -1.0);
817 }
818 
819 // Iterate
820 void
821 VectorPath::Iterate(Iterator* iterator, float smoothScale) const
822 {
823 	if (fPointCount > 1) {
824 		// generate a curve for each segment of the path
825 		// then	iterate over the segments of the curve
826 		agg::curve4_inc curve;
827 		curve.approximation_scale(smoothScale);
828 
829 		for (int32 i = 0; i < fPointCount - 1; i++) {
830 iterator->MoveTo(fPath[i].point);
831 			curve.init(fPath[i].point.x, fPath[i].point.y,
832 					   fPath[i].point_out.x, fPath[i].point_out.y,
833 					   fPath[i + 1].point_in.x, fPath[i + 1].point_in.y,
834 					   fPath[i + 1].point.x, fPath[i + 1].point.y);
835 
836 			double x, y;
837 			unsigned cmd = curve.vertex(&x, &y);
838 			while (!agg::is_stop(cmd)) {
839 				BPoint p(x, y);
840 				iterator->LineTo(p);
841 				cmd = curve.vertex(&x, &y);
842 			}
843 		}
844 		if (fClosed) {
845 iterator->MoveTo(fPath[fPointCount - 1].point);
846 			curve.init(fPath[fPointCount - 1].point.x, fPath[fPointCount - 1].point.y,
847 					   fPath[fPointCount - 1].point_out.x, fPath[fPointCount - 1].point_out.y,
848 					   fPath[0].point_in.x, fPath[0].point_in.y,
849 					   fPath[0].point.x, fPath[0].point.y);
850 
851 			double x, y;
852 			unsigned cmd = curve.vertex(&x, &y);
853 			while (!agg::is_stop(cmd)) {
854 				BPoint p(x, y);
855 				iterator->LineTo(p);
856 				cmd = curve.vertex(&x, &y);
857 			}
858 		}
859 	}
860 }
861 
862 // CleanUp
863 void
864 VectorPath::CleanUp()
865 {
866 	if (fPointCount == 0)
867 		return;
868 
869 	bool notify = false;
870 
871 	// remove last point if it is coincident with the first
872 	if (fClosed && fPointCount >= 1) {
873 		if (fPath[0].point == fPath[fPointCount - 1].point) {
874 			fPath[0].point_in = fPath[fPointCount - 1].point_in;
875 			_SetPointCount(fPointCount - 1);
876 			notify = true;
877 		}
878 	}
879 
880 	for (int32 i = 0; i < fPointCount; i++) {
881 		// check for unnecessary, duplicate points
882 		if (i > 0) {
883 			if (fPath[i - 1].point == fPath[i].point &&
884 				fPath[i - 1].point == fPath[i - 1].point_out &&
885 				fPath[i].point == fPath[i].point_in) {
886 				// the previous point can be removed
887 				BPoint in = fPath[i - 1].point_in;
888 				if (RemovePoint(i - 1)) {
889 					i--;
890 					fPath[i].point_in = in;
891 					notify = true;
892 				}
893 			}
894 		}
895 		// re-establish connections of in-out control points if
896 		// they line up with the main control point
897 		if (fPath[i].point_in == fPath[i].point_out ||
898 			fPath[i].point == fPath[i].point_out ||
899 			fPath[i].point == fPath[i].point_in ||
900 			(fabs(agg::calc_line_point_distance(
901 							fPath[i].point_in.x, fPath[i].point_in.y,
902 							fPath[i].point.x, fPath[i].point.y,
903 							fPath[i].point_out.x, fPath[i].point_out.y)) < 0.01 &&
904 			 fabs(agg::calc_line_point_distance(
905 			 				fPath[i].point_out.x, fPath[i].point_out.y,
906 							fPath[i].point.x, fPath[i].point.y,
907 							fPath[i].point_in.x, fPath[i].point_in.y)) < 0.01)) {
908 
909 			fPath[i].connected = true;
910 			notify = true;
911 		}
912 	}
913 
914 	if (notify)
915 		_NotifyPathChanged();
916 }
917 
918 // Reverse
919 void
920 VectorPath::Reverse()
921 {
922 	VectorPath temp(*this);
923 	int32 index = 0;
924 	for (int32 i = fPointCount - 1; i >= 0; i--) {
925 		temp.SetPoint(index, fPath[i].point,
926 							 fPath[i].point_out,
927 							 fPath[i].point_in,
928 							 fPath[i].connected);
929 		index++;
930 	}
931 	*this = temp;
932 
933 	_NotifyPathReversed();
934 }
935 
936 // ApplyTransform
937 void
938 VectorPath::ApplyTransform(const Transformable& transform)
939 {
940 	if (transform.IsIdentity())
941 		return;
942 
943 	for (int32 i = 0; i < fPointCount; i++) {
944 		transform.Transform(&(fPath[i].point));
945 		transform.Transform(&(fPath[i].point_out));
946 		transform.Transform(&(fPath[i].point_in));
947 	}
948 
949 	_NotifyPathChanged();
950 }
951 
952 // PrintToStream
953 void
954 VectorPath::PrintToStream() const
955 {
956 	for (int32 i = 0; i < fPointCount; i++) {
957 		printf("point %ld: (%f, %f) -> (%f, %f) -> (%f, %f) (%d)\n", i,
958 				fPath[i].point_in.x, fPath[i].point_in.y,
959 				fPath[i].point.x, fPath[i].point.y,
960 				fPath[i].point_out.x, fPath[i].point_out.y,
961 				fPath[i].connected);
962 	}
963 }
964 
965 // GetAGGPathStorage
966 bool
967 VectorPath::GetAGGPathStorage(agg::path_storage& path) const
968 {
969 	return get_path_storage(path, fPath, fPointCount, fClosed);
970 }
971 
972 // #pragma mark -
973 
974 #ifdef ICON_O_MATIC
975 
976 // AddListener
977 bool
978 VectorPath::AddListener(PathListener* listener)
979 {
980 	if (listener && !fListeners.HasItem((void*)listener))
981 		return fListeners.AddItem((void*)listener);
982 	return false;
983 }
984 
985 // RemoveListener
986 bool
987 VectorPath::RemoveListener(PathListener* listener)
988 {
989 	return fListeners.RemoveItem((void*)listener);
990 }
991 
992 // CountListeners
993 int32
994 VectorPath::CountListeners() const
995 {
996 	return fListeners.CountItems();
997 }
998 
999 // ListenerAtFast
1000 PathListener*
1001 VectorPath::ListenerAtFast(int32 index) const
1002 {
1003 	return (PathListener*)fListeners.ItemAtFast(index);
1004 }
1005 
1006 #endif // ICON_O_MATIC
1007 
1008 // #pragma mark -
1009 
1010 // _SetPoint
1011 void
1012 VectorPath::_SetPoint(int32 index, BPoint point)
1013 {
1014 	fPath[index].point = point;
1015 	fPath[index].point_in = point;
1016 	fPath[index].point_out = point;
1017 
1018 	fPath[index].connected = true;
1019 
1020 	fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
1021 }
1022 
1023 // _SetPoint
1024 void
1025 VectorPath::_SetPoint(int32 index,
1026 					  const BPoint& point,
1027 					  const BPoint& pointIn,
1028 					  const BPoint& pointOut,
1029 					  bool connected)
1030 {
1031 	fPath[index].point = point;
1032 	fPath[index].point_in = pointIn;
1033 	fPath[index].point_out = pointOut;
1034 
1035 	fPath[index].connected = connected;
1036 
1037 	fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
1038 }
1039 
1040 // #pragma mark -
1041 
1042 // _SetPointCount
1043 bool
1044 VectorPath::_SetPointCount(int32 count)
1045 {
1046 	// handle reallocation if we run out of room
1047 	if (count >= fAllocCount) {
1048 		fAllocCount = ((count) / ALLOC_CHUNKS + 1) * ALLOC_CHUNKS;
1049 		if (fPath) {
1050 			fPath = obj_renew(fPath, control_point, fAllocCount);
1051 		} else {
1052 			fPath = obj_new(control_point, fAllocCount);
1053 		}
1054 		memset(fPath + fPointCount, 0, (fAllocCount - fPointCount) * sizeof(control_point));
1055 	}
1056 	// update point count
1057 	if (fPath) {
1058 		fPointCount = count;
1059 	} else {
1060 		// reallocation might have failed
1061 		fPointCount = 0;
1062 		fAllocCount = 0;
1063 		fprintf(stderr, "VectorPath::_SetPointCount(%ld) - allocation failed!\n", count);
1064 	}
1065 
1066 	fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
1067 
1068 	return fPath != NULL;
1069 }
1070 
1071 // #pragma mark -
1072 
1073 #ifdef ICON_O_MATIC
1074 
1075 // _NotifyPointAdded
1076 void
1077 VectorPath::_NotifyPointAdded(int32 index) const
1078 {
1079 	BList listeners(fListeners);
1080 	int32 count = listeners.CountItems();
1081 	for (int32 i = 0; i < count; i++) {
1082 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1083 		listener->PointAdded(index);
1084 	}
1085 }
1086 
1087 // _NotifyPointChanged
1088 void
1089 VectorPath::_NotifyPointChanged(int32 index) const
1090 {
1091 	BList listeners(fListeners);
1092 	int32 count = listeners.CountItems();
1093 	for (int32 i = 0; i < count; i++) {
1094 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1095 		listener->PointChanged(index);
1096 	}
1097 }
1098 
1099 // _NotifyPointRemoved
1100 void
1101 VectorPath::_NotifyPointRemoved(int32 index) const
1102 {
1103 	BList listeners(fListeners);
1104 	int32 count = listeners.CountItems();
1105 	for (int32 i = 0; i < count; i++) {
1106 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1107 		listener->PointRemoved(index);
1108 	}
1109 }
1110 
1111 // _NotifyPathChanged
1112 void
1113 VectorPath::_NotifyPathChanged() const
1114 {
1115 	BList listeners(fListeners);
1116 	int32 count = listeners.CountItems();
1117 	for (int32 i = 0; i < count; i++) {
1118 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1119 		listener->PathChanged();
1120 	}
1121 }
1122 
1123 // _NotifyClosedChanged
1124 void
1125 VectorPath::_NotifyClosedChanged() const
1126 {
1127 	BList listeners(fListeners);
1128 	int32 count = listeners.CountItems();
1129 	for (int32 i = 0; i < count; i++) {
1130 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1131 		listener->PathClosedChanged();
1132 	}
1133 }
1134 
1135 // _NotifyPathReversed
1136 void
1137 VectorPath::_NotifyPathReversed() const
1138 {
1139 	BList listeners(fListeners);
1140 	int32 count = listeners.CountItems();
1141 	for (int32 i = 0; i < count; i++) {
1142 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1143 		listener->PathReversed();
1144 	}
1145 }
1146 
1147 #endif // ICON_O_MATIC
1148