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