1 /**
2  * Copyright: Mike Wey 2011
3  * License:   zlib (See accompanying LICENSE file)
4  * Authors:   Mike Wey
5  */
6 
7 module dmagick.DrawingContext;
8 
9 import std.algorithm;
10 import std.array;
11 import std.conv;
12 import std.file;
13 import std..string;
14 
15 import dmagick.Color;
16 import dmagick.Exception;
17 import dmagick.Geometry;
18 import dmagick.Image;
19 import dmagick.Options;
20 import dmagick.Utils;
21 
22 import dmagick.c.composite;
23 import dmagick.c.draw;
24 import dmagick.c.geometry;
25 import dmagick.c.type;
26 
27 /// See_Also: $(CXREF draw, _AlignType)
28 public alias dmagick.c.draw.AlignType AlignType;
29 /// See_Also: $(CXREF draw, _ClipPathUnits)
30 public alias dmagick.c.draw.ClipPathUnits ClipPathUnits;
31 /// See_Also: $(CXREF draw, _DecorationType)
32 public alias dmagick.c.draw.DecorationType DecorationType;
33 /// See_Also: $(CXREF draw, _FillRule)
34 public alias dmagick.c.draw.FillRule FillRule;
35 /// See_Also: $(CXREF draw, _LineCap)
36 public alias dmagick.c.draw.LineCap LineCap;
37 /// See_Also: $(CXREF draw, _LineJoin)
38 public alias dmagick.c.draw.LineJoin LineJoin;
39 /// See_Also: $(CXREF draw, _PaintMethod)
40 public alias dmagick.c.draw.PaintMethod PaintMethod;
41 /// See_Also: $(CXREF type, _StretchType)
42 public alias dmagick.c.type.StretchType StretchType;
43 /// See_Also: $(CXREF type, _StyleType)
44 public alias dmagick.c.type.StyleType StyleType;
45 
46 alias ptrdiff_t ssize_t;
47 
48 /**
49  * Drawable provides a convenient interface for preparing vector,
50  * image, or text arguments.
51  */
52 class DrawingContext
53 {
54 	string operations;
55 
56 	/**
57 	 * Apply the drawing context to the image.
58 	 */
59 	void draw(Image image)
60 	{
61 		copyString(image.options.drawInfo.primitive, operations);
62 		scope(exit) copyString(image.options.drawInfo.primitive, null);
63 
64 		DrawImage(image.imageRef, image.options.drawInfo);
65 
66 		DMagickException.throwException(&(image.imageRef.exception));
67 	}
68 
69 	/**
70 	 * Transforms the coordinate system by a 3x3 transformation matrix.
71 	 */
72 	void affine(AffineMatrix matrix)
73 	{
74 		operations ~= format(" affine %s,%s,%s,%s,%s,%s",
75 			matrix.sx, matrix.rx, matrix.ry, matrix.sy, matrix.tx, matrix.ty);
76 	}
77 
78 	/**
79 	 * Specify if the text and stroke should be antialiased.
80 	 */
81 	void antialias(bool antialias)
82 	{
83 		strokeAntialias = antialias;
84 		textAntialias = antialias;
85 	}
86 
87 	/**
88 	 * Draws an arc within a rectangle.
89 	 */
90 	void arc(size_t startX, size_t startY, size_t endX, size_t endY, double startDegrees, double endDegrees)
91 	{
92 		operations ~= format(" arc %s,%s %s,%s %s,%s",
93 			startX, startY, endX, endY, startDegrees, endDegrees);
94 	}
95 
96 	/**
97 	 * Draw a cubic Bezier curve.
98 	 * 
99 	 * The arguments are pairs of points. At least 4 pairs must be specified.
100 	 * Each point xn, yn on the curve is associated with a control point
101 	 * cxn, cyn. The first point, x1, y1, is the starting point. The last
102 	 * point, xn, yn, is the ending point. Other point/control point pairs
103 	 * specify intermediate points on a polybezier curve.
104 	 */
105 	void bezier(size_t x1, size_t y1, size_t cx1, size_t cy1,
106 		size_t cx2, size_t cy2, size_t x2, size_t y2,
107 		size_t[] points ...)
108 	in
109 	{
110 		assert ( points.length % 2 == 0,
111 			"bezier needs an even number of arguments, "~
112 			"each x coordinate needs a coresponding y coordinate." );
113 	}
114 	body
115 	{
116 		operations ~= format(" bezier %s,%s %s,%s %s,%s %s,%s",
117 			x1, y1, cx1, cy1, cx2, cy2, x2, y2);
118 
119 		for( int i = 0; i < points.length; i+=2 )
120 			operations ~= format(" %s,%s", points[i], points[i+1]);
121 	}
122 
123 	/**
124 	 * Set the image border color. The default is "#dfdfdf".
125 	 */
126 	void borderColor(const(Color) color)
127 	{
128 		operations ~= format(" border-color %s", color);
129 	}
130 
131 	/**
132 	 * Defines a clip-path. Within the delegate, call other drawing
133 	 * primitive methods (rectangle, polygon, text, etc.) to define the
134 	 * clip-path. The union of all the primitives (excluding the effects
135 	 * of rendering methods such as stroke_width, etc.) is the clip-path.
136 	 * 
137 	 * Params:
138 	 *     path = The delegate that defines the clip-path using
139 	 *            using the provided DrawingContext.
140 	 */
141 	void clipPath(void delegate(DrawingContext path) defineClipPath)
142 	{
143 		static size_t count;
144 
145 		DrawingContext path = new DrawingContext();
146 		defineClipPath(path);
147 
148 		operations ~= format(" push defs push clip-path path%s push graphic-context", count);
149 		operations ~= path.operations;
150 		operations ~= " pop graphic-context pop clip-path pop defs";
151 		operations ~= format(" clip-path url(#path%s)", count);
152 
153 		count++;
154 	}
155 
156 	/**
157 	 * Specify how to determine if a point on the image is inside
158 	 * clipping region.
159 	 * 
160 	 * See_Also: $(LINK2 http://www.w3.org/TR/SVG/painting.html#FillRuleProperty,
161 	 *     the 'fill-rule' property) in the Scalable Vector Graphics (SVG)
162 	 *     1.1 Specification.
163 	 */
164 	void clipRule(FillRule rule)
165 	{
166 		if ( rule == FillRule.UndefinedRule )
167 			throw new DrawException("Undefined Fill Rule");
168 
169 		operations ~= format(" clip-rule %s", to!(string)(rule)[0 .. $-4]);
170 	}
171 
172 	/**
173 	 * Defines the coordinate space within the clipping region.
174 	 * 
175 	 * See_Also: $(LINK2 http://www.w3.org/TR/SVG/masking.html#EstablishingANewClippingPath,
176 	 *     Establishing a New Clipping Path) in the
177 	 *     Scalable Vector Graphics (SVG) 1.1 Specification.
178 	 */
179 	void clipUnits(ClipPathUnits units)
180 	{
181 		if ( units == ClipPathUnits.UndefinedPathUnits )
182 			throw new DrawException("Undefined Path Unit");
183 
184 		operations ~= format( " clip-units %s", toLower(to!(string)(units)) );
185 	}
186 
187 	unittest
188 	{
189 		auto dc = new DrawingContext();
190 		dc.clipUnits(ClipPathUnits.UserSpace);
191 
192 		assert(dc.operations == " clip-units userspace");
193 	}
194 
195 	/**
196 	 * Draw a circle.
197 	 * 
198 	 * Params:
199 	 *     xOrigin    = The x coordinate for the center of the circle.
200 	 *     yOrigin    = The y coordinate for the center of the circle.
201 	 *     xPerimeter = The x coordinate for a point on the perimeter of
202 	 *                  the circle.
203 	 *     yPerimeter = The x coordinate for a point on the perimeter of
204 	 *                  the circle.
205 	 */
206 	void circle(size_t xOrigin, size_t yOrigin, size_t xPerimeter, size_t yPerimeter)
207 	{
208 		operations ~= format(" circle %s,%s %s,%s",
209 			xOrigin, yOrigin, xPerimeter, yPerimeter);
210 	}
211 
212 	///ditto
213 	void circle(size_t xOrigin, size_t yOrigin, size_t radius)
214 	{
215 		circle(xOrigin, yOrigin, xOrigin, yOrigin + radius);
216 	}
217 
218 	/**
219 	 * Set color in image according to the specified PaintMethod constant.
220 	 * If you use the PaintMethod.FillToBorderMethod, assign the border
221 	 * color with the DrawingContext.borderColor property.
222 	 */
223 	void color(size_t x, size_t y, PaintMethod method)
224 	{
225 		if ( method == PaintMethod.UndefinedMethod )
226 			throw new DrawException("Undefined Paint Method");
227 		
228 		operations ~= format(" color %s,%s %s", x, y, to!(string)(method)[0 .. $-6]);
229 	}
230 
231 	/**
232 	 * Composite filename/image with the receiver image.
233 	 * 
234 	 * Params:
235 	 *     xOffset     = The x-offset of the composited image,
236 	 *                   measured from the upper-left corner
237 	 *                   of the image.
238 	 *     yOffset     = The y-offset of the composited image,
239 	 *                   measured from the upper-left corner
240 	 *                   of the image.
241 	 *     width       = Scale the composite image to this size.
242 	 *                   If value is 0, the composite image is not scaled.
243 	 *     height      = Scale the composite image to this size.
244 	 *                   If value is 0, the composite image is not scaled.
245 	 *     filename    = Filename of the mage to use in the
246 	 *                   composite operation.
247 	 *     image       = Image to use in the composite operation.
248 	 *     compositeOp = The composite operation to use.
249 	 */
250 	void composite(
251 		ssize_t xOffset,
252 		ssize_t yOffset,
253 		size_t width,
254 		size_t height,
255 		string filename,
256 		CompositeOperator compositeOp = CompositeOperator.OverCompositeOp)
257 	{
258 		if ( compositeOp == CompositeOperator.UndefinedCompositeOp)
259 			throw new DrawException("Undefined Composite Operator");
260 
261 		operations  ~= format(" image %s %s,%s %s,%s '%s'",
262 			to!(string)(compositeOp)[0 .. $-11], xOffset, yOffset, width, height, filename);
263 	}
264 
265 	///ditto
266 	void composite(
267 		ssize_t xOffset,
268 		ssize_t yOffset,
269 		size_t width,
270 		size_t height,
271 		Image image,
272 		CompositeOperator compositeOp = CompositeOperator.OverCompositeOp)
273 	{
274 		if ( image.filename !is null && image.filename.exists && !image.changed )
275 		{
276 			composite(xOffset, yOffset, width, height, image.filename, compositeOp);
277 			return;
278 		}
279 
280 		string filename = saveTempFile(image);
281 
282 		composite(xOffset, yOffset, width, height, filename, compositeOp);
283 	}
284 
285 	/**
286 	 * Specify text decoration.
287 	 */
288 	void decorate(DecorationType decoration)
289 	{
290 		operations ~= " decorate ";
291 
292 		final switch ( decoration )
293 		{
294 			case DecorationType.NoDecoration:
295 				operations ~= "none";         break;
296 			case DecorationType.UnderlineDecoration:
297 				operations ~= "underline";    break;
298 			case DecorationType.OverlineDecoration:
299 				operations ~= "overline";     break;
300 			case DecorationType.LineThroughDecoration:
301 				operations ~= "line-through"; break;
302 
303 			case DecorationType.UndefinedDecoration:
304 				throw new DrawException("Undefined Decoration");
305 		}
306 	}
307 
308 	/**
309 	 * Draw an ellipse.
310 	 * 
311 	 * Params:
312 	 *     xOrigin      = The x coordinate of the ellipse.
313 	 *     yOrigin      = The y coordinate of the ellipse.
314 	 *     width        = The horizontal radii. 
315 	 *     height       = The vertical radii.
316 	 *     startDegrees = Where to start the ellipse.
317 	 *                    0 degrees is at 3 o'clock.
318 	 *     endDegrees   = Whare to end the ellipse.
319 	 */
320 	void ellipse(size_t xOrigin, size_t yOrigin, size_t width, size_t height, double startDegrees, double endDegrees)
321 	{
322 		operations ~= format(" ellipse %s,%s %s,%s %s,%s",
323 			xOrigin, yOrigin, width, height, startDegrees, endDegrees);
324 	}
325 
326 	/**
327 	 * Specify the font encoding.
328 	 * Note: This specifies the character repertory (i.e., charset),
329 	 * and not the text encoding method (e.g., UTF-8, UTF-16, etc.).
330 	 */
331 	void encoding(FontEncoding encoding)
332 	{
333 		switch ( encoding )
334 		{
335 			case FontEncoding.Latin1:
336 				operations ~= " encoding Latin-1";
337 				break;
338 			case FontEncoding.Latin2:
339 				operations ~= " encoding Latin-2";
340 				break;
341 			default:
342 				operations ~= format(" encoding %s", to!(string)(encoding));
343 				break;
344 		}
345 	}
346 
347 	unittest
348 	{
349 		auto dc = new DrawingContext();
350 		dc.encoding(FontEncoding.Latin1);
351 
352 		assert(dc.operations == " encoding Latin-1");
353 	}
354 
355 	/**
356 	 * Color to use when filling drawn objects.
357 	 * The default is "black".
358 	 */
359 	void fill(const(Color) fillColor)
360 	{
361 		operations ~= format(" fill %s", fillColor);
362 	}
363 
364 	///ditto
365 	alias fill fillColor;
366 
367 	/**
368 	 * Pattern to use when filling drawn objects.
369 	 * 
370 	 * Within the delegate, call other drawing primitive methods (rectangle,
371 	 * polygon, text, etc.) to define the pattern.
372 	 */
373 	void fill(size_t x, size_t y, size_t width, size_t height, void delegate(DrawingContext path) pattern)
374 	{
375 		operations ~= format(" fill url(#%s)", definePattern(x, y, width, height, pattern));
376 	}
377 	
378 	///ditto
379 	alias fill fillPattern;
380 
381 	/**
382 	 * The gradient to use when filling drawn objects.
383 	 */
384 	void fill(Gradient gradient)
385 	{
386 		operations ~= gradient.defineGradient();
387 
388 		operations ~= format(" fill url(#%s)", gradient.id());
389 	}
390 
391 	/**
392 	 * Specify the fill opacity.
393 	 * 
394 	 * Params:
395 	 *     opacity = A number between 0 and 1.
396 	 */
397 	void fillOpacity(double opacity)
398 	in
399 	{
400 		assert(opacity >= 0);
401 		assert(opacity <= 1);
402 	}
403 	body
404 	{
405 		operations ~= format(" fill-opacity %s", opacity);
406 	}
407 
408 	/**
409 	 * Specify how to determine if a point on the image is inside a shape.
410 	 * 
411 	 * See_Also: $(LINK2 http://www.w3.org/TR/SVG/painting.html#FillRuleProperty,
412 	 *     the 'fill-rule' property) in the Scalable Vector Graphics (SVG)
413 	 *     1.1 Specification.
414 	 */
415 	void fillRule(FillRule rule)
416 	{
417 		if ( rule == FillRule.UndefinedRule )
418 			throw new DrawException("Undefined Fill Rule");
419 
420 		operations ~= format(" fill-rule %s", to!(string)(rule)[0 .. $-4]);
421 	}
422 
423 	/**
424 	 * The _font name or filename.
425 	 * You can tag a _font to specify whether it is a Postscript,
426 	 * Truetype, or OPTION1 _font. For example, Arial.ttf is a
427 	 * Truetype _font, ps:helvetica is Postscript, and x:fixed is OPTION1.
428 	 * 
429 	 * The _font name can be a complete filename such as
430 	 * "/mnt/windows/windows/fonts/Arial.ttf". The _font name can
431 	 * also be a fully qualified X font name such as
432 	 * "-urw-times-medium-i-normal--0-0-0-0-p-0-iso8859-13".
433 	 */
434 	void font(string font)
435 	{
436 		operations ~= format(" font '%s'", font);
437 	}
438 
439 	/**
440 	 * Specify the font family, such as "arial" or "helvetica".
441 	 */
442 	void fontFamily(string family)
443 	{
444 		operations ~= format(" font-family '%s'", family);
445 	}	
446 
447 	/**
448 	 * Text rendering font point size
449 	 */
450 	void fontSize(double pointSize)
451 	{
452 		operations ~= format(" font-size %s", pointSize);
453 	}
454 
455 	/**
456 	 * Specify the spacing between text characters.
457 	 */
458 	void fontStretch(StretchType type)
459 	{
460 		if ( type == StretchType.UndefinedStretch )
461 			throw new DrawException("Undefined Stretch type");
462 
463 		operations ~= format(" font-stretch %s", to!(string)(type)[0 .. $-7]);
464 	}
465 
466 	/**
467 	 * Specify the font style, i.e. italic, oblique, or normal.
468 	 */
469 	void fontStyle(StyleType type)
470 	{
471 		if ( type == StyleType.UndefinedStyle )
472 			throw new DrawException("Undefined Style type");
473 
474 		operations ~= format(" font-style %s", to!(string)(type)[0 .. $-5]);
475 	}
476 
477 	/**
478 	 * Specify the font weight.
479 	 * 
480 	 * Eighter use the FontWeight enum or specify a number
481 	 * between 100 and 900.
482 	 */
483 	void fontWeight(size_t weight)
484 	{
485 		operations ~= format(" font-weight %s", weight);
486 	}
487 
488 	///ditto
489 	void fontWeight(FontWeight weight)
490 	{
491 		if ( weight == FontWeight.Any )
492 			operations ~= " font-weight all";
493 		else
494 			operations ~= format(" font-weight %s", weight);
495 	}
496 
497 	/**
498 	 * Specify how the text is positioned. The default is NorthWestGravity.
499 	 */
500 	void gravity(GravityType type)
501 	{
502 		if ( type == GravityType.UndefinedGravity )
503 			throw new DrawException("Undefined Gravity type");
504 
505 		operations ~= format(" gravity %s", to!(string)(type)[0 .. $-7]);
506 	}
507 
508 	/**
509 	 * Modify the spacing between lines when text has multiple lines.
510 	 * 
511 	 * If positive, inserts additional space between lines. If negative,
512 	 * removes space between lines. The amount of space inserted
513 	 * or removed depends on the font.
514 	 */
515 	void interlineSpacing(double spacing)
516 	{
517 		operations ~= format(" interline-spacing %s", spacing);
518 	}
519 
520 	/**
521 	 * Modify the spacing between words in text.
522 	 * 
523 	 * If positive, inserts additional space between words. If negative,
524 	 * removes space between words. The amount of space inserted
525 	 * or removed depends on the font.
526 	 */
527 	void interwordSpacing(double spacing)
528 	{
529 		operations ~= format(" interword-spacing %s", spacing);
530 	}
531 
532 	/**
533 	 * Modify the spacing between letters in text.
534 	 * 
535 	 * If positive, inserts additional space between letters. If negative,
536 	 * removes space between letters. The amount of space inserted or
537 	 * removed depends on the font but is usually measured in pixels. That
538 	 * is, the following call adds about 5 pixels between each letter.
539 	 */
540 	void kerning(double kerning)
541 	{
542 		operations ~= format(" kerning %s", kerning);
543 	}
544 
545 	/**
546 	 * Draw a line from start to end.
547 	 */
548 	void line(size_t xStart, size_t yStart, size_t xEnd, size_t yEnd)
549 	{
550 		operations ~= format(" line %s,%s %s,%s",
551 			xStart, yStart, xEnd, yEnd);
552 	}
553 
554 	/**
555 	 * Make the image transparent according to the specified
556 	 * PaintMethod constant.
557 	 * 
558 	 * If you use the PaintMethod.FillToBorderMethod, assign the border
559 	 * color with the DrawingContext.borderColor property.
560 	 */
561 	void matte(size_t x, size_t y, PaintMethod method)
562 	{
563 		if ( method == PaintMethod.UndefinedMethod )
564 			throw new DrawException("Undefined Paint Method");
565 		
566 		operations ~= format(" matte %s,%s %s", x, y, to!(string)(method)[0 .. $-6]);
567 	}
568 
569 	/**
570 	 * Specify the fill and stroke opacities.
571 	 * 
572 	 * Params:
573 	 *     opacity = A number between 0 and 1.
574 	 */
575 	void opacity(double opacity)
576 	in
577 	{
578 		assert(opacity >= 0);
579 		assert(opacity <= 1);
580 	}
581 	body
582 	{
583 		operations ~= format(" opacity %s", opacity);
584 	}
585 
586 	/**
587 	 * Draw using SVG-compatible path drawing commands.
588 	 * 
589 	 * See_Also: "$(LINK2 http://www.w3.org/TR/SVG/paths.html,
590 	 *     Paths)" in the Scalable Vector Graphics (SVG) 1.1 Specification. 
591 	 */
592 	void path(string svgPath)
593 	{
594 		operations ~= " path "~svgPath;
595 	}
596 
597 	/**
598 	 * Set the pixel at x,y to the fill color.
599 	 */
600 	void point(size_t x, size_t y)
601 	{
602 		operations ~= format(" point %s,%s", x,y);
603 	}
604 
605 	/**
606 	 * Draw a polygon.
607 	 * 
608 	 * The arguments are a sequence of 2 or more points. If the last
609 	 * point is not the same as the first, the polygon is closed by
610 	 * drawing a line from the last point to the first.
611 	 */
612 	void polygon(size_t[] points ...)
613 	in
614 	{
615 		assert ( points.length % 2 == 0,
616 			"polygon needs an even number of arguments, "~
617 			"each x coordinate needs a coresponding y coordinate." );
618 	}
619 	body
620 	{
621 		operations ~= " polygon";
622 
623 		for( int i = 0; i < points.length; i+=2 )
624 			operations ~= format(" %s,%s", points[i], points[i+1]);
625 	}
626 
627 	/**
628 	 * Draw a polyline. Unlike a polygon,
629 	 * a polyline is not automatically closed.
630 	 */
631 	void polyline(size_t[] points ...)
632 	in
633 	{
634 		assert ( points.length % 2 == 0,
635 			"polyline needs an even number of arguments, "~
636 			"each x coordinate needs a coresponding y coordinate." );
637 	}
638 	body
639 	{
640 		operations ~= " polyline";
641 
642 		for( int i = 0; i < points.length; i+=2 )
643 			operations ~= format(" %s,%s", points[i], points[i+1]);
644 	}
645 
646 	/**
647 	 * Restore the graphics context to the state it was in when
648 	 * push was called last.
649 	 */
650 	void pop()
651 	{
652 		operations ~= " pop graphic-context";
653 	}
654 
655 	/**
656 	 * Save the current state of the graphics context, including the
657 	 * attribute settings and the current set of primitives. Use the
658 	 * pop primitive to restore the state.
659 	 */
660 	void push()
661 	{
662 		operations ~= " push graphic-context";
663 	}
664 
665 	/**
666 	 * Draw a rectangle.
667 	 */
668 	void rectangle(size_t xStart, size_t yStart, size_t xEnd, size_t yEnd)
669 	{
670 		operations ~= format(" rectangle %s,%s %s,%s",
671 			xStart, yStart, xEnd, yEnd);
672 	}
673 
674 	/**
675 	 * Specify a rotation transformation to the coordinate space.
676 	 */
677 	void rotate(double angle)
678 	{
679 		operations ~= format(" rotate %s", angle);
680 	}
681 
682 	/**
683 	 * Draw a rectangle with rounded corners.
684 	 * 
685 	 * Params:
686 	 *     xStart       = The x coordinate for the upper left hand corner
687 	 *                    of the rectangle.
688 	 *     yStart       = The y coordinate for the upper left hand corner
689 	 *                    of the rectangle.
690 	 *     xEnd         = The x coordinate for the lower left hand corner
691 	 *                    of the rectangle.
692 	 *     yEnd         = The y coordinate for the lower left hand corner
693 	 *                    of the rectangle.
694 	 *     cornerWidth  = The width of the corner.
695 	 *     cornerHeight = The height of the corner.
696 	 */
697 	void roundRectangle(
698 		size_t xStart, size_t yStart,
699 		size_t xEnd, size_t yEnd,
700 		size_t cornerWidth, size_t cornerHeight)
701 	{
702 		operations ~= format(" roundRectangle %s,%s %s,%s %s,%s",
703 			xStart, yStart, xEnd, yEnd, cornerWidth, cornerHeight);
704 	}
705 
706 	/**
707 	 * Define a scale transformation to the coordinate space.
708 	 */
709 	void scale(double xScale, double yScale)
710 	{
711 		operations ~= format(" scale %s,%s", xScale, yScale);
712 	}
713 
714 	/**
715 	 * Define a skew transformation along the x-axis.
716 	 * 
717 	 * Params:
718 	 *     angle = The amount of skew, in degrees.
719 	 */
720 	void skewX(double angle)
721 	{
722 		operations ~= format(" skewX %s", angle);
723 	}
724 
725 	/**
726 	 * Define a skew transformation along the y-axis.
727 	 * 
728 	 * Params:
729 	 *     angle = The amount of skew, in degrees.
730 	 */
731 	void skewY(double angle)
732 	{
733 		operations ~= format(" skewY %s", angle);
734 	}
735 
736 	/**
737 	 * Color to use when drawing object outlines.
738 	 */
739 	void stroke(const(Color) strokeColor)
740 	{
741 		operations ~= format(" stroke %s", strokeColor);
742 	}
743 
744 	///ditto
745 	alias stroke strokeColor;
746 
747 	/**
748 	 * Pattern to use when filling drawn objects.
749 	 * 
750 	 * Within the delegate, call other drawing primitive methods (rectangle,
751 	 * polygon, text, etc.) to define the pattern.
752 	 */
753 	void stroke(size_t x, size_t y, size_t width, size_t height, void delegate(DrawingContext path) pattern)
754 	{
755 		operations ~= format(" stroke url(#%s)", definePattern(x, y, width, height, pattern));
756 	}
757 	
758 	///ditto
759 	alias stroke strokePattern;
760 
761 	/**
762 	 * The gradient to use when filling drawn objects.
763 	 */
764 	void stroke(Gradient gradient)
765 	{
766 		operations ~= gradient.defineGradient();
767 
768 		operations ~= format(" stroke url(#%s)", gradient.id());
769 	}
770 
771 	/**
772 	 * Specify if the stroke should be antialiased.
773 	 */
774 	void strokeAntialias(bool antialias)
775 	{
776 		operations ~= format(" stroke-antialias %s", (antialias ? 1 : 0));
777 	}
778 
779 	/**
780 	 * Describe a pattern of dashes to be used when stroking paths.
781 	 * The arguments are a list of pixel widths of alternating
782 	 * dashes and gaps.
783 	 * 
784 	 * The first argument is the width of the first dash. The second is
785 	 * the width of the gap following the first dash. The third argument
786 	 * is another dash width, followed by another gap width, etc.
787 	 */
788 	void strokeDashArray(const(double)[] dashArray ...)
789 	{
790 		if ( dashArray.length == 0 )
791 		{
792 			operations ~= " stroke-dasharray none";
793 		}
794 		else
795 		{
796 			operations ~= format(" stroke-dasharray %s",
797 				array(joiner(map!"to!(string)(a)"(dashArray), ",")) );
798 		}
799 	}
800 
801 	unittest
802 	{
803 		auto dc = new DrawingContext();
804 		dc.strokeDashArray(10, 10, 10);
805 
806 		assert(dc.operations == " stroke-dasharray 10,10,10");
807 	}
808 
809 	/**
810 	 * Specify the initial distance into the dash pattern.
811 	 */
812 	void strokeDashOffset(double offset)
813 	{
814 		operations ~= format(" stroke-dashoffset %s", offset);
815 	}
816 
817 	/**
818 	 * Specify how the line ends should be drawn.
819 	 */
820 	void strokeLineCap(LineCap cap)
821 	{
822 		if ( cap == LineCap.UndefinedCap )
823 			throw new DrawException("Undefined Line cap.");
824 
825 		operations ~= format(" stroke-linecap %s", to!(string)(cap)[0 .. $-3]);
826 	}
827 
828 	/**
829 	 * Specify how corners are drawn.
830 	 */
831 	void strokeLineJoin(LineJoin join)
832 	{
833 		if ( join == LineJoin.UndefinedJoin )
834 			throw new DrawException("Undefined Line join.");
835 
836 		operations ~= format(" stroke-linejoin %s", to!(string)(join)[0 .. $-4]);
837 	}
838 
839 	/**
840 	 * Specify a constraint on the length of the "miter"
841 	 * formed by two lines meeting at an angle. If the angle
842 	 * if very sharp, the miter could be very long relative
843 	 * to the line thickness. The miter _limit is a _limit on
844 	 * the ratio of the miter length to the line width.
845 	 * The default is 4.
846 	 */
847 	void strokeMiterLimit(size_t limit)
848 	{
849 		operations ~= format(" stroke-miterlimit %s", limit);
850 	}
851 
852 	/**
853 	 * Specify the stroke opacity.
854 	 * 
855 	 * Params:
856 	 *     opacity = A number between 0 and 1.
857 	 */
858 	void strokeOpacity(double opacity)
859 	in
860 	{
861 		assert(opacity >= 0);
862 		assert(opacity <= 1);
863 	}
864 	body
865 	{
866 		operations ~= format(" stroke-opacity %s", opacity);
867 	}
868 
869 	/**
870 	 * Specify the stroke width in pixels. The default is 1.
871 	 */
872 	void strokeWidth(double width)
873 	{
874 		operations ~= format(" stroke-width %s", width);
875 	}
876 
877 	/**
878 	 * Draw text at the location specified by (x,y). Use gravity to
879 	 * position text relative to (x, y). Specify the font appearance
880 	 * with the font, fontFamily, fontStretch, fontStyle, and fontWeight
881 	 * properties. Specify the text attributes with the textAlign,
882 	 * textAnchor, textAntialias, and textUndercolor properties.
883 	 * 
884 	 * To include a '%' in the text, use '%%'.
885 	 * 
886 	 * See_Also: Image.annotate for the image properties you can
887 	 *     include in the string.
888 	 */
889 	void text(size_t x, size_t y, string text)
890 	{
891 		operations ~= format(" text %s,%s %s", x, y, escapeText(text));
892 	}
893 
894 	/**
895 	 * Align text relative to the starting point.
896 	 */
897 	void textAlign(AlignType type)
898 	{
899 		if ( type == AlignType.UndefinedAlign )
900 			throw new DrawException("Undefined Align type.");
901 
902 		operations ~= format(" text-align %s", to!(string)(type)[0 .. $-5]);
903 	}
904 
905 	/**
906 	 * Specify if the text should be antialiased.
907 	 */
908 	void textAntialias(bool antialias)
909 	{
910 		operations ~= format(" text-antialias %s", (antialias ? 1 : 0));
911 	}
912 
913 	/**
914 	 * If set, causes the text to be drawn over a box of the specified color.
915 	 */
916 	void textUnderColor(Color color)
917 	{
918 		operations ~= format(" text-undercolor %s", color);
919 	}
920 
921 	///ditto
922 	alias textUnderColor boxColor;
923 
924 	/**
925 	 * Specify a translation operation on the coordinate space.
926 	 */
927 	void translate(size_t x, size_t y)
928 	{
929 		operations ~= format(" translate %s,%s", x, y);
930 	}
931 
932 	/**
933 	 * Generate to operations to define the pattern.
934 	 */
935 	private string definePattern(size_t x, size_t y, size_t width, size_t height, void delegate(DrawingContext path) pattern)
936 	{
937 		static size_t count;
938 		count++;
939 
940 		DrawingContext patt = new DrawingContext();
941 		pattern(patt);
942 
943 		operations ~= format(" push defs push pattern patt%s %s,%s %s,%s push graphic-context", count, x, y, width, height);
944 		operations ~= patt.operations;
945 		operations ~= " pop graphic-context pop pattern pop defs";
946 
947 		return format("patt%s", count);
948 	}
949 
950 	/**
951 	 * Escape the text so it can be added to the operations string.
952 	 */
953 	private static string escapeText(string text)
954 	{
955 		string escaped;
956 
957 		//reserve text.lengt + 10% to avoid realocating when appending.
958 		escaped.reserve(cast(size_t)(text.length * 0.1));
959 		escaped ~= '\"';
960 
961 		foreach ( c; text )
962 		{
963 			if ( c == '\"' || c == '\\' )
964 				escaped ~= '\\';
965 
966 			escaped ~= c;
967 		}
968 
969 		escaped ~= '\"';
970 
971 		return escaped;
972 	}
973 
974 	unittest
975 	{
976 		assert(escapeText(q{Hello world})       == q{"Hello world"});
977 		assert(escapeText(q{"Hello world"})     == q{"\"Hello world\""});
978 		assert(escapeText(q{"\"Hello world\""}) == q{"\"\\\"Hello world\\\"\""});
979 	}
980 
981 	/**
982 	 * Save the image in the temp directory and return the filename.
983 	 */
984 	private static string saveTempFile(Image image)
985 	{
986 		import std.datetime;
987 		import std.path;
988 		import std.process;
989 		import core.runtime;
990 
991 		string tempPath;
992 		string filename;
993 
994 		version(Windows)
995 		{
996 			tempPath = environment.get("TMP");
997 			if ( tempPath is null )
998 				tempPath = environment.get("TEMP");
999 			if ( tempPath is null )
1000 				tempPath = buildPath(environment.get("USERPROFILE"), "AppData/Local/Temp");
1001 			if ( tempPath is null || !tempPath.exists )
1002 				tempPath = buildPath(environment.get("WinDir"), "Temp");
1003 		}
1004 		else
1005 		{
1006 			import core.sys.posix.stdio;
1007 
1008 			tempPath = environment.get("TMPDIR");
1009 			if ( tempPath is null )
1010 				tempPath = P_tmpdir;
1011 		}
1012 
1013 		do
1014 		{
1015 			filename = buildPath(tempPath, "DMagick."~std.conv.to!(string)(Clock.currTime().stdTime));
1016 
1017 			if ( image.magick !is null && toLower(image.magick) != "canvas" )
1018 				filename ~= "."~image.magick;
1019 			else
1020 				filename ~= ".png";
1021 		}
1022 		while ( filename.exists );
1023 
1024 		image.write(filename);
1025 
1026 		return filename;
1027 	}
1028 
1029 	unittest
1030 	{
1031 		auto image = new Image(Geometry(200, 200), new Color("blue"));
1032 		string filename = saveTempFile(image);
1033 
1034 		assert(filename.exists);
1035 
1036 		remove(filename);
1037 	}
1038 }
1039 
1040 /**
1041  * This defines a Gradient used when drawing.
1042  * 
1043  * One thing to remember it that the gradient is always drawn from the
1044  * top left corner of the image. And is repeated if it's smaller then the
1045  * image height or width. This mean that the gradient you see in the object
1046  * you are filling does not determine the stating point of the gradient
1047  * but is filled the part of the gradient that would be there when starting
1048  * at the top left corner of the image.
1049  */
1050 struct Gradient
1051 {
1052 	private static size_t count;
1053 	private size_t currentCount;
1054 
1055 	//Is the id to use this gradient already set.
1056 	private bool isDefined = false;
1057 
1058 	size_t size;
1059 	GradientDirection direction;
1060 
1061 	Color startColor;
1062 	Color endColor;
1063 
1064 	/**
1065 	 * Define a linear gradient.
1066 	 * 
1067 	 * Params:
1068 	 *     startColor = The starting Color.
1069 	 *     endColor   = The end Color.
1070 	 *     size       = The height or with of the gradient.
1071 	 *     Direction  = Determines is the gradient fades from top to bottom
1072 	 *                  or from left to right.
1073 	 */
1074 	this(Color startColor, Color endColor, size_t size, GradientDirection direction = GradientDirection.Vertical)
1075 	{
1076 		currentCount = count++;
1077 
1078 		this.size = size;
1079 		this.direction = direction;
1080 		this.startColor = startColor;
1081 		this.endColor = endColor;
1082 	}
1083 
1084 	/**
1085 	 * Generate the string used to define this gradient.
1086 	 */
1087 	private string defineGradient()
1088 	{
1089 		if ( isDefined )
1090 			return "";
1091 
1092 		string operations = " push defs push defs push gradient";
1093 
1094 		if ( direction == GradientDirection.Vertical )
1095 		{
1096 			operations ~= format(" grad%s linear %s,%s %s,%s",
1097 					currentCount, 0, 0, 0, size);
1098 		}
1099 		else
1100 		{
1101 			operations ~= format(" grad%s linear %s,%s %s,%s",
1102 					currentCount, 0, 0, size, 0);
1103 		}
1104 
1105 		operations ~= format(" stop-color %s %s", startColor, 0);
1106 		operations ~= format(" stop-color %s %s", endColor, 1);
1107 
1108 		operations ~= " pop gradient pop defs";
1109 
1110 		return operations;
1111 	}
1112 
1113 	/**
1114 	 * If the gradient is defined, this id is neded to use it.
1115 	 */
1116 	private string id()
1117 	{
1118 		return format("grad%s", currentCount);
1119 	}
1120 
1121 }
1122 
1123 /**
1124  * This enumeration lists specific character repertories (i.e., charsets),
1125  * and not text encoding methods (e.g., UTF-8, UTF-16, etc.).
1126  */
1127 enum FontEncoding
1128 {
1129 	AdobeCustom,    ///
1130 	AdobeExpert,    ///ditto
1131 	AdobeStandard,  ///ditto
1132 	AppleRoman,     ///ditto
1133 	BIG5,           ///ditto
1134 	GB2312,         ///ditto
1135 	Johab,          ///ditto
1136 	Latin1,         ///ditto
1137 	Latin2,         ///ditto
1138 	None,           ///ditto
1139 	SJIScode,       ///ditto
1140 	Symbol,         ///ditto
1141 	Unicode,        ///ditto
1142 	Wansung,        ///ditto
1143 }
1144 
1145 /**
1146  * The font weight can be specified as one of 100, 200, 300, 400, 500,
1147  * 600, 700, 800, or 900, or one of the following constants.
1148  */
1149 enum FontWeight
1150 {
1151         Any,     /// No weight specified.
1152         Normal,  /// Normal weight, equivalent to 400.
1153         Bold,    /// Bold. equivalent to 700. 
1154         Bolder,  /// Increases weight by 100.
1155         Lighter, /// Decreases weight by 100.
1156 }
1157 
1158 /**
1159  * GradientDirection determines if the gradient fades from top to bottom
1160  * or from left to right.
1161  */
1162 enum GradientDirection
1163 {
1164 	Horizontal, /// Top to bottom.
1165 	Vertical    /// Left to right.
1166 }
1167 
1168 /+
1169  + ImageMagick's gradient implementation is a lot more limmiting then
1170  + it whoud seem. This was the first Gradient implementation based on:
1171  + http://www.imagemagick.org/script/magick-vector-graphics.php and
1172  + http://www.linux-nantes.org/~fmonnier/OCaml/MVG/
1173  + But a look at the source of DrawImage reveals that only simple
1174  + linear gradients are supported.
1175 
1176 /**
1177  * This defines a Gradient used when drawing.
1178  */
1179 struct Gradient
1180 {
1181 	private static size_t count;
1182 	private size_t currentCount;
1183 
1184 	//Is the id to use this gradient already set.
1185 	private bool isDefined = false;
1186 
1187 	GradientType  type;
1188 	//GradientUnits units;
1189 	double x1, y1, x2, y2, radius;
1190 	StopColor[] stopColors;
1191 
1192 	/**
1193 	 * Define a linear gradient.
1194 	 * 
1195 	 * x1, y1, x2 and y2 define a gradient vector for the linear gradient.
1196 	 * This gradient vector provides starting and ending points onto which
1197 	 * the gradient stops are mapped. The values of x1, y1, x2 and y2 can
1198 	 * be either numbers or percentages.
1199 	 */
1200 	static Gradient linear(double x1, double y1, double x2, double y2)
1201 	{
1202 		Gradient gradient;
1203 
1204 		gradient.type = GradientType.LinearGradient;
1205 
1206 		gradient.currentCount = count++;
1207 		gradient.x1 = x1;
1208 		gradient.y1 = y1;
1209 		gradient.x2 = x2;
1210 		gradient.y2 = y2;
1211 
1212 		return gradient;
1213 	}
1214 
1215 	/**
1216 	 * Define a radial gradient.
1217 	 * 
1218 	 * cx, cy and r define the largest (i.e., outermost) circle for the
1219 	 * radial gradient. The gradient will be drawn such that the 100%
1220 	 * gradient stop is mapped to the perimeter of this largest
1221 	 * (i.e., outermost) circle.
1222 	 * 
1223 	 * Params:
1224 	 *     xCenter = x coordinate for the center of the circle.
1225 	 *     yCenter = y coordinate for the center of the circle.
1226 	 *     xFocal  = x coordinate the focal point for the radial gradient.
1227 	 *               The gradient will be drawn such that the 0% gradient
1228 	 *               stop is mapped to (xFocal, yFocal).
1229 	 *     yFocal  = y coordinate the focal point
1230 	 *     radius  = The radius of the gradient. A value of zero will cause
1231 	 *               the area to be painted as a single color using the
1232 	 *               color and opacity of the last gradient stop.
1233 	 */
1234 	static Gradient radial(double xCenter, double yCenter, double xFocal, double yFocal, double radius)
1235 	{
1236 		Gradient gradient;
1237 
1238 		gradient.type = GradientType.RadialGradient;
1239 
1240 		gradient.currentCount = count++;
1241 		gradient.x1 = xCenter;
1242 		gradient.y1 = yCenter;
1243 		gradient.x2 = xFocal;
1244 		gradient.y2 = yFocal;
1245 		gradient.radius = radius;
1246 
1247 		return gradient;
1248 	}
1249 
1250 	/**
1251 	 * Define a radial gradient.
1252 	 * 
1253 	 * The same as above but with the focal point at
1254 	 * the center of the circle.
1255 	 */
1256 	static Gradient radial(double xCenter, double yCenter, double radius)
1257 	{
1258 		return radial(xCenter, yCenter, xCenter, yCenter, radius);
1259 	}
1260 
1261 	/**
1262 	 * Defines the coordinate system to use.
1263 	 */
1264 	Gradient gradientUnits(GradientUnits units)
1265 	{
1266 		this.units = units;
1267 
1268 		return this;
1269 	}
1270 
1271 	/**
1272 	 * Define the color to use, and there offsets in the gradient.
1273 	 * 
1274 	 * Params:
1275 	 *     color  = The color to use at this stop.
1276 	 *     offset = For linear gradients, the offset attribute represents
1277 	 *              a location along the gradient vector. For radial
1278 	 *              gradients, it represents a percentage distance
1279 	 *              from (fx,fy) to the edge of the outermost/largest circle.
1280 	 *              offset should bwe between 0 and 1.
1281 	 */
1282 	Gradient stopColor(Color color, double offset)
1283 	{
1284 		stopColors ~= StopColor(color, offset);
1285 
1286 		return this;
1287 	}
1288 
1289 	/**
1290 	 * Generate the string used to define this gradient.
1291 	 */
1292 	private string defineGradient()
1293 	{
1294 		if ( isDefined )
1295 			return "";
1296 
1297 		string operations = " push defs";
1298 
1299 		if ( type == GradientType.LinearGradient )
1300 		{
1301 			operations ~= format(" push gradient grad%s linear %s,%s %s,%s",
1302 				currentCount, x1, y1, x2, y2);
1303 		}
1304 		else
1305 		{
1306 			operations ~= format(" push gradient grad%s radial %s,%s %s,%s %s",
1307 				currentCount, x1, y1, x2, y2, radius);
1308 		}
1309 
1310 		if ( units != GradientUnits.Undefined )
1311 			operations ~= format(" gradient-units %s", units);
1312 		
1313 		foreach ( stop; stopColors )
1314 		{
1315 			operations ~= format(" stop-color %s %s", stop.color, stop.offset);
1316 		}
1317 
1318 		operations ~= " pop gradient pop defs";
1319 
1320 		return operations;
1321 	}
1322 
1323 	/**
1324 	 * If the gradient is defined, this id is neded to use it.
1325 	 */
1326 	private string id()
1327 	{
1328 		return format("grad%s", currentCount);
1329 	}
1330 
1331 	private struct StopColor
1332 	{
1333 		Color color;
1334 		double offset;
1335 	}
1336 }
1337 
1338 /**
1339  * Defines the coordinate system to use for Gradients.
1340  */
1341 enum GradientUnits : string
1342 {
1343 	/**
1344 	 * No coordinate systewm defined.
1345 	 */
1346 	Undefined         = "",
1347 
1348 	/**
1349 	 * The values supplied to Gradient represent values in the coordinate
1350 	 * system that results from taking the current user coordinate system
1351 	 * in place at the time when the gradient element is referenced.
1352 	 */
1353 	UserSpace         = "userSpace",
1354 
1355 	/**
1356 	 * The user coordinate system for the values supplied to Gradient is
1357 	 * established using the bounding box of the element to which the
1358 	 * gradient is applied.
1359 	 */
1360 	UserSpaceOnUse    = "userSpaceOnUse",
1361 
1362 	/**
1363 	 * The normal of the linear gradient is perpendicular to the gradient
1364 	 * vector in object bounding box space. When the object's bounding box
1365 	 * is not square, the gradient normal which is initially perpendicular
1366 	 * to the gradient vector within object bounding box space may render
1367 	 * non-perpendicular relative to the gradient vector in user space.
1368 	 * If the gradient vector is parallel to one of the axes of the bounding
1369 	 * box, the gradient normal will remain perpendicular.
1370 	 * This transformation is due to application of the non-uniform scaling
1371 	 * transformation from bounding box space to user space.
1372 	 */
1373 	ObjectBoundingBox = "objectBoundingBox",
1374 }
1375 +/