xref: /haiku/src/libs/icon/shape/VectorPath.cpp (revision f2b4344867e97c3f4e742a1b4a15e6879644601a)
1 /*
2  * Copyright 2006-2009, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 
9 #include "VectorPath.h"
10 
11 #include <malloc.h>
12 #include <stdio.h>
13 #include <string.h>
14 
15 #include <agg_basics.h>
16 #include <agg_bounding_rect.h>
17 #include <agg_conv_curve.h>
18 #include <agg_curves.h>
19 #include <agg_math.h>
20 
21 #ifdef ICON_O_MATIC
22 #	include <debugger.h>
23 #	include <typeinfo>
24 #endif // ICON_O_MATIC
25 
26 #include <Message.h>
27 #include <TypeConstants.h>
28 
29 #ifdef ICON_O_MATIC
30 #	include "support.h"
31 
32 #	include "CommonPropertyIDs.h"
33 #	include "IconProperty.h"
34 #	include "Icons.h"
35 #	include "Property.h"
36 #	include "PropertyObject.h"
37 #endif // ICON_O_MATIC
38 
39 #include "Transformable.h"
40 
41 
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 void
321 VectorPath::MakeEmpty()
322 {
323 	_SetPointCount(0);
324 }
325 
326 
327 // #pragma mark -
328 
329 
330 bool
331 VectorPath::AddPoint(BPoint point)
332 {
333 	int32 index = fPointCount;
334 
335 	if (_SetPointCount(fPointCount + 1)) {
336 		_SetPoint(index, point);
337 		_NotifyPointAdded(index);
338 		return true;
339 	}
340 
341 	return false;
342 }
343 
344 
345 bool
346 VectorPath::AddPoint(const BPoint& point, const BPoint& pointIn,
347 	const BPoint& pointOut, bool connected)
348 {
349 	int32 index = fPointCount;
350 
351 	if (_SetPointCount(fPointCount + 1)) {
352 		_SetPoint(index, point, pointIn, pointOut, connected);
353 		_NotifyPointAdded(index);
354 		return true;
355 	}
356 
357 	return false;
358 }
359 
360 
361 bool
362 VectorPath::AddPoint(BPoint point, int32 index)
363 {
364 	if (index < 0)
365 		index = 0;
366 	if (index > fPointCount)
367 		index = fPointCount;
368 
369 	if (_SetPointCount(fPointCount + 1)) {
370 		// handle insert
371 		if (index < fPointCount - 1) {
372 			for (int32 i = fPointCount; i > index; i--) {
373 				fPath[i].point = fPath[i - 1].point;
374 				fPath[i].point_in = fPath[i - 1].point_in;
375 				fPath[i].point_out = fPath[i - 1].point_out;
376 				fPath[i].connected = fPath[i - 1].connected;
377 			}
378 		}
379 		_SetPoint(index, point);
380 		_NotifyPointAdded(index);
381 		return true;
382 	}
383 	return false;
384 }
385 
386 
387 bool
388 VectorPath::RemovePoint(int32 index)
389 {
390 	if (index >= 0 && index < fPointCount) {
391 		if (index < fPointCount - 1) {
392 			// move points
393 			for (int32 i = index; i < fPointCount - 1; i++) {
394 				fPath[i].point = fPath[i + 1].point;
395 				fPath[i].point_in = fPath[i + 1].point_in;
396 				fPath[i].point_out = fPath[i + 1].point_out;
397 				fPath[i].connected = fPath[i + 1].connected;
398 			}
399 		}
400 		fPointCount -= 1;
401 
402 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
403 
404 		_NotifyPointRemoved(index);
405 		return true;
406 	}
407 	return false;
408 }
409 
410 
411 bool
412 VectorPath::SetPoint(int32 index, BPoint point)
413 {
414 	if (index == fPointCount)
415 		index = 0;
416 	if (index >= 0 && index < fPointCount) {
417 		BPoint offset = point - fPath[index].point;
418 		fPath[index].point = point;
419 		fPath[index].point_in += offset;
420 		fPath[index].point_out += offset;
421 
422 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
423 
424 		_NotifyPointChanged(index);
425 		return true;
426 	}
427 	return false;
428 }
429 
430 
431 bool
432 VectorPath::SetPoint(int32 index, BPoint point, BPoint pointIn, BPoint pointOut,
433 	bool connected)
434 {
435 	if (index == fPointCount)
436 		index = 0;
437 	if (index >= 0 && index < fPointCount) {
438 		fPath[index].point = point;
439 		fPath[index].point_in = pointIn;
440 		fPath[index].point_out = pointOut;
441 		fPath[index].connected = connected;
442 
443 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
444 
445 		_NotifyPointChanged(index);
446 		return true;
447 	}
448 	return false;
449 }
450 
451 
452 bool
453 VectorPath::SetPointIn(int32 i, BPoint point)
454 {
455 	if (i == fPointCount)
456 		i = 0;
457 	if (i >= 0 && i < fPointCount) {
458 		// first, set the "in" point
459 		fPath[i].point_in = point;
460 		// now see what to do about the "out" point
461 		if (fPath[i].connected) {
462 			// keep all three points in one line
463 			BPoint v = fPath[i].point - fPath[i].point_in;
464 			float distIn = sqrtf(v.x * v.x + v.y * v.y);
465 			if (distIn > 0.0) {
466 				float distOut = agg::calc_distance(
467 					fPath[i].point.x, fPath[i].point.y,
468 					fPath[i].point_out.x, fPath[i].point_out.y);
469 				float scale = (distIn + distOut) / distIn;
470 				v.x *= scale;
471 				v.y *= scale;
472 				fPath[i].point_out = fPath[i].point_in + v;
473 			}
474 		}
475 
476 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
477 
478 		_NotifyPointChanged(i);
479 		return true;
480 	}
481 	return false;
482 }
483 
484 
485 bool
486 VectorPath::SetPointOut(int32 i, BPoint point, bool mirrorDist)
487 {
488 	if (i == fPointCount)
489 		i = 0;
490 	if (i >= 0 && i < fPointCount) {
491 		// first, set the "out" point
492 		fPath[i].point_out = point;
493 		// now see what to do about the "out" point
494 		if (mirrorDist) {
495 			// mirror "in" point around main control point
496 			BPoint v = fPath[i].point - fPath[i].point_out;
497 			fPath[i].point_in = fPath[i].point + v;
498 		} else if (fPath[i].connected) {
499 			// keep all three points in one line
500 			BPoint v = fPath[i].point - fPath[i].point_out;
501 			float distOut = sqrtf(v.x * v.x + v.y * v.y);
502 			if (distOut > 0.0) {
503 				float distIn = agg::calc_distance(
504 					fPath[i].point.x, fPath[i].point.y,
505 					fPath[i].point_in.x, fPath[i].point_in.y);
506 				float scale = (distIn + distOut) / distOut;
507 				v.x *= scale;
508 				v.y *= scale;
509 				fPath[i].point_in = fPath[i].point_out + v;
510 			}
511 		}
512 
513 		fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
514 
515 		_NotifyPointChanged(i);
516 		return true;
517 	}
518 	return false;
519 }
520 
521 
522 bool
523 VectorPath::SetInOutConnected(int32 index, bool connected)
524 {
525 	if (index >= 0 && index < fPointCount) {
526 		fPath[index].connected = connected;
527 		_NotifyPointChanged(index);
528 		return true;
529 	}
530 	return false;
531 }
532 
533 
534 // #pragma mark -
535 
536 
537 bool
538 VectorPath::GetPointAt(int32 index, BPoint& point) const
539 {
540 	if (index == fPointCount)
541 		index = 0;
542 	if (index >= 0 && index < fPointCount) {
543 		point = fPath[index].point;
544 		return true;
545 	}
546 	return false;
547 }
548 
549 
550 bool
551 VectorPath::GetPointInAt(int32 index, BPoint& point) const
552 {
553 	if (index == fPointCount)
554 		index = 0;
555 	if (index >= 0 && index < fPointCount) {
556 		point = fPath[index].point_in;
557 		return true;
558 	}
559 	return false;
560 }
561 
562 
563 bool
564 VectorPath::GetPointOutAt(int32 index, BPoint& point) const
565 {
566 	if (index == fPointCount)
567 		index = 0;
568 	if (index >= 0 && index < fPointCount) {
569 		point = fPath[index].point_out;
570 		return true;
571 	}
572 	return false;
573 }
574 
575 
576 bool
577 VectorPath::GetPointsAt(int32 index, BPoint& point, BPoint& pointIn,
578 	BPoint& pointOut, bool* connected) const
579 {
580 	if (index >= 0 && index < fPointCount) {
581 		point = fPath[index].point;
582 		pointIn = fPath[index].point_in;
583 		pointOut = fPath[index].point_out;
584 
585 		if (connected)
586 			*connected = fPath[index].connected;
587 
588 		return true;
589 	}
590 	return false;
591 }
592 
593 
594 int32
595 VectorPath::CountPoints() const
596 {
597 	return fPointCount;
598 }
599 
600 
601 // #pragma mark -
602 
603 
604 #ifdef ICON_O_MATIC
605 
606 static float
607 distance_to_curve(const BPoint& p, const BPoint& a, const BPoint& aOut,
608 	const BPoint& bIn, const BPoint& b)
609 {
610 	agg::curve4_inc curve(a.x, a.y, aOut.x, aOut.y, bIn.x, bIn.y, b.x, b.y);
611 
612 	float segDist = FLT_MAX;
613 	double x1, y1, x2, y2;
614 	unsigned cmd = curve.vertex(&x1, &y1);
615 	while (!agg::is_stop(cmd)) {
616 		cmd = curve.vertex(&x2, &y2);
617 		// first figure out if point is between segment start and end points
618 		double a = agg::calc_distance(p.x, p.y, x2, y2);
619 		double b = agg::calc_distance(p.x, p.y, x1, y1);
620 
621 		float currentDist = min_c(a, b);
622 
623 		if (a > 0.0 && b > 0.0) {
624 			double c = agg::calc_distance(x1, y1, x2, y2);
625 
626 			double alpha = acos((b * b + c * c - a * a) / (2 * b * c));
627 			double beta = acos((a * a + c * c - b * b) / (2 * a * c));
628 
629 			if (alpha <= M_PI_2 && beta <= M_PI_2) {
630 				currentDist = fabs(agg::calc_line_point_distance(x1, y1, x2, y2,
631 					p.x, p.y));
632 			}
633 		}
634 
635 		if (currentDist < segDist)
636 			segDist = currentDist;
637 
638 		x1 = x2;
639 		y1 = y2;
640 	}
641 	return segDist;
642 }
643 
644 
645 bool
646 VectorPath::GetDistance(BPoint p, float* distance, int32* index) const
647 {
648 	if (fPointCount > 1) {
649 		// generate a curve for each segment of the path
650 		// then	iterate over the segments of the curve measuring the distance
651 		*distance = FLT_MAX;
652 
653 		for (int32 i = 0; i < fPointCount - 1; i++) {
654 			float segDist = distance_to_curve(p, fPath[i].point,
655 				fPath[i].point_out, fPath[i + 1].point_in, fPath[i + 1].point);
656 			if (segDist < *distance) {
657 				*distance = segDist;
658 				*index = i + 1;
659 			}
660 		}
661 		if (fClosed) {
662 			float segDist = distance_to_curve(p, fPath[fPointCount - 1].point,
663 				fPath[fPointCount - 1].point_out, fPath[0].point_in,
664 				fPath[0].point);
665 			if (segDist < *distance) {
666 				*distance = segDist;
667 				*index = fPointCount;
668 			}
669 		}
670 		return true;
671 	}
672 	return false;
673 }
674 
675 
676 bool
677 VectorPath::FindBezierScale(int32 index, BPoint point, double* scale) const
678 {
679 	if (index >= 0 && index < fPointCount && scale) {
680 		int maxStep = 1000;
681 
682 		double t = 0.0;
683 		double dt = 1.0 / maxStep;
684 
685 		*scale = 0.0;
686 		double min = FLT_MAX;
687 
688 		BPoint curvePoint;
689 		for (int step = 1; step < maxStep; step++) {
690 			t += dt;
691 
692 			GetPoint(index, t, curvePoint);
693 			double d = agg::calc_distance(curvePoint.x, curvePoint.y,
694 				point.x, point.y);
695 
696 			if (d < min) {
697 				min = d;
698 				*scale = t;
699 			}
700 		}
701 		return true;
702 	}
703 	return false;
704 }
705 
706 
707 bool
708 VectorPath::GetPoint(int32 index, double t, BPoint& point) const
709 {
710 	if (index >= 0 && index < fPointCount) {
711 		double t1 = (1 - t) * (1 - t) * (1 - t);
712 		double t2 = (1 - t) * (1 - t) * t * 3;
713 		double t3 = (1 - t) * t * t * 3;
714 		double t4 = t * t * t;
715 
716 		if (index < fPointCount - 1) {
717 			point.x = fPath[index].point.x * t1 + fPath[index].point_out.x * t2
718 				+ fPath[index + 1].point_in.x * t3
719 				+ fPath[index + 1].point.x * t4;
720 
721 			point.y = fPath[index].point.y * t1 + fPath[index].point_out.y * t2
722 				+ fPath[index + 1].point_in.y * t3
723 				+ fPath[index + 1].point.y * t4;
724 		} else if (fClosed) {
725 			point.x = fPath[fPointCount - 1].point.x * t1
726 				+ fPath[fPointCount - 1].point_out.x * t2
727 				+ fPath[0].point_in.x * t3 + fPath[0].point.x * t4;
728 
729 			point.y = fPath[fPointCount - 1].point.y * t1
730 				+ fPath[fPointCount - 1].point_out.y * t2
731 				+ fPath[0].point_in.y * t3 + fPath[0].point.y * t4;
732 		}
733 
734 		return true;
735 	}
736 	return false;
737 }
738 
739 #endif // ICON_O_MATIC
740 
741 
742 void
743 VectorPath::SetClosed(bool closed)
744 {
745 	if (fClosed != closed) {
746 		fClosed = closed;
747 		_NotifyClosedChanged();
748 		Notify();
749 	}
750 }
751 
752 
753 BRect
754 VectorPath::Bounds() const
755 {
756 	// the bounds of the actual curves, not the control points!
757 	if (!fCachedBounds.IsValid())
758 		 fCachedBounds = _Bounds();
759 	return fCachedBounds;
760 }
761 
762 
763 BRect
764 VectorPath::_Bounds() const
765 {
766 	agg::path_storage path;
767 
768 	BRect b;
769 	if (get_path_storage(path, fPath, fPointCount, fClosed)) {
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,
781 			fPath[0].point.y);
782 	} else {
783 		b.Set(0.0, 0.0, -1.0, -1.0);
784 	}
785 	return b;
786 }
787 
788 
789 BRect
790 VectorPath::ControlPointBounds() const
791 {
792 	if (fPointCount > 0) {
793 		BRect r(fPath[0].point, fPath[0].point);
794 		for (int32 i = 0; i < fPointCount; i++) {
795 			// include point
796 			r.left = min_c(r.left, fPath[i].point.x);
797 			r.top = min_c(r.top, fPath[i].point.y);
798 			r.right = max_c(r.right, fPath[i].point.x);
799 			r.bottom = max_c(r.bottom, fPath[i].point.y);
800 			// include "in" point
801 			r.left = min_c(r.left, fPath[i].point_in.x);
802 			r.top = min_c(r.top, fPath[i].point_in.y);
803 			r.right = max_c(r.right, fPath[i].point_in.x);
804 			r.bottom = max_c(r.bottom, fPath[i].point_in.y);
805 			// include "out" point
806 			r.left = min_c(r.left, fPath[i].point_out.x);
807 			r.top = min_c(r.top, fPath[i].point_out.y);
808 			r.right = max_c(r.right, fPath[i].point_out.x);
809 			r.bottom = max_c(r.bottom, fPath[i].point_out.y);
810 		}
811 		return r;
812 	}
813 	return BRect(0.0, 0.0, -1.0, -1.0);
814 }
815 
816 
817 void
818 VectorPath::Iterate(Iterator* iterator, float smoothScale) const
819 {
820 	if (fPointCount > 1) {
821 		// generate a curve for each segment of the path
822 		// then	iterate over the segments of the curve
823 		agg::curve4_inc curve;
824 		curve.approximation_scale(smoothScale);
825 
826 		for (int32 i = 0; i < fPointCount - 1; i++) {
827 			iterator->MoveTo(fPath[i].point);
828 			curve.init(fPath[i].point.x, fPath[i].point.y,
829 				fPath[i].point_out.x, fPath[i].point_out.y,
830 				fPath[i + 1].point_in.x, fPath[i + 1].point_in.y,
831 				fPath[i + 1].point.x, fPath[i + 1].point.y);
832 
833 			double x, y;
834 			unsigned cmd = curve.vertex(&x, &y);
835 			while (!agg::is_stop(cmd)) {
836 				BPoint p(x, y);
837 				iterator->LineTo(p);
838 				cmd = curve.vertex(&x, &y);
839 			}
840 		}
841 		if (fClosed) {
842 			iterator->MoveTo(fPath[fPointCount - 1].point);
843 			curve.init(fPath[fPointCount - 1].point.x,
844 				fPath[fPointCount - 1].point.y,
845 				fPath[fPointCount - 1].point_out.x,
846 				fPath[fPointCount - 1].point_out.y,
847 				fPath[0].point_in.x, fPath[0].point_in.y,
848 				fPath[0].point.x, fPath[0].point.y);
849 
850 			double x, y;
851 			unsigned cmd = curve.vertex(&x, &y);
852 			while (!agg::is_stop(cmd)) {
853 				BPoint p(x, y);
854 				iterator->LineTo(p);
855 				cmd = curve.vertex(&x, &y);
856 			}
857 		}
858 	}
859 }
860 
861 
862 void
863 VectorPath::CleanUp()
864 {
865 	if (fPointCount == 0)
866 		return;
867 
868 	bool notify = false;
869 
870 	// remove last point if it is coincident with the first
871 	if (fClosed && fPointCount >= 1) {
872 		if (fPath[0].point == fPath[fPointCount - 1].point) {
873 			fPath[0].point_in = fPath[fPointCount - 1].point_in;
874 			_SetPointCount(fPointCount - 1);
875 			notify = true;
876 		}
877 	}
878 
879 	for (int32 i = 0; i < fPointCount; i++) {
880 		// check for unnecessary, duplicate points
881 		if (i > 0) {
882 			if (fPath[i - 1].point == fPath[i].point
883 				&& fPath[i - 1].point == fPath[i - 1].point_out
884 				&& fPath[i].point == fPath[i].point_in) {
885 				// the previous point can be removed
886 				BPoint in = fPath[i - 1].point_in;
887 				if (RemovePoint(i - 1)) {
888 					i--;
889 					fPath[i].point_in = in;
890 					notify = true;
891 				}
892 			}
893 		}
894 		// re-establish connections of in-out control points if
895 		// they line up with the main control point
896 		if (fPath[i].point_in == fPath[i].point_out
897 			|| fPath[i].point == fPath[i].point_out
898 			|| fPath[i].point == fPath[i].point_in
899 			|| (fabs(agg::calc_line_point_distance(fPath[i].point_in.x,
900 					fPath[i].point_in.y, fPath[i].point.x, fPath[i].point.y,
901 					fPath[i].point_out.x, fPath[i].point_out.y)) < 0.01
902 				&& fabs(agg::calc_line_point_distance(fPath[i].point_out.x,
903 					fPath[i].point_out.y, fPath[i].point.x, fPath[i].point.y,
904 					fPath[i].point_in.x, fPath[i].point_in.y)) < 0.01)) {
905 			fPath[i].connected = true;
906 			notify = true;
907 		}
908 	}
909 
910 	if (notify)
911 		_NotifyPathChanged();
912 }
913 
914 
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, fPath[i].point_out,
922 			fPath[i].point_in, fPath[i].connected);
923 		index++;
924 	}
925 	*this = temp;
926 
927 	_NotifyPathReversed();
928 }
929 
930 
931 void
932 VectorPath::ApplyTransform(const Transformable& transform)
933 {
934 	if (transform.IsIdentity())
935 		return;
936 
937 	for (int32 i = 0; i < fPointCount; i++) {
938 		transform.Transform(&(fPath[i].point));
939 		transform.Transform(&(fPath[i].point_out));
940 		transform.Transform(&(fPath[i].point_in));
941 	}
942 
943 	_NotifyPathChanged();
944 }
945 
946 
947 void
948 VectorPath::PrintToStream() const
949 {
950 	for (int32 i = 0; i < fPointCount; i++) {
951 		printf("point %ld: (%f, %f) -> (%f, %f) -> (%f, %f) (%d)\n", i,
952 			fPath[i].point_in.x, fPath[i].point_in.y,
953 			fPath[i].point.x, fPath[i].point.y,
954 			fPath[i].point_out.x, fPath[i].point_out.y, fPath[i].connected);
955 	}
956 }
957 
958 
959 bool
960 VectorPath::GetAGGPathStorage(agg::path_storage& path) const
961 {
962 	return get_path_storage(path, fPath, fPointCount, fClosed);
963 }
964 
965 
966 // #pragma mark -
967 
968 
969 #ifdef ICON_O_MATIC
970 
971 bool
972 VectorPath::AddListener(PathListener* listener)
973 {
974 	if (listener && !fListeners.HasItem((void*)listener))
975 		return fListeners.AddItem((void*)listener);
976 	return false;
977 }
978 
979 
980 bool
981 VectorPath::RemoveListener(PathListener* listener)
982 {
983 	return fListeners.RemoveItem((void*)listener);
984 }
985 
986 
987 int32
988 VectorPath::CountListeners() const
989 {
990 	return fListeners.CountItems();
991 }
992 
993 
994 PathListener*
995 VectorPath::ListenerAtFast(int32 index) const
996 {
997 	return (PathListener*)fListeners.ItemAtFast(index);
998 }
999 
1000 #endif // ICON_O_MATIC
1001 
1002 
1003 // #pragma mark -
1004 
1005 
1006 void
1007 VectorPath::_SetPoint(int32 index, BPoint point)
1008 {
1009 	fPath[index].point = point;
1010 	fPath[index].point_in = point;
1011 	fPath[index].point_out = point;
1012 
1013 	fPath[index].connected = true;
1014 
1015 	fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
1016 }
1017 
1018 
1019 void
1020 VectorPath::_SetPoint(int32 index, const BPoint& point, const BPoint& pointIn,
1021 	const BPoint& pointOut, bool connected)
1022 {
1023 	fPath[index].point = point;
1024 	fPath[index].point_in = pointIn;
1025 	fPath[index].point_out = pointOut;
1026 
1027 	fPath[index].connected = connected;
1028 
1029 	fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
1030 }
1031 
1032 
1033 // #pragma mark -
1034 
1035 
1036 bool
1037 VectorPath::_SetPointCount(int32 count)
1038 {
1039 	// handle reallocation if we run out of room
1040 	if (count >= fAllocCount) {
1041 		fAllocCount = ((count) / ALLOC_CHUNKS + 1) * ALLOC_CHUNKS;
1042 		if (fPath)
1043 			fPath = obj_renew(fPath, control_point, fAllocCount);
1044 		else
1045 			fPath = obj_new(control_point, fAllocCount);
1046 
1047 		if (fPath != NULL) {
1048 			memset(fPath + fPointCount, 0,
1049 				(fAllocCount - fPointCount) * sizeof(control_point));
1050 		}
1051 	}
1052 
1053 	// update point count
1054 	if (fPath) {
1055 		fPointCount = count;
1056 	} else {
1057 		// reallocation might have failed
1058 		fPointCount = 0;
1059 		fAllocCount = 0;
1060 		fprintf(stderr, "VectorPath::_SetPointCount(%ld) - allocation failed!\n",
1061 			count);
1062 	}
1063 
1064 	fCachedBounds.Set(0.0, 0.0, -1.0, -1.0);
1065 
1066 	return fPath != NULL;
1067 }
1068 
1069 
1070 // #pragma mark -
1071 
1072 
1073 #ifdef ICON_O_MATIC
1074 
1075 void
1076 VectorPath::_NotifyPointAdded(int32 index) const
1077 {
1078 	BList listeners(fListeners);
1079 	int32 count = listeners.CountItems();
1080 	for (int32 i = 0; i < count; i++) {
1081 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1082 		listener->PointAdded(index);
1083 	}
1084 }
1085 
1086 
1087 void
1088 VectorPath::_NotifyPointChanged(int32 index) const
1089 {
1090 	BList listeners(fListeners);
1091 	int32 count = listeners.CountItems();
1092 	for (int32 i = 0; i < count; i++) {
1093 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1094 		listener->PointChanged(index);
1095 	}
1096 }
1097 
1098 
1099 void
1100 VectorPath::_NotifyPointRemoved(int32 index) const
1101 {
1102 	BList listeners(fListeners);
1103 	int32 count = listeners.CountItems();
1104 	for (int32 i = 0; i < count; i++) {
1105 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1106 		listener->PointRemoved(index);
1107 	}
1108 }
1109 
1110 
1111 void
1112 VectorPath::_NotifyPathChanged() const
1113 {
1114 	BList listeners(fListeners);
1115 	int32 count = listeners.CountItems();
1116 	for (int32 i = 0; i < count; i++) {
1117 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1118 		listener->PathChanged();
1119 	}
1120 }
1121 
1122 
1123 void
1124 VectorPath::_NotifyClosedChanged() const
1125 {
1126 	BList listeners(fListeners);
1127 	int32 count = listeners.CountItems();
1128 	for (int32 i = 0; i < count; i++) {
1129 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1130 		listener->PathClosedChanged();
1131 	}
1132 }
1133 
1134 
1135 void
1136 VectorPath::_NotifyPathReversed() const
1137 {
1138 	BList listeners(fListeners);
1139 	int32 count = listeners.CountItems();
1140 	for (int32 i = 0; i < count; i++) {
1141 		PathListener* listener = (PathListener*)listeners.ItemAtFast(i);
1142 		listener->PathReversed();
1143 	}
1144 }
1145 
1146 #endif // ICON_O_MATIC
1147