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