1from cython.operator cimport dereference as deref
2from numbers import Number
3from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect
4from _cy_rectangle cimport cy_Interval, wrap_Interval
5
6from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform
7
8from _cy_curves cimport is_Curve, get_Curve_p
9
10
11cdef class cy_Iterator:
12    cdef Iterator* thisptr
13    cdef ConstIterator* startptr
14    cdef ConstIterator* endptr
15
16    def __cinit__(self):
17        self.thisptr = new Iterator()
18
19    def __dealloc__(self):
20        del self.thisptr
21
22    def __iter__(self):
23        return self
24
25    def __next__(self):
26        if deref(<BaseIterator[ConstIterator, Path] *>  self.endptr) == deref(<BaseIterator[ConstIterator, Path] *> self.thisptr):
27            raise StopIteration
28        cdef Curve * r = <Curve *> &(<BaseIterator[Iterator, Path] * > self.thisptr).c_item()
29        (<BaseIterator[Iterator, Path] *> self.thisptr).c_next()
30        return wrap_Curve_p ( r )
31
32
33
34
35cdef cy_Iterator wrap_Iterator(Iterator i, ConstIterator starti, ConstIterator endi):
36    cdef Iterator * retp = new Iterator()
37    retp[0] = i
38    cdef cy_Iterator r = cy_Iterator.__new__(cy_Iterator)
39    r.thisptr = retp
40
41    cdef ConstIterator * endp = new ConstIterator()
42    endp[0] = endi
43    r.endptr = endp
44
45    cdef ConstIterator * startp = new ConstIterator()
46    startp[0] = starti
47    r.startptr = startp
48
49    return r
50
51cdef class cy_Path:
52
53    """Path is a ordered sequence of curves.
54
55    You can iterate this class, accessing curves one at time, or access
56    them using indices.
57
58    Two constants, NO_STITCHING and STITCH_DISCONTINUOUS are members of
59    Path namespace, and are used to specify type of stitching, if
60    necessary.
61
62    Path is either open or closed, but in both cases carries closing
63    segment, connecting last and first point.
64
65    Corresponds to Path class in 2geom.
66    """
67
68    NO_STITCHING = c_NO_STITCHING
69    STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS
70
71    def __cinit__(self, cy_Point p=cy_Point()):
72        """Create Path containing only one point."""
73        self.thisptr = new Path(deref( p.thisptr ))
74
75    @classmethod
76    def fromList(cls, l, Stitching stitching=NO_STITCHING, closed=False):
77        """Create path from list of curves.
78
79        Specify stithing and closed flag in additional arguments.
80        """
81        p = cy_Path()
82        for curve in l:
83            p.append_curve(curve, stitching)
84        p.close(closed)
85        return p
86
87    @classmethod
88    def fromPath(cls, cy_Path p, fr=-1, to = 1, bint closed=False):
89        """Create path copying another's path curves.
90
91        Either copy all curves, or ones from
92        fr - index of first curve copied
93        to
94        to -  index of first curve not copied.
95
96        Also takes closed flag.
97        """
98        if fr == -1:
99            return wrap_Path( Path(p.thisptr.begin_const(), p.thisptr.end_default(), closed) )
100        else:
101            return wrap_Path( Path(p._const_iterator_at_index(fr), p._const_iterator_at_index(to), closed) )
102
103    def __dealloc__(self):
104        del self.thisptr
105
106    def __getitem__(self, unsigned int i):
107        """Get curve with index i."""
108        cdef Curve * r = <Curve *> & deref(self.thisptr)[i]
109        return wrap_Curve_p(r)
110
111    def __call__(self, double t):
112        """Evaluate path at time t.
113
114        Note: t can be greater than 1 here, it can go to self.size()
115        """
116        return wrap_Point(deref( self.thisptr ) (t) )
117
118    def __richcmp__(cy_Path self, cy_Path other, int op):
119        if op == 2:
120            return deref( self.thisptr ) == deref( other.thisptr )
121        elif op == 3:
122            return deref( self.thisptr ) != deref( other.thisptr )
123
124    def __mul__(cy_Path self, m):
125        """Transform path with a transform."""
126        cdef Affine at
127        if is_transform(m):
128            at = get_Affine(m)
129            return wrap_Path( deref(self.thisptr) * at )
130
131    #This is not the fastest way, but it's pretty nice from python's perspective
132    #Anyway, I would expect that performance hit is minimal, since i is generally really small
133    cdef ConstIterator _const_iterator_at_index(self, int i):
134        cdef ConstIterator ci = self.thisptr.begin_const()
135        cdef ConstIterator * cip = &ci
136        for ii in range(i):
137            (<BaseIteratorConst *> cip).c_next()
138        return ci
139
140    cdef Iterator _iterator_at_index(self, int i):
141        cdef Iterator ci = self.thisptr.begin()
142        cdef Iterator * cip = &ci
143        for ii in range(i):
144            (<BaseIterator[Iterator, Path] *> cip).c_next()
145        return ci
146
147    def swap(self, cy_Path other):
148        """Swap curves with another path."""
149        self.thisptr.swap(deref( other.thisptr ))
150
151#This is the same as __getitem__
152#~     def at_index(self, unsigned int i):
153#~         return wrap_Curve(self.thisptr.at_index(i))
154
155    def front(self):
156        """Get first curve."""
157        #this is AFAIK the shortest way to do this
158        cdef Curve * r = <Curve *> &self.thisptr.front()
159        return wrap_Curve_p(r)
160
161    def back(self):
162        """Same as back_open."""
163        cdef Curve * r = <Curve *> &self.thisptr.back()
164        return wrap_Curve_p(r)
165
166    def back_open(self):
167        """Get last curve, treating self as open."""
168        cdef Curve * r = <Curve *> &self.thisptr.back_open()
169        return wrap_Curve_p(r)
170
171    def back_closed(self):
172        """Get last curve, treating self as closed."""
173        cdef Curve * r = <Curve *> &self.thisptr.back_closed()
174        return wrap_Curve_p(r)
175
176    def back_default(self):
177        """Get last curve."""
178        cdef Curve * r = <Curve *> &self.thisptr.back_default()
179        return wrap_Curve_p(r)
180
181    def curves(self):
182        """Same as curves_open"""
183        return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(),  self.thisptr.end())
184
185    def curves_open(self):
186        """Return all curves as iterable, treating self as open."""
187        return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_open())
188
189    def curves_closed(self):
190        """Return all curves as iterable, treating self as closed."""
191        return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_closed())
192
193    def curves_default(self):
194        """Return all curves as iterable."""
195        return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_default())
196
197    def __iter__(self):
198        return self.curves_default()
199
200    def size_open(self):
201        """Get number of curves, treating self as open."""
202        return self.thisptr.size_open()
203
204    def size_closed(self):
205        """Get number of curves, treating self as closed."""
206        return self.thisptr.size_closed()
207
208    def size_default(self):
209        """Get number of curves."""
210        return self.thisptr.size_default()
211
212    def size(self):
213        """Same as size_open."""
214        return self.thisptr.size()
215
216#Does the same as size_open, which doesn't correspond with name.
217#~     def max_size(self):
218#~         return self.thisptr.max_size()
219
220    def empty(self):
221        """Test whether path contains no curves."""
222        return self.thisptr.empty()
223
224    def closed(self):
225        """Return state of closed flag."""
226        return self.thisptr.closed()
227
228    def close(self, bint closed):
229        """Set closed flag."""
230        self.thisptr.close(closed)
231
232    def bounds_fast(self):
233        """Return fast bounding rectangle for path.
234
235        It's not guaranteed to give the tighest bound.
236        """
237        return wrap_OptRect(self.thisptr.boundsFast())
238
239    def bounds_exact(self):
240        """Give the tighest bounding rectangle for path."""
241        return wrap_OptRect(self.thisptr.boundsExact())
242
243#~     def toPwSb(self):
244#~         return wrap_Piecewise<Geom::D2<Geom::SBasis> >(self.thisptr.toPwSb())
245
246    def point_at(self, double t):
247        """Same as self(t)."""
248        return wrap_Point(self.thisptr.pointAt(t))
249
250    def value_at(self, double t, Dim2 d):
251        """Same as self(t)[d]."""
252        return self.thisptr.valueAt(t, d)
253
254    def __call__(self, double t):
255        """Evaluate path at time t.
256
257        Equivalent to self[floor(t)](t-floor(t))
258        """
259        return wrap_Point(deref( self.thisptr ) (t) )
260
261    def roots(self, double v, Dim2 d):
262        """Find time values where self(t)[d] == v"""
263        return wrap_vector_double(self.thisptr.roots(v, d))
264
265    def all_nearest_times(self, cy_Point _point, double fr=-1, double to=1):
266        """Return all values of t that |self(t) - point| is minimized."""
267        if fr == -1:
268            return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr )))
269        return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr ), fr, to))
270
271
272    def nearest_time_per_curve(self, cy_Point _point):
273        """Find nearest points, return one time value per each curve."""
274        return wrap_vector_double(self.thisptr.nearestTimePerCurve(deref( _point.thisptr )))
275
276    def nearest_time(self, cy_Point _point, double fr=-1, double to=1):#, cy_double * distance_squared):
277        """Return such t that |self(t) - point| is minimized."""
278        if fr == -1:
279            return self.thisptr.nearestTime(deref( _point.thisptr ), NULL)
280        return self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, NULL)
281
282    def nearest_time_and_dist_sq(self, cy_Point _point, double fr=-1, double to=1):
283        """Return such t that |self(t) - point| is minimized and square of that distance."""
284        cdef double t, dist
285        if fr == -1:
286            t = self.thisptr.nearestTime(deref( _point.thisptr ), &dist )
287        else:
288            t = self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, &dist)
289        return (t, dist)
290
291    def append_portion_to(self, cy_Path p, double f, double t):
292        """Append portion of path to self."""
293        self.thisptr.appendPortionTo(deref( p.thisptr ), f, t)
294
295    def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None):
296        """Return portion of curve between two time values.
297
298        Alternatively use argument interval.
299        """
300        if interval is None:
301            return wrap_Path(self.thisptr.portion(fr, to))
302        else:
303            return wrap_Path(self.thisptr.portion(deref( interval.thisptr )))
304
305    def reversed(self):
306        """Return reversed curve."""
307        return wrap_Path(self.thisptr.reversed())
308
309    def insert(self, int pos, curve, Stitching stitching=NO_STITCHING):
310        """Insert curve into position pos.
311
312        Args:
313            pos: Position of inserted curve.
314            curve: Curve to insert.
315            stitching=NO_STITCHING
316        """
317        cdef Curve * cptr = get_Curve_p(curve)
318        if cptr:
319            self.thisptr.insert( self._iterator_at_index(pos), deref( cptr ), stitching )
320        else:
321            raise TypeError("passed curve is not C++ Curve")
322
323    def insert_slice(self, int pos, cy_Path p, int first, int last, Stitching stitching=NO_STITCHING):
324        """Insert curves to position pos.
325
326        Args:
327            pos: Position of inserted slice.
328            p: Path from which slice is inserted.
329            first: First inserted curve position (in p).
330            last: Fist not inserted curve position (in p).
331            stiching=NO_STITCHING
332        """
333        self.thisptr.insert(self._iterator_at_index(pos), p._const_iterator_at_index(first), p._const_iterator_at_index(last), stitching)
334
335    def clear(self):
336        """Clear all curves."""
337        self.thisptr.clear()
338
339    def erase(self, int pos, Stitching stitching=NO_STITCHING):
340        """Erase curve at position pos.
341
342        Args:
343            pos: Position of erased curve.
344            stitching=NO_STITCHING
345        """
346        self.thisptr.erase(self._iterator_at_index(pos), stitching)
347
348    def erase_slice(self, int start, int end, Stitching stitching=NO_STITCHING):
349        """Erase curves with indices [start, end).
350
351        Args:
352            start, end: Curves with indices start...end-1 are erased
353            stitching=NO_STITCHING
354        """
355        self.thisptr.erase(self._iterator_at_index(start), self._iterator_at_index(end), stitching)
356
357    def erase_last(self):
358        """Erase last curve."""
359        self.thisptr.erase_last()
360
361    def replace(self, int replaced, curve, Stitching stitching=NO_STITCHING):
362        """Replace curve at position replaced with another curve.
363
364        Args:
365            replaced: Position of replaced curve.
366            curve: New curve.
367            stitching=NO_STITCHING
368        """
369        cdef Curve * cptr = get_Curve_p(curve)
370        if cptr:
371            self.thisptr.replace(self._iterator_at_index(replaced), deref( cptr ), stitching)
372        else:
373            raise TypeError("passed curve is not C++ Curve")
374
375    def replace_slice(self, int first_replaced, int last_replaced, curve, Stitching stitching=NO_STITCHING):
376        """Replace slice of curves by new curve.
377
378        Args:
379            first_replaced, last_replace: Curves with indices
380                first_replaced ... last_replaced
381            curve: New curve.
382            stitching=NO_STITCHING
383        """
384        cdef Curve * cptr = get_Curve_p(curve)
385        if cptr:
386            self.thisptr.replace(self._iterator_at_index(first_replaced), self._iterator_at_index(last_replaced), deref( cptr ), stitching)
387        else:
388            raise TypeError("passed curve is not C++ Curve")
389
390#How to implement this nicely?
391#~     def replaceByList(self, int replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching):
392#~         self.thisptr.replace(deref( replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching)
393#~     def replace(self, cy_Iterator first_replaced, cy_Iterator last_replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching):
394#~         self.thisptr.replace(deref( first_replaced.thisptr ), deref( last_replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching)
395
396    def start(self, cy_Point p):
397        """Erase all curves and set first point."""
398        self.thisptr.start(deref( p.thisptr ))
399
400    def initial_point(self):
401        """Get initial point."""
402        return wrap_Point(self.thisptr.initialPoint())
403
404    def final_point(self):
405        """Get final point."""
406        return wrap_Point(self.thisptr.finalPoint())
407
408    def set_initial(self, cy_Point p):
409        """Set initial point."""
410        self.thisptr.setInitial(deref( p.thisptr ))
411
412    def set_final(self, cy_Point p):
413        """Set final point."""
414        self.thisptr.setFinal(deref( p.thisptr ))
415
416    def append_curve(self, curve, Stitching stitching=NO_STITCHING):
417        """Append curve to path.
418
419        Args:
420            curve: Curve to append.
421            stitching=NO_STITCHING
422        """
423        cdef Curve * cptr = get_Curve_p(curve)
424        if cptr:
425            self.thisptr.append( deref( cptr ), stitching)
426        else:
427            raise TypeError("passed curve is not C++ Curve")
428
429    def append_SBasis(self, cy_SBasis x, cy_SBasis y, Stitching stitching=NO_STITCHING):
430        """Append two SBasis functions to path.
431
432        Args:
433            x, y: SBasis functions to append.
434            stitching=NO_STITCHING
435        """
436        cdef D2[SBasis] sb = D2[SBasis]( deref(x.thisptr), deref(y.thisptr) )
437        self.thisptr.append(sb, stitching)
438
439    def append_path(self, cy_Path other, Stitching stitching=NO_STITCHING):
440        """Append another path to path.
441
442        Args:
443            other: Path to append.
444            stitching=NO_STITCHING
445        """
446        self.thisptr.append(deref( other.thisptr ), stitching)
447
448    def stitch_to(self, cy_Point p):
449        """Set last point to p, creating stitching segment to it."""
450        self.thisptr.stitchTo(deref( p.thisptr ))
451
452cdef cy_Path wrap_Path(Path p):
453    cdef Path * retp = new Path(Point())
454    retp[0] = p
455    cdef cy_Path r = cy_Path.__new__(cy_Path)
456    r.thisptr = retp
457    return r
458