2 * Simple Framebuffer Gfx/GUI lib
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 /* *****************************************************************************
20 Anti-Grain Geometry (AGG) - Version 2.5
21 A high quality rendering engine for C++
22 Copyright (C) 2002-2006 Maxim Shemanarev
23 Contact: mcseem@antigrain.com
27 AGG is free software; you can redistribute it and/or
28 modify it under the terms of the GNU General Public License
29 as published by the Free Software Foundation; either version 2
30 of the License, or (at your option) any later version.
32 AGG is distributed in the hope that it will be useful,
33 but WITHOUT ANY WARRANTY; without even the implied warranty of
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 GNU General Public License for more details.
37 You should have received a copy of the GNU General Public License
38 along with AGG; if not, write to the Free Software
39 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
41 ***************************************************************************** */
42 module iv
.egra
.gfx
.aggmini
.stroker
;
43 package(iv
.egra
.gfx
.aggmini
):
44 nothrow @trusted @nogc:
46 private import iv
.egra
.gfx
.aggmini
: DisableCopyingMixin
;
47 private import iv
.egra
.gfx
.aggmini
.supmath
;
48 private import iv
.egra
.gfx
.aggmini
.enums
;
51 // ////////////////////////////////////////////////////////////////////////// //
52 public alias AGGPoint
= AGGPointImpl
!float;
54 public struct AGGPointImpl(T
) if (__traits(isIntegral
, T
) ||
__traits(isFloating
, T
)) {
55 public nothrow @trusted @nogc:
57 alias Me
= AGGPointImpl
!T
;
60 static if (__traits(isFloating
, T
)) {
68 // ////////////////////////////////////////////////////////////////////////// //
71 * alias value_type = VertexType; // shoud support `VertexType(x, y)`
72 * vc.remove_all(); -- start new polygon
73 * vc.add(VT); -- add point to the current polygon
75 struct StrokeCalc(VertexConsumer
) {
78 float mWidthAbs
= 0.5f;
79 float mWidthEps
= 0.5f/1024.0f;
81 float mMiterLimit
= 4.0f;
82 float mInnerMiterLimit
= 1.01f;
83 float mApproxScale
= 1.0f;
84 LineCap mLineCap
= LineCap
.Butt
;
85 LineJoin mLineJoin
= LineJoin
.Miter
;
86 InnerJoin mInnerJoin
= InnerJoin
.Miter
;
88 public nothrow @trusted @nogc:
89 alias CoordType
= VertexConsumer
.ValueType
;
91 mixin(DisableCopyingMixin
);
93 @property void lineCap (in LineCap lc
) { pragma(inline
, true); mLineCap
= lc
; }
94 @property void lineJoin (in LineJoin lj
) { pragma(inline
, true); mLineJoin
= lj
; }
95 @property void innerJoin (in InnerJoin ij
) { pragma(inline
, true); mInnerJoin
= ij
; }
97 @property LineCap
lineCap () const pure { pragma(inline
, true); return mLineCap
; }
98 @property LineJoin
lineJoin () const pure { pragma(inline
, true); return mLineJoin
; }
99 @property InnerJoin
innerJoin () const pure { pragma(inline
, true); return mInnerJoin
; }
101 @property float width () const pure { pragma(inline
, true); return mWidth
*2.0f; }
102 @property void width (in float w
) {
111 mWidthEps
= mWidth
/1024.0f;
114 @property void miterLimit (in float ml
) { pragma(inline
, true); mMiterLimit
= ml
; }
115 @property void miterLimitTheta (in float t
) { pragma(inline
, true); import core
.stdc
.math
: sinf
; mMiterLimit
= 1.0f/sinf(t
*0.5f); }
116 @property void innerMiterLimit (in float ml
) { pragma(inline
, true); mInnerMiterLimit
= ml
; }
117 @property void approximationScale (in float as
) { pragma(inline
, true); mApproxScale
= as
; }
119 @property float miterLimit () const pure { pragma(inline
, true); return mMiterLimit
; }
120 @property float innerMiterLimit () const pure { pragma(inline
, true); return mInnerMiterLimit
; }
121 @property float approximationScale () const pure { pragma(inline
, true); return mApproxScale
; }
123 void setFromParams (in ref AGGParams params
) {
124 lineCap
= params
.lineCap
;
125 lineJoin
= params
.lineJoin
;
126 innerJoin
= params
.innerJoin
;
127 width
= params
.width
;
128 miterLimit
= params
.miterLimit
;
129 innerMiterLimit
= params
.innerMiterLimit
;
130 approximationScale
= params
.approximationScale
;
133 void calcCap() (ref VertexConsumer vc
, in auto ref VertexDist v0
, in auto ref VertexDist v1
, in float len
) {
134 import core
.stdc
.math
: acosf
, atan2f
, cosf
, sinf
;
138 float dx1
= (v1
.y
-v0
.y
)/len
;
139 float dy1
= (v1
.x
-v0
.x
)/len
;
146 if (mLineCap
!= LineCap
.Round
) {
147 if (mLineCap
== LineCap
.Square
) {
148 dx2
= dy1
*mWidthSign
;
149 dy2
= dx1
*mWidthSign
;
151 addVertex(vc
, v0
.x
-dx1
-dx2
, v0
.y
+dy1
-dy2
);
152 addVertex(vc
, v0
.x
+dx1
-dx2
, v0
.y
-dy1
-dy2
);
154 float da = acosf(mWidthAbs
/(mWidthAbs
+0.125f/mApproxScale
))*2.0f;
155 immutable int n
= cast(int)(FLT_PI
/da);
157 addVertex(vc
, v0
.x
-dx1
, v0
.y
+dy1
);
158 if (mWidthSign
> 0.0f) {
159 float a1
= atan2f(dy1
, -dx1
);
161 foreach (immutable int i
; 0..n
) {
162 addVertex(vc
, v0
.x
+cosf(a1
)*mWidth
, v0
.y
+sinf(a1
)*mWidth
);
166 float a1
= atan2f(-dy1
, dx1
);
168 foreach (immutable int i
; 0..n
) {
169 addVertex(vc
, v0
.x
+cosf(a1
)*mWidth
, v0
.y
+sinf(a1
)*mWidth
);
173 addVertex(vc
, v0
.x
+dx1
, v0
.y
-dy1
);
177 void calcJoin() (ref VertexConsumer vc
, in auto ref VertexDist v0
, in auto ref VertexDist v1
, in auto ref VertexDist v2
, in float len1
, in float len2
) {
178 import core
.stdc
.math
: sqrtf
;
180 immutable float dx1
= mWidth
*(v1
.y
-v0
.y
)/len1
;
181 immutable float dy1
= mWidth
*(v1
.x
-v0
.x
)/len1
;
182 immutable float dx2
= mWidth
*(v2
.y
-v1
.y
)/len2
;
183 immutable float dy2
= mWidth
*(v2
.x
-v1
.x
)/len2
;
187 float cp
= cross3(v0
.x
, v0
.y
, v1
.x
, v1
.y
, v2
.x
, v2
.y
);
188 if (cp
!= 0.0f && (cp
> 0.0f) == (mWidth
> 0.0f)) {
190 float limit
= (len1
< len2 ? len1
: len2
)/mWidthAbs
;
191 if (limit
< mInnerMiterLimit
) limit
= mInnerMiterLimit
;
193 switch (mInnerJoin
) {
194 default: // inner_bevel
195 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
196 addVertex(vc
, v1
.x
+dx2
, v1
.y
-dy2
);
198 case InnerJoin
.Miter
:
199 calcMiter(vc
, v0
, v1
, v2
, dx1
, dy1
, dx2
, dy2
, LineJoin
.MiterRevert
, limit
, 0.0f);
202 case InnerJoin
.Round
:
203 cp
= (dx1
-dx2
)*(dx1
-dx2
)+(dy1
-dy2
)*(dy1
-dy2
);
204 if (cp
< len1
*len1
&& cp
< len2
*len2
) {
205 calcMiter(vc
, v0
, v1
, v2
, dx1
, dy1
, dx2
, dy2
, LineJoin
.MiterRevert
, limit
, 0.0f);
207 if (mInnerJoin
== InnerJoin
.Jag
) {
208 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
209 addVertex(vc
, v1
.x
, v1
.y
);
210 addVertex(vc
, v1
.x
+dx2
, v1
.y
-dy2
);
212 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
213 addVertex(vc
, v1
.x
, v1
.y
);
214 calcArc(vc
, v1
.x
, v1
.y
, dx2
, -dy2
, dx1
, -dy1
);
215 addVertex(vc
, v1
.x
, v1
.y
);
216 addVertex(vc
, v1
.x
+dx2
, v1
.y
-dy2
);
224 // Calculate the distance between v1 and
225 // the central point of the bevel line segment
226 float dx
= (dx1
+dx2
)*0.5f;
227 float dy
= (dy1
+dy2
)*0.5f;
228 immutable float dbevel
= sqrtf(dx
*dx
+dy
*dy
);
230 if (mLineJoin
== LineJoin
.Round || mLineJoin
== LineJoin
.Bevel
) {
231 // This is an optimization that reduces the number of points
232 // in cases of almost collinear segments. If there's no
233 // visible difference between bevel and miter joins we'd rather
234 // use miter join because it adds only one point instead of two.
236 // Here we calculate the middle point between the bevel points
237 // and then, the distance between v1 and this middle point.
238 // At outer joins this distance always less than stroke width,
239 // because it's actually the height of an isosceles triangle of
240 // v1 and its two bevel points. If the difference between this
241 // width and this value is small (no visible bevel) we can
242 // add just one point.
244 // The constant in the expression makes the result approximately
245 // the same as in round joins and caps. You can safely comment
246 // out this entire "if".
247 if (mApproxScale
*(mWidthAbs
-dbevel
) < mWidthEps
) {
248 if (intersection(v0
.x
+dx1
, v0
.y
-dy1
, v1
.x
+dx1
, v1
.y
-dy1
, v1
.x
+dx2
, v1
.y
-dy2
, v2
.x
+dx2
, v2
.y
-dy2
, &dx
, &dy
)) {
249 addVertex(vc
, dx
, dy
);
251 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
259 case LineJoin
.MiterRevert
:
260 case LineJoin
.MiterRound
:
261 calcMiter(vc
, v0
, v1
, v2
, dx1
, dy1
, dx2
, dy2
, mLineJoin
, mMiterLimit
, dbevel
);
264 calcArc(vc
, v1
.x
, v1
.y
, dx1
, -dy1
, dx2
, -dy2
);
266 default: // Bevel join
267 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
268 addVertex(vc
, v1
.x
+dx2
, v1
.y
-dy2
);
275 void addVertex (ref VertexConsumer vc
, in float x
, in float y
) {
276 pragma(inline
, true);
277 vc
.add(CoordType(x
, y
));
280 void calcArc (ref VertexConsumer vc
, in float x
, in float y
, in float dx1
, in float dy1
, in float dx2
, in float dy2
) {
281 import core
.stdc
.math
: acosf
, atan2f
, cosf
, sinf
;
283 float a1
= atan2f(dy1
*mWidthSign
, dx1
*mWidthSign
);
284 float a2
= atan2f(dy2
*mWidthSign
, dx2
*mWidthSign
);
286 immutable float da = acosf(mWidthAbs
/(mWidthAbs
+0.125f/mApproxScale
))*2.0f;
288 addVertex(vc
, x
+dx1
, y
+dy1
);
289 if (mWidthSign
> 0.0f) {
290 if (a1
> a2
) a2
+= 2.0f*FLT_PI
;
291 immutable int n
= cast(int)((a2
-a1
)/da);
292 immutable float daa = (a2
-a1
)/(n
+1);
294 foreach (immutable int i
; 0..n
) {
295 addVertex(vc
, x
+cosf(a1
)*mWidth
, y
+sinf(a1
)*mWidth
);
299 if (a1
< a2
) a2
-= 2.0f*FLT_PI
;
300 immutable int n
= cast(int)((a1
-a2
)/da);
301 immutable float daa = (a1
-a2
)/(n
+1);
303 foreach (immutable int i
; 0..n
) {
304 addVertex(vc
, x
+cosf(a1
)*mWidth
, y
+sinf(a1
)*mWidth
);
308 addVertex(vc
, x
+dx2
, y
+dy2
);
311 void calcMiter (ref VertexConsumer vc
, in ref VertexDist v0
, in ref VertexDist v1
, in ref VertexDist v2
,
312 in float dx1
, in float dy1
, in float dx2
, in float dy2
, in LineJoin lj
,
313 in float mlimit
, in float dbevel
)
318 immutable float lim
= mWidthAbs
*mlimit
;
319 bool miterLimitExceeded
= true; // assume the worst
320 bool intersectionFailed
= true; // assume the worst
322 if (intersection(v0
.x
+dx1
, v0
.y
-dy1
, v1
.x
+dx1
, v1
.y
-dy1
, v1
.x
+dx2
, v1
.y
-dy2
, v2
.x
+dx2
, v2
.y
-dy2
, &xi
, &yi
)) {
323 // Calculation of the intersection succeeded
324 di = distance(v1
.x
, v1
.y
, xi
, yi
);
326 // Inside the miter limit
327 addVertex(vc
, xi
, yi
);
328 miterLimitExceeded
= false;
330 intersectionFailed
= false;
332 // Calculation of the intersection failed, most probably
333 // the three points lie one straight line.
334 // First check if v0 and v2 lie on the opposite sides of vector:
335 // (v1.x, v1.y) -> (v1.x+dx1, v1.y-dy1), that is, the perpendicular
336 // to the line determined by vertices v0 and v1.
337 // This condition determines whether the next line segments continues
338 // the previous one or goes back.
339 immutable float x2
= v1
.x
+dx1
;
340 immutable float y2
= v1
.y
-dy1
;
341 if ((cross3(v0
.x
, v0
.y
, v1
.x
, v1
.y
, x2
, y2
) < 0.0f) == (cross3(v1
.x
, v1
.y
, v2
.x
, v2
.y
, x2
, y2
) < 0.0f)) {
342 // This case means that the next segment continues
343 // the previous one (straight line)
344 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
345 miterLimitExceeded
= false;
349 if (miterLimitExceeded
) {
350 // Miter limit exceeded
351 //------------------------
353 case LineJoin
.MiterRevert
:
354 // For the compatibility with SVG, PDF, etc,
355 // we use a simple bevel join instead of
357 addVertex(vc
, v1
.x
+dx1
, v1
.y
-dy1
);
358 addVertex(vc
, v1
.x
+dx2
, v1
.y
-dy2
);
360 case LineJoin
.MiterRound
:
361 calcArc(vc
, v1
.x
, v1
.y
, dx1
, -dy1
, dx2
, -dy2
);
364 // If no miter-revert, calculate new dx1, dy1, dx2, dy2
365 if (intersectionFailed
) {
366 immutable float mlimitM
= mlimit
*mWidthSign
;
367 addVertex(vc
, v1
.x
+dx1
+dy1
*mlimitM
, v1
.y
-dy1
+dx1
*mlimitM
);
368 addVertex(vc
, v1
.x
+dx2
-dy2
*mlimitM
, v1
.y
-dy2
-dx2
*mlimitM
);
370 immutable float x1
= v1
.x
+dx1
;
371 immutable float y1
= v1
.y
-dy1
;
372 immutable float x2
= v1
.x
+dx2
;
373 immutable float y2
= v1
.y
-dy2
;
374 di = (lim
-dbevel
)/(di-dbevel
);
375 addVertex(vc
, x1
+(xi
-x1
)*di, y1
+(yi
-y1
)*di);
376 addVertex(vc
, x2
+(xi
-x2
)*di, y2
+(yi
-y2
)*di);
385 // ////////////////////////////////////////////////////////////////////////// //
387 public nothrow @trusted @nogc:
388 alias VertexStorage
= VertexSequence
!VertexDist
;
389 alias CoordStorage
= SimpleVector
!AGGPoint
;
407 StrokeCalc
!CoordStorage mStroker
;
408 VertexStorage mSrcVertices
;
409 CoordStorage mOutVertices
;
412 State mStatus
= State
.Initial
;
418 mixin(DisableCopyingMixin
);
420 @property void lineCap (LineCap lc
) { pragma(inline
, true); mStroker
.lineCap(lc
); }
421 @property void lineJoin (LineJoin lj
) { pragma(inline
, true); mStroker
.lineJoin(lj
); }
422 @property void innerJoin (InnerJoin ij
) { pragma(inline
, true); mStroker
.innerJoin(ij
); }
424 @property LineCap
lineCap () const pure { pragma(inline
, true); return mStroker
.lineCap(); }
425 @property LineJoin
lineJoin () const pure { pragma(inline
, true); return mStroker
.lineJoin(); }
426 @property InnerJoin
innerJoin () const pure { pragma(inline
, true); return mStroker
.innerJoin(); }
428 @property void width (float w
) { pragma(inline
, true); mStroker
.width(w
); }
429 @property void miterLimit (float ml
) { pragma(inline
, true); mStroker
.miterLimit(ml
); }
430 @property void miterLimitTheta (float t
) { pragma(inline
, true); mStroker
.miterLimitTheta(t
); }
431 @property void innerMiterLimit (float ml
) { pragma(inline
, true); mStroker
.innerMiterLimit(ml
); }
432 @property void approximationScale (float as
) { pragma(inline
, true); mStroker
.approximationScale(as
); }
434 @property float width () const pure { pragma(inline
, true); return mStroker
.width(); }
435 @property float miterLimit () const pure { pragma(inline
, true); return mStroker
.miterLimit(); }
436 @property float innerMiterLimit () const pure { pragma(inline
, true); return mStroker
.innerMiterLimit(); }
437 @property float approximationScale () const pure { pragma(inline
, true); return mStroker
.approximationScale(); }
439 @property void shorten (float s
) { pragma(inline
, true); mShorten
= s
; }
440 @property float shorten () const pure { pragma(inline
, true); return mShorten
; }
442 void setFromParams (in ref AGGParams params
) {
443 mStroker
.setFromParams(params
);
444 shorten
= params
.shorten
;
447 // Generator interface
449 mSrcVertices
.removeAll();
451 mOutVertices
.removeAll();
454 mStatus
= mPrevStatus
= State
.Initial
;
457 void addVertex (float x
, float y
, uint cmd
) {
458 mStatus
= State
.Initial
;
459 if (isMoveTo(cmd
)) mSrcVertices
.modifyLast(VertexDist(x
, y
));
460 else if (isVertex(cmd
)) mSrcVertices
.add(VertexDist(x
, y
));
461 else if (isEndPoly(cmd
)) mClosed
= getCloseFlag(cmd
);
464 // Vertex Source Interface
466 if (mStatus
== State
.Initial
) {
467 mSrcVertices
.close(mClosed
!= 0);
468 shortenPath(mSrcVertices
, mShorten
, mClosed
);
469 if (mSrcVertices
.length
< 3) mClosed
= 0;
471 mStatus
= State
.Ready
;
476 uint vertex (float* x
, float* y
) {
477 uint cmd
= PathCommand
.LineTo
;
478 while (!isStop(cmd
)) {
479 final switch (mStatus
) {
485 if (mSrcVertices
.length
< 2+cast(uint)(mClosed
!= 0)) {
486 cmd
= PathCommand
.Stop
;
489 mStatus
= (mClosed ? State
.Outline1
: State
.Cap1
);
490 cmd
= PathCommand
.MoveTo
;
496 mStroker
.calcCap(mOutVertices
, mSrcVertices
[0], mSrcVertices
[1], mSrcVertices
[0].dist
);
498 mPrevStatus
= State
.Outline1
;
499 mStatus
= State
.OutVertices
;
504 mStroker
.calcCap(mOutVertices
, mSrcVertices
[mSrcVertices
.length
-1], mSrcVertices
[mSrcVertices
.length
-2], mSrcVertices
[mSrcVertices
.length
-2].dist
);
505 mPrevStatus
= State
.Outline2
;
506 mStatus
= State
.OutVertices
;
512 if (mSrcVertex
>= mSrcVertices
.length
) {
513 mPrevStatus
= State
.CloseFirst
;
514 mStatus
= State
.End_poly1
;
518 if (mSrcVertex
>= mSrcVertices
.length
-1) {
519 mStatus
= State
.Cap2
;
523 mStroker
.calcJoin(mOutVertices
, mSrcVertices
.prev(mSrcVertex
), mSrcVertices
.curr(mSrcVertex
), mSrcVertices
.next(mSrcVertex
), mSrcVertices
.prev(mSrcVertex
).dist
, mSrcVertices
.curr(mSrcVertex
).dist
);
525 mPrevStatus
= mStatus
;
526 mStatus
= State
.OutVertices
;
530 case State
.CloseFirst
:
531 mStatus
= State
.Outline2
;
532 cmd
= PathCommand
.MoveTo
;
536 if (mSrcVertex
<= uint(mClosed
== 0)) {
537 mStatus
= State
.End_poly2
;
538 mPrevStatus
= State
.Stop
;
543 mStroker
.calcJoin(mOutVertices
, mSrcVertices
.next(mSrcVertex
), mSrcVertices
.curr(mSrcVertex
), mSrcVertices
.prev(mSrcVertex
), mSrcVertices
.curr(mSrcVertex
).dist
, mSrcVertices
.prev(mSrcVertex
).dist
);
545 mPrevStatus
= mStatus
;
546 mStatus
= State
.OutVertices
;
550 case State
.OutVertices
:
551 if (mOutVertex
>= mOutVertices
.length
) {
552 mStatus
= mPrevStatus
;
554 const(AGGPoint
)* c
= &mOutVertices
[mOutVertex
++];
561 case State
.End_poly1
:
562 mStatus
= mPrevStatus
;
563 return PathCommand
.EndPoly|PathFlag
.Close|PathFlag
.CCW
;
565 case State
.End_poly2
:
566 mStatus
= mPrevStatus
;
567 return PathCommand
.EndPoly|PathFlag
.Close|PathFlag
.CW
;
570 cmd
= PathCommand
.Stop
;
579 // ////////////////////////////////////////////////////////////////////////// //
581 public nothrow @trusted @nogc:
582 alias VertexStorage
= VertexSequence
!VertexDist
;
583 alias CoordStorage
= SimpleVector
!AGGPoint
;
595 StrokeCalc
!CoordStorage mStroker
;
597 VertexStorage mSrcVertices
;
598 CoordStorage mOutVertices
;
603 uint mOrientation
= 0;
604 // default orientation for polys w/o explitict one
605 uint mDefaultOrientation
= 0;
606 // force this orientation for all polys
607 uint mForcedOrientation
= 0;
608 // flip all orientations?
609 bool mFlipOrientation
= false;
612 mixin(DisableCopyingMixin
);
614 @property void lineCap (LineCap lc
) { pragma(inline
, true); mStroker
.lineCap(lc
); }
615 @property void lineJoin (LineJoin lj
) { pragma(inline
, true); mStroker
.lineJoin(lj
); }
616 @property void innerJoin (InnerJoin ij
) { pragma(inline
, true); mStroker
.innerJoin(ij
); }
618 @property LineCap
lineCap () const pure { pragma(inline
, true); return mStroker
.lineCap(); }
619 @property LineJoin
lineJoin () const pure { pragma(inline
, true); return mStroker
.lineJoin(); }
620 @property InnerJoin
innerJoin () const pure { pragma(inline
, true); return mStroker
.innerJoin(); }
622 @property void width (float w
) { pragma(inline
, true); mStroker
.width(mWidth
= w
); }
623 @property void miterLimit (float ml
) { pragma(inline
, true); mStroker
.miterLimit(ml
); }
624 @property void miterLimitTheta (float t
) { pragma(inline
, true); mStroker
.miterLimitTheta(t
); }
625 @property void innerMiterLimit (float ml
) { pragma(inline
, true); mStroker
.innerMiterLimit(ml
); }
626 @property void approximationScale (float as
) { pragma(inline
, true); mStroker
.approximationScale(as
); }
628 @property float width () const pure { pragma(inline
, true); return mWidth
; }
629 @property float miterLimit () const pure { pragma(inline
, true); return mStroker
.miterLimit(); }
630 @property float innerMiterLimit () const pure { pragma(inline
, true); return mStroker
.innerMiterLimit(); }
631 @property float approximationScale () const pure { pragma(inline
, true); return mStroker
.approximationScale(); }
633 // 0: autodetect; -1: ccw; 1: cw
634 @property void defaultOrientation (in int v
) { pragma(inline
, true); mDefaultOrientation
= (v
< 0 ? PathFlag
.CCW
: v
> 0 ? PathFlag
.CW
: 0); }
635 @property int defaultOrientation () const pure { pragma(inline
, true); return (mDefaultOrientation
== PathFlag
.CW ?
1 : mDefaultOrientation
== PathFlag
.CCW ?
-1 : 0); }
637 // 0: autodetect; -1: ccw; 1: cw
638 @property void forcedOrientation (in int v
) { pragma(inline
, true); mForcedOrientation
= (v
< 0 ? PathFlag
.CCW
: v
> 0 ? PathFlag
.CW
: 0); }
639 @property int forcedOrientation () const pure { pragma(inline
, true); return (mForcedOrientation
== PathFlag
.CW ?
1 : mDefaultOrientation
== PathFlag
.CCW ?
-1 : 0); }
641 @property void flipOrientation (in bool v
) { pragma(inline
, true); mFlipOrientation
= v
; }
642 @property bool flipOrientation () const pure { pragma(inline
, true); return mFlipOrientation
; }
644 void setFromParams (in ref AGGParams params
) {
645 mStroker
.setFromParams(params
);
646 width
= params
.width
;
649 // Generator interface
651 mSrcVertices
.removeAll();
653 mOutVertices
.removeAll();
657 mStatus
= State
.Initial
;
660 void addVertex (float x
, float y
, uint cmd
) {
661 mStatus
= State
.Initial
;
663 mSrcVertices
.modifyLast(VertexDist(x
, y
));
664 } else if (isVertex(cmd
)) {
665 mSrcVertices
.add(VertexDist(x
, y
));
666 } else if (isEndPoly(cmd
)) {
667 mClosed
= getCloseFlag(cmd
);
668 if (!mOrientation
) mOrientation
= getOrientation(cmd
);
672 // Vertex Source Interface
674 if (mStatus
== State
.Initial
) {
675 mSrcVertices
.close(true);
676 if (mForcedOrientation
) {
677 // forced orientation for all polys?
678 mOrientation
= mForcedOrientation
;
679 } else if (!mOrientation
) {
680 // poly orientation is unknown
681 if (mDefaultOrientation
) {
682 // we have default one, use it
683 mOrientation
= mDefaultOrientation
;
685 // no default, autodetect
686 mOrientation
= (calcPolygonArea(mSrcVertices
) > 0 ? PathFlag
.CCW
: PathFlag
.CW
);
689 if (mFlipOrientation
) mOrientation
= (mOrientation
== PathFlag
.CCW ? PathFlag
.CW
: PathFlag
.CCW
);
690 //if (isOriented(mOrientation)) {
691 mStroker
.width(isCCW(mOrientation
) ? mWidth
: -mWidth
);
693 mStatus
= State
.Ready
;
697 uint vertex (float* x
, float* y
) {
698 uint cmd
= PathCommand
.LineTo
;
699 while (!isStop(cmd
)) {
700 final switch (mStatus
) {
706 if (mSrcVertices
.length
< 2+cast(uint)(mClosed
!= 0)) {
707 cmd
= PathCommand
.Stop
;
710 mStatus
= State
.Outline
;
711 cmd
= PathCommand
.MoveTo
;
717 if (mSrcVertex
>= mSrcVertices
.length
) {
718 mStatus
= State
.EndPoly
;
721 mStroker
.calcJoin(mOutVertices
, mSrcVertices
.prev(mSrcVertex
), mSrcVertices
.curr(mSrcVertex
), mSrcVertices
.next(mSrcVertex
), mSrcVertices
.prev(mSrcVertex
).dist
, mSrcVertices
.curr(mSrcVertex
).dist
);
723 mStatus
= State
.OutVertices
;
727 case State
.OutVertices
:
728 if (mOutVertex
>= mOutVertices
.length
) {
729 mStatus
= State
.Outline
;
731 const(AGGPoint
)* c
= &mOutVertices
[mOutVertex
++];
739 //k8: always emit "endpoly"
740 //if (!mClosed) return PathCommand.Stop;
741 mStatus
= State
.Stop
;
742 return PathCommand
.EndPoly|PathFlag
.Close|PathFlag
.CCW
;
745 return PathCommand
.Stop
;
753 // ////////////////////////////////////////////////////////////////////////// //
755 private nothrow @trusted @nogc:
756 alias VertexStorage
= VertexSequence
!VertexDist
;
767 float[MaxDashes
] mDashes
= 0;
768 float mTotalDashLen
= 0;
770 float mDashStart
= 0;
772 float mCurrDashStart
= 0;
775 const(VertexDist
)* m_v1
= null;
776 const(VertexDist
)* m_v2
= null;
778 VertexStorage mSrcVertices
;
780 State mStatus
= State
.Initial
;
784 mixin(DisableCopyingMixin
);
786 @property void shorten (in float s
) { pragma(inline
, true); mShorten
= s
; }
787 @property float shorten () const pure { pragma(inline
, true); return mShorten
; }
789 @property bool hasDashes () const pure { pragma(inline
, true); return (mNumDashes
> 0); }
791 void removeAllDashes () {
798 void addDash (in float dashLen
, in float gapLen
) {
799 if (mNumDashes
< MaxDashes
) {
800 mTotalDashLen
+= dashLen
+gapLen
;
801 mDashes
[mNumDashes
++] = dashLen
;
802 mDashes
[mNumDashes
++] = gapLen
;
806 void dashStart (in float ds) {
807 import core
.stdc
.math
: fabsf
;
809 calcDashStart(fabsf(ds));
812 // Vertex Generator Interface
814 mStatus
= State
.Initial
;
815 mSrcVertices
.removeAll();
818 calcDashStart(mDashStart
);
822 void addVertex (float x
, float y
, uint cmd
) {
823 mStatus
= State
.Initial
;
824 if (isMoveTo(cmd
)) mSrcVertices
.modifyLast(VertexDist(x
, y
));
825 else if (isVertex(cmd
)) mSrcVertices
.add(VertexDist(x
, y
));
826 else if (isEndPoly(cmd
)) mClosed
= getCloseFlag(cmd
);
829 // Vertex Source Interface
831 if (mStatus
== State
.Initial
) {
832 mSrcVertices
.close(mClosed
!= 0);
833 shortenPath(mSrcVertices
, mShorten
, mClosed
);
835 mStatus
= State
.Ready
;
839 uint vertex (float* x
, float* y
) {
840 uint cmd
= PathCommand
.MoveTo
;
842 while (!isStop(cmd
)) {
843 final switch (mStatus
) {
849 if (mNumDashes
< 2 || mSrcVertices
.length
< 2) {
850 cmd
= PathCommand
.Stop
;
853 mStatus
= State
.Polyline
;
855 m_v1
= &mSrcVertices
[0];
856 m_v2
= &mSrcVertices
[1];
857 mCurrRest
= m_v1
.dist
;
860 if (mDashStart
>= 0) calcDashStart(mDashStart
);
861 return PathCommand
.MoveTo
;
864 immutable float dashRest
= mDashes
[mCurrDash
]-mCurrDashStart
;
865 cmd
= (mCurrDash
&1 ? PathCommand
.MoveTo
: PathCommand
.LineTo
);
866 if (mCurrRest
> dashRest
) {
867 mCurrRest
-= dashRest
;
869 if (mCurrDash
>= mNumDashes
) mCurrDash
= 0;
871 *x
= m_v2
.x
-(m_v2
.x
-m_v1
.x
)*mCurrRest
/m_v1
.dist
;
872 *y
= m_v2
.y
-(m_v2
.y
-m_v1
.y
)*mCurrRest
/m_v1
.dist
;
874 mCurrDashStart
+= mCurrRest
;
879 mCurrRest
= m_v1
.dist
;
881 if (mSrcVertex
> mSrcVertices
.length
) {
882 mStatus
= State
.Stop
;
884 m_v2
= &mSrcVertices
[mSrcVertex
>= mSrcVertices
.length ?
0 :mSrcVertex
];
887 if (mSrcVertex
>= mSrcVertices
.length
) {
888 mStatus
= State
.Stop
;
890 m_v2
= &mSrcVertices
[mSrcVertex
];
897 cmd
= PathCommand
.Stop
;
901 return PathCommand
.Stop
;
905 void calcDashStart (float ds) {
909 if (ds > mDashes
[mCurrDash
]) {
910 ds -= mDashes
[mCurrDash
];
913 if (mCurrDash
>= mNumDashes
) mCurrDash
= 0;