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 			import std.conv:to;
1016 			filename = buildPath(tempPath, "DMagick."~to!(string)(Clock.currTime().stdTime));
1017 
1018 			if ( image.magick !is null && toLower(image.magick) != "canvas" )
1019 				filename ~= "."~image.magick;
1020 			else
1021 				filename ~= ".png";
1022 		}
1023 		while ( filename.exists );
1024 
1025 		image.write(filename);
1026 
1027 		return filename;
1028 	}
1029 
1030 	unittest
1031 	{
1032 		auto image = new Image(Geometry(200, 200), new Color("blue"));
1033 		string filename = saveTempFile(image);
1034 
1035 		assert(filename.exists);
1036 
1037 		remove(filename);
1038 	}
1039 }
1040 
1041 /**
1042  * This defines a Gradient used when drawing.
1043  * 
1044  * One thing to remember it that the gradient is always drawn from the
1045  * top left corner of the image. And is repeated if it's smaller then the
1046  * image height or width. This mean that the gradient you see in the object
1047  * you are filling does not determine the stating point of the gradient
1048  * but is filled the part of the gradient that would be there when starting
1049  * at the top left corner of the image.
1050  */
1051 struct Gradient
1052 {
1053 	private static size_t count;
1054 	private size_t currentCount;
1055 
1056 	//Is the id to use this gradient already set.
1057 	private bool isDefined = false;
1058 
1059 	size_t size;
1060 	GradientDirection direction;
1061 
1062 	Color startColor;
1063 	Color endColor;
1064 
1065 	/**
1066 	 * Define a linear gradient.
1067 	 * 
1068 	 * Params:
1069 	 *     startColor = The starting Color.
1070 	 *     endColor   = The end Color.
1071 	 *     size       = The height or with of the gradient.
1072 	 *     Direction  = Determines is the gradient fades from top to bottom
1073 	 *                  or from left to right.
1074 	 */
1075 	this(Color startColor, Color endColor, size_t size, GradientDirection direction = GradientDirection.Vertical)
1076 	{
1077 		currentCount = count++;
1078 
1079 		this.size = size;
1080 		this.direction = direction;
1081 		this.startColor = startColor;
1082 		this.endColor = endColor;
1083 	}
1084 
1085 	/**
1086 	 * Generate the string used to define this gradient.
1087 	 */
1088 	private string defineGradient()
1089 	{
1090 		if ( isDefined )
1091 			return "";
1092 
1093 		string operations = " push defs push defs push gradient";
1094 
1095 		if ( direction == GradientDirection.Vertical )
1096 		{
1097 			operations ~= format(" grad%s linear %s,%s %s,%s",
1098 					currentCount, 0, 0, 0, size);
1099 		}
1100 		else
1101 		{
1102 			operations ~= format(" grad%s linear %s,%s %s,%s",
1103 					currentCount, 0, 0, size, 0);
1104 		}
1105 
1106 		operations ~= format(" stop-color %s %s", startColor, 0);
1107 		operations ~= format(" stop-color %s %s", endColor, 1);
1108 
1109 		operations ~= " pop gradient pop defs";
1110 
1111 		return operations;
1112 	}
1113 
1114 	/**
1115 	 * If the gradient is defined, this id is neded to use it.
1116 	 */
1117 	private string id()
1118 	{
1119 		return format("grad%s", currentCount);
1120 	}
1121 
1122 }
1123 
1124 /**
1125  * This enumeration lists specific character repertories (i.e., charsets),
1126  * and not text encoding methods (e.g., UTF-8, UTF-16, etc.).
1127  */
1128 enum FontEncoding
1129 {
1130 	AdobeCustom,    ///
1131 	AdobeExpert,    ///ditto
1132 	AdobeStandard,  ///ditto
1133 	AppleRoman,     ///ditto
1134 	BIG5,           ///ditto
1135 	GB2312,         ///ditto
1136 	Johab,          ///ditto
1137 	Latin1,         ///ditto
1138 	Latin2,         ///ditto
1139 	None,           ///ditto
1140 	SJIScode,       ///ditto
1141 	Symbol,         ///ditto
1142 	Unicode,        ///ditto
1143 	Wansung,        ///ditto
1144 }
1145 
1146 /**
1147  * The font weight can be specified as one of 100, 200, 300, 400, 500,
1148  * 600, 700, 800, or 900, or one of the following constants.
1149  */
1150 enum FontWeight
1151 {
1152         Any,     /// No weight specified.
1153         Normal,  /// Normal weight, equivalent to 400.
1154         Bold,    /// Bold. equivalent to 700. 
1155         Bolder,  /// Increases weight by 100.
1156         Lighter, /// Decreases weight by 100.
1157 }
1158 
1159 /**
1160  * GradientDirection determines if the gradient fades from top to bottom
1161  * or from left to right.
1162  */
1163 enum GradientDirection
1164 {
1165 	Horizontal, /// Top to bottom.
1166 	Vertical    /// Left to right.
1167 }
1168 
1169 /+
1170  + ImageMagick's gradient implementation is a lot more limmiting then
1171  + it whoud seem. This was the first Gradient implementation based on:
1172  + http://www.imagemagick.org/script/magick-vector-graphics.php and
1173  + http://www.linux-nantes.org/~fmonnier/OCaml/MVG/
1174  + But a look at the source of DrawImage reveals that only simple
1175  + linear gradients are supported.
1176 
1177 /**
1178  * This defines a Gradient used when drawing.
1179  */
1180 struct Gradient
1181 {
1182 	private static size_t count;
1183 	private size_t currentCount;
1184 
1185 	//Is the id to use this gradient already set.
1186 	private bool isDefined = false;
1187 
1188 	GradientType  type;
1189 	//GradientUnits units;
1190 	double x1, y1, x2, y2, radius;
1191 	StopColor[] stopColors;
1192 
1193 	/**
1194 	 * Define a linear gradient.
1195 	 * 
1196 	 * x1, y1, x2 and y2 define a gradient vector for the linear gradient.
1197 	 * This gradient vector provides starting and ending points onto which
1198 	 * the gradient stops are mapped. The values of x1, y1, x2 and y2 can
1199 	 * be either numbers or percentages.
1200 	 */
1201 	static Gradient linear(double x1, double y1, double x2, double y2)
1202 	{
1203 		Gradient gradient;
1204 
1205 		gradient.type = GradientType.LinearGradient;
1206 
1207 		gradient.currentCount = count++;
1208 		gradient.x1 = x1;
1209 		gradient.y1 = y1;
1210 		gradient.x2 = x2;
1211 		gradient.y2 = y2;
1212 
1213 		return gradient;
1214 	}
1215 
1216 	/**
1217 	 * Define a radial gradient.
1218 	 * 
1219 	 * cx, cy and r define the largest (i.e., outermost) circle for the
1220 	 * radial gradient. The gradient will be drawn such that the 100%
1221 	 * gradient stop is mapped to the perimeter of this largest
1222 	 * (i.e., outermost) circle.
1223 	 * 
1224 	 * Params:
1225 	 *     xCenter = x coordinate for the center of the circle.
1226 	 *     yCenter = y coordinate for the center of the circle.
1227 	 *     xFocal  = x coordinate the focal point for the radial gradient.
1228 	 *               The gradient will be drawn such that the 0% gradient
1229 	 *               stop is mapped to (xFocal, yFocal).
1230 	 *     yFocal  = y coordinate the focal point
1231 	 *     radius  = The radius of the gradient. A value of zero will cause
1232 	 *               the area to be painted as a single color using the
1233 	 *               color and opacity of the last gradient stop.
1234 	 */
1235 	static Gradient radial(double xCenter, double yCenter, double xFocal, double yFocal, double radius)
1236 	{
1237 		Gradient gradient;
1238 
1239 		gradient.type = GradientType.RadialGradient;
1240 
1241 		gradient.currentCount = count++;
1242 		gradient.x1 = xCenter;
1243 		gradient.y1 = yCenter;
1244 		gradient.x2 = xFocal;
1245 		gradient.y2 = yFocal;
1246 		gradient.radius = radius;
1247 
1248 		return gradient;
1249 	}
1250 
1251 	/**
1252 	 * Define a radial gradient.
1253 	 * 
1254 	 * The same as above but with the focal point at
1255 	 * the center of the circle.
1256 	 */
1257 	static Gradient radial(double xCenter, double yCenter, double radius)
1258 	{
1259 		return radial(xCenter, yCenter, xCenter, yCenter, radius);
1260 	}
1261 
1262 	/**
1263 	 * Defines the coordinate system to use.
1264 	 */
1265 	Gradient gradientUnits(GradientUnits units)
1266 	{
1267 		this.units = units;
1268 
1269 		return this;
1270 	}
1271 
1272 	/**
1273 	 * Define the color to use, and there offsets in the gradient.
1274 	 * 
1275 	 * Params:
1276 	 *     color  = The color to use at this stop.
1277 	 *     offset = For linear gradients, the offset attribute represents
1278 	 *              a location along the gradient vector. For radial
1279 	 *              gradients, it represents a percentage distance
1280 	 *              from (fx,fy) to the edge of the outermost/largest circle.
1281 	 *              offset should bwe between 0 and 1.
1282 	 */
1283 	Gradient stopColor(Color color, double offset)
1284 	{
1285 		stopColors ~= StopColor(color, offset);
1286 
1287 		return this;
1288 	}
1289 
1290 	/**
1291 	 * Generate the string used to define this gradient.
1292 	 */
1293 	private string defineGradient()
1294 	{
1295 		if ( isDefined )
1296 			return "";
1297 
1298 		string operations = " push defs";
1299 
1300 		if ( type == GradientType.LinearGradient )
1301 		{
1302 			operations ~= format(" push gradient grad%s linear %s,%s %s,%s",
1303 				currentCount, x1, y1, x2, y2);
1304 		}
1305 		else
1306 		{
1307 			operations ~= format(" push gradient grad%s radial %s,%s %s,%s %s",
1308 				currentCount, x1, y1, x2, y2, radius);
1309 		}
1310 
1311 		if ( units != GradientUnits.Undefined )
1312 			operations ~= format(" gradient-units %s", units);
1313 		
1314 		foreach ( stop; stopColors )
1315 		{
1316 			operations ~= format(" stop-color %s %s", stop.color, stop.offset);
1317 		}
1318 
1319 		operations ~= " pop gradient pop defs";
1320 
1321 		return operations;
1322 	}
1323 
1324 	/**
1325 	 * If the gradient is defined, this id is neded to use it.
1326 	 */
1327 	private string id()
1328 	{
1329 		return format("grad%s", currentCount);
1330 	}
1331 
1332 	private struct StopColor
1333 	{
1334 		Color color;
1335 		double offset;
1336 	}
1337 }
1338 
1339 /**
1340  * Defines the coordinate system to use for Gradients.
1341  */
1342 enum GradientUnits : string
1343 {
1344 	/**
1345 	 * No coordinate systewm defined.
1346 	 */
1347 	Undefined         = "",
1348 
1349 	/**
1350 	 * The values supplied to Gradient represent values in the coordinate
1351 	 * system that results from taking the current user coordinate system
1352 	 * in place at the time when the gradient element is referenced.
1353 	 */
1354 	UserSpace         = "userSpace",
1355 
1356 	/**
1357 	 * The user coordinate system for the values supplied to Gradient is
1358 	 * established using the bounding box of the element to which the
1359 	 * gradient is applied.
1360 	 */
1361 	UserSpaceOnUse    = "userSpaceOnUse",
1362 
1363 	/**
1364 	 * The normal of the linear gradient is perpendicular to the gradient
1365 	 * vector in object bounding box space. When the object's bounding box
1366 	 * is not square, the gradient normal which is initially perpendicular
1367 	 * to the gradient vector within object bounding box space may render
1368 	 * non-perpendicular relative to the gradient vector in user space.
1369 	 * If the gradient vector is parallel to one of the axes of the bounding
1370 	 * box, the gradient normal will remain perpendicular.
1371 	 * This transformation is due to application of the non-uniform scaling
1372 	 * transformation from bounding box space to user space.
1373 	 */
1374 	ObjectBoundingBox = "objectBoundingBox",
1375 }
1376 +/