VectSharp  2.2.1
A light library for C# vector graphics
GraphicsPath.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.Linq;
21 using System.Text;
22 
23 namespace VectSharp
24 {
25  /// <summary>
26  /// Represents a graphics path that can be filled or stroked.
27  /// </summary>
28  public class GraphicsPath
29  {
30  /// <summary>
31  /// The segments that make up the path.
32  /// </summary>
33  public List<Segment> Segments { get; set; } = new List<Segment>();
34 
35 
36  /// <summary>
37  /// Move the current point without tracing a segment from the previous point.
38  /// </summary>
39  /// <param name="p">The new point.</param>
40  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
42  {
43  cachedLength = double.NaN;
44  cachedBounds = Rectangle.NaN;
45  Segments.Add(new MoveSegment(p));
46  return this;
47  }
48 
49  /// <summary>
50  /// Move the current point without tracing a segment from the previous point.
51  /// </summary>
52  /// <param name="x">The horizontal coordinate of the new point.</param>
53  /// <param name="y">The vertical coordinate of the new point.</param>
54  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
55  public GraphicsPath MoveTo(double x, double y)
56  {
57  MoveTo(new Point(x, y));
58  return this;
59  }
60 
61  /// <summary>
62  /// Move the current point and trace a segment from the previous point.
63  /// </summary>
64  /// <param name="p">The new point.</param>
65  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
67  {
68  cachedLength = double.NaN;
69  cachedBounds = Rectangle.NaN;
70 
71  if (Segments.Count == 0)
72  {
73  Segments.Add(new MoveSegment(p));
74  }
75  else
76  {
77  Segments.Add(new LineSegment(p));
78  }
79  return this;
80  }
81 
82  /// <summary>
83  /// Move the current point and trace a segment from the previous point.
84  /// </summary>
85  /// <param name="x">The horizontal coordinate of the new point.</param>
86  /// <param name="y">The vertical coordinate of the new point.</param>
87  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
88  public GraphicsPath LineTo(double x, double y)
89  {
90  LineTo(new Point(x, y));
91  return this;
92  }
93 
94  /// <summary>
95  /// Trace an arc segment from a circle with the specified <paramref name="center"/> and <paramref name="radius"/>, starting at <paramref name="startAngle"/> and ending at <paramref name="endAngle"/>.
96  /// The current point is updated to the end point of the arc.
97  /// </summary>
98  /// <param name="center">The center of the arc.</param>
99  /// <param name="radius">The radius of the arc.</param>
100  /// <param name="startAngle">The start angle (in radians) of the arc.</param>
101  /// <param name="endAngle">The end angle (in radians) of the arc.</param>
102  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
103  public GraphicsPath Arc(Point center, double radius, double startAngle, double endAngle)
104  {
105  cachedLength = double.NaN;
106  cachedBounds = Rectangle.NaN;
107 
108  if (Segments.Count == 0)
109  {
110  Segments.Add(new MoveSegment(center.X + radius * Math.Cos(startAngle), center.Y + radius * Math.Sin(startAngle)));
111  }
112  Segments.Add(new ArcSegment(center, radius, startAngle, endAngle));
113  return this;
114  }
115 
116  /// <summary>
117  /// Trace an arc segment from a circle with the specified center and <paramref name="radius"/>, starting at <paramref name="startAngle"/> and ending at <paramref name="endAngle"/>.
118  /// The current point is updated to the end point of the arc.
119  /// </summary>
120  /// <param name="centerX">The horizontal coordinate of the center of the arc.</param>
121  /// <param name="centerY">The vertical coordinate of the center of the arc.</param>
122  /// <param name="radius">The radius of the arc.</param>
123  /// <param name="startAngle">The start angle (in radians) of the arc.</param>
124  /// <param name="endAngle">The end angle (in radians) of the arc.</param>
125  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
126  public GraphicsPath Arc(double centerX, double centerY, double radius, double startAngle, double endAngle)
127  {
128  Arc(new Point(centerX, centerY), radius, startAngle, endAngle);
129  return this;
130  }
131 
132  /// <summary>
133  /// Trace an arc from an ellipse with the specified radii, rotated by <paramref name="axisAngle"/> with respect to the x-axis, starting at the current point and ending at the <paramref name="endPoint"/>.
134  /// </summary>
135  /// <param name="radiusX">The horizontal radius of the ellipse.</param>
136  /// <param name="radiusY">The vertical radius of the ellipse.</param>
137  /// <param name="axisAngle">The angle of the horizontal axis of the ellipse with respect to the horizontal axis.</param>
138  /// <param name="largeArc">Determines whether the large or the small arc is drawn.</param>
139  /// <param name="sweepClockwise">Determines whether the clockwise or anticlockwise arc is drawn.</param>
140  /// <param name="endPoint">The end point of the arc.</param>
141  /// <returns></returns>
142  public GraphicsPath EllipticalArc(double radiusX, double radiusY, double axisAngle, bool largeArc, bool sweepClockwise, Point endPoint)
143  {
144  if (radiusX == 0 || radiusY == 0)
145  {
146  return this.LineTo(endPoint);
147  }
148  else
149  {
150  radiusX = Math.Abs(radiusX);
151  radiusY = Math.Abs(radiusY);
152  }
153 
154  double x1 = 0;
155  double y1 = 0;
156 
157  if (this.Segments.Count > 0)
158  {
159  for (int i = this.Segments.Count - 1; i >= 0; i--)
160  {
161  if (this.Segments[i].Type != SegmentType.Close)
162  {
163  x1 = this.Segments[i].Point.X;
164  y1 = this.Segments[i].Point.Y;
165  break;
166  }
167  }
168  }
169 
170  double x2 = endPoint.X;
171  double y2 = endPoint.Y;
172 
173  double x1P = Math.Cos(axisAngle) * (x1 - x2) * 0.5 + Math.Sin(axisAngle) * (y1 - y2) * 0.5;
174 
175  if (Math.Abs(x1P) < 1e-7)
176  {
177  x1P = 0;
178  }
179 
180  double y1P = -Math.Sin(axisAngle) * (x1 - x2) * 0.5 + Math.Cos(axisAngle) * (y1 - y2) * 0.5;
181 
182  if (Math.Abs(y1P) < 1e-7)
183  {
184  y1P = 0;
185  }
186 
187  double lambda = x1P * x1P / (radiusX * radiusX) + y1P * y1P / (radiusY * radiusY);
188 
189  if (lambda > 1)
190  {
191  double sqrtLambda = Math.Sqrt(lambda);
192  radiusX *= sqrtLambda;
193  radiusY *= sqrtLambda;
194  }
195 
196  double sqrtTerm = (largeArc != sweepClockwise ? 1 : -1) * Math.Sqrt(Math.Max(0, (radiusX * radiusX * radiusY * radiusY - radiusX * radiusX * y1P * y1P - radiusY * radiusY * x1P * x1P) / (radiusX * radiusX * y1P * y1P + radiusY * radiusY * x1P * x1P)));
197 
198  double cXP = sqrtTerm * radiusX * y1P / radiusY;
199  double cYP = -sqrtTerm * radiusY * x1P / radiusX;
200 
201  double cX = Math.Cos(axisAngle) * cXP - Math.Sin(axisAngle) * cYP + (x1 + x2) * 0.5;
202  double cY = Math.Sin(axisAngle) * cXP + Math.Cos(axisAngle) * cYP + (y1 + y2) * 0.5;
203 
204  double theta1 = AngleVectors(1, 0, (x1P - cXP) / radiusX, (y1P - cYP) / radiusY);
205  double deltaTheta = AngleVectors((x1P - cXP) / radiusX, (y1P - cYP) / radiusY, (-x1P - cXP) / radiusX, (-y1P - cYP) / radiusY) % (2 * Math.PI);
206 
207  if (!sweepClockwise && deltaTheta > 0)
208  {
209  deltaTheta -= 2 * Math.PI;
210  }
211  else if (sweepClockwise && deltaTheta < 0)
212  {
213  deltaTheta += 2 * Math.PI;
214  }
215 
216  double r = Math.Min(radiusX, radiusY);
217 
218  ArcSegment arc = new ArcSegment(0, 0, r, theta1, theta1 + deltaTheta);
219 
220  Segment[] segments = arc.ToBezierSegments();
221 
222  for (int i = 0; i < segments.Length; i++)
223  {
224  for (int j = 0; j < segments[i].Points.Length; j++)
225  {
226  double newX = segments[i].Points[j].X * radiusX / r;
227  double newY = segments[i].Points[j].Y * radiusY / r;
228 
229  segments[i].Points[j] = new Point(newX * Math.Cos(axisAngle) - newY * Math.Sin(axisAngle) + cX, newX * Math.Sin(axisAngle) + newY * Math.Cos(axisAngle) + cY);
230  }
231  }
232 
233  cachedLength = double.NaN;
234  cachedBounds = Rectangle.NaN;
235 
236  this.Segments.AddRange(segments);
237 
238  return this;
239  }
240 
241  private static double AngleVectors(double uX, double uY, double vX, double vY)
242  {
243  double tbr = Math.Acos((uX * vX + uY * vY) / Math.Sqrt((uX * uX + uY * uY) * (vX * vX + vY * vY)));
244  double sign = Math.Sign(uX * vY - uY * vX);
245  if (sign != 0)
246  {
247  tbr *= sign;
248  }
249  return tbr;
250  }
251 
252 
253  /// <summary>
254  /// Trace a cubic Bezier curve from the current point to a destination point, with two control points.
255  /// The current point is updated to the end point of the Bezier curve.
256  /// </summary>
257  /// <param name="control1">The first control point.</param>
258  /// <param name="control2">The second control point.</param>
259  /// <param name="endPoint">The destination point.</param>
260  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
261  public GraphicsPath CubicBezierTo(Point control1, Point control2, Point endPoint)
262  {
263  cachedLength = double.NaN;
264  cachedBounds = Rectangle.NaN;
265 
266  if (Segments.Count == 0)
267  {
268  Segments.Add(new MoveSegment(control1));
269  }
270  Segments.Add(new CubicBezierSegment(control1, control2, endPoint));
271  return this;
272  }
273 
274  /// <summary>
275  /// Trace a cubic Bezier curve from the current point to a destination point, with two control points.
276  /// The current point is updated to the end point of the Bezier curve.
277  /// </summary>
278  /// <param name="control1X">The horizontal coordinate of the first control point.</param>
279  /// <param name="control1Y">The vertical coordinate of the first control point.</param>
280  /// <param name="control2X">The horizontal coordinate of the second control point.</param>
281  /// <param name="control2Y">The vertical coordinate of the second control point.</param>
282  /// <param name="endPointX">The horizontal coordinate of the destination point.</param>
283  /// <param name="endPointY">The vertical coordinate of the destination point.</param>
284  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
285  public GraphicsPath CubicBezierTo(double control1X, double control1Y, double control2X, double control2Y, double endPointX, double endPointY)
286  {
287  CubicBezierTo(new Point(control1X, control1Y), new Point(control2X, control2Y), new Point(endPointX, endPointY));
288  return this;
289  }
290 
291  /// <summary>
292  /// Trace a segment from the current point to the start point of the figure and flag the figure as closed.
293  /// </summary>
294  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
296  {
297  cachedLength = double.NaN;
298  cachedBounds = Rectangle.NaN;
299  Segments.Add(new CloseSegment());
300  return this;
301  }
302 
303  /// <summary>
304  /// Add the contour of a text string to the current path.
305  /// </summary>
306  /// <param name="originX">The horizontal coordinate of the text origin.</param>
307  /// <param name="originY">The vertical coordinate of the text origin. See <paramref name="textBaseline"/>.</param>
308  /// <param name="text">The string to draw.</param>
309  /// <param name="font">The font with which to draw the text.</param>
310  /// <param name="textBaseline">The text baseline (determines what <paramref name="originY"/> represents).</param>
311  /// /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
312  public GraphicsPath AddText(double originX, double originY, string text, Font font, TextBaselines textBaseline = TextBaselines.Top)
313  {
314  return AddText(new Point(originX, originY), text, font, textBaseline);
315  }
316 
317  /// <summary>
318  /// Add the contour of a text string to the current path.
319  /// </summary>
320  /// <param name="origin">The text origin. See <paramref name="textBaseline"/>.</param>
321  /// <param name="text">The string to draw.</param>
322  /// <param name="font">The font with which to draw the text.</param>
323  /// <param name="textBaseline">The text baseline (determines what the vertical component of <paramref name="origin"/> represents).</param>
324  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
325  public GraphicsPath AddText(Point origin, string text, Font font, TextBaselines textBaseline = TextBaselines.Top)
326  {
327  Font.DetailedFontMetrics metrics = font.MeasureTextAdvanced(text);
328 
329  Point baselineOrigin = origin;
330 
331  switch (textBaseline)
332  {
333  case TextBaselines.Baseline:
334  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y);
335  break;
336  case TextBaselines.Top:
337  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y + metrics.Top);
338  break;
339  case TextBaselines.Bottom:
340  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y + metrics.Bottom);
341  break;
342  case TextBaselines.Middle:
343  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y + (metrics.Top - metrics.Bottom) * 0.5 + metrics.Bottom);
344  break;
345  }
346 
347  Point currentGlyphPlacementDelta = new Point();
348  Point currentGlyphAdvanceDelta = new Point();
349  Point nextGlyphPlacementDelta = new Point();
350  Point nextGlyphAdvanceDelta = new Point();
351 
352  for (int i = 0; i < text.Length; i++)
353  {
354  char c = text[i];
355 
356  if (Font.EnableKerning && i < text.Length - 1)
357  {
358  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
359  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
360  nextGlyphAdvanceDelta = new Point();
361  nextGlyphPlacementDelta = new Point();
362 
363  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(c, text[i + 1]);
364 
365  if (kerning != null)
366  {
367  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
368  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
369 
370  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
371  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
372  }
373  }
374 
376 
377  for (int j = 0; j < glyphPaths.Length; j++)
378  {
379  for (int k = 0; k < glyphPaths[j].Length; k++)
380  {
381  if (k == 0)
382  {
383  this.MoveTo(glyphPaths[j][k].X + baselineOrigin.X + currentGlyphPlacementDelta.X, -glyphPaths[j][k].Y + baselineOrigin.Y + currentGlyphPlacementDelta.Y);
384  }
385  else
386  {
387  if (glyphPaths[j][k].IsOnCurve)
388  {
389  this.LineTo(glyphPaths[j][k].X + baselineOrigin.X + currentGlyphPlacementDelta.X, -glyphPaths[j][k].Y + baselineOrigin.Y + currentGlyphPlacementDelta.Y);
390  }
391  else
392  {
393  Point startPoint = this.Segments.Last().Point;
394  Point quadCtrl = new Point(glyphPaths[j][k].X + baselineOrigin.X + currentGlyphPlacementDelta.X, -glyphPaths[j][k].Y + baselineOrigin.Y + currentGlyphPlacementDelta.Y);
395  Point endPoint = new Point(glyphPaths[j][k + 1].X + baselineOrigin.X + currentGlyphPlacementDelta.X, -glyphPaths[j][k + 1].Y + baselineOrigin.Y + currentGlyphPlacementDelta.Y);
396 
397 
398  Point ctrl1 = new Point(startPoint.X / 3 + 2 * quadCtrl.X / 3, startPoint.Y / 3 + 2 * quadCtrl.Y / 3);
399  Point ctrl2 = new Point(endPoint.X / 3 + 2 * quadCtrl.X / 3, endPoint.Y / 3 + 2 * quadCtrl.Y / 3);
400 
401  this.CubicBezierTo(ctrl1, ctrl2, endPoint);
402 
403  k++;
404  }
405  }
406  }
407 
408  this.Close();
409  }
410 
411  baselineOrigin.X += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
412  baselineOrigin.Y += (currentGlyphAdvanceDelta.Y) * font.FontSize / 1000;
413  }
414  return this;
415  }
416 
417  /// <summary>
418  /// Add the contour of a text string flowing along a <see cref="GraphicsPath"/> to the current path.
419  /// </summary>
420  /// <param name="path">The <see cref="GraphicsPath"/> along which the text will flow.</param>
421  /// <param name="text">The string to draw.</param>
422  /// <param name="font">The font with which to draw the text.</param>
423  /// <param name="reference">The (relative) starting point on the path starting from which the text should be drawn (0 is the start of the path, 1 is the end of the path).</param>
424  /// <param name="anchor">The anchor in the text string that will correspond to the point specified by the <paramref name="reference"/>.</param>
425  /// <param name="textBaseline">The text baseline (determines which the position of the text in relation to the <paramref name="path"/>.</param>
426  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
427  public GraphicsPath AddTextOnPath(GraphicsPath path, string text, Font font, double reference = 0, TextAnchors anchor = TextAnchors.Left, TextBaselines textBaseline = TextBaselines.Top)
428  {
429  cachedLength = double.NaN;
430  cachedBounds = Rectangle.NaN;
431 
432  double currDelta = 0;
433  double pathLength = path.MeasureLength();
434 
435  Font.DetailedFontMetrics fullMetrics = font.MeasureTextAdvanced(text);
436 
437  switch (anchor)
438  {
439  case TextAnchors.Left:
440  break;
441  case TextAnchors.Center:
442  currDelta = -fullMetrics.Width * 0.5 / pathLength;
443  break;
444  case TextAnchors.Right:
445  currDelta = -fullMetrics.Width / pathLength;
446  break;
447  }
448 
449  Point currentGlyphPlacementDelta = new Point();
450  Point currentGlyphAdvanceDelta = new Point();
451  Point nextGlyphPlacementDelta = new Point();
452  Point nextGlyphAdvanceDelta = new Point();
453 
454  for (int i = 0; i < text.Length; i++)
455  {
456  string c = text.Substring(i, 1);
457 
458  if (Font.EnableKerning && i < text.Length - 1)
459  {
460  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
461  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
462  nextGlyphAdvanceDelta = new Point();
463  nextGlyphPlacementDelta = new Point();
464 
465  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(text[i], text[i + 1]);
466 
467  if (kerning != null)
468  {
469  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
470  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
471 
472  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
473  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
474  }
475  }
476 
477  Font.DetailedFontMetrics metrics = font.MeasureTextAdvanced(c);
478 
479  Point origin = path.GetPointAtRelative(reference + currDelta + currentGlyphPlacementDelta.X * font.FontSize / 1000);
480 
481  Point tangent = path.GetTangentAtRelative(reference + currDelta + currentGlyphPlacementDelta.X * font.FontSize / 1000 + (metrics.Width + metrics.RightSideBearing + metrics.LeftSideBearing) / pathLength * 0.5);
482 
483  origin = new Point(origin.X - tangent.Y * currentGlyphPlacementDelta.Y * font.FontSize / 1000, origin.Y + tangent.X * currentGlyphPlacementDelta.Y * font.FontSize / 1000);
484 
485  GraphicsPath glyphPath = new GraphicsPath();
486 
487  switch (textBaseline)
488  {
489  case TextBaselines.Top:
490  if (i > 0)
491  {
492  glyphPath.AddText(new Point(metrics.LeftSideBearing, fullMetrics.Top), c, font, textBaseline: TextBaselines.Baseline);
493  }
494  else
495  {
496  glyphPath.AddText(new Point(0, fullMetrics.Top), c, font, textBaseline: TextBaselines.Baseline);
497  }
498  break;
499  case TextBaselines.Baseline:
500  if (i > 0)
501  {
502  glyphPath.AddText(new Point(metrics.LeftSideBearing, 0), c, font, textBaseline: TextBaselines.Baseline);
503  }
504  else
505  {
506  glyphPath.AddText(new Point(0, 0), c, font, textBaseline: TextBaselines.Baseline);
507  }
508  break;
509  case TextBaselines.Bottom:
510  if (i > 0)
511  {
512  glyphPath.AddText(new Point(metrics.LeftSideBearing, fullMetrics.Bottom), c, font, textBaseline: TextBaselines.Baseline);
513  }
514  else
515  {
516  glyphPath.AddText(new Point(0, fullMetrics.Bottom), c, font, textBaseline: TextBaselines.Baseline);
517  }
518  break;
519  case TextBaselines.Middle:
520  if (i > 0)
521  {
522  glyphPath.AddText(new Point(metrics.LeftSideBearing, fullMetrics.Bottom + fullMetrics.Height / 2), c, font, textBaseline: TextBaselines.Baseline);
523  }
524  else
525  {
526  glyphPath.AddText(new Point(0, fullMetrics.Bottom + fullMetrics.Height / 2), c, font, textBaseline: TextBaselines.Baseline);
527  }
528  break;
529  }
530 
531  double angle = Math.Atan2(tangent.Y, tangent.X);
532 
533  for (int j = 0; j < glyphPath.Segments.Count; j++)
534  {
535  if (glyphPath.Segments[j].Points != null)
536  {
537  for (int k = 0; k < glyphPath.Segments[j].Points.Length; k++)
538  {
539  double newX = glyphPath.Segments[j].Points[k].X * Math.Cos(angle) - glyphPath.Segments[j].Points[k].Y * Math.Sin(angle) + origin.X;
540  double newY = glyphPath.Segments[j].Points[k].X * Math.Sin(angle) + glyphPath.Segments[j].Points[k].Y * Math.Cos(angle) + origin.Y;
541 
542  glyphPath.Segments[j].Points[k] = new Point(newX, newY);
543  }
544  }
545 
546  this.Segments.Add(glyphPath.Segments[j]);
547  }
548 
549  if (i > 0)
550  {
551  currDelta += (metrics.Width + metrics.RightSideBearing + metrics.LeftSideBearing + currentGlyphAdvanceDelta.X * font.FontSize / 1000) / pathLength;
552  }
553  else
554  {
555  currDelta += (metrics.Width + metrics.RightSideBearing + currentGlyphAdvanceDelta.X * font.FontSize / 1000) / pathLength;
556  }
557  }
558 
559  return this;
560  }
561 
562 
563 
564  /// <summary>
565  /// Add the contour of the underline of the specified text string to the current path.
566  /// </summary>
567  /// <param name="origin">The text origin. See <paramref name="textBaseline"/>.</param>
568  /// <param name="text">The string whose underline will be drawn.</param>
569  /// <param name="font">The font with which to draw the text.</param>
570  /// <param name="textBaseline">The text baseline (determines what the vertical component of <paramref name="origin"/> represents).</param>
571  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
572  public GraphicsPath AddTextUnderline(Point origin, string text, Font font, TextBaselines textBaseline = TextBaselines.Top)
573  {
574  if (font.Underline == null)
575  {
576  return this;
577  }
578 
579  Font.DetailedFontMetrics metrics = font.MeasureTextAdvanced(text);
580 
581  double italicAngle = font.FontFamily.TrueTypeFile?.GetItalicAngle() ?? 0;
582 
583  if (double.IsNaN(italicAngle))
584  {
585  italicAngle = 0;
586  }
587 
588  Point baselineOrigin = origin;
589 
590  switch (textBaseline)
591  {
592  case TextBaselines.Baseline:
593  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y);
594  break;
595  case TextBaselines.Top:
596  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y + metrics.Top);
597  break;
598  case TextBaselines.Bottom:
599  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y + metrics.Bottom);
600  break;
601  case TextBaselines.Middle:
602  baselineOrigin = new Point(origin.X - metrics.LeftSideBearing, origin.Y + (metrics.Top - metrics.Bottom) * 0.5 + metrics.Bottom);
603  break;
604  }
605 
606  if (!font.Underline.SkipDescenders)
607  {
608  double italicShift;
609 
610  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
611  {
612  italicShift = 0;
613  }
614  else
615  {
616  italicShift = font.Underline.Thickness * font.FontSize * Math.Tan(italicAngle / 180.0 * Math.PI);
617  }
618 
619  if (font.Underline.LineCap == LineCaps.Butt)
620  {
621  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
622  {
623  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize);
624  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width, baselineOrigin.Y + font.Underline.Position * font.FontSize);
625  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
626  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
627  this.Close();
628  }
629  else
630  {
631  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize);
632  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width, baselineOrigin.Y + font.Underline.Position * font.FontSize);
633  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
634  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
635  this.Close();
636  }
637  }
638  else if (font.Underline.LineCap == LineCaps.Square)
639  {
640  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
641  {
642  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
643  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
644  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
645  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
646  this.Close();
647  }
648  else
649  {
650  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
651  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
652  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + font.Underline.Thickness * font.FontSize * 0.5 + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
653  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize * 0.5 + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
654  this.Close();
655  }
656  }
657  else if (font.Underline.LineCap == LineCaps.Round)
658  {
659  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
660  {
661  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize);
662  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width, baselineOrigin.Y + font.Underline.Position * font.FontSize);
663  this.Arc(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize * 0.5, font.Underline.Thickness * font.FontSize * 0.5, -Math.PI / 2, Math.PI / 2);
664  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
665  this.Arc(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize * 0.5, font.Underline.Thickness * font.FontSize * 0.5, Math.PI / 2, 3 * Math.PI / 2);
666  this.Close();
667  }
668  else
669  {
670  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing - italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize);
671  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width, baselineOrigin.Y + font.Underline.Position * font.FontSize);
672  this.CubicBezierTo(baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize,
673  baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + italicShift + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize,
674  baselineOrigin.X + metrics.LeftSideBearing + metrics.Width + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
675 
676  this.LineTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
677  this.CubicBezierTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize,
678  baselineOrigin.X + metrics.LeftSideBearing - italicShift - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize,
679  baselineOrigin.X + metrics.LeftSideBearing - italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize);
680 
681  this.Close();
682  }
683  }
684 
685  return this;
686  }
687  else
688  {
689  if (font.Underline.LineCap == LineCaps.Butt)
690  {
691  double italicShift;
692 
693  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
694  {
695  italicShift = 0;
696  }
697  else
698  {
699  italicShift = font.Underline.Thickness * font.FontSize * Math.Tan(italicAngle / 180.0 * Math.PI);
700  }
701 
702  bool started = false;
703 
704  double currX = baselineOrigin.X;
705  double underlineStartX = baselineOrigin.X + metrics.LeftSideBearing;
706  double currUnderlineX = underlineStartX - metrics.LeftSideBearing;
707 
708  Point currentGlyphPlacementDelta = new Point();
709  Point currentGlyphAdvanceDelta = new Point();
710  Point nextGlyphPlacementDelta = new Point();
711  Point nextGlyphAdvanceDelta = new Point();
712 
713  for (int i = 0; i < text.Length; i++)
714  {
715  char c = text[i];
716 
717  if (Font.EnableKerning && i < text.Length - 1)
718  {
719  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
720  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
721  nextGlyphAdvanceDelta = new Point();
722  nextGlyphPlacementDelta = new Point();
723 
724  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(c, text[i + 1]);
725 
726  if (kerning != null)
727  {
728  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
729  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
730 
731  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
732  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
733  }
734  }
735 
736  double[] intersections = font.FontFamily.TrueTypeFile.Get1000EmUnderlineIntersections(c, font.Underline.Position * 1000, font.Underline.Thickness * 1000);
737 
738  if (intersections != null)
739  {
740  intersections[0] = intersections[0] * font.FontSize / 1000;
741  intersections[1] = intersections[1] * font.FontSize / 1000;
742 
743  if (currX + intersections[0] - font.Underline.Thickness * font.FontSize >= underlineStartX)
744  {
745  if (!started)
746  {
747  started = true;
748  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize).LineTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize);
749  }
750 
751  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
752  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
753  this.Close();
754  }
755 
756  started = true;
757 
758  this.MoveTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
759  this.LineTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
760 
761  underlineStartX = currX + intersections[1] + font.Underline.Thickness * font.FontSize;
762  currUnderlineX = Math.Max(currX + intersections[1] + font.Underline.Thickness * font.FontSize, currX + (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000);
763  }
764  else if (i == text.Length - 1)
765  {
766  if (c != ' ')
767  {
768  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000;
769  }
770  else
771  {
772  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
773  }
774  }
775  else
776  {
777  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
778  }
779 
780  currX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
781  }
782 
783  if (!started)
784  {
785  started = true;
786  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize).LineTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize);
787  }
788 
789  this.LineTo(currUnderlineX, baselineOrigin.Y + font.Underline.Position * font.FontSize);
790  this.LineTo(currUnderlineX + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
791  this.LineTo(underlineStartX + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
792  this.Close();
793  }
794  else if (font.Underline.LineCap == LineCaps.Square)
795  {
796  double italicShift;
797 
798  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
799  {
800  italicShift = 0;
801  }
802  else
803  {
804  italicShift = font.Underline.Thickness * font.FontSize * Math.Tan(italicAngle / 180.0 * Math.PI);
805  }
806 
807  bool started = false;
808 
809  double currX = baselineOrigin.X;
810  double underlineStartX = baselineOrigin.X + metrics.LeftSideBearing;
811  double currUnderlineX = underlineStartX - metrics.LeftSideBearing;
812 
813  Point currentGlyphPlacementDelta = new Point();
814  Point currentGlyphAdvanceDelta = new Point();
815  Point nextGlyphPlacementDelta = new Point();
816  Point nextGlyphAdvanceDelta = new Point();
817 
818  for (int i = 0; i < text.Length; i++)
819  {
820  char c = text[i];
821 
822  if (Font.EnableKerning && i < text.Length - 1)
823  {
824  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
825  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
826  nextGlyphAdvanceDelta = new Point();
827  nextGlyphPlacementDelta = new Point();
828 
829  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(c, text[i + 1]);
830 
831  if (kerning != null)
832  {
833  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
834  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
835 
836  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
837  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
838  }
839  }
840 
841  double[] intersections = font.FontFamily.TrueTypeFile.Get1000EmUnderlineIntersections(c, font.Underline.Position * 1000, font.Underline.Thickness * 1000);
842 
843  if (intersections != null)
844  {
845  intersections[0] = intersections[0] * font.FontSize / 1000;
846  intersections[1] = intersections[1] * font.FontSize / 1000;
847 
848  if (currX + intersections[0] - font.Underline.Thickness * font.FontSize >= underlineStartX)
849  {
850  if (!started)
851  {
852  started = true;
853  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing + italicShift - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize).LineTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
854  }
855  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
856  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
857 
858  this.Close();
859  }
860 
861  started = true;
862 
863  this.MoveTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
864  this.LineTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
865 
866  underlineStartX = currX + intersections[1] + font.Underline.Thickness * font.FontSize;
867  currUnderlineX = Math.Max(currX + intersections[1] + font.Underline.Thickness * font.FontSize, currX + (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000);
868  }
869  else if (i == text.Length - 1)
870  {
871  if (c != ' ')
872  {
873  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000;
874  }
875  else
876  {
877  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
878  }
879  }
880  else
881  {
882  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
883  }
884 
885  currX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
886  }
887 
888  if (!started)
889  {
890  started = true;
891  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing + italicShift - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize).LineTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
892  }
893 
894  this.LineTo(currUnderlineX + font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize);
895  this.LineTo(currUnderlineX + italicShift + font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
896  this.LineTo(underlineStartX + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
897  this.Close();
898  }
899  else if (font.Underline.LineCap == LineCaps.Round)
900  {
901  if (!font.Underline.FollowItalicAngle || italicAngle == 0)
902  {
903  bool started = false;
904 
905  double currX = baselineOrigin.X;
906  double underlineStartX = baselineOrigin.X + metrics.LeftSideBearing;
907  double currUnderlineX = underlineStartX - metrics.LeftSideBearing;
908 
909  Point currentGlyphPlacementDelta = new Point();
910  Point currentGlyphAdvanceDelta = new Point();
911  Point nextGlyphPlacementDelta = new Point();
912  Point nextGlyphAdvanceDelta = new Point();
913 
914  for (int i = 0; i < text.Length; i++)
915  {
916  char c = text[i];
917 
918  if (Font.EnableKerning && i < text.Length - 1)
919  {
920  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
921  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
922  nextGlyphAdvanceDelta = new Point();
923  nextGlyphPlacementDelta = new Point();
924 
925  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(c, text[i + 1]);
926 
927  if (kerning != null)
928  {
929  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
930  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
931 
932  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
933  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
934  }
935  }
936 
937  double[] intersections = font.FontFamily.TrueTypeFile.Get1000EmUnderlineIntersections(c, font.Underline.Position * 1000, font.Underline.Thickness * 1000);
938 
939  if (intersections != null)
940  {
941  intersections[0] = intersections[0] * font.FontSize / 1000;
942  intersections[1] = intersections[1] * font.FontSize / 1000;
943 
944  if (currX + intersections[0] - font.Underline.Thickness * font.FontSize >= underlineStartX)
945  {
946  if (!started)
947  {
948  started = true;
949  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
950  this.Arc(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize * 0.5, font.Underline.Thickness * font.FontSize * 0.5, Math.PI / 2, 3 * Math.PI / 2);
951  }
952 
953  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
954  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
955 
956  this.Close();
957  }
958 
959  started = true;
960 
961  this.MoveTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
962  this.LineTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
963 
964  underlineStartX = currX + intersections[1] + font.Underline.Thickness * font.FontSize;
965  currUnderlineX = Math.Max(currX + intersections[1] + font.Underline.Thickness * font.FontSize, currX + (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000);
966  }
967  else if (i == text.Length - 1)
968  {
969  if (c != ' ')
970  {
971  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000;
972  }
973  else
974  {
975  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
976  }
977  }
978  else
979  {
980  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
981  }
982 
983  currX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
984  }
985 
986  if (!started)
987  {
988  started = true;
989  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
990  this.Arc(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize * 0.5, font.Underline.Thickness * font.FontSize * 0.5, Math.PI / 2, 3 * Math.PI / 2);
991  }
992 
993  this.LineTo(currUnderlineX, baselineOrigin.Y + font.Underline.Position * font.FontSize);
994 
995  this.Arc(currUnderlineX, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize * 0.5, font.Underline.Thickness * font.FontSize * 0.5, -Math.PI / 2, Math.PI / 2);
996 
997  this.LineTo(underlineStartX, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
998  this.Close();
999  }
1000  else
1001  {
1002  double italicShift = font.Underline.Thickness * font.FontSize * Math.Tan(italicAngle / 180.0 * Math.PI);
1003 
1004  bool started = false;
1005 
1006  double currX = baselineOrigin.X;
1007  double underlineStartX = baselineOrigin.X + metrics.LeftSideBearing;
1008  double currUnderlineX = underlineStartX - metrics.LeftSideBearing;
1009 
1010  Point currentGlyphPlacementDelta = new Point();
1011  Point currentGlyphAdvanceDelta = new Point();
1012  Point nextGlyphPlacementDelta = new Point();
1013  Point nextGlyphAdvanceDelta = new Point();
1014 
1015  for (int i = 0; i < text.Length; i++)
1016  {
1017  char c = text[i];
1018 
1019  if (Font.EnableKerning && i < text.Length - 1)
1020  {
1021  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
1022  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
1023  nextGlyphAdvanceDelta = new Point();
1024  nextGlyphPlacementDelta = new Point();
1025 
1026  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(c, text[i + 1]);
1027 
1028  if (kerning != null)
1029  {
1030  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
1031  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
1032 
1033  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
1034  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
1035  }
1036  }
1037 
1038  double[] intersections = font.FontFamily.TrueTypeFile.Get1000EmUnderlineIntersections(c, font.Underline.Position * 1000, font.Underline.Thickness * 1000);
1039 
1040  if (intersections != null)
1041  {
1042  intersections[0] = intersections[0] * font.FontSize / 1000;
1043  intersections[1] = intersections[1] * font.FontSize / 1000;
1044 
1045  if (currX + intersections[0] - font.Underline.Thickness * font.FontSize >= underlineStartX)
1046  {
1047  if (!started)
1048  {
1049  started = true;
1050 
1051  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1052 
1053  this.CubicBezierTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize,
1054  baselineOrigin.X + metrics.LeftSideBearing - italicShift - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize,
1055  baselineOrigin.X + metrics.LeftSideBearing - italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize);
1056  }
1057 
1058  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
1059  this.LineTo(currX + intersections[0] - font.Underline.Thickness * font.FontSize + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1060 
1061  this.Close();
1062  }
1063 
1064  started = true;
1065 
1066  this.MoveTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1067  this.LineTo(currX + intersections[1] + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize);
1068 
1069  underlineStartX = currX + intersections[1] + font.Underline.Thickness * font.FontSize;
1070  currUnderlineX = Math.Max(currX + intersections[1] + font.Underline.Thickness * font.FontSize, currX + (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000);
1071  }
1072  else if (i == text.Length - 1)
1073  {
1074  if (c != ' ')
1075  {
1076  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000 - font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(c).RightSideBearing * font.FontSize / 1000;
1077  }
1078  else
1079  {
1080  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
1081  }
1082  }
1083  else
1084  {
1085  currUnderlineX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
1086  }
1087 
1088  currX += (font.FontFamily.TrueTypeFile.Get1000EmGlyphWidth(c) + currentGlyphAdvanceDelta.X) * font.FontSize / 1000;
1089  }
1090 
1091  if (!started)
1092  {
1093  started = true;
1094 
1095  this.MoveTo(baselineOrigin.X + metrics.LeftSideBearing, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1096 
1097  this.CubicBezierTo(baselineOrigin.X + metrics.LeftSideBearing - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize,
1098  baselineOrigin.X + metrics.LeftSideBearing - italicShift - font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize,
1099  baselineOrigin.X + metrics.LeftSideBearing - italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize);
1100  }
1101 
1102  this.LineTo(currUnderlineX, baselineOrigin.Y + font.Underline.Position * font.FontSize);
1103 
1104  //this.LineTo(currUnderlineX + italicShift + font.Underline.Thickness * font.FontSize * 0.5, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1105  this.CubicBezierTo(currUnderlineX + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize,
1106  currUnderlineX + italicShift + font.Underline.Thickness * font.FontSize, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize,
1107  currUnderlineX + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1108 
1109  this.LineTo(underlineStartX + italicShift, baselineOrigin.Y + font.Underline.Position * font.FontSize + font.Underline.Thickness * font.FontSize);
1110  this.Close();
1111  }
1112  }
1113  }
1114  return this;
1115  }
1116 
1117 
1118  /// <summary>
1119  /// Adds a smooth spline composed of cubic bezier segments that pass through the specified points.
1120  /// </summary>
1121  /// <param name="points">The points through which the spline should pass.</param>
1122  /// <returns>The <see cref="GraphicsPath"/>, to allow for chained calls.</returns>
1123  public GraphicsPath AddSmoothSpline(params Point[] points)
1124  {
1125  if (points.Length == 0)
1126  {
1127  return this;
1128  }
1129  else if (points.Length == 1)
1130  {
1131  return this.LineTo(points[0]);
1132  }
1133  else if (points.Length == 2)
1134  {
1135  return this.LineTo(points[0]).LineTo(points[1]);
1136  }
1137 
1138  Point[] smoothedSpline = SmoothSpline.SmoothSplines(points);
1139 
1140  this.LineTo(smoothedSpline[0]);
1141 
1142  for (int i = 1; i < smoothedSpline.Length; i += 3)
1143  {
1144  this.CubicBezierTo(smoothedSpline[i], smoothedSpline[i + 1], smoothedSpline[i + 2]);
1145  }
1146 
1147  return this;
1148  }
1149 
1150  private double cachedLength = double.NaN;
1151 
1152  /// <summary>
1153  /// Measures the length of the <see cref="GraphicsPath"/>.
1154  /// </summary>
1155  /// <returns>The length of the <see cref="GraphicsPath"/></returns>
1156  public double MeasureLength()
1157  {
1158  if (double.IsNaN(cachedLength))
1159  {
1160  cachedLength = 0;
1161  Point currPoint = new Point();
1162  Point figureStartPoint = new Point();
1163 
1164  for (int i = 0; i < this.Segments.Count; i++)
1165  {
1166  switch (this.Segments[i].Type)
1167  {
1168  case SegmentType.Move:
1169  currPoint = this.Segments[i].Point;
1170  figureStartPoint = this.Segments[i].Point;
1171  break;
1172  case SegmentType.Line:
1173  if (i > 0)
1174  {
1175  cachedLength += this.Segments[i].Measure(currPoint);
1176  currPoint = this.Segments[i].Point;
1177  }
1178  else
1179  {
1180  currPoint = this.Segments[i].Point;
1181  figureStartPoint = this.Segments[i].Point;
1182  }
1183  break;
1184  case SegmentType.Arc:
1185  if (i > 0)
1186  {
1187  cachedLength += this.Segments[i].Measure(currPoint);
1188  currPoint = this.Segments[i].Point;
1189  }
1190  else
1191  {
1192  ArcSegment seg = (ArcSegment)this.Segments[i];
1193  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1194  cachedLength += this.Segments[i].Measure(figureStartPoint);
1195  currPoint = this.Segments[i].Point;
1196  }
1197  break;
1198  case SegmentType.Close:
1199  cachedLength += Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1200  currPoint = figureStartPoint;
1201  break;
1202  case SegmentType.CubicBezier:
1203  if (i > 0)
1204  {
1205  cachedLength += this.Segments[i].Measure(currPoint);
1206  currPoint = this.Segments[i].Point;
1207  }
1208  else
1209  {
1210  currPoint = this.Segments[i].Points[0];
1211  figureStartPoint = this.Segments[i].Points[0];
1212  cachedLength += this.Segments[i].Measure(currPoint);
1213  currPoint = this.Segments[i].Point;
1214  }
1215  break;
1216  }
1217  }
1218  }
1219 
1220  return cachedLength;
1221  }
1222 
1223  /// <summary>
1224  /// Gets the point at the relative position specified on the <see cref="GraphicsPath"/>.
1225  /// </summary>
1226  /// <param name="position">The position on the <see cref="GraphicsPath"/> (0 is the start of the path, 1 is the end of the path).</param>
1227  /// <returns>The point at the specified position.</returns>
1228  public Point GetPointAtRelative(double position)
1229  {
1230  return GetPointAtAbsolute(position * this.MeasureLength());
1231  }
1232 
1233  /// <summary>
1234  /// Gets the point at the absolute position specified on the <see cref="GraphicsPath"/>.
1235  /// </summary>
1236  /// <param name="length">The distance to the point from the start of the <see cref="GraphicsPath"/>.</param>
1237  /// <returns>The point at the specified position.</returns>
1238  public Point GetPointAtAbsolute(double length)
1239  {
1240  double pathLength = this.MeasureLength();
1241 
1242  if (length >= 0 && length <= pathLength)
1243  {
1244  double currLen = 0;
1245 
1246  Point currPoint = new Point();
1247  Point figureStartPoint = new Point();
1248 
1249  for (int i = 0; i < this.Segments.Count; i++)
1250  {
1251  switch (this.Segments[i].Type)
1252  {
1253  case SegmentType.Move:
1254  currPoint = this.Segments[i].Point;
1255  figureStartPoint = this.Segments[i].Point;
1256  break;
1257  case SegmentType.Line:
1258  if (i > 0)
1259  {
1260  double segLength = this.Segments[i].Measure(currPoint);
1261 
1262  if (currLen + segLength < length)
1263  {
1264  currLen += segLength;
1265  currPoint = this.Segments[i].Point;
1266  }
1267  else
1268  {
1269  double pos = (length - currLen) / segLength;
1270  return this.Segments[i].GetPointAt(currPoint, pos);
1271  }
1272  }
1273  else
1274  {
1275  currPoint = this.Segments[i].Point;
1276  figureStartPoint = this.Segments[i].Point;
1277  }
1278  break;
1279  case SegmentType.Arc:
1280  if (i > 0)
1281  {
1282  double segLength = this.Segments[i].Measure(currPoint);
1283 
1284  if (currLen + segLength < length)
1285  {
1286  currLen += segLength;
1287  currPoint = this.Segments[i].Point;
1288  }
1289  else
1290  {
1291  double pos = (length - currLen) / segLength;
1292  return this.Segments[i].GetPointAt(currPoint, pos);
1293  }
1294  }
1295  else
1296  {
1297  ArcSegment seg = (ArcSegment)this.Segments[i];
1298  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1299  currPoint = figureStartPoint;
1300 
1301  double segLength = this.Segments[i].Measure(currPoint);
1302 
1303  if (currLen + segLength < length)
1304  {
1305  currLen += segLength;
1306  currPoint = this.Segments[i].Point;
1307  }
1308  else
1309  {
1310  double pos = (length - currLen) / segLength;
1311  return this.Segments[i].GetPointAt(currPoint, pos);
1312  }
1313  }
1314  break;
1315  case SegmentType.Close:
1316  {
1317  double segLength = Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1318 
1319  if (currLen + segLength < length)
1320  {
1321  currLen += segLength;
1322  currPoint = figureStartPoint;
1323  }
1324  else
1325  {
1326  double pos = (length - currLen) / segLength;
1327  return new Point(currPoint.X * (1 - pos) + figureStartPoint.X * pos, currPoint.Y * (1 - pos) + figureStartPoint.Y * pos);
1328  }
1329  }
1330  break;
1331  case SegmentType.CubicBezier:
1332  if (i > 0)
1333  {
1334  double segLength = this.Segments[i].Measure(currPoint);
1335 
1336  if (currLen + segLength < length)
1337  {
1338  currLen += segLength;
1339  currPoint = this.Segments[i].Point;
1340  }
1341  else
1342  {
1343  double pos = (length - currLen) / segLength;
1344  return this.Segments[i].GetPointAt(currPoint, pos);
1345  }
1346  }
1347  else
1348  {
1349  currPoint = this.Segments[i].Points[0];
1350  figureStartPoint = this.Segments[i].Points[0];
1351  double segLength = this.Segments[i].Measure(currPoint);
1352 
1353  if (currLen + segLength < length)
1354  {
1355  currLen += segLength;
1356  currPoint = this.Segments[i].Point;
1357  }
1358  else
1359  {
1360  double pos = (length - currLen) / segLength;
1361  return this.Segments[i].GetPointAt(currPoint, pos);
1362  }
1363  }
1364  break;
1365  }
1366  }
1367 
1368  throw new InvalidOperationException("Unexpected code path!");
1369  }
1370  else if (length > pathLength)
1371  {
1372  double currLength = 0;
1373 
1374  Point currPoint = new Point();
1375  Point figureStartPoint = new Point();
1376 
1377  for (int i = 0; i < this.Segments.Count - 1; i++)
1378  {
1379  switch (this.Segments[i].Type)
1380  {
1381  case SegmentType.Move:
1382  currPoint = this.Segments[i].Point;
1383  figureStartPoint = this.Segments[i].Point;
1384  break;
1385  case SegmentType.Line:
1386  if (i > 0)
1387  {
1388  currLength += this.Segments[i].Measure(currPoint);
1389  currPoint = this.Segments[i].Point;
1390  }
1391  else
1392  {
1393  currPoint = this.Segments[i].Point;
1394  figureStartPoint = this.Segments[i].Point;
1395  }
1396  break;
1397  case SegmentType.Arc:
1398  if (i > 0)
1399  {
1400  currLength += this.Segments[i].Measure(currPoint);
1401  currPoint = this.Segments[i].Point;
1402  }
1403  else
1404  {
1405  ArcSegment seg = (ArcSegment)this.Segments[i];
1406  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1407  currLength += this.Segments[i].Measure(figureStartPoint);
1408  currPoint = this.Segments[i].Point;
1409  }
1410  break;
1411  case SegmentType.Close:
1412  currLength += Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1413  currPoint = figureStartPoint;
1414  break;
1415  case SegmentType.CubicBezier:
1416  if (i > 0)
1417  {
1418  currLength += this.Segments[i].Measure(currPoint);
1419  currPoint = this.Segments[i].Point;
1420  }
1421  else
1422  {
1423  currPoint = this.Segments[i].Points[0];
1424  figureStartPoint = this.Segments[i].Points[0];
1425  currLength += this.Segments[i].Measure(currPoint);
1426  currPoint = this.Segments[i].Point;
1427  }
1428  break;
1429  }
1430  }
1431 
1432  switch (this.Segments[this.Segments.Count - 1].Type)
1433  {
1434  case SegmentType.Arc:
1435  case SegmentType.CubicBezier:
1436  case SegmentType.Line:
1437  {
1438  double pos = 1 + (length - pathLength) / this.Segments[this.Segments.Count - 1].Measure(currPoint);
1439  return this.Segments[this.Segments.Count - 1].GetPointAt(currPoint, pos);
1440  }
1441  case SegmentType.Move:
1442  return currPoint;
1443  case SegmentType.Close:
1444  return this.GetPointAtAbsolute(length - pathLength);
1445  }
1446 
1447  throw new InvalidOperationException("Unexpected code path!");
1448  }
1449  else
1450  {
1451  Point currPoint = new Point();
1452  Point figureStartPoint = new Point();
1453 
1454  for (int i = 0; i < this.Segments.Count; i++)
1455  {
1456  switch (this.Segments[i].Type)
1457  {
1458  case SegmentType.Move:
1459  currPoint = this.Segments[i].Point;
1460  figureStartPoint = this.Segments[i].Point;
1461  break;
1462  case SegmentType.Line:
1463  if (i > 0)
1464  {
1465  double segLength = this.Segments[i].Measure(currPoint);
1466  double pos = length / segLength;
1467  return this.Segments[i].GetPointAt(currPoint, pos);
1468  }
1469  else
1470  {
1471  currPoint = this.Segments[i].Point;
1472  figureStartPoint = this.Segments[i].Point;
1473  }
1474  break;
1475  case SegmentType.Arc:
1476  if (i > 0)
1477  {
1478  double segLength = this.Segments[i].Measure(currPoint);
1479  double pos = length / segLength;
1480  return this.Segments[i].GetPointAt(currPoint, pos);
1481  }
1482  else
1483  {
1484  ArcSegment seg = (ArcSegment)this.Segments[i];
1485  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1486  currPoint = figureStartPoint;
1487 
1488  double segLength = this.Segments[i].Measure(currPoint);
1489  double pos = length / segLength;
1490  return this.Segments[i].GetPointAt(currPoint, pos);
1491  }
1492  case SegmentType.Close:
1493  {
1494  double segLength = Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1495  double pos = length / segLength;
1496  return new Point(currPoint.X * (1 - pos) + figureStartPoint.X * pos, currPoint.Y * (1 - pos) + figureStartPoint.Y * pos);
1497  }
1498  case SegmentType.CubicBezier:
1499  if (i > 0)
1500  {
1501  double segLength = this.Segments[i].Measure(currPoint);
1502  double pos = length / segLength;
1503  return this.Segments[i].GetPointAt(currPoint, pos);
1504  }
1505  else
1506  {
1507  currPoint = this.Segments[i].Points[0];
1508  figureStartPoint = this.Segments[i].Points[0];
1509  double segLength = this.Segments[i].Measure(currPoint);
1510  double pos = length / segLength;
1511  return this.Segments[i].GetPointAt(currPoint, pos);
1512  }
1513  }
1514  }
1515 
1516  throw new InvalidOperationException("Unexpected code path!");
1517  }
1518  }
1519 
1520  /// <summary>
1521  /// Gets the tangent to the point at the relative position specified on the <see cref="GraphicsPath"/>.
1522  /// </summary>
1523  /// <param name="position">The position on the <see cref="GraphicsPath"/> (0 is the start of the path, 1 is the end of the path).</param>
1524  /// <returns>The tangent to the point at the specified position.</returns>
1525  public Point GetTangentAtRelative(double position)
1526  {
1527  return GetTangentAtAbsolute(position * this.MeasureLength());
1528  }
1529 
1530  /// <summary>
1531  /// Gets the tangent to the point at the absolute position specified on the <see cref="GraphicsPath"/>.
1532  /// </summary>
1533  /// <param name="length">The distance to the point from the start of the <see cref="GraphicsPath"/>.</param>
1534  /// <returns>The tangent to the point at the specified position.</returns>
1535  public Point GetTangentAtAbsolute(double length)
1536  {
1537  double pathLength = this.MeasureLength();
1538 
1539  if (length >= 0 && length <= pathLength)
1540  {
1541  double currLen = 0;
1542 
1543  Point currPoint = new Point();
1544  Point figureStartPoint = new Point();
1545 
1546  for (int i = 0; i < this.Segments.Count; i++)
1547  {
1548  switch (this.Segments[i].Type)
1549  {
1550  case SegmentType.Move:
1551  currPoint = this.Segments[i].Point;
1552  figureStartPoint = this.Segments[i].Point;
1553  break;
1554  case SegmentType.Line:
1555  if (i > 0)
1556  {
1557  double segLength = this.Segments[i].Measure(currPoint);
1558 
1559  if (currLen + segLength < length)
1560  {
1561  currLen += segLength;
1562  currPoint = this.Segments[i].Point;
1563  }
1564  else
1565  {
1566  double pos = (length - currLen) / segLength;
1567  return this.Segments[i].GetTangentAt(currPoint, pos);
1568  }
1569  }
1570  else
1571  {
1572  currPoint = this.Segments[i].Point;
1573  figureStartPoint = this.Segments[i].Point;
1574  }
1575  break;
1576  case SegmentType.Arc:
1577  if (i > 0)
1578  {
1579  double segLength = this.Segments[i].Measure(currPoint);
1580 
1581  if (currLen + segLength < length)
1582  {
1583  currLen += segLength;
1584  currPoint = this.Segments[i].Point;
1585  }
1586  else
1587  {
1588  double pos = (length - currLen) / segLength;
1589  return this.Segments[i].GetTangentAt(currPoint, pos);
1590  }
1591  }
1592  else
1593  {
1594  ArcSegment seg = (ArcSegment)this.Segments[i];
1595  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1596  currPoint = figureStartPoint;
1597 
1598  double segLength = this.Segments[i].Measure(currPoint);
1599 
1600  if (currLen + segLength < length)
1601  {
1602  currLen += segLength;
1603  currPoint = this.Segments[i].Point;
1604  }
1605  else
1606  {
1607  double pos = (length - currLen) / segLength;
1608  return this.Segments[i].GetTangentAt(currPoint, pos);
1609  }
1610  }
1611  break;
1612  case SegmentType.Close:
1613  {
1614  double segLength = Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1615 
1616  if (currLen + segLength < length)
1617  {
1618  currLen += segLength;
1619  currPoint = figureStartPoint;
1620  }
1621  else
1622  {
1623  double pos = (length - currLen) / segLength;
1624  return new Point(figureStartPoint.X - currPoint.X, figureStartPoint.Y - currPoint.Y).Normalize();
1625  }
1626  }
1627  break;
1628  case SegmentType.CubicBezier:
1629  if (i > 0)
1630  {
1631  double segLength = this.Segments[i].Measure(currPoint);
1632 
1633  if (currLen + segLength < length)
1634  {
1635  currLen += segLength;
1636  currPoint = this.Segments[i].Point;
1637  }
1638  else
1639  {
1640  double pos = (length - currLen) / segLength;
1641  return this.Segments[i].GetTangentAt(currPoint, pos);
1642  }
1643  }
1644  else
1645  {
1646  currPoint = this.Segments[i].Points[0];
1647  figureStartPoint = this.Segments[i].Points[0];
1648  double segLength = this.Segments[i].Measure(currPoint);
1649 
1650  if (currLen + segLength < length)
1651  {
1652  currLen += segLength;
1653  currPoint = this.Segments[i].Point;
1654  }
1655  else
1656  {
1657  double pos = (length - currLen) / segLength;
1658  return this.Segments[i].GetTangentAt(currPoint, pos);
1659  }
1660  }
1661  break;
1662  }
1663  }
1664 
1665  throw new InvalidOperationException("Unexpected code path!");
1666  }
1667  else if (length > pathLength)
1668  {
1669  double currLength = 0;
1670 
1671  Point currPoint = new Point();
1672  Point figureStartPoint = new Point();
1673 
1674  for (int i = 0; i < this.Segments.Count - 1; i++)
1675  {
1676  switch (this.Segments[i].Type)
1677  {
1678  case SegmentType.Move:
1679  currPoint = this.Segments[i].Point;
1680  figureStartPoint = this.Segments[i].Point;
1681  break;
1682  case SegmentType.Line:
1683  if (i > 0)
1684  {
1685  currLength += this.Segments[i].Measure(currPoint);
1686  currPoint = this.Segments[i].Point;
1687  }
1688  else
1689  {
1690  currPoint = this.Segments[i].Point;
1691  figureStartPoint = this.Segments[i].Point;
1692  }
1693  break;
1694  case SegmentType.Arc:
1695  if (i > 0)
1696  {
1697  currLength += this.Segments[i].Measure(currPoint);
1698  currPoint = this.Segments[i].Point;
1699  }
1700  else
1701  {
1702  ArcSegment seg = (ArcSegment)this.Segments[i];
1703  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1704  currLength += this.Segments[i].Measure(figureStartPoint);
1705  currPoint = this.Segments[i].Point;
1706  }
1707  break;
1708  case SegmentType.Close:
1709  currLength += Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1710  currPoint = figureStartPoint;
1711  break;
1712  case SegmentType.CubicBezier:
1713  if (i > 0)
1714  {
1715  currLength += this.Segments[i].Measure(currPoint);
1716  currPoint = this.Segments[i].Point;
1717  }
1718  else
1719  {
1720  currPoint = this.Segments[i].Points[0];
1721  figureStartPoint = this.Segments[i].Points[0];
1722  currLength += this.Segments[i].Measure(currPoint);
1723  currPoint = this.Segments[i].Point;
1724  }
1725  break;
1726  }
1727  }
1728 
1729  switch (this.Segments[this.Segments.Count - 1].Type)
1730  {
1731  case SegmentType.Arc:
1732  case SegmentType.CubicBezier:
1733  case SegmentType.Line:
1734  {
1735  double pos = 1 + (length - pathLength) / this.Segments[this.Segments.Count - 1].Measure(currPoint);
1736  return this.Segments[this.Segments.Count - 1].GetTangentAt(currPoint, pos);
1737  }
1738  case SegmentType.Move:
1739  return new Point();
1740  case SegmentType.Close:
1741  return this.GetTangentAtAbsolute(length - pathLength);
1742  }
1743 
1744  throw new InvalidOperationException("Unexpected code path!");
1745  }
1746  else
1747  {
1748  Point currPoint = new Point();
1749  Point figureStartPoint = new Point();
1750 
1751  for (int i = 0; i < this.Segments.Count; i++)
1752  {
1753  switch (this.Segments[i].Type)
1754  {
1755  case SegmentType.Move:
1756  currPoint = this.Segments[i].Point;
1757  figureStartPoint = this.Segments[i].Point;
1758  break;
1759  case SegmentType.Line:
1760  if (i > 0)
1761  {
1762  double segLength = this.Segments[i].Measure(currPoint);
1763  double pos = length / segLength;
1764  return this.Segments[i].GetTangentAt(currPoint, pos);
1765  }
1766  else
1767  {
1768  currPoint = this.Segments[i].Point;
1769  figureStartPoint = this.Segments[i].Point;
1770  }
1771  break;
1772  case SegmentType.Arc:
1773  if (i > 0)
1774  {
1775  double segLength = this.Segments[i].Measure(currPoint);
1776  double pos = length / segLength;
1777  return this.Segments[i].GetTangentAt(currPoint, pos);
1778  }
1779  else
1780  {
1781  ArcSegment seg = (ArcSegment)this.Segments[i];
1782  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
1783  currPoint = figureStartPoint;
1784 
1785  double segLength = this.Segments[i].Measure(currPoint);
1786  double pos = length / segLength;
1787  return this.Segments[i].GetTangentAt(currPoint, pos);
1788  }
1789  case SegmentType.Close:
1790  {
1791  double segLength = Math.Sqrt((currPoint.X - figureStartPoint.X) * (currPoint.X - figureStartPoint.X) + (currPoint.Y - figureStartPoint.Y) * (currPoint.Y - figureStartPoint.Y));
1792  double pos = length / segLength;
1793  return new Point(figureStartPoint.X - currPoint.X, figureStartPoint.Y - currPoint.Y).Normalize();
1794  }
1795  case SegmentType.CubicBezier:
1796  if (i > 0)
1797  {
1798  double segLength = this.Segments[i].Measure(currPoint);
1799  double pos = length / segLength;
1800  return this.Segments[i].GetTangentAt(currPoint, pos);
1801  }
1802  else
1803  {
1804  currPoint = this.Segments[i].Points[0];
1805  figureStartPoint = this.Segments[i].Points[0];
1806  double segLength = this.Segments[i].Measure(currPoint);
1807  double pos = length / segLength;
1808  return this.Segments[i].GetTangentAt(currPoint, pos);
1809  }
1810  }
1811  }
1812 
1813  throw new InvalidOperationException("Unexpected code path!");
1814  }
1815  }
1816 
1817  /// <summary>
1818  /// Gets the normal to the point at the absolute position specified on the <see cref="GraphicsPath"/>.
1819  /// </summary>
1820  /// <param name="length">The distance to the point from the start of the <see cref="GraphicsPath"/>.</param>
1821  /// <returns>The normal to the point at the specified position.</returns>
1822  public Point GetNormalAtAbsolute(double length)
1823  {
1824  Point tangent = this.GetTangentAtAbsolute(length);
1825  return new Point(-tangent.Y, tangent.X);
1826  }
1827 
1828  /// <summary>
1829  /// Gets the normal to the point at the relative position specified on the <see cref="GraphicsPath"/>.
1830  /// </summary>
1831  /// <param name="position">The position on the <see cref="GraphicsPath"/> (0 is the start of the path, 1 is the end of the path).</param>
1832  /// <returns>The normal to the point at the specified position.</returns>
1833  public Point GetNormalAtRelative(double position)
1834  {
1835  Point tangent = this.GetTangentAtRelative(position);
1836  return new Point(-tangent.Y, tangent.X);
1837  }
1838 
1839  /// <summary>
1840  /// Linearises a <see cref="GraphicsPath"/>, replacing curve segments with series of line segments that approximate them.
1841  /// </summary>
1842  /// <param name="resolution">The absolute length between successive samples in curve segments.</param>
1843  /// <returns>A <see cref="GraphicsPath"/> composed only of linear segments that approximates the current <see cref="GraphicsPath"/>.</returns>
1844  public GraphicsPath Linearise(double resolution)
1845  {
1846  if (!(resolution > 0))
1847  {
1848  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, "The resolution must be greater than 0!");
1849  }
1850 
1851  GraphicsPath tbr = new GraphicsPath();
1852 
1853  Point? previousPoint = null;
1854 
1855  foreach (Segment seg in this.Segments)
1856  {
1857  tbr.Segments.AddRange(seg.Linearise(previousPoint, resolution));
1858 
1859  if (seg.Type != SegmentType.Close)
1860  {
1861  previousPoint = seg.Point;
1862  }
1863  }
1864 
1865  return tbr;
1866  }
1867 
1868  /// <summary>
1869  /// Gets a collection of the end points of all the segments in the <see cref="GraphicsPath"/>, divided by figure.
1870  /// </summary>
1871  /// <returns>A collection of the end points of all the segments in the <see cref="GraphicsPath"/>, divided by figure.</returns>
1872  public IEnumerable<List<Point>> GetPoints()
1873  {
1874  Point startPoint = new Point();
1875 
1876  List<Point> currFigure = null;
1877  bool returned = true;
1878 
1879  foreach (Segment seg in this.Segments)
1880  {
1881  if (seg.Type != SegmentType.Close)
1882  {
1883  Point currPoint = seg.Point;
1884  if (seg.Type == SegmentType.Move)
1885  {
1886  if (!returned)
1887  {
1888  yield return currFigure;
1889  }
1890 
1891  startPoint = currPoint;
1892  currFigure = new List<Point>();
1893  returned = false;
1894  }
1895  currFigure.Add(currPoint);
1896  }
1897  else
1898  {
1899  currFigure.Add(startPoint);
1900  yield return currFigure;
1901  returned = true;
1902  }
1903  }
1904 
1905  if (!returned)
1906  {
1907  yield return currFigure;
1908  }
1909  }
1910 
1911 
1912  /// <summary>
1913  /// Gets a collection of the tangents at the end point of the segments in which the <see cref="GraphicsPath"/> would be linearised, divided by figure.
1914  /// </summary>
1915  /// <param name="resolution">The absolute length between successive samples in curve segments.</param>
1916  /// <returns>A collection of the tangents at the end point of the segments in which the <see cref="GraphicsPath"/> would be linearised, divided by figure.</returns>
1917  public IEnumerable<List<Point>> GetLinearisationPointsNormals(double resolution)
1918  {
1919  if (!(resolution > 0))
1920  {
1921  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, "The resolution must be greater than 0!");
1922  }
1923 
1924  Point previousPoint = new Point();
1925  Point startPoint = new Point();
1926 
1927  List<Point> currFigure = null;
1928  bool returned = true;
1929 
1930  for (int i = 0; i < this.Segments.Count; i++)
1931  {
1932  Segment seg = this.Segments[i];
1933 
1934  if (seg.Type != SegmentType.Close)
1935  {
1936  Point currPoint = seg.Point;
1937  if (seg.Type == SegmentType.Move)
1938  {
1939  if (!returned)
1940  {
1941  yield return currFigure;
1942  }
1943 
1944  startPoint = currPoint;
1945  currFigure = new List<Point>();
1946  returned = false;
1947 
1948  if (i < this.Segments.Count - 1 && this.Segments[i + 1].Type != SegmentType.Move)
1949  {
1950  Point tangent = this.Segments[i + 1].GetTangentAt(seg.Point, 0);
1951 
1952  currFigure.Add(new Point(-tangent.Y, tangent.X));
1953  }
1954  else
1955  {
1956  currFigure.Add(new Point());
1957  }
1958  }
1959  else
1960  {
1961  foreach (Point tangent in seg.GetLinearisationTangents(previousPoint, resolution))
1962  {
1963  currFigure.Add(new Point(-tangent.Y, tangent.X));
1964  }
1965  }
1966 
1967  previousPoint = currPoint;
1968  }
1969  else
1970  {
1971  Point normal;
1972 
1973  if (!startPoint.IsEqual(previousPoint, 1e-4))
1974  {
1975  Point tangent = new Point(startPoint.X - previousPoint.X, startPoint.Y - previousPoint.Y).Normalize();
1976  normal = new Point(-tangent.Y, tangent.X);
1977  }
1978  else
1979  {
1980  normal = currFigure[currFigure.Count - 1];
1981  }
1982 
1983  currFigure.Add(normal);
1984  currFigure[0] = new Point((currFigure[1].X + normal.X) * 0.5, (currFigure[1].Y + normal.Y) * 0.5).Normalize();
1985 
1986  yield return currFigure;
1987  returned = true;
1988  }
1989  }
1990 
1991  if (!returned)
1992  {
1993  yield return currFigure;
1994  }
1995 
1996  }
1997 
1998 
1999  private enum VertexType
2000  {
2001  Start, End, Regular, Split, Merge
2002  };
2003 
2004  /// <summary>
2005  /// Divides a <see cref="GraphicsPath"/> into triangles.
2006  /// </summary>
2007  /// <param name="resolution">The resolution that will be used to linearise curve segments in the <see cref="GraphicsPath"/>.</param>
2008  /// <param name="clockwise">If this is <see langword="true"/>, the triangles will have their vertices in a clockwise order, otherwise they will be in anticlockwise order.</param>
2009  /// <returns>A collection of distinct <see cref="GraphicsPath"/>s, each representing one triangle.</returns>
2010  public IEnumerable<GraphicsPath> Triangulate(double resolution, bool clockwise)
2011  {
2012  double shiftAmount = 0.01 * resolution;
2013 
2014  if (!(resolution > 0))
2015  {
2016  throw new ArgumentOutOfRangeException(nameof(resolution), resolution, "The resolution must be greater than 0!");
2017  }
2018 
2019  GraphicsPath linearisedPath = this.Linearise(resolution);
2020 
2021  List<Point> vertices = new List<Point>();
2022  List<List<int>> vertexEdges = new List<List<int>>();
2023  List<(int, int)> edges = new List<(int, int)>();
2024  int lastStartingPoint = -1;
2025  int lastSegmentEnd = -1;
2026  double area = 0;
2027 
2028  foreach (Segment seg in linearisedPath.Segments)
2029  {
2030  if (seg is MoveSegment)
2031  {
2032  vertices.Add(seg.Point);
2033  vertexEdges.Add(new List<int>(2));
2034  lastStartingPoint = vertices.Count - 1;
2035  lastSegmentEnd = vertices.Count - 1;
2036  }
2037  else if (seg is LineSegment)
2038  {
2039  if (!vertices[lastSegmentEnd].IsEqual(seg.Point, 1e-4))
2040  {
2041  vertices.Add(seg.Point);
2042  vertexEdges.Add(new List<int>(2));
2043  edges.Add((lastSegmentEnd, vertices.Count - 1));
2044  area += (seg.Point.X - vertices[lastSegmentEnd].X) * (seg.Point.Y + vertices[lastSegmentEnd].Y);
2045  vertexEdges[lastSegmentEnd].Add(edges.Count - 1);
2046  vertexEdges[vertices.Count - 1].Add(edges.Count - 1);
2047  lastSegmentEnd = vertices.Count - 1;
2048  }
2049  }
2050  else if (seg is CloseSegment)
2051  {
2052  if (!vertices[lastSegmentEnd].IsEqual(vertices[lastStartingPoint], 1e-4))
2053  {
2054  edges.Add((lastSegmentEnd, lastStartingPoint));
2055  area += (vertices[lastStartingPoint].X - vertices[lastSegmentEnd].X) * (vertices[lastStartingPoint].Y + vertices[lastSegmentEnd].Y);
2056  vertexEdges[lastSegmentEnd].Add(edges.Count - 1);
2057  vertexEdges[lastStartingPoint].Add(edges.Count - 1);
2058  }
2059  else
2060  {
2061  vertices.RemoveAt(lastSegmentEnd);
2062  vertexEdges.RemoveAt(lastSegmentEnd);
2063 
2064  for (int i = 0; i < edges.Count; i++)
2065  {
2066  if (edges[i].Item1 == lastSegmentEnd)
2067  {
2068  edges[i] = (lastStartingPoint, edges[i].Item2);
2069  vertexEdges[lastStartingPoint].Add(i);
2070  }
2071  else if (edges[i].Item2 == lastSegmentEnd)
2072  {
2073  edges[i] = (edges[i].Item1, lastStartingPoint);
2074  vertexEdges[lastStartingPoint].Add(i);
2075  }
2076  }
2077  }
2078 
2079  lastStartingPoint = -1;
2080  lastSegmentEnd = -1;
2081  }
2082  }
2083 
2084  if (vertices.Count < 3)
2085  {
2086  yield break;
2087  }
2088 
2089  bool isAntiClockwise = area > 0;
2090 
2091  int compareVertices(Point a, Point b)
2092  {
2093  if (a.Y - b.Y != 0)
2094  {
2095  return Math.Sign(a.Y - b.Y);
2096  }
2097  else
2098  {
2099  return Math.Sign(a.X - b.X);
2100  }
2101  }
2102 
2103  Dictionary<double, int> yCoordinates = new Dictionary<double, int>();
2104  Dictionary<double, int> yShiftCount = new Dictionary<double, int>();
2105 
2106  foreach (Point pt in vertices)
2107  {
2108  if (yCoordinates.ContainsKey(pt.Y))
2109  {
2110  yCoordinates[pt.Y]++;
2111  }
2112  else
2113  {
2114  yCoordinates[pt.Y] = 1;
2115  yShiftCount[pt.Y] = 0;
2116  }
2117  }
2118 
2119  HashSet<double> yS = new HashSet<double>(from el in yCoordinates select el.Key);
2120 
2121  for (int i = 0; i < vertices.Count; i++)
2122  {
2123  if (yCoordinates[vertices[i].Y] > 1)
2124  {
2125  int shiftCount = yShiftCount[vertices[i].Y];
2126 
2127  double targetCoordinate;
2128 
2129  do
2130  {
2131  shiftCount++;
2132 
2133  targetCoordinate = vertices[i].Y + (2 * (shiftCount % 2) - 1) * (1 - Math.Pow(0.5, (shiftCount - 1) / 2 + 1)) * shiftAmount;
2134  }
2135  while (yS.Contains(targetCoordinate));
2136 
2137  yS.Add(targetCoordinate);
2138  yCoordinates[vertices[i].Y]--;
2139  yShiftCount[vertices[i].Y] = shiftCount;
2140  vertices[i] = new Point(vertices[i].X, targetCoordinate);
2141  }
2142 
2143  }
2144 
2145  Queue<int> sortedVertices = new Queue<int>(Enumerable.Range(0, vertices.Count).OrderBy(i => vertices[i], Comparer<Point>.Create(compareVertices)));
2146 
2147  VertexType[] vertexTypes = new VertexType[vertices.Count];
2148 
2149  List<(int, int)> exploredEdges = new List<(int, int)>();
2150  List<int> helpers = new List<int>();
2151  List<(int, int)> diagonals = new List<(int, int)>();
2152  int[] nexts = new int[vertices.Count];
2153  int[] prevs = new int[vertices.Count];
2154 
2155  while (sortedVertices.Count > 0)
2156  {
2157  int vertex = sortedVertices.Dequeue();
2158 
2159  Point pt = vertices[vertex];
2160 
2161  (int, int) edge1 = edges[vertexEdges[vertex][0]];
2162  (int, int) edge2 = edges[vertexEdges[vertex][1]];
2163 
2164  int neighbour1 = edge1.Item1 != vertex ? edge1.Item1 : edge1.Item2;
2165  int neighbour2 = edge2.Item1 != vertex ? edge2.Item1 : edge2.Item2;
2166 
2167  int minNeighbour = Math.Min(neighbour1, neighbour2);
2168  int maxNeighbour = Math.Max(neighbour1, neighbour2);
2169 
2170  int prev, next;
2171 
2172  if (vertex - minNeighbour == 1 && maxNeighbour - vertex == 1)
2173  {
2174  prev = minNeighbour;
2175  next = maxNeighbour;
2176  }
2177  else if ((minNeighbour - vertex == 1 && maxNeighbour - vertex > 1) || vertex - maxNeighbour == 1 && vertex - minNeighbour > 1)
2178  {
2179  prev = maxNeighbour;
2180  next = minNeighbour;
2181  }
2182  else
2183  {
2184  throw new InvalidOperationException("Could not make sense of the ordering of the vertices!");
2185  }
2186 
2187  nexts[vertex] = next;
2188  prevs[vertex] = prev;
2189 
2190  Point prevPoint = vertices[prev];
2191  Point nextPoint = vertices[next];
2192 
2193  double angle = Math.Atan2(prevPoint.Y - pt.Y, prevPoint.X - pt.X) - Math.Atan2(nextPoint.Y - pt.Y, nextPoint.X - pt.X);
2194 
2195  if (angle < 0)
2196  {
2197  angle += 2 * Math.PI;
2198  }
2199 
2200  VertexType vertexType;
2201 
2202  if (prevPoint.Y >= pt.Y && nextPoint.Y >= pt.Y && angle < Math.PI)
2203  {
2204  vertexType = VertexType.Start;
2205  }
2206  else if (prevPoint.Y >= pt.Y && nextPoint.Y >= pt.Y && angle > Math.PI)
2207  {
2208  vertexType = VertexType.Split;
2209  }
2210  else if (prevPoint.Y <= pt.Y && nextPoint.Y <= pt.Y && angle < Math.PI)
2211  {
2212  vertexType = VertexType.End;
2213  }
2214  else if (prevPoint.Y <= pt.Y && nextPoint.Y <= pt.Y && angle > Math.PI)
2215  {
2216  vertexType = VertexType.Merge;
2217  }
2218  else
2219  {
2220  vertexType = VertexType.Regular;
2221  }
2222 
2223  vertexTypes[vertex] = vertexType;
2224 
2225  //gpr.FillText(vertices[vertex], vertex.ToString(), new Font(new FontFamily(FontFamily.StandardFontFamilies.Helvetica), 4), Colours.Orange);
2226 
2227  if (vertexType == VertexType.Start)
2228  {
2229  exploredEdges.Add((prev, vertex));
2230  helpers.Add(vertex);
2231 
2232  //gpr.StrokeRectangle(pt.X - 1, pt.Y - 1, 2, 2, Colours.Green, 0.25);
2233  }
2234  else if (vertexType == VertexType.End)
2235  {
2236  int eiM1 = -1;
2237 
2238  for (int i = exploredEdges.Count - 1; i >= 0; i--)
2239  {
2240  if (exploredEdges[i].Item1 == vertex && exploredEdges[i].Item2 == next)
2241  {
2242  eiM1 = i;
2243  break;
2244  }
2245  }
2246 
2247  if (eiM1 >= 0)
2248  {
2249  if (vertexTypes[helpers[eiM1]] == VertexType.Merge)
2250  {
2251  diagonals.Add((helpers[eiM1], vertex));
2252  }
2253 
2254  exploredEdges.RemoveAt(eiM1);
2255  helpers.RemoveAt(eiM1);
2256  }
2257  }
2258  else if (vertexType == VertexType.Split)
2259  {
2260  (int, int) ej = (-1, -1);
2261  int ejIndex = -1;
2262 
2263  double xJ = double.MinValue;
2264 
2265  for (int i = 0; i < exploredEdges.Count; i++)
2266  {
2267  if ((vertices[exploredEdges[i].Item1].Y <= pt.Y && vertices[exploredEdges[i].Item2].Y >= pt.Y) || (vertices[exploredEdges[i].Item1].Y >= pt.Y && vertices[exploredEdges[i].Item2].Y <= pt.Y))
2268  {
2269  double dy = pt.Y - vertices[exploredEdges[i].Item1].Y;
2270  double dx = dy * (vertices[exploredEdges[i].Item2].X - vertices[exploredEdges[i].Item1].X) / (vertices[exploredEdges[i].Item2].Y - vertices[exploredEdges[i].Item1].Y);
2271 
2272  double x = dx + vertices[exploredEdges[i].Item1].X;
2273 
2274  if (x < pt.X && x >= xJ)
2275  {
2276  xJ = x;
2277  ej = exploredEdges[i];
2278  ejIndex = i;
2279  }
2280  }
2281  }
2282 
2283  if (ejIndex >= 0)
2284  {
2285  diagonals.Add((helpers[ejIndex], vertex));
2286 
2287  helpers[ejIndex] = vertex;
2288 
2289  exploredEdges.Add((prev, vertex));
2290  helpers.Add(vertex);
2291  }
2292 
2293 
2294  /*(int, int) ej = (-1, -1);
2295  (int, int) ek = (-1, -1);
2296 
2297  double xJ = double.MinValue;
2298  double xK = double.MaxValue;
2299 
2300  for (int i = 0; i < edges.Count; i++)
2301  {
2302  if ((vertices[edges[i].Item1].Y < pt.Y && vertices[edges[i].Item2].Y > pt.Y) || (vertices[edges[i].Item1].Y > pt.Y && vertices[edges[i].Item2].Y < pt.Y))
2303  {
2304  double dy = pt.Y - vertices[edges[i].Item1].Y;
2305  double dx = dy * (vertices[edges[i].Item2].X - vertices[edges[i].Item1].X) / (vertices[edges[i].Item2].Y - vertices[edges[i].Item1].Y);
2306 
2307  double x = dx + vertices[edges[i].Item1].X;
2308 
2309  if (x < pt.X && x >= xJ)
2310  {
2311  xJ = x;
2312  ej = edges[i];
2313  }
2314 
2315  if (x > pt.X && x <= xK)
2316  {
2317  xK = x;
2318  ek = edges[i];
2319  }
2320  }
2321  }
2322 
2323  Point helper = new Point(double.NaN, double.MinValue);
2324  int helperIndex = -1;
2325 
2326  for (int i = 0; i < vertices.Count; i++)
2327  {
2328  Point h = vertices[i];
2329  if (h.Y < pt.Y && h.Y > helper.Y)
2330  {
2331  double dyJ = h.Y - vertices[ej.Item1].Y;
2332  double dxJ = dyJ * (vertices[ej.Item2].X - vertices[ej.Item1].X) / (vertices[ej.Item2].Y - vertices[ej.Item1].Y);
2333  double hxJ = dxJ + vertices[ej.Item1].X;
2334 
2335  double dyK = h.Y - vertices[ek.Item1].Y;
2336  double dxK = dyJ * (vertices[ek.Item2].X - vertices[ek.Item1].X) / (vertices[ek.Item2].Y - vertices[ek.Item1].Y);
2337  double hxK = dxJ + vertices[ek.Item1].X;
2338 
2339  if (h.X >= hxJ && h.X <= hxK)
2340  {
2341  helper = h;
2342  helperIndex = i;
2343  }
2344  }
2345  }
2346 
2347  diagonals.Add((vertex, helperIndex));*/
2348  }
2349  else if (vertexType == VertexType.Merge)
2350  {
2351  int eiM1 = -1;
2352 
2353  for (int i = exploredEdges.Count - 1; i >= 0; i--)
2354  {
2355  if (exploredEdges[i].Item1 == vertex && exploredEdges[i].Item2 == next)
2356  {
2357  eiM1 = i;
2358  break;
2359  }
2360  }
2361 
2362  if (eiM1 >= 0)
2363  {
2364  if (vertexTypes[helpers[eiM1]] == VertexType.Merge)
2365  {
2366  diagonals.Add((helpers[eiM1], vertex));
2367  }
2368 
2369  exploredEdges.RemoveAt(eiM1);
2370  helpers.RemoveAt(eiM1);
2371  }
2372 
2373  (int, int) ej = (-1, -1);
2374  int ejIndex = -1;
2375 
2376  double xJ = double.MinValue;
2377 
2378  for (int i = 0; i < exploredEdges.Count; i++)
2379  {
2380  if ((vertices[exploredEdges[i].Item1].Y <= pt.Y && vertices[exploredEdges[i].Item2].Y >= pt.Y) || (vertices[exploredEdges[i].Item1].Y >= pt.Y && vertices[exploredEdges[i].Item2].Y <= pt.Y))
2381  {
2382  double dy = pt.Y - vertices[exploredEdges[i].Item1].Y;
2383  double dx = dy * (vertices[exploredEdges[i].Item2].X - vertices[exploredEdges[i].Item1].X) / (vertices[exploredEdges[i].Item2].Y - vertices[exploredEdges[i].Item1].Y);
2384 
2385  double x = dx + vertices[exploredEdges[i].Item1].X;
2386 
2387  if (x < pt.X && x >= xJ)
2388  {
2389  xJ = x;
2390  ej = exploredEdges[i];
2391  ejIndex = i;
2392  }
2393  }
2394  }
2395 
2396  if (ejIndex >= 0)
2397  {
2398  if (vertexTypes[helpers[ejIndex]] == VertexType.Merge)
2399  {
2400  diagonals.Add((helpers[ejIndex], vertex));
2401  }
2402 
2403  helpers[ejIndex] = vertex;
2404  }
2405 
2406  //gpr.FillPath(new GraphicsPath().MoveTo(pt.X - 1, pt.Y - 1).LineTo(pt.X, pt.Y + 1).LineTo(pt.X + 1, pt.Y - 1).Close(), Colours.Green);
2407 
2408  /*(int, int) ej = (-1, -1);
2409  (int, int) ek = (-1, -1);
2410 
2411  double xJ = double.MinValue;
2412  double xK = double.MaxValue;
2413 
2414  for (int i = 0; i < edges.Count; i++)
2415  {
2416  if ((vertices[edges[i].Item1].Y < pt.Y && vertices[edges[i].Item2].Y > pt.Y) || (vertices[edges[i].Item1].Y > pt.Y && vertices[edges[i].Item2].Y < pt.Y))
2417  {
2418  double dy = pt.Y - vertices[edges[i].Item1].Y;
2419  double dx = dy * (vertices[edges[i].Item2].X - vertices[edges[i].Item1].X) / (vertices[edges[i].Item2].Y - vertices[edges[i].Item1].Y);
2420 
2421  double x = dx + vertices[edges[i].Item1].X;
2422 
2423  if (x < pt.X && x >= xJ)
2424  {
2425  xJ = x;
2426  ej = edges[i];
2427  }
2428 
2429  if (x > pt.X && x <= xK)
2430  {
2431  xK = x;
2432  ek = edges[i];
2433  }
2434  }
2435  }
2436 
2437  Point helper = new Point(double.NaN, double.MaxValue);
2438  int helperIndex = -1;
2439 
2440  for (int i = 0; i < vertices.Count; i++)
2441  {
2442  Point h = vertices[i];
2443  if (h.Y > pt.Y && h.Y < helper.Y)
2444  {
2445  double dyJ = h.Y - vertices[ej.Item1].Y;
2446  double dxJ = dyJ * (vertices[ej.Item2].X - vertices[ej.Item1].X) / (vertices[ej.Item2].Y - vertices[ej.Item1].Y);
2447  double hxJ = dxJ + vertices[ej.Item1].X;
2448 
2449  double dyK = h.Y - vertices[ek.Item1].Y;
2450  double dxK = dyJ * (vertices[ek.Item2].X - vertices[ek.Item1].X) / (vertices[ek.Item2].Y - vertices[ek.Item1].Y);
2451  double hxK = dxJ + vertices[ek.Item1].X;
2452 
2453  if (h.X >= hxJ && h.X <= hxK)
2454  {
2455  helper = h;
2456  helperIndex = i;
2457  }
2458  }
2459  }
2460 
2461  diagonals.Add((vertex, helperIndex));*/
2462  }
2463  else if (vertexType == VertexType.Regular)
2464  {
2465  if ((isAntiClockwise && (prevPoint.Y < pt.Y || pt.Y < nextPoint.Y)) || (!isAntiClockwise && (prevPoint.Y > pt.Y || pt.Y > nextPoint.Y)))
2466  {
2467  int eiM1 = -1;
2468 
2469  for (int i = exploredEdges.Count - 1; i >= 0; i--)
2470  {
2471  if (exploredEdges[i].Item1 == vertex && exploredEdges[i].Item2 == next)
2472  {
2473  eiM1 = i;
2474  break;
2475  }
2476  }
2477 
2478  if (eiM1 >= 0)
2479  {
2480  if (vertexTypes[helpers[eiM1]] == VertexType.Merge)
2481  {
2482  diagonals.Add((helpers[eiM1], vertex));
2483  }
2484 
2485  exploredEdges.RemoveAt(eiM1);
2486  helpers.RemoveAt(eiM1);
2487  }
2488 
2489  exploredEdges.Add((prev, vertex));
2490  helpers.Add(vertex);
2491  }
2492  else
2493  {
2494  (int, int) ej = (-1, -1);
2495  int ejIndex = -1;
2496 
2497  double xJ = double.MinValue;
2498 
2499  for (int i = 0; i < exploredEdges.Count; i++)
2500  {
2501  if ((vertices[exploredEdges[i].Item1].Y <= pt.Y && vertices[exploredEdges[i].Item2].Y >= pt.Y) || (vertices[exploredEdges[i].Item1].Y >= pt.Y && vertices[exploredEdges[i].Item2].Y <= pt.Y))
2502  {
2503  double dy = pt.Y - vertices[exploredEdges[i].Item1].Y;
2504  double dx = dy * (vertices[exploredEdges[i].Item2].X - vertices[exploredEdges[i].Item1].X) / (vertices[exploredEdges[i].Item2].Y - vertices[exploredEdges[i].Item1].Y);
2505 
2506  double x = dx + vertices[exploredEdges[i].Item1].X;
2507 
2508  if (x < pt.X && x >= xJ)
2509  {
2510  xJ = x;
2511  ej = exploredEdges[i];
2512  ejIndex = i;
2513  }
2514  }
2515  }
2516 
2517  if (ejIndex >= 0)
2518  {
2519  if (vertexTypes[helpers[ejIndex]] == VertexType.Merge)
2520  {
2521  diagonals.Add((helpers[ejIndex], vertex));
2522  }
2523 
2524  helpers[ejIndex] = vertex;
2525  }
2526  }
2527  }
2528  }
2529 
2530  for (int i = diagonals.Count - 1; i >= 0; i--)
2531  {
2532  for (int j = 0; j < edges.Count; j++)
2533  {
2534  if (CompareEdges(diagonals[i], edges[j]))
2535  {
2536  diagonals.RemoveAt(i);
2537  break;
2538  }
2539  }
2540  }
2541 
2542  List<List<(int, int)>> polygons = SplitPolygons(edges, diagonals, vertices, isAntiClockwise ? prevs : nexts);
2543 
2544  int[] directions = new int[vertices.Count];
2545 
2546  int ind = 0;
2547 
2548  foreach (List<(int, int)> polygon in polygons)
2549  {
2550  foreach (GraphicsPath pth in TriangulateMonotone(vertices, polygon, directions, clockwise ? -1 : 1))
2551  {
2552  yield return pth;
2553  }
2554 
2555  ind++;
2556  }
2557  }
2558 
2559  private static bool CompareEdges((int, int) edge1, (int, int) edge2)
2560  {
2561  return (edge1.Item1 == edge2.Item1 && edge1.Item2 == edge2.Item2) || (edge1.Item1 == edge2.Item2 && edge1.Item2 == edge2.Item1);
2562  }
2563 
2564  private static List<List<(int, int)>> SplitPolygons(List<(int, int)> edges, List<(int, int)> diagonals, List<Point> vertices, int[] nexts)
2565  {
2566  List<List<(int, int)>> polygons = new List<List<(int, int)>>();
2567 
2568  List<int>[] outPaths = new List<int>[vertices.Count];
2569 
2570  for (int i = 0; i < edges.Count; i++)
2571  {
2572  if (outPaths[edges[i].Item1] == null)
2573  {
2574  outPaths[edges[i].Item1] = new List<int>();
2575  }
2576 
2577  if (outPaths[edges[i].Item2] == null)
2578  {
2579  outPaths[edges[i].Item2] = new List<int>();
2580  }
2581 
2582  outPaths[edges[i].Item1].Add(edges[i].Item2);
2583  outPaths[edges[i].Item2].Add(edges[i].Item1);
2584  }
2585 
2586  for (int i = 0; i < diagonals.Count; i++)
2587  {
2588  if (outPaths[diagonals[i].Item1] == null)
2589  {
2590  outPaths[diagonals[i].Item1] = new List<int>();
2591  }
2592 
2593  if (outPaths[diagonals[i].Item2] == null)
2594  {
2595  outPaths[diagonals[i].Item2] = new List<int>();
2596  }
2597 
2598  outPaths[diagonals[i].Item1].Add(diagonals[i].Item2);
2599  outPaths[diagonals[i].Item1].Add(diagonals[i].Item2);
2600  outPaths[diagonals[i].Item2].Add(diagonals[i].Item1);
2601  outPaths[diagonals[i].Item2].Add(diagonals[i].Item1);
2602  }
2603 
2604  List<int> activeVertices = (from el in Enumerable.Range(0, outPaths.Length) where outPaths[el] != null && outPaths[el].Count > 0 select el).ToList();
2605 
2606  int[] newNexts = new int[nexts.Length];
2607 
2608  for (int i = 0; i < nexts.Length; i++)
2609  {
2610  if (activeVertices.Contains(i))
2611  {
2612  int currNext = nexts[i];
2613  while (!outPaths[i].Contains(currNext))
2614  {
2615  currNext = nexts[currNext];
2616  }
2617  newNexts[i] = currNext;
2618  }
2619  else
2620  {
2621  newNexts[i] = -1;
2622  }
2623  }
2624 
2625  while (activeVertices.Count > 0)
2626  {
2627  List<(int, int)> polygon = new List<(int, int)>();
2628 
2629  int startPoint = activeVertices.Min();
2630 
2631  if (!outPaths[startPoint].Contains(newNexts[startPoint]))
2632  {
2633  throw new InvalidOperationException("Missing edge!");
2634  }
2635 
2636  int prevVertex = startPoint;
2637  int currVertex = newNexts[startPoint];
2638 
2639  outPaths[startPoint].Remove(currVertex);
2640  outPaths[currVertex].Remove(startPoint);
2641  polygon.Add((startPoint, currVertex));
2642 
2643  while (currVertex != startPoint)
2644  {
2645  Point currPoint = vertices[currVertex];
2646  Point prevPoint = vertices[prevVertex];
2647 
2648  double angleIncoming = Math.Atan2(prevPoint.Y - currPoint.Y, prevPoint.X - currPoint.X);
2649  if (angleIncoming < 0)
2650  {
2651  angleIncoming += 2 * Math.PI;
2652  }
2653 
2654  double maxAngle = double.MinValue;
2655  int candidateVertex = -1;
2656 
2657  for (int i = 0; i < outPaths[currVertex].Count; i++)
2658  {
2659  double angleI = Math.Atan2(vertices[outPaths[currVertex][i]].Y - currPoint.Y, vertices[outPaths[currVertex][i]].X - currPoint.X);
2660  if (angleI < 0)
2661  {
2662  angleI += 2 * Math.PI;
2663  }
2664  angleI -= angleIncoming;
2665  if (angleI < 0)
2666  {
2667  angleI += 2 * Math.PI;
2668  }
2669 
2670  if (angleI > maxAngle)
2671  {
2672  candidateVertex = outPaths[currVertex][i];
2673  maxAngle = angleI;
2674  }
2675  }
2676 
2677  outPaths[currVertex].Remove(candidateVertex);
2678  outPaths[candidateVertex].Remove(currVertex);
2679  polygon.Add((currVertex, candidateVertex));
2680 
2681  prevVertex = currVertex;
2682  currVertex = candidateVertex;
2683  }
2684 
2685  polygons.Add(polygon);
2686  activeVertices = (from el in Enumerable.Range(0, outPaths.Length) where outPaths[el] != null && outPaths[el].Count > 0 select el).ToList();
2687 
2688  if (activeVertices.Contains(currVertex))
2689  {
2690  int currNext = newNexts[currVertex];
2691  while (!outPaths[currVertex].Contains(currNext))
2692  {
2693  currNext = newNexts[currNext];
2694  }
2695  newNexts[currVertex] = currNext;
2696  }
2697 
2698  if (activeVertices.Contains(prevVertex))
2699  {
2700  int currNext = newNexts[prevVertex];
2701  while (!outPaths[prevVertex].Contains(currNext))
2702  {
2703  currNext = newNexts[currNext];
2704  }
2705  newNexts[prevVertex] = currNext;
2706  }
2707  }
2708 
2709  return polygons;
2710  }
2711 
2712  private static IEnumerable<GraphicsPath> TriangulateMonotone(List<Point> vertices, List<(int, int)> edges, int[] directions, int targetSign)
2713  {
2714  int getDirection((int, int) edge)
2715  {
2716  if (vertices[edge.Item2].Y != vertices[edge.Item1].Y)
2717  {
2718  return Math.Sign(vertices[edge.Item2].Y - vertices[edge.Item1].Y);
2719  }
2720  else
2721  {
2722  for (int i = 0; i < edges.Count; i++)
2723  {
2724  if (edges[i].Item2 == edge.Item1)
2725  {
2726  return getDirection(edges[i]);
2727  }
2728  }
2729  }
2730 
2731  throw new InvalidOperationException("Unknown edge direction!");
2732  }
2733 
2734 
2735  for (int i = 0; i < edges.Count; i++)
2736  {
2737  directions[edges[i].Item1] = getDirection(edges[i]);
2738  }
2739 
2740  int[] sortedVertices = (from el in edges select el.Item1).OrderBy(a => a, Comparer<int>.Create((a, b) =>
2741  {
2742  if (vertices[a].Y != vertices[b].Y)
2743  {
2744  return Math.Sign(vertices[a].Y - vertices[b].Y);
2745  }
2746  else
2747  {
2748  return Math.Sign(vertices[a].X - vertices[b].X);
2749  }
2750  })).ToArray();
2751 
2752  List<(int, int)> diagonals = new List<(int, int)>();
2753 
2754  Stack<int> stack = new Stack<int>();
2755 
2756  stack.Push(sortedVertices[0]);
2757  stack.Push(sortedVertices[1]);
2758 
2759  for (int i = 2; i < sortedVertices.Length - 1; i++)
2760  {
2761  int onTop = stack.Peek();
2762 
2763  if (directions[sortedVertices[i]] != directions[onTop])
2764  {
2765  while (stack.Count > 1)
2766  {
2767  int v = stack.Pop();
2768 
2769  diagonals.Add((v, sortedVertices[i]));
2770  }
2771 
2772  stack.Pop();
2773 
2774  stack.Push(sortedVertices[i - 1]);
2775  stack.Push(sortedVertices[i]);
2776  }
2777  else
2778  {
2779  int lastPopped = stack.Pop();
2780 
2781  bool shouldContinue = true;
2782 
2783  while (shouldContinue && stack.Count > 0)
2784  {
2785  int currVert = stack.Peek();
2786 
2787  double areaSign = Math.Sign((vertices[currVert].X - vertices[lastPopped].X) * (vertices[sortedVertices[i]].Y - vertices[lastPopped].Y) - (vertices[currVert].Y - vertices[lastPopped].Y) * (vertices[sortedVertices[i]].X - vertices[lastPopped].X));
2788 
2789  double dirSign = Math.Sign(vertices[currVert].X - vertices[lastPopped].X);
2790 
2791  if (areaSign * dirSign > 0)
2792  {
2793  lastPopped = stack.Pop();
2794 
2795  diagonals.Add((currVert, sortedVertices[i]));
2796  }
2797  else
2798  {
2799  shouldContinue = false;
2800  }
2801  }
2802 
2803  stack.Push(lastPopped);
2804  stack.Push(sortedVertices[i]);
2805  }
2806  }
2807 
2808  stack.Pop();
2809 
2810  while (stack.Count > 1)
2811  {
2812  int v = stack.Pop();
2813 
2814  diagonals.Add((v, sortedVertices[sortedVertices.Length - 1]));
2815  }
2816 
2817  List<int>[] connections = new List<int>[vertices.Count];
2818 
2819  for (int i = 0; i < edges.Count; i++)
2820  {
2821  connections[edges[i].Item1] = new List<int>();
2822  }
2823 
2824  for (int i = 0; i < edges.Count; i++)
2825  {
2826  connections[edges[i].Item1].Add(edges[i].Item2);
2827  connections[edges[i].Item2].Add(edges[i].Item1);
2828  }
2829 
2830  for (int i = 0; i < diagonals.Count; i++)
2831  {
2832  connections[diagonals[i].Item1].Add(diagonals[i].Item2);
2833  connections[diagonals[i].Item1].Add(diagonals[i].Item2);
2834 
2835  connections[diagonals[i].Item2].Add(diagonals[i].Item1);
2836  connections[diagonals[i].Item2].Add(diagonals[i].Item1);
2837  }
2838 
2839  int totalTriangles = (edges.Count + diagonals.Count * 2) / 3;
2840 
2841  List<List<(int, int)>> polygons = new List<List<(int, int)>>();
2842 
2843  while (polygons.Count < totalTriangles)
2844  {
2845  int p1 = -1;
2846  int p2 = -1;
2847 
2848  for (int i = 0; i < connections.Length; i++)
2849  {
2850  if (connections[i] != null && connections[i].Count > 0)
2851  {
2852  p1 = i;
2853  p2 = connections[i][0];
2854 
2855  connections[i].Remove(p2);
2856  connections[p2].Remove(i);
2857  break;
2858  }
2859  }
2860 
2861  int p3 = -1;
2862 
2863  for (int i = 0; i < connections[p1].Count; i++)
2864  {
2865  if (connections[connections[p1][i]].Contains(p2))
2866  {
2867  p3 = connections[p1][i];
2868  connections[p1].Remove(p3);
2869  connections[p2].Remove(p3);
2870  connections[p3].Remove(p1);
2871  connections[p3].Remove(p2);
2872  break;
2873  }
2874  }
2875 
2876  int sign = Math.Sign((vertices[p1].X - vertices[p2].X) * (vertices[p3].Y - vertices[p2].Y) - (vertices[p1].Y - vertices[p2].Y) * (vertices[p3].X - vertices[p2].X));
2877 
2878  if (sign == targetSign)
2879  {
2880  polygons.Add(new List<(int, int)>() { (p1, p2), (p2, p3), (p3, p1) });
2881  }
2882  else
2883  {
2884  polygons.Add(new List<(int, int)>() { (p1, p3), (p3, p2), (p2, p1) });
2885  }
2886  }
2887 
2888  foreach (List<(int, int)> polygon in polygons)
2889  {
2890  GraphicsPath polygonPath = new GraphicsPath();
2891  foreach ((int, int) edge in polygon)
2892  {
2893  if (polygonPath.Segments.Count == 0)
2894  {
2895  polygonPath.MoveTo(vertices[edge.Item1]);
2896  }
2897 
2898  polygonPath.LineTo(vertices[edge.Item2]);
2899  }
2900 
2901  yield return polygonPath;
2902  }
2903  }
2904 
2905  /// <summary>
2906  /// Transforms all of the <see cref="Point"/>s in the <see cref="GraphicsPath"/> with an arbitrary transformation function.
2907  /// </summary>
2908  /// <param name="transformationFunction">An arbitrary transformation function.</param>
2909  /// <returns>A new <see cref="GraphicsPath"/> in which all points have been replaced using the <paramref name="transformationFunction"/>.</returns>
2910  public GraphicsPath Transform(Func<Point, Point> transformationFunction)
2911  {
2912  GraphicsPath tbr = new GraphicsPath();
2913 
2914  foreach (Segment seg in this.Segments)
2915  {
2916  tbr.Segments.AddRange(seg.Transform(transformationFunction));
2917  }
2918 
2919  return tbr;
2920  }
2921 
2922 
2923  private Rectangle cachedBounds = Rectangle.NaN;
2924 
2925  /// <summary>
2926  /// Compute the rectangular bounds of the path.
2927  /// </summary>
2928  /// <returns>The smallest <see cref="Rectangle"/> that contains the path.</returns>
2930  {
2931  if (double.IsNaN(cachedBounds.Location.X) || double.IsNaN(cachedBounds.Location.Y) || double.IsNaN(cachedBounds.Size.Width) || double.IsNaN(cachedBounds.Size.Height))
2932  {
2933  Point min = new Point(double.MaxValue, double.MaxValue);
2934  Point max = new Point(double.MinValue, double.MinValue);
2935 
2936  Point currPoint = new Point();
2937  Point figureStartPoint = new Point();
2938 
2939  for (int i = 0; i < this.Segments.Count; i++)
2940  {
2941  switch (this.Segments[i].Type)
2942  {
2943  case SegmentType.Move:
2944  currPoint = this.Segments[i].Point;
2945  figureStartPoint = this.Segments[i].Point;
2946  min = Point.Min(min, this.Segments[i].Point);
2947  max = Point.Max(max, this.Segments[i].Point);
2948  break;
2949  case SegmentType.Line:
2950  if (i > 0)
2951  {
2952  currPoint = this.Segments[i].Point;
2953  }
2954  else
2955  {
2956  currPoint = this.Segments[i].Point;
2957  figureStartPoint = this.Segments[i].Point;
2958  }
2959  min = Point.Min(min, this.Segments[i].Point);
2960  max = Point.Max(max, this.Segments[i].Point);
2961  break;
2962  case SegmentType.Arc:
2963  ArcSegment seg = (ArcSegment)this.Segments[i];
2964 
2965  if (i == 0)
2966  {
2967  figureStartPoint = new Point(seg.Points[0].X + Math.Cos(seg.StartAngle) * seg.Radius, seg.Points[0].Y + Math.Sin(seg.StartAngle) * seg.Radius);
2968  currPoint = figureStartPoint;
2969 
2970  min = Point.Min(min, currPoint);
2971  max = Point.Max(max, currPoint);
2972  }
2973 
2974  double theta1 = seg.StartAngle;
2975  double theta2 = seg.EndAngle;
2976 
2977  if (theta1 > theta2)
2978  {
2979  double temp = theta1;
2980  theta1 = theta2;
2981  theta2 = temp;
2982  }
2983 
2984  theta1 /= 2 * Math.PI;
2985  theta2 /= 2 * Math.PI;
2986 
2987  int minAngle = (int)Math.Min(0, Math.Floor(theta1)) * 4;
2988  int maxAngle = (int)Math.Max(1, Math.Ceiling(theta2)) * 4;
2989 
2990  theta1 *= 4;
2991  theta2 *= 4;
2992 
2993  bool right = false;
2994  bool bottom = false;
2995  bool left = false;
2996  bool top = false;
2997 
2998  for (int j = minAngle; j < maxAngle; j++)
2999  {
3000  if (theta1 <= j && theta2 >= j)
3001  {
3002  switch (j % 4)
3003  {
3004  case 0:
3005  right = true;
3006  break;
3007  case 1:
3008  bottom = true;
3009  break;
3010  case 2:
3011  left = true;
3012  break;
3013  case 3:
3014  top = true;
3015  break;
3016  }
3017  }
3018  }
3019 
3020  if (right)
3021  {
3022  Point p = new Point(seg.Points[0].X + seg.Radius, seg.Points[0].Y);
3023  min = Point.Min(min, p);
3024  max = Point.Max(max, p);
3025  }
3026 
3027  if (bottom)
3028  {
3029  Point p = new Point(seg.Points[0].X, seg.Points[0].Y + seg.Radius);
3030  min = Point.Min(min, p);
3031  max = Point.Max(max, p);
3032  }
3033 
3034  if (left)
3035  {
3036  Point p = new Point(seg.Points[0].X - seg.Radius, seg.Points[0].Y);
3037  min = Point.Min(min, p);
3038  max = Point.Max(max, p);
3039  }
3040 
3041  if (top)
3042  {
3043  Point p = new Point(seg.Points[0].X, seg.Points[0].Y - seg.Radius);
3044  min = Point.Min(min, p);
3045  max = Point.Max(max, p);
3046  }
3047 
3048  min = Point.Min(min, this.Segments[i].Point);
3049  max = Point.Max(max, this.Segments[i].Point);
3050 
3051  currPoint = this.Segments[i].Point;
3052  break;
3053  case SegmentType.Close:
3054  currPoint = figureStartPoint;
3055  break;
3056  case SegmentType.CubicBezier:
3057  if (i == 0)
3058  {
3059  currPoint = this.Segments[i].Points[0];
3060  figureStartPoint = this.Segments[i].Points[0];
3061  }
3062 
3063  min = Point.Min(min, this.Segments[i].Point);
3064  max = Point.Max(max, this.Segments[i].Point);
3065 
3066  {
3067  double aX = -currPoint.X + 3 * this.Segments[i].Points[0].X - 3 * this.Segments[i].Points[1].X + this.Segments[i].Point.X;
3068  double bX = 2 * (currPoint.X - 2 * this.Segments[i].Points[0].X + this.Segments[i].Points[1].X);
3069  double cX = this.Segments[i].Points[0].X - currPoint.X;
3070 
3071  double t1X;
3072  double t2X;
3073 
3074  if (Math.Abs(aX) < 1e-5)
3075  {
3076  if (Math.Abs(bX) >= 1e-5)
3077  {
3078  t1X = -cX / bX;
3079  t2X = t1X;
3080  }
3081  else
3082  {
3083  t1X = double.NaN;
3084  t2X = double.NaN;
3085  }
3086  }
3087  else
3088  {
3089  double delta = bX * bX - 4 * aX * cX;
3090 
3091  if (delta >= -1e-5)
3092  {
3093  delta = Math.Max(delta, 0);
3094 
3095  delta = Math.Sqrt(delta);
3096  t1X = (-bX + delta) / (2 * aX);
3097  t2X = (-bX - delta) / (2 * aX);
3098  }
3099  else
3100  {
3101  t1X = double.NaN;
3102  t2X = double.NaN;
3103  }
3104  }
3105 
3106  if (t1X >= 0 && t1X <= 1)
3107  {
3108  Point p1 = ((CubicBezierSegment)this.Segments[i]).GetBezierPointAt(currPoint, t1X);
3109  min = Point.Min(min, p1);
3110  max = Point.Max(max, p1);
3111  }
3112 
3113  if (t2X >= 0 && t2X <= 1)
3114  {
3115  Point p2 = ((CubicBezierSegment)this.Segments[i]).GetBezierPointAt(currPoint, t2X);
3116  min = Point.Min(min, p2);
3117  max = Point.Max(max, p2);
3118  }
3119  }
3120 
3121  {
3122  double aY = -currPoint.Y + 3 * this.Segments[i].Points[0].Y - 3 * this.Segments[i].Points[1].Y + this.Segments[i].Point.Y;
3123  double bY = 2 * (currPoint.Y - 2 * this.Segments[i].Points[0].Y + this.Segments[i].Points[1].Y);
3124  double cY = this.Segments[i].Points[0].Y - currPoint.Y;
3125 
3126  double t1Y;
3127  double t2Y;
3128 
3129  if (Math.Abs(aY) < 1e-5)
3130  {
3131  if (Math.Abs(bY) >= 1e-5)
3132  {
3133  t1Y = -cY / bY;
3134  t2Y = t1Y;
3135  }
3136  else
3137  {
3138  t1Y = double.NaN;
3139  t2Y = double.NaN;
3140  }
3141  }
3142  else
3143  {
3144  double delta = bY * bY - 4 * aY * cY;
3145 
3146  if (delta >= -1e-5)
3147  {
3148  delta = Math.Max(delta, 0);
3149 
3150  delta = Math.Sqrt(delta);
3151  t1Y = (-bY + delta) / (2 * aY);
3152  t2Y = (-bY - delta) / (2 * aY);
3153  }
3154  else
3155  {
3156  t1Y = double.NaN;
3157  t2Y = double.NaN;
3158  }
3159  }
3160 
3161  if (t1Y >= 0 && t1Y <= 1)
3162  {
3163  Point p1 = ((CubicBezierSegment)this.Segments[i]).GetBezierPointAt(currPoint, t1Y);
3164  min = Point.Min(min, p1);
3165  max = Point.Max(max, p1);
3166  }
3167 
3168  if (t2Y >= 0 && t2Y <= 1)
3169  {
3170  Point p2 = ((CubicBezierSegment)this.Segments[i]).GetBezierPointAt(currPoint, t2Y);
3171  min = Point.Min(min, p2);
3172  max = Point.Max(max, p2);
3173  }
3174  }
3175 
3176  currPoint = this.Segments[i].Point;
3177  break;
3178  }
3179  }
3180 
3181  cachedBounds = new Rectangle(min, max);
3182  }
3183 
3184  return cachedBounds;
3185  }
3186  }
3187 }
VectSharp.Rectangle
Represents a rectangle.
Definition: Point.cs:173
VectSharp.GraphicsPath.Arc
GraphicsPath Arc(Point center, double radius, double startAngle, double endAngle)
Trace an arc segment from a circle with the specified center and radius , starting at startAngle an...
Definition: GraphicsPath.cs:103
VectSharp.GraphicsPath.GetLinearisationPointsNormals
IEnumerable< List< Point > > GetLinearisationPointsNormals(double resolution)
Gets a collection of the tangents at the end point of the segments in which the GraphicsPath would be...
Definition: GraphicsPath.cs:1917
VectSharp.GraphicsPath.GetTangentAtRelative
Point GetTangentAtRelative(double position)
Gets the tangent to the point at the relative position specified on the GraphicsPath.
Definition: GraphicsPath.cs:1525
VectSharp.TrueTypeFile
Represents a font file in TrueType format. Reference: http://stevehanov.ca/blog/?id=143,...
Definition: TrueType.cs:31
VectSharp.GraphicsPath.Arc
GraphicsPath Arc(double centerX, double centerY, double radius, double startAngle, double endAngle)
Trace an arc segment from a circle with the specified center and radius , starting at startAngle and...
Definition: GraphicsPath.cs:126
VectSharp.Font.Underline
FontUnderline Underline
Determines the underline style of text drawn using this font. If this is null, the text is not underl...
Definition: Font.cs:294
VectSharp.GraphicsPath.AddSmoothSpline
GraphicsPath AddSmoothSpline(params Point[] points)
Adds a smooth spline composed of cubic bezier segments that pass through the specified points.
Definition: GraphicsPath.cs:1123
VectSharp.Point.Min
static Point Min(Point p1, Point p2)
Computes the top-left corner of the Rectangle identified by two Points.
Definition: Point.cs:86
VectSharp.TrueTypeFile.Get1000EmKerning
PairKerning Get1000EmKerning(char glyph1, char glyph2)
Gets the kerning between two glyphs.
Definition: TrueType.cs:2490
VectSharp.GraphicsPath.GetPointAtAbsolute
Point GetPointAtAbsolute(double length)
Gets the point at the absolute position specified on the GraphicsPath.
Definition: GraphicsPath.cs:1238
VectSharp.GraphicsPath.MeasureLength
double MeasureLength()
Measures the length of the GraphicsPath.
Definition: GraphicsPath.cs:1156
VectSharp.Font.DetailedFontMetrics.Width
double Width
Width of the text (measured on the actual glyph outlines).
Definition: Font.cs:106
VectSharp.GraphicsPath.Linearise
GraphicsPath Linearise(double resolution)
Linearises a GraphicsPath, replacing curve segments with series of line segments that approximate the...
Definition: GraphicsPath.cs:1844
VectSharp.TrueTypeFile.Get1000EmUnderlineIntersections
double[] Get1000EmUnderlineIntersections(char glyph, double position, double thickness)
Computes the intersections between an underline at the specified position and thickness and a glyph,...
Definition: TrueType.cs:2340
VectSharp.GraphicsPath.GetBounds
Rectangle GetBounds()
Compute the rectangular bounds of the path.
Definition: GraphicsPath.cs:2929
VectSharp.GraphicsPath.AddText
GraphicsPath AddText(Point origin, string text, Font font, TextBaselines textBaseline=TextBaselines.Top)
Add the contour of a text string to the current path.
Definition: GraphicsPath.cs:325
VectSharp.GraphicsPath
Represents a graphics path that can be filled or stroked.
Definition: GraphicsPath.cs:29
VectSharp.TrueTypeFile.TrueTypePoint
Represents a point in a TrueType path description.
Definition: TrueType.cs:1361
VectSharp
Definition: Brush.cs:26
VectSharp.Segment.GetLinearisationTangents
abstract IEnumerable< Point > GetLinearisationTangents(Point? previousPoint, double resolution)
Gets the tanget at the points at which the segment would be linearised.
VectSharp.Font.FontUnderline.FollowItalicAngle
bool FollowItalicAngle
Determine whether the shape of the underline is slanted to follow the angle of italic fonts.
Definition: Font.cs:63
VectSharp.Font.MeasureTextAdvanced
DetailedFontMetrics MeasureTextAdvanced(string text)
Measure all the metrics of a text string when typeset with this font.
Definition: Font.cs:358
VectSharp.TrueTypeFile.Bearings.RightSideBearing
int RightSideBearing
The right-side bearing of the glyph.
Definition: TrueType.cs:2195
VectSharp.Size.Height
double Height
Height of the object.
Definition: Point.cs:155
VectSharp.LineCaps
LineCaps
Represents line caps.
Definition: Enums.cs:71
VectSharp.GraphicsPath.GetNormalAtRelative
Point GetNormalAtRelative(double position)
Gets the normal to the point at the relative position specified on the GraphicsPath.
Definition: GraphicsPath.cs:1833
VectSharp.Point.IsEqual
bool IsEqual(Point p2, double tolerance)
Checks whether this Point is equal to another Point, up to a specified tolerance.
Definition: Point.cs:75
VectSharp.Font
Represents a typeface with a specific size.
Definition: Font.cs:29
VectSharp.TextBaselines
TextBaselines
Represent text baselines.
Definition: Enums.cs:24
VectSharp.Font.FontUnderline.Position
double Position
Determines the position of the top of the underline with respect to the text baseline....
Definition: Font.cs:48
VectSharp.Rectangle.Size
Size Size
The size of the rectangle.
Definition: Point.cs:187
VectSharp.GraphicsPath.EllipticalArc
GraphicsPath EllipticalArc(double radiusX, double radiusY, double axisAngle, bool largeArc, bool sweepClockwise, Point endPoint)
Trace an arc from an ellipse with the specified radii, rotated by axisAngle with respect to the x-ax...
Definition: GraphicsPath.cs:142
VectSharp.GraphicsPath.AddText
GraphicsPath AddText(double originX, double originY, string text, Font font, TextBaselines textBaseline=TextBaselines.Top)
Add the contour of a text string to the current path.
Definition: GraphicsPath.cs:312
VectSharp.Font.FontUnderline.LineCap
LineCaps LineCap
Determines the caps at the start and end of the underline.
Definition: Font.cs:58
VectSharp.GraphicsPath.Triangulate
IEnumerable< GraphicsPath > Triangulate(double resolution, bool clockwise)
Divides a GraphicsPath into triangles.
Definition: GraphicsPath.cs:2010
VectSharp.GraphicsPath.CubicBezierTo
GraphicsPath CubicBezierTo(double control1X, double control1Y, double control2X, double control2Y, double endPointX, double endPointY)
Trace a cubic Bezier curve from the current point to a destination point, with two control points....
Definition: GraphicsPath.cs:285
VectSharp.Rectangle.NaN
static readonly Rectangle NaN
A rectangle whose dimensions are all double.NaN.
Definition: Point.cs:177
VectSharp.Segment
Represents a segment as part of a GraphicsPath.
Definition: Segment.cs:29
VectSharp.GraphicsPath.LineTo
GraphicsPath LineTo(double x, double y)
Move the current point and trace a segment from the previous point.
Definition: GraphicsPath.cs:88
VectSharp.GraphicsPath.Transform
GraphicsPath Transform(Func< Point, Point > transformationFunction)
Transforms all of the Points in the GraphicsPath with an arbitrary transformation function.
Definition: GraphicsPath.cs:2910
VectSharp.GraphicsPath.GetTangentAtAbsolute
Point GetTangentAtAbsolute(double length)
Gets the tangent to the point at the absolute position specified on the GraphicsPath.
Definition: GraphicsPath.cs:1535
VectSharp.Font.EnableKerning
static bool EnableKerning
Determines whether text kerning is enabled. Note that, even when this is set to false,...
Definition: Font.cs:33
VectSharp.Font.DetailedFontMetrics
Represents detailed information about the metrics of a text string when drawn with a certain font.
Definition: Font.cs:102
VectSharp.GraphicsPath.AddTextUnderline
GraphicsPath AddTextUnderline(Point origin, string text, Font font, TextBaselines textBaseline=TextBaselines.Top)
Add the contour of the underline of the specified text string to the current path.
Definition: GraphicsPath.cs:572
VectSharp.SegmentType
SegmentType
Types of Segment.
Definition: Enums.cs:152
VectSharp.GraphicsPath.LineTo
GraphicsPath LineTo(Point p)
Move the current point and trace a segment from the previous point.
Definition: GraphicsPath.cs:66
VectSharp.Font.FontUnderline.SkipDescenders
bool SkipDescenders
Determines whether the underline skips the parts of the glyph that would intersect with it or not.
Definition: Font.cs:43
VectSharp.Font.FontUnderline.Thickness
double Thickness
Determines the thickness of the underline, expressed as a fraction of the font size.
Definition: Font.cs:53
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.TrueTypeFile.Get1000EmGlyphWidth
double Get1000EmGlyphWidth(char glyph)
Computes the advance width of a glyph, in thousandths of em unit.
Definition: TrueType.cs:2082
VectSharp.GraphicsPath.MoveTo
GraphicsPath MoveTo(Point p)
Move the current point without tracing a segment from the previous point.
Definition: GraphicsPath.cs:41
VectSharp.Segment.Points
Point[] Points
The points used to define the Segment.
Definition: Segment.cs:39
VectSharp.Segment.Type
abstract SegmentType Type
The type of the Segment.
Definition: Segment.cs:34
VectSharp.Font.FontFamily
FontFamily FontFamily
Font typeface.
Definition: Font.cs:158
VectSharp.GraphicsPath.GetPointAtRelative
Point GetPointAtRelative(double position)
Gets the point at the relative position specified on the GraphicsPath.
Definition: GraphicsPath.cs:1228
VectSharp.TrueTypeFile.GetItalicAngle
double GetItalicAngle()
Computes the italic angle for the current font, in thousandths of em unit. This is computed from the ...
Definition: TrueType.cs:2320
VectSharp.TextAnchors
TextAnchors
Represents text anchors.
Definition: Enums.cs:50
VectSharp.GraphicsPath.MoveTo
GraphicsPath MoveTo(double x, double y)
Move the current point without tracing a segment from the previous point.
Definition: GraphicsPath.cs:55
VectSharp.Rectangle.Location
Point Location
The top-left corner of the rectangle.
Definition: Point.cs:182
VectSharp.Size.Width
double Width
Width of the object.
Definition: Point.cs:150
VectSharp.TrueTypeFile.PairKerning
Contains information describing how the position of two glyphs in a kerning pair should be altered.
Definition: TrueType.cs:4252
VectSharp.GraphicsPath.Close
GraphicsPath Close()
Trace a segment from the current point to the start point of the figure and flag the figure as closed...
Definition: GraphicsPath.cs:295
VectSharp.Point.Normalize
Point Normalize()
Normalises a Point.
Definition: Point.cs:63
VectSharp.GraphicsPath.AddTextOnPath
GraphicsPath AddTextOnPath(GraphicsPath path, string text, Font font, double reference=0, TextAnchors anchor=TextAnchors.Left, TextBaselines textBaseline=TextBaselines.Top)
Add the contour of a text string flowing along a GraphicsPath to the current path.
Definition: GraphicsPath.cs:427
VectSharp.Segment.Linearise
abstract IEnumerable< Segment > Linearise(Point? previousPoint, double resolution)
Transform the segment into a series of linear segments. Segments that are already linear are not chan...
VectSharp.Point.Max
static Point Max(Point p1, Point p2)
Computes the bottom-right corner of the Rectangle identified by two Points.
Definition: Point.cs:97
VectSharp.Font.FontSize
double FontSize
Font size, in graphics units.
Definition: Font.cs:153
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.GraphicsPath.CubicBezierTo
GraphicsPath CubicBezierTo(Point control1, Point control2, Point endPoint)
Trace a cubic Bezier curve from the current point to a destination point, with two control points....
Definition: GraphicsPath.cs:261
VectSharp.Segment.Transform
abstract IEnumerable< Segment > Transform(Func< Point, Point > transformationFunction)
Applies an arbitrary transformation to all of the points of the Segment.
VectSharp.Segment.Point
virtual Point Point
The end point of the Segment.
Definition: Segment.cs:45
VectSharp.GraphicsPath.GetNormalAtAbsolute
Point GetNormalAtAbsolute(double length)
Gets the normal to the point at the absolute position specified on the GraphicsPath.
Definition: GraphicsPath.cs:1822
VectSharp.TrueTypeFile.GetGlyphPath
TrueTypePoint[][] GetGlyphPath(int glyphIndex, double size)
Get the path that describes the shape of a glyph.
Definition: TrueType.cs:2061
VectSharp.GraphicsPath.GetPoints
IEnumerable< List< Point > > GetPoints()
Gets a collection of the end points of all the segments in the GraphicsPath, divided by figure.
Definition: GraphicsPath.cs:1872
VectSharp.Point.Y
double Y
Vertical (y) coordinate, measured to the bottom of the origin.
Definition: Point.cs:37
VectSharp.FontFamily.TrueTypeFile
TrueTypeFile TrueTypeFile
Parsed TrueType font file for this font family. See also: VectSharp.TrueTypeFile.
Definition: Font.cs:586
VectSharp.GraphicsPath.Segments
List< Segment > Segments
The segments that make up the path.
Definition: GraphicsPath.cs:33