1 /*
2     canvas_ity v1.00 -- ISC license
3     Copyright (c) 2022 Andrew Kensler
4     Copyright (c) 2024 Guillaume Piolat - translation to D.
5   
6     Permission to use, copy, modify, and/or distribute this software 
7     for any purpose with or without fee is hereby granted, provided 
8     that the above copyright notice and this permission notice appear 
9     in all copies.
10   
11     THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 
12     WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 
13     WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL THE 
14     AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 
15     CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
16     LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 
17     NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
18     CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 
20 
21   ======== ABOUT ========
22   
23     This is a tiny D library for rasterizing immediate-mode 2D vector 
24     graphics, closely modeled on the basic W3C (not WHATWG) HTML5 2D
25     canvas specification:
26         https://www.w3.org/TR/2015/REC-2dcontext-20151119/
27   
28     The priorities for this library are high-quality rendering, ease 
29     of use, and compact size. Speed is important too, but secondary to 
30     the other priorities. Notably, this library takes an opinionated 
31     approach and does not provide options for trading off quality for 
32     speed.
33    
34     Despite its small size, it supports nearly everything listed in 
35     the W3C HTML5 2D canvas specification, except for hit regions and 
36     getting certain properties. 
37    
38     The main differences lie in the surface-level API to make this
39     easier for C++ use, while the underlying implementation is 
40     carefully based on the specification. 
41    
42     In particular: stroke, fill, gradient, pattern, image, and font 
43     styles are specified slightly differently (avoiding strings and 
44     auxiliary classes). Nonetheless, the goal is that this library 
45     could produce a conforming HTML5 2D canvas implementation if 
46     wrapped in a thin layer of JavaScript bindings. 
47    
48     See the accompanying C++ automated test suite and its HTML5 port 
49     for a mapping between the APIs and a comparison of this library's 
50     rendering output against browser canvas implementations.
51     Original link: https://github.com/a-e-k/canvas_ity
52 
53 
54   ======== FEATURES ========
55   
56     High-quality rendering:
57   
58   - Trapezoidal area antialiasing provides very smooth antialiasing, 
59     even when lines are nearly horizontal or vertical.
60   
61   - Gamma-correct blending, interpolation, and resampling are used
62     throughout. It linearizes all colors and premultiplies alpha on
63     input and converts back to unpremultiplied sRGB on output. This
64     reduces muddiness on many gradients (e.g., red to green), makes 
65     line thicknesses more perceptually uniform, and avoids dark 
66     fringes when interpolating opacity.
67   
68   - Bicubic convolution resampling is used whenever it needs to 
69     resample a pattern or image. This smoothly interpolates with 
70     less blockiness when magnifying, and antialiases well when 
71     minifying. It can simultaneously magnify and minify along 
72     different axes.
73   
74   - Ordered dithering is used on output. This reduces banding on 
75     subtle gradients while still being compression-friendly.
76   
77   - High curvature is handled carefully in line joins. Thick lines 
78     are drawn correctly as though tracing with a wide pen nib, even
79     where the lines curve sharply. (Simpler curve offsetting 
80     approaches tend to show bite-like artifacts in these cases.)
81   
82   - Uses no static or global variables. Threads may safely work with
83     different canvas instances concurrently without locking.
84   
85 
86   ======== LIMITATIONS ========
87  
88   - Trapezoidal antialiasing overestimates coverage where paths self-
89     intersect within a single pixel.  Where inner joins are visible, 
90     this can lead to a "grittier" appearance due to the extra 
91     windings used.
92 
93   - Clipping uses an antialiased sparse pixel mask rather than 
94     geometrically intersecting paths. Therefore, it is not 
95     subpixel-accurate.
96 
97   - Text rendering is extremely basic and mainly for convenience. It 
98     only supports left-to-right text, and does not do any hinting, 
99     kerning, ligatures, text shaping, or text layout. If you require 
100     any of those, consider using another library to provide those and 
101     feed the results to this library as either placed glyphs or raw 
102     paths.
103 
104   - TRUETYPE FONT PARSING IS NOT SECURE!  It does some basic validity
105     checking, but should only be used with known-good or sanitized 
106     fonts.
107 
108   - Parameter checking does not test for non-finite floating-point 
109     values.
110 
111   - Rendering is single-threaded, not explicitly vectorized, and not 
112     GPU-accelerated. It also copies data to avoid ownership issues.
113     If you need the speed, you are better off using a more 
114     fully-featured library.
115 
116   - The library does no input or output on its own.  Instead, you must
117       provide it with buffers to copy into or out of.
118 
119   ======== USAGE ========
120 
121     1. Construct an instance of `Canvasity` with a iven image buffer.
122     2. Use public methods of `Canvasity`.
123 */
124 /// D Port of canvas_ity.h 
125 /// Modifications:
126 /// * some SIMD
127 /// * buffer is passed and external instead of owned, it can be any
128 ///   format
129 /// * removal of text, gradients, patterns, TODO add them back
130 /// * integration with `gamut` and `colors`
131 module canvasity;
132 
133 nothrow @nogc:
134 
135 import core.stdc.stdlib: malloc, free;
136 import core.stdc.math: cosf, sinf, tanf, floorf, fmodf, roundf,
137                        sqrtf, acosf, ceilf, atan2f;
138 import core.stdc.string: memset, memcpy;
139 import dplug.core.vec;
140 import dplug.core.nogc;
141 import gamut;
142 import colors;
143 import inteli.emmintrin;
144 import inteli.math;
145 
146 // Public API enums
147 
148 /**
149     Compositing operation for blending new drawing and old pixels.
150     The sourceCopy, sourceIn, sourceOut, destinationAtop, and
151     destinationIn operations may clear parts of the canvas outside 
152     the new drawing but within the clip region. Defaults to:
153     `sourceOver`.
154 */
155 enum CanvasCompositeOp {
156     sourceIn = 1,   /// Replace old with new where old was opaque.
157     sourceCopy,     /// Replace old with new.
158     sourceOut,      /// Replace old with new where old is transparent.
159     destinationIn,  /// Clear old where new is transparent.
160     destinationAtop = 7, /// Show old over new where new is opaque.
161     add     = 10,   /// Sum old with new.
162     lighter = 10,   /// ditto
163     destinationOver,/// Show new under old.
164     destinationOut, /// Clear old where new is opaque.
165     sourceAtop,     /// Show new over old where old is opaque.
166     sourceOver,     /// Show new over old.
167     exclusiveOr     /// Show new and old, clear where both are opaque.
168 }
169 
170 /**
171     The shape used to draw the end points of lines.
172 
173     The actual shape may be affected by the current transform at the 
174     time of drawing. Only affects stroking. 
175 
176     Defaults to `LineCap.butt`.
177 */
178 enum CanvasLineCap {
179 
180     butt,   /// Use a flat cap flush to the end of the line.
181     square, /// Use a half-square cap that extends past the end of the
182             /// line.
183     circle  /// Use a semicircular cap.
184 }
185 
186 /**
187     The shape used to join two line segments where they meet.
188 
189     The actual shape may be affected by the current transform at the 
190     time of drawing. Only affects stroking.
191 
192     Defaults to `LineJoin.miter`.
193 */
194 enum CanvasLineJoin {
195 
196     miter, /// Continue the ends until they intersect, if within miter
197            /// limit.
198     bevel, /// Connect the ends with a flat triangle.
199     round  /// Join the ends with a circular arc.
200 }
201 
202 
203 // TODO comment
204 private enum repetition_style {
205 
206     repeat, 
207     repeat_x, 
208     repeat_y, 
209     no_repeat 
210 }
211 
212 /**  
213     Horizontal position of the text relative to the anchor point.
214 
215     When drawing text, the positioning of the text relative to the 
216     anchor point includes the side bearings of the first and last 
217     glyphs.
218     Defaults to leftward.
219 */
220 private enum align_style { // TODO rename and provide API to be like in HTML
221 
222     leftward,   /// Draw the text's left edge at the anchor point.
223     rightward,  /// Draw the text's right edge at the anchor point.
224     center,     /// Draw the text's horizontal center at the anchor 
225                 /// point.
226     start = 0,  /// This is a synonym for leftward.
227     ending      /// This is a synonym for rightward.
228 }
229 
230 
231 /**
232     Vertical position of the text relative to the anchor point.
233 
234     Defaults to alphabetic.
235 */
236 private enum baseline_style { // TODO rename and provide API to be like in HTML
237 
238     alphabetic, /// Alphabetic baseline as the anchor point.
239     top,        /// Top of the em box as the anchor point.
240     middle,     /// Exact middle of the em box as the anchor point.
241     bottom,     /// Bottom of the em box as the anchor point.
242     hanging,    /// Draw 60% of an em over the baseline at the anchor 
243                 /// point. (??? Not what I remember from printed:font)
244     ideographic = 3 /// This is a synonym for bottom.
245 }
246 
247 /**
248     The gamma space where colors are manipulated.
249     Proper sRGB conversion to linear and back is very expensive.
250 
251     Note: alpha itself is always kept as is and considered linear.
252 */
253 enum CanvasGammaCurve {
254 
255     none,   /// Colors are blended in storage space 
256             /// without gamma-conversion beyond going float(fastest).
257             /// It can look way worse than the other modes.
258 
259     pow2,   /// Colors are squared/sqrt to fake linear space.
260             /// without accounting for sRGB linear part.
261             /// Essentially nearly the quality of linear at much 
262             /// cheaper cost.
263 
264     linear, /// Colors converted to linear space before blend (slow).
265 }
266 
267 /**
268     Options you can give while initializing a `Canvasity`.
269 */
270 struct CanvasOptions {
271 
272     /// Default: medium quality.
273     CanvasGammaCurve gammaCurve = CanvasGammaCurve.pow2;
274 }
275 
276 
277 /**
278     Main canvas API.
279     Given an image buffer, a `Canvasity` can draw 2D shapes without
280     any GPU usage.
281 */
282 struct Canvasity {
283 
284 nothrow @nogc:
285 public:
286 
287 
288     // ======== LIFECYCLE ========
289 
290     /**
291         Construct a new canvas. Reset to default state.
292 
293         The buffer is provided externally and will NOT be cleared to
294         transparent black. It MUST outlive the Canvasity.
295 
296         Initially, the visible coordinates will run from (0,0) in the
297         upper-left to (width, height) in the lower-right and with 
298         pixel centers offset (0.5, 0.5) from the integer grid, though 
299         all this may be changed by transforms.  
300 
301         The sizes must be between 1 and 32768, inclusive.
302 
303         Note: internal allocated memory is reused if you keep 
304         reusing the same instance with `.initialize`.
305        
306         Params: 
307             buffer  Buffer to use as output. Not cleared on init.
308             options Creation options.
309 
310         Warning: this does NOT clear the image. If you reuse the same
311          Canvasity struct, all allocations will be reused eventually, 
312          leading to zero allocation per frame.
313     */
314     this(ref Image buffer, 
315          CanvasOptions options = CanvasOptions.init) {
316 
317         initialize(buffer, options);
318     }
319     ///ditto
320     void initialize(ref Image buffer,
321                     CanvasOptions options = CanvasOptions.init) {
322 
323         initialized = true;
324 
325         // Initialize as reference.
326         outBitmap = buffer.layer(0);
327 
328         this.options = options;
329         this.size_x = outBitmap.width();
330         this.size_y = outBitmap.height();
331 
332         // Initialize state
333         if (_state is null) {
334             size_t stackSize = (maxSaveRestoreDepth + 1);
335             size_t stateBytes = State.sizeof * stackSize;
336             _state = cast(State*) malloc(stateBytes);
337 
338             // Initialize all stack items with State.init
339             for (size_t n = 0; n < stackSize; ++n) {
340                 State i;
341                 memcpy(&_state[n], &i, State.sizeof);
342             }
343         }
344         else
345         {
346             // Reset state[0] to State.init, without overriding the Vec
347             // allocations
348             {
349                 State initial;
350                 _state[0] = initial;
351             }
352         }
353 
354         // Reset stack, some allocation will linger if canvas reused.
355         _stateCount = 1;
356 
357         fillStyle("black");
358         strokeStyle("black");
359         
360         // Initialize clipping state
361         // PERF: create that lazily?
362         
363         State* curr = current;
364         ushort sz_x = cast(ushort)size_x;
365         for ( ushort y = 0; y < size_y; ++y ) {
366             pixel_run piece_1 = pixel_run(   0, y, 1.0f);
367             pixel_run piece_2 = pixel_run(sz_x, y, -1.0f);
368             curr.mask.pushBack(piece_1);
369             curr.mask.pushBack(piece_2);
370         }
371 
372         interType = scanlinesInterType(outBitmap.type, PixelType.rgbaf32);
373         scanBuf.resize(buffer.width * pixelTypeSize(PixelType.rgbaf32));
374         interBuf.resize(buffer.width * pixelTypeSize(interType));
375     }
376 
377     ~this() {
378 
379         if (!initialized)
380             return;
381 
382         // free each State
383         size_t stackSize = (maxSaveRestoreDepth + 1);
384         for (size_t n = 0; n < stackSize; ++n) {
385             _state[n].free();
386         }
387 
388         free( _state);
389     }
390 
391 
392     // ======== TRANSFORMS ========
393 
394     // document things that are part of the state stack, and saved
395     // by the save/restore sequence.
396     enum { savedBySaveRestore }
397 
398 
399     /**
400         Scale the current transform.
401 
402         Negative scaling factors will flip or mirror it in that 
403         direction. The scaling factors must be non-zero.
404         If either is zero, most drawing operations will do nothing.
405 
406         Params:
407             x = Horizontal scaling factor.
408             y = Vertical scaling factor.
409     */
410     @savedBySaveRestore
411     void scale(float x, float y) {
412         transform(x, 0, 0, y, 0, 0);
413     }
414 
415 
416     /**
417         Rotate the current transform.
418     
419         The rotation is applied clockwise in a direction around the 
420         origin.
421 
422         Note: To rotate around another point, first translate that 
423               point to the origin, then do the rotation, and then 
424               translate back.
425 
426         Params:
427             angle = Clockwise angle in radians.
428     */
429     @savedBySaveRestore
430     void rotate(float angle) {
431         float cosine = cosf(angle);
432         float sine = sinf(angle);
433         transform(cosine, sine, -sine, cosine, 0, 0);
434     }
435 
436 
437     /** 
438         Translate the current transform.
439     
440         By default, positive x values shift that many pixels to the 
441         right, while negative y values shift left, positive y values 
442         shift up, and negative y values shift down.
443     
444         Params:
445             x = Amount to shift horizontally.
446             y = Amount to shift vertically.
447     */
448     @savedBySaveRestore
449     void translate(float x, float y) {
450         transform(1, 0, 0, 1, x, y);
451     }
452 
453 
454     /**
455         Add an arbitrary transform to the current transform.
456 
457         This takes six values for the upper two rows of a homogenous 
458         3x3 matrix (i.e., {{a, c, e}, {b, d, f}, {0.0, 0.0, 1.0}}) 
459         describing an arbitrary affine transform and appends it to 
460         the current transform. The values can represent any affine 
461         transform such as scaling,  rotation, translation, or skew, 
462         or any composition of affine transforms.  
463         The matrix must be invertible.  If it is not, most drawing 
464         operations will do nothing.
465 
466         Params:
467             a  = Horizontal scaling factor (m11).
468             b  = Vertical skewing (m12).
469             c  = Horizontal skewing (m21).
470             d  = vertical scaling factor (m22).
471             e  = Horizontal translation (m31).
472             f  = Vertical translation (m32).
473     */
474     @savedBySaveRestore
475     void transform(float a, float b, 
476                    float c, float d, 
477                    float e, float f) {
478         affine_matrix fwd = current.forward;
479         setTransform( fwd.a * a + fwd.c * b,
480                       fwd.b * a + fwd.d * b,
481                       fwd.a * c + fwd.c * d,
482                       fwd.b * c + fwd.d * d,
483                       fwd.a * e + fwd.c * f + fwd.e,
484                       fwd.b * e + fwd.d * f + fwd.f );
485     }
486 
487 
488     /** 
489         Replace the current transform.
490        
491         This takes six values for the upper two rows of a homogenous 
492         3x3 matrix (i.e., {{a, c, e}, {b, d, f}, {0.0, 0.0, 1.0}}) 
493         describing an arbitrary affine transform and replaces the 
494         current transform with it. The values can represent any affine 
495         transform such as scaling, rotation, translation, or skew, or 
496         any composition of affine transforms. 
497 
498         The matrix must be invertible. f it is not, most drawing 
499         operations will do nothing.
500        
501         Note: to reset the current transform back to the default, use
502               1.0, 0.0, 0.0, 1.0, 0.0, 0.0.
503        
504         Params:
505             a = Horizontal scaling factor (m11).
506             b = Vertical skewing (m12).
507             c = Horizontal skewing (m21).
508             d = Vertical scaling factor (m22).
509             e = Horizontal translation (m31).
510             f = Vertical translation (m32).
511     */
512     @savedBySaveRestore
513     void setTransform(float a, float b, 
514                       float c, float d, 
515                       float e, float f)
516     {
517         float determinant = a * d - b * c;
518         float scaling = determinant != 0 ? (1 / determinant) : 0;
519         affine_matrix new_forward = affine_matrix(a, b, c, d, e, f);
520         affine_matrix new_inverse = affine_matrix(
521             scaling * d, scaling * -b, scaling * -c, scaling * a,
522             scaling * ( c * f - d * e ), scaling * ( b * e - a * f ));
523         current.forward = new_forward;
524         current.inverse = new_inverse;
525     }
526 
527 
528     // ======== COMPOSITING ========
529 
530 
531     /**
532         Set/get the compositing operation for blending new drawing
533         and old pixels.
534     */
535     @savedBySaveRestore
536     void globalCompositeOperation(CanvasCompositeOp op) {
537         current.op = op;
538     }
539     ///ditto
540     void globalCompositeOperation(const(char)[] compositeOp) {
541 
542         CanvasCompositeOp op;
543         switch(compositeOp) with (CanvasCompositeOp) {
544             case "source-in":        op = sourceIn;        break;
545             case "copy":             op = sourceCopy;      break;
546             case "source-out":       op = sourceOut;       break;
547             case "destination-in":   op = destinationIn;   break;
548             case "destination-atop": op = destinationAtop; break;
549             case "add":              op = add;             break;
550             case "lighter":          op = lighter;         break;
551             case "destination-over": op = destinationOver; break;
552             case "destination-out":  op = destinationOut;  break;
553             case "source-atop":      op = sourceAtop;      break;
554             case "source-over":      op = sourceOver;      break;
555             case "xor":              op = exclusiveOr;     break;
556             default: return; // ignored
557         }
558         current.op = op;
559     }
560     ///ditto
561     CanvasCompositeOp globalCompositeOperation() {
562         return current.op;
563     }
564 
565 
566 
567     /**
568         Set/get the opacity applied to all drawing operations.
569 
570         If an operation already uses a transparent color, this can 
571         make it yet more transparent. 
572 
573         `alpha` must be in the range 0.0 for fully transparent 
574                                   to 1.0 for fully opaque.
575 
576         Defaults to 1.0 (opaque).
577 
578         Params:
579             alpha Degree of opacity applied to all drawing operations.
580     */
581     @savedBySaveRestore
582     void globalAlpha(float alpha) {
583         if (0 <= alpha && alpha <= 1.0)
584             current.global_alpha = alpha;
585     }
586     ///ditto
587     float globalAlpha() {
588         return current.global_alpha;
589     }
590 
591 
592     // ======== SHADOWS ========
593 
594 
595     /** 
596         Set the color and opacity of the shadow.
597         
598         Shadows will only be drawn if the shadow color has any 
599         opacity and the shadow is either offset or blurred.
600 
601         Defaults to transparent black.
602 
603         Can give:
604         - a `Color`, such as one obtained by the `colors` package
605         - a CSS string, 
606         - a `RGBA8` 8-bit sRGB ubyte quadruplet
607         - a `RGBA16` 16-bit sRGB ubyte quadruplet
608         - a `RGBAf` 32-bit sRGB ubyte quadruplet
609     */
610     @savedBySaveRestore
611     void shadowColor(Color col) {
612         RGBAf co = col.toRGBAf();
613         rgba c = rgba(co.r,co.g,co.b,co.a);
614         c = clamped(c);
615         fromGammaSpace((&c)[0..1], options.gammaCurve);
616         current.shadow_color = premultiplied(c);
617     }
618     ///ditto
619     void shadowColor(const(char)[] cssColor) {
620         shadowColor(Color(cssColor));
621     }
622     ///ditto
623     void shadowColor(RGBAf col)  { shadowColor(Color(col)); }
624     ///ditto
625     void shadowColor(RGBA8 col)  { shadowColor(Color(col)); }
626     ///ditto
627     void shadowColor(RGBA16 col) { shadowColor(Color(col)); }
628     // <old method of original library>
629     deprecated void shadowColor(float r, float g, float b, float a) {
630         shadowColor(RGBAf(r, g, b, a));
631     }
632     // </old method of original library>
633 
634     // TODO: getter, store shadow color as Color
635 
636 
637     /**
638         Set/get the level of gaussian blurring on the shadow.
639 
640         Zero produces no blur, while larger values will blur the 
641         shadow more strongly. This is not affected by the current 
642         transform. Must be non-negative. If it is not, this does 
643         nothing. 
644 
645         Defaults to 0.0 (no blur).
646         
647         Params:
648             level The level of gaussian blurring on the shadow.
649     */     
650     @savedBySaveRestore
651     void shadowBlur(float level) {
652         if (0.0f <= level)
653             current.shadow_blur = level;
654     }
655     ///ditto
656     float shadowBlur() {
657         return current.shadow_blur;
658     }
659 
660     /** 
661         Set/get offset of the shadow in pixels.
662         
663         Negative shifts left/top, positive shifts right/bottom. 
664         Not affected by the current transform. 
665         Defaults to 0 (no offset).
666     */
667     @savedBySaveRestore
668     void shadowOffsetX(float offsetX) {
669         current.shadow_offset_x = offsetX;
670     }
671     ///ditto
672     float shadowOffsetX() {
673         return current.shadow_offset_x;
674     }
675     ///ditto
676     @savedBySaveRestore
677     void shadowOffsetY(float offsetY) {
678         current.shadow_offset_y = offsetY;
679     }
680     ///ditto    
681     float shadowOffsetY() {
682         return current.shadow_offset_y;
683     }
684 
685 
686     // ======== LINE STYLES ========
687 
688 
689     /**
690         Set/get the width of the lines when stroking.
691        
692         Initially this is measured in pixels, though the current 
693         transform at the time of drawing can affect this. 
694         Must be positive. If it is not, this does nothing. 
695         Defaults to 1.0.
696        
697         Params: 
698             width Width of the lines when stroking.
699     */
700     @savedBySaveRestore
701     void lineWidth(float width) {
702         if ( 0.0f < width )
703             current.line_width = width;
704     }
705     ///ditto
706     float lineWidth() {
707         return current.line_width;
708     }
709 
710 
711     ///
712     @savedBySaveRestore
713     void lineCap(CanvasLineCap capStyle) {
714         current.line_cap = capStyle;
715     }
716     ///ditto
717     void lineCap(const(char)[] capStyle) {
718         switch(capStyle) with (CanvasLineCap) {
719             case "butt":   current.line_cap = butt; break;
720             case "square": current.line_cap = square; break;
721             case "circle": current.line_cap = circle; break;
722             default:
723         }
724     }
725     ///ditto
726     CanvasLineCap lineCap() {
727         return current.line_cap;
728     }
729 
730 
731     ///
732     @savedBySaveRestore
733     void lineJoin(CanvasLineJoin joinStyle) {
734         current.line_join = joinStyle;
735     }
736     ///ditto
737     void lineJoin(const(char)[] joinStyle) {
738         switch(joinStyle) with (CanvasLineJoin) {
739             case "miter": current.line_join = miter; break;
740             case "bevel": current.line_join = bevel; break;
741             case "round": current.line_join = round; break;
742             default:
743         }
744     }
745     ///ditto
746     CanvasLineJoin lineJoin() {
747         return current.line_join;
748     }
749 
750 
751     /**
752         Set/get the limit on maximum pointiness allowed for miter 
753         joins.
754 
755         If the distance from the point where the lines intersect to 
756         the point where the outside edges of the join intersect 
757         exceeds this ratio relative to the line width, then a bevel 
758         join will be used instead, and the miter will be lopped off. 
759         Larger values allow pointier miters.  Only affects stroking 
760         and only when the line join style is miter. Must be positive.
761         If it is not, this does nothing.
762 
763         Defaults to 10.0.
764         
765         Params:
766             limit Limit on maximum pointiness allowed for miter joins.
767     */
768     @savedBySaveRestore
769     void miterLimit(float limit) {
770         if (0 < limit)
771             current.miter_limit = limit;
772     }
773     ///ditto
774     float miterLimit() {
775         return current.miter_limit;
776     }
777 
778 
779     /** 
780         Set or clear the line dash pattern.
781 
782         Takes an array with entries alternately giving the lengths of 
783         dash and gap segments. All must be non-negative; if any are
784         not, this does nothing. These will be used to draw with dashed 
785         lines when stroking, with each subpath starting at the length 
786         along the dash pattern indicated by the line dash offset.
787         Initially these lengths are measured in pixels, though the 
788         current transform at the time of drawing can affect this.  
789         The count must be non-negative. If it is odd, the array will 
790         be appended to itself to make an even count. If it is zero, 
791         or the pointer is null, the dash pattern will be cleared and 
792         strokes will be drawn as solid lines. The array is copied and
793         it is safe to change or destroy it after this call. 
794         Defaults to solid lines.
795 
796         Params:
797             segments Pointer to array for dash pattern.
798             count    Number of entries in the array.
799     */
800     @savedBySaveRestore
801     void setLineDash(const(float)*segments, int count ) {
802 
803         if (segments)
804             for (int i = 0; i < count; ++i)
805                 if (segments[i] < 0)
806                     return;
807 
808         current.line_dash.clearContents();
809 
810         if ( ! segments)
811             return;
812         
813         for (int i = 0; i < count; ++i)
814             current.line_dash.pushBack(segments[i]);
815 
816         if (count & 1) // odd
817             for (int i = 0; i < count; ++i)
818                 current.line_dash.pushBack(segments[i]);
819     }
820     ///ditto
821     @savedBySaveRestore
822     void setLineDash(const(float)[] segments) {
823         setLineDash(segments.ptr, cast(int)segments.length);
824     }
825     ///ditto
826     @savedBySaveRestore
827     void setLineDash() {
828         setLineDash(null, 0);
829     }
830 
831 
832     /** 
833         Offset where each subpath starts the dash pattern.
834     
835         Changing this shifts the location of the dashes along the path 
836         and animating it will produce a marching ants effect. Only 
837         affects stroking and only when a dash pattern is set. May be 
838         negative or exceed the length of the dash pattern, in which 
839         case it will wrap.
840         Defaults to 0.0.
841     */
842     @savedBySaveRestore
843     void lineDashOffset(float offset) {
844         current.line_dash_offset = offset;
845     }
846     float lineDashOffset() {
847         return current.line_dash_offset;
848     }
849 
850 
851     // ======== FILL AND STROKE STYLES ========
852 
853 
854     /** 
855         Set filling or stroking to use a constant color and opacity.
856 
857         Can give:
858         - a `Color`, such as one obtained by the `colors` package
859         - a CSS string, 
860         - a `RGBA8` 8-bit sRGB ubyte quadruplet
861         - a `RGBA16` 16-bit sRGB ubyte quadruplet
862         - a `RGBAf` 32-bit sRGB ubyte quadruplet
863     */
864     @savedBySaveRestore
865     void fillStyle(Color col) {
866         RGBAf c = col.toRGBAf();
867         set_color(brush_type.fill_style, c.r, c.g, c.b, c.a);
868     }
869     ///ditto
870     void fillStyle(const(char)[] cssColor) {
871         fillStyle(Color(cssColor));
872     }
873     ///ditto
874     void fillStyle(RGBAf col)  { fillStyle(Color(col)); }
875     ///ditto
876     void fillStyle(RGBA8 col)  { fillStyle(Color(col)); }
877     ///ditto
878     void fillStyle(RGBA16 col) { fillStyle(Color(col)); }
879     ///ditto
880     void fillStyle(T)(T col) if (isLikeRGBA8!T) {
881         // Support a color-like struct like Dplug's RGBA
882         fillStyle(RGBA8(cast(ubyte)col.r, 
883                         cast(ubyte)col.g, 
884                         cast(ubyte)col.b, 
885                         cast(ubyte)col.a));
886     }
887 
888     ///ditto
889     @savedBySaveRestore
890     void strokeStyle(Color col) {
891         RGBAf c = col.toRGBAf();
892         set_color(brush_type.stroke_style, c.r, c.g, c.b, c.a);
893     }
894     ///ditto
895     void strokeStyle(const(char)[] cssColor) {
896         strokeStyle(Color(cssColor));
897     }
898     ///ditto
899     void strokeStyle(RGBAf col)  { strokeStyle(Color(col)); }
900     ///ditto
901     void strokeStyle(RGBA8 col)  { strokeStyle(Color(col)); }
902     ///ditto
903     void strokeStyle(RGBA16 col) { strokeStyle(Color(col)); }
904     ///ditto
905     void strokeStyle(T)(T rgba)  if (isLikeRGBA8!T) {
906         strokeStyle(RGBA8(cast(ubyte)rgba.r, 
907                           cast(ubyte)rgba.g, 
908                           cast(ubyte)rgba.b, 
909                           cast(ubyte)rgba.a));
910     }
911 
912     // <Old canvasity ways to give a color>
913     deprecated("Use fillStyle(str or Color) instead") 
914         void fillStyle(float r, float g, float b, float a) {
915             set_color(brush_type.fill_style, r, g, b, a);
916     }
917     deprecated("Use strokeStyle(str or Color) instead")
918     void strokeStyle(float r, float g, float b, float a) {
919         set_color(brush_type.stroke_style, r, g, b, a);
920     }
921     // </Old canvasity ways to give a color>
922 
923 
924     // Note: the following doesn't follow the HTML5 Canvas API, which 
925     // is different from dplug:canvas unfortunately.
926 
927 
928     // TODO: port later
929 
930 /+
931     /// @brief  Set filling or stroking to use a linear gradient.
932     ///
933     /// Positions the start and end points of the gradient and clears all
934     /// color stops to reset the gradient to transparent black.  Color stops
935     /// can then be added again.  When drawing, pixels will be painted with
936     /// the color of the gradient at the nearest point on the line segment
937     /// between the start and end points.  This is affected by the current
938     /// transform at the time of drawing.
939     ///
940     /// @param type     whether to set the fill_style or stroke_style
941     /// @param start_x  horizontal coordinate of the start of the gradient
942     /// @param start_y  vertical coordinate of the start of the gradient
943     /// @param end_x    horizontal coordinate of the end of the gradient
944     /// @param end_y    vertical coordinate of the end of the gradient
945     ///
946     void set_linear_gradient(brush_type type, float start_x, float start_y, 
947                              float end_x, float end_y )
948     {
949         paint_brush* brush = type == brush_type.fill_style ? &fill_brush : &stroke_brush;
950         brush.type = paint_brush.types.linear;
951         brush.colors.clearContents();
952         brush.stops.clearContents();
953         brush.start = xy( start_x, start_y );
954         brush.end = xy( end_x, end_y );
955     }
956 
957     /// @brief  Set filling or stroking to use a radial gradient.
958     ///
959     /// Positions the start and end circles of the gradient and clears all
960     /// color stops to reset the gradient to transparent black.  Color stops
961     /// can then be added again.  When drawing, pixels will be painted as
962     /// though the starting circle moved and changed size linearly to match
963     /// the ending circle, while sweeping through the colors of the gradient.
964     /// Pixels not touched by the moving circle will be left transparent
965     /// black.  The radial gradient is affected by the current transform
966     /// at the time of drawing.  The radii must be non-negative.
967     ///
968     /// @param type          whether to set the fill_style or stroke_style
969     /// @param start_x       horizontal starting coordinate of the circle
970     /// @param start_y       vertical starting coordinate of the circle
971     /// @param start_radius  starting radius of the circle
972     /// @param end_x         horizontal ending coordinate of the circle
973     /// @param end_y         vertical ending coordinate of the circle
974     /// @param end_radius    ending radius of the circle
975     ///
976     void set_radial_gradient(brush_type type,
977                              float start_x,
978                              float start_y,
979                              float start_radius,
980                              float end_x,
981                              float end_y,
982                              float end_radius )
983     {
984         if ( start_radius < 0.0f || end_radius < 0.0f )
985             return;
986         paint_brush* brush = type == brush_type.fill_style ? &fill_brush : &stroke_brush;
987         brush.type = paint_brush.types.radial;
988         brush.colors.clear();
989         brush.stops.clear();
990         brush.start = xy( start_x, start_y );
991         brush.end = xy( end_x, end_y );
992         brush.start_radius = start_radius;
993         brush.end_radius = end_radius;
994     }
995 
996     /// @brief  Add a color stop to a linear or radial gradient.
997     ///
998     /// Each color stop has an offset which defines its position from 0.0 at
999     /// the start of the gradient to 1.0 at the end.  Colors and opacity are
1000     /// linearly interpolated along the gradient between adjacent pairs of
1001     /// stops without premultiplying the alpha.  If more than one stop is
1002     /// added for a given offset, the first one added is considered closest
1003     /// to 0.0 and the last is closest to 1.0.  If no stop is at offset 0.0
1004     /// or 1.0, the stops with the closest offsets will be extended.  If no
1005     /// stops are added, the gradient will be fully transparent black.  Set a
1006     /// new linear or radial gradient to clear all the stops and redefine the
1007     /// gradient colors.  Color stops may be added to a gradient at any time.
1008     /// The color and opacity values will be clamped to the 0.0 to 1.0 range,
1009     /// inclusive.  The offset must be in the 0.0 to 1.0 range, inclusive.
1010     /// If it is not, or if chosen style type is not currently set to a
1011     /// gradient, this does nothing.
1012     ///
1013     /// @param type    whether to add to the fill_style or stroke_style
1014     /// @param offset  position of the color stop along the gradient
1015     /// @param red     sRGB red component of the color stop
1016     /// @param green   sRGB green component of the color stop
1017     /// @param blue    sRGB blue component of the color stop
1018     /// @param alpha   opacity of the color stop (not premultiplied)
1019     ///
1020     void add_color_stop(brush_type type,
1021                         float offset,
1022                         float red,
1023                         float green,
1024                         float blue,
1025                         float alpha )
1026     {
1027         paint_brush* brush = type == brush_type.fill_style ? &fill_brush : &stroke_brush;
1028         if ( ( brush.type != paint_brush.types.linear &&
1029                brush.type != paint_brush.types.radial ) ||
1030              offset < 0.0f || 1.0f < offset )
1031             return;
1032 
1033         // Finds the first element in stop that is greater than offset.
1034         size_t index = brush.stops.length;
1035         for (size_t i = 0; i < brush.stops.length; ++i)
1036         {
1037             if ( brush.stops[i] > offset)
1038             {
1039                 index = i;
1040                 break;
1041             }
1042         }
1043 
1044         rgba color = linearized( clamped( rgba( red, green, blue, alpha ) ) );
1045 
1046         // Insert into colors and stops
1047         brush.colors.pushBack(rgba.init);
1048         brush.stops.pushBack(float.init);
1049         int last = cast(int)(brush.colors.length - 1);
1050         for (int i = last; i > index; --i)
1051         {
1052             brush.colors[i] = brush.colors[i-1];
1053             brush.stops[i] = brush.stops[i-1];
1054         }
1055         brush.colors[index] = color;
1056         brush.stops[index] = offset;
1057     }
1058     +/
1059 
1060     
1061 
1062     // ======== BUILDING PATHS ========
1063 
1064 
1065     /**
1066         Reset the current path.
1067 
1068         The current path and all subpaths will be cleared after this,
1069         and a new path can be built.
1070     */
1071     void beginPath() {
1072         path.points.clearContents();
1073         path.subpaths.clearContents();
1074     }
1075 
1076 
1077     /**
1078         Create a new subpath.
1079        
1080         The given point will become the first point of the new subpath 
1081         and is subject to the current transform at the time this is 
1082         called.
1083        
1084         Params:
1085             x  Horizontal coordinate of the new first point.
1086             y  Vertical coordinate of the new first point.
1087     */       
1088     void moveTo(float x, float y) {
1089         xy transformed = forwardTransform(xy(x, y));
1090         if ( (path.subpaths.length != 0) 
1091             && path.subpaths[$-1].count == 1 ) {
1092             path.points[$-1] = transformed;
1093             return;
1094         }
1095         subpath_data subpath = subpath_data(1, false);
1096         path.points.pushBack(transformed);
1097         path.subpaths.pushBack( subpath );
1098     }
1099     ///ditto
1100     void moveTo(T)(T v) {
1101         moveTo(v.x, v.y);
1102     }
1103 
1104 
1105     /**
1106         Close the current subpath.
1107         
1108         Adds a straight line from the end of the current subpath back 
1109         to its first point and marks the subpath as closed so that 
1110         this line will join with the beginning of the path at this 
1111         point. A new, empty subpath will be started beginning with the 
1112         same first point. If the current path is empty, this does 
1113         nothing.
1114     */        
1115     void closePath() {
1116         if (path.subpaths.length == 0)
1117             return;
1118         size_t pointsInSubpath = path.subpaths[$-1].count;
1119         xy first = path.points[path.points.length - pointsInSubpath];
1120 
1121         // MAYDO: ugly, maybe lineTo and moveTo could have a private 
1122         // impl with abs coordinates
1123         affine_matrix saved_forward = current.forward;
1124         current.forward = affine_matrix.identity;
1125 
1126         // finish path
1127         lineTo(first.x, first.y);
1128         path.subpaths[$-1].closed = true;
1129 
1130         // move there.
1131         moveTo(first.x, first.y);
1132         current.forward = saved_forward;
1133     }
1134 
1135 
1136     /**
1137         Extend the current subpath with a straight line.
1138        
1139         The line will go from the current end point (if the current 
1140         path is not empty) to the given point, which will become the 
1141         new end point. Its position is affected by the current 
1142         transform at the time that this is called. If the current path 
1143         was empty, this is equivalent to just a move.
1144 
1145         Params:
1146             x  Horizontal coordinate of the new end point.
1147             y  Vertical coordinate of the new end point.
1148     */       
1149     void lineTo(float x, float y) {
1150         if (path.subpaths.length == 0) {
1151             moveTo(x, y);
1152             return;
1153         }
1154         xy p1 = path.points[$-1];
1155         xy p2 = forwardTransform(xy(x, y));
1156         if (dot(p2 - p1, p2 - p1 ) == 0.0f)
1157             return;
1158         // PERF: pushBack all 3 at once
1159         path.points.pushBack(p1);
1160         path.points.pushBack(p2);
1161         path.points.pushBack(p2);
1162         path.subpaths[$-1].count += 3;
1163     }
1164     ///ditto
1165     void lineTo(T)(T v) { 
1166         lineTo(v.x, v.y); // support point types
1167     }
1168 
1169 
1170     /**
1171         Extend the current subpath with a quadratic Bezier curve.
1172        
1173         The curve will go from the current end point (or the control 
1174         point if the current path is empty) to the given point, which 
1175         will become the new end point. The curve will be tangent 
1176         toward the control point at both ends. The current transform 
1177         at the time that this is called will affect both points passed 
1178         in.
1179        
1180         Tip: to make multiple curves join smoothly, ensure that each 
1181              new end point is on a line between the adjacent control 
1182              points.
1183        
1184         Params:
1185             cx  Horizontal coordinate of the control point.
1186             cy  Vertical coordinate of the control point.
1187             x   Horizontal coordinate of the new end point.
1188             y   Vertical coordinate of the new end point.
1189     */       
1190     void quadraticCurveYo(float cx, float cy, float x, float y )
1191     {
1192         if (path.subpaths.length == 0)
1193             moveTo(cx, cy);
1194         xy point_1 = path.points[$-1];
1195         xy control = forwardTransform(xy(cx, cy));
1196         xy point_2 = forwardTransform(xy( x,  y));
1197         xy control_1 = lerp(point_1, control, 2.0f / 3.0f);
1198         xy control_2 = lerp(point_2, control, 2.0f / 3.0f);
1199         // PERF: same, pushback all 3 at once
1200         path.points.pushBack(control_1);
1201         path.points.pushBack(control_2);
1202         path.points.pushBack(point_2);
1203         path.subpaths[$-1].count += 3;
1204     }
1205     ///ditto
1206     void quadraticCurveYo(T)(T c, T p) {
1207         quadraticCurveYo(c.x, c.y, p.x, p.y); // support point types
1208     }
1209 
1210     /**
1211         Extend the current subpath with a cubic Bezier curve.
1212        
1213         The curve will go from the current end point (or the first 
1214         control point if the current path is empty) to the given 
1215         point, which will become the new end point. The curve will be 
1216         tangent toward the first control point at the beginning and 
1217         tangent toward the second control point at the end. The 
1218         current transform at the time that this is called will affect 
1219         all points passed in.
1220        
1221         Tip: to make multiple curves join smoothly, ensure that each 
1222              new end point is on a line between the adjacent control 
1223              points.
1224        
1225         Params:
1226             c1_x  Horizontal coordinate of 1st control point.
1227             c1_y  Vertical coordinate of 1st control point.
1228             c2_x  Horizontal coordinate of 2nd control point.
1229             c2_y  Vertical coordinate of 2nd control point.
1230             x     Horizontal coordinate of new end point.
1231             y     Vertical coordinate of new end point.
1232     */       
1233     void bezierCurveTo(float c1_x, float c1_y,
1234                        float c2_x, float c2_y,
1235                        float x, float y ) {
1236         if ( path.subpaths.length == 0 )
1237             moveTo( c1_x, c1_y );
1238         xy control_1 = forwardTransform(xy( c1_x, c1_y ));
1239         xy control_2 = forwardTransform(xy( c2_x, c2_y ));
1240         xy point_2   = forwardTransform(xy(    x,    y ));
1241         // PERF: same, pushback all 3 at once
1242         path.points.pushBack(control_1);
1243         path.points.pushBack(control_2);
1244         path.points.pushBack(point_2);
1245         path.subpaths[$-1].count += 3;
1246     }
1247     ///ditto
1248     void bezierCurveTo(T)(T c1, T c2, T p) {
1249         // support point types
1250         bezierCurveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
1251     }
1252 
1253     /**
1254         Extend the current subpath with an arc tangent to two lines.
1255        
1256         The arc is from the circle with the given radius tangent to 
1257         both the line from the current end point to the vertex, and to 
1258         the line from the vertex to the given point. A straight line 
1259         will be added from the current end point to the first tangent 
1260         point (unless the current path is empty), then the shortest 
1261         arc from the first to the second tangent points will be added.
1262         The second tangent point will become the new end point. 
1263         If the radius is large, these tangent points may fall outside 
1264         the line segments. The current transform at the time that this 
1265         is called will affect the passed in points and the arc. 
1266         If the current path was empty, this is equivalent to a move.  
1267         If the arc would be degenerate, it is equivalent to a line to 
1268         the vertex point. The radius must be non-negative. 
1269         If it is not, or if the current transform is not invertible, 
1270         this does nothing.
1271        
1272         Note: To draw a polygon with rounded corners, call this once 
1273              for each vertex and pass the midpoint of the adjacent 
1274              edge as the second point; this works especially well for 
1275              rounded boxes.
1276        
1277         Params:
1278             v_x    Horizontal coordinate where the tangent lines meet.
1279             v_y    Vertical coordinate where the tangent lines meet.
1280             x      A horizontal coordinate on the second tangent line.
1281             y      A vertical coordinate on the second tangent line.
1282             r Radius of the circle containing the arc.
1283     */
1284     void arcTo(float v_x, float v_y, float x, float y, float r ) {
1285         affine_matrix fwd = current.forward;
1286         if ( (r < 0) || ( ! fwd.isInvertible) == 0.0f)
1287             return;
1288         if (path.subpaths.length == 0)
1289             moveTo(v_x, v_y);
1290         xy point_1 = inverseTransform(path.points[$-1]);
1291         xy vertex  = xy(v_x, v_y);
1292         xy point_2 = xy(x, y);
1293         xy edge_1 = normalized(point_1 - vertex);
1294         xy edge_2 = normalized(point_2 - vertex);
1295         float sine = fabsf( dot( perpendicular( edge_1 ), edge_2 ) );
1296         enum float epsilon = 1.0e-4f;
1297         if (sine < epsilon) {
1298             lineTo(v_x, v_y);
1299             return;
1300         }
1301         xy offset = ( edge_1 + edge_2 ) * (r / sine);
1302         xy center = vertex + offset;
1303         float a1 = direction(dot(offset, edge_1)*edge_1 - offset);
1304         float a2 = direction(dot(offset, edge_2)*edge_2 - offset);
1305         bool reverse = cast(int)(floorf((a2 - a1) / 3.14159265f)) & 1;
1306         arc(center.x, center.y, r, a1, a2, reverse);
1307     }
1308     ///ditto
1309     void arcTo(T)(T v, T p, float r) {
1310         // support point types
1311         arcTo(v.x, v.y, p.x, p.y, r);
1312     }
1313 
1314 
1315     /** 
1316         Extend the current subpath with an arc between two angles.
1317         
1318         The arc is from the circle centered at the given point and 
1319         with the given radius.  A straight line will be added from the
1320         current end point to the starting point of the arc (unless the
1321         current path is empty), then the arc along the circle from the 
1322         starting angle to the ending angle in the given direction will 
1323         be added. 
1324 
1325         If they are more than two pi radians apart in the given 
1326         direction, the arc will stop after one full circle. The point 
1327         at the ending angle will become the new end point of the path.
1328         Initially, the angles are clockwise relative to the x-axis.  
1329         The current transform at the time that this is called will 
1330         affect the passed in point, angles, and arc.
1331         The radius must be non-negative else it does nothing.
1332         
1333         Params:
1334             x                Horizontal coordinate of circle center.
1335             y                Vertical coordinate of circle center.
1336             radius           Radius of the circle containing the arc.
1337             start_angle      Radians clockwise from x-axis to begin.
1338             end_angle        Radians clockwise from x-axis to end.
1339             counterClockwise `true` if arc turns counter-clockwise.
1340                              The default is false (clockwise).
1341     */        
1342     void arc(float x, float y, float radius, 
1343              float start_angle, float end_angle, 
1344              bool counter_clockwise = false) {
1345 
1346         if (radius < 0)
1347             return;
1348 
1349         enum float tau = 6.28318531f;
1350         float winding = counter_clockwise ? -1.0f : 1.0f;
1351         float from = fmodf(start_angle, tau);
1352         float span = fmodf(end_angle, tau) - from;
1353 
1354         if (( end_angle - start_angle) * winding >= tau)
1355             span = tau * winding;
1356         else if (span * winding < 0.0f)
1357             span += tau * winding;
1358 
1359         xy centered_1 = radius * xy(cosf(from), sinf(from));
1360         lineTo(x + centered_1.x, y + centered_1.y);
1361         if (span == 0.0f)
1362             return;
1363 
1364         float fsteps = roundf(16.0f / tau * span * winding);
1365         int steps = cast(int)(fmaxf(1.0f, fsteps));
1366         float segment = span / cast(float)(steps);
1367         float alpha = 4.0f / 3.0f * tanf(0.25f * segment);
1368 
1369         // Note: it's a bit of the same weakness as dplug:canvas,
1370         //       in that the number of bezier subdivide do not depend
1371         //       on the transform.
1372         for ( int step = 0; step < steps; ++step ) {
1373             float angle = from + cast(float)( step + 1 ) * segment;
1374             xy centered_2 = radius * xy(cosf(angle), sinf(angle));
1375             xy point_1   = xy( x, y ) + centered_1;
1376             xy point_2   = xy( x, y ) + centered_2;
1377             xy control_1 = point_1 + alpha*perpendicular(centered_1);
1378             xy control_2 = point_2 - alpha*perpendicular(centered_2);
1379             bezierCurveTo(control_1.x, control_1.y,
1380                           control_2.x, control_2.y,
1381                           point_2.x, point_2.y );
1382             centered_1 = centered_2;
1383         }
1384     }
1385     ///ditto
1386     void arc(T)(T p, float radius,  float start_angle, 
1387                 float end_angle, bool counter_clockwise = false) {
1388         // support point types
1389         arc(p.x, p.y, radius, start_angle, end_angle, counter_clockwise);
1390     }
1391 
1392 
1393     /** 
1394         Add a closed subpath in the shape of a rectangle.
1395        
1396         The rectangle has one corner at the given point and then goes 
1397         in the direction along the width before going in the direction 
1398         of the height towards the opposite corner.  The current 
1399         transform at the time that this is called will affect the 
1400         given point and rectangle. The width and/or the height may be 
1401         negative or zero, and this can affect the winding direction.
1402        
1403         Params:
1404             x       = Horizontal coordinate of a rectangle corner.
1405             y       = Vertical coordinate of a rectangle corner.
1406             width   = Width of the rectangle.
1407             height  = Height of the rectangle.
1408     */       
1409     void rect(float x, float y, float width, float height) {
1410         moveTo(x, y);
1411         lineTo(x + width, y);
1412         lineTo(x + width, y + height);
1413         lineTo(x, y + height);
1414         closePath();
1415     }
1416 
1417 
1418     // ======== DRAWING PATHS ========
1419 
1420 
1421     /** Draw the interior of the current path using the fill style.
1422        
1423         Interior pixels are determined by the non-zero winding rule, 
1424         with all open subpaths implicitly closed by a straight line 
1425         beforehand. If shadows have been enabled by setting the shadow 
1426         color with any opacity and either offsetting or blurring the 
1427         shadows, then the shadows of the filled areas will be drawn 
1428         first, followed by the filled areas themselves. Both will be 
1429         blended into the canvas according to the global alpha, the 
1430         global compositing operation, and the clip region. If the fill
1431         style is a gradient or a pattern, it will be affected by the 
1432         current transform. The current path is left unchanged by 
1433         filling; begin a new path to clear it. If the current 
1434         transform is not invertible, this does nothing.
1435     */       
1436     void fill() {
1437         path_to_lines(false);
1438         render_main(current.fill_brush);
1439     }
1440     
1441 
1442     /** 
1443         Draw the edges of the current path using the stroke style.
1444        
1445         Edges of the path will be expanded into strokes according to 
1446         the current dash pattern, dash offset, line width, line join 
1447         style (and possibly miter limit), line cap, and transform. 
1448         If shadows have been enabled by setting the shadow color with 
1449         any opacity and either offsetting or blurring the shadows, 
1450         then the shadow will be drawn for the stroked lines first, 
1451         then the stroked lines themselves. Both will be blended into 
1452         the canvas according to the global alpha, the global 
1453         compositing operation, and the clip region. If the stroke 
1454         style is a gradient or a pattern, it will be affected by the 
1455         current transform. The current path is left unchanged by 
1456         stroking; begin a new path to clear it. If the current 
1457         transform is not invertible, this does nothing.
1458 
1459         Note: to draw with a calligraphy-like angled brush effect, add 
1460               a non-uniform scale transform just before stroking.
1461     */
1462     void stroke() {
1463         path_to_lines(true);
1464         stroke_lines();
1465         render_main(current.stroke_brush);
1466     }
1467 
1468     /**
1469         Restrict the clip region by the current path.
1470 
1471         Intersects the current clip region with the interior of the 
1472         current path (the region that would be filled), and replaces 
1473         the current clip region with this intersection.  Subsequent 
1474         calls to clip can only reduce this further. When filling or 
1475         stroking, only pixels within the current clip region will 
1476         change. The current path is left unchanged by updating the 
1477         clip region; begin a new path to clear it.  Defaults to the 
1478         entire canvas.
1479 
1480         Tip: to be able to reset the current clip region, save the 
1481             canvas state first before clipping then restore the state 
1482             to reset it.
1483     */
1484     void clip() {
1485         path_to_lines(false);
1486         lines_to_runs(xy(0.0f, 0.0f), 0);
1487         size_t part = runs.length;
1488         runs.pushBack(current.mask);
1489         Vec!pixel_run* mask = &current.mask;
1490         mask.clearContents();
1491         int y = -1;
1492         float last = 0;
1493         float sum_1 = 0;
1494         float sum_2 = 0;
1495         size_t index_1 = 0;
1496         size_t index_2 = part;
1497         while (index_1 < part && index_2 < runs.length) {            
1498             bool which = comparePixelRuns(runs[index_1], 
1499                                           runs[index_2]) < 0;
1500             pixel_run next = (which != 0) ? runs[index_1] 
1501                                           : runs[index_2];
1502             if (next.y != y) {
1503                 y = next.y;
1504                 last = 0;
1505                 sum_1 = 0;
1506                 sum_2 = 0;
1507             }
1508             if ( which )
1509                 sum_1 += runs[ index_1++ ].delta;
1510             else
1511                 sum_2 += runs[ index_2++ ].delta;
1512             float visibility = ( fminf(fabsf(sum_1), 1.0f) *
1513                                  fminf(fabsf(sum_2), 1.0f) );
1514             if ( visibility == last )
1515                 continue;
1516             size_t lastI = mask.length - 1;
1517             if ( (mask.length != 0) &&
1518                  (*mask)[lastI].x == next.x && (*mask)[lastI].y == next.y)
1519                 (*mask)[lastI].delta += visibility - last;
1520             else {
1521                 pixel_run piece;
1522                 piece = pixel_run(next.x, next.y, visibility-last);
1523                 mask.pushBack(piece);
1524             }
1525             last = visibility;
1526         }
1527     }
1528 
1529     /**
1530         Tests whether a point is in or on the current path.
1531        
1532         Interior areas are determined by the non-zero winding rule, 
1533         with all open subpaths treated as implicitly closed by a 
1534         straight line beforehand. Points exactly on the boundary are 
1535         also considered inside. The point to test is interpreted 
1536         without being affected by the current transform, nor is the 
1537         clip region considered. The current path is left unchanged by 
1538         this test.
1539        
1540         Params:
1541             x  = Horizontal coordinate of the point to test.
1542             y  = Vertical coordinate of the point to test.
1543         Returns: `true` if the point is in or on the current path.
1544     */       
1545     bool isPointInPath(float x, float y) {
1546         path_to_lines( false );
1547         int winding = 0;
1548 
1549         size_t subpath = 0;
1550         size_t beginning = 0;
1551         size_t ending = 0;
1552 
1553         xy[] points = lines.points[];
1554         for (size_t i = 0; i < points.length; ++i) {
1555             while ( i >= ending ) {
1556                 beginning = ending;
1557                 ending += lines.subpaths[subpath++].count;
1558             }
1559             xy A = points[i];
1560             xy B = points[i + 1 < ending ? i + 1 : beginning];
1561 
1562             if ( (A.y < y && y <= B.y) || (B.y < y && y <= A.y) ) {
1563                 float side = dot(perpendicular(B - A), xy(x, y) - A);
1564                 if (side == 0.0f)
1565                     return true;
1566                 winding += side > 0.0f ? 1 : -1;
1567             }
1568             else if ( A.y == y && y == B.y &&
1569                   ( ( A.x <= x && x <= B.x ) ||
1570                     ( B.x <= x && x <= A.x ) ) )
1571                 return true;
1572         }
1573         return winding != 0;
1574     }
1575     ///ditto
1576     bool isPointInPath(T)(T p) {
1577         return isPointInPath(p.x, p.y);
1578     }
1579 
1580 
1581     // ======== DRAWING RECTANGLES ========
1582 
1583 
1584     /**
1585         Clear a rectangular area back to transparent black.
1586        
1587         The clip region may limit the area cleared. The current path 
1588         is not affected by this clearing. The current transform at the 
1589         time that this is called will affect the given point and 
1590         rectangle. The width and/or the height may be negative or 
1591         zero. If either is zero, or the current transform is not 
1592         invertible, this does nothing.
1593        
1594         Params:
1595             x       = Horizontal coordinate of rectangle corner.
1596             y       = Vertical coordinate of rectangle corner.
1597             width   = Width of the rectangle.
1598             height  = Height of the rectangle.
1599     */       
1600     void clearRect(float x, float y, float width, float height) {
1601 
1602         CanvasCompositeOp saved_operation  = current.op;
1603         float saved_global_alpha           = current.global_alpha;
1604         float saved_alpha                  = current.shadow_color.a;
1605         paint_brush.types saved_type       = current.fill_brush.type;
1606         
1607         current.op              = CanvasCompositeOp.destinationOut;
1608         current.global_alpha    = 1.0f;
1609         current.shadow_color.a  = 0.0f;
1610         current.fill_brush.type = paint_brush.types.color;
1611 
1612         fillRect(x, y, width, height);
1613 
1614         current.fill_brush.type = saved_type;
1615         current.shadow_color.a  = saved_alpha;
1616         current.global_alpha    = saved_global_alpha;
1617         current.op              = saved_operation;
1618     }
1619 
1620 
1621     /**
1622         Fill a rectangular area.
1623 
1624         This behaves as though the current path were reset to a single
1625         rectangle and then filled as usual. However, the current path 
1626         is not actually changed. The current transform at the time 
1627         that this is called will affect the given point and rectangle.  
1628         The width and/or the height may be negative but not zero.  
1629         If either is zero, or the current transform is not invertible, 
1630         this does nothing.
1631        
1632         Params:
1633             x   = Horizontal coordinate of a rectangle corner.
1634             y   = Vertical coordinate of a rectangle corner.
1635             w   = Width of the rectangle.
1636             h   = Height of the rectangle.
1637     */       
1638     void fillRect(float x, float y, float w, float h) {
1639 
1640         if (w == 0 || h == 0)
1641             return;
1642 
1643         Vec!xy* points = &lines.points;
1644         points.clearContents();
1645         lines.subpaths.clearContents();
1646         // PERF
1647         points.pushBack(forwardTransform(xy(x, y)));
1648         points.pushBack(forwardTransform(xy(x + w, y)));
1649         points.pushBack(forwardTransform(xy(x + w, y + h)));
1650         points.pushBack(forwardTransform(xy(x, y + h)));
1651         subpath_data entry = subpath_data(4, true);
1652         lines.subpaths.pushBack(entry);
1653         render_main(current.fill_brush);
1654     }
1655 
1656 
1657     /** 
1658         Stroke a rectangular area.
1659        
1660         This behaves as though the current path were reset to a single
1661         rectangle and then stroked as usual.  However, the current 
1662         path is not actually changed. The current transform at the 
1663         time that this is called will affect the given point and 
1664         rectangle. The width and/or the height may be negative or 
1665         zero. If both are zero, or the current transform is not 
1666         invertible, this does nothing. If only one is zero, this 
1667         behaves as though it strokes a single horizontal or vertical 
1668         line.
1669        
1670         Params:
1671             x  = Horizontal coordinate of a rectangle corner.
1672             y  = Vertical coordinate of a rectangle corner.
1673             w  = Width of the rectangle.
1674             h  = Height of the rectangle.
1675     */       
1676     void strokeRect(float x, float y, float w, float h) {
1677         if ( w == 0.0f && h == 0.0f )
1678             return;
1679         Vec!xy* points = &lines.points;
1680         points.clearContents();
1681         lines.subpaths.clearContents();
1682         if ( w == 0.0f || h == 0.0f ) {
1683             points.pushBack(forwardTransform(xy(  x, y  )));
1684             points.pushBack(forwardTransform(xy(x+w, y+h)));
1685             subpath_data entry = subpath_data(2, false);
1686             lines.subpaths.pushBack( entry );
1687         }
1688         else {
1689             points.pushBack(forwardTransform(xy(  x, y  )));
1690             points.pushBack(forwardTransform(xy(x+w, y  )));
1691             points.pushBack(forwardTransform(xy(x+w, y+h)));
1692             points.pushBack(forwardTransform(xy(  x, y+h)));
1693             points.pushBack(forwardTransform(xy(  x, y  )));
1694             subpath_data entry = { 5, true };
1695             lines.subpaths.pushBack(entry);
1696         }
1697         stroke_lines();
1698         render_main(current.stroke_brush);
1699     }
1700 
1701 
1702     // ======== DRAWING TEXT ========
1703 
1704 
1705     /** 
1706         Set the font to use for text drawing.
1707 
1708         The font must be a TrueType font (TTF) file which has been 
1709         loaded or mapped into memory.  Following some basic 
1710         validation, the relevant sections of the font file contents 
1711         are copied, and it is safe to change or destroy after this 
1712         call. As an optimization, calling this with either a null 
1713         pointer or zero for the number of bytes will allow for 
1714         changing the size of the previous font without recopying from
1715         the file.  Note that the font parsing is not meant to be 
1716         secure; only use this with trusted TTF files!
1717        
1718         Params:
1719             font   = Contents of a TrueType font (TTF) file.
1720             bytes  = Number of bytes in the font file.
1721             size   = Size in pixels per em to draw at.
1722 
1723         Returns: 
1724             `true` if the font was set successfully.
1725     */       
1726     bool setFont(const(ubyte) *font, int bytes, float size) {
1727 
1728         if ( font && bytes ) {
1729             current.face.data.clearContents();
1730             current.face.cmap = 0;
1731             current.face.glyf = 0;
1732             current.face.head = 0;
1733             current.face.hhea = 0;
1734             current.face.hmtx = 0;
1735             current.face.loca = 0;
1736             current.face.maxp = 0;
1737             current.face.os_2 = 0;
1738             if ( bytes < 6 )
1739                 return false;
1740             int version_ = ( font[ 0 ] << 24 | font[ 1 ] << 16 |
1741                             font[ 2 ] <<  8 | font[ 3 ] <<  0 );
1742             int tables = font[ 4 ] << 8 | font[ 5 ];
1743             if ( ( version_ != 0x00010000 && version_ != 0x74727565 ) 
1744                 || bytes < tables * 16 + 12 )
1745                 return false;
1746 
1747             foreach(ubyte b; font[0..tables*16+12])
1748                 current.face.data.pushBack(b);
1749 
1750             //face.data.insert( face.data.end(), font, 
1751             //                  font + tables * 16 + 12 );
1752             for ( int index = 0; index < tables; ++index )
1753             {
1754                 int tag = signed_32(current.face.data, index * 16 + 12);
1755                 int ofs = signed_32(current.face.data, index * 16 + 20);
1756                 int span = signed_32(current.face.data, index * 16 + 24);
1757                 if ( bytes < ofs + span )
1758                 {
1759                     current.face.data.clearContents();
1760                     return false;
1761                 }
1762                 int place = cast(int)( current.face.data.length() );
1763                 if ( tag == 0x636d6170 )
1764                     current.face.cmap = place;
1765                 else if ( tag == 0x676c7966 )
1766                     current.face.glyf = place;
1767                 else if ( tag == 0x68656164 )
1768                     current.face.head = place;
1769                 else if ( tag == 0x68686561 )
1770                     current.face.hhea = place;
1771                 else if ( tag == 0x686d7478 )
1772                     current.face.hmtx = place;
1773                 else if ( tag == 0x6c6f6361 )
1774                     current.face.loca = place;
1775                 else if ( tag == 0x6d617870 )
1776                     current.face.maxp = place;
1777                 else if ( tag == 0x4f532f32 )
1778                     current.face.os_2 = place;
1779                 else
1780                     continue;
1781                 foreach(ubyte b; font[ofs..ofs+span])
1782                     current.face.data.pushBack(b);
1783             }
1784             if ( !current.face.cmap || !current.face.glyf 
1785                  || !current.face.head || !current.face.hhea 
1786                  || !current.face.hmtx || !current.face.loca 
1787                  || !current.face.maxp || !current.face.os_2 )
1788             {
1789                 current.face.data.clearContents();
1790                 return false;
1791             }
1792         }
1793         if ( current.face.data.length == 0 )
1794             return false;
1795         int units_per_em = unsigned_16( current.face.data, 
1796                                         current.face.head + 18 );
1797         current.face.scale = size / cast(float)( units_per_em );
1798         return true;
1799     }
1800 
1801 
1802     /**
1803         Draw a line of text by filling its outline.
1804        
1805         This behaves as though the current path were reset to the 
1806         outline of the given text in the current font and size, 
1807         positioned relative to the given anchor point according to the 
1808         current alignment and baseline, and then filled as usual. 
1809         However, the current path is not actually changed. The current
1810         transform at the time that this is called will affect the 
1811         given anchor point and the text outline. However, the 
1812         comparison to the maximum width in pixels and the condensing 
1813         if needed is done before applying the current transform.
1814         The maximum width (if given) must be positive. 
1815         If it is not, or the text pointer is null, or the font has not 
1816         been set yet, or the last setting of it was unsuccessful, or 
1817         the current transform is not invertible, this does nothing.
1818        
1819         Params:
1820             text      = Null-terminated UTF-8 string of text to fill.
1821             x         = Horizontal coordinate of the anchor point.
1822             y         = Vertical coordinate of the anchor point.
1823             maxWidth  = Horizontal width to condense wider text to.
1824     */
1825     // TODO: take regular D string instead
1826     void fillText(const(char)* text, float x, float y, 
1827                   float maxWidth = 1.0e30f) {
1828         text_to_lines(text, xy(x, y), maxWidth, false);
1829         render_main(current.fill_brush);
1830     }
1831 
1832     /** 
1833         Draw a line of text by stroking its outline.
1834 
1835         This behaves as though the current path were reset to the 
1836         outline of the given text in the current font and size, 
1837         positioned relative to the given anchor point according to the 
1838         current alignment and baseline, and then stroked as usual. 
1839         However, the current path is not actually changed. The current
1840         transform at the time that this is called will affect the 
1841         given anchor point and the text outline. 
1842         However, the comparison to the maximum width in pixels and the
1843         condensing if needed is done before applying the current 
1844         transform. The maximum width (if given) must be positive. 
1845         If it is not, or the text pointer is null, or the font has not 
1846         been set yet, or the last setting of it was unsuccessful, or 
1847         the current transform is not invertible, this does nothing.
1848         
1849         Params:
1850             text     = Null-terminated UTF-8 string to stroke.
1851             x        = Horizontal coordinate of the anchor point.
1852             y        = Vertical coordinate of the anchor point.
1853             maxWidth = Horizontal width to condense wider text to.
1854     */       
1855     void stroke_text(const(char)* text, 
1856                      float x, float y, 
1857                      float maxWidth = 1.0e30f) {
1858         text_to_lines(text, xy(x, y), maxWidth, true);
1859         stroke_lines();
1860         render_main(current.stroke_brush);
1861     }
1862 
1863     /**
1864         Measure the width in pixels of a line of text.
1865 
1866         The measured width is the advance width, which includes the 
1867         side bearings of the first and last glyphs.  However, text as 
1868         drawn may go outside this (e.g., due to glyphs that spill 
1869         beyond their nominal widths or stroked text with wide lines).  
1870         Measurements ignore the current transform.  If the text 
1871         pointer is null, or the font has not been set yet, or the last 
1872         setting of it was unsuccessful, this returns zero.
1873 
1874         Params:
1875             text = Null-terminated UTF-8 string to measure.
1876 
1877         Returns:
1878             Width of the text in pixels.
1879         FUTURE: more metrics, use a font API in another package
1880     */
1881     float measure_text(const(char)* text) {
1882         if ( (current.face.data.length == 0) || !text )
1883             return 0.0f;
1884         int hmetrics = unsigned_16(current.face.data, 
1885                                    current.face.hhea+34);
1886         int width = 0;
1887         for ( int index = 0; text[index]; ) {
1888             int glyph = character_to_glyph(text, index);
1889             int entry = min_int( glyph, hmetrics - 1 );
1890             width += unsigned_16(current.face.data, 
1891                                  current.face.hmtx+entry*4);
1892         }
1893         return cast(float)(width) * current.face.scale;
1894     }
1895 
1896     // ======== DRAWING IMAGES ========
1897 
1898     /** 
1899         Draw an image onto the canvas.
1900        
1901         The position of the rectangle that the image is drawn to is 
1902         affected by the current transform at the time of drawing, and 
1903         the image will be resampled as needed (with the filtering 
1904         always clamping to the edges of the image). The drawing is 
1905         also affected by the shadow, global alpha, global compositing 
1906         operation settings, and by the clip region. The current path 
1907         is not affected by drawing an image. The image data, which 
1908         should be in top to bottom rows of contiguous pixels from left 
1909         to right, is not retained and it is safe to change or destroy 
1910         it after this call. The width and height must both be positive 
1911         and the width and/or the height to scale to may be negative 
1912         but not zero. Otherwise, or if the image pointer is null, or 
1913         the current transform is not invertible, this does nothing.
1914        
1915         Note: to use a small piece of a larger image, reduce the width 
1916               and height, and offset the image pointer while keeping 
1917               the stride.
1918        
1919         Params:
1920             image     = Unpremultiplied sRGB RGBA8 image data.
1921             width     = Width of the image in pixels.
1922             height    = Height of the image in pixels.
1923             stride    = Bytes between the start of each image row.
1924             x         = Horizontal coordinate to draw the corner at.
1925             y         = Vertical coordinate to draw the corner at.
1926             to_width  = Width to scale the image to.
1927             to_height = Height to scale the image to.
1928     */       
1929     void drawImage(const(ubyte)* image,
1930                    int width,
1931                    int height,
1932                    int stride,
1933                    float x,
1934                    float y,
1935                    float to_width,
1936                    float to_height) {
1937         if (!image || width <= 0 || height <= 0 ||
1938              to_width == 0.0f || to_height == 0.0f)
1939             return;
1940         swap_brush(current.fill_brush, image_brush, temp_brush);
1941         setPattern(brush_type.fill_style, image, width, height, 
1942                   stride, repetition_style.repeat);
1943         swap_brush(current.fill_brush, image_brush, temp_brush);
1944         Vec!xy* pts = &lines.points;
1945         pts.clearContents();
1946         lines.subpaths.clearContents();
1947         pts.pushBack(forwardTransform(xy(x, y )));
1948         pts.pushBack(forwardTransform(xy(x+to_width, y)));
1949         pts.pushBack(forwardTransform(xy(x+to_width, y+to_height)));
1950         pts.pushBack(forwardTransform(xy(x, y+to_height)));
1951         subpath_data entry = subpath_data(4, true);
1952         lines.subpaths.pushBack(entry);
1953         affine_matrix saved_forward = current.forward;
1954         affine_matrix saved_inverse = current.inverse;
1955         translate( x + fminf( 0.0f, to_width ),
1956                    y + fminf( 0.0f, to_height ) );
1957         scale( fabsf( to_width ) / cast(float)( width ),
1958                fabsf( to_height ) / cast(float)( height ) );
1959         render_main( image_brush );
1960         current.forward = saved_forward;
1961         current.inverse = saved_inverse;
1962     }
1963 
1964 
1965     // ======== PIXEL MANIPULATION ========
1966 
1967     // Note: in original canvas_ity, there is a getImageData call,
1968     // and putImageData call, because the buffer is internal. 
1969     // Dithering is applied on 
1970     // export using this as luminance offset (index by [y&3][x&3])
1971     // divided by 255.
1972     // But if we dither on each operation, the offset will 
1973     // accumulate?
1974     //
1975     // static immutable float[4][4] bayer = [
1976     //     [ 0.03125f, 0.53125f, 0.15625f, 0.65625f ],
1977     //     [ 0.78125f, 0.28125f, 0.90625f, 0.40625f ],
1978     //     [ 0.21875f, 0.71875f, 0.09375f, 0.59375f ],
1979     //     [ 0.96875f, 0.46875f, 0.84375f, 0.34375f ] 
1980     // ];
1981 
1982     // ======== CANVAS STATE ========
1983 
1984     // Maximum number of times you can call save() and have things restored.
1985     // If you exceed this limit, it will crash.
1986     enum maxSaveRestoreDepth = 15;
1987 
1988     /** 
1989         Save the current state as though to a stack.
1990 
1991         The full state of the canvas is saved, except for the pixels 
1992         in the canvas buffer, and the current path.
1993 
1994         TODO: this isn't strictly true, as the pattern image isn't 
1995               saved.
1996        
1997         Tip: to be able to reset the current clip region, save the 
1998              canvas state first before clipping then restore the state 
1999              to reset it.
2000     */       
2001     void save()
2002     {
2003         // PERF: state index into resources and just hold an index
2004         // to brushes/fonts/gradients.
2005 
2006         // PERF: states are still kept in the stack, so that their 
2007         // allocations are reused
2008         // First push a .init state without data
2009         int lastTop = _stateCount - 1;
2010         _state[lastTop + 1] = _state[lastTop];
2011         _stateCount++;
2012         assert(_stateCount <= maxSaveRestoreDepth);
2013     }
2014 
2015     /**
2016         Restore a previously saved state as though from a stack.
2017     */
2018     void restore() {
2019 
2020         // too many restore() without corresponding save()
2021         if (_stateCount <= 1)
2022             assert(false); 
2023 
2024         _stateCount--;
2025     }
2026 
2027     // non-copyable
2028     @disable this(this);
2029 
2030 private:
2031 
2032     enum brush_type 
2033     { 
2034         fill_style, 
2035         stroke_style
2036     }
2037 
2038     int size_x;
2039     int size_y;
2040 
2041     xy forwardTransform(xy pt)
2042     {
2043         return matrix_mul_vec(current.forward, pt);
2044     }
2045 
2046     xy inverseTransform(xy pt)
2047     {
2048         return matrix_mul_vec(current.inverse, pt);
2049     }
2050 
2051     // Canvas state. It is store on a stack by `save`/`restore` calls.
2052     struct State 
2053     {
2054     nothrow @nogc:
2055         @disable this(this);
2056         CanvasCompositeOp op         = CanvasCompositeOp.sourceOver;
2057         float shadow_offset_x        = 0.0f;
2058         float shadow_offset_y        = 0.0f;
2059         CanvasLineCap line_cap       = CanvasLineCap.butt;
2060         CanvasLineJoin line_join     = CanvasLineJoin.miter;
2061         float line_dash_offset       = 0.0f;
2062         align_style text_align       = align_style.start;
2063         baseline_style text_baseline = baseline_style.alphabetic;
2064         affine_matrix forward        = affine_matrix.identity;
2065         affine_matrix inverse        = affine_matrix.identity;
2066         float global_alpha           = 1.0f;
2067         rgba shadow_color            = rgba(0.0f, 0.0f, 0.0f, 0.0f);
2068         float shadow_blur            = 0.0f;
2069         float line_width             = 1.0f;
2070         float miter_limit            = 10.0f;
2071         Vec!float line_dash;
2072         paint_brush fill_brush;
2073         paint_brush stroke_brush;
2074         Vec!pixel_run mask;
2075         font_face face;
2076 
2077         // that assign ensures amortized allocation by reusing vectors
2078         void opAssign(ref const(State) other) {
2079 
2080             this.op               = other.op;
2081             this.shadow_offset_x  = other.shadow_offset_x;
2082             this.shadow_offset_y  = other.shadow_offset_y;
2083             this.line_cap         = other.line_cap;
2084             this.line_join        = other.line_join;
2085             this.line_dash_offset = other.line_dash_offset;
2086             this.text_align       = other.text_align;
2087             this.text_baseline    = other.text_baseline;
2088             this.forward          = other.forward;
2089             this.inverse          = other.inverse;
2090             this.global_alpha     = other.global_alpha;
2091             this.shadow_color     = other.shadow_color;
2092             this.shadow_blur      = other.shadow_blur;
2093             this.line_width       = other.line_width;
2094             this.miter_limit      = other.miter_limit;
2095             assign_vec!float(this.line_dash, other.line_dash);
2096             this.fill_brush       = other.fill_brush;
2097             this.stroke_brush     = other.stroke_brush;
2098             assign_vec!pixel_run(this.mask, other.mask);
2099             this.face = other.face;
2100         }
2101 
2102         void free() {
2103             destroyNoGC(line_dash);
2104             destroyNoGC(stroke_brush);
2105             destroyNoGC(fill_brush);
2106             destroyNoGC(mask);
2107         }
2108 
2109     }
2110     
2111     paint_brush image_brush; // Note: not sure why not in State
2112     paint_brush temp_brush;
2113     bezier_path path;
2114     line_path lines;
2115     line_path scratch;
2116     Vec!pixel_run runs;
2117 
2118     bool initialized = false;
2119     Image outBitmap;
2120 
2121     Vec!float shadow;
2122     Vec!ubyte scanBuf;
2123     PixelType interType;
2124     Vec!ubyte interBuf;
2125 
2126     // State stack.    
2127     // +1 to be able to call `save()` maxSaveRestoreDepth times.
2128     int _stateCount = 0;
2129     State* _state;
2130     CanvasOptions options;
2131 
2132     // ".current" state is the last element of that stack.
2133     // Holds current color, transforms, etc.
2134     State* current() pure {
2135         return _state + (_stateCount - 1);
2136     }
2137 
2138     void set_color(brush_type type, float red, float green, 
2139                                     float blue, float alpha )
2140     {
2141         paint_brush* brush = type == brush_type.fill_style ? 
2142                               &(current.fill_brush) 
2143                             : &(current.stroke_brush);
2144         brush.type = paint_brush.types.color;
2145         brush.colors.clearContents();
2146 
2147         rgba c = rgba(red, green, blue, alpha);
2148         c = clamped(c);
2149         fromGammaSpace((&c)[0..1], options.gammaCurve);
2150         brush.colors.pushBack( premultiplied(c) );
2151     }
2152 
2153     // Tessellate (at low-level) a cubic Bezier curve and add it to the polyline
2154     // data.  This recursively splits the curve until two criteria are met
2155     // (subject to a hard recursion depth limit).  First, the control points
2156     // must not be farther from the line between the endpoints than the tolerance.
2157     // By the Bezier convex hull property, this ensures that the distance between
2158     // the true curve and the polyline approximation will be no more than the
2159     // tolerance.  Secondly, it takes the cosine of an angular turn limit; the
2160     // curve will be split until it turns less than this amount.  This is used
2161     // for stroking, with the angular limit chosen such that the sagita of an arc
2162     // with that angle and a half-stroke radius will be equal to the tolerance.
2163     // This keeps expanded strokes approximately within tolerance.  Note that
2164     // in the base case, it adds the control points as well as the end points.
2165     // This way, stroke expansion infers the correct tangents from the ends of
2166     // the polylines.
2167     //
2168     void add_tessellation(xy point_1, xy control_1, xy control_2, xy point_2, float angular, int limit )
2169     {
2170         enum float tolerance = 0.125f;
2171         float flatness = tolerance * tolerance;
2172         xy edge_1 = control_1 - point_1;
2173         xy edge_2 = control_2 - control_1;
2174         xy edge_3 = point_2 - control_2;
2175         xy segment = point_2 - point_1;
2176         float squared_1 = dot( edge_1, edge_1 );
2177         float squared_2 = dot( edge_2, edge_2 );
2178         float squared_3 = dot( edge_3, edge_3 );
2179         enum float epsilon = 1.0e-4f;
2180         float length_squared = dot( segment, segment );
2181         if (length_squared < epsilon) length_squared = epsilon;
2182         float projection_1 = dot( edge_1, segment ) / length_squared;
2183         float projection_2 = dot( edge_3, segment ) / length_squared;
2184         float clamped_1 = projection_1;
2185         if (clamped_1 < 0) clamped_1 = 0;
2186         if (clamped_1 > 1) clamped_1 = 1;
2187         float clamped_2 = projection_2;
2188         if (clamped_2 < 0) clamped_2 = 0;
2189         if (clamped_2 > 1) clamped_2 = 1;
2190         xy to_line_1 = point_1 + clamped_1 * segment - control_1;
2191         xy to_line_2 = point_2 - clamped_2 * segment - control_2;
2192         float cosine = 1.0f;
2193         if ( angular > -1.0f )
2194         {
2195             if ( squared_1 * squared_3 != 0.0f )
2196                 cosine = dot( edge_1, edge_3 ) / sqrtf( squared_1 * squared_3 );
2197             else if ( squared_1 * squared_2 != 0.0f )
2198                 cosine = dot( edge_1, edge_2 ) / sqrtf( squared_1 * squared_2 );
2199             else if ( squared_2 * squared_3 != 0.0f )
2200                 cosine = dot( edge_2, edge_3 ) / sqrtf( squared_2 * squared_3 );
2201         }
2202         if ( ( dot( to_line_1, to_line_1 ) <= flatness &&
2203                dot( to_line_2, to_line_2 ) <= flatness &&
2204               cosine >= angular ) ||
2205              !limit )
2206         {
2207             if ( angular > -1.0f && squared_1 != 0.0f )
2208                 lines.points.pushBack( control_1 );
2209             if ( angular > -1.0f && squared_2 != 0.0f )
2210                 lines.points.pushBack( control_2 );
2211             if ( angular == -1.0f || squared_3 != 0.0f )
2212                 lines.points.pushBack( point_2 );
2213             return;
2214         }
2215         xy left_1 = lerp( point_1, control_1, 0.5f );
2216         xy middle = lerp( control_1, control_2, 0.5f );
2217         xy right_2 = lerp( control_2, point_2, 0.5f );
2218         xy left_2 = lerp( left_1, middle, 0.5f );
2219         xy right_1 = lerp( middle, right_2, 0.5f );
2220         xy split = lerp( left_2, right_1, 0.5f );
2221         add_tessellation( point_1, left_1, left_2, split, angular, limit - 1 );
2222         add_tessellation( split, right_1, right_2, point_2, angular, limit - 1 );
2223     }
2224 
2225     // Tessellate (at high-level) a cubic Bezier curve and add it to the polyline
2226     // data.  This solves both for the extreme in curvature and for the horizontal
2227     // and vertical extrema.  It then splits the curve into segments at these
2228     // points and passes them off to the lower-level recursive tessellation.
2229     // This preconditioning means the polyline exactly includes any cusps or
2230     // ends of tight loops without the flatness test needing to locate it via
2231     // bisection, and the angular limit test can use simple dot products without
2232     // fear of curves turning more than 90 degrees.
2233     //
2234     void add_bezier(xy point_1, xy control_1,
2235                             xy control_2,
2236                             xy point_2,
2237                             float angular )
2238     {
2239         xy edge_1 = control_1 - point_1;
2240         xy edge_2 = control_2 - control_1;
2241         xy edge_3 = point_2 - control_2;
2242         if ( dot( edge_1, edge_1 ) == 0.0f &&
2243              dot( edge_3, edge_3 ) == 0.0f )
2244         {
2245             lines.points.pushBack( point_2 );
2246             return;
2247         }
2248         float[7] at = [ 0.0f, 1.0f, 0, 0, 0, 0, 0 ];
2249         int cuts = 2;
2250         xy extrema_a = -9.0f * edge_2 + 3.0f * ( point_2 - point_1 );
2251         xy extrema_b = 6.0f * ( point_1 + control_2 ) - 12.0f * control_1;
2252         xy extrema_c = 3.0f * edge_1;
2253         enum float epsilon = 1.0e-4f;
2254         if ( fabsf( extrema_a.x ) > epsilon )
2255         {
2256             float discriminant =
2257                 extrema_b.x * extrema_b.x - 4.0f * extrema_a.x * extrema_c.x;
2258             if ( discriminant >= 0.0f )
2259             {
2260                 float sign = extrema_b.x > 0.0f ? 1.0f : -1.0f;
2261                 float term = -extrema_b.x - sign * sqrtf( discriminant );
2262                 float extremum_1 = term / ( 2.0f * extrema_a.x );
2263                 at[ cuts++ ] = extremum_1;
2264                 at[ cuts++ ] = extrema_c.x / ( extrema_a.x * extremum_1 );
2265             }
2266         }
2267         else if ( fabsf( extrema_b.x ) > epsilon )
2268             at[ cuts++ ] = -extrema_c.x / extrema_b.x;
2269         if ( fabsf( extrema_a.y ) > epsilon )
2270         {
2271             float discriminant =
2272                 extrema_b.y * extrema_b.y - 4.0f * extrema_a.y * extrema_c.y;
2273             if ( discriminant >= 0.0f )
2274             {
2275                 float sign = extrema_b.y > 0.0f ? 1.0f : -1.0f;
2276                 float term = -extrema_b.y - sign * sqrtf( discriminant );
2277                 float extremum_1 = term / ( 2.0f * extrema_a.y );
2278                 at[ cuts++ ] = extremum_1;
2279                 at[ cuts++ ] = extrema_c.y / ( extrema_a.y * extremum_1 );
2280             }
2281         }
2282         else if ( fabsf( extrema_b.y ) > epsilon )
2283             at[ cuts++ ] = -extrema_c.y / extrema_b.y;
2284         float determinant_1 = dot( perpendicular( edge_1 ), edge_2 );
2285         float determinant_2 = dot( perpendicular( edge_1 ), edge_3 );
2286         float determinant_3 = dot( perpendicular( edge_2 ), edge_3 );
2287         float curve_a = determinant_1 - determinant_2 + determinant_3;
2288         float curve_b = -2.0f * determinant_1 + determinant_2;
2289         if ( fabsf( curve_a ) > epsilon &&
2290              fabsf( curve_b ) > epsilon )
2291             at[ cuts++ ] = -0.5f * curve_b / curve_a;
2292         for ( int index = 1; index < cuts; ++index )
2293         {
2294             float value = at[ index ];
2295             int sorted = index - 1;
2296             for ( ; 0 <= sorted && value < at[ sorted ]; --sorted )
2297                 at[ sorted + 1 ] = at[ sorted ];
2298             at[ sorted + 1 ] = value;
2299         }
2300         xy split_point_1 = point_1;
2301         for ( int index = 0; index < cuts - 1; ++index )
2302         {
2303             if ( !( 0.0f <= at[ index ] && at[ index + 1 ] <= 1.0f &&
2304                     at[ index ] != at[ index + 1 ] ) )
2305                 continue;
2306             float ratio = at[ index ] / at[ index + 1 ];
2307             xy partial_1 = lerp( point_1, control_1, at[ index + 1 ] );
2308             xy partial_2 = lerp( control_1, control_2, at[ index + 1 ] );
2309             xy partial_3 = lerp( control_2, point_2, at[ index + 1 ] );
2310             xy partial_4 = lerp( partial_1, partial_2, at[ index + 1 ] );
2311             xy partial_5 = lerp( partial_2, partial_3, at[ index + 1 ] );
2312             xy partial_6 = lerp( partial_1, partial_4, ratio );
2313             xy split_point_2 = lerp( partial_4, partial_5, at[ index + 1 ] );
2314             xy split_control_2 = lerp( partial_4, split_point_2, ratio );
2315             xy split_control_1 = lerp( partial_6, split_control_2, ratio );
2316             add_tessellation( split_point_1, split_control_1,
2317                               split_control_2, split_point_2,
2318                              angular, 20 );
2319             split_point_1 = split_point_2;
2320         }
2321     }
2322 
2323 
2324     // Convert the current path to a set of polylines.  This walks over the
2325     // complete set of subpaths in the current path (stored as sets of cubic
2326     // Beziers) and converts each Bezier curve segment to a polyline while
2327     // preserving information about where subpaths begin and end and whether
2328     // they are closed or open.  This replaces the previous polyline data.
2329     //
2330     void path_to_lines(bool stroking )
2331     {
2332         enum float tolerance = 0.125f;
2333         float ratio = tolerance / fmaxf( 0.5f * current.line_width, tolerance );
2334         float angular = stroking ? ( ratio - 2.0f ) * ratio * 2.0f + 1.0f : -1.0f;
2335         lines.points.clearContents();
2336         lines.subpaths.clearContents();
2337         size_t index = 0;
2338         size_t ending = 0;
2339         for ( size_t subpath = 0; subpath < path.subpaths.length; ++subpath )
2340         {
2341             ending += path.subpaths[ subpath ].count;
2342             size_t first = lines.points.length;
2343             xy point_1 = path.points[ index++ ];
2344             lines.points.pushBack( point_1 );
2345             for ( ; index < ending; index += 3 )
2346             {
2347                 xy control_1 = path.points[ index + 0 ];
2348                 xy control_2 = path.points[ index + 1 ];
2349                 xy point_2 = path.points[ index + 2 ];
2350                 add_bezier( point_1, control_1, control_2, point_2, angular );
2351                 point_1 = point_2;
2352             }
2353             subpath_data entry = subpath_data(
2354                 lines.points.length - first,
2355                 path.subpaths[ subpath ].closed );
2356                 lines.subpaths.pushBack( entry );
2357         }
2358     }
2359 
2360     // Add a text glyph directly to the polylines.  Given a glyph index, this
2361     // parses the data for that glyph directly from the TTF glyph data table and
2362     // immediately tessellates it to a set of a polyline subpaths which it adds
2363     // to any subpaths already present.  It uses the current transform matrix to
2364     // transform from the glyph's vertices in font units to the proper size and
2365     // position on the canvas.
2366     //
2367     void add_glyph(
2368                            int glyph,
2369                            float angular )
2370     {
2371         int loc_format = unsigned_16( current.face.data, current.face.head + 50 );
2372         int offset = current.face.glyf + ( loc_format ?
2373                                    signed_32( current.face.data, current.face.loca + glyph * 4 ) :
2374                                   unsigned_16( current.face.data, current.face.loca + glyph * 2 ) * 2 );
2375         int next = current.face.glyf + ( loc_format ?
2376                                  signed_32( current.face.data, current.face.loca + glyph * 4 + 4 ) :
2377                                 unsigned_16( current.face.data, current.face.loca + glyph * 2 + 2 ) * 2 );
2378         if ( offset == next )
2379             return;
2380         int contours = signed_16( current.face.data, offset );
2381         if ( contours < 0 )
2382         {
2383             offset += 10;
2384             for ( ; ; )
2385             {
2386                 int flags = unsigned_16( current.face.data, offset );
2387                 int component = unsigned_16( current.face.data, offset + 2 );
2388                 if ( !( flags & 2 ) )
2389                     return; // Matching points are not supported
2390                 float e = cast( float )( flags & 1 ?
2391                                                 signed_16( current.face.data, offset + 4 ) :
2392                                                signed_8( current.face.data, offset + 4 ) );
2393                 float f = cast( float )( flags & 1 ?
2394                                                 signed_16( current.face.data, offset + 6 ) :
2395                                                signed_8( current.face.data, offset + 5 ) );
2396                 offset += flags & 1 ? 8 : 6;
2397                 float a = flags & 200 ? cast( float )(
2398                                                              signed_16( current.face.data, offset ) ) / 16384.0f : 1.0f;
2399                 float b = flags & 128 ? cast( float )(
2400                                                              signed_16( current.face.data, offset + 2 ) ) / 16384.0f : 0.0f;
2401                 float c = flags & 128 ? cast( float )(
2402                                                              signed_16( current.face.data, offset + 4 ) ) / 16384.0f : 0.0f;
2403                 float d = flags & 8 ? a :
2404                 flags & 64 ? cast(float)(
2405                                                   signed_16( current.face.data, offset + 2 ) ) / 16384.0f :
2406                 flags & 128 ? cast(float)(
2407                                                    signed_16( current.face.data, offset + 6 ) ) / 16384.0f :
2408                 1.0f;
2409                 offset += flags & 8 ? 2 : flags & 64 ? 4 : flags & 128 ? 8 : 0;
2410                 affine_matrix saved_forward = current.forward;
2411                 affine_matrix saved_inverse = current.inverse;
2412                 transform( a, b, c, d, e, f );
2413                 add_glyph( component, angular );
2414                 current.forward = saved_forward;
2415                 current.inverse = saved_inverse;
2416                 if ( !( flags & 32 ) )
2417                     return;
2418             }
2419         }
2420         int hmetrics = unsigned_16( current.face.data, current.face.hhea + 34 );
2421         int left_side_bearing = glyph < hmetrics ?
2422             signed_16( current.face.data, current.face.hmtx + glyph * 4 + 2 ) :
2423         signed_16( current.face.data, current.face.hmtx + hmetrics * 2 + glyph * 2 );
2424         int x_min = signed_16( current.face.data, offset + 2 );
2425         int points = unsigned_16( current.face.data, offset + 8 + contours * 2 ) + 1;
2426         int instructions = unsigned_16( current.face.data, offset + 10 + contours * 2 );
2427         int flags_array = offset + 12 + contours * 2 + instructions;
2428         int flags_size = 0;
2429         int x_size = 0;
2430         for ( int index = 0; index < points; )
2431         {
2432             int flags = unsigned_8( current.face.data, flags_array + flags_size++ );
2433             int repeated = flags & 8 ?
2434                 unsigned_8( current.face.data, flags_array + flags_size++ ) + 1 : 1;
2435             x_size += repeated * ( flags & 2 ? 1 : flags & 16 ? 0 : 2 );
2436             index += repeated;
2437         }
2438         int x_array = flags_array + flags_size;
2439         int y_array = x_array + x_size;
2440         int x = left_side_bearing - x_min;
2441         int y = 0;
2442         int flags = 0;
2443         int repeated = 0;
2444         int index = 0;
2445         for ( int contour = 0; contour < contours; ++contour )
2446         {
2447             int beginning = index;
2448             int ending = unsigned_16( current.face.data, offset + 10 + contour * 2 );
2449             xy begin_point = xy( 0.0f, 0.0f );
2450             bool begin_on = false;
2451             xy end_point = xy( 0.0f, 0.0f );
2452             bool end_on = false;
2453             size_t first = lines.points.length;
2454             for ( ; index <= ending; ++index )
2455             {
2456                 if ( repeated )
2457                     --repeated;
2458                 else
2459                 {
2460                     flags = unsigned_8( current.face.data, flags_array++ );
2461                     if ( flags & 8 )
2462                         repeated = unsigned_8( current.face.data, flags_array++ );
2463                 }
2464                 if ( flags & 2 )
2465                     x += ( unsigned_8( current.face.data, x_array ) *
2466                            ( flags & 16 ? 1 : -1 ) );
2467                 else if ( !( flags & 16 ) )
2468                     x += signed_16( current.face.data, x_array );
2469                 if ( flags & 4 )
2470                     y += ( unsigned_8( current.face.data, y_array ) *
2471                            ( flags & 32 ? 1 : -1 ) );
2472                 else if ( !( flags & 32 ) )
2473                     y += signed_16( current.face.data, y_array );
2474                 x_array += flags & 2 ? 1 : flags & 16 ? 0 : 2;
2475                 y_array += flags & 4 ? 1 : flags & 32 ? 0 : 2;
2476                 xy point = forwardTransform( xy( cast(float)( x ),
2477                                          cast(float)( y ) ) );
2478                 int on_curve = flags & 1;
2479                 if ( index == beginning )
2480                 {
2481                     begin_point = point;
2482                     begin_on = on_curve != 0;
2483                     if ( on_curve )
2484                         lines.points.pushBack( point );
2485                 }
2486                 else
2487                 {
2488                     xy point_2 = on_curve ? point :
2489                     lerp( end_point, point, 0.5f );
2490                     if ( lines.points.length == first ||
2491                          ( end_on && on_curve ) )
2492                         lines.points.pushBack( point_2 );
2493                     else if ( !end_on || on_curve )
2494                     {
2495                         xy point_1 = lines.points[$-1];
2496                         xy control_1 = lerp( point_1, end_point, 2.0f / 3.0f );
2497                         xy control_2 = lerp( point_2, end_point, 2.0f / 3.0f );
2498                         add_bezier( point_1, control_1, control_2, point_2,
2499                                     angular );
2500                     }
2501                 }
2502                 end_point = point;
2503                 end_on = on_curve != 0;
2504             }
2505             if ( begin_on ^ end_on )
2506             {
2507                 xy point_1 = lines.points[$-1];
2508                 xy point_2 = lines.points[ first ];
2509                 xy control = end_on ? begin_point : end_point;
2510                 xy control_1 = lerp( point_1, control, 2.0f / 3.0f );
2511                 xy control_2 = lerp( point_2, control, 2.0f / 3.0f );
2512                 add_bezier( point_1, control_1, control_2, point_2, angular );
2513             }
2514             else if ( !begin_on && !end_on )
2515             {
2516                 xy point_1 = lines.points[$-1];
2517                 xy split = lerp( begin_point, end_point, 0.5f );
2518                 xy point_2 = lines.points[ first ];
2519                 xy left_1 = lerp( point_1, end_point, 2.0f / 3.0f );
2520                 xy left_2 = lerp( split, end_point, 2.0f / 3.0f );
2521                 xy right_1 = lerp( split, begin_point, 2.0f / 3.0f );
2522                 xy right_2 = lerp( point_2, begin_point, 2.0f / 3.0f );
2523                 add_bezier( point_1, left_1, left_2, split, angular );
2524                 add_bezier( split, right_1, right_2, point_2, angular );
2525             }
2526             lines.points.pushBack( lines.points[ first ] );
2527             subpath_data entry = subpath_data(lines.points.length - first, true);
2528             lines.subpaths.pushBack( entry );
2529         }
2530     }
2531 
2532     // Decode the next codepoint from a null-terminated UTF-8 string to its glyph
2533     // index within the font.  The index to the next codepoint in the string
2534     // is advanced accordingly.  It checks for valid UTF-8 encoding, but not
2535     // for valid unicode codepoints.  Where it finds an invalid encoding, it
2536     // decodes it as the Unicode replacement character (U+FFFD) and advances to
2537     // the invalid byte, per Unicode recommendation.  It also replaces low-ASCII
2538     // whitespace characters with regular spaces.  After decoding the codepoint,
2539     // it looks up the corresponding glyph index from the current font's character
2540     // map table, returning a glyph index of 0 for the .notdef character (i.e.,
2541     // "tofu") if the font lacks a glyph for that codepoint.
2542     //
2543     int character_to_glyph(const(char)* text, ref int index )
2544     {
2545         int bytes = ( ( text[ index ] & 0x80 ) == 0x00 ? 1 :
2546                       ( text[ index ] & 0xe0 ) == 0xc0 ? 2 :
2547                      ( text[ index ] & 0xf0 ) == 0xe0 ? 3 :
2548                      ( text[ index ] & 0xf8 ) == 0xf0 ? 4 :
2549                      0 );
2550         const int[5] masks = [ 0x0, 0x7f, 0x1f, 0x0f, 0x07 ];
2551         int codepoint = bytes ? text[ index ] & masks[ bytes ] : 0xfffd;
2552         ++index;
2553         while ( --bytes > 0 )
2554             if ( ( text[ index ] & 0xc0 ) == 0x80 )
2555                 codepoint = codepoint << 6 | ( text[ index++ ] & 0x3f );
2556             else
2557             {
2558                 codepoint = 0xfffd;
2559                 break;
2560             }
2561         if ( codepoint == '\t' || codepoint == '\v' || codepoint == '\f' ||
2562              codepoint == '\r' || codepoint == '\n' )
2563             codepoint = ' ';
2564         int tables = unsigned_16( current.face.data, current.face.cmap + 2 );
2565         int format_12 = 0;
2566         int format_4 = 0;
2567         int format_0 = 0;
2568         for ( int table = 0; table < tables; ++table )
2569         {
2570             int platform = unsigned_16( current.face.data, current.face.cmap + table * 8 + 4 );
2571             int encoding = unsigned_16( current.face.data, current.face.cmap + table * 8 + 6 );
2572             int offset = signed_32( current.face.data, current.face.cmap + table * 8 + 8 );
2573             int format = unsigned_16( current.face.data, current.face.cmap + offset );
2574             if ( platform == 3 && encoding == 10 && format == 12 )
2575                 format_12 = current.face.cmap + offset;
2576             else if ( platform == 3 && encoding == 1 && format == 4 )
2577                 format_4 = current.face.cmap + offset;
2578             else if ( format == 0 )
2579                 format_0 = current.face.cmap + offset;
2580         }
2581         if ( format_12 )
2582         {
2583             int groups = signed_32( current.face.data, format_12 + 12 );
2584             for ( int group = 0; group < groups; ++group )
2585             {
2586                 int start = signed_32( current.face.data, format_12 + 16 + group * 12 );
2587                 int end = signed_32( current.face.data, format_12 + 20 + group * 12 );
2588                 int glyph = signed_32( current.face.data, format_12 + 24 + group * 12 );
2589                 if ( start <= codepoint && codepoint <= end )
2590                     return codepoint - start + glyph;
2591             }
2592         }
2593         else if ( format_4 )
2594         {
2595             int segments = unsigned_16( current.face.data, format_4 + 6 );
2596             int end_array = format_4 + 14;
2597             int start_array = end_array + 2 + segments;
2598             int delta_array = start_array + segments;
2599             int range_array = delta_array + segments;
2600             for ( int segment = 0; segment < segments; segment += 2 )
2601             {
2602                 int start = unsigned_16( current.face.data, start_array + segment );
2603                 int end = unsigned_16( current.face.data, end_array + segment );
2604                 int delta = signed_16( current.face.data, delta_array + segment );
2605                 int range = unsigned_16( current.face.data, range_array + segment );
2606                 if ( start <= codepoint && codepoint <= end )
2607                     return range ?
2608                         unsigned_16( current.face.data, range_array + segment +
2609                                      ( codepoint - start ) * 2 + range ) :
2610                 ( codepoint + delta ) & 0xffff;
2611             }
2612         }
2613         else if ( format_0 && 0 <= codepoint && codepoint < 256 )
2614             return unsigned_8( current.face.data, format_0 + 6 + codepoint );
2615         return 0;
2616     }
2617 
2618     // Convert a text string to a set of polylines.  This works out the placement
2619     // of the text string relative to the anchor position.  Then it walks through
2620     // the string, sizing and placing each character by temporarily changing the
2621     // current transform matrix to map from font units to canvas pixel coordinates
2622     // before adding the glyph to the polylines.  This replaces the previous
2623     // polyline data.
2624     //
2625     void text_to_lines(const(char)* text, xy position, float maximum_width, bool stroking )
2626     {
2627         enum float tolerance = 0.125f;
2628         float ratio = tolerance / fmaxf( 0.5f * current.line_width, tolerance );
2629         float angular = stroking ? ( ratio - 2.0f ) * ratio * 2.0f + 1.0f : -1.0f;
2630         lines.points.clearContents();
2631         lines.subpaths.clearContents();
2632         if ( (current.face.data.length == 0) || !text || maximum_width <= 0.0f )
2633             return;
2634         float width = maximum_width == 1.0e30f && current.text_align == align_style.leftward ? 0.0f :
2635         measure_text( text );
2636         float reduction = maximum_width / fmaxf( maximum_width, width );
2637         if ( current.text_align == align_style.rightward )
2638             position.x -= width * reduction;
2639         else if ( current.text_align == align_style.center )
2640             position.x -= 0.5f * width * reduction;
2641         xy scaling = current.face.scale * xy( reduction, 1.0f );
2642         float units_per_em = cast(float)(
2643                                          unsigned_16( current.face.data, current.face.head + 18 ) );
2644         float ascender = cast(float)(
2645                                      signed_16( current.face.data, current.face.os_2 + 68 ) );
2646         float descender = cast(float)(
2647                                       signed_16( current.face.data, current.face.os_2 + 70 ) );
2648         float normalize = current.face.scale * units_per_em / ( ascender - descender );
2649         if ( current.text_baseline == baseline_style.top )
2650             position.y += ascender * normalize;
2651         else if ( current.text_baseline == baseline_style.middle )
2652             position.y += ( ascender + descender ) * 0.5f * normalize;
2653         else if ( current.text_baseline == baseline_style.bottom )
2654             position.y += descender * normalize;
2655         else if ( current.text_baseline == baseline_style.hanging )
2656             position.y += 0.6f * current.face.scale * units_per_em;
2657         affine_matrix saved_forward = current.forward;
2658         affine_matrix saved_inverse = current.inverse;
2659         int hmetrics = unsigned_16( current.face.data, current.face.hhea + 34 );
2660         int place = 0;
2661         for ( int index = 0; text[ index ]; )
2662         {
2663             int glyph = character_to_glyph( text, index );
2664             current.forward = saved_forward;
2665             transform( scaling.x, 0.0f, 0.0f, -scaling.y,
2666                        position.x + cast(float)( place ) * scaling.x,
2667                       position.y );
2668             add_glyph( glyph, angular );
2669             int entry = min_int( glyph, hmetrics - 1 );
2670             place += unsigned_16( current.face.data, current.face.hmtx + entry * 4 );
2671         }
2672         current.forward = saved_forward;
2673         current.inverse = saved_inverse;
2674     }    
2675 
2676 
2677     // Break the polylines into smaller pieces according to the dash settings.
2678     // This walks along the polyline subpaths and dash pattern together, emitting
2679     // new points via lerping where dash segments begin and end.  Each dash
2680     // segment becomes a new open subpath in the polyline.  Some care is to
2681     // taken to handle two special cases of closed subpaths.  First, those that
2682     // are completely within the first dash segment should be emitted as-is and
2683     // remain closed.  Secondly, those that start and end within a dash should
2684     // have the two dashes merged together so that the lines join.  This replaces
2685     // the previous polyline data.
2686     //
2687     void dash_lines()
2688     {
2689         if ( current.line_dash.length == 0 )
2690             return;
2691 
2692         assign_vec!xy(scratch.points, lines.points);
2693         lines.points.clearContents();
2694 
2695         assign_vec!subpath_data(scratch.subpaths, lines.subpaths);
2696         lines.subpaths.clearContents();
2697 
2698         float total = 0;
2699         foreach(ld; current.line_dash[])
2700         {
2701             total += ld;
2702         }
2703         float offset = fmodf( current.line_dash_offset, total );
2704         if ( offset < 0.0f )
2705             offset += total;
2706         size_t start = 0;
2707         while ( offset >= current.line_dash[ start ] )
2708         {
2709             offset -= current.line_dash[ start ];
2710             start = start + 1 < current.line_dash.length ? start + 1 : 0;
2711         }
2712         size_t ending = 0;
2713         for ( size_t subpath = 0; subpath < scratch.subpaths.length; ++subpath )
2714         {
2715             size_t index = ending;
2716             ending += scratch.subpaths[ subpath ].count;
2717             size_t first = lines.points.length;
2718             size_t segment = start;
2719             bool emit = ~start & 1;
2720             size_t merge_point = lines.points.length;
2721             size_t merge_subpath = lines.subpaths.length;
2722             bool merge_emit = emit;
2723             float next = current.line_dash[ start ] - offset;
2724             for ( ; index + 1 < ending; ++index )
2725             {
2726                 xy from = scratch.points[ index ];
2727                 xy to = scratch.points[ index + 1 ];
2728                 if ( emit )
2729                     lines.points.pushBack( from );
2730                 float line = length( inverseTransform(to) - inverseTransform(from) );
2731                 while ( next < line )
2732                 {
2733                     lines.points.pushBack( lerp( from, to, next / line ) );
2734                     if ( emit )
2735                     {
2736                         subpath_data entry = {
2737                             lines.points.length - first, false };
2738                             lines.subpaths.pushBack( entry );
2739                             first = lines.points.length;
2740                     }
2741                     segment = segment + 1 < current.line_dash.length ? segment + 1 : 0;
2742                     emit = !emit;
2743                     next += current.line_dash[ segment ];
2744                 }
2745                 next -= line;
2746             }
2747             if ( emit )
2748             {
2749                 lines.points.pushBack( scratch.points[ index ] );
2750                 subpath_data entry = { lines.points.length - first, false };
2751                 lines.subpaths.pushBack( entry );
2752                 if ( scratch.subpaths[ subpath ].closed && merge_emit )
2753                 {
2754                     if ( lines.subpaths.length == merge_subpath + 1 )
2755                         lines.subpaths[$-1].closed = true;
2756                     else
2757                     {
2758                         size_t count = lines.subpaths[$-1].count;
2759                         rotateArray!xy(lines.points[], merge_point, lines.points.length - count, lines.points.length);
2760                         lines.subpaths[ merge_subpath ].count += count;
2761                         lines.subpaths.popBack();
2762                     }
2763                 }
2764             }
2765         }
2766     }
2767 
2768     // std::rotate translation
2769     // first  - the beginning of the original range
2770     // middle - the element that should appear at the beginning of the rotated range
2771     // last   - the end of the original range
2772     size_t rotateArray(T)(T[] arr, size_t first, size_t middle, size_t last)
2773     {
2774         if (first == middle)
2775             return last;
2776 
2777         if (middle == last)
2778             return first;
2779 
2780         size_t write = first;
2781         size_t next_read = first; // read position for when “read” hits “last”
2782 
2783         for (size_t read = middle; read != last; ++write, ++read)
2784         {
2785             if (write == next_read)
2786                 next_read = read; // track where “first” went
2787             T tmp = arr[write];
2788             arr[write] = arr[read];
2789             arr[read] = tmp;
2790         }
2791 
2792         // rotate the remaining sequence into place
2793         rotateArray(arr, write, next_read, last);
2794         return write;
2795     }
2796 
2797     /*
2798         Set filling or stroking to draw with an image pattern.
2799 
2800         Initially, pixels in the pattern correspond exactly to pixels 
2801         on the canvas, with the pattern starting in the upper left. 
2802         The pattern is affected by the current transform at the time 
2803         of drawing, and the pattern will be resampled as needed (with 
2804         the filtering always wrapping regardless of the repetition 
2805         setting). The pattern can be repeated either horizontally, 
2806         vertically, both, or neither, relative to the source image. If
2807         the pattern is not repeated, then beyond it will be considered
2808         transparent black. The pattern image, which should be in top 
2809         to bottom rows of contiguous pixels from left to right, is 
2810         copied and it is safe to change or destroy it after this call.
2811         The width and height must both be positive. If either are not,
2812         or the image pointer is null, this does nothing.
2813        
2814         Tip: to use a small piece of a larger image, reduce the width 
2815              and height, and offset the image pointer while keeping 
2816              the stride.
2817        
2818         Params:
2819             type        Whether to set the fill_style or stroke_style.
2820             image       Unpremultiplied sRGB RGBA8 image data.
2821             width       Width of the pattern image in pixels.
2822             height      Height of the pattern image in pixels.
2823             stride      Bytes between the start of each image row.
2824             repetition  repeat, repeat_x, repeat_y, or no_repeat.
2825     */       
2826     void setPattern(brush_type type,
2827                      const(ubyte)* image,
2828                      int width,
2829                      int height,
2830                      int stride,
2831                      repetition_style repetition) {
2832         if ( !image || width <= 0 || height <= 0 )
2833             return;
2834         paint_brush* brush = type == brush_type.fill_style ? &current.fill_brush : &current.stroke_brush;
2835         brush.type = paint_brush.types.pattern;
2836         brush.colors.clearContents();
2837         for ( int y = 0; y < height; ++y )
2838             for ( int x = 0; x < width; ++x )
2839             {
2840                 int index = y * stride + x * 4;
2841                 rgba color = rgba(image[ index + 0 ] / 255.0f, image[ index + 1 ] / 255.0f,
2842                                   image[ index + 2 ] / 255.0f, image[ index + 3 ] / 255.0f );
2843                 fromGammaSpace((&color)[0..1], options.gammaCurve);
2844                 brush.colors.pushBack( premultiplied( color ) );
2845             }
2846         brush.width = width;
2847         brush.height = height;
2848         brush.repetition = repetition;
2849     }
2850 
2851 
2852     // Trace along a series of points from a subpath in the scratch polylines
2853     // and add new points to the main polylines with the stroke expansion on
2854     // one side.  Calling this again with the ends reversed adds the other
2855     // half of the stroke.  If the original subpath was closed, each pass
2856     // adds a complete closed loop, with one adding the inside and one adding
2857     // the outside.  The two will wind in opposite directions.  If the original
2858     // subpath was open, each pass ends with one of the line caps and the two
2859     // passes together form a single closed loop.  In either case, this handles
2860     // adding line joins, including inner joins.  Care is taken to fill tight
2861     // inside turns correctly by adding additional windings.  See Figure 10 of
2862     // "Converting Stroked Primitives to Filled Primitives" by Diego Nehab, for
2863     // the inspiration for these extra windings.
2864     //
2865     void add_half_stroke(size_t beginning, size_t ending, bool closed )
2866     {
2867         float half = current.line_width * 0.5f;
2868         float ratio = current.miter_limit * current.miter_limit * half * half;
2869         xy in_direction = xy( 0.0f, 0.0f );
2870         float in_length = 0.0f;
2871         xy point = inverseTransform(scratch.points[ beginning ]);
2872         size_t finish = beginning;
2873         size_t index = beginning;
2874         do
2875         {
2876             xy next = inverseTransform(scratch.points[ index ]);
2877             xy out_direction = normalized( next - point );
2878             float out_length = length( next - point );
2879             enum float epsilon = 1.0e-4f;
2880             if ( in_length != 0.0f && out_length >= epsilon )
2881             {
2882                 if ( closed && finish == beginning )
2883                     finish = index;
2884                 xy side_in = point + half * perpendicular( in_direction );
2885                 xy side_out = point + half * perpendicular( out_direction );
2886                 float turn = dot( perpendicular( in_direction ), out_direction );
2887                 if ( fabsf( turn ) < epsilon )
2888                     turn = 0.0f;
2889                 xy offset = turn == 0.0f ? xy( 0.0f, 0.0f ) :
2890                 half / turn * ( out_direction - in_direction );
2891                 bool tight = ( dot( offset, in_direction ) < -in_length &&
2892                                dot( offset, out_direction ) > out_length );
2893                 if ( turn > 0.0f && tight )
2894                 {
2895                     swap_xy(side_in, side_out );
2896                     swap_xy( in_direction, out_direction );
2897                     // PERF
2898                     lines.points.pushBack( forwardTransform(side_out) );
2899                     lines.points.pushBack( forwardTransform(point) );
2900                     lines.points.pushBack( forwardTransform(side_in) );
2901                 }
2902                 if ( ( turn > 0.0f && !tight ) ||
2903                      ( turn != 0.0f && current.line_join == CanvasLineJoin.miter &&
2904                        dot( offset, offset ) <= ratio ) )
2905                     lines.points.pushBack( forwardTransform( point + offset ) );
2906                 else if ( current.line_join == CanvasLineJoin.round )
2907                 {
2908                     float cosine = dot( in_direction, out_direction );
2909                     float angle = acosf(fminf( fmaxf( cosine, -1.0f ), 1.0f ) );
2910                     float alpha = 4.0f / 3.0f * tanf( 0.25f * angle );
2911                     lines.points.pushBack( forwardTransform(side_in ));
2912                     add_bezier(
2913                                forwardTransform(side_in),
2914                                forwardTransform(( side_in + alpha * half * in_direction )),
2915                                forwardTransform(( side_out - alpha * half * out_direction )),
2916                                forwardTransform(side_out),
2917                                -1.0f );
2918                 }
2919                 else
2920                 {
2921                     lines.points.pushBack( forwardTransform(side_in ));
2922                     lines.points.pushBack( forwardTransform(side_out ));
2923                 }
2924                 if ( turn > 0.0f && tight )
2925                 {
2926                     lines.points.pushBack( forwardTransform(side_out ));
2927                     lines.points.pushBack( forwardTransform(point ));
2928                     lines.points.pushBack( forwardTransform(side_in ));
2929                     swap_xy( in_direction, out_direction );
2930                 }
2931             }
2932             if ( out_length >= epsilon )
2933             {
2934                 in_direction = out_direction;
2935                 in_length = out_length;
2936                 point = next;
2937             }
2938             index = ( index == ending ? beginning :
2939                       ending > beginning ? index + 1 :
2940                      index - 1 );
2941         } while ( index != finish );
2942         if ( closed || in_length == 0.0f )
2943             return;
2944         xy ahead = half * in_direction;
2945         xy side = perpendicular( ahead );
2946         if ( current.line_cap == CanvasLineCap.butt )
2947         {
2948             lines.points.pushBack( forwardTransform(( point + side ) ));
2949             lines.points.pushBack( forwardTransform(( point - side ) ));
2950         }
2951         else if ( current.line_cap == CanvasLineCap.square )
2952         {
2953             lines.points.pushBack( forwardTransform(( point + ahead + side ) ));
2954             lines.points.pushBack( forwardTransform(( point + ahead - side ) ));
2955         }
2956         else if ( current.line_cap == CanvasLineCap.circle )
2957         {
2958             enum float alpha = 0.55228475f; // 4/3*tan(pi/8)
2959             lines.points.pushBack( forwardTransform(( point + side ) ));
2960             add_bezier( forwardTransform( point + side ),
2961                         forwardTransform( point + side + alpha * ahead ),
2962                        forwardTransform( point + ahead + alpha * side ),
2963                        forwardTransform( point + ahead ),
2964                        -1.0f );
2965             add_bezier( forwardTransform( point + ahead ),
2966                         forwardTransform( point + ahead - alpha * side ),
2967                        forwardTransform( point - side + alpha * ahead ),
2968                        forwardTransform( point - side ),
2969                        -1.0f );
2970         }
2971     }
2972 
2973     // Perform stroke expansion on the polylines.  After first breaking them up
2974     // according to the dash pattern (if any), it then moves the polyline data to
2975     // the scratch space.  Each subpath is then traced both forwards and backwards
2976     // to add the points for a half stroke, which together create the points for
2977     // one (if the original subpath was open) or two (if it was closed) new closed
2978     // subpaths which, when filled, will draw the stroked lines.  While the lower
2979     // level code this calls only adds the points of the half strokes, this
2980     // adds subpath information for the strokes.  This replaces the previous
2981     // polyline data.
2982     //
2983     void stroke_lines()
2984     {
2985         affine_matrix fwd = current.forward;
2986         if ( fwd.a * fwd.d - fwd.b * fwd.c == 0.0f )
2987             return;
2988         dash_lines();
2989         
2990         assign_vec!xy(scratch.points, lines.points);
2991         lines.points.clearContents();
2992 
2993         assign_vec!subpath_data(scratch.subpaths, lines.subpaths);
2994         lines.subpaths.clearContents();
2995 
2996         size_t ending = 0;
2997         for ( size_t subpath = 0; subpath < scratch.subpaths.length; ++subpath )
2998         {
2999             size_t beginning = ending;
3000             ending += scratch.subpaths[ subpath ].count;
3001             if ( ending - beginning < 2 )
3002                 continue;
3003             size_t first = lines.points.length;
3004             add_half_stroke( beginning, ending - 1,
3005                              scratch.subpaths[ subpath ].closed );
3006             if ( scratch.subpaths[ subpath ].closed )
3007             {
3008                 subpath_data entry = { lines.points.length - first, true };
3009                 lines.subpaths.pushBack( entry );
3010                 first = lines.points.length;
3011             }
3012             add_half_stroke( ending - 1, beginning,
3013                              scratch.subpaths[ subpath ].closed );
3014             subpath_data entry = { lines.points.length - first, true };
3015             lines.subpaths.pushBack( entry );
3016         }
3017     }
3018 
3019     // Scan-convert a single polyline segment.  This walks along the pixels that
3020     // the segment touches in left-to-right order, using signed trapezoidal area
3021     // to accumulate a list of changes in signed coverage at each visible pixel
3022     // when processing them from left to right.  Each run of horizontal pixels
3023     // ends with one final update to the right of the last pixel to bring the
3024     // total up to date.  Note that this does not clip to the screen boundary.
3025     //
3026     void add_runs(xy from, xy to )
3027     {
3028         enum float epsilon = 2.0e-5f;
3029         if ( fabsf( to.y - from.y ) < epsilon)
3030             return;
3031         float sign = to.y > from.y ? 1.0f : -1.0f;
3032         if ( from.x > to.x )
3033             swap_xy( from, to );
3034         xy now = from;
3035         xy pixel = xy( floorf( now.x ), floorf( now.y ) );
3036         xy corner = pixel + xy( 1.0f, to.y > from.y ? 1.0f : 0.0f );
3037         xy slope = xy( ( to.x - from.x ) / ( to.y - from.y ),
3038                        ( to.y - from.y ) / ( to.x - from.x ) );
3039         xy next_x = ( to.x - from.x < epsilon ) ? to :
3040         xy( corner.x, now.y + ( corner.x - now.x ) * slope.y );
3041         xy next_y = xy( now.x + ( corner.y - now.y ) * slope.x, corner.y );
3042         if ( ( from.y < to.y && to.y < next_y.y ) ||
3043              ( from.y > to.y && to.y > next_y.y ) )
3044             next_y = to;
3045         float y_step = to.y > from.y ? 1.0f : -1.0f;
3046         do
3047         {
3048             float carry = 0.0f;
3049             while ( next_x.x < next_y.x )
3050             {
3051                 float strip = fminf( fmaxf( ( next_x.y - now.y ) * y_step,
3052                                                   0.0f ), 1.0f );
3053                 float mid = ( next_x.x + now.x ) * 0.5f;
3054                 float area = ( mid - pixel.x ) * strip;
3055                 pixel_run piece = pixel_run(cast(ushort)pixel.x,
3056                                             cast(ushort)pixel.y,
3057                                             ( carry + strip - area ) * sign );
3058                 runs.pushBack( piece );
3059                 carry = area;
3060                 now = next_x;
3061                 next_x.x += 1.0f;
3062                 next_x.y = ( next_x.x - from.x ) * slope.y + from.y;
3063                 pixel.x += 1.0f;
3064             }
3065             float strip = fminf( fmaxf( ( next_y.y - now.y ) * y_step,
3066                                               0.0f ), 1.0f );
3067             float mid = ( next_y.x + now.x ) * 0.5f;
3068             float area = ( mid - pixel.x ) * strip;
3069             pixel_run piece_1 = pixel_run( cast(ushort)pixel.x, cast(ushort)pixel.y, ( carry + strip - area ) * sign );
3070             pixel_run piece_2 = pixel_run( cast(ushort)(pixel.x + 1.0f ),cast(ushort)pixel.y, area * sign );
3071             runs.pushBack( piece_1 );
3072             runs.pushBack( piece_2 );
3073             now = next_y;
3074             next_y.y += y_step;
3075             next_y.x = ( next_y.y - from.y ) * slope.x + from.x;
3076             pixel.y += y_step;
3077             if ( ( from.y < to.y && to.y < next_y.y ) ||
3078                  ( from.y > to.y && to.y > next_y.y ) )
3079                 next_y = to;
3080         } while ( now.y != to.y );
3081     }
3082 
3083     // Scan-convert the polylines to prepare them for antialiased rendering.
3084     // For each of the polyline loops, it first clips them to the screen.
3085     // See "Reentrant Polygon Clipping" by Sutherland and Hodgman for details.
3086     // Then it walks the polyline loop and scan-converts each line segment to
3087     // produce a list of changes in signed pixel coverage when processed in
3088     // left-to-right, top-to-bottom order.  The list of changes is then sorted
3089     // into that order, and multiple changes to the same pixel are coalesced
3090     // by summation.  The result is a sparse, run-length encoded description
3091     // of the coverage of each pixel to be drawn.
3092     //
3093     void lines_to_runs(xy offset, int padding )
3094     {
3095         runs.clearContents();
3096         float width = cast(float)( size_x + padding );
3097         float height = cast(float)( size_y + padding );
3098         size_t ending = 0;
3099         for ( size_t subpath = 0; subpath < lines.subpaths.length; ++subpath )
3100         {
3101             size_t beginning = ending;
3102             ending += lines.subpaths[ subpath ].count;
3103             scratch.points.clearContents();
3104             for ( size_t index = beginning; index < ending; ++index )
3105                 scratch.points.pushBack( offset + lines.points[ index ] );
3106             for ( int edge = 0; edge < 4; ++edge )
3107             {
3108                 xy normal = xy( edge == 0 ? 1.0f : edge == 2 ? -1.0f : 0.0f,
3109                                 edge == 1 ? 1.0f : edge == 3 ? -1.0f : 0.0f );
3110                 float place = edge == 2 ? width : edge == 3 ? height : 0.0f;
3111                 size_t first = scratch.points.length;
3112                 for ( size_t index = 0; index < first; ++index )
3113                 {
3114                     xy from = scratch.points[ ( index ? index : first ) - 1 ];
3115                     xy to = scratch.points[ index ];
3116                     float from_side = dot( from, normal ) + place;
3117                     float to_side = dot( to, normal ) + place;
3118                     if ( from_side * to_side < 0.0f )
3119                         scratch.points.pushBack( lerp( from, to,
3120                                                         from_side / ( from_side - to_side ) ) );
3121                     if ( to_side >= 0.0f )
3122                         scratch.points.pushBack( to );
3123                 }
3124 
3125                 scratch.points.removeAndShiftRestOfArray(0, first);
3126             }
3127             size_t last = scratch.points.length;
3128             for ( size_t index = 0; index < last; ++index )
3129             {
3130                 xy from = scratch.points[ ( index ? index : last ) - 1 ];
3131                 xy to = scratch.points[ index ];
3132                 add_runs( xy( fminf( fmaxf( from.x, 0.0f ), width ),
3133                               fminf( fmaxf( from.y, 0.0f ), height ) ),
3134                           xy( fminf( fmaxf( to.x, 0.0f ), width ),
3135                               fminf( fmaxf( to.y, 0.0f ), height ) ) );
3136             }
3137         }
3138         if ( runs.isEmpty)
3139             return;
3140 
3141         // copied here for the sake of being a delegate
3142         int comparePixelRuns(in pixel_run a, in pixel_run b )
3143         {
3144             if (a.y != b.y)
3145                 return a.y - b.y;
3146             if (a.x != b.x)
3147                 return a.x - b.x;
3148 
3149             float diff = fabsf(a.delta ) - fabsf( b.delta );
3150             if (diff < 0)
3151                 return -1;
3152             if (diff > 0)
3153                 return 1;
3154             return 0;
3155         }
3156 
3157         timSort!pixel_run(runs[], timsortBuf, &comparePixelRuns); 
3158 
3159         // PERF: why push the pixel runs with delta 0.0f or -0.0f? Sounds
3160         // like missed opportunity to sort and remove them later.
3161 
3162         size_t to = 0;
3163         for ( size_t from = 1; from < runs.length; ++from )
3164         {
3165             if ( runs[ from ].x == runs[ to ].x && runs[ from ].y == runs[ to ].y )
3166                 runs[ to ].delta += runs[ from ].delta;
3167             else if ( runs[ from ].delta != 0.0f )
3168                 runs[ ++to ] = runs[ from ];
3169         }
3170 
3171         to += 1;
3172         runs.resize(to);
3173     }
3174 
3175     import dplug.core.vec: Vec;
3176     private Vec!pixel_run timsortBuf; // used as temp space
3177 
3178     // Paint a pixel according to its point location and a paint style to produce
3179     // a premultiplied, linearized RGBA color.  This handles all supported paint
3180     // styles: solid colors, linear gradients, radial gradients, and patterns.
3181     // For gradients and patterns, it takes into account the current transform.
3182     // Patterns are resampled using a separable bicubic convolution filter,
3183     // with edges handled according to the wrap mode.  See "Cubic Convolution
3184     // Interpolation for Digital Image Processing" by Keys.  This filter is best
3185     // known for magnification, but also works well for antialiased minification,
3186     // since it's actually a Catmull-Rom spline approximation of Lanczos-2.
3187     //
3188     rgba paint_pixel(xy point, ref const(paint_brush) brush )
3189     {
3190         if ( brush.colors.isEmpty() )
3191             return rgba( 0.0f, 0.0f, 0.0f, 0.0f );
3192         if ( brush.type == paint_brush.types.color )
3193             return brush.colors[0];
3194         point = inverseTransform(point);
3195         affine_matrix inverse = current.inverse;
3196         if ( brush.type == paint_brush.types.pattern )
3197         {
3198             float width = cast(float)( brush.width );
3199             float height = cast(float)( brush.height );
3200             if ( ( ( brush.repetition & 2 ) &&
3201                    ( point.x < 0.0f || width <= point.x ) ) ||
3202                  ( ( brush.repetition & 1 ) &&
3203                    ( point.y < 0.0f || height <= point.y ) ) )
3204                 return rgba( 0.0f, 0.0f, 0.0f, 0.0f );
3205             float scale_x = fabsf( inverse.a ) + fabsf( inverse.c );
3206             float scale_y = fabsf( inverse.b ) + fabsf( inverse.d );
3207             scale_x = fmaxf( 1.0f, fminf( scale_x, width * 0.25f ) );
3208             scale_y = fmaxf( 1.0f, fminf( scale_y, height * 0.25f ) );
3209             float reciprocal_x = 1.0f / scale_x;
3210             float reciprocal_y = 1.0f / scale_y;
3211             point = point - xy( 0.5f, 0.5f );
3212             int left = cast(int)( ceilf( point.x - scale_x * 2.0f ) );
3213             int top = cast(int)( ceilf( point.y - scale_y * 2.0f ) );
3214             int right = cast(int)( ceilf( point.x + scale_x * 2.0f ) );
3215             int bottom = cast(int)( ceilf( point.y + scale_y * 2.0f ) );
3216             rgba total_color = rgba( 0.0f, 0.0f, 0.0f, 0.0f );
3217             float total_weight = 0.0f;
3218             for ( int pattern_y = top; pattern_y < bottom; ++pattern_y )
3219             {
3220                 float y = fabsf( reciprocal_y *
3221                                  ( cast(float)( pattern_y ) - point.y ) );
3222                 float weight_y = ( y < 1.0f ?
3223                                    (    1.5f * y - 2.5f ) * y          * y + 1.0f :
3224                                   ( ( -0.5f * y + 2.5f ) * y - 4.0f ) * y + 2.0f );
3225                 int wrapped_y = pattern_y % brush.height;
3226                 if ( wrapped_y < 0 )
3227                     wrapped_y += brush.height;
3228                 if ( &brush == &image_brush )
3229                     wrapped_y = min_int( max_int( pattern_y, 0 ),
3230                                           brush.height - 1 );
3231                 for ( int pattern_x = left; pattern_x < right; ++pattern_x )
3232                 {
3233                     float x = fabsf( reciprocal_x *
3234                                      ( cast(float)( pattern_x ) - point.x ) );
3235                     float weight_x = ( x < 1.0f ?
3236                                        (    1.5f * x - 2.5f ) * x          * x + 1.0f :
3237                                       ( ( -0.5f * x + 2.5f ) * x - 4.0f ) * x + 2.0f );
3238                     int wrapped_x = pattern_x % brush.width;
3239                     if ( wrapped_x < 0 )
3240                         wrapped_x += brush.width;
3241                     if ( &brush == &image_brush )
3242                         wrapped_x = min_int( max_int( pattern_x, 0 ),
3243                                               brush.width - 1 );
3244                     float weight = weight_x * weight_y;
3245                     size_t index = cast(size_t)(wrapped_y * brush.width + wrapped_x );
3246                     total_color = total_color + (weight * brush.colors[ index ]);
3247                     total_weight += weight;
3248                 }
3249             }
3250             return ( 1.0f / total_weight ) * total_color;
3251         }
3252         float offset;
3253         xy relative = point - brush.start;
3254         xy line = brush.end - brush.start;
3255         float gradient = dot( relative, line );
3256         float span = dot( line, line );
3257         if ( brush.type == paint_brush.types.linear )
3258         {
3259             if ( span == 0.0f )
3260                 return rgba( 0.0f, 0.0f, 0.0f, 0.0f );
3261             offset = gradient / span;
3262         }
3263         else
3264         {
3265             float initial = brush.start_radius;
3266             float change = brush.end_radius - initial;
3267             float a = span - change * change;
3268             float b = -2.0f * ( gradient + initial * change );
3269             float c = dot( relative, relative ) - initial * initial;
3270             float discriminant = b * b - 4.0f * a * c;
3271             if ( discriminant < 0.0f ||
3272                  ( span == 0.0f && change == 0.0f ) )
3273                 return rgba( 0.0f, 0.0f, 0.0f, 0.0f );
3274             float root = sqrtf( discriminant );
3275             float reciprocal = 1.0f / ( 2.0f * a );
3276             float offset_1 = ( -b - root ) * reciprocal;
3277             float offset_2 = ( -b + root ) * reciprocal;
3278             float radius_1 = initial + change * offset_1;
3279             float radius_2 = initial + change * offset_2;
3280             if ( radius_2 >= 0.0f )
3281                 offset = offset_2;
3282             else if ( radius_1 >= 0.0f )
3283                 offset = offset_1;
3284             else
3285                 return rgba( 0.0f, 0.0f, 0.0f, 0.0f );
3286         }
3287 
3288         // Finds the first element in stop that is greater than offset.
3289         size_t index = brush.stops.length;
3290         for (size_t i = 0; i < brush.stops.length; ++i)
3291         {
3292             if ( brush.stops[i] > offset)
3293             {
3294                 index = i;
3295                 break;
3296             }
3297         }
3298 
3299         if ( index == 0 )
3300             return premultiplied( brush.colors[0] );
3301         if ( index == brush.stops.length )
3302             return premultiplied( brush.colors[$-1] );
3303         float mix = ( ( offset - brush.stops[ index - 1 ] ) /
3304                       ( brush.stops[ index ] - brush.stops[ index - 1 ] ) );
3305         rgba delta = brush.colors[ index ] - brush.colors[ index - 1 ];
3306         return premultiplied( brush.colors[ index - 1 ] + mix * delta );
3307     }
3308 
3309     // Render the shadow of the polylines into the pixel buffer if needed.  After
3310     // computing the border as the maximum distance that one pixel can affect
3311     // another via the blur, it scan-converts the lines to runs with the shadow
3312     // offset and that extra amount of border padding.  Then it bounds the scan
3313     // converted shape, pads this with border, clips that to the extended canvas
3314     // size, and rasterizes to fill a working area with the alpha.  Next, a fast
3315     // approximation of a Gaussian blur is done using three passes of box blurs
3316     // each in the rows and columns.  Note that these box blurs have a small extra
3317     // weight on the tails to allow for fractional widths.  See "Theoretical
3318     // Foundations of Gaussian Convolution by Extended Box Filtering" by Gwosdek
3319     // et al. for details.  Finally, it colors the blurred alpha image with
3320     // the shadow color and blends this into the pixel buffer according to the
3321     // compositing settings and clip mask.  Note that it does not bother clearing
3322     // outside the area of the alpha image when the compositing settings require
3323     // clearing; that will be done on the subsequent main rendering pass.
3324     //
3325     void render_shadow(ref const(paint_brush) brush )
3326     {
3327         if ( current.shadow_color.a == 0.0f || ( current.shadow_blur == 0.0f &&
3328                                          current.shadow_offset_x == 0.0f &&
3329                                         current.shadow_offset_y == 0.0f ) )
3330             return;
3331         float sigma_squared = 0.25f * current.shadow_blur * current.shadow_blur;
3332         size_t radius = cast(size_t)(
3333                                               0.5f * sqrtf( 4.0f * sigma_squared + 1.0f ) - 0.5f );
3334         int border = 3 * ( cast(int)( radius ) + 1 );
3335         xy offset = xy( cast(float)( border ) + current.shadow_offset_x,
3336                         cast(float)( border ) + current.shadow_offset_y );
3337         lines_to_runs( offset, 2 * border );
3338         int left = size_x + 2 * border;
3339         int right = 0;
3340         int top = size_y + 2 * border;
3341         int bottom = 0;
3342         for ( size_t index = 0; index < runs.length; ++index )
3343         {
3344             left = min_int( left, cast(int)( runs[ index ].x ) );
3345             right = max_int( right, cast(int)( runs[ index ].x ) );
3346             top = min_int( top, cast(int)( runs[ index ].y ) );
3347             bottom = max_int( bottom, cast(int)( runs[ index ].y ) );
3348         }
3349         left = max_int( left - border, 0 );
3350         right = min_int( right + border, size_x + 2 * border ) + 1;
3351         top = max_int( top - border, 0 );
3352         bottom = min_int( bottom + border, size_y + 2 * border );
3353         size_t width = cast(size_t)( max_int( right - left, 0 ) );
3354         size_t height = cast(size_t)( max_int( bottom - top, 0 ) );
3355         size_t working = width * height;
3356         shadow.clearContents();
3357         shadow.resize( working + max_size_t( width, height ) );
3358         memset(shadow.ptr, 0, float.sizeof * shadow.length);
3359         enum float threshold = 1.0f / 8160.0f;
3360         {
3361             int x = -1;
3362             int y = -1;
3363             float sum = 0.0f;
3364             for ( size_t index = 0; index < runs.length; ++index )
3365             {
3366                 pixel_run next = runs[ index ];
3367                 float coverage = fminf( fabsf( sum ), 1.0f );
3368                 int to = next.y == y ? next.x : x + 1;
3369                 if ( coverage >= threshold )
3370                     for ( ; x < to; ++x )
3371                         shadow[ cast(size_t)( y - top ) * width +
3372                                 cast(size_t)( x - left ) ] = coverage *
3373                         paint_pixel( xy( cast(float)( x ) + 0.5f,
3374                                          cast(float)( y ) + 0.5f ) -
3375                                      offset, brush ).a;
3376                 if ( next.y != y )
3377                     sum = 0.0f;
3378                 x = next.x;
3379                 y = next.y;
3380                 sum += next.delta;
3381             }
3382         }
3383         float alpha = cast(float)( 2 * radius + 1 ) *
3384             ( cast(float)( radius * ( radius + 1 ) ) - sigma_squared ) /
3385             ( 2.0f * sigma_squared -
3386               cast(float)( 6 * ( radius + 1 ) * ( radius + 1 ) ) );
3387         float divisor = 2.0f * ( alpha + cast(float)( radius ) ) + 1.0f;
3388         float weight_1 = alpha / divisor;
3389         float weight_2 = ( 1.0f - alpha ) / divisor;
3390         for ( size_t y = 0; y < height; ++y )
3391             for ( int pass = 0; pass < 3; ++pass )
3392             {
3393                 for ( size_t x = 0; x < width; ++x )
3394                     shadow[ working + x ] = shadow[ y * width + x ];
3395                 float running = weight_1 * shadow[ working + radius + 1 ];
3396                 for ( size_t x = 0; x <= radius; ++x )
3397                     running += ( weight_1 + weight_2 ) * shadow[ working + x ];
3398                 shadow[ y * width ] = running;
3399                 for ( size_t x = 1; x < width; ++x )
3400                 {
3401                     if ( x >= radius + 1 )
3402                         running -= weight_2 * shadow[ working + x - radius - 1 ];
3403                     if ( x >= radius + 2 )
3404                         running -= weight_1 * shadow[ working + x - radius - 2 ];
3405                     if ( x + radius < width )
3406                         running += weight_2 * shadow[ working + x + radius ];
3407                     if ( x + radius + 1 < width )
3408                         running += weight_1 * shadow[ working + x + radius + 1 ];
3409                     shadow[ y * width + x ] = running;
3410                 }
3411             }
3412         for ( size_t x = 0; x < width; ++x )
3413             for ( int pass = 0; pass < 3; ++pass )
3414             {
3415                 for ( size_t y = 0; y < height; ++y )
3416                     shadow[ working + y ] = shadow[ y * width + x ];
3417                 float running = weight_1 * shadow[ working + radius + 1 ];
3418                 for ( size_t y = 0; y <= radius; ++y )
3419                     running += ( weight_1 + weight_2 ) * shadow[ working + y ];
3420                 shadow[ x ] = running;
3421                 for ( size_t y = 1; y < height; ++y )
3422                 {
3423                     if ( y >= radius + 1 )
3424                         running -= weight_2 * shadow[ working + y - radius - 1 ];
3425                     if ( y >= radius + 2 )
3426                         running -= weight_1 * shadow[ working + y - radius - 2 ];
3427                     if ( y + radius < height )
3428                         running += weight_2 * shadow[ working + y + radius ];
3429                     if ( y + radius + 1 < height )
3430                         running += weight_1 * shadow[ working + y + radius + 1 ];
3431                     shadow[ y * width + x ] = running;
3432                 }
3433             }
3434         int operation = current.op;
3435         int x = -1;
3436         int y = -1;
3437         float sum = 0.0f;
3438         for ( size_t index = 0; index < current.mask.length; ++index )
3439         {
3440             pixel_run next = current.mask[ index ];
3441             float visibility = fminf( fabsf( sum ), 1.0f );
3442             int to = min_int( next.y == y ? next.x : x + 1, right - border );
3443             if ( visibility >= threshold &&
3444                  top <= y + border && y + border < bottom ) {
3445 
3446                 int x_offset = pixelTypeSize(outBitmap.type()) * x;
3447 
3448                 // How many pixels to do at once?
3449                 int scanWidth = to - x;
3450 
3451                 // 1. Convert pixels [x..to] to rgbaf32
3452                 scanlinesConvert(outBitmap.type(), 
3453                                  cast(ubyte*)(outBitmap.scanptr(y)) + x_offset,
3454                                  outBitmap.pitchInBytes(),
3455                                  PixelType.rgbaf32,
3456                                  scanBuf.ptr,
3457                                  0,
3458                                  scanWidth,
3459                                  1,
3460                                  interType,
3461                                  interBuf.ptr);
3462 
3463                 // 2. Background is made delinearized then premultiplied
3464                 rgba* scan = cast(rgba*) scanBuf.ptr;
3465                 fromGammaSpace(scan[0..scanWidth], options.gammaCurve);
3466                 for(int n = 0; n < scanWidth; ++n)
3467                 {
3468                     scan[n] = premultiplied(scan[n]);
3469                 }
3470 
3471                 // 3. Render shadow
3472                 for(int n = 0; n < scanWidth; ++n)
3473                 {
3474                     rgba back = scan[n];
3475                     rgba fore = current.global_alpha *
3476                         shadow[
3477                             cast(size_t)( y + border - top ) * width +
3478                             cast(size_t)( x + n + border - left ) ] *
3479                         current.shadow_color;
3480                     float mix_fore = operation & 1 ? back.a : 0.0f;
3481                     if ( operation & 2 )
3482                         mix_fore = 1.0f - mix_fore;
3483                     float mix_back = operation & 4 ? fore.a : 0.0f;
3484                     if ( operation & 8 )
3485                         mix_back = 1.0f - mix_back;
3486                     rgba blend = mix_fore * fore + mix_back * back;
3487                     blend.a = fminf( blend.a, 1.0f );
3488                     rgba colout = visibility * blend 
3489                                 + ( 1.0f - visibility ) * back;
3490                     scan[n] = colout;
3491                 }
3492 
3493                 // 4. Unpremultiply and put back in gamma-space.
3494                 for(int n = 0; n < scanWidth; ++n)
3495                 {
3496                     scan[n] = unpremultiplied(scan[n]);
3497                 }
3498                 toGammaSpace(scan[0..scanWidth], options.gammaCurve);
3499 
3500                 // 5. Convert pixels [x..to] to rgbaf32
3501                 scanlinesConvert(PixelType.rgbaf32,
3502                                  scanBuf.ptr,
3503                                  0,
3504                                  outBitmap.type(), 
3505                                  cast(ubyte*)(outBitmap.scanptr(y)) + x_offset,
3506                                  outBitmap.pitchInBytes(),
3507 
3508                                  scanWidth,
3509                                  1,
3510                                  interType,
3511                                  interBuf.ptr);
3512 
3513             }
3514 
3515             if ( next.y != y )
3516                 sum = 0.0f;
3517             x = max_int( cast(int)( next.x ), left - border );
3518             y = next.y;
3519             sum += next.delta;
3520         }
3521     }
3522 
3523     // Render the polylines into the pixel buffer.  It scan-converts the lines
3524     // to runs which represent changes to the signed fractional coverage when
3525     // read from left-to-right, top-to-bottom.  It scans through these to
3526     // determine spans of pixels that need to be drawn, paints those pixels
3527     // according to the brush, and then blends them into the buffer according
3528     // to the current compositing settings.  This is slightly more complicated
3529     // because it interleaves this with a simultaneous scan through a similar
3530     // set of runs representing the current clip mask to determine which pixels
3531     // it can composite into.  Note that shadows are always drawn first.
3532     //
3533     void render_main(ref const(paint_brush) brush )
3534     {
3535         if ( ! current.forward.isInvertible)
3536             return;
3537 
3538         render_shadow( brush );
3539         lines_to_runs( xy( 0.0f, 0.0f ), 0 );
3540         int operation = current.op;
3541         int x = -1;
3542         int y = -1;
3543         float path_sum = 0.0f;
3544         float clip_sum = 0.0f;
3545         size_t path_index = 0;
3546         size_t clip_index = 0;
3547         while ( clip_index < current.mask.length )
3548         {
3549             bool which = ( path_index < runs.length) &&
3550                            (comparePixelRuns(runs[ path_index ], current.mask[ clip_index ] ) < 0);
3551             pixel_run next = which ? runs[ path_index ] : current.mask[ clip_index ];
3552             float coverage = fminf( fabsf( path_sum ), 1.0f );
3553             float visibility = fminf( fabsf( clip_sum ), 1.0f );
3554             int to = next.y == y ? next.x : x + 1;
3555             enum float threshold = 1.0f / 8160.0f;
3556             if ( ( coverage >= threshold || ~operation & 8 ) &&
3557                  visibility >= threshold ) {
3558 
3559                 int x_offset = pixelTypeSize(outBitmap.type()) * x;
3560 
3561                 // How many pixels to do at once?
3562                 int scanWidth = to - x;
3563 
3564                 // 1. Convert pixels [x..to] to rgbaf32
3565                 scanlinesConvert(outBitmap.type(), 
3566                                     cast(ubyte*)(outBitmap.scanptr(y)) + x_offset,
3567                                     outBitmap.pitchInBytes(),
3568                                     PixelType.rgbaf32,
3569                                     scanBuf.ptr,
3570                                     0,
3571                                     scanWidth,
3572                                     1,
3573                                     interType,
3574                                     interBuf.ptr);
3575 
3576                 // 2. Background is made delinearized then premultiplied
3577                 rgba* scan = cast(rgba*) scanBuf.ptr;
3578                 fromGammaSpace(scan[0..scanWidth], options.gammaCurve);
3579                 for(int n = 0; n < scanWidth; ++n)
3580                 {
3581                     scan[n] = premultiplied(scan[n]);
3582                 }
3583 
3584                 // 3. Render main
3585                 for(int n = 0; n < scanWidth; ++n)
3586                 {
3587                     rgba back = scan[n];
3588 
3589                     rgba fore = coverage * current.global_alpha *
3590                         paint_pixel( xy( cast(float)( x ) + 0.5f,
3591                                             cast(float)( y ) + 0.5f ),
3592                                         brush );
3593                     float mix_fore = operation & 1 ? back.a : 0.0f;
3594                     if ( operation & 2 )
3595                         mix_fore = 1.0f - mix_fore;
3596                     float mix_back = operation & 4 ? fore.a : 0.0f;
3597                     if ( operation & 8 )
3598                         mix_back = 1.0f - mix_back;
3599                     rgba blend = mix_fore * fore + mix_back * back;
3600                     blend.a = fminf( blend.a, 1.0f );
3601 
3602                     rgba colout = visibility * blend 
3603                         + ( 1.0f - visibility ) * back;
3604                     scan[n] = colout;
3605                 }
3606 
3607                 // 4. Unpremultiply and put back in gamma-space.
3608                 for(int n = 0; n < scanWidth; ++n)
3609                 {
3610                     scan[n] = unpremultiplied(scan[n]);
3611                 }
3612                 toGammaSpace(scan[0..scanWidth], options.gammaCurve);
3613 
3614                 // 5. Convert pixels [x..to] to rgbaf32
3615                 scanlinesConvert(PixelType.rgbaf32,
3616                                     scanBuf.ptr,
3617                                     0,
3618                                     outBitmap.type(), 
3619                                     cast(ubyte*)(outBitmap.scanptr(y)) + x_offset,
3620                                     outBitmap.pitchInBytes(),
3621 
3622                                     scanWidth,
3623                                     1,
3624                                     interType,
3625                                     interBuf.ptr);
3626             }
3627             x = next.x;
3628             if ( next.y != y )
3629             {
3630                 y = next.y;
3631                 path_sum = 0.0f;
3632                 clip_sum = 0.0f;
3633             }
3634             if ( which )
3635                 path_sum += runs[ path_index++ ].delta;
3636             else
3637                 clip_sum += current.mask[ clip_index++ ].delta;
3638         }
3639     }
3640 }
3641 
3642 // ======== IMPLEMENTATION ========
3643 //
3644 // The general internal data flow (albeit not control flow!) for rendering
3645 // a shape onto the canvas is as follows:
3646 //
3647 // 1. Construct a set of polybeziers representing the current path via the
3648 //      public path building API (move_to, line_to, bezier_curve_to, etc.).
3649 // 2. Adaptively tessellate the polybeziers into polylines (path_to_lines).
3650 // 3. Maybe break the polylines into shorter polylines according to the dash
3651 //      pattern (dash_lines).
3652 // 4. Maybe stroke expand the polylines into new polylines that can be filled
3653 //      to show the lines with width, joins, and caps (stroke_lines).
3654 // 5. Scan-convert the polylines into a sparse representation of fractional
3655 //      pixel coverage (lines_to_runs).
3656 // 6. Maybe paint the covered pixel span alphas "offscreen", blur, color,
3657 //      and blend them onto the canvas where not clipped (render_shadow).
3658 // 7. Paint the covered pixel spans and blend them onto the canvas where not
3659 //      clipped (render_main).
3660 
3661 
3662 
3663 // Helpers for TTF file parsing
3664 int unsigned_8(ref Vec!ubyte data, int index ) 
3665 {
3666     return data[ cast(size_t)index ]; 
3667 }
3668 
3669 int signed_8( ref Vec!ubyte data, int index ) 
3670 {
3671     size_t place = cast(size_t)( index );
3672     return cast(byte)(data[ place ]); 
3673 }
3674 
3675 int unsigned_16( ref Vec!ubyte data, int index ) {
3676     size_t place = cast(size_t)( index );
3677     return data[ place ] << 8 | data[ place + 1 ]; 
3678 }
3679 
3680 int signed_16( ref Vec!ubyte data, int index ) 
3681 {
3682     size_t place = cast(size_t)( index );
3683     return cast(short)( data[ place ] << 8 | data[ place + 1 ] ); 
3684 }
3685 
3686 int signed_32( ref Vec!ubyte data, int index ) 
3687 {
3688     size_t place = cast(size_t)( index );
3689     return ( data[ place + 0 ] << 24 | data[ place + 1 ] << 16 |
3690              data[ place + 2 ] <<  8 | data[ place + 3 ] <<  0 ); 
3691 }
3692 
3693 int comparePixelRuns(in pixel_run a, in pixel_run b )
3694 {
3695     if (a.y != b.y)
3696         return a.y - b.y;
3697     if (a.x != b.x)
3698         return a.x - b.x;
3699 
3700     float diff = fabsf(a.delta ) - fabsf( b.delta );
3701     if (diff < 0)
3702         return -1;
3703     if (diff > 0)
3704         return 1;
3705     return 0;
3706 }
3707 
3708 // Implementation details
3709 private
3710 {
3711     template isLikeRGBA8(T)
3712     {
3713         enum isLikeRGBA8 = 
3714               !is(T==RGBA8)
3715             && T.sizeof == 4 
3716             && __traits(hasMember, T, "r")
3717             && __traits(hasMember, T, "g")
3718             && __traits(hasMember, T, "b")
3719             && __traits(hasMember, T, "a");
3720     }
3721 
3722     float fabsf(float a)
3723     {
3724         return a >= 0 ? a : -a;
3725     }
3726     struct xy
3727     {
3728         pure nothrow @nogc @safe:
3729         float x, y; // nothing seems to rely on .init
3730 
3731         xy opBinary(string op)(float t) const if (op == "*")
3732         {
3733             xy r;
3734             r.x = x * t;
3735             r.y = y * t;
3736             return r;
3737         }
3738 
3739         xy opBinaryRight(string op)(float t) const if (op == "*")
3740         {
3741             xy r;
3742             r.x = x * t;
3743             r.y = y * t;
3744             return r;
3745         }
3746 
3747         xy opBinary(string op)(xy o) const if (op == "+")
3748         {
3749             xy r;
3750             r.x = x + o.x;
3751             r.y = y + o.y;
3752             return r;
3753         }
3754 
3755         xy opBinary(string op)(xy o) const if (op == "-")
3756         {
3757             xy r;
3758             r.x = x - o.x;
3759             r.y = y - o.y;
3760             return r;
3761         }
3762     }
3763 
3764     xy matrix_mul_vec(const(affine_matrix) left, xy right)
3765     {
3766         return xy( left.a * right.x + left.c * right.y + left.e,
3767                    left.b * right.x + left.d * right.y + left.f );
3768     }
3769 
3770     float dot(xy left, xy right ) 
3771     {
3772         return left.x * right.x + left.y * right.y; 
3773     }
3774 
3775     float length( xy that ) 
3776     {
3777         return sqrtf( dot( that, that ) ); 
3778     }
3779 
3780     float direction( xy that ) 
3781     {
3782         return atan2f( that.y, that.x ); 
3783     }
3784 
3785     xy normalized( xy that ) 
3786     {
3787         float len = length( that );
3788         float m = fmaxf(len, 1.0e-6f);
3789         return that * (1.0f / m); 
3790     }
3791 
3792     xy perpendicular( xy that ) 
3793     {
3794         return xy( -that.y, that.x ); 
3795     }
3796 
3797     xy lerp( xy from, xy to, float ratio ) 
3798     {
3799         return from + ( to - from ) * ratio; 
3800     }
3801 
3802     struct rgba
3803     {
3804         pure nothrow @nogc @safe:
3805         float r, g, b, a;
3806 
3807         rgba opBinary(string op)(float t) const if (op == "*")
3808         {
3809             rgba c;
3810             c.r = r * t;
3811             c.g = g * t;
3812             c.b = b * t;
3813             c.a = a * t;
3814             return c;
3815         }
3816 
3817         rgba opBinaryRight(string op)(float t) const if (op == "*")
3818         {
3819             rgba c;
3820             c.r = r * t;
3821             c.g = g * t;
3822             c.b = b * t;
3823             c.a = a * t;
3824             return c;
3825         }
3826 
3827         rgba opBinary(string op)(rgba o) const if (op == "+")
3828         {
3829             rgba c;
3830             c.r = r + o.r;
3831             c.g = g + o.g;
3832             c.b = b + o.b;
3833             c.a = a + o.a;
3834             return c;
3835         }
3836 
3837         rgba opBinary(string op)(rgba o) const if (op == "-")
3838         {
3839             rgba c;
3840             c.r = r - o.r;
3841             c.g = g - o.g;
3842             c.b = b - o.b;
3843             c.a = a - o.a;
3844             return c;
3845         }
3846     }
3847 
3848     void fromGammaSpace(rgba[] arr, CanvasGammaCurve gammaCurve)
3849     {
3850         // Convert sRGB 0 to 1 to linear space 0 to 1
3851         static rgba linearized( rgba col ) 
3852         {
3853             __m128 c = _mm_loadu_ps(cast(float*)&col);
3854             __m128 top = _mm_pow_ps( _mm_add_ps(c,_mm_set1_ps(0.055f)) * _mm_set1_ps(0.94786729857), _mm_set1_ps(2.4f));
3855             __m128 bottom = _mm_set1_ps(0.0773993808f) * c;
3856             __m128 mask = _mm_cmplt_ps(c, _mm_set1_ps(0.04045f));
3857             c = _mm_or_ps(_mm_and_ps(mask, bottom), _mm_andnot_ps(mask, top));
3858             c.ptr[3] = col.a;
3859             _mm_storeu_ps(cast(float*)&col, c);
3860             return col;
3861         }
3862 
3863 
3864         final switch(gammaCurve)
3865         {
3866             case CanvasGammaCurve.linear:
3867                 foreach(ref col; arr)
3868                     col = linearized(col);
3869                 break;
3870             case CanvasGammaCurve.pow2:
3871                 foreach(ref col; arr) {
3872                     col.r = col.r * col.r;
3873                     col.g = col.g * col.g;
3874                     col.b = col.b * col.b;
3875                 }
3876                 break;
3877             case CanvasGammaCurve.none:
3878                 break;
3879         }
3880     }
3881 
3882     void toGammaSpace(rgba[] arr, CanvasGammaCurve gammaCurve)
3883     {
3884         static rgba delinearized(rgba col) 
3885         {
3886             __m128 c = _mm_loadu_ps(cast(float*)&col);
3887             __m128 top = _mm_pow_ps(c, _mm_set1_ps(0.41666666666f)) * _mm_set1_ps(1.055f) - _mm_set1_ps(0.055f);
3888             __m128 bottom = _mm_set1_ps(12.92f) * c;
3889             __m128 mask = _mm_cmplt_ps(c, _mm_set1_ps(0.0031308f));
3890             c = _mm_or_ps(_mm_and_ps(mask, bottom), _mm_andnot_ps(mask, top));
3891             c.ptr[3] = col.a;
3892             _mm_storeu_ps(cast(float*)&col, c);
3893             return col;
3894         }
3895 
3896         final switch(gammaCurve)
3897         {
3898             case CanvasGammaCurve.linear:
3899                 foreach(ref col; arr)
3900                     col = delinearized(col);
3901                 break;
3902             case CanvasGammaCurve.pow2:
3903                 foreach(ref col; arr) {
3904                     __m128 c = _mm_loadu_ps(cast(float*)&col);
3905                     c = _mm_sqrt_ps(c);
3906                     c.ptr[3] = col.a;
3907                     _mm_storeu_ps(cast(float*)&col, c);
3908                 }
3909                 break;
3910             case CanvasGammaCurve.none:
3911                 break;
3912         }
3913     }
3914 
3915     rgba premultiplied( rgba col ) 
3916     {
3917         return rgba(col.r*col.a, col.g*col.a, col.b*col.a, col.a); 
3918     }
3919 
3920     rgba unpremultiplied( rgba that ) 
3921     {
3922         enum float threshold = 1.0f / 8160.0f;
3923         return that.a < threshold ? rgba( 0.0f, 0.0f, 0.0f, 0.0f ) :
3924         rgba( 1.0f / that.a * that.r, 1.0f / that.a * that.g,
3925               1.0f / that.a * that.b, that.a ); 
3926     }
3927 
3928     rgba clamped(rgba that) pure
3929     {
3930         if (that.r < 0) that.r = 0;
3931         if (that.r > 1) that.r = 1;
3932         if (that.g < 0) that.g = 0;
3933         if (that.g > 1) that.g = 1;
3934         if (that.b < 0) that.b = 0;
3935         if (that.b > 1) that.b = 1;
3936         if (that.a < 0) that.a = 0;
3937         if (that.a > 1) that.a = 1;
3938         return that;
3939     }
3940 
3941     struct affine_matrix 
3942     { 
3943     pure nothrow @nogc @safe:
3944         float a, b, c, d, e, f; 
3945 
3946         enum identity = affine_matrix(1, 0, 0, 1, 0, 0);
3947 
3948         bool isInvertible() {
3949             return (a * d - b * c) != 0.0f;
3950         }
3951     }
3952 
3953     struct paint_brush 
3954     {
3955     nothrow @nogc:
3956         enum types 
3957         { 
3958             color, 
3959             linear, 
3960             radial, 
3961             pattern 
3962         }
3963         types type;
3964 
3965         @disable this(this); // copying Vec doesn't work like in C++!
3966 
3967         Vec!rgba colors;
3968         Vec!float stops;
3969         xy start = xy(0.0f, 0.0f);
3970         xy end = xy(0.0f, 0.0f);
3971         float start_radius, end_radius;
3972         int width, height; 
3973         repetition_style repetition;
3974 
3975         void opAssign(ref const(paint_brush) other)
3976         {
3977             this.type = other.type;
3978             colors.clearContents();
3979 	    Vec!rgba* pcol = cast(Vec!rgba*)&other.colors; // const_cast
3980             colors.pushBack(*pcol);
3981             stops.clearContents();
3982 	    Vec!float* pstops = cast(Vec!float*)&other.stops;
3983             stops.pushBack(*pstops);
3984             this.start = other.start;
3985             this.end = other.end;
3986             this.start_radius = other.start_radius;
3987             this.end_radius = other.end_radius;
3988             this.width = other.width;
3989             this.height = other.height;
3990             this.repetition = other.repetition;
3991         }
3992 
3993     }
3994 
3995     void swap_scalar(T)(ref T a, ref T b)
3996     {
3997         T c = a;
3998         a = b;
3999         b = c;
4000     }
4001 
4002     alias swap_xy = swap_scalar!xy;
4003 
4004     // a = b;
4005     void assign_vec(T)(ref Vec!T a, ref const(Vec!T) b)
4006     {
4007         a.clearContents();
4008 	// Useful to be compatible as far as LDC 1.20
4009 	Vec!T* p = cast(Vec!T*)&b; // const_cast
4010         a.pushBack(*p);
4011     }
4012 
4013     void swap_brush(ref paint_brush a, 
4014                     ref paint_brush b, 
4015                     ref paint_brush tmp)
4016     {
4017         tmp = a;
4018         a = b;
4019         b = tmp;
4020     }
4021 
4022     struct font_face 
4023     { 
4024     nothrow @nogc:
4025         @disable this(this);
4026         Vec!ubyte data;
4027         int cmap, glyf, head, hhea, hmtx, loca, maxp, os_2;
4028         float scale;
4029 
4030         void opAssign(ref const(font_face) other)
4031         {
4032             data.clearContents();
4033 	    Vec!ubyte* p = cast(Vec!ubyte*)&other.data;
4034             data.pushBack(*p);
4035             this.cmap = other.cmap;
4036             this.glyf = other.glyf;
4037             this.head = other.head;
4038             this.hhea = other.hhea;
4039             this.hmtx = other.hmtx;
4040             this.loca = other.loca;
4041             this.maxp = other.maxp;
4042             this.os_2 = other.os_2;
4043             this.scale = other.scale;
4044         }
4045     }
4046 
4047     struct subpath_data 
4048     { 
4049         size_t count; 
4050         bool closed; 
4051     }
4052 
4053     struct bezier_path 
4054     { 
4055         @disable this(this); // copying Vec doesn't work like in C++!
4056         Vec!xy points;
4057         Vec!subpath_data subpaths; 
4058     }
4059 
4060     struct line_path 
4061     { 
4062         @disable this(this); // copying Vec doesn't work like in C++!
4063         Vec!xy points;
4064         Vec!subpath_data subpaths;
4065     }
4066 
4067     struct pixel_run 
4068     { 
4069         ushort x, y; 
4070         float delta; 
4071     }
4072 
4073     float fminf(float a, float b) pure
4074     {
4075         return a < b ? a : b;
4076     }
4077 
4078     float fmaxf(float a, float b) pure
4079     {
4080         return a > b ? a : b;
4081     }
4082 
4083     int min_int(int a, int b) pure
4084     {
4085         return a < b ? a : b;
4086     }
4087 
4088     int max_int(int a, int b) pure
4089     {
4090         return a > b ? a : b;
4091     }
4092 
4093     size_t max_size_t(size_t a, size_t b) pure
4094     {
4095         return a > b ? a : b;
4096     }
4097 
4098     // FUTURE: move into dplug.core
4099     void vec_insert(T)(ref Vec!T v, size_t index, T value)
4100     {
4101         v.pushBack(T.init);
4102         size_t last = v.length - 1;
4103         for (int i = last; i > index; --i)
4104         {
4105             v[i] = v[i-1];
4106         }
4107         v[index] = value;
4108     }
4109 }
4110 
4111 // .init must be valid and survive .destroyNoGC
4112 unittest
4113 {
4114     Canvasity c;
4115     destroyNoGC(c);
4116     destroyNoGC(c);
4117 }