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