1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.graphics.Path;
14 
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.internal.Compatibility;
17 import org.eclipse.swt.internal.cairo.Cairo : Cairo;
18 import org.eclipse.swt.internal.gtk.OS;
19 import org.eclipse.swt.graphics.Resource;
20 import org.eclipse.swt.graphics.Device;
21 import org.eclipse.swt.graphics.Font;
22 import org.eclipse.swt.graphics.GC;
23 import org.eclipse.swt.graphics.GCData;
24 import org.eclipse.swt.graphics.PathData;
25 import java.lang.all;
26 
27 version(Tango){
28     import tango.stdc..string;
29 } else {
30     import core.stdc..string;
31 }
32 
33 /**
34  * Instances of this class represent paths through the two-dimensional
35  * coordinate system. Paths do not have to be continuous, and can be
36  * described using lines, rectangles, arcs, cubic or quadratic bezier curves,
37  * glyphs, or other paths.
38  * <p>
39  * Application code must explicitly invoke the <code>Path.dispose()</code>
40  * method to release the operating system resources managed by each instance
41  * when those instances are no longer required.
42  * </p>
43  * <p>
44  * This class requires the operating system's advanced graphics subsystem
45  * which may not be available on some platforms.
46  * </p>
47  *
48  * @see <a href="http://www.eclipse.org/swt/snippets/#path">Path, Pattern snippets</a>
49  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: GraphicsExample</a>
50  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
51  * 
52  * @since 3.1
53  */
54 public class Path : Resource {
55     alias Resource.init_ init_;
56     /**
57      * the OS resource for the Path
58      * (Warning: This field is platform dependent)
59      * <p>
60      * <b>IMPORTANT:</b> This field is <em>not</em> part of the SWT
61      * public API. It is marked public only so that it can be shared
62      * within the packages provided by SWT. It is not available on all
63      * platforms and should never be accessed from application code.
64      * </p>
65      */
66     public org.eclipse.swt.internal.gtk.OS.cairo_t* handle;
67 
68     bool moved, closed = true;
69 
70 /**
71  * Constructs a new empty Path.
72  * <p>
73  * This operation requires the operating system's advanced
74  * graphics subsystem which may not be available on some
75  * platforms.
76  * </p>
77  *
78  * @param device the device on which to allocate the path
79  *
80  * @exception IllegalArgumentException <ul>
81  *    <li>ERROR_NULL_ARGUMENT - if the device is null and there is no current device</li>
82  * </ul>
83  * @exception SWTException <ul>
84  *    <li>ERROR_NO_GRAPHICS_LIBRARY - if advanced graphics are not available</li>
85  * </ul>
86  * @exception SWTError <ul>
87  *    <li>ERROR_NO_HANDLES if a handle for the path could not be obtained</li>
88  * </ul>
89  *
90  * @see #dispose()
91  */
92 public this (Device device) {
93     super(device);
94     this.device.checkCairo();
95     auto surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_ARGB32, 1, 1);
96     if (surface is null) SWT.error(SWT.ERROR_NO_HANDLES);
97     handle = Cairo.cairo_create(surface);
98     Cairo.cairo_surface_destroy(surface);
99     if (handle is null) SWT.error(SWT.ERROR_NO_HANDLES);
100     init_();
101 }
102 
103 /**
104  * Constructs a new Path that is a copy of <code>path</code>. If
105  * <code>flatness</code> is less than or equal to zero, an unflatten
106  * copy of the path is created. Otherwise, it specifies the maximum
107  * error between the path and its flatten copy. Smaller numbers give
108  * better approximation.
109  * <p>
110  * This operation requires the operating system's advanced
111  * graphics subsystem which may not be available on some
112  * platforms.
113  * </p>
114  * 
115  * @param device the device on which to allocate the path
116  * @param path the path to make a copy
117  * @param flatness the flatness value
118  * 
119  * @exception IllegalArgumentException <ul>
120  *    <li>ERROR_NULL_ARGUMENT - if the device is null and there is no current device</li>
121  *    <li>ERROR_NULL_ARGUMENT - if the path is null</li>
122  *    <li>ERROR_INVALID_ARGUMENT - if the path has been disposed</li>
123  * </ul>
124  * @exception SWTException <ul>
125  *    <li>ERROR_NO_GRAPHICS_LIBRARY - if advanced graphics are not available</li>
126  * </ul>
127  * @exception SWTError <ul>
128  *    <li>ERROR_NO_HANDLES if a handle for the path could not be obtained</li>
129  * </ul>
130  * 
131  * @see #dispose()
132  * @since 3.4
133  */
134 public this (Device device, Path path, float flatness) {
135     super(device);
136     if (path is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
137     if (path.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
138     auto surface = Cairo.cairo_image_surface_create(Cairo.CAIRO_FORMAT_ARGB32, 1, 1);
139     if (surface is null) SWT.error(SWT.ERROR_NO_HANDLES);
140     handle = Cairo.cairo_create(surface);
141     Cairo.cairo_surface_destroy(surface);
142     if (handle is null) SWT.error(SWT.ERROR_NO_HANDLES);
143     cairo_path_t* copy;
144     flatness = Math.max(0, flatness);
145     if (flatness is 0) {
146         copy = Cairo.cairo_copy_path(path.handle);
147     } else {
148         double tolerance = Cairo.cairo_get_tolerance(path.handle);
149         Cairo.cairo_set_tolerance(path.handle, flatness);
150         copy = Cairo.cairo_copy_path_flat(path.handle);
151         Cairo.cairo_set_tolerance(path.handle, tolerance);
152     }
153     if (copy is null) {
154         Cairo.cairo_destroy(handle);
155         SWT.error(SWT.ERROR_NO_HANDLES);
156     }
157     Cairo.cairo_append_path(handle, copy);
158     Cairo.cairo_path_destroy(copy);
159     init_();
160 }
161 
162 /**
163  * Constructs a new Path with the specifed PathData.
164  * <p>
165  * This operation requires the operating system's advanced
166  * graphics subsystem which may not be available on some
167  * platforms.
168  * </p>
169  * 
170  * @param device the device on which to allocate the path
171  * @param data the data for the path
172  * 
173  * @exception IllegalArgumentException <ul>
174  *    <li>ERROR_NULL_ARGUMENT - if the device is null and there is no current device</li>
175  *    <li>ERROR_NULL_ARGUMENT - if the data is null</li>
176  * </ul>
177  * @exception SWTException <ul>
178  *    <li>ERROR_NO_GRAPHICS_LIBRARY - if advanced graphics are not available</li>
179  * </ul>
180  * @exception SWTError <ul>
181  *    <li>ERROR_NO_HANDLES if a handle for the path could not be obtained</li>
182  * </ul>
183  * 
184  * @see #dispose()
185  * @since 3.4
186  */
187 public this (Device device, PathData data) {
188     this(device);
189     if (data is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
190     init_(data);
191 }
192 
193 /**
194  * Adds to the receiver a circular or elliptical arc that lies within
195  * the specified rectangular area.
196  * <p>
197  * The resulting arc begins at <code>startAngle</code> and extends
198  * for <code>arcAngle</code> degrees.
199  * Angles are interpreted such that 0 degrees is at the 3 o'clock
200  * position. A positive value indicates a counter-clockwise rotation
201  * while a negative value indicates a clockwise rotation.
202  * </p><p>
203  * The center of the arc is the center of the rectangle whose origin
204  * is (<code>x</code>, <code>y</code>) and whose size is specified by the
205  * <code>width</code> and <code>height</code> arguments.
206  * </p><p>
207  * The resulting arc covers an area <code>width + 1</code> pixels wide
208  * by <code>height + 1</code> pixels tall.
209  * </p>
210  *
211  * @param x the x coordinate of the upper-left corner of the arc
212  * @param y the y coordinate of the upper-left corner of the arc
213  * @param width the width of the arc
214  * @param height the height of the arc
215  * @param startAngle the beginning angle
216  * @param arcAngle the angular extent of the arc, relative to the start angle
217  *
218  * @exception SWTException <ul>
219  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
220  * </ul>
221  */
222 public void addArc(float x, float y, float width, float height, float startAngle, float arcAngle) {
223     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
224     moved = true;
225     if (width is height) {
226         float angle = -startAngle * cast(float)Compatibility.PI / 180;
227         if (closed) Cairo.cairo_move_to(handle, (x + width / 2f) + width / 2f * Math.cos(angle), (y + height / 2f) + height / 2f * Math.sin(angle));
228         if (arcAngle >= 0) {
229             Cairo.cairo_arc_negative(handle, x + width / 2f, y + height / 2f, width / 2f, angle, -(startAngle + arcAngle) * cast(float)Compatibility.PI / 180);
230         } else {
231             Cairo.cairo_arc(handle, x + width / 2f, y + height / 2f, width / 2f, angle, -(startAngle + arcAngle) * cast(float)Compatibility.PI / 180);
232         }
233     } else {
234         Cairo.cairo_save(handle);
235         Cairo.cairo_translate(handle, x + width / 2f, y + height / 2f);
236         Cairo.cairo_scale(handle, width / 2f, height / 2f);
237         float angle = -startAngle * cast(float)Compatibility.PI / 180;
238         if (closed) Cairo.cairo_move_to(handle, Math.cos(angle), Math.sin(angle));
239         if (arcAngle >= 0) {
240             Cairo.cairo_arc_negative(handle, 0, 0, 1, angle, -(startAngle + arcAngle) * cast(float)Compatibility.PI / 180);
241         } else {
242             Cairo.cairo_arc(handle, 0, 0, 1, angle, -(startAngle + arcAngle) * cast(float)Compatibility.PI / 180);
243         }
244         Cairo.cairo_restore(handle);
245     }
246     closed = false;
247     if (Math.abs(arcAngle) >= 360) close();
248 }
249 
250 /**
251  * Adds to the receiver the path described by the parameter.
252  *
253  * @param path the path to add to the receiver
254  *
255  * @exception IllegalArgumentException <ul>
256  *    <li>ERROR_NULL_ARGUMENT - if the parameter is null</li>
257  *    <li>ERROR_INVALID_ARGUMENT - if the parameter has been disposed</li>
258  * </ul>
259  * @exception SWTException <ul>
260  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
261  * </ul>
262  */
263 public void addPath(Path path) {
264     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
265     if (path is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
266     if (path.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
267     moved = false;
268     auto copy = Cairo.cairo_copy_path(path.handle);
269     if (copy is null) SWT.error(SWT.ERROR_NO_HANDLES);
270     Cairo.cairo_append_path(handle, copy);
271     Cairo.cairo_path_destroy(copy);
272     closed = path.closed;
273 }
274 
275 /**
276  * Adds to the receiver the rectangle specified by x, y, width and height.
277  *
278  * @param x the x coordinate of the rectangle to add
279  * @param y the y coordinate of the rectangle to add
280  * @param width the width of the rectangle to add
281  * @param height the height of the rectangle to add
282  *
283  * @exception SWTException <ul>
284  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
285  * </ul>
286  */
287 public void addRectangle(float x, float y, float width, float height) {
288     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
289     moved = false;
290     Cairo.cairo_rectangle(handle, x, y, width, height);
291     closed = true;
292 }
293 
294 /**
295  * Adds to the receiver the pattern of glyphs generated by drawing
296  * the given string using the given font starting at the point (x, y).
297  *
298  * @param string the text to use
299  * @param x the x coordinate of the starting point
300  * @param y the y coordinate of the starting point
301  * @param font the font to use
302  *
303  * @exception IllegalArgumentException <ul>
304  *    <li>ERROR_NULL_ARGUMENT - if the font is null</li>
305  *    <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li>
306  * </ul>
307  * @exception SWTException <ul>
308  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
309  * </ul>
310  */
311 public void addString(String str, float x, float y, Font font) {
312     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
313     if (font is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
314     if (font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
315     moved = false;
316     GC.addCairoString(handle, str, x, y, font);
317     closed = true;
318 }
319 
320 /**
321  * Closes the current sub path by adding to the receiver a line
322  * from the current point of the path back to the starting point
323  * of the sub path.
324  *
325  * @exception SWTException <ul>
326  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
327  * </ul>
328  */
329 public void close() {
330     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
331     Cairo.cairo_close_path(handle);
332     moved = false;
333     closed = true;
334 }
335 
336 /**
337  * Returns <code>true</code> if the specified point is contained by
338  * the receiver and false otherwise.
339  * <p>
340  * If outline is <code>true</code>, the point (x, y) checked for containment in
341  * the receiver's outline. If outline is <code>false</code>, the point is
342  * checked to see if it is contained within the bounds of the (closed) area
343  * covered by the receiver.
344  *
345  * @param x the x coordinate of the point to test for containment
346  * @param y the y coordinate of the point to test for containment
347  * @param gc the GC to use when testing for containment
348  * @param outline controls whether to check the outline or contained area of the path
349  * @return <code>true</code> if the path contains the point and <code>false</code> otherwise
350  *
351  * @exception IllegalArgumentException <ul>
352  *    <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
353  *    <li>ERROR_INVALID_ARGUMENT - if the gc has been disposed</li>
354  * </ul>
355  * @exception SWTException <ul>
356  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
357  * </ul>
358  */
359 public bool contains(float x, float y, GC gc, bool outline) {
360     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
361     if (gc is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
362     if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
363     //TODO - see Windows
364     gc.initCairo();
365     gc.checkGC(GC.LINE_CAP | GC.LINE_JOIN | GC.LINE_STYLE | GC.LINE_WIDTH);
366     bool result = false;
367     auto cairo = gc.data.cairo;
368     auto copy = Cairo.cairo_copy_path(handle);
369     if (copy is null) SWT.error(SWT.ERROR_NO_HANDLES);
370     Cairo.cairo_append_path(cairo, copy);
371     Cairo.cairo_path_destroy(copy);
372     if (outline) {
373         result = Cairo.cairo_in_stroke(cairo, x, y) !is 0;
374     } else {
375         result = Cairo.cairo_in_fill(cairo, x, y) !is 0;
376     }
377     Cairo.cairo_new_path(cairo);
378     return result;
379 }
380 
381 /**
382  * Adds to the receiver a cubic bezier curve based on the parameters.
383  *
384  * @param cx1 the x coordinate of the first control point of the spline
385  * @param cy1 the y coordinate of the first control of the spline
386  * @param cx2 the x coordinate of the second control of the spline
387  * @param cy2 the y coordinate of the second control of the spline
388  * @param x the x coordinate of the end point of the spline
389  * @param y the y coordinate of the end point of the spline
390  *
391  * @exception SWTException <ul>
392  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
393  * </ul>
394  */
395 public void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) {
396     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
397     if (!moved) {
398         double currentX = 0, currentY = 0;
399         Cairo.cairo_get_current_point(handle, &currentX, &currentY);
400         Cairo.cairo_move_to(handle, currentX, currentY);
401         moved = true;
402     }
403     Cairo.cairo_curve_to(handle, cx1, cy1, cx2, cy2, x, y);
404     closed = false;
405 }
406 
407 /**
408  * Replaces the first four elements in the parameter with values that
409  * describe the smallest rectangle that will completely contain the
410  * receiver (i.e. the bounding box).
411  *
412  * @param bounds the array to hold the result
413  *
414  * @exception IllegalArgumentException <ul>
415  *    <li>ERROR_NULL_ARGUMENT - if the parameter is null</li>
416  *    <li>ERROR_INVALID_ARGUMENT - if the parameter is too small to hold the bounding box</li>
417  * </ul>
418  * @exception SWTException <ul>
419  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
420  * </ul>
421  */
422 public void getBounds(float[] bounds) {
423     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
424     if (bounds is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
425     if (bounds.length < 4) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
426     auto copy = Cairo.cairo_copy_path(handle);
427     if (copy is null) SWT.error(SWT.ERROR_NO_HANDLES);
428     cairo_path_t* path = new cairo_path_t();
429     memmove(path, copy, cairo_path_t.sizeof);
430     double minX = 0, minY = 0, maxX = 0, maxY = 0;
431     if (path.num_data > 0) {
432         minX = minY = double.max;
433         maxX = maxY = -double.max;
434         int i = 0;
435         cairo_path_data_t* data = new cairo_path_data_t();
436         while (i < path.num_data) {
437             *data = path.data[i];
438             switch (data.header.type) {
439                 case Cairo.CAIRO_PATH_MOVE_TO:
440                     minX = Math.min(minX, path.data[i+1].point.x);
441                     minY = Math.min(minY, path.data[i+1].point.y);
442                     maxX = Math.max(maxX, path.data[i+1].point.x);
443                     maxY = Math.max(maxY, path.data[i+1].point.y);
444                     break;
445                 case Cairo.CAIRO_PATH_LINE_TO:
446                     minX = Math.min(minX, path.data[i+1].point.x);
447                     minY = Math.min(minY, path.data[i+1].point.y);
448                     maxX = Math.max(maxX, path.data[i+1].point.x);
449                     maxY = Math.max(maxY, path.data[i+1].point.y);
450                     break;
451                 case Cairo.CAIRO_PATH_CURVE_TO:
452                     minX = Math.min(minX, path.data[i+1].point.x);
453                     minY = Math.min(minY, path.data[i+1].point.y);
454                     maxX = Math.max(maxX, path.data[i+1].point.x);
455                     maxY = Math.max(maxY, path.data[i+1].point.y);
456                     minX = Math.min(minX, path.data[i+2].point.x);
457                     minY = Math.min(minY, path.data[i+2].point.y);
458                     maxX = Math.max(maxX, path.data[i+2].point.x);
459                     maxY = Math.max(maxY, path.data[i+2].point.y);
460                     minX = Math.min(minX, path.data[i+3].point.x);
461                     minY = Math.min(minY, path.data[i+3].point.y);
462                     maxX = Math.max(maxX, path.data[i+3].point.x);
463                     maxY = Math.max(maxY, path.data[i+3].point.y);
464                     break;
465                 case Cairo.CAIRO_PATH_CLOSE_PATH: break;
466                 default:
467             }
468             i += data.header.length;
469         }
470     }
471     bounds[0] = cast(float)minX;
472     bounds[1] = cast(float)minY;
473     bounds[2] = cast(float)(maxX - minX);
474     bounds[3] = cast(float)(maxY - minY);
475     Cairo.cairo_path_destroy(copy);
476 }
477 
478 /**
479  * Replaces the first two elements in the parameter with values that
480  * describe the current point of the path.
481  *
482  * @param point the array to hold the result
483  *
484  * @exception IllegalArgumentException <ul>
485  *    <li>ERROR_NULL_ARGUMENT - if the parameter is null</li>
486  *    <li>ERROR_INVALID_ARGUMENT - if the parameter is too small to hold the end point</li>
487  * </ul>
488  * @exception SWTException <ul>
489  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
490  * </ul>
491  */
492 public void getCurrentPoint(float[] point) {
493     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
494     if (point is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
495     if (point.length < 2) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
496     double x = 0, y = 0;
497     Cairo.cairo_get_current_point(handle, &x, &y);
498     point[0] = cast(float)x;
499     point[1] = cast(float)y;
500 }
501 
502 /**
503  * Returns a device independent representation of the receiver.
504  *
505  * @return the PathData for the receiver
506  *
507  * @exception SWTException <ul>
508  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
509  * </ul>
510  *
511  * @see PathData
512  */
513 public PathData getPathData() {
514     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
515     auto copy = Cairo.cairo_copy_path(handle);
516     if (copy is null) SWT.error(SWT.ERROR_NO_HANDLES);
517     cairo_path_t* path = new cairo_path_t();
518     *path = *copy;
519     byte[] types = new byte[path.num_data];
520     float[] pts = new float[path.num_data * 6];
521     int typeIndex = 0, ptsIndex = 0;
522     if (path.num_data > 0) {
523         int i = 0;
524         double[] points = new double[6];
525         cairo_path_data_t* data = new cairo_path_data_t();
526         while (i < path.num_data) {
527             switch (data.header.type) {
528                 case Cairo.CAIRO_PATH_MOVE_TO:
529                     types[typeIndex++] = SWT.PATH_MOVE_TO;
530                     pts[ptsIndex++] = cast(float)path.data[i+1].point.x;
531                     pts[ptsIndex++] = cast(float)path.data[i+1].point.y;
532                     break;
533                 case Cairo.CAIRO_PATH_LINE_TO:
534                     types[typeIndex++] = SWT.PATH_LINE_TO;
535                     pts[ptsIndex++] = cast(float)path.data[i+1].point.x;
536                     pts[ptsIndex++] = cast(float)path.data[i+1].point.y;
537                     break;
538                 case Cairo.CAIRO_PATH_CURVE_TO:
539                     types[typeIndex++] = SWT.PATH_CUBIC_TO;
540                     pts[ptsIndex++] = cast(float)path.data[i+1].point.x;
541                     pts[ptsIndex++] = cast(float)path.data[i+1].point.y;
542                     pts[ptsIndex++] = cast(float)path.data[i+2].point.x;
543                     pts[ptsIndex++] = cast(float)path.data[i+2].point.y;
544                     pts[ptsIndex++] = cast(float)path.data[i+3].point.x;
545                     pts[ptsIndex++] = cast(float)path.data[i+3].point.y;
546                     break;
547                 case Cairo.CAIRO_PATH_CLOSE_PATH:
548                     types[typeIndex++] = SWT.PATH_CLOSE;
549                     break;
550                 default:
551             }
552             i += data.header.length;
553         }
554     }
555     if (typeIndex !is types.length) {
556         byte[] newTypes = new byte[typeIndex];
557         System.arraycopy(types, 0, newTypes, 0, typeIndex);
558         types = newTypes;
559     }
560     if (ptsIndex !is pts.length) {
561         float[] newPts = new float[ptsIndex];
562         System.arraycopy(pts, 0, newPts, 0, ptsIndex);
563         pts = newPts;
564     }
565     Cairo.cairo_path_destroy(copy);
566     PathData result = new PathData();
567     result.types = types;
568     result.points = pts;
569     return result;
570 }
571 
572 /**
573  * Adds to the receiver a line from the current point to
574  * the point specified by (x, y).
575  *
576  * @param x the x coordinate of the end of the line to add
577  * @param y the y coordinate of the end of the line to add
578  *
579  * @exception SWTException <ul>
580  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
581  * </ul>
582  */
583 public void lineTo(float x, float y) {
584     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
585     if (!moved) {
586         double currentX = 0, currentY = 0;
587         Cairo.cairo_get_current_point(handle, &currentX, &currentY);
588         Cairo.cairo_move_to(handle, currentX, currentY);
589         moved = true;
590     }
591     Cairo.cairo_line_to(handle, x, y);
592     closed = false;
593 }
594 
595 /**
596  * Sets the current point of the receiver to the point
597  * specified by (x, y). Note that this starts a new
598  * sub path.
599  *
600  * @param x the x coordinate of the new end point
601  * @param y the y coordinate of the new end point
602  *
603  * @exception SWTException <ul>
604  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
605  * </ul>
606  */
607 public void moveTo(float x, float y) {
608     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
609     /*
610     * Bug in Cairo.  If cairo_move_to() is not called at the
611     * begining of a subpath, the first cairo_line_to() or
612     * cairo_curve_to() segment do not output anything.  The fix
613     * is to detect that the app did not call cairo_move_to()
614     * before those calls and call it explicitly.
615     */
616     moved = true;
617     Cairo.cairo_move_to(handle, x, y);
618     closed = true;
619 }
620 
621 /**
622  * Adds to the receiver a quadratic curve based on the parameters.
623  *
624  * @param cx the x coordinate of the control point of the spline
625  * @param cy the y coordinate of the control point of the spline
626  * @param x the x coordinate of the end point of the spline
627  * @param y the y coordinate of the end point of the spline
628  *
629  * @exception SWTException <ul>
630  *    <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
631  * </ul>
632  */
633 public void quadTo(float cx, float cy, float x, float y) {
634     if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
635     double currentX = 0, currentY = 0;
636     Cairo.cairo_get_current_point(handle, &currentX, &currentY);
637     if (!moved) {
638         Cairo.cairo_move_to(handle, currentX, currentY);
639         moved = true;
640     }
641     float x0 = cast(float)currentX;
642     float y0 = cast(float)currentY;
643     float cx1 = x0 + 2 * (cx - x0) / 3;
644     float cy1 = y0 + 2 * (cy - y0) / 3;
645     float cx2 = cx1 + (x - x0) / 3;
646     float cy2 = cy1 + (y - y0) / 3;
647     Cairo.cairo_curve_to(handle, cx1, cy1, cx2, cy2, x, y);
648     closed = false;
649 }
650 
651 override
652 void destroy() {
653     Cairo.cairo_destroy(handle);
654     handle = null;
655 }
656 
657 void init_(PathData data) {
658     byte[] types = data.types;
659     float[] points = data.points;
660     for (int i = 0, j = 0; i < types.length; i++) {
661         switch (types[i]) {
662             case SWT.PATH_MOVE_TO:
663                 moveTo(points[j++], points[j++]);
664                 break;
665             case SWT.PATH_LINE_TO:
666                 lineTo(points[j++], points[j++]);
667                 break;
668             case SWT.PATH_CUBIC_TO:
669                 cubicTo(points[j++], points[j++], points[j++], points[j++], points[j++], points[j++]);
670                 break;
671             case SWT.PATH_QUAD_TO:
672                 quadTo(points[j++], points[j++], points[j++], points[j++]);
673                 break;
674             case SWT.PATH_CLOSE:
675                 close();
676                 break;
677             default:
678                 dispose();
679                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
680         }
681     }
682 }
683 
684 /**
685  * Returns <code>true</code> if the Path has been disposed,
686  * and <code>false</code> otherwise.
687  * <p>
688  * This method gets the dispose state for the Path.
689  * When a Path has been disposed, it is an error to
690  * invoke any other method using the Path.
691  *
692  * @return <code>true</code> when the Path is disposed, and <code>false</code> otherwise
693  */
694 public override bool isDisposed() {
695     return handle is null;
696 }
697 
698 /**
699  * Returns a string containing a concise, human-readable
700  * description of the receiver.
701  *
702  * @return a string representation of the receiver
703  */
704 public override String toString() {
705     if (isDisposed()) return "Path {*DISPOSED*}";
706     return Format( "Path {{{}}", handle );
707 }
708 
709 }