sq3: show SQLite error messages on stderr by default
[iv.d.git] / egra / gfx / aggmini / stroker.d
blob0e79e344e064b01cfbf9b7c3dd9b81ff00f61568
1 /*
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
24 mcseemagg@yahoo.com
25 http://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,
40 MA 02110-1301, USA.
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:
56 alias ValueT = T;
57 alias Me = AGGPointImpl!T;
59 public:
60 static if (__traits(isFloating, T)) {
61 T x, y;
62 } else {
63 T x=T.min, y=T.min;
68 // ////////////////////////////////////////////////////////////////////////// //
70 * VertexConsumer API:
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) {
76 private:
77 float mWidth = 0.5f;
78 float mWidthAbs = 0.5f;
79 float mWidthEps = 0.5f/1024.0f;
80 int mWidthSign = 1;
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) {
103 mWidth = w*0.5f;
104 if (mWidth < 0.0f) {
105 mWidthAbs = -mWidth;
106 mWidthSign = -1;
107 } else {
108 mWidthAbs = mWidth;
109 mWidthSign = 1;
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;
136 vc.removeAll();
138 float dx1 = (v1.y-v0.y)/len;
139 float dy1 = (v1.x-v0.x)/len;
140 float dx2 = 0.0f;
141 float dy2 = 0.0f;
143 dx1 *= mWidth;
144 dy1 *= mWidth;
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);
153 } else {
154 float da = acosf(mWidthAbs/(mWidthAbs+0.125f/mApproxScale))*2.0f;
155 immutable int n = cast(int)(FLT_PI/da);
156 da = FLT_PI/(n+1);
157 addVertex(vc, v0.x-dx1, v0.y+dy1);
158 if (mWidthSign > 0.0f) {
159 float a1 = atan2f(dy1, -dx1);
160 a1 += da;
161 foreach (immutable int i; 0..n) {
162 addVertex(vc, v0.x+cosf(a1)*mWidth, v0.y+sinf(a1)*mWidth);
163 a1 += da;
165 } else {
166 float a1 = atan2f(-dy1, dx1);
167 a1 -= da;
168 foreach (immutable int i; 0..n) {
169 addVertex(vc, v0.x+cosf(a1)*mWidth, v0.y+sinf(a1)*mWidth);
170 a1 -= da;
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;
185 vc.removeAll();
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)) {
189 // Inner join
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);
197 break;
198 case InnerJoin.Miter:
199 calcMiter(vc, v0, v1, v2, dx1, dy1, dx2, dy2, LineJoin.MiterRevert, limit, 0.0f);
200 break;
201 case InnerJoin.Jag:
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);
206 } else {
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);
211 } else {
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);
219 break;
221 } else {
222 // Outer join
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);
250 } else {
251 addVertex(vc, v1.x+dx1, v1.y-dy1);
253 return;
257 switch (mLineJoin) {
258 case LineJoin.Miter:
259 case LineJoin.MiterRevert:
260 case LineJoin.MiterRound:
261 calcMiter(vc, v0, v1, v2, dx1, dy1, dx2, dy2, mLineJoin, mMiterLimit, dbevel);
262 break;
263 case LineJoin.Round:
264 calcArc(vc, v1.x, v1.y, dx1, -dy1, dx2, -dy2);
265 break;
266 default: // Bevel join
267 addVertex(vc, v1.x+dx1, v1.y-dy1);
268 addVertex(vc, v1.x+dx2, v1.y-dy2);
269 break;
274 private:
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);
293 a1 += daa;
294 foreach (immutable int i; 0..n) {
295 addVertex(vc, x+cosf(a1)*mWidth, y+sinf(a1)*mWidth);
296 a1 += daa;
298 } else {
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);
302 a1 -= daa;
303 foreach (immutable int i; 0..n) {
304 addVertex(vc, x+cosf(a1)*mWidth, y+sinf(a1)*mWidth);
305 a1 -= daa;
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)
315 float xi = v1.x;
316 float yi = v1.y;
317 float di = 1.0f;
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);
325 if (di <= lim) {
326 // Inside the miter limit
327 addVertex(vc, xi, yi);
328 miterLimitExceeded = false;
330 intersectionFailed = false;
331 } else {
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 //------------------------
352 switch (lj) {
353 case LineJoin.MiterRevert:
354 // For the compatibility with SVG, PDF, etc,
355 // we use a simple bevel join instead of
356 // "smart" bevel
357 addVertex(vc, v1.x+dx1, v1.y-dy1);
358 addVertex(vc, v1.x+dx2, v1.y-dy2);
359 break;
360 case LineJoin.MiterRound:
361 calcArc(vc, v1.x, v1.y, dx1, -dy1, dx2, -dy2);
362 break;
363 default:
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);
369 } else {
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);
378 break;
385 // ////////////////////////////////////////////////////////////////////////// //
386 struct Stroker {
387 public nothrow @trusted @nogc:
388 alias VertexStorage = VertexSequence!VertexDist;
389 alias CoordStorage = SimpleVector!AGGPoint;
391 private:
392 enum State {
393 Initial,
394 Ready,
395 Cap1,
396 Cap2,
397 Outline1,
398 CloseFirst,
399 Outline2,
400 OutVertices,
401 End_poly1,
402 End_poly2,
403 Stop,
406 private:
407 StrokeCalc!CoordStorage mStroker;
408 VertexStorage mSrcVertices;
409 CoordStorage mOutVertices;
410 float mShorten = 0;
411 uint mClosed = 0;
412 State mStatus = State.Initial;
413 State mPrevStatus;
414 uint mSrcVertex = 0;
415 uint mOutVertex = 0;
417 public:
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
448 void removeAll () {
449 mSrcVertices.removeAll();
450 mSrcVertex = 0;
451 mOutVertices.removeAll();
452 mOutVertex = 0;
453 mClosed = 0;
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
465 void rewind () {
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;
472 mSrcVertex = 0;
473 mOutVertex = 0;
476 uint vertex (float* x, float* y) {
477 uint cmd = PathCommand.LineTo;
478 while (!isStop(cmd)) {
479 final switch (mStatus) {
480 case State.Initial:
481 rewind();
482 goto case;
484 case State.Ready:
485 if (mSrcVertices.length < 2+cast(uint)(mClosed != 0)) {
486 cmd = PathCommand.Stop;
487 break;
489 mStatus = (mClosed ? State.Outline1 : State.Cap1);
490 cmd = PathCommand.MoveTo;
491 mSrcVertex = 0;
492 mOutVertex = 0;
493 break;
495 case State.Cap1:
496 mStroker.calcCap(mOutVertices, mSrcVertices[0], mSrcVertices[1], mSrcVertices[0].dist);
497 mSrcVertex = 1;
498 mPrevStatus = State.Outline1;
499 mStatus = State.OutVertices;
500 mOutVertex = 0;
501 break;
503 case State.Cap2:
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;
507 mOutVertex = 0;
508 break;
510 case State.Outline1:
511 if (mClosed) {
512 if (mSrcVertex >= mSrcVertices.length) {
513 mPrevStatus = State.CloseFirst;
514 mStatus = State.End_poly1;
515 break;
517 } else {
518 if (mSrcVertex >= mSrcVertices.length-1) {
519 mStatus = State.Cap2;
520 break;
523 mStroker.calcJoin(mOutVertices, mSrcVertices.prev(mSrcVertex), mSrcVertices.curr(mSrcVertex), mSrcVertices.next(mSrcVertex), mSrcVertices.prev(mSrcVertex).dist, mSrcVertices.curr(mSrcVertex).dist);
524 ++mSrcVertex;
525 mPrevStatus = mStatus;
526 mStatus = State.OutVertices;
527 mOutVertex = 0;
528 break;
530 case State.CloseFirst:
531 mStatus = State.Outline2;
532 cmd = PathCommand.MoveTo;
533 goto case;
535 case State.Outline2:
536 if (mSrcVertex <= uint(mClosed == 0)) {
537 mStatus = State.End_poly2;
538 mPrevStatus = State.Stop;
539 break;
542 --mSrcVertex;
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;
547 mOutVertex = 0;
548 break;
550 case State.OutVertices:
551 if (mOutVertex >= mOutVertices.length) {
552 mStatus = mPrevStatus;
553 } else {
554 const(AGGPoint)* c = &mOutVertices[mOutVertex++];
555 *x = c.x;
556 *y = c.y;
557 return cmd;
559 break;
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;
569 case State.Stop:
570 cmd = PathCommand.Stop;
571 break;
574 return cmd;
579 // ////////////////////////////////////////////////////////////////////////// //
580 struct Contourer {
581 public nothrow @trusted @nogc:
582 alias VertexStorage = VertexSequence!VertexDist;
583 alias CoordStorage = SimpleVector!AGGPoint;
585 enum State {
586 Initial,
587 Ready,
588 Outline,
589 OutVertices,
590 EndPoly,
591 Stop,
594 private:
595 StrokeCalc!CoordStorage mStroker;
596 float mWidth = 1;
597 VertexStorage mSrcVertices;
598 CoordStorage mOutVertices;
599 State mStatus;
600 uint mSrcVertex = 0;
601 uint mOutVertex;
602 uint mClosed = 0;
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;
611 public:
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
650 void removeAll () {
651 mSrcVertices.removeAll();
652 mSrcVertex = 0;
653 mOutVertices.removeAll();
654 mOutVertex = 0;
655 mClosed = 0;
656 mOrientation = 0;
657 mStatus = State.Initial;
660 void addVertex (float x, float y, uint cmd) {
661 mStatus = State.Initial;
662 if (isMoveTo(cmd)) {
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
673 void rewind () {
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;
684 } else {
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;
694 mSrcVertex = 0;
697 uint vertex (float* x, float* y) {
698 uint cmd = PathCommand.LineTo;
699 while (!isStop(cmd)) {
700 final switch (mStatus) {
701 case State.Initial:
702 rewind();
703 goto case;
705 case State.Ready:
706 if (mSrcVertices.length < 2+cast(uint)(mClosed != 0)) {
707 cmd = PathCommand.Stop;
708 break;
710 mStatus = State.Outline;
711 cmd = PathCommand.MoveTo;
712 mSrcVertex = 0;
713 mOutVertex = 0;
714 goto case;
716 case State.Outline:
717 if (mSrcVertex >= mSrcVertices.length) {
718 mStatus = State.EndPoly;
719 break;
721 mStroker.calcJoin(mOutVertices, mSrcVertices.prev(mSrcVertex), mSrcVertices.curr(mSrcVertex), mSrcVertices.next(mSrcVertex), mSrcVertices.prev(mSrcVertex).dist, mSrcVertices.curr(mSrcVertex).dist);
722 ++mSrcVertex;
723 mStatus = State.OutVertices;
724 mOutVertex = 0;
725 goto case;
727 case State.OutVertices:
728 if (mOutVertex >= mOutVertices.length) {
729 mStatus = State.Outline;
730 } else {
731 const(AGGPoint)* c = &mOutVertices[mOutVertex++];
732 *x = c.x;
733 *y = c.y;
734 return cmd;
736 break;
738 case State.EndPoly:
739 //k8: always emit "endpoly"
740 //if (!mClosed) return PathCommand.Stop;
741 mStatus = State.Stop;
742 return PathCommand.EndPoly|PathFlag.Close|PathFlag.CCW;
744 case State.Stop:
745 return PathCommand.Stop;
748 return cmd;
753 // ////////////////////////////////////////////////////////////////////////// //
754 struct Dasher {
755 private nothrow @trusted @nogc:
756 alias VertexStorage = VertexSequence!VertexDist;
757 enum MaxDashes = 32;
759 enum State {
760 Initial,
761 Ready,
762 Polyline,
763 Stop,
766 private:
767 float[MaxDashes] mDashes = 0;
768 float mTotalDashLen = 0;
769 uint mNumDashes = 0;
770 float mDashStart = 0;
771 float mShorten = 0;
772 float mCurrDashStart = 0;
773 uint mCurrDash = 0;
774 float mCurrRest = 0;
775 const(VertexDist)* m_v1 = null;
776 const(VertexDist)* m_v2 = null;
778 VertexStorage mSrcVertices;
779 uint mClosed = 0;
780 State mStatus = State.Initial;
781 uint mSrcVertex = 0;
783 public:
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 () {
792 mTotalDashLen = 0;
793 mNumDashes = 0;
794 mCurrDashStart = 0;
795 mCurrDash = 0;
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;
808 mDashStart = ds;
809 calcDashStart(fabsf(ds));
812 // Vertex Generator Interface
813 void removeAll () {
814 mStatus = State.Initial;
815 mSrcVertices.removeAll();
816 mSrcVertex = 0;
817 mClosed = 0;
818 calcDashStart(mDashStart);
819 mCurrRest = 0;
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
830 void rewind () {
831 if (mStatus == State.Initial) {
832 mSrcVertices.close(mClosed != 0);
833 shortenPath(mSrcVertices, mShorten, mClosed);
835 mStatus = State.Ready;
836 mSrcVertex = 0;
839 uint vertex (float* x, float* y) {
840 uint cmd = PathCommand.MoveTo;
842 while (!isStop(cmd)) {
843 final switch (mStatus) {
844 case State.Initial:
845 rewind();
846 goto case;
848 case State.Ready:
849 if (mNumDashes < 2 || mSrcVertices.length < 2) {
850 cmd = PathCommand.Stop;
851 break;
853 mStatus = State.Polyline;
854 mSrcVertex = 1;
855 m_v1 = &mSrcVertices[0];
856 m_v2 = &mSrcVertices[1];
857 mCurrRest = m_v1.dist;
858 *x = m_v1.x;
859 *y = m_v1.y;
860 if (mDashStart >= 0) calcDashStart(mDashStart);
861 return PathCommand.MoveTo;
863 case State.Polyline:
864 immutable float dashRest = mDashes[mCurrDash]-mCurrDashStart;
865 cmd = (mCurrDash&1 ? PathCommand.MoveTo : PathCommand.LineTo);
866 if (mCurrRest > dashRest) {
867 mCurrRest -= dashRest;
868 ++mCurrDash;
869 if (mCurrDash >= mNumDashes) mCurrDash = 0;
870 mCurrDashStart = 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;
873 } else {
874 mCurrDashStart += mCurrRest;
875 *x = m_v2.x;
876 *y = m_v2.y;
877 ++mSrcVertex;
878 m_v1 = m_v2;
879 mCurrRest = m_v1.dist;
880 if (mClosed) {
881 if (mSrcVertex > mSrcVertices.length) {
882 mStatus = State.Stop;
883 } else {
884 m_v2 = &mSrcVertices[mSrcVertex >= mSrcVertices.length ? 0 :mSrcVertex];
886 } else {
887 if (mSrcVertex >= mSrcVertices.length) {
888 mStatus = State.Stop;
889 } else {
890 m_v2 = &mSrcVertices[mSrcVertex];
894 return cmd;
896 case State.Stop:
897 cmd = PathCommand.Stop;
898 break;
901 return PathCommand.Stop;
904 private:
905 void calcDashStart (float ds) {
906 mCurrDash = 0;
907 mCurrDashStart = 0;
908 while (ds > 0) {
909 if (ds > mDashes[mCurrDash]) {
910 ds -= mDashes[mCurrDash];
911 ++mCurrDash;
912 mCurrDashStart = 0;
913 if (mCurrDash >= mNumDashes) mCurrDash = 0;
914 } else {
915 mCurrDashStart = ds;
916 ds = 0;