2006-08-07 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / System.Drawing / System.Drawing / AdvancedStroke.jvm.cs
1 using System;\r
2 using System.Drawing.Drawing2D;\r
3 using java.lang;\r
4 \r
5 using java.awt;\r
6 using java.awt.geom;\r
7 using sun.dc.path;\r
8 using sun.dc.pr;\r
9 \r
10 namespace System.Drawing {\r
11 \r
12         internal enum PenFit {\r
13                 NotThin,\r
14                 Thin,\r
15                 ThinAntiAlias\r
16         }\r
17 \r
18         internal class AdvancedStroke : Stroke {\r
19 \r
20                 public const float PenUnits = 0.01f;
21                 public const int MinPenUnits = 100;
22                 public const int MinPenUnitsAA = 20;
23                 public const float MinPenSizeAA = PenUnits * MinPenUnitsAA;\r
24                 public const double MinPenSizeAASquared = (MinPenSizeAA * MinPenSizeAA);
25                 public const double MinPenSizeSquared = 1.000000001;\r
26                 public const double MinPenSizeNorm = 1.5;\r
27                 public const double MinPenSizeSquaredNorm = (MinPenSizeNorm * MinPenSizeNorm);\r
28 \r
29                 /**\r
30                  * Joins path segments by extending their outside edges until\r
31                  * they meet.\r
32                  */\r
33                 public const int JOIN_MITER = 0;\r
34 \r
35                 /**\r
36                  * Joins path segments by rounding off the corner at a radius\r
37                  * of half the line width.\r
38                  */\r
39                 public const int JOIN_ROUND = 1;\r
40 \r
41                 /**\r
42                  * Joins path segments by connecting the outer corners of their\r
43                  * wide outlines with a straight segment.\r
44                  */\r
45                 public const int JOIN_BEVEL = 2;\r
46 \r
47                 /**\r
48                  * Ends unclosed subpaths and dash segments with no added\r
49                  * decoration.\r
50                  */\r
51                 public const int CAP_BUTT = 0;\r
52 \r
53                 /**\r
54                  * Ends unclosed subpaths and dash segments with a round\r
55                  * decoration that has a radius equal to half of the width\r
56                  * of the pen.\r
57                  */\r
58                 public const int CAP_ROUND = 1;\r
59 \r
60                 /**\r
61                  * Ends unclosed subpaths and dash segments with a square\r
62                  * projection that extends beyond the end of the segment\r
63                  * to a distance equal to half of the line width.\r
64                  */\r
65                 public const int CAP_SQUARE = 2;\r
66 \r
67                 float width;\r
68 \r
69                 int join;\r
70                 int cap;\r
71                 float miterlimit;\r
72 \r
73                 float[] dash;\r
74                 float dash_phase;\r
75 \r
76                 AffineTransform _penTransform;\r
77                 AffineTransform _outputTransform;\r
78                 PenFit _penFit;\r
79 \r
80                 /**\r
81                  * Constructs a new <code>AdvancedStroke</code> with the specified\r
82                  * attributes.\r
83                  * @param width the width of this <code>AdvancedStroke</code>.  The\r
84                  *         width must be greater than or equal to 0.0f.  If width is\r
85                  *         set to 0.0f, the stroke is rendered as the thinnest\r
86                  *         possible line for the target device and the antialias\r
87                  *         hint setting.\r
88                  * @param cap the decoration of the ends of a <code>AdvancedStroke</code>\r
89                  * @param join the decoration applied where path segments meet\r
90                  * @param miterlimit the limit to trim the miter join.  The miterlimit\r
91                  *        must be greater than or equal to 1.0f.\r
92                  * @param dash the array representing the dashing pattern\r
93                  * @param dash_phase the offset to start the dashing pattern\r
94                  * @throws IllegalArgumentException if <code>width</code> is negative\r
95                  * @throws IllegalArgumentException if <code>cap</code> is not either\r
96                  *         CAP_BUTT, CAP_ROUND or CAP_SQUARE\r
97                  * @throws IllegalArgumentException if <code>miterlimit</code> is less\r
98                  *         than 1 and <code>join</code> is JOIN_MITER\r
99                  * @throws IllegalArgumentException if <code>join</code> is not\r
100                  *         either JOIN_ROUND, JOIN_BEVEL, or JOIN_MITER\r
101                  * @throws IllegalArgumentException if <code>dash_phase</code>\r
102                  *         is negative and <code>dash</code> is not <code>null</code>\r
103                  * @throws IllegalArgumentException if the length of\r
104                  *         <code>dash</code> is zero\r
105                  * @throws IllegalArgumentException if dash lengths are all zero.\r
106                  */\r
107                 public AdvancedStroke(float width, int cap, int join, float miterlimit,\r
108                         float[] dash, float dash_phase, AffineTransform penTransform,\r
109                         AffineTransform outputTransform, PenFit penFit) {\r
110                         if (width < 0.0f) {\r
111                                 throw new IllegalArgumentException("negative width");\r
112                         }\r
113                         if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {\r
114                                 throw new IllegalArgumentException("illegal end cap value");\r
115                         }\r
116                         if (join == JOIN_MITER) {\r
117                                 if (miterlimit < 1.0f) {\r
118                                         throw new IllegalArgumentException("miter limit < 1");\r
119                                 }\r
120                         } else if (join != JOIN_ROUND && join != JOIN_BEVEL) {\r
121                                 throw new IllegalArgumentException("illegal line join value");\r
122                         }\r
123                         if (dash != null) {\r
124                                 if (dash_phase < 0.0f) {\r
125                                         throw new IllegalArgumentException("negative dash phase");\r
126                                 }\r
127                                 bool allzero = true;\r
128                                 for (int i = 0; i < dash.Length; i++) {\r
129                                         float d = dash[i];\r
130                                         if (d > 0.0) {\r
131                                                 allzero = false;\r
132                                         } else if (d < 0.0) {\r
133                                                 throw new IllegalArgumentException("negative dash length");\r
134                                         }\r
135                                 }\r
136                                 if (allzero) {\r
137                                         throw new IllegalArgumentException("dash lengths all zero");\r
138                                 }\r
139                         }\r
140                         this.width      = width;\r
141                         this.cap        = cap;\r
142                         this.join       = join;\r
143                         this.miterlimit = miterlimit;\r
144                         if (dash != null) {\r
145                                 this.dash = (float []) dash.Clone();\r
146                         }\r
147                         this.dash_phase = dash_phase;\r
148                         this._penTransform = penTransform;\r
149                         this._outputTransform = outputTransform;\r
150                         this._penFit = penFit;\r
151                 }\r
152 \r
153                 /**\r
154                  * Constructs a solid <code>AdvancedStroke</code> with the specified \r
155                  * attributes.\r
156                  * @param width the width of the <code>AdvancedStroke</code>\r
157                  * @param cap the decoration of the ends of a <code>AdvancedStroke</code>\r
158                  * @param join the decoration applied where path segments meet\r
159                  * @param miterlimit the limit to trim the miter join\r
160                  * @throws IllegalArgumentException if <code>width</code> is negative\r
161                  * @throws IllegalArgumentException if <code>cap</code> is not either\r
162                  *         CAP_BUTT, CAP_ROUND or CAP_SQUARE\r
163                  * @throws IllegalArgumentException if <code>miterlimit</code> is less\r
164                  *         than 1 and <code>join</code> is JOIN_MITER\r
165                  * @throws IllegalArgumentException if <code>join</code> is not\r
166                  *         either JOIN_ROUND, JOIN_BEVEL, or JOIN_MITER\r
167                  */\r
168                 public AdvancedStroke(float width, int cap, int join, float miterlimit) :\r
169                         this(width, cap, join, miterlimit, null, 0.0f, null, null, PenFit.NotThin) {\r
170                 }\r
171 \r
172                 /**\r
173                  * Constructs a solid <code>AdvancedStroke</code> with the specified \r
174                  * attributes.  The <code>miterlimit</code> parameter is \r
175                  * unnecessary in cases where the default is allowable or the \r
176                  * line joins are not specified as JOIN_MITER.\r
177                  * @param width the width of the <code>AdvancedStroke</code>\r
178                  * @param cap the decoration of the ends of a <code>AdvancedStroke</code>\r
179                  * @param join the decoration applied where path segments meet\r
180                  * @throws IllegalArgumentException if <code>width</code> is negative\r
181                  * @throws IllegalArgumentException if <code>cap</code> is not either\r
182                  *         CAP_BUTT, CAP_ROUND or CAP_SQUARE\r
183                  * @throws IllegalArgumentException if <code>join</code> is not\r
184                  *         either JOIN_ROUND, JOIN_BEVEL, or JOIN_MITER\r
185                  */\r
186                 public AdvancedStroke(float width, int cap, int join) :\r
187                         this(width, cap, join, 10.0f, null, 0.0f, null, null, PenFit.NotThin) {\r
188                 }\r
189 \r
190                 /**\r
191                  * Constructs a solid <code>AdvancedStroke</code> with the specified \r
192                  * line width and with default values for the cap and join \r
193                  * styles.\r
194                  * @param width the width of the <code>AdvancedStroke</code>\r
195                  * @throws IllegalArgumentException if <code>width</code> is negative\r
196                  */\r
197                 public AdvancedStroke(float width) :\r
198                         this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f, null, null, PenFit.NotThin) {\r
199                 }\r
200 \r
201                 /**\r
202                  * Constructs a new <code>AdvancedStroke</code> with defaults for all \r
203                  * attributes.\r
204                  * The default attributes are a solid line of width 1.0, CAP_SQUARE,\r
205                  * JOIN_MITER, a miter limit of 10.0.\r
206                  */\r
207                 public AdvancedStroke() :\r
208                         this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f, null, null, PenFit.NotThin) {\r
209                 }\r
210 \r
211 \r
212                 /**\r
213                  * Returns a <code>Shape</code> whose interior defines the \r
214                  * stroked outline of a specified <code>Shape</code>.\r
215                  * @param s the <code>Shape</code> boundary be stroked\r
216                  * @return the <code>Shape</code> of the stroked outline.\r
217                  */\r
218                 public Shape createStrokedShape(Shape s) {\r
219                         FillAdapter filler = new FillAdapter();\r
220                         PathStroker stroker = new PathStroker(filler);\r
221                         PathConsumer consumer;\r
222 \r
223                         stroker.setPenDiameter(width);\r
224                         switch (_penFit) {\r
225                                 case PenFit.Thin:\r
226                                         stroker.setPenFitting(PenUnits, MinPenUnits);\r
227                                         break;\r
228                                 case PenFit.ThinAntiAlias:\r
229                                         stroker.setPenFitting(PenUnits, MinPenUnitsAA);\r
230                                         break;\r
231                         }\r
232 \r
233                         float[] t4 = null;\r
234                         if (PenTransform != null && !PenTransform.isIdentity() && (PenTransform.getDeterminant() > 1e-25)) {\r
235                                 t4 = new float[]{\r
236                                         (float)PenTransform.getScaleX(), (float)PenTransform.getShearY(), \r
237                                         (float)PenTransform.getShearX(), (float)PenTransform.getScaleY()\r
238                                 };\r
239                         }\r
240 \r
241                         float[] t6 = null;\r
242                         if (OutputTransform != null && !OutputTransform.isIdentity()) {\r
243                                 t6 = new float[] {\r
244                                         (float)OutputTransform.getScaleX(), (float)OutputTransform.getShearY(), \r
245                                         (float)OutputTransform.getShearX(), (float)OutputTransform.getScaleY(),\r
246                                         (float)OutputTransform.getTranslateX(), (float)OutputTransform.getTranslateY()\r
247                                 };\r
248                         }\r
249 \r
250                         stroker.setPenT4(t4);\r
251                         stroker.setOutputT6(t6);\r
252                         stroker.setCaps(RasterizerCaps[cap]);\r
253                         stroker.setCorners(RasterizerCorners[join], miterlimit);\r
254                         if (dash != null) {\r
255                                 PathDasher dasher = new PathDasher(stroker);\r
256                                 dasher.setDash(dash, dash_phase);\r
257                                 dasher.setDashT4(t4);\r
258                                 consumer = dasher;\r
259                         } else {\r
260                                 consumer = stroker;\r
261                         }\r
262 \r
263                         PathIterator pi = s.getPathIterator(null);\r
264 \r
265                         try {\r
266                                 consumer.beginPath();\r
267                                 bool pathClosed = false;\r
268                                 float mx = 0.0f;\r
269                                 float my = 0.0f;\r
270                                 float[] point  = new float[6];\r
271 \r
272                                 while (!pi.isDone()) {\r
273                                         int type = pi.currentSegment(point);\r
274                                         if (pathClosed == true) {\r
275                                                 pathClosed = false;\r
276                                                 if (type !=  PathIterator__Finals.SEG_MOVETO) {\r
277                                                         // Force current point back to last moveto point\r
278                                                         consumer.beginSubpath(mx, my);\r
279                                                 }\r
280                                         }\r
281                                         switch ((GraphicsPath.JPI)type) {\r
282                                                 case GraphicsPath.JPI.SEG_MOVETO:\r
283                                                         mx = point[0];\r
284                                                         my = point[1];\r
285                                                         consumer.beginSubpath(point[0], point[1]);\r
286                                                         break;\r
287                                                 case GraphicsPath.JPI.SEG_LINETO:\r
288                                                         consumer.appendLine(point[0], point[1]);\r
289                                                         break;\r
290                                                 case GraphicsPath.JPI.SEG_QUADTO:\r
291                                                         // Quadratic curves take two points\r
292                                                         consumer.appendQuadratic(point[0], point[1],\r
293                                                                 point[2], point[3]);\r
294                                                         break;\r
295                                                 case GraphicsPath.JPI.SEG_CUBICTO:\r
296                                                         // Cubic curves take three points\r
297                                                         consumer.appendCubic(point[0], point[1],\r
298                                                                 point[2], point[3],\r
299                                                                 point[4], point[5]);\r
300                                                         break;\r
301                                                 case GraphicsPath.JPI.SEG_CLOSE:\r
302                                                         consumer.closedSubpath();\r
303                                                         pathClosed = true;\r
304                                                         break;\r
305                                         }\r
306                                         pi.next();\r
307                                 }\r
308 \r
309                                 consumer.endPath();\r
310                         } catch (PathException e) {\r
311                                 throw new InternalError("Unable to Stroke shape ("+\r
312                                         e.Message+")");\r
313                         }\r
314 \r
315                         return filler.getShape();\r
316                 }\r
317 \r
318                 /**\r
319                  * Returns the line width.  Line width is represented in user space, \r
320                  * which is the default-coordinate space used by Java 2D.  See the\r
321                  * <code>Graphics2D</code> class comments for more information on\r
322                  * the user space coordinate system.\r
323                  * @return the line width of this <code>AdvancedStroke</code>.\r
324                  * @see Graphics2D\r
325                  */\r
326                 public float getLineWidth() {\r
327                         return width;\r
328                 }\r
329 \r
330                 /**\r
331                  * Returns the end cap style.\r
332                  * @return the end cap style of this <code>AdvancedStroke</code> as one\r
333                  * of the static <code>int</code> values that define possible end cap\r
334                  * styles.\r
335                  */\r
336                 public int getEndCap() {\r
337                         return cap;\r
338                 }\r
339 \r
340                 /**\r
341                  * Returns the line join style.\r
342                  * @return the line join style of the <code>AdvancedStroke</code> as one\r
343                  * of the static <code>int</code> values that define possible line\r
344                  * join styles.\r
345                  */\r
346                 public int getLineJoin() {\r
347                         return join;\r
348                 }\r
349 \r
350                 /**\r
351                  * Returns the limit of miter joins.\r
352                  * @return the limit of miter joins of the <code>AdvancedStroke</code>.\r
353                  */\r
354                 public float getMiterLimit() {\r
355                         return miterlimit;\r
356                 }\r
357 \r
358                 /**\r
359                  * Returns the array representing the lengths of the dash segments.\r
360                  * Alternate entries in the array represent the user space lengths\r
361                  * of the opaque and transparent segments of the dashes.\r
362                  * As the pen moves along the outline of the <code>Shape</code>\r
363                  * to be stroked, the user space\r
364                  * distance that the pen travels is accumulated.  The distance\r
365                  * value is used to index into the dash array.\r
366                  * The pen is opaque when its current cumulative distance maps\r
367                  * to an even element of the dash array and transparent otherwise.\r
368                  * @return the dash array.\r
369                  */\r
370                 public float[] getDashArray() {\r
371                         if (dash == null) {\r
372                                 return null;\r
373                         }\r
374 \r
375                         return (float[]) dash.Clone();\r
376                 }\r
377 \r
378                 /**\r
379                  * Returns the current dash phase.\r
380                  * The dash phase is a distance specified in user coordinates that \r
381                  * represents an offset into the dashing pattern. In other words, the dash \r
382                  * phase defines the point in the dashing pattern that will correspond to \r
383                  * the beginning of the stroke.\r
384                  * @return the dash phase as a <code>float</code> value.\r
385                  */\r
386                 public float getDashPhase() {\r
387                         return dash_phase;\r
388                 }\r
389 \r
390                 /**\r
391                  * Returns the hashcode for this stroke.\r
392                  * @return      a hash code for this stroke.\r
393                  */\r
394                 public override int GetHashCode() {\r
395                         int hash = Float.floatToIntBits(width);\r
396                         hash = hash * 31 + join;\r
397                         hash = hash * 31 + cap;\r
398                         hash = hash * 31 + Float.floatToIntBits(miterlimit);\r
399                         if (dash != null) {\r
400                                 hash = hash * 31 + Float.floatToIntBits(dash_phase);\r
401                                 for (int i = 0; i < dash.Length; i++) {\r
402                                         hash = hash * 31 + Float.floatToIntBits(dash[i]);\r
403                                 }\r
404                         }\r
405                         return hash;\r
406                 }\r
407 \r
408                 /**\r
409                  * Returns true if this AdvancedStroke represents the same\r
410                  * stroking operation as the given argument.\r
411                  */\r
412                 /**\r
413                  * Tests if a specified object is equal to this <code>AdvancedStroke</code>\r
414                  * by first testing if it is a <code>AdvancedStroke</code> and then comparing \r
415                  * its width, join, cap, miter limit, dash, and dash phase attributes with \r
416                  * those of this <code>AdvancedStroke</code>.\r
417                  * @param  obj the specified object to compare to this \r
418                  *              <code>AdvancedStroke</code>\r
419                  * @return <code>true</code> if the width, join, cap, miter limit, dash, and\r
420                  *            dash phase are the same for both objects;\r
421                  *            <code>false</code> otherwise.\r
422                  */\r
423                 public override bool Equals(object obj) {\r
424                         if (!(obj is AdvancedStroke)) {\r
425                                 return false;\r
426                         }\r
427 \r
428                         AdvancedStroke bs = (AdvancedStroke) obj;\r
429                         if (width != bs.width) {\r
430                                 return false;\r
431                         }\r
432 \r
433                         if (join != bs.join) {\r
434                                 return false;\r
435                         }\r
436 \r
437                         if (cap != bs.cap) {\r
438                                 return false;\r
439                         }\r
440 \r
441                         if (miterlimit != bs.miterlimit) {\r
442                                 return false;\r
443                         }\r
444 \r
445                         if (dash != null) {\r
446                                 if (dash_phase != bs.dash_phase) {\r
447                                         return false;\r
448                                 }\r
449 \r
450                                 if (!java.util.Arrays.equals(dash, bs.dash)) {\r
451                                         return false;\r
452                                 }\r
453                         }\r
454                         else if (bs.dash != null) {\r
455                                 return false;\r
456                         }\r
457 \r
458                         return true;\r
459                 }\r
460 \r
461                 public AffineTransform PenTransform { \r
462                         get{\r
463                                 return _penTransform;\r
464                         }\r
465                         set{\r
466                                 _penTransform = value;\r
467                         }\r
468                 }\r
469 \r
470                 public AffineTransform OutputTransform {\r
471                         get {\r
472                                 return _outputTransform;\r
473                         }\r
474                         set {\r
475                                 _outputTransform = value;\r
476                         }\r
477                 }\r
478 \r
479                 private static readonly int[] RasterizerCaps = {\r
480                                                                                                                    Rasterizer.BUTT, Rasterizer.ROUND, Rasterizer.SQUARE\r
481                                                                                                            };\r
482 \r
483                 private static readonly int[] RasterizerCorners = {\r
484                                                                                                                           Rasterizer.MITER, Rasterizer.ROUND, Rasterizer.BEVEL\r
485                                                                                                                   };\r
486 \r
487                 #region FillAdapter\r
488 \r
489                 private class FillAdapter : PathConsumer {\r
490                         bool closed;\r
491                         GeneralPath path;\r
492 \r
493                         public FillAdapter() {\r
494                                 path = new GeneralPath(GeneralPath.WIND_NON_ZERO);\r
495                         }\r
496 \r
497                         public Shape getShape() {\r
498                                 return path;\r
499                         }\r
500 \r
501                         public void beginPath() {}\r
502 \r
503                         public void beginSubpath(float x0, float y0) {\r
504                                 if (closed) {\r
505                                         path.closePath();\r
506                                         closed = false;\r
507                                 }\r
508                                 path.moveTo(x0, y0);\r
509                         }\r
510 \r
511                         public void appendLine(float x1, float y1) {\r
512                                 path.lineTo(x1, y1);\r
513                         }\r
514 \r
515                         public void appendQuadratic(float xm, float ym, float x1, float y1) {\r
516                                 path.quadTo(xm, ym, x1, y1);\r
517                         }\r
518 \r
519                         public void appendCubic(float xm, float ym,\r
520                                 float xn, float yn,\r
521                                 float x1, float y1) {\r
522                                 path.curveTo(xm, ym, xn, yn, x1, y1);\r
523                         }\r
524 \r
525                         public void closedSubpath() {\r
526                                 closed = true;\r
527                         }\r
528 \r
529                         public void endPath() {\r
530                                 if (closed) {\r
531                                         path.closePath();\r
532                                         closed = false;\r
533                                 }\r
534                         }\r
535 \r
536                         public void useProxy(FastPathProducer proxy) {\r
537                                 proxy.sendTo(this);\r
538                         }\r
539 \r
540                         public long getCPathConsumer() {\r
541                                 return 0;\r
542                         }\r
543 \r
544                         public void dispose() {\r
545                         }\r
546                 }\r
547 \r
548                 #endregion\r
549         }\r
550 }