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 ((void *)p, (n) * sizeof(type)))
44#define obj_free				free
45
46#define ALLOC_CHUNKS 20
47
48
49bool
50get_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
80PathListener::PathListener()
81{
82}
83
84
85PathListener::~PathListener()
86{
87}
88#endif
89
90
91// #pragma mark -
92
93
94VectorPath::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
110VectorPath::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
127VectorPath::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((void*)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
171VectorPath::~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
194PropertyObject*
195VectorPath::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
216bool
217VectorPath::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
235status_t
236VectorPath::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
300VectorPath&
301VectorPath::operator=(const VectorPath& from)
302{
303	_SetPointCount(from.fPointCount);
304	fClosed = from.fClosed;
305	if (fPath) {
306		memcpy((void*)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
320bool
321VectorPath::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
348void
349VectorPath::MakeEmpty()
350{
351	_SetPointCount(0);
352}
353
354
355// #pragma mark -
356
357
358bool
359VectorPath::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
373bool
374VectorPath::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
389bool
390VectorPath::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
415bool
416VectorPath::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
439bool
440VectorPath::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
459bool
460VectorPath::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
480bool
481VectorPath::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
513bool
514VectorPath::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
550bool
551VectorPath::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
565bool
566VectorPath::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
578bool
579VectorPath::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
591bool
592VectorPath::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
604bool
605VectorPath::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
622int32
623VectorPath::CountPoints() const
624{
625	return fPointCount;
626}
627
628
629// #pragma mark -
630
631
632#ifdef ICON_O_MATIC
633
634static float
635distance_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
673bool
674VectorPath::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
704bool
705VectorPath::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
735bool
736VectorPath::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
770void
771VectorPath::SetClosed(bool closed)
772{
773	if (fClosed != closed) {
774		fClosed = closed;
775		_NotifyClosedChanged();
776		Notify();
777	}
778}
779
780
781BRect
782VectorPath::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
791BRect
792VectorPath::_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
817BRect
818VectorPath::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
845void
846VectorPath::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
890void
891VectorPath::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
943void
944VectorPath::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
959void
960VectorPath::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
975void
976VectorPath::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
987bool
988VectorPath::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
999bool
1000VectorPath::AddListener(PathListener* listener)
1001{
1002	if (listener && !fListeners.HasItem((void*)listener))
1003		return fListeners.AddItem((void*)listener);
1004	return false;
1005}
1006
1007
1008bool
1009VectorPath::RemoveListener(PathListener* listener)
1010{
1011	return fListeners.RemoveItem((void*)listener);
1012}
1013
1014
1015int32
1016VectorPath::CountListeners() const
1017{
1018	return fListeners.CountItems();
1019}
1020
1021
1022PathListener*
1023VectorPath::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
1034void
1035VectorPath::_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
1047void
1048VectorPath::_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
1064bool
1065VectorPath::_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((void*)(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
1103void
1104VectorPath::_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
1115void
1116VectorPath::_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
1127void
1128VectorPath::_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
1139void
1140VectorPath::_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
1151void
1152VectorPath::_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
1163void
1164VectorPath::_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