egra: agg mini code cleanups
[iv.d.git] / egra / gfx / aggmini / stroker.d
blob92342cc31fc871ebee7b524945b05577742eb117
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 // ////////////////////////////////////////////////////////////////////////// //
53 * VertexConsumer API:
54 * alias value_type = VertexType; // shoud support `VertexType(x, y)`
55 * vc.remove_all(); -- start new polygon
56 * vc.add(VT); -- add point to the current polygon
58 struct StrokeCalc(VertexConsumer) {
59 private:
60 float mWidth = 0.5f;
61 float mWidthAbs = 0.5f;
62 float mWidthEps = 0.5f/1024.0f;
63 int mWidthSign = 1;
64 float mMiterLimit = 4.0f;
65 float mInnerMiterLimit = 1.01f;
66 float mApproxScale = 1.0f;
67 LineCap mLineCap = LineCap.Butt;
68 LineJoin mLineJoin = LineJoin.Miter;
69 InnerJoin mInnerJoin = InnerJoin.Miter;
71 public nothrow @trusted @nogc:
72 alias CoordType = VertexConsumer.ValueType;
74 mixin(DisableCopyingMixin);
76 @property void lineCap (in LineCap lc) { pragma(inline, true); mLineCap = lc; }
77 @property void lineJoin (in LineJoin lj) { pragma(inline, true); mLineJoin = lj; }
78 @property void innerJoin (in InnerJoin ij) { pragma(inline, true); mInnerJoin = ij; }
80 @property LineCap lineCap () const pure { pragma(inline, true); return mLineCap; }
81 @property LineJoin lineJoin () const pure { pragma(inline, true); return mLineJoin; }
82 @property InnerJoin innerJoin () const pure { pragma(inline, true); return mInnerJoin; }
84 @property float width () const pure { pragma(inline, true); return mWidth*2.0f; }
85 @property void width (in float w) {
86 mWidth = w*0.5f;
87 if (mWidth < 0.0f) {
88 mWidthAbs = -mWidth;
89 mWidthSign = -1;
90 } else {
91 mWidthAbs = mWidth;
92 mWidthSign = 1;
94 mWidthEps = mWidth/1024.0f;
97 @property void miterLimit (in float ml) { pragma(inline, true); mMiterLimit = ml; }
98 @property void miterLimitTheta (in float t) { pragma(inline, true); import core.stdc.math : sinf; mMiterLimit = 1.0f/sinf(t*0.5f); }
99 @property void innerMiterLimit (in float ml) { pragma(inline, true); mInnerMiterLimit = ml; }
100 @property void approximationScale (in float as) { pragma(inline, true); mApproxScale = as; }
102 @property float miterLimit () const pure { pragma(inline, true); return mMiterLimit; }
103 @property float innerMiterLimit () const pure { pragma(inline, true); return mInnerMiterLimit; }
104 @property float approximationScale () const pure { pragma(inline, true); return mApproxScale; }
106 void calcCap() (ref VertexConsumer vc, in auto ref VertexDist v0, in auto ref VertexDist v1, in float len) {
107 import core.stdc.math : acosf, atan2f, cosf, sinf;
109 vc.removeAll();
111 float dx1 = (v1.y-v0.y)/len;
112 float dy1 = (v1.x-v0.x)/len;
113 float dx2 = 0.0f;
114 float dy2 = 0.0f;
116 dx1 *= mWidth;
117 dy1 *= mWidth;
119 if (mLineCap != LineCap.Round) {
120 if (mLineCap == LineCap.Square) {
121 dx2 = dy1*mWidthSign;
122 dy2 = dx1*mWidthSign;
124 addVertex(vc, v0.x-dx1-dx2, v0.y+dy1-dy2);
125 addVertex(vc, v0.x+dx1-dx2, v0.y-dy1-dy2);
126 } else {
127 float da = acosf(mWidthAbs/(mWidthAbs+0.125f/mApproxScale))*2.0f;
128 immutable int n = cast(int)(FLT_PI/da);
129 da = FLT_PI/(n+1);
130 addVertex(vc, v0.x-dx1, v0.y+dy1);
131 if (mWidthSign > 0.0f) {
132 float a1 = atan2f(dy1, -dx1);
133 a1 += da;
134 foreach (immutable int i; 0..n) {
135 addVertex(vc, v0.x+cosf(a1)*mWidth, v0.y+sinf(a1)*mWidth);
136 a1 += da;
138 } else {
139 float a1 = atan2f(-dy1, dx1);
140 a1 -= da;
141 foreach (immutable int i; 0..n) {
142 addVertex(vc, v0.x+cosf(a1)*mWidth, v0.y+sinf(a1)*mWidth);
143 a1 -= da;
146 addVertex(vc, v0.x+dx1, v0.y-dy1);
150 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) {
151 import core.stdc.math : sqrtf;
153 immutable float dx1 = mWidth*(v1.y-v0.y)/len1;
154 immutable float dy1 = mWidth*(v1.x-v0.x)/len1;
155 immutable float dx2 = mWidth*(v2.y-v1.y)/len2;
156 immutable float dy2 = mWidth*(v2.x-v1.x)/len2;
158 vc.removeAll();
160 float cp = cross3(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y);
161 if (cp != 0.0f && (cp > 0.0f) == (mWidth > 0.0f)) {
162 // Inner join
163 float limit = (len1 < len2 ? len1 : len2)/mWidthAbs;
164 if (limit < mInnerMiterLimit) limit = mInnerMiterLimit;
166 switch (mInnerJoin) {
167 default: // inner_bevel
168 addVertex(vc, v1.x+dx1, v1.y-dy1);
169 addVertex(vc, v1.x+dx2, v1.y-dy2);
170 break;
171 case InnerJoin.Miter:
172 calcMiter(vc, v0, v1, v2, dx1, dy1, dx2, dy2, LineJoin.MiterRevert, limit, 0.0f);
173 break;
174 case InnerJoin.Jag:
175 case InnerJoin.Round:
176 cp = (dx1-dx2)*(dx1-dx2)+(dy1-dy2)*(dy1-dy2);
177 if (cp < len1*len1 && cp < len2*len2) {
178 calcMiter(vc, v0, v1, v2, dx1, dy1, dx2, dy2, LineJoin.MiterRevert, limit, 0.0f);
179 } else {
180 if (mInnerJoin == InnerJoin.Jag) {
181 addVertex(vc, v1.x+dx1, v1.y-dy1);
182 addVertex(vc, v1.x, v1.y);
183 addVertex(vc, v1.x+dx2, v1.y-dy2);
184 } else {
185 addVertex(vc, v1.x+dx1, v1.y-dy1);
186 addVertex(vc, v1.x, v1.y);
187 calcArc(vc, v1.x, v1.y, dx2, -dy2, dx1, -dy1);
188 addVertex(vc, v1.x, v1.y);
189 addVertex(vc, v1.x+dx2, v1.y-dy2);
192 break;
194 } else {
195 // Outer join
197 // Calculate the distance between v1 and
198 // the central point of the bevel line segment
199 float dx = (dx1+dx2)*0.5f;
200 float dy = (dy1+dy2)*0.5f;
201 immutable float dbevel = sqrtf(dx*dx+dy*dy);
203 if (mLineJoin == LineJoin.Round || mLineJoin == LineJoin.Bevel) {
204 // This is an optimization that reduces the number of points
205 // in cases of almost collinear segments. If there's no
206 // visible difference between bevel and miter joins we'd rather
207 // use miter join because it adds only one point instead of two.
209 // Here we calculate the middle point between the bevel points
210 // and then, the distance between v1 and this middle point.
211 // At outer joins this distance always less than stroke width,
212 // because it's actually the height of an isosceles triangle of
213 // v1 and its two bevel points. If the difference between this
214 // width and this value is small (no visible bevel) we can
215 // add just one point.
217 // The constant in the expression makes the result approximately
218 // the same as in round joins and caps. You can safely comment
219 // out this entire "if".
220 if (mApproxScale*(mWidthAbs-dbevel) < mWidthEps) {
221 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)) {
222 addVertex(vc, dx, dy);
223 } else {
224 addVertex(vc, v1.x+dx1, v1.y-dy1);
226 return;
230 switch (mLineJoin) {
231 case LineJoin.Miter:
232 case LineJoin.MiterRevert:
233 case LineJoin.MiterRound:
234 calcMiter(vc, v0, v1, v2, dx1, dy1, dx2, dy2, mLineJoin, mMiterLimit, dbevel);
235 break;
236 case LineJoin.Round:
237 calcArc(vc, v1.x, v1.y, dx1, -dy1, dx2, -dy2);
238 break;
239 default: // Bevel join
240 addVertex(vc, v1.x+dx1, v1.y-dy1);
241 addVertex(vc, v1.x+dx2, v1.y-dy2);
242 break;
247 private:
248 void addVertex (ref VertexConsumer vc, in float x, in float y) {
249 pragma(inline, true);
250 vc.add(CoordType(x, y));
253 void calcArc (ref VertexConsumer vc, in float x, in float y, in float dx1, in float dy1, in float dx2, in float dy2) {
254 import core.stdc.math : acosf, atan2f, cosf, sinf;
256 float a1 = atan2f(dy1*mWidthSign, dx1*mWidthSign);
257 float a2 = atan2f(dy2*mWidthSign, dx2*mWidthSign);
259 immutable float da = acosf(mWidthAbs/(mWidthAbs+0.125f/mApproxScale))*2.0f;
261 addVertex(vc, x+dx1, y+dy1);
262 if (mWidthSign > 0.0f) {
263 if (a1 > a2) a2 += 2.0f*FLT_PI;
264 immutable int n = cast(int)((a2-a1)/da);
265 immutable float daa = (a2-a1)/(n+1);
266 a1 += daa;
267 foreach (immutable int i; 0..n) {
268 addVertex(vc, x+cosf(a1)*mWidth, y+sinf(a1)*mWidth);
269 a1 += daa;
271 } else {
272 if (a1 < a2) a2 -= 2.0f*FLT_PI;
273 immutable int n = cast(int)((a1-a2)/da);
274 immutable float daa = (a1-a2)/(n+1);
275 a1 -= daa;
276 foreach (immutable int i; 0..n) {
277 addVertex(vc, x+cosf(a1)*mWidth, y+sinf(a1)*mWidth);
278 a1 -= daa;
281 addVertex(vc, x+dx2, y+dy2);
284 void calcMiter (ref VertexConsumer vc, in ref VertexDist v0, in ref VertexDist v1, in ref VertexDist v2,
285 in float dx1, in float dy1, in float dx2, in float dy2, in LineJoin lj,
286 in float mlimit, in float dbevel)
288 float xi = v1.x;
289 float yi = v1.y;
290 float di = 1.0f;
291 immutable float lim = mWidthAbs*mlimit;
292 bool miterLimitExceeded = true; // assume the worst
293 bool intersectionFailed = true; // assume the worst
295 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)) {
296 // Calculation of the intersection succeeded
297 di = distance(v1.x, v1.y, xi, yi);
298 if (di <= lim) {
299 // Inside the miter limit
300 addVertex(vc, xi, yi);
301 miterLimitExceeded = false;
303 intersectionFailed = false;
304 } else {
305 // Calculation of the intersection failed, most probably
306 // the three points lie one straight line.
307 // First check if v0 and v2 lie on the opposite sides of vector:
308 // (v1.x, v1.y) -> (v1.x+dx1, v1.y-dy1), that is, the perpendicular
309 // to the line determined by vertices v0 and v1.
310 // This condition determines whether the next line segments continues
311 // the previous one or goes back.
312 immutable float x2 = v1.x+dx1;
313 immutable float y2 = v1.y-dy1;
314 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)) {
315 // This case means that the next segment continues
316 // the previous one (straight line)
317 addVertex(vc, v1.x+dx1, v1.y-dy1);
318 miterLimitExceeded = false;
322 if (miterLimitExceeded) {
323 // Miter limit exceeded
324 //------------------------
325 switch (lj) {
326 case LineJoin.MiterRevert:
327 // For the compatibility with SVG, PDF, etc,
328 // we use a simple bevel join instead of
329 // "smart" bevel
330 addVertex(vc, v1.x+dx1, v1.y-dy1);
331 addVertex(vc, v1.x+dx2, v1.y-dy2);
332 break;
333 case LineJoin.MiterRound:
334 calcArc(vc, v1.x, v1.y, dx1, -dy1, dx2, -dy2);
335 break;
336 default:
337 // If no miter-revert, calculate new dx1, dy1, dx2, dy2
338 if (intersectionFailed) {
339 immutable float mlimitM = mlimit*mWidthSign;
340 addVertex(vc, v1.x+dx1+dy1*mlimitM, v1.y-dy1+dx1*mlimitM);
341 addVertex(vc, v1.x+dx2-dy2*mlimitM, v1.y-dy2-dx2*mlimitM);
342 } else {
343 immutable float x1 = v1.x+dx1;
344 immutable float y1 = v1.y-dy1;
345 immutable float x2 = v1.x+dx2;
346 immutable float y2 = v1.y-dy2;
347 di = (lim-dbevel)/(di-dbevel);
348 addVertex(vc, x1+(xi-x1)*di, y1+(yi-y1)*di);
349 addVertex(vc, x2+(xi-x2)*di, y2+(yi-y2)*di);
351 break;
358 // ////////////////////////////////////////////////////////////////////////// //
359 struct Stroker {
360 public nothrow @trusted @nogc:
361 alias VertexStorage = VertexSequence!VertexDist;
362 alias CoordStorage = SimpleVector!AGGPoint;
364 private:
365 enum State {
366 Initial,
367 Ready,
368 Cap1,
369 Cap2,
370 Outline1,
371 CloseFirst,
372 Outline2,
373 OutVertices,
374 End_poly1,
375 End_poly2,
376 Stop,
379 private:
380 StrokeCalc!CoordStorage mStroker;
381 VertexStorage mSrcVertices;
382 CoordStorage mOutVertices;
383 float mShorten = 0;
384 uint mClosed = 0;
385 State mStatus = State.Initial;
386 State mPrevStatus;
387 uint mSrcVertex = 0;
388 uint mOutVertex = 0;
390 public:
391 mixin(DisableCopyingMixin);
393 @property void lineCap (LineCap lc) { pragma(inline, true); mStroker.lineCap(lc); }
394 @property void lineJoin (LineJoin lj) { pragma(inline, true); mStroker.lineJoin(lj); }
395 @property void innerJoin (InnerJoin ij) { pragma(inline, true); mStroker.innerJoin(ij); }
397 @property LineCap lineCap () const pure { pragma(inline, true); return mStroker.lineCap(); }
398 @property LineJoin lineJoin () const pure { pragma(inline, true); return mStroker.lineJoin(); }
399 @property InnerJoin innerJoin () const pure { pragma(inline, true); return mStroker.innerJoin(); }
401 @property void width (float w) { pragma(inline, true); mStroker.width(w); }
402 @property void miterLimit (float ml) { pragma(inline, true); mStroker.miterLimit(ml); }
403 @property void miterLimitTheta (float t) { pragma(inline, true); mStroker.miterLimitTheta(t); }
404 @property void innerMiterLimit (float ml) { pragma(inline, true); mStroker.innerMiterLimit(ml); }
405 @property void approximationScale (float as) { pragma(inline, true); mStroker.approximationScale(as); }
407 @property float width () const pure { pragma(inline, true); return mStroker.width(); }
408 @property float miterLimit () const pure { pragma(inline, true); return mStroker.miterLimit(); }
409 @property float innerMiterLimit () const pure { pragma(inline, true); return mStroker.innerMiterLimit(); }
410 @property float approximationScale () const pure { pragma(inline, true); return mStroker.approximationScale(); }
412 @property void shorten (float s) { pragma(inline, true); mShorten = s; }
413 @property float shorten () const pure { pragma(inline, true); return mShorten; }
415 // Generator interface
416 void removeAll () {
417 mSrcVertices.removeAll();
418 mSrcVertex = 0;
419 mOutVertices.removeAll();
420 mOutVertex = 0;
421 mClosed = 0;
422 mStatus = mPrevStatus = State.Initial;
425 void addVertex (float x, float y, uint cmd) {
426 mStatus = State.Initial;
427 if (isMoveTo(cmd)) mSrcVertices.modifyLast(VertexDist(x, y));
428 else if (isVertex(cmd)) mSrcVertices.add(VertexDist(x, y));
429 else mClosed = getCloseFlag(cmd);
432 // Vertex Source Interface
433 void rewind () {
434 if (mStatus == State.Initial) {
435 mSrcVertices.close(mClosed != 0);
436 shortenPath(mSrcVertices, mShorten, mClosed);
437 if (mSrcVertices.length < 3) mClosed = 0;
439 mStatus = State.Ready;
440 mSrcVertex = 0;
441 mOutVertex = 0;
444 uint vertex (float* x, float* y) {
445 uint cmd = PathCommand.LineTo;
446 while (!isStop(cmd)) {
447 final switch (mStatus) {
448 case State.Initial:
449 rewind();
450 goto case;
452 case State.Ready:
453 if (mSrcVertices.length < 2+cast(uint)(mClosed != 0)) {
454 cmd = PathCommand.Stop;
455 break;
457 mStatus = (mClosed ? State.Outline1 : State.Cap1);
458 cmd = PathCommand.MoveTo;
459 mSrcVertex = 0;
460 mOutVertex = 0;
461 break;
463 case State.Cap1:
464 mStroker.calcCap(mOutVertices, mSrcVertices[0], mSrcVertices[1], mSrcVertices[0].dist);
465 mSrcVertex = 1;
466 mPrevStatus = State.Outline1;
467 mStatus = State.OutVertices;
468 mOutVertex = 0;
469 break;
471 case State.Cap2:
472 mStroker.calcCap(mOutVertices, mSrcVertices[mSrcVertices.length-1], mSrcVertices[mSrcVertices.length-2], mSrcVertices[mSrcVertices.length-2].dist);
473 mPrevStatus = State.Outline2;
474 mStatus = State.OutVertices;
475 mOutVertex = 0;
476 break;
478 case State.Outline1:
479 if (mClosed) {
480 if (mSrcVertex >= mSrcVertices.length) {
481 mPrevStatus = State.CloseFirst;
482 mStatus = State.End_poly1;
483 break;
485 } else {
486 if (mSrcVertex >= mSrcVertices.length-1) {
487 mStatus = State.Cap2;
488 break;
491 mStroker.calcJoin(mOutVertices, mSrcVertices.prev(mSrcVertex), mSrcVertices.curr(mSrcVertex), mSrcVertices.next(mSrcVertex), mSrcVertices.prev(mSrcVertex).dist, mSrcVertices.curr(mSrcVertex).dist);
492 ++mSrcVertex;
493 mPrevStatus = mStatus;
494 mStatus = State.OutVertices;
495 mOutVertex = 0;
496 break;
498 case State.CloseFirst:
499 mStatus = State.Outline2;
500 cmd = PathCommand.MoveTo;
501 goto case;
503 case State.Outline2:
504 if (mSrcVertex <= uint(mClosed == 0)) {
505 mStatus = State.End_poly2;
506 mPrevStatus = State.Stop;
507 break;
510 --mSrcVertex;
511 mStroker.calcJoin(mOutVertices, mSrcVertices.next(mSrcVertex), mSrcVertices.curr(mSrcVertex), mSrcVertices.prev(mSrcVertex), mSrcVertices.curr(mSrcVertex).dist, mSrcVertices.prev(mSrcVertex).dist);
513 mPrevStatus = mStatus;
514 mStatus = State.OutVertices;
515 mOutVertex = 0;
516 break;
518 case State.OutVertices:
519 if (mOutVertex >= mOutVertices.length) {
520 mStatus = mPrevStatus;
521 } else {
522 const(AGGPoint)* c = &mOutVertices[mOutVertex++];
523 *x = c.x;
524 *y = c.y;
525 return cmd;
527 break;
529 case State.End_poly1:
530 mStatus = mPrevStatus;
531 return PathCommand.EndPoly|PathFlag.Close|PathFlag.CCW;
533 case State.End_poly2:
534 mStatus = mPrevStatus;
535 return PathCommand.EndPoly|PathFlag.Close|PathFlag.CW;
537 case State.Stop:
538 cmd = PathCommand.Stop;
539 break;
542 return cmd;
547 // ////////////////////////////////////////////////////////////////////////// //
548 struct Contourer {
549 public nothrow @trusted @nogc:
550 alias VertexStorage = VertexSequence!VertexDist;
551 alias CoordStorage = SimpleVector!AGGPoint;
553 enum State {
554 Initial,
555 Ready,
556 Outline,
557 OutVertices,
558 EndPoly,
559 Stop,
562 private:
563 StrokeCalc!CoordStorage mStroker;
564 float mWidth = 1;
565 VertexStorage mSrcVertices;
566 CoordStorage mOutVertices;
567 State mStatus;
568 uint mSrcVertex = 0;
569 uint mOutVertex;
570 uint mClosed = 0;
571 uint mOrientation = 0;
572 bool mAutoDetect = false;
574 public:
575 mixin(DisableCopyingMixin);
577 @property void lineCap (LineCap lc) { pragma(inline, true); mStroker.lineCap(lc); }
578 @property void lineJoin (LineJoin lj) { pragma(inline, true); mStroker.lineJoin(lj); }
579 @property void innerJoin (InnerJoin ij) { pragma(inline, true); mStroker.innerJoin(ij); }
581 @property LineCap lineCap () const pure { pragma(inline, true); return mStroker.lineCap(); }
582 @property LineJoin lineJoin () const pure { pragma(inline, true); return mStroker.lineJoin(); }
583 @property InnerJoin innerJoin () const pure { pragma(inline, true); return mStroker.innerJoin(); }
585 @property void width (float w) { pragma(inline, true); mStroker.width(mWidth = w); }
586 @property void miterLimit (float ml) { pragma(inline, true); mStroker.miterLimit(ml); }
587 @property void miterLimitTheta (float t) { pragma(inline, true); mStroker.miterLimitTheta(t); }
588 @property void innerMiterLimit (float ml) { pragma(inline, true); mStroker.innerMiterLimit(ml); }
589 @property void approximationScale (float as) { pragma(inline, true); mStroker.approximationScale(as); }
591 @property float width () const pure { pragma(inline, true); return mWidth; }
592 @property float miterLimit () const pure { pragma(inline, true); return mStroker.miterLimit(); }
593 @property float innerMiterLimit () const pure { pragma(inline, true); return mStroker.innerMiterLimit(); }
594 @property float approximationScale () const pure { pragma(inline, true); return mStroker.approximationScale(); }
596 @property void autoDetectOrientation (bool v) { pragma(inline, true); mAutoDetect = v; }
597 @property bool autoDetectOrientation () const pure { pragma(inline, true); return mAutoDetect; }
599 // Generator interface
600 void removeAll () {
601 mSrcVertices.removeAll();
602 mSrcVertex = 0;
603 mOutVertices.removeAll();
604 mOutVertex = 0;
605 mClosed = 0;
606 mOrientation = 0;
607 mStatus = State.Initial;
610 void addVertex (float x, float y, uint cmd) {
611 mStatus = State.Initial;
612 if (isMoveTo(cmd)) {
613 mSrcVertices.modifyLast(VertexDist(x, y));
614 } else if (isVertex(cmd)) {
615 mSrcVertices.add(VertexDist(x, y));
616 } else if (isEndPoly(cmd)) {
617 mClosed = getCloseFlag(cmd);
618 if (mOrientation == PathFlag.None) mOrientation = getOrientation(cmd);
622 // Vertex Source Interface
623 void rewind () {
624 if (mStatus == State.Initial) {
625 mSrcVertices.close(true);
626 if (mAutoDetect) {
627 if (!isOriented(mOrientation)) {
628 mOrientation = (calcPolygonArea(mSrcVertices) > 0 ? PathFlag.CCW : PathFlag.CW);
631 if (isOriented(mOrientation)) {
632 mStroker.width(isCCW(mOrientation) ? mWidth : -mWidth);
635 mStatus = State.Ready;
636 mSrcVertex = 0;
639 uint vertex (float* x, float* y) {
640 uint cmd = PathCommand.LineTo;
641 while (!isStop(cmd)) {
642 final switch (mStatus) {
643 case State.Initial:
644 rewind();
645 goto case;
647 case State.Ready:
648 if (mSrcVertices.length < 2+cast(uint)(mClosed != 0)) {
649 cmd = PathCommand.Stop;
650 break;
652 mStatus = State.Outline;
653 cmd = PathCommand.MoveTo;
654 mSrcVertex = 0;
655 mOutVertex = 0;
656 goto case;
658 case State.Outline:
659 if (mSrcVertex >= mSrcVertices.length) {
660 mStatus = State.EndPoly;
661 break;
663 mStroker.calcJoin(mOutVertices, mSrcVertices.prev(mSrcVertex), mSrcVertices.curr(mSrcVertex), mSrcVertices.next(mSrcVertex), mSrcVertices.prev(mSrcVertex).dist, mSrcVertices.curr(mSrcVertex).dist);
664 ++mSrcVertex;
665 mStatus = State.OutVertices;
666 mOutVertex = 0;
667 goto case;
669 case State.OutVertices:
670 if (mOutVertex >= mOutVertices.length) {
671 mStatus = State.Outline;
672 } else {
673 const(AGGPoint)* c = &mOutVertices[mOutVertex++];
674 *x = c.x;
675 *y = c.y;
676 return cmd;
678 break;
680 case State.EndPoly:
681 if (!mClosed) return PathCommand.Stop;
682 mStatus = State.Stop;
683 return PathCommand.EndPoly|PathFlag.Close|PathFlag.CCW;
685 case State.Stop:
686 return PathCommand.Stop;
689 return cmd;
694 // ////////////////////////////////////////////////////////////////////////// //
695 struct Dasher {
696 private nothrow @trusted @nogc:
697 alias VertexStorage = VertexSequence!VertexDist;
698 enum MaxDashes = 32;
700 enum State {
701 Initial,
702 Ready,
703 Polyline,
704 Stop,
707 private:
708 float[MaxDashes] mDashes = 0;
709 float mTotalDashLen = 0;
710 uint mNumDashes = 0;
711 float mDashStart = 0;
712 float mShorten = 0;
713 float mCurrDashStart = 0;
714 uint mCurrDash = 0;
715 float mCurrRest = 0;
716 const(VertexDist)* m_v1 = null;
717 const(VertexDist)* m_v2 = null;
719 VertexStorage mSrcVertices;
720 uint mClosed = 0;
721 State mStatus = State.Initial;
722 uint mSrcVertex = 0;
724 public:
725 mixin(DisableCopyingMixin);
727 @property void shorten (in float s) { pragma(inline, true); mShorten = s; }
728 @property float shorten () const pure { pragma(inline, true); return mShorten; }
730 @property bool hasDashes () const pure { pragma(inline, true); return (mNumDashes > 0); }
732 void removeAllDashes () {
733 mTotalDashLen = 0;
734 mNumDashes = 0;
735 mCurrDashStart = 0;
736 mCurrDash = 0;
739 void addDash (in float dashLen, in float gapLen) {
740 if (mNumDashes < MaxDashes) {
741 mTotalDashLen += dashLen+gapLen;
742 mDashes[mNumDashes++] = dashLen;
743 mDashes[mNumDashes++] = gapLen;
747 void dashStart (in float ds) {
748 import core.stdc.math : fabsf;
749 mDashStart = ds;
750 calcDashStart(fabsf(ds));
753 // Vertex Generator Interface
754 void removeAll () {
755 mStatus = State.Initial;
756 mSrcVertices.removeAll();
757 mSrcVertex = 0;
758 mClosed = 0;
759 calcDashStart(mDashStart);
760 mCurrRest = 0;
763 void addVertex (float x, float y, uint cmd) {
764 mStatus = State.Initial;
765 if (isMoveTo(cmd)) mSrcVertices.modifyLast(VertexDist(x, y));
766 else if (isVertex(cmd)) mSrcVertices.add(VertexDist(x, y));
767 else mClosed = getCloseFlag(cmd);
770 // Vertex Source Interface
771 void rewind () {
772 if (mStatus == State.Initial) {
773 mSrcVertices.close(mClosed != 0);
774 shortenPath(mSrcVertices, mShorten, mClosed);
776 mStatus = State.Ready;
777 mSrcVertex = 0;
780 uint vertex (float* x, float* y) {
781 uint cmd = PathCommand.MoveTo;
783 while (!isStop(cmd)) {
784 final switch (mStatus) {
785 case State.Initial:
786 rewind();
787 goto case;
789 case State.Ready:
790 if (mNumDashes < 2 || mSrcVertices.length < 2) {
791 cmd = PathCommand.Stop;
792 break;
794 mStatus = State.Polyline;
795 mSrcVertex = 1;
796 m_v1 = &mSrcVertices[0];
797 m_v2 = &mSrcVertices[1];
798 mCurrRest = m_v1.dist;
799 *x = m_v1.x;
800 *y = m_v1.y;
801 if (mDashStart >= 0) calcDashStart(mDashStart);
802 return PathCommand.MoveTo;
804 case State.Polyline:
805 immutable float dashRest = mDashes[mCurrDash]-mCurrDashStart;
806 cmd = (mCurrDash&1 ? PathCommand.MoveTo : PathCommand.LineTo);
807 if (mCurrRest > dashRest) {
808 mCurrRest -= dashRest;
809 ++mCurrDash;
810 if (mCurrDash >= mNumDashes) mCurrDash = 0;
811 mCurrDashStart = 0;
812 *x = m_v2.x-(m_v2.x-m_v1.x)*mCurrRest/m_v1.dist;
813 *y = m_v2.y-(m_v2.y-m_v1.y)*mCurrRest/m_v1.dist;
814 } else {
815 mCurrDashStart += mCurrRest;
816 *x = m_v2.x;
817 *y = m_v2.y;
818 ++mSrcVertex;
819 m_v1 = m_v2;
820 mCurrRest = m_v1.dist;
821 if (mClosed) {
822 if (mSrcVertex > mSrcVertices.length) {
823 mStatus = State.Stop;
824 } else {
825 m_v2 = &mSrcVertices[mSrcVertex >= mSrcVertices.length ? 0 :mSrcVertex];
827 } else {
828 if (mSrcVertex >= mSrcVertices.length) {
829 mStatus = State.Stop;
830 } else {
831 m_v2 = &mSrcVertices[mSrcVertex];
835 return cmd;
837 case State.Stop:
838 cmd = PathCommand.Stop;
839 break;
842 return PathCommand.Stop;
845 private:
846 void calcDashStart (float ds) {
847 mCurrDash = 0;
848 mCurrDashStart = 0;
849 while (ds > 0) {
850 if (ds > mDashes[mCurrDash]) {
851 ds -= mDashes[mCurrDash];
852 ++mCurrDash;
853 mCurrDashStart = 0;
854 if (mCurrDash >= mNumDashes) mCurrDash = 0;
855 } else {
856 mCurrDashStart = ds;
857 ds = 0;