18 using Avalonia.Controls;
19 using Avalonia.Controls.Shapes;
21 using Avalonia.Media.Imaging;
23 using System.Collections.Generic;
25 using System.Runtime.InteropServices;
30 internal static class MatrixUtils
32 public static Avalonia.Matrix ToAvaloniaMatrix(
this double[,] matrix)
34 return new Avalonia.Matrix(matrix[0, 0], matrix[1, 0], matrix[0, 1], matrix[1, 1], matrix[0, 2], matrix[1, 2]);
37 public static double[] Multiply(
double[,] matrix,
double[] vector)
39 double[] tbr =
new double[2];
41 tbr[0] = matrix[0, 0] * vector[0] + matrix[0, 1] * vector[1] + matrix[0, 2];
42 tbr[1] = matrix[1, 0] * vector[0] + matrix[1, 1] * vector[1] + matrix[1, 2];
47 public static double[,] Multiply(
double[,] matrix1,
double[,] matrix2)
49 double[,] tbr =
new double[3, 3];
51 for (
int i = 0; i < 3; i++)
53 for (
int j = 0; j < 3; j++)
55 for (
int k = 0; k < 3; k++)
57 tbr[i, j] += matrix1[i, k] * matrix2[k, j];
65 public static double[,] Rotate(
double[,] matrix,
double angle)
67 double[,] rotationMatrix =
new double[3, 3];
68 rotationMatrix[0, 0] = Math.Cos(angle);
69 rotationMatrix[0, 1] = -Math.Sin(angle);
70 rotationMatrix[1, 0] = Math.Sin(angle);
71 rotationMatrix[1, 1] = Math.Cos(angle);
72 rotationMatrix[2, 2] = 1;
74 return Multiply(matrix, rotationMatrix);
77 public static double[,] Translate(
double[,] matrix,
double x,
double y)
79 double[,] translationMatrix =
new double[3, 3];
80 translationMatrix[0, 0] = 1;
81 translationMatrix[0, 2] = x;
82 translationMatrix[1, 1] = 1;
83 translationMatrix[1, 2] = y;
84 translationMatrix[2, 2] = 1;
86 return Multiply(matrix, translationMatrix);
89 public static double[,] Scale(
double[,] matrix,
double scaleX,
double scaleY)
91 double[,] scaleMatrix =
new double[3, 3];
92 scaleMatrix[0, 0] = scaleX;
93 scaleMatrix[1, 1] = scaleY;
94 scaleMatrix[2, 2] = 1;
96 return Multiply(matrix, scaleMatrix);
99 public static double[,] Identity =
new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
100 public static double[,] Invert(
double[,] m)
102 double[,] tbr =
new double[3, 3];
104 tbr[0, 0] = (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
105 tbr[0, 1] = -(m[0, 1] * m[2, 2] - m[0, 2] * m[2, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
106 tbr[0, 2] = (m[0, 1] * m[1, 2] - m[0, 2] * m[1, 1]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
107 tbr[1, 0] = -(m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
108 tbr[1, 1] = (m[0, 0] * m[2, 2] - m[0, 2] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
109 tbr[1, 2] = -(m[0, 0] * m[1, 2] - m[0, 2] * m[1, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
110 tbr[2, 0] = (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
111 tbr[2, 1] = -(m[0, 0] * m[2, 1] - m[0, 1] * m[2, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
112 tbr[2, 2] = (m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0]) / (m[0, 0] * m[1, 1] * m[2, 2] - m[0, 0] * m[1, 2] * m[2, 1] - m[1, 0] * m[0, 1] * m[2, 2] + m[2, 0] * m[0, 1] * m[1, 2] + m[1, 0] * m[0, 2] * m[2, 1] - m[2, 0] * m[0, 2] * m[1, 1]);
118 internal static class Utils
120 public static void CoerceNaNAndInfinityToZero(ref
double val)
122 if (
double.IsNaN(val) ||
double.IsInfinity(val) || val ==
double.MinValue || val ==
double.MaxValue)
128 public static void CoerceNaNAndInfinityToZero(ref
double val1, ref
double val2)
130 if (
double.IsNaN(val1) ||
double.IsInfinity(val1) || val1 ==
double.MinValue || val1 ==
double.MaxValue)
135 if (
double.IsNaN(val2) ||
double.IsInfinity(val2) || val2 ==
double.MinValue || val2 ==
double.MaxValue)
141 public static void CoerceNaNAndInfinityToZero(ref
double val1, ref
double val2, ref
double val3, ref
double val4)
143 if (
double.IsNaN(val1) ||
double.IsInfinity(val1) || val1 ==
double.MinValue || val1 ==
double.MaxValue)
148 if (
double.IsNaN(val2) ||
double.IsInfinity(val2) || val2 ==
double.MinValue || val2 ==
double.MaxValue)
153 if (
double.IsNaN(val3) ||
double.IsInfinity(val3) || val3 ==
double.MinValue || val3 ==
double.MaxValue)
158 if (
double.IsNaN(val4) ||
double.IsInfinity(val4) || val4 ==
double.MinValue || val4 ==
double.MaxValue)
164 public static void CoerceNaNAndInfinityToZero(ref
double val1, ref
double val2, ref
double val3, ref
double val4, ref
double val5, ref
double val6)
166 if (
double.IsNaN(val1) ||
double.IsInfinity(val1) || val1 ==
double.MinValue || val1 ==
double.MaxValue)
171 if (
double.IsNaN(val2) ||
double.IsInfinity(val2) || val2 ==
double.MinValue || val2 ==
double.MaxValue)
176 if (
double.IsNaN(val3) ||
double.IsInfinity(val3) || val3 ==
double.MinValue || val3 ==
double.MaxValue)
181 if (
double.IsNaN(val4) ||
double.IsInfinity(val4) || val4 ==
double.MinValue || val4 ==
double.MaxValue)
186 if (
double.IsNaN(val5) ||
double.IsInfinity(val5) || val5 ==
double.MinValue || val5 ==
double.MaxValue)
191 if (
double.IsNaN(val6) ||
double.IsInfinity(val6) || val6 ==
double.MinValue || val6 ==
double.MaxValue)
200 public Dictionary<string, Delegate> TaggedActions {
get;
set; } =
new Dictionary<string, Delegate>();
202 private bool removeTaggedActions =
true;
204 public string Tag {
get;
set; }
208 private Avalonia.Controls.Canvas currControlElement;
210 private Stack<Avalonia.Controls.Canvas> controlElements;
215 currentPath =
new PathGeometry();
216 currentFigure =
new PathFigure() { IsClosed =
false };
217 figureInitialised =
false;
218 ControlItem =
new Avalonia.Controls.Canvas() { Width = width, Height = height, ClipToBounds =
true };
219 removeTaggedActions = removeTaggedActionsAfterExecution;
221 _transform =
new double[3, 3];
223 _transform[0, 0] = 1;
224 _transform[1, 1] = 1;
225 _transform[2, 2] = 1;
227 states =
new Stack<double[,]>();
229 _textOption = textOption;
231 currControlElement = ControlItem;
232 controlElements =
new Stack<Avalonia.Controls.Canvas>();
233 controlElements.Push(ControlItem);
234 _filterOption = filterOption;
237 public Avalonia.Controls.Canvas ControlItem {
get; }
239 public double Width {
get {
return ControlItem.Width; } }
240 public double Height {
get {
return ControlItem.Height; } }
242 public void Translate(
double x,
double y)
244 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
246 _transform = MatrixUtils.Translate(_transform, x, y);
248 currentPath =
new PathGeometry();
249 currentFigure =
new PathFigure() { IsClosed =
false };
250 figureInitialised =
false;
255 private void PathText(
string text,
double x,
double y)
257 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
261 for (
int j = 0; j < textPath.
Segments.Count; j++)
281 public void StrokeText(
string text,
double x,
double y)
283 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
285 PathText(text, x, y);
289 public void FillText(
string text,
double x,
double y)
291 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
300 double[,] currTransform =
null;
301 double[,] deltaTransform = MatrixUtils.Identity;
305 currTransform = MatrixUtils.Translate(_transform, x, y);
312 blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.
Top;
316 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
318 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top -
Font.
WinAscent);
319 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top -
Font.
WinAscent);
323 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top -
Font.
Ascent);
324 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top -
Font.
Ascent);
330 blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
334 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
336 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 -
Font.
WinAscent);
337 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 -
Font.
WinAscent);
341 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 -
Font.
Ascent);
342 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 -
Font.
Ascent);
351 blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
355 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
357 currTransform = MatrixUtils.Translate(_transform, left - lsb, top -
Font.
WinAscent);
358 deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top -
Font.
YMax);
362 currTransform = MatrixUtils.Translate(_transform, left - lsb, top -
Font.
Ascent);
363 deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top -
Font.
YMax);
369 blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Bottom;
373 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
375 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top -
Font.
WinAscent + metrics.Bottom);
376 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top -
Font.
WinAscent + metrics.Bottom);
380 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top -
Font.
Ascent + metrics.Bottom);
381 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top -
Font.
Ascent + metrics.Bottom);
386 blk.RenderTransform =
new MatrixTransform(currTransform.ToAvaloniaMatrix());
387 blk.RenderTransformOrigin =
new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
389 Avalonia.Media.Brush foreground =
null;
393 foreground =
new SolidColorBrush(Color.FromArgb(FillAlpha, (
byte)(solid.R * 255), (
byte)(solid.G * 255), (
byte)(solid.B * 255)));
397 foreground = linearGradient.ToLinearGradientBrush(deltaTransform);
401 foreground = radialGradient.ToRadialGradientBrush(metrics.Width + metrics.LeftSideBearing + metrics.RightSideBearing, deltaTransform);
404 blk.Foreground = foreground;
406 currControlElement.Children.Add(blk);
408 if (!
string.IsNullOrEmpty(Tag))
410 if (TaggedActions.ContainsKey(Tag))
412 TaggedActions[Tag].DynamicInvoke(blk);
414 if (removeTaggedActions)
416 TaggedActions.Remove(Tag);
423 PathText(text, x, y);
429 private byte StrokeAlpha = 255;
432 private byte FillAlpha = 255;
434 public void SetFillStyle((
int r,
int g,
int b,
double a) style)
436 FillStyle =
Colour.
FromRgba(style.r, style.g, style.b, (
int)(style.a * 255));
437 FillAlpha = (byte)(style.a * 255);
440 public void SetFillStyle(
Brush style)
446 FillAlpha = (byte)(solid.A * 255);
454 public void SetStrokeStyle((
int r,
int g,
int b,
double a) style)
456 StrokeStyle =
Colour.
FromRgba(style.r, style.g, style.b, (
int)(style.a * 255));
457 StrokeAlpha = (byte)(style.a * 255);
460 public void SetStrokeStyle(
Brush style)
466 StrokeAlpha = (byte)(solid.A * 255);
476 public void SetLineDash(
LineDash dash)
481 public void Rotate(
double angle)
483 Utils.CoerceNaNAndInfinityToZero(ref angle);
485 _transform = MatrixUtils.Rotate(_transform, angle);
487 currentPath =
new PathGeometry();
488 currentFigure =
new PathFigure() { IsClosed =
false };
489 figureInitialised =
false;
492 public void Transform(
double a,
double b,
double c,
double d,
double e,
double f)
494 Utils.CoerceNaNAndInfinityToZero(ref a, ref b, ref c, ref d, ref e, ref f);
496 double[,] transfMatrix =
new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
497 _transform = MatrixUtils.Multiply(_transform, transfMatrix);
499 currentPath =
new PathGeometry();
500 currentFigure =
new PathFigure() { IsClosed =
false };
501 figureInitialised =
false;
504 public void Scale(
double x,
double y)
506 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
508 _transform = MatrixUtils.Scale(_transform, x, y);
510 currentPath =
new PathGeometry();
511 currentFigure =
new PathFigure() { IsClosed =
false };
512 figureInitialised =
false;
515 private double[,] _transform;
517 private readonly Stack<double[,]> states;
521 states.Push((
double[,])_transform.Clone());
522 controlElements.Push(currControlElement);
525 public void Restore()
527 _transform = states.Pop();
528 currControlElement = controlElements.Pop();
531 public double LineWidth {
get;
set; }
532 public LineCaps LineCap {
get;
set; }
574 public (
double Width,
double Height) MeasureText(
string text)
576 Avalonia.Media.FormattedText txt =
new Avalonia.Media.FormattedText() { Text = text, Typeface =
new Typeface(
FontFamily), FontSize =
Font.
FontSize };
577 return (txt.Bounds.Width, txt.Bounds.Height);
580 private PathGeometry currentPath;
581 private PathFigure currentFigure;
583 private bool figureInitialised =
false;
585 public void MoveTo(
double x,
double y)
587 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
589 if (figureInitialised)
591 currentPath.Figures.Add(currentFigure);
594 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(x, y), IsClosed =
false };
595 figureInitialised =
true;
598 public void LineTo(
double x,
double y)
600 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
602 if (figureInitialised)
604 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x, y) });
608 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(x, y), IsClosed =
false };
609 figureInitialised =
true;
613 public void Rectangle(
double x0,
double y0,
double width,
double height)
615 Utils.CoerceNaNAndInfinityToZero(ref x0, ref y0, ref width, ref height);
617 if (currentFigure !=
null && figureInitialised)
619 currentPath.Figures.Add(currentFigure);
622 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(x0, y0), IsClosed =
false };
623 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x0 + width, y0) });
624 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x0 + width, y0 + height) });
625 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x0, y0 + height) });
626 currentFigure.IsClosed =
true;
628 currentPath.Figures.Add(currentFigure);
629 figureInitialised =
false;
632 public void CubicBezierTo(
double p1X,
double p1Y,
double p2X,
double p2Y,
double p3X,
double p3Y)
634 Utils.CoerceNaNAndInfinityToZero(ref p1X, ref p1Y, ref p2X, ref p2Y, ref p3X, ref p3Y);
636 if (figureInitialised)
638 currentFigure.Segments.Add(
new Avalonia.Media.BezierSegment() { Point1 = new Avalonia.Point(p1X, p1Y), Point2 = new Avalonia.Point(p2X, p2Y), Point3 = new Avalonia.Point(p3X, p3Y) });
642 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(p1X, p1Y), IsClosed =
false };
643 figureInitialised =
true;
649 currentFigure.IsClosed =
true;
650 currentPath.Figures.Add(currentFigure);
651 figureInitialised =
false;
656 if (figureInitialised)
658 currentFigure.IsClosed =
false;
659 currentPath.Figures.Add(currentFigure);
662 Avalonia.Media.Brush stroke =
null;
666 stroke =
new SolidColorBrush(Color.FromArgb(StrokeAlpha, (
byte)(solid.R * 255), (
byte)(solid.G * 255), (
byte)(solid.B * 255)));
670 stroke = linearGradient.ToLinearGradientBrush();
674 stroke = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
678 Path pth =
new Path() { Fill =
null, Stroke = stroke, StrokeThickness = LineWidth, StrokeDashArray =
new Avalonia.Collections.AvaloniaList<
double> { (
LineDash[0] + (LineCap ==
LineCaps.Butt ? 0 : LineWidth)) / LineWidth, (
LineDash[1] - (LineCap ==
LineCaps.Butt ? 0 : LineWidth)) / LineWidth }, StrokeDashOffset =
LineDash[2] / LineWidth };
683 pth.StrokeLineCap = PenLineCap.Flat;
686 pth.StrokeLineCap = PenLineCap.Round;
689 pth.StrokeLineCap = PenLineCap.Square;
696 pth.StrokeJoin = PenLineJoin.Bevel;
699 pth.StrokeJoin = PenLineJoin.Round;
702 pth.StrokeJoin = PenLineJoin.Miter;
706 pth.Data = currentPath;
708 pth.RenderTransform =
new MatrixTransform(_transform.ToAvaloniaMatrix());
709 pth.RenderTransformOrigin =
new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
711 currControlElement.Children.Add(pth);
713 currentPath =
new PathGeometry();
714 currentFigure =
new PathFigure() { IsClosed =
false };
715 figureInitialised =
false;
717 if (!
string.IsNullOrEmpty(Tag))
719 if (TaggedActions.ContainsKey(Tag))
721 TaggedActions[Tag].DynamicInvoke(pth);
723 if (removeTaggedActions)
725 TaggedActions.Remove(Tag);
733 if (figureInitialised)
735 currentPath.Figures.Add(currentFigure);
738 Avalonia.Media.Brush fill =
null;
742 fill =
new SolidColorBrush(Color.FromArgb(FillAlpha, (
byte)(solid.R * 255), (
byte)(solid.G * 255), (
byte)(solid.B * 255)));
746 fill = linearGradient.ToLinearGradientBrush();
750 fill = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
753 Path pth =
new Path() { Fill = fill, Stroke =
null };
755 pth.Data = currentPath;
757 pth.RenderTransform =
new MatrixTransform(_transform.ToAvaloniaMatrix());
758 pth.RenderTransformOrigin =
new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
760 currControlElement.Children.Add(pth);
762 currentPath =
new PathGeometry();
763 currentFigure =
new PathFigure() { IsClosed =
false };
764 figureInitialised =
false;
766 if (!
string.IsNullOrEmpty(Tag))
768 if (TaggedActions.ContainsKey(Tag))
770 TaggedActions[Tag].DynamicInvoke(pth);
772 if (removeTaggedActions)
774 TaggedActions.Remove(Tag);
780 public void SetClippingPath()
782 if (figureInitialised)
784 currentPath.Figures.Add(currentFigure);
787 Avalonia.Controls.Canvas newControlElement =
new Avalonia.Controls.Canvas();
789 newControlElement.Clip = currentPath;
791 newControlElement.RenderTransformOrigin =
new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
792 newControlElement.RenderTransform =
new MatrixTransform(_transform.ToAvaloniaMatrix());
794 _transform =
new double[3, 3];
796 _transform[0, 0] = 1;
797 _transform[1, 1] = 1;
798 _transform[2, 2] = 1;
800 currControlElement.Children.Add(newControlElement);
801 currControlElement = newControlElement;
803 currentPath =
new PathGeometry();
804 currentFigure =
new PathFigure() { IsClosed =
false };
805 figureInitialised =
false;
809 public void DrawRasterImage(
int sourceX,
int sourceY,
int sourceWidth,
int sourceHeight,
double destinationX,
double destinationY,
double destinationWidth,
double destinationHeight,
RasterImage image)
811 Utils.CoerceNaNAndInfinityToZero(ref destinationX, ref destinationY, ref destinationWidth, ref destinationHeight);
813 Image img =
new Image() { Source =
new CroppedBitmap(
new Bitmap(image.
PNGStream),
new Avalonia.PixelRect(sourceX, sourceY, sourceWidth, sourceHeight)), Width = destinationWidth, Height = destinationHeight };
817 RenderOptions.SetBitmapInterpolationMode(img, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.HighQuality);
821 RenderOptions.SetBitmapInterpolationMode(img, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.Default);
824 double[,] transf = MatrixUtils.Translate(_transform, destinationX, destinationY);
825 img.RenderTransform =
new MatrixTransform(transf.ToAvaloniaMatrix());
826 img.RenderTransformOrigin =
new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
828 currControlElement.Children.Add(img);
830 if (!
string.IsNullOrEmpty(Tag))
832 if (TaggedActions.ContainsKey(Tag))
834 TaggedActions[Tag].DynamicInvoke(img);
836 if (removeTaggedActions)
838 TaggedActions.Remove(Tag);
871 filtered = locInvFilter.Filter(rasterised, scale);
875 filtered = filterWithLoc.Filter(rasterised, bounds, scale);
878 if (filtered !=
null)
880 rasterised.Dispose();
912 filtered = locInvFilter.Filter(rasterised, scale);
916 filtered = filterWithLoc.Filter(rasterised, bounds, scale);
919 if (filtered !=
null)
921 rasterised.Dispose();
928 throw new NotImplementedException(
@"The filter could not be rasterised! You can avoid this error by doing one of the following:
929 • Add a reference to VectSharp.Raster or VectSharp.Raster.ImageSharp (you may also need to add a using directive somewhere to force the assembly to be loaded).
930 • Provide your own implementation of Graphics.RasterisationMethod.
931 • Set the FilterOption.Operation to ""RasteriseAllWithSkia"", ""IgnoreAll"" or ""SkipAll"".");
946 internal class RenderCanvas : Avalonia.Controls.Canvas
948 private List<RenderAction> RenderActions;
949 private List<RenderAction> TaggedRenderActions;
951 private SolidColorBrush BackgroundBrush;
953 static Avalonia.Point Origin =
new Avalonia.Point(0, 0);
955 public Dictionary<string, (IImage, bool)> Images;
959 this.RenderActions.Remove(action);
960 this.RenderActions.Add(action);
962 if (!
string.IsNullOrEmpty(action.
Tag))
964 int index = this.TaggedRenderActions.IndexOf(action);
967 this.TaggedRenderActions.RemoveAt(index);
968 this.TaggedRenderActions.Insert(0, action);
975 this.RenderActions.Remove(action);
976 this.RenderActions.Insert(0, action);
978 if (!
string.IsNullOrEmpty(action.
Tag))
980 int index = this.TaggedRenderActions.IndexOf(action);
983 this.TaggedRenderActions.RemoveAt(index);
984 this.TaggedRenderActions.Add(action);
991 this.BackgroundBrush =
new SolidColorBrush(Color.FromArgb((
byte)(backgroundColour.
A * 255), (
byte)(backgroundColour.
R * 255), (
byte)(backgroundColour.
G * 255), (
byte)(backgroundColour.
B * 255)));
994 this.Height = height;
995 this.Images =
new Dictionary<string, (IImage, bool)>();
996 AvaloniaDrawingContext ctx =
new AvaloniaDrawingContext(this.Width, this.Height, removeTaggedActionsAfterExecution, textOption, this.Images, filterOption);
997 foreach (KeyValuePair<string, Delegate> action
in taggedActions)
999 ctx.TaggedActions.Add(action.Key, (Func<
RenderAction, IEnumerable<RenderAction>>)action.Value);
1003 this.RenderActions = ctx.RenderActions;
1005 this.TaggedRenderActions =
new List<RenderAction>();
1007 for (
int i = this.RenderActions.Count - 1; i >= 0; i--)
1009 RenderActions[i].InternalParent =
this;
1010 if (!
string.IsNullOrEmpty(this.RenderActions[i].Tag))
1012 TaggedRenderActions.Add(this.RenderActions[i]);
1016 this.PointerPressed += PointerPressedAction;
1017 this.PointerReleased += PointerReleasedAction;
1018 this.PointerMoved += PointerMoveAction;
1019 this.PointerLeave += PointerLeaveAction;
1023 private int CurrentPressedAction = -1;
1024 private void PointerPressedAction(
object sender, Avalonia.Input.PointerPressedEventArgs e)
1026 Avalonia.Point position = e.GetPosition(
this);
1028 for (
int i = 0; i < TaggedRenderActions.Count; i++)
1030 if (TaggedRenderActions[i].ClippingPath ==
null || TaggedRenderActions[i].ClippingPath.FillContains(position))
1032 Avalonia.Point localPosition = position.Transform(TaggedRenderActions[i].InverseTransform);
1036 if ((TaggedRenderActions[i].Fill !=
null && TaggedRenderActions[i].Geometry.FillContains(localPosition)) || TaggedRenderActions[i].Geometry.StrokeContains(TaggedRenderActions[i].Stroke, localPosition))
1038 TaggedRenderActions[i].FirePointerPressed(e);
1039 CurrentPressedAction = i;
1045 if (TaggedRenderActions[i].Fill !=
null && TaggedRenderActions[i].Text.HitTestPoint(localPosition).IsInside)
1047 TaggedRenderActions[i].FirePointerPressed(e);
1048 CurrentPressedAction = i;
1054 if (TaggedRenderActions[i].ImageDestination.Value.Contains(localPosition))
1056 TaggedRenderActions[i].FirePointerPressed(e);
1057 CurrentPressedAction = i;
1065 private void PointerReleasedAction(
object sender, Avalonia.Input.PointerReleasedEventArgs e)
1067 if (CurrentPressedAction >= 0)
1069 TaggedRenderActions[CurrentPressedAction].FirePointerReleased(e);
1070 CurrentPressedAction = -1;
1074 Avalonia.Point position = e.GetPosition(
this);
1076 for (
int i = 0; i < TaggedRenderActions.Count; i++)
1078 if (TaggedRenderActions[i].ClippingPath ==
null || TaggedRenderActions[i].ClippingPath.FillContains(position))
1080 Avalonia.Point localPosition = position.Transform(TaggedRenderActions[i].InverseTransform);
1084 if ((TaggedRenderActions[i].Fill !=
null && TaggedRenderActions[i].Geometry.FillContains(localPosition)) || TaggedRenderActions[i].Geometry.StrokeContains(TaggedRenderActions[i].Stroke, localPosition))
1086 TaggedRenderActions[i].FirePointerReleased(e);
1092 if (TaggedRenderActions[i].Fill !=
null && TaggedRenderActions[i].Text.HitTestPoint(localPosition).IsInside)
1094 TaggedRenderActions[i].FirePointerReleased(e);
1100 if (TaggedRenderActions[i].ImageDestination.Value.Contains(localPosition))
1102 TaggedRenderActions[i].FirePointerReleased(e);
1111 private int CurrentOverAction = -1;
1112 private void PointerMoveAction(
object sender, Avalonia.Input.PointerEventArgs e)
1114 Avalonia.Point position = e.GetPosition(
this);
1118 for (
int i = 0; i < TaggedRenderActions.Count; i++)
1120 if (TaggedRenderActions[i].ClippingPath ==
null || TaggedRenderActions[i].ClippingPath.FillContains(position))
1122 Avalonia.Point localPosition = position.Transform(TaggedRenderActions[i].InverseTransform);
1126 if ((TaggedRenderActions[i].Fill !=
null && TaggedRenderActions[i].Geometry.FillContains(localPosition)) || TaggedRenderActions[i].Geometry.StrokeContains(TaggedRenderActions[i].Stroke, localPosition))
1130 if (CurrentOverAction != i)
1132 if (CurrentOverAction >= 0)
1134 TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1136 CurrentOverAction = i;
1137 TaggedRenderActions[CurrentOverAction].FirePointerEnter(e);
1145 if (TaggedRenderActions[i].Fill !=
null && TaggedRenderActions[i].Text.HitTestPoint(localPosition).IsInside)
1149 if (CurrentOverAction != i)
1151 if (CurrentOverAction >= 0)
1153 TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1155 CurrentOverAction = i;
1156 TaggedRenderActions[CurrentOverAction].FirePointerEnter(e);
1164 if (TaggedRenderActions[i].ImageDestination.Value.Contains(localPosition))
1168 if (CurrentOverAction != i)
1170 if (CurrentOverAction >= 0)
1172 TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1174 CurrentOverAction = i;
1175 TaggedRenderActions[CurrentOverAction].FirePointerEnter(e);
1186 if (CurrentOverAction >= 0)
1188 TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1190 CurrentOverAction = -1;
1194 private void PointerLeaveAction(
object sender, Avalonia.Input.PointerEventArgs e)
1196 if (CurrentOverAction >= 0)
1198 TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1200 CurrentOverAction = -1;
1204 public override void Render(DrawingContext context)
1206 context.FillRectangle(this.BackgroundBrush,
new Avalonia.Rect(0, 0, Width, Height));
1212 DrawingContext.PushedState? state =
null;
1217 context.DrawGeometry(
null,
new Pen(Brushes.Transparent), act.
ClippingPath);
1221 using (context.PushPreTransform(act.
Transform))
1230 context.DrawGeometry(
null,
new Pen(Brushes.Transparent), act.
ClippingPath);
1235 DrawingContext.PushedState? state =
null;
1240 context.DrawGeometry(
null,
new Pen(Brushes.Transparent), act.
ClippingPath);
1244 using (context.PushPreTransform(act.
Transform))
1246 context.DrawText(act.
Fill, Origin, act.
Text);
1253 context.DrawGeometry(
null,
new Pen(Brushes.Transparent), act.
ClippingPath);
1258 DrawingContext.PushedState? state =
null;
1263 context.DrawGeometry(
null,
new Pen(Brushes.Transparent), act.
ClippingPath);
1267 (IImage, bool) image = Images[act.
ImageId];
1269 using (context.PushPreTransform(act.
Transform))
1271 context.DrawImage(image.Item1, act.
ImageSource.Value, act.
ImageDestination.Value, image.Item2 ? Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.HighQuality : Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.Default);
1278 context.DrawGeometry(
null,
new Pen(Brushes.Transparent), act.
ClippingPath);
1326 public Avalonia.Media.FormattedText
Text {
get;
set; }
1358 private Avalonia.Matrix _transform = Avalonia.Matrix.Identity;
1370 get {
return _transform; }
1381 public string Tag {
get;
set; }
1383 internal RenderCanvas InternalParent {
get;
set; }
1392 return InternalParent;
1417 internal void FirePointerEnter(Avalonia.Input.PointerEventArgs e)
1422 internal void FirePointerLeave(Avalonia.Input.PointerEventArgs e)
1427 internal void FirePointerPressed(Avalonia.Input.PointerPressedEventArgs e)
1432 internal void FirePointerReleased(Avalonia.Input.PointerReleasedEventArgs e)
1437 private RenderAction()
1499 public static RenderAction ImageAction(
string imageId, Avalonia.Rect sourceRect, Avalonia.Rect destinationRect, Avalonia.Matrix transform,
Geometry clippingPath,
string tag =
null)
1518 this.InternalParent.BringToFront(
this);
1526 this.InternalParent.SendToBack(
this);
1533 public Dictionary<string, Func<RenderAction, IEnumerable<RenderAction>>> TaggedActions {
get;
set; } =
new Dictionary<string, Func<RenderAction, IEnumerable<RenderAction>>>();
1535 private bool removeTaggedActions =
true;
1537 public string Tag {
get;
set; }
1539 AvaloniaContextInterpreter.TextOptions _textOption;
1541 private Dictionary<string, (IImage, bool)> Images;
1543 private Geometry _clippingPath;
1544 private Stack<Geometry> clippingPaths;
1545 private FilterOption _filterOption;
1547 public AvaloniaDrawingContext(
double width,
double height,
bool removeTaggedActionsAfterExecution, AvaloniaContextInterpreter.TextOptions textOption, Dictionary<
string, (IImage,
bool)> images, FilterOption filterOption)
1549 this.Images = images;
1551 currentPath =
new PathGeometry();
1552 currentFigure =
new PathFigure() { IsClosed =
false };
1553 figureInitialised =
false;
1555 RenderActions =
new List<RenderAction>();
1556 removeTaggedActions = removeTaggedActionsAfterExecution;
1561 _transform =
new double[3, 3];
1563 _transform[0, 0] = 1;
1564 _transform[1, 1] = 1;
1565 _transform[2, 2] = 1;
1567 states =
new Stack<double[,]>();
1569 _textOption = textOption;
1571 _clippingPath =
null;
1572 clippingPaths =
new Stack<Geometry>();
1573 clippingPaths.Push(_clippingPath);
1575 _filterOption = filterOption;
1578 public List<RenderAction> RenderActions {
get;
set; }
1580 public double Width {
get;
private set; }
1581 public double Height {
get;
private set; }
1583 public void Translate(
double x,
double y)
1585 _transform = MatrixUtils.Translate(_transform, x, y);
1587 currentPath =
new PathGeometry();
1588 currentFigure =
new PathFigure() { IsClosed =
false };
1589 figureInitialised =
false;
1594 private void PathText(
string text,
double x,
double y)
1596 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1598 GraphicsPath textPath =
new GraphicsPath().AddText(x, y, text, Font, TextBaseline);
1600 for (
int j = 0; j < textPath.Segments.Count; j++)
1602 switch (textPath.Segments[j].Type)
1605 this.MoveTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
1608 this.LineTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
1611 this.CubicBezierTo(textPath.Segments[j].Points[0].X, textPath.Segments[j].Points[0].Y, textPath.Segments[j].Points[1].X, textPath.Segments[j].Points[1].Y, textPath.Segments[j].Points[2].X, textPath.Segments[j].Points[2].Y);
1620 public void StrokeText(
string text,
double x,
double y)
1622 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1624 PathText(text, x, y);
1628 public void FillText(
string text,
double x,
double y)
1630 if (!Font.EnableKerning)
1632 FillSimpleText(text, x, y);
1636 List<(string, Point)> tSpans =
new List<(
string, Point)>();
1638 System.Text.StringBuilder currentRun =
new System.Text.StringBuilder();
1639 Point currentKerning =
new Point();
1641 Point currentGlyphPlacementDelta =
new Point();
1642 Point currentGlyphAdvanceDelta =
new Point();
1643 Point nextGlyphPlacementDelta =
new Point();
1644 Point nextGlyphAdvanceDelta =
new Point();
1646 for (
int i = 0; i < text.Length; i++)
1648 if (i < text.Length - 1)
1650 currentGlyphPlacementDelta = nextGlyphPlacementDelta;
1651 currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
1652 nextGlyphAdvanceDelta =
new Point();
1653 nextGlyphPlacementDelta =
new Point();
1655 TrueTypeFile.PairKerning kerning = Font.FontFamily.TrueTypeFile.Get1000EmKerning(text[i], text[i + 1]);
1657 if (kerning !=
null)
1659 currentGlyphPlacementDelta =
new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
1660 currentGlyphAdvanceDelta =
new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
1662 nextGlyphPlacementDelta =
new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
1663 nextGlyphAdvanceDelta =
new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
1667 if (currentGlyphPlacementDelta.X != 0 || currentGlyphPlacementDelta.Y != 0 || currentGlyphAdvanceDelta.X != 0 || currentGlyphAdvanceDelta.Y != 0)
1669 if (currentRun.Length > 0)
1671 tSpans.Add((currentRun.ToString(), currentKerning));
1673 tSpans.Add((text[i].ToString(),
new Point(currentGlyphPlacementDelta.X * Font.FontSize / 1000, currentGlyphPlacementDelta.Y * Font.FontSize / 1000)));
1676 currentKerning =
new Point((currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X) * Font.FontSize / 1000, (currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y) * Font.FontSize / 1000);
1680 tSpans.Add((text[i].ToString(),
new Point(currentGlyphPlacementDelta.X * Font.FontSize / 1000 + currentKerning.X, currentGlyphPlacementDelta.Y * Font.FontSize / 1000 + currentKerning.Y)));
1683 currentKerning =
new Point((currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X) * Font.FontSize / 1000, (currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y) * Font.FontSize / 1000);
1688 currentRun.Append(text[i]);
1692 if (currentRun.Length > 0)
1694 tSpans.Add((currentRun.ToString(), currentKerning));
1700 Font.DetailedFontMetrics fullMetrics = Font.MeasureTextAdvanced(text);
1704 if (Font.FontFamily.TrueTypeFile !=
null)
1706 currY += fullMetrics.Top;
1711 if (Font.FontFamily.TrueTypeFile !=
null)
1713 currY += (fullMetrics.Top + fullMetrics.Bottom) * 0.5;
1718 if (Font.FontFamily.TrueTypeFile !=
null)
1720 currY += fullMetrics.Bottom;
1726 for (
int i = 0; i < tSpans.Count; i++)
1728 Font.DetailedFontMetrics metrics = Font.MeasureTextAdvanced(tSpans[i].Item1);
1732 FillSimpleText(tSpans[i].Item1, currX + tSpans[i].Item2.X, currY + tSpans[i].Item2.Y);
1736 FillSimpleText(tSpans[i].Item1, currX + metrics.LeftSideBearing - fullMetrics.LeftSideBearing + tSpans[i].Item2.X, currY + tSpans[i].Item2.Y);
1740 currX += metrics.AdvanceWidth + tSpans[i].Item2.X;
1741 currY += tSpans[i].Item2.Y;
1746 public void FillSimpleText(
string text,
double x,
double y)
1748 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1750 if (_textOption == AvaloniaContextInterpreter.TextOptions.NeverConvert || (_textOption == AvaloniaContextInterpreter.TextOptions.ConvertIfNecessary && Font.FontFamily.IsStandardFamily && Font.FontFamily.FileName !=
"ZapfDingbats" && Font.FontFamily.FileName !=
"Symbol"))
1752 Avalonia.Media.FormattedText txt =
new Avalonia.Media.FormattedText()
1755 Typeface =
new Typeface(Avalonia.Media.FontFamily.Parse(FontFamily), (Font.FontFamily.IsOblique ? FontStyle.Oblique : Font.FontFamily.IsItalic ? FontStyle.Italic : FontStyle.Normal), (Font.FontFamily.IsBold ? FontWeight.Bold : FontWeight.Regular)),
1756 FontSize = Font.FontSize
1764 double[,] currTransform =
null;
1765 double[,] deltaTransform = MatrixUtils.Identity;
1767 if (Font.FontFamily.TrueTypeFile !=
null)
1769 currTransform = MatrixUtils.Translate(_transform, x, y);
1772 Font.DetailedFontMetrics metrics = Font.MeasureTextAdvanced(text);
1776 if (Font.FontFamily.TrueTypeFile !=
null)
1778 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1780 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top - Font.WinAscent);
1781 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top - Font.WinAscent);
1785 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top - Font.Ascent);
1786 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top - Font.Ascent);
1793 if (Font.FontFamily.TrueTypeFile !=
null)
1795 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1797 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 - Font.WinAscent);
1798 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 - Font.WinAscent);
1802 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 - Font.Ascent);
1803 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top + metrics.Top / 2 + metrics.Bottom / 2 - Font.Ascent);
1809 double lsb = Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(text[0]).LeftSideBearing * Font.FontSize / 1000;
1811 if (Font.FontFamily.TrueTypeFile !=
null)
1813 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1815 currTransform = MatrixUtils.Translate(_transform, left - lsb, top - Font.WinAscent);
1816 deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top - Font.YMax);
1820 currTransform = MatrixUtils.Translate(_transform, left - lsb, top - Font.Ascent);
1821 deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top - Font.YMax);
1827 if (Font.FontFamily.TrueTypeFile !=
null)
1829 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1831 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top - Font.WinAscent + metrics.Bottom);
1832 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top - Font.WinAscent + metrics.Bottom);
1836 currTransform = MatrixUtils.Translate(_transform, left - metrics.LeftSideBearing, top - Font.Ascent + metrics.Bottom);
1837 deltaTransform = MatrixUtils.Translate(deltaTransform, left - metrics.LeftSideBearing, top - Font.Ascent + metrics.Bottom);
1842 Avalonia.Media.Brush fill =
null;
1844 if (this.FillStyle is SolidColourBrush solid)
1846 fill =
new SolidColorBrush(Color.FromArgb(FillAlpha, (
byte)(solid.R * 255), (
byte)(solid.G * 255), (
byte)(solid.B * 255)));
1848 else if (this.FillStyle is LinearGradientBrush linearGradient)
1850 fill = linearGradient.ToLinearGradientBrush(deltaTransform);
1852 else if (this.FillStyle is RadialGradientBrush radialGradient)
1854 fill = radialGradient.ToRadialGradientBrush(metrics.Width + metrics.LeftSideBearing + metrics.RightSideBearing, deltaTransform);
1857 RenderAction act = RenderAction.TextAction(txt, fill, currTransform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
1859 if (!
string.IsNullOrEmpty(Tag))
1861 if (TaggedActions.ContainsKey(Tag))
1863 IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
1865 foreach (RenderAction action
in actions)
1867 RenderActions.Add(action);
1870 if (removeTaggedActions)
1872 TaggedActions.Remove(Tag);
1877 RenderActions.Add(act);
1880 else if (TaggedActions.ContainsKey(
""))
1882 IEnumerable<RenderAction> actions = TaggedActions[
""](act);
1884 foreach (RenderAction action
in actions)
1886 RenderActions.Add(action);
1891 RenderActions.Add(act);
1896 PathText(text, x, y);
1901 public Brush StrokeStyle {
get;
private set; } = Colour.FromRgb(0, 0, 0);
1902 private byte StrokeAlpha = 255;
1904 public Brush FillStyle {
get;
private set; } = Colour.FromRgb(0, 0, 0);
1905 private byte FillAlpha = 255;
1907 public void SetFillStyle((
int r,
int g,
int b,
double a) style)
1909 FillStyle = Colour.FromRgba(style.r, style.g, style.b, (
int)(style.a * 255));
1910 FillAlpha = (byte)(style.a * 255);
1913 public void SetFillStyle(Brush style)
1917 if (style is SolidColourBrush solid)
1919 FillAlpha = (byte)(solid.A * 255);
1927 public void SetStrokeStyle((
int r,
int g,
int b,
double a) style)
1929 StrokeStyle = Colour.FromRgba(style.r, style.g, style.b, (
int)(style.a * 255));
1930 StrokeAlpha = (byte)(style.a * 255);
1933 public void SetStrokeStyle(Brush style)
1935 StrokeStyle = style;
1937 if (style is SolidColourBrush solid)
1939 StrokeAlpha = (byte)(solid.A * 255);
1947 private double[] LineDash;
1949 public void SetLineDash(LineDash dash)
1951 LineDash =
new double[] { dash.UnitsOn, dash.UnitsOff, dash.Phase };
1954 public void Rotate(
double angle)
1956 Utils.CoerceNaNAndInfinityToZero(ref angle);
1958 _transform = MatrixUtils.Rotate(_transform, angle);
1960 currentPath =
new PathGeometry();
1961 currentFigure =
new PathFigure() { IsClosed =
false };
1962 figureInitialised =
false;
1965 public void Transform(
double a,
double b,
double c,
double d,
double e,
double f)
1967 Utils.CoerceNaNAndInfinityToZero(ref a, ref b, ref c, ref d, ref e, ref f);
1969 double[,] transfMatrix =
new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
1970 _transform = MatrixUtils.Multiply(_transform, transfMatrix);
1972 currentPath =
new PathGeometry();
1973 currentFigure =
new PathFigure() { IsClosed =
false };
1974 figureInitialised =
false;
1977 public void Scale(
double x,
double y)
1979 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1981 _transform = MatrixUtils.Scale(_transform, x, y);
1983 currentPath =
new PathGeometry();
1984 currentFigure =
new PathFigure() { IsClosed =
false };
1985 figureInitialised =
false;
1988 private double[,] _transform;
1990 private readonly Stack<double[,]> states;
1994 states.Push((
double[,])_transform.Clone());
1995 clippingPaths.Push(_clippingPath?.Clone());
1998 public void Restore()
2000 _transform = states.Pop();
2001 _clippingPath = clippingPaths.Pop();
2004 public double LineWidth {
get;
set; }
2005 public LineCaps LineCap {
get;
set; }
2008 private string FontFamily;
2022 if (!Font.FontFamily.IsStandardFamily)
2024 if (Font.FontFamily is ResourceFontFamily fam)
2026 FontFamily = fam.ResourceName;
2030 if (Font.FontFamily.TrueTypeFile !=
null)
2032 FontFamily = Font.FontFamily.TrueTypeFile.GetFontFamilyName();
2036 FontFamily = Font.FontFamily.FileName;
2042 FontFamily =
"resm:VectSharp.StandardFonts.?assembly=VectSharp#" + Font.FontFamily.TrueTypeFile.GetFontFamilyName();
2047 public (
double Width,
double Height) MeasureText(
string text)
2049 Avalonia.Media.FormattedText txt =
new Avalonia.Media.FormattedText() { Text = text, Typeface =
new Typeface(FontFamily), FontSize = Font.FontSize };
2050 return (txt.Bounds.Width, txt.Bounds.Height);
2053 private PathGeometry currentPath;
2054 private PathFigure currentFigure;
2056 private bool figureInitialised =
false;
2058 public void MoveTo(
double x,
double y)
2060 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
2062 if (figureInitialised)
2064 currentPath.Figures.Add(currentFigure);
2067 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(x, y), IsClosed =
false };
2068 figureInitialised =
true;
2071 public void LineTo(
double x,
double y)
2073 Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
2075 if (figureInitialised)
2077 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x, y) });
2081 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(x, y), IsClosed =
false };
2082 figureInitialised =
true;
2086 public void Rectangle(
double x0,
double y0,
double width,
double height)
2088 Utils.CoerceNaNAndInfinityToZero(ref x0, ref y0, ref width, ref height);
2090 if (currentFigure !=
null && figureInitialised)
2092 currentPath.Figures.Add(currentFigure);
2095 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(x0, y0), IsClosed =
false };
2096 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x0 + width, y0) });
2097 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x0 + width, y0 + height) });
2098 currentFigure.Segments.Add(
new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x0, y0 + height) });
2099 currentFigure.IsClosed =
true;
2101 currentPath.Figures.Add(currentFigure);
2102 figureInitialised =
false;
2105 public void CubicBezierTo(
double p1X,
double p1Y,
double p2X,
double p2Y,
double p3X,
double p3Y)
2107 Utils.CoerceNaNAndInfinityToZero(ref p1X, ref p1Y, ref p2X, ref p2Y, ref p3X, ref p3Y);
2109 if (figureInitialised)
2111 currentFigure.Segments.Add(
new Avalonia.Media.BezierSegment() { Point1 = new Avalonia.Point(p1X, p1Y), Point2 = new Avalonia.Point(p2X, p2Y), Point3 = new Avalonia.Point(p3X, p3Y) });
2115 currentFigure =
new PathFigure() { StartPoint =
new Avalonia.Point(p1X, p1Y), IsClosed =
false };
2116 figureInitialised =
true;
2122 currentFigure.IsClosed =
true;
2123 currentPath.Figures.Add(currentFigure);
2124 figureInitialised =
false;
2127 public void Stroke()
2129 if (figureInitialised)
2131 currentFigure.IsClosed =
false;
2132 currentPath.Figures.Add(currentFigure);
2135 Avalonia.Media.Brush stroke =
null;
2137 if (this.StrokeStyle is SolidColourBrush solid)
2139 stroke =
new SolidColorBrush(Color.FromArgb(StrokeAlpha, (
byte)(solid.R * 255), (
byte)(solid.G * 255), (
byte)(solid.B * 255)));
2141 else if (this.StrokeStyle is LinearGradientBrush linearGradient)
2143 stroke = linearGradient.ToLinearGradientBrush();
2145 else if (this.StrokeStyle is RadialGradientBrush radialGradient)
2147 stroke = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
2150 Pen pen =
new Pen(stroke,
2152 new DashStyle(
new double[] { (LineDash[0] + (LineCap ==
LineCaps.Butt ? 0 : LineWidth)) / LineWidth, (LineDash[1] - (LineCap ==
LineCaps.Butt ? 0 : LineWidth)) / LineWidth }, LineDash[2] / LineWidth));
2157 pen.LineCap = PenLineCap.Flat;
2160 pen.LineCap = PenLineCap.Round;
2163 pen.LineCap = PenLineCap.Square;
2170 pen.LineJoin = PenLineJoin.Bevel;
2173 pen.LineJoin = PenLineJoin.Round;
2176 pen.LineJoin = PenLineJoin.Miter;
2180 RenderAction act = RenderAction.PathAction(currentPath, pen,
null, _transform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
2182 if (!
string.IsNullOrEmpty(Tag))
2184 if (TaggedActions.ContainsKey(Tag))
2186 IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
2188 foreach (RenderAction action
in actions)
2190 RenderActions.Add(action);
2193 if (removeTaggedActions)
2195 TaggedActions.Remove(Tag);
2200 RenderActions.Add(act);
2203 else if (TaggedActions.ContainsKey(
""))
2205 IEnumerable<RenderAction> actions = TaggedActions[
""](act);
2207 foreach (RenderAction action
in actions)
2209 RenderActions.Add(action);
2214 RenderActions.Add(act);
2217 currentPath =
new PathGeometry();
2218 currentFigure =
new PathFigure() { IsClosed =
false };
2219 figureInitialised =
false;
2224 if (figureInitialised)
2226 currentPath.Figures.Add(currentFigure);
2229 Avalonia.Media.Brush fill =
null;
2231 if (this.FillStyle is SolidColourBrush solid)
2233 fill =
new SolidColorBrush(Color.FromArgb(FillAlpha, (
byte)(solid.R * 255), (
byte)(solid.G * 255), (
byte)(solid.B * 255)));
2235 else if (this.FillStyle is LinearGradientBrush linearGradient)
2237 fill = linearGradient.ToLinearGradientBrush();
2239 else if (this.FillStyle is RadialGradientBrush radialGradient)
2241 fill = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
2244 RenderAction act = RenderAction.PathAction(currentPath,
null, fill, _transform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
2246 if (!
string.IsNullOrEmpty(Tag))
2248 if (TaggedActions.ContainsKey(Tag))
2250 IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
2252 foreach (RenderAction action
in actions)
2254 RenderActions.Add(action);
2257 if (removeTaggedActions)
2259 TaggedActions.Remove(Tag);
2264 RenderActions.Add(act);
2267 else if (TaggedActions.ContainsKey(
""))
2269 IEnumerable<RenderAction> actions = TaggedActions[
""](act);
2271 foreach (RenderAction action
in actions)
2273 RenderActions.Add(action);
2278 RenderActions.Add(act);
2281 currentPath =
new PathGeometry();
2282 currentFigure =
new PathFigure() { IsClosed =
false };
2283 figureInitialised =
false;
2286 private static void TransformGeometry(PathGeometry geo,
double[,] transf)
2288 for (
int i = 0; i < geo.Figures.Count; i++)
2290 double[] tP = MatrixUtils.Multiply(transf,
new double[] { geo.Figures[i].StartPoint.X, geo.Figures[i].StartPoint.Y });
2291 geo.Figures[i].StartPoint =
new Avalonia.Point(tP[0], tP[1]);
2293 for (
int j = 0; j < geo.Figures[i].Segments.Count; j++)
2295 if (geo.Figures[i].Segments[j] is LineSegment lS)
2297 tP = MatrixUtils.Multiply(transf,
new double[] { lS.Point.X, lS.Point.Y });
2298 lS.Point =
new Avalonia.Point(tP[0], tP[1]);
2300 else if (geo.Figures[i].Segments[j] is BezierSegment bS)
2302 tP = MatrixUtils.Multiply(transf,
new double[] { bS.Point1.X, bS.Point1.Y });
2303 double[] tP2 = MatrixUtils.Multiply(transf,
new double[] { bS.Point2.X, bS.Point2.Y });
2304 double[] tP3 = MatrixUtils.Multiply(transf,
new double[] { bS.Point3.X, bS.Point3.Y });
2306 bS.Point1 =
new Avalonia.Point(tP[0], tP[1]);
2307 bS.Point2 =
new Avalonia.Point(tP2[0], tP2[1]);
2308 bS.Point3 =
new Avalonia.Point(tP3[0], tP3[1]);
2314 public void SetClippingPath()
2316 if (figureInitialised)
2318 currentPath.Figures.Add(currentFigure);
2321 TransformGeometry(currentPath, _transform);
2323 if (_clippingPath ==
null)
2325 _clippingPath = currentPath;
2330 _clippingPath = (StreamGeometry)Activator.CreateInstance(typeof(StreamGeometry), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
null,
new object[] { _clippingPath.PlatformImpl.Intersect(currentPath.PlatformImpl) },
null);
2333 currentPath =
new PathGeometry();
2334 currentFigure =
new PathFigure() { IsClosed =
false };
2335 figureInitialised =
false;
2338 public void DrawRasterImage(
int sourceX,
int sourceY,
int sourceWidth,
int sourceHeight,
double destinationX,
double destinationY,
double destinationWidth,
double destinationHeight, RasterImage image)
2340 Utils.CoerceNaNAndInfinityToZero(ref destinationX, ref destinationY, ref destinationWidth, ref destinationHeight);
2342 if (!this.Images.ContainsKey(image.Id))
2344 Bitmap bmp =
new Bitmap(image.PNGStream);
2345 this.Images.Add(image.Id, (bmp, image.Interpolate));
2348 RenderAction act = RenderAction.ImageAction(image.Id,
new Avalonia.Rect(sourceX, sourceY, sourceWidth, sourceHeight),
new Avalonia.Rect(destinationX, destinationY, destinationWidth, destinationHeight), _transform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
2350 if (!
string.IsNullOrEmpty(Tag))
2352 if (TaggedActions.ContainsKey(Tag))
2354 IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
2356 foreach (RenderAction action
in actions)
2358 RenderActions.Add(action);
2361 if (removeTaggedActions)
2363 TaggedActions.Remove(Tag);
2368 RenderActions.Add(act);
2371 else if (TaggedActions.ContainsKey(
""))
2373 IEnumerable<RenderAction> actions = TaggedActions[
""](act);
2375 foreach (RenderAction action
in actions)
2377 RenderActions.Add(action);
2382 RenderActions.Add(act);
2386 public void DrawFilteredGraphics(Graphics graphics,
IFilter filter)
2388 if (this._filterOption.Operation == FilterOption.FilterOperations.RasteriseAllWithSkia)
2390 double scale = this._filterOption.RasterisationResolution;
2392 Rectangle bounds = graphics.GetBounds();
2396 if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
2398 if (!this._filterOption.RasterisationResolutionRelative)
2400 scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
2403 RasterImage rasterised = SKRenderContextInterpreter.Rasterise(graphics, bounds, scale,
true);
2404 RasterImage filtered =
null;
2408 filterWithRastParam.RasteriseParameter(SKRenderContextInterpreter.Rasterise, scale);
2413 filtered = locInvFilter.Filter(rasterised, scale);
2417 filtered = filterWithLoc.Filter(rasterised, bounds, scale);
2420 if (filtered !=
null)
2422 rasterised.Dispose();
2424 DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
2428 else if (this._filterOption.Operation == FilterOption.FilterOperations.RasteriseAllWithVectSharp)
2430 double scale = this._filterOption.RasterisationResolution;
2432 Rectangle bounds = graphics.GetBounds();
2436 if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
2438 if (!this._filterOption.RasterisationResolutionRelative)
2440 scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
2443 if (graphics.TryRasterise(bounds, scale,
true, out RasterImage rasterised))
2445 RasterImage filtered =
null;
2449 filterWithRastParam.RasteriseParameter(SKRenderContextInterpreter.Rasterise, scale);
2454 filtered = locInvFilter.Filter(rasterised, scale);
2458 filtered = filterWithLoc.Filter(rasterised, bounds, scale);
2461 if (filtered !=
null)
2463 rasterised.Dispose();
2465 DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
2470 throw new NotImplementedException(
@"The filter could not be rasterised! You can avoid this error by doing one of the following:
2471 • Add a reference to VectSharp.Raster or VectSharp.Raster.ImageSharp (you may also need to add a using directive somewhere to force the assembly to be loaded).
2472 • Provide your own implementation of Graphics.RasterisationMethod.
2473 • Set the FilterOption.Operation to ""RasteriseAllWithSkia"", ""IgnoreAll"" or ""SkipAll"".");
2477 else if (this._filterOption.Operation == FilterOption.FilterOperations.IgnoreAll)
2479 graphics.CopyToIGraphicsContext(
this);
2524 if (filterOption ==
null)
2529 AvaloniaContext ctx =
new AvaloniaContext(page.
Width, page.
Height,
true, textOption, filterOption);
2532 return ctx.ControlItem;
2545 if (graphicsAsControls)
2547 Avalonia.Controls.Canvas tbr = page.PaintToCanvas(textOption, filterOption);
2553 return new RenderCanvas(page.
Graphics, page.
Background, page.
Width, page.
Height,
new Dictionary<string, Delegate>(),
true, textOption, filterOption) { Background =
new SolidColorBrush(Color.FromArgb((
byte)(page.
Background.
A * 255), (
byte)(page.
Background.
R * 255), (
byte)(page.
Background.
G * 255), (
byte)(page.
Background.
B * 255))) };
2568 public static Avalonia.Controls.Canvas
PaintToCanvas(
this Page page,
bool graphicsAsControls, Dictionary<string, Delegate> taggedActions,
bool removeTaggedActionsAfterExecution =
true,
TextOptions textOption =
TextOptions.ConvertIfNecessary,
FilterOption filterOption =
default)
2570 if (graphicsAsControls)
2572 Avalonia.Controls.Canvas tbr = page.PaintToCanvas(taggedActions, removeTaggedActionsAfterExecution, textOption, filterOption);
2578 return new RenderCanvas(page.
Graphics, page.
Background, page.
Width, page.
Height, taggedActions, removeTaggedActionsAfterExecution, textOption, filterOption) { Background =
new SolidColorBrush(Color.FromArgb((
byte)(page.
Background.
A * 255), (
byte)(page.
Background.
R * 255), (
byte)(page.
Background.
G * 255), (
byte)(page.
Background.
B * 255))) }; ;
2594 if (filterOption ==
null)
2599 AvaloniaContext ctx =
new AvaloniaContext(page.
Width, page.
Height, removeTaggedActionsAfterExecution, textOption, filterOption)
2601 TaggedActions = taggedActions
2605 return ctx.ControlItem;
2608 internal static Avalonia.Media.LinearGradientBrush ToLinearGradientBrush(
this LinearGradientBrush brush,
double[,] transformMatrix =
null)
2613 if (transformMatrix !=
null)
2615 double[,] inverse = MatrixUtils.Invert(transformMatrix);
2617 double[] startVec = MatrixUtils.Multiply(inverse,
new double[] { start.
X, start.
Y });
2618 double[] endVec = MatrixUtils.Multiply(inverse,
new double[] { end.
X, end.
Y });
2620 start =
new Point(startVec[0], startVec[1]);
2621 end =
new Point(endVec[0], endVec[1]);
2624 Avalonia.Media.LinearGradientBrush tbr =
new Avalonia.Media.LinearGradientBrush()
2626 SpreadMethod = GradientSpreadMethod.Pad,
2627 StartPoint =
new Avalonia.RelativePoint(start.
X, start.
Y, Avalonia.RelativeUnit.Absolute),
2628 EndPoint =
new Avalonia.RelativePoint(end.
X, end.
Y, Avalonia.RelativeUnit.Absolute)
2631 Avalonia.Media.GradientStops stops =
new Avalonia.Media.GradientStops();
2632 stops.AddRange(from el in brush.
GradientStops select
new Avalonia.Media.GradientStop(Color.FromArgb((
byte)(el.Colour.A * 255), (
byte)(el.Colour.R * 255), (
byte)(el.Colour.G * 255), (
byte)(el.Colour.B * 255)), el.Offset));
2634 tbr.GradientStops = stops;
2639 internal static Avalonia.Media.RadialGradientBrush ToRadialGradientBrush(
this RadialGradientBrush brush,
double objectWidth,
double[,] transformMatrix =
null)
2641 Point focus = brush.FocalPoint;
2642 Point centre = brush.Centre;
2644 if (transformMatrix !=
null)
2646 double[,] inverse = MatrixUtils.Invert(transformMatrix);
2648 double[] focusVec = MatrixUtils.Multiply(inverse,
new double[] { focus.X, focus.Y });
2649 double[] centreVec = MatrixUtils.Multiply(inverse,
new double[] { centre.X, centre.Y });
2651 focus =
new Point(focusVec[0], focusVec[1]);
2652 centre =
new Point(centreVec[0], centreVec[1]);
2655 Avalonia.Media.RadialGradientBrush tbr =
new Avalonia.Media.RadialGradientBrush()
2657 SpreadMethod = GradientSpreadMethod.Pad,
2658 Center =
new Avalonia.RelativePoint(centre.X, centre.Y, Avalonia.RelativeUnit.Absolute),
2659 GradientOrigin =
new Avalonia.RelativePoint(focus.X, focus.Y, Avalonia.RelativeUnit.Absolute),
2660 Radius = brush.Radius / objectWidth
2663 Avalonia.Media.GradientStops stops =
new Avalonia.Media.GradientStops();
2664 stops.AddRange(from el in brush.GradientStops select
new Avalonia.Media.GradientStop(Color.FromArgb((
byte)(el.Colour.A * 255), (
byte)(el.Colour.R * 255), (
byte)(el.Colour.G * 255), (
byte)(el.Colour.B * 255)), el.Offset));
2665 tbr.GradientStops = stops;