VectSharp  2.2.1
A light library for C# vector graphics
AvaloniaContext.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 Avalonia.Controls;
19 using Avalonia.Controls.Shapes;
20 using Avalonia.Media;
21 using Avalonia.Media.Imaging;
22 using System;
23 using System.Collections.Generic;
24 using System.Linq;
25 using System.Runtime.InteropServices;
26 using VectSharp.Filters;
27 
29 {
30  internal static class MatrixUtils
31  {
32  public static Avalonia.Matrix ToAvaloniaMatrix(this double[,] matrix)
33  {
34  return new Avalonia.Matrix(matrix[0, 0], matrix[1, 0], matrix[0, 1], matrix[1, 1], matrix[0, 2], matrix[1, 2]);
35  }
36 
37  public static double[] Multiply(double[,] matrix, double[] vector)
38  {
39  double[] tbr = new double[2];
40 
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];
43 
44  return tbr;
45  }
46 
47  public static double[,] Multiply(double[,] matrix1, double[,] matrix2)
48  {
49  double[,] tbr = new double[3, 3];
50 
51  for (int i = 0; i < 3; i++)
52  {
53  for (int j = 0; j < 3; j++)
54  {
55  for (int k = 0; k < 3; k++)
56  {
57  tbr[i, j] += matrix1[i, k] * matrix2[k, j];
58  }
59  }
60  }
61 
62  return tbr;
63  }
64 
65  public static double[,] Rotate(double[,] matrix, double angle)
66  {
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;
73 
74  return Multiply(matrix, rotationMatrix);
75  }
76 
77  public static double[,] Translate(double[,] matrix, double x, double y)
78  {
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;
85 
86  return Multiply(matrix, translationMatrix);
87  }
88 
89  public static double[,] Scale(double[,] matrix, double scaleX, double scaleY)
90  {
91  double[,] scaleMatrix = new double[3, 3];
92  scaleMatrix[0, 0] = scaleX;
93  scaleMatrix[1, 1] = scaleY;
94  scaleMatrix[2, 2] = 1;
95 
96  return Multiply(matrix, scaleMatrix);
97  }
98 
99  public static double[,] Identity = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
100  public static double[,] Invert(double[,] m)
101  {
102  double[,] tbr = new double[3, 3];
103 
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]);
113 
114  return tbr;
115  }
116  }
117 
118  internal static class Utils
119  {
120  public static void CoerceNaNAndInfinityToZero(ref double val)
121  {
122  if (double.IsNaN(val) || double.IsInfinity(val) || val == double.MinValue || val == double.MaxValue)
123  {
124  val = 0;
125  }
126  }
127 
128  public static void CoerceNaNAndInfinityToZero(ref double val1, ref double val2)
129  {
130  if (double.IsNaN(val1) || double.IsInfinity(val1) || val1 == double.MinValue || val1 == double.MaxValue)
131  {
132  val1 = 0;
133  }
134 
135  if (double.IsNaN(val2) || double.IsInfinity(val2) || val2 == double.MinValue || val2 == double.MaxValue)
136  {
137  val2 = 0;
138  }
139  }
140 
141  public static void CoerceNaNAndInfinityToZero(ref double val1, ref double val2, ref double val3, ref double val4)
142  {
143  if (double.IsNaN(val1) || double.IsInfinity(val1) || val1 == double.MinValue || val1 == double.MaxValue)
144  {
145  val1 = 0;
146  }
147 
148  if (double.IsNaN(val2) || double.IsInfinity(val2) || val2 == double.MinValue || val2 == double.MaxValue)
149  {
150  val2 = 0;
151  }
152 
153  if (double.IsNaN(val3) || double.IsInfinity(val3) || val3 == double.MinValue || val3 == double.MaxValue)
154  {
155  val3 = 0;
156  }
157 
158  if (double.IsNaN(val4) || double.IsInfinity(val4) || val4 == double.MinValue || val4 == double.MaxValue)
159  {
160  val4 = 0;
161  }
162  }
163 
164  public static void CoerceNaNAndInfinityToZero(ref double val1, ref double val2, ref double val3, ref double val4, ref double val5, ref double val6)
165  {
166  if (double.IsNaN(val1) || double.IsInfinity(val1) || val1 == double.MinValue || val1 == double.MaxValue)
167  {
168  val1 = 0;
169  }
170 
171  if (double.IsNaN(val2) || double.IsInfinity(val2) || val2 == double.MinValue || val2 == double.MaxValue)
172  {
173  val2 = 0;
174  }
175 
176  if (double.IsNaN(val3) || double.IsInfinity(val3) || val3 == double.MinValue || val3 == double.MaxValue)
177  {
178  val3 = 0;
179  }
180 
181  if (double.IsNaN(val4) || double.IsInfinity(val4) || val4 == double.MinValue || val4 == double.MaxValue)
182  {
183  val4 = 0;
184  }
185 
186  if (double.IsNaN(val5) || double.IsInfinity(val5) || val5 == double.MinValue || val5 == double.MaxValue)
187  {
188  val5 = 0;
189  }
190 
191  if (double.IsNaN(val6) || double.IsInfinity(val6) || val6 == double.MinValue || val6 == double.MaxValue)
192  {
193  val6 = 0;
194  }
195  }
196  }
197 
198  internal class AvaloniaContext : IGraphicsContext
199  {
200  public Dictionary<string, Delegate> TaggedActions { get; set; } = new Dictionary<string, Delegate>();
201 
202  private bool removeTaggedActions = true;
203 
204  public string Tag { get; set; }
205 
206  private AvaloniaContextInterpreter.TextOptions _textOption;
207 
208  private Avalonia.Controls.Canvas currControlElement;
209 
210  private Stack<Avalonia.Controls.Canvas> controlElements;
211  private FilterOption _filterOption;
212 
213  public AvaloniaContext(double width, double height, bool removeTaggedActionsAfterExecution, AvaloniaContextInterpreter.TextOptions textOption, FilterOption filterOption)
214  {
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;
220 
221  _transform = new double[3, 3];
222 
223  _transform[0, 0] = 1;
224  _transform[1, 1] = 1;
225  _transform[2, 2] = 1;
226 
227  states = new Stack<double[,]>();
228 
229  _textOption = textOption;
230 
231  currControlElement = ControlItem;
232  controlElements = new Stack<Avalonia.Controls.Canvas>();
233  controlElements.Push(ControlItem);
234  _filterOption = filterOption;
235  }
236 
237  public Avalonia.Controls.Canvas ControlItem { get; }
238 
239  public double Width { get { return ControlItem.Width; } }
240  public double Height { get { return ControlItem.Height; } }
241 
242  public void Translate(double x, double y)
243  {
244  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
245 
246  _transform = MatrixUtils.Translate(_transform, x, y);
247 
248  currentPath = new PathGeometry();
249  currentFigure = new PathFigure() { IsClosed = false };
250  figureInitialised = false;
251  }
252 
253  public TextBaselines TextBaseline { get; set; }
254 
255  private void PathText(string text, double x, double y)
256  {
257  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
258 
259  GraphicsPath textPath = new GraphicsPath().AddText(x, y, text, Font, TextBaseline);
260 
261  for (int j = 0; j < textPath.Segments.Count; j++)
262  {
263  switch (textPath.Segments[j].Type)
264  {
265  case VectSharp.SegmentType.Move:
266  this.MoveTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
267  break;
268  case VectSharp.SegmentType.Line:
269  this.LineTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
270  break;
271  case VectSharp.SegmentType.CubicBezier:
272  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);
273  break;
274  case VectSharp.SegmentType.Close:
275  this.Close();
276  break;
277  }
278  }
279  }
280 
281  public void StrokeText(string text, double x, double y)
282  {
283  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
284 
285  PathText(text, x, y);
286  Stroke();
287  }
288 
289  public void FillText(string text, double x, double y)
290  {
291  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
292 
293  if (_textOption == AvaloniaContextInterpreter.TextOptions.NeverConvert || (_textOption == AvaloniaContextInterpreter.TextOptions.ConvertIfNecessary && Font.FontFamily.IsStandardFamily && Font.FontFamily.FileName != "ZapfDingbats" && Font.FontFamily.FileName != "Symbol"))
294  {
295  TextBlock blk = new TextBlock() { ClipToBounds = false, Text = text, FontFamily = Avalonia.Media.FontFamily.Parse(FontFamily), FontSize = Font.FontSize, FontStyle = (Font.FontFamily.IsOblique ? FontStyle.Oblique : Font.FontFamily.IsItalic ? FontStyle.Italic : FontStyle.Normal), FontWeight = (Font.FontFamily.IsBold ? FontWeight.Bold : FontWeight.Regular) };
296 
297  double top = y;
298  double left = x;
299 
300  double[,] currTransform = null;
301  double[,] deltaTransform = MatrixUtils.Identity;
302 
303  if (Font.FontFamily.TrueTypeFile != null)
304  {
305  currTransform = MatrixUtils.Translate(_transform, x, y);
306  }
307 
309 
310  if (TextBaseline == TextBaselines.Top)
311  {
312  blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
313 
314  if (Font.FontFamily.TrueTypeFile != null)
315  {
316  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
317  {
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);
320  }
321  else
322  {
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);
325  }
326  }
327  }
328  else if (TextBaseline == TextBaselines.Middle)
329  {
330  blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
331 
332  if (Font.FontFamily.TrueTypeFile != null)
333  {
334  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
335  {
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);
338  }
339  else
340  {
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);
343  }
344 
345  }
346  }
347  else if (TextBaseline == TextBaselines.Baseline)
348  {
349  double lsb = Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(text[0]).LeftSideBearing * Font.FontSize / 1000;
350 
351  blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
352 
353  if (Font.FontFamily.TrueTypeFile != null)
354  {
355  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
356  {
357  currTransform = MatrixUtils.Translate(_transform, left - lsb, top - Font.WinAscent);
358  deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top - Font.YMax);
359  }
360  else
361  {
362  currTransform = MatrixUtils.Translate(_transform, left - lsb, top - Font.Ascent);
363  deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top - Font.YMax);
364  }
365  }
366  }
367  else if (TextBaseline == TextBaselines.Bottom)
368  {
369  blk.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Bottom;
370 
371  if (Font.FontFamily.TrueTypeFile != null)
372  {
373  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
374  {
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);
377  }
378  else
379  {
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);
382  }
383  }
384  }
385 
386  blk.RenderTransform = new MatrixTransform(currTransform.ToAvaloniaMatrix());
387  blk.RenderTransformOrigin = new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
388 
389  Avalonia.Media.Brush foreground = null;
390 
391  if (this.FillStyle is SolidColourBrush solid)
392  {
393  foreground = new SolidColorBrush(Color.FromArgb(FillAlpha, (byte)(solid.R * 255), (byte)(solid.G * 255), (byte)(solid.B * 255)));
394  }
395  else if (this.FillStyle is LinearGradientBrush linearGradient)
396  {
397  foreground = linearGradient.ToLinearGradientBrush(deltaTransform);
398  }
399  else if (this.FillStyle is RadialGradientBrush radialGradient)
400  {
401  foreground = radialGradient.ToRadialGradientBrush(metrics.Width + metrics.LeftSideBearing + metrics.RightSideBearing, deltaTransform);
402  }
403 
404  blk.Foreground = foreground;
405 
406  currControlElement.Children.Add(blk);
407 
408  if (!string.IsNullOrEmpty(Tag))
409  {
410  if (TaggedActions.ContainsKey(Tag))
411  {
412  TaggedActions[Tag].DynamicInvoke(blk);
413 
414  if (removeTaggedActions)
415  {
416  TaggedActions.Remove(Tag);
417  }
418  }
419  }
420  }
421  else
422  {
423  PathText(text, x, y);
424  Fill();
425  }
426  }
427 
428  public Brush StrokeStyle { get; private set; } = Colour.FromRgb(0, 0, 0);
429  private byte StrokeAlpha = 255;
430 
431  public Brush FillStyle { get; private set; } = Colour.FromRgb(0, 0, 0);
432  private byte FillAlpha = 255;
433 
434  public void SetFillStyle((int r, int g, int b, double a) style)
435  {
436  FillStyle = Colour.FromRgba(style.r, style.g, style.b, (int)(style.a * 255));
437  FillAlpha = (byte)(style.a * 255);
438  }
439 
440  public void SetFillStyle(Brush style)
441  {
442  FillStyle = style;
443 
444  if (style is SolidColourBrush solid)
445  {
446  FillAlpha = (byte)(solid.A * 255);
447  }
448  else
449  {
450  FillAlpha = 255;
451  }
452  }
453 
454  public void SetStrokeStyle((int r, int g, int b, double a) style)
455  {
456  StrokeStyle = Colour.FromRgba(style.r, style.g, style.b, (int)(style.a * 255));
457  StrokeAlpha = (byte)(style.a * 255);
458  }
459 
460  public void SetStrokeStyle(Brush style)
461  {
462  StrokeStyle = style;
463 
464  if (style is SolidColourBrush solid)
465  {
466  StrokeAlpha = (byte)(solid.A * 255);
467  }
468  else
469  {
470  StrokeAlpha = 255;
471  }
472  }
473 
474  private double[] LineDash;
475 
476  public void SetLineDash(LineDash dash)
477  {
478  LineDash = new double[] { dash.UnitsOn, dash.UnitsOff, dash.Phase };
479  }
480 
481  public void Rotate(double angle)
482  {
483  Utils.CoerceNaNAndInfinityToZero(ref angle);
484 
485  _transform = MatrixUtils.Rotate(_transform, angle);
486 
487  currentPath = new PathGeometry();
488  currentFigure = new PathFigure() { IsClosed = false };
489  figureInitialised = false;
490  }
491 
492  public void Transform(double a, double b, double c, double d, double e, double f)
493  {
494  Utils.CoerceNaNAndInfinityToZero(ref a, ref b, ref c, ref d, ref e, ref f);
495 
496  double[,] transfMatrix = new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
497  _transform = MatrixUtils.Multiply(_transform, transfMatrix);
498 
499  currentPath = new PathGeometry();
500  currentFigure = new PathFigure() { IsClosed = false };
501  figureInitialised = false;
502  }
503 
504  public void Scale(double x, double y)
505  {
506  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
507 
508  _transform = MatrixUtils.Scale(_transform, x, y);
509 
510  currentPath = new PathGeometry();
511  currentFigure = new PathFigure() { IsClosed = false };
512  figureInitialised = false;
513  }
514 
515  private double[,] _transform;
516 
517  private readonly Stack<double[,]> states;
518 
519  public void Save()
520  {
521  states.Push((double[,])_transform.Clone());
522  controlElements.Push(currControlElement);
523  }
524 
525  public void Restore()
526  {
527  _transform = states.Pop();
528  currControlElement = controlElements.Pop();
529  }
530 
531  public double LineWidth { get; set; }
532  public LineCaps LineCap { get; set; }
533  public LineJoins LineJoin { get; set; }
534 
535  private string FontFamily;
536  private Font _Font;
537 
538  public Font Font
539  {
540  get
541  {
542  return _Font;
543  }
544 
545  set
546  {
547  _Font = value;
548 
550  {
551  FontFamily = fam.ResourceName;
552  }
553  else
554  {
556  {
557  if (Font.FontFamily.TrueTypeFile != null)
558  {
560  }
561  else
562  {
564  }
565  }
566  else
567  {
568  FontFamily = "resm:VectSharp.StandardFonts.?assembly=VectSharp#" + Font.FontFamily.TrueTypeFile.GetFontFamilyName();
569  }
570  }
571  }
572  }
573 
574  public (double Width, double Height) MeasureText(string text)
575  {
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);
578  }
579 
580  private PathGeometry currentPath;
581  private PathFigure currentFigure;
582 
583  private bool figureInitialised = false;
584 
585  public void MoveTo(double x, double y)
586  {
587  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
588 
589  if (figureInitialised)
590  {
591  currentPath.Figures.Add(currentFigure);
592  }
593 
594  currentFigure = new PathFigure() { StartPoint = new Avalonia.Point(x, y), IsClosed = false };
595  figureInitialised = true;
596  }
597 
598  public void LineTo(double x, double y)
599  {
600  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
601 
602  if (figureInitialised)
603  {
604  currentFigure.Segments.Add(new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x, y) });
605  }
606  else
607  {
608  currentFigure = new PathFigure() { StartPoint = new Avalonia.Point(x, y), IsClosed = false };
609  figureInitialised = true;
610  }
611  }
612 
613  public void Rectangle(double x0, double y0, double width, double height)
614  {
615  Utils.CoerceNaNAndInfinityToZero(ref x0, ref y0, ref width, ref height);
616 
617  if (currentFigure != null && figureInitialised)
618  {
619  currentPath.Figures.Add(currentFigure);
620  }
621 
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;
627 
628  currentPath.Figures.Add(currentFigure);
629  figureInitialised = false;
630  }
631 
632  public void CubicBezierTo(double p1X, double p1Y, double p2X, double p2Y, double p3X, double p3Y)
633  {
634  Utils.CoerceNaNAndInfinityToZero(ref p1X, ref p1Y, ref p2X, ref p2Y, ref p3X, ref p3Y);
635 
636  if (figureInitialised)
637  {
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) });
639  }
640  else
641  {
642  currentFigure = new PathFigure() { StartPoint = new Avalonia.Point(p1X, p1Y), IsClosed = false };
643  figureInitialised = true;
644  }
645  }
646 
647  public void Close()
648  {
649  currentFigure.IsClosed = true;
650  currentPath.Figures.Add(currentFigure);
651  figureInitialised = false;
652  }
653 
654  public void Stroke()
655  {
656  if (figureInitialised)
657  {
658  currentFigure.IsClosed = false;
659  currentPath.Figures.Add(currentFigure);
660  }
661 
662  Avalonia.Media.Brush stroke = null;
663 
664  if (this.StrokeStyle is SolidColourBrush solid)
665  {
666  stroke = new SolidColorBrush(Color.FromArgb(StrokeAlpha, (byte)(solid.R * 255), (byte)(solid.G * 255), (byte)(solid.B * 255)));
667  }
668  else if (this.StrokeStyle is LinearGradientBrush linearGradient)
669  {
670  stroke = linearGradient.ToLinearGradientBrush();
671  }
672  else if (this.StrokeStyle is RadialGradientBrush radialGradient)
673  {
674  stroke = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
675  }
676 
677 
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 };
679 
680  switch (LineCap)
681  {
682  case LineCaps.Butt:
683  pth.StrokeLineCap = PenLineCap.Flat;
684  break;
685  case LineCaps.Round:
686  pth.StrokeLineCap = PenLineCap.Round;
687  break;
688  case LineCaps.Square:
689  pth.StrokeLineCap = PenLineCap.Square;
690  break;
691  }
692 
693  switch (LineJoin)
694  {
695  case LineJoins.Bevel:
696  pth.StrokeJoin = PenLineJoin.Bevel;
697  break;
698  case LineJoins.Round:
699  pth.StrokeJoin = PenLineJoin.Round;
700  break;
701  case LineJoins.Miter:
702  pth.StrokeJoin = PenLineJoin.Miter;
703  break;
704  }
705 
706  pth.Data = currentPath;
707 
708  pth.RenderTransform = new MatrixTransform(_transform.ToAvaloniaMatrix());
709  pth.RenderTransformOrigin = new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
710 
711  currControlElement.Children.Add(pth);
712 
713  currentPath = new PathGeometry();
714  currentFigure = new PathFigure() { IsClosed = false };
715  figureInitialised = false;
716 
717  if (!string.IsNullOrEmpty(Tag))
718  {
719  if (TaggedActions.ContainsKey(Tag))
720  {
721  TaggedActions[Tag].DynamicInvoke(pth);
722 
723  if (removeTaggedActions)
724  {
725  TaggedActions.Remove(Tag);
726  }
727  }
728  }
729  }
730 
731  public void Fill()
732  {
733  if (figureInitialised)
734  {
735  currentPath.Figures.Add(currentFigure);
736  }
737 
738  Avalonia.Media.Brush fill = null;
739 
740  if (this.FillStyle is SolidColourBrush solid)
741  {
742  fill = new SolidColorBrush(Color.FromArgb(FillAlpha, (byte)(solid.R * 255), (byte)(solid.G * 255), (byte)(solid.B * 255)));
743  }
744  else if (this.FillStyle is LinearGradientBrush linearGradient)
745  {
746  fill = linearGradient.ToLinearGradientBrush();
747  }
748  else if (this.FillStyle is RadialGradientBrush radialGradient)
749  {
750  fill = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
751  }
752 
753  Path pth = new Path() { Fill = fill, Stroke = null };
754 
755  pth.Data = currentPath;
756 
757  pth.RenderTransform = new MatrixTransform(_transform.ToAvaloniaMatrix());
758  pth.RenderTransformOrigin = new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
759 
760  currControlElement.Children.Add(pth);
761 
762  currentPath = new PathGeometry();
763  currentFigure = new PathFigure() { IsClosed = false };
764  figureInitialised = false;
765 
766  if (!string.IsNullOrEmpty(Tag))
767  {
768  if (TaggedActions.ContainsKey(Tag))
769  {
770  TaggedActions[Tag].DynamicInvoke(pth);
771 
772  if (removeTaggedActions)
773  {
774  TaggedActions.Remove(Tag);
775  }
776  }
777  }
778  }
779 
780  public void SetClippingPath()
781  {
782  if (figureInitialised)
783  {
784  currentPath.Figures.Add(currentFigure);
785  }
786 
787  Avalonia.Controls.Canvas newControlElement = new Avalonia.Controls.Canvas();
788 
789  newControlElement.Clip = currentPath;
790 
791  newControlElement.RenderTransformOrigin = new Avalonia.RelativePoint(0, 0, Avalonia.RelativeUnit.Absolute);
792  newControlElement.RenderTransform = new MatrixTransform(_transform.ToAvaloniaMatrix());
793 
794  _transform = new double[3, 3];
795 
796  _transform[0, 0] = 1;
797  _transform[1, 1] = 1;
798  _transform[2, 2] = 1;
799 
800  currControlElement.Children.Add(newControlElement);
801  currControlElement = newControlElement;
802 
803  currentPath = new PathGeometry();
804  currentFigure = new PathFigure() { IsClosed = false };
805  figureInitialised = false;
806  }
807 
808 
809  public void DrawRasterImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, RasterImage image)
810  {
811  Utils.CoerceNaNAndInfinityToZero(ref destinationX, ref destinationY, ref destinationWidth, ref destinationHeight);
812 
813  Image img = new Image() { Source = new CroppedBitmap(new Bitmap(image.PNGStream), new Avalonia.PixelRect(sourceX, sourceY, sourceWidth, sourceHeight)), Width = destinationWidth, Height = destinationHeight };
814 
815  if (image.Interpolate)
816  {
817  RenderOptions.SetBitmapInterpolationMode(img, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.HighQuality);
818  }
819  else
820  {
821  RenderOptions.SetBitmapInterpolationMode(img, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.Default);
822  }
823 
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);
827 
828  currControlElement.Children.Add(img);
829 
830  if (!string.IsNullOrEmpty(Tag))
831  {
832  if (TaggedActions.ContainsKey(Tag))
833  {
834  TaggedActions[Tag].DynamicInvoke(img);
835 
836  if (removeTaggedActions)
837  {
838  TaggedActions.Remove(Tag);
839  }
840  }
841  }
842  }
843 
844  public void DrawFilteredGraphics(Graphics graphics, IFilter filter)
845  {
846  if (this._filterOption.Operation == FilterOption.FilterOperations.RasteriseAllWithSkia)
847  {
848  double scale = this._filterOption.RasterisationResolution;
849 
850  Rectangle bounds = graphics.GetBounds();
851 
852  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
853 
854  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
855  {
856  if (!this._filterOption.RasterisationResolutionRelative)
857  {
858  scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
859  }
860 
861  RasterImage rasterised = SKRenderContextInterpreter.Rasterise(graphics, bounds, scale, true);
862  RasterImage filtered = null;
863 
864  if (filter is IFilterWithRasterisableParameter filterWithRastParam)
865  {
866  filterWithRastParam.RasteriseParameter(SKRenderContextInterpreter.Rasterise, scale);
867  }
868 
869  if (filter is ILocationInvariantFilter locInvFilter)
870  {
871  filtered = locInvFilter.Filter(rasterised, scale);
872  }
873  else if (filter is IFilterWithLocation filterWithLoc)
874  {
875  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
876  }
877 
878  if (filtered != null)
879  {
880  rasterised.Dispose();
881 
882  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
883  }
884  }
885  }
886  else if (this._filterOption.Operation == FilterOption.FilterOperations.RasteriseAllWithVectSharp)
887  {
888  double scale = this._filterOption.RasterisationResolution;
889 
890  Rectangle bounds = graphics.GetBounds();
891 
892  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
893 
894  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
895  {
896  if (!this._filterOption.RasterisationResolutionRelative)
897  {
898  scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
899  }
900 
901  if (graphics.TryRasterise(bounds, scale, true, out RasterImage rasterised))
902  {
903  RasterImage filtered = null;
904 
905  if (filter is IFilterWithRasterisableParameter filterWithRastParam)
906  {
907  filterWithRastParam.RasteriseParameter(SKRenderContextInterpreter.Rasterise, scale);
908  }
909 
910  if (filter is ILocationInvariantFilter locInvFilter)
911  {
912  filtered = locInvFilter.Filter(rasterised, scale);
913  }
914  else if (filter is IFilterWithLocation filterWithLoc)
915  {
916  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
917  }
918 
919  if (filtered != null)
920  {
921  rasterised.Dispose();
922 
923  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
924  }
925  }
926  else
927  {
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"".");
932  }
933  }
934  }
935  else if (this._filterOption.Operation == FilterOption.FilterOperations.IgnoreAll)
936  {
937  graphics.CopyToIGraphicsContext(this);
938  }
939  else
940  {
941 
942  }
943  }
944  }
945 
946  internal class RenderCanvas : Avalonia.Controls.Canvas
947  {
948  private List<RenderAction> RenderActions;
949  private List<RenderAction> TaggedRenderActions;
950 
951  private SolidColorBrush BackgroundBrush;
952 
953  static Avalonia.Point Origin = new Avalonia.Point(0, 0);
954 
955  public Dictionary<string, (IImage, bool)> Images;
956 
957  public void BringToFront(RenderAction action)
958  {
959  this.RenderActions.Remove(action);
960  this.RenderActions.Add(action);
961 
962  if (!string.IsNullOrEmpty(action.Tag))
963  {
964  int index = this.TaggedRenderActions.IndexOf(action);
965  if (index >= 0)
966  {
967  this.TaggedRenderActions.RemoveAt(index);
968  this.TaggedRenderActions.Insert(0, action);
969  }
970  }
971  }
972 
973  public void SendToBack(RenderAction action)
974  {
975  this.RenderActions.Remove(action);
976  this.RenderActions.Insert(0, action);
977 
978  if (!string.IsNullOrEmpty(action.Tag))
979  {
980  int index = this.TaggedRenderActions.IndexOf(action);
981  if (index >= 0)
982  {
983  this.TaggedRenderActions.RemoveAt(index);
984  this.TaggedRenderActions.Add(action);
985  }
986  }
987  }
988 
989  public RenderCanvas(Graphics content, Colour backgroundColour, double width, double height, Dictionary<string, Delegate> taggedActions, bool removeTaggedActionsAfterExecution, AvaloniaContextInterpreter.TextOptions textOption, FilterOption filterOption)
990  {
991  this.BackgroundBrush = new SolidColorBrush(Color.FromArgb((byte)(backgroundColour.A * 255), (byte)(backgroundColour.R * 255), (byte)(backgroundColour.G * 255), (byte)(backgroundColour.B * 255)));
992 
993  this.Width = width;
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)
998  {
999  ctx.TaggedActions.Add(action.Key, (Func<RenderAction, IEnumerable<RenderAction>>)action.Value);
1000  }
1001 
1002  content.CopyToIGraphicsContext(ctx);
1003  this.RenderActions = ctx.RenderActions;
1004 
1005  this.TaggedRenderActions = new List<RenderAction>();
1006 
1007  for (int i = this.RenderActions.Count - 1; i >= 0; i--)
1008  {
1009  RenderActions[i].InternalParent = this;
1010  if (!string.IsNullOrEmpty(this.RenderActions[i].Tag))
1011  {
1012  TaggedRenderActions.Add(this.RenderActions[i]);
1013  }
1014  }
1015 
1016  this.PointerPressed += PointerPressedAction;
1017  this.PointerReleased += PointerReleasedAction;
1018  this.PointerMoved += PointerMoveAction;
1019  this.PointerLeave += PointerLeaveAction;
1020  }
1021 
1022 
1023  private int CurrentPressedAction = -1;
1024  private void PointerPressedAction(object sender, Avalonia.Input.PointerPressedEventArgs e)
1025  {
1026  Avalonia.Point position = e.GetPosition(this);
1027 
1028  for (int i = 0; i < TaggedRenderActions.Count; i++)
1029  {
1030  if (TaggedRenderActions[i].ClippingPath == null || TaggedRenderActions[i].ClippingPath.FillContains(position))
1031  {
1032  Avalonia.Point localPosition = position.Transform(TaggedRenderActions[i].InverseTransform);
1033 
1034  if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.Path)
1035  {
1036  if ((TaggedRenderActions[i].Fill != null && TaggedRenderActions[i].Geometry.FillContains(localPosition)) || TaggedRenderActions[i].Geometry.StrokeContains(TaggedRenderActions[i].Stroke, localPosition))
1037  {
1038  TaggedRenderActions[i].FirePointerPressed(e);
1039  CurrentPressedAction = i;
1040  break;
1041  }
1042  }
1043  else if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.Text)
1044  {
1045  if (TaggedRenderActions[i].Fill != null && TaggedRenderActions[i].Text.HitTestPoint(localPosition).IsInside)
1046  {
1047  TaggedRenderActions[i].FirePointerPressed(e);
1048  CurrentPressedAction = i;
1049  break;
1050  }
1051  }
1052  else if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.RasterImage)
1053  {
1054  if (TaggedRenderActions[i].ImageDestination.Value.Contains(localPosition))
1055  {
1056  TaggedRenderActions[i].FirePointerPressed(e);
1057  CurrentPressedAction = i;
1058  break;
1059  }
1060  }
1061  }
1062  }
1063  }
1064 
1065  private void PointerReleasedAction(object sender, Avalonia.Input.PointerReleasedEventArgs e)
1066  {
1067  if (CurrentPressedAction >= 0)
1068  {
1069  TaggedRenderActions[CurrentPressedAction].FirePointerReleased(e);
1070  CurrentPressedAction = -1;
1071  }
1072  else
1073  {
1074  Avalonia.Point position = e.GetPosition(this);
1075 
1076  for (int i = 0; i < TaggedRenderActions.Count; i++)
1077  {
1078  if (TaggedRenderActions[i].ClippingPath == null || TaggedRenderActions[i].ClippingPath.FillContains(position))
1079  {
1080  Avalonia.Point localPosition = position.Transform(TaggedRenderActions[i].InverseTransform);
1081 
1082  if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.Path)
1083  {
1084  if ((TaggedRenderActions[i].Fill != null && TaggedRenderActions[i].Geometry.FillContains(localPosition)) || TaggedRenderActions[i].Geometry.StrokeContains(TaggedRenderActions[i].Stroke, localPosition))
1085  {
1086  TaggedRenderActions[i].FirePointerReleased(e);
1087  break;
1088  }
1089  }
1090  else if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.Text)
1091  {
1092  if (TaggedRenderActions[i].Fill != null && TaggedRenderActions[i].Text.HitTestPoint(localPosition).IsInside)
1093  {
1094  TaggedRenderActions[i].FirePointerReleased(e);
1095  break;
1096  }
1097  }
1098  else if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.RasterImage)
1099  {
1100  if (TaggedRenderActions[i].ImageDestination.Value.Contains(localPosition))
1101  {
1102  TaggedRenderActions[i].FirePointerReleased(e);
1103  break;
1104  }
1105  }
1106  }
1107  }
1108  }
1109  }
1110 
1111  private int CurrentOverAction = -1;
1112  private void PointerMoveAction(object sender, Avalonia.Input.PointerEventArgs e)
1113  {
1114  Avalonia.Point position = e.GetPosition(this);
1115 
1116  bool found = false;
1117 
1118  for (int i = 0; i < TaggedRenderActions.Count; i++)
1119  {
1120  if (TaggedRenderActions[i].ClippingPath == null || TaggedRenderActions[i].ClippingPath.FillContains(position))
1121  {
1122  Avalonia.Point localPosition = position.Transform(TaggedRenderActions[i].InverseTransform);
1123 
1124  if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.Path)
1125  {
1126  if ((TaggedRenderActions[i].Fill != null && TaggedRenderActions[i].Geometry.FillContains(localPosition)) || TaggedRenderActions[i].Geometry.StrokeContains(TaggedRenderActions[i].Stroke, localPosition))
1127  {
1128  found = true;
1129 
1130  if (CurrentOverAction != i)
1131  {
1132  if (CurrentOverAction >= 0)
1133  {
1134  TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1135  }
1136  CurrentOverAction = i;
1137  TaggedRenderActions[CurrentOverAction].FirePointerEnter(e);
1138  }
1139 
1140  break;
1141  }
1142  }
1143  else if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.Text)
1144  {
1145  if (TaggedRenderActions[i].Fill != null && TaggedRenderActions[i].Text.HitTestPoint(localPosition).IsInside)
1146  {
1147  found = true;
1148 
1149  if (CurrentOverAction != i)
1150  {
1151  if (CurrentOverAction >= 0)
1152  {
1153  TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1154  }
1155  CurrentOverAction = i;
1156  TaggedRenderActions[CurrentOverAction].FirePointerEnter(e);
1157  }
1158 
1159  break;
1160  }
1161  }
1162  else if (TaggedRenderActions[i].ActionType == RenderAction.ActionTypes.RasterImage)
1163  {
1164  if (TaggedRenderActions[i].ImageDestination.Value.Contains(localPosition))
1165  {
1166  found = true;
1167 
1168  if (CurrentOverAction != i)
1169  {
1170  if (CurrentOverAction >= 0)
1171  {
1172  TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1173  }
1174  CurrentOverAction = i;
1175  TaggedRenderActions[CurrentOverAction].FirePointerEnter(e);
1176  }
1177 
1178  break;
1179  }
1180  }
1181  }
1182  }
1183 
1184  if (!found)
1185  {
1186  if (CurrentOverAction >= 0)
1187  {
1188  TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1189  }
1190  CurrentOverAction = -1;
1191  }
1192  }
1193 
1194  private void PointerLeaveAction(object sender, Avalonia.Input.PointerEventArgs e)
1195  {
1196  if (CurrentOverAction >= 0)
1197  {
1198  TaggedRenderActions[CurrentOverAction].FirePointerLeave(e);
1199  }
1200  CurrentOverAction = -1;
1201  }
1202 
1203 
1204  public override void Render(DrawingContext context)
1205  {
1206  context.FillRectangle(this.BackgroundBrush, new Avalonia.Rect(0, 0, Width, Height));
1207 
1208  foreach (RenderAction act in this.RenderActions)
1209  {
1210  if (act.ActionType == RenderAction.ActionTypes.Path)
1211  {
1212  DrawingContext.PushedState? state = null;
1213 
1214  if (act.ClippingPath != null)
1215  {
1216  //Random draw operation needed due to https://github.com/AvaloniaUI/Avalonia/issues/4408
1217  context.DrawGeometry(null, new Pen(Brushes.Transparent), act.ClippingPath);
1218  state = context.PushGeometryClip(act.ClippingPath);
1219  }
1220 
1221  using (context.PushPreTransform(act.Transform))
1222  {
1223  context.DrawGeometry(act.Fill, act.Stroke, act.Geometry);
1224  }
1225 
1226  if (state != null)
1227  {
1228  state?.Dispose();
1229  //Random draw operation needed due to https://github.com/AvaloniaUI/Avalonia/issues/4408
1230  context.DrawGeometry(null, new Pen(Brushes.Transparent), act.ClippingPath);
1231  }
1232  }
1233  else if (act.ActionType == RenderAction.ActionTypes.Text)
1234  {
1235  DrawingContext.PushedState? state = null;
1236 
1237  if (act.ClippingPath != null)
1238  {
1239  //Random draw operation needed due to https://github.com/AvaloniaUI/Avalonia/issues/4408
1240  context.DrawGeometry(null, new Pen(Brushes.Transparent), act.ClippingPath);
1241  state = context.PushGeometryClip(act.ClippingPath);
1242  }
1243 
1244  using (context.PushPreTransform(act.Transform))
1245  {
1246  context.DrawText(act.Fill, Origin, act.Text);
1247  }
1248 
1249  if (state != null)
1250  {
1251  state?.Dispose();
1252  //Random draw operation needed due to https://github.com/AvaloniaUI/Avalonia/issues/4408
1253  context.DrawGeometry(null, new Pen(Brushes.Transparent), act.ClippingPath);
1254  }
1255  }
1256  else if (act.ActionType == RenderAction.ActionTypes.RasterImage)
1257  {
1258  DrawingContext.PushedState? state = null;
1259 
1260  if (act.ClippingPath != null)
1261  {
1262  //Random draw operation needed due to https://github.com/AvaloniaUI/Avalonia/issues/4408
1263  context.DrawGeometry(null, new Pen(Brushes.Transparent), act.ClippingPath);
1264  state = context.PushGeometryClip(act.ClippingPath);
1265  }
1266 
1267  (IImage, bool) image = Images[act.ImageId];
1268 
1269  using (context.PushPreTransform(act.Transform))
1270  {
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);
1272  }
1273 
1274  if (state != null)
1275  {
1276  state?.Dispose();
1277  //Random draw operation needed due to https://github.com/AvaloniaUI/Avalonia/issues/4408
1278  context.DrawGeometry(null, new Pen(Brushes.Transparent), act.ClippingPath);
1279  }
1280  }
1281  }
1282 
1283  }
1284  }
1285 
1286 
1287  /// <summary>
1288  /// Represents a light-weight rendering action.
1289  /// </summary>
1290  public class RenderAction
1291  {
1292  /// <summary>
1293  /// Types of rendering actions.
1294  /// </summary>
1295  public enum ActionTypes
1296  {
1297  /// <summary>
1298  /// The render action represents a path object.
1299  /// </summary>
1300  Path,
1301 
1302  /// <summary>
1303  /// The render action represents a text object.
1304  /// </summary>
1305  Text,
1306 
1307  /// <summary>
1308  /// The render action represents a raster image.
1309  /// </summary>
1310  RasterImage
1311  }
1312 
1313  /// <summary>
1314  /// Type of the rendering action.
1315  /// </summary>
1316  public ActionTypes ActionType { get; private set; }
1317 
1318  /// <summary>
1319  /// Geometry that needs to be rendered (null if the action type is <see cref="ActionTypes.Text"/>). If you change this, you need to invalidate the <see cref="Parent"/>'s visual.
1320  /// </summary>
1321  public Geometry Geometry { get; set; }
1322 
1323  /// <summary>
1324  /// Text that needs to be rendered (null if the action type is <see cref="ActionTypes.Path"/>). If you change this, you need to invalidate the <see cref="Parent"/>'s visual.
1325  /// </summary>
1326  public Avalonia.Media.FormattedText Text { get; set; }
1327 
1328  /// <summary>
1329  /// Rendering stroke (null if the action type is <see cref="ActionTypes.Text"/> or if the rendered action only has a <see cref="Fill"/>). If you change this, you need to invalidate the <see cref="Parent"/>'s visual.
1330  /// </summary>
1331  public Pen Stroke { get; set; }
1332 
1333  /// <summary>
1334  /// Rendering fill (null if the rendered action only has a <see cref="Stroke"/>). If you change this, you need to invalidate the <see cref="Parent"/>'s visual.
1335  /// </summary>
1336  public IBrush Fill { get; set; }
1337 
1338  /// <summary>
1339  /// Univocal identifier of the image that needs to be drawn.
1340  /// </summary>
1341  public string ImageId { get; set; }
1342 
1343  /// <summary>
1344  /// The source rectangle of the image.
1345  /// </summary>
1346  public Avalonia.Rect? ImageSource { get; set; }
1347 
1348  /// <summary>
1349  /// The destination rectangle of the image.
1350  /// </summary>
1351  public Avalonia.Rect? ImageDestination { get; set; }
1352 
1353  /// <summary>
1354  /// The current clipping path.
1355  /// </summary>
1356  public Geometry ClippingPath { get; set; }
1357 
1358  private Avalonia.Matrix _transform = Avalonia.Matrix.Identity;
1359 
1360  /// <summary>
1361  /// Inverse transformation matrix.
1362  /// </summary>
1363  public Avalonia.Matrix InverseTransform { get; private set; } = Avalonia.Matrix.Identity;
1364 
1365  /// <summary>
1366  /// Rendering transformation matrix. If you change this, you need to invalidate the <see cref="Parent"/>'s visual.
1367  /// </summary>
1368  public Avalonia.Matrix Transform
1369  {
1370  get { return _transform; }
1371  set
1372  {
1373  _transform = value;
1374  InverseTransform = _transform.Invert();
1375  }
1376  }
1377 
1378  /// <summary>
1379  /// A tag to access the <see cref="RenderAction"/>.
1380  /// </summary>
1381  public string Tag { get; set; }
1382 
1383  internal RenderCanvas InternalParent { get; set; }
1384 
1385  /// <summary>
1386  /// The container of this <see cref="RenderAction"/>.
1387  /// </summary>
1388  public Avalonia.Controls.Canvas Parent
1389  {
1390  get
1391  {
1392  return InternalParent;
1393  }
1394  }
1395 
1396  /// <summary>
1397  /// Raised when the pointer enters the area covered by the <see cref="RenderAction"/>.
1398  /// </summary>
1399  public event EventHandler<Avalonia.Input.PointerEventArgs> PointerEnter;
1400 
1401  /// <summary>
1402  /// Raised when the pointer leaves the area covered by the <see cref="RenderAction"/>.
1403  /// </summary>
1404  public event EventHandler<Avalonia.Input.PointerEventArgs> PointerLeave;
1405 
1406  /// <summary>
1407  /// Raised when the pointer is pressed while over the area covered by the <see cref="RenderAction"/>.
1408  /// </summary>
1409  public event EventHandler<Avalonia.Input.PointerPressedEventArgs> PointerPressed;
1410 
1411  /// <summary>
1412  /// Raised when the pointer is released after a <see cref="PointerPressed"/> event.
1413  /// </summary>
1414  public event EventHandler<Avalonia.Input.PointerReleasedEventArgs> PointerReleased;
1415 
1416 
1417  internal void FirePointerEnter(Avalonia.Input.PointerEventArgs e)
1418  {
1419  this.PointerEnter?.Invoke(this, e);
1420  }
1421 
1422  internal void FirePointerLeave(Avalonia.Input.PointerEventArgs e)
1423  {
1424  this.PointerLeave?.Invoke(this, e);
1425  }
1426 
1427  internal void FirePointerPressed(Avalonia.Input.PointerPressedEventArgs e)
1428  {
1429  this.PointerPressed?.Invoke(this, e);
1430  }
1431 
1432  internal void FirePointerReleased(Avalonia.Input.PointerReleasedEventArgs e)
1433  {
1434  this.PointerReleased?.Invoke(this, e);
1435  }
1436 
1437  private RenderAction()
1438  {
1439 
1440  }
1441 
1442  /// <summary>
1443  /// Creates a new <see cref="RenderAction"/> representing a path.
1444  /// </summary>
1445  /// <param name="geometry">The geometry to be rendered.</param>
1446  /// <param name="stroke">The stroke of the path (can be null).</param>
1447  /// <param name="fill">The fill of the path (can be null).</param>
1448  /// <param name="transform">The transform that will be applied to the path.</param>
1449  /// <param name="clippingPath">The clipping path.</param>
1450  /// <param name="tag">A tag to access the <see cref="RenderAction"/>. If this is null this <see cref="RenderAction"/> is not visible in the hit test.</param>
1451  /// <returns>A new <see cref="RenderAction"/> representing a path.</returns>
1452  public static RenderAction PathAction(Geometry geometry, Pen stroke, IBrush fill, Avalonia.Matrix transform, Geometry clippingPath, string tag = null)
1453  {
1454  return new RenderAction()
1455  {
1456  ActionType = ActionTypes.Path,
1457  Geometry = geometry,
1458  Stroke = stroke,
1459  Fill = fill,
1460  Transform = transform,
1461  ClippingPath = clippingPath,
1462  Tag = tag
1463  };
1464  }
1465 
1466  /// <summary>
1467  /// Creates a new <see cref="RenderAction"/> representing text.
1468  /// </summary>
1469  /// <param name="text">The text to be rendered.</param>
1470  /// <param name="fill">The fill of the text (can be null).</param>
1471  /// <param name="transform">The transform that will be applied to the text.</param>
1472  /// <param name="clippingPath">The clipping path.</param>
1473  /// <param name="tag">A tag to access the <see cref="RenderAction"/>. If this is null this <see cref="RenderAction"/> is not visible in the hit test.</param>
1474  /// <returns>A new <see cref="RenderAction"/> representing text.</returns>
1475  public static RenderAction TextAction(Avalonia.Media.FormattedText text, IBrush fill, Avalonia.Matrix transform, Geometry clippingPath, string tag = null)
1476  {
1477  return new RenderAction()
1478  {
1479  ActionType = ActionTypes.Text,
1480  Text = text,
1481  Stroke = null,
1482  Fill = fill,
1483  Transform = transform,
1484  ClippingPath = clippingPath,
1485  Tag = tag
1486  };
1487  }
1488 
1489  /// <summary>
1490  /// Creates a new <see cref="RenderAction"/> representing an image.
1491  /// </summary>
1492  /// <param name="imageId">The univocal identifier of the image to draw.</param>
1493  /// <param name="sourceRect">The source rectangle of the image.</param>
1494  /// <param name="destinationRect">The destination rectangle of the image.</param>
1495  /// <param name="transform">The transform that will be applied to the image.</param>
1496  /// <param name="clippingPath">The clipping path.</param>
1497  /// <param name="tag">A tag to access the <see cref="RenderAction"/>. If this is null this <see cref="RenderAction"/> is not visible in the hit test.</param>
1498  /// <returns>A new <see cref="RenderAction"/> representing an image.</returns>
1499  public static RenderAction ImageAction(string imageId, Avalonia.Rect sourceRect, Avalonia.Rect destinationRect, Avalonia.Matrix transform, Geometry clippingPath, string tag = null)
1500  {
1501  return new RenderAction()
1502  {
1503  ActionType = ActionTypes.RasterImage,
1504  ImageId = imageId,
1505  ImageSource = sourceRect,
1506  ImageDestination = destinationRect,
1507  Transform = transform,
1508  ClippingPath = clippingPath,
1509  Tag = tag
1510  };
1511  }
1512 
1513  /// <summary>
1514  /// Brings the render action to the front of the rendering queue. This method can only be invoked after the output has been fully initialised.
1515  /// </summary>
1516  public void BringToFront()
1517  {
1518  this.InternalParent.BringToFront(this);
1519  }
1520 
1521  /// <summary>
1522  /// Brings the render action to the back of the rendering queue. This method can only be invoked after the output has been fully initialised.
1523  /// </summary>
1524  public void SendToBack()
1525  {
1526  this.InternalParent.SendToBack(this);
1527  }
1528  }
1529 
1530 
1531  internal class AvaloniaDrawingContext : IGraphicsContext
1532  {
1533  public Dictionary<string, Func<RenderAction, IEnumerable<RenderAction>>> TaggedActions { get; set; } = new Dictionary<string, Func<RenderAction, IEnumerable<RenderAction>>>();
1534 
1535  private bool removeTaggedActions = true;
1536 
1537  public string Tag { get; set; }
1538 
1539  AvaloniaContextInterpreter.TextOptions _textOption;
1540 
1541  private Dictionary<string, (IImage, bool)> Images;
1542 
1543  private Geometry _clippingPath;
1544  private Stack<Geometry> clippingPaths;
1545  private FilterOption _filterOption;
1546 
1547  public AvaloniaDrawingContext(double width, double height, bool removeTaggedActionsAfterExecution, AvaloniaContextInterpreter.TextOptions textOption, Dictionary<string, (IImage, bool)> images, FilterOption filterOption)
1548  {
1549  this.Images = images;
1550 
1551  currentPath = new PathGeometry();
1552  currentFigure = new PathFigure() { IsClosed = false };
1553  figureInitialised = false;
1554 
1555  RenderActions = new List<RenderAction>();
1556  removeTaggedActions = removeTaggedActionsAfterExecution;
1557 
1558  Width = width;
1559  Height = height;
1560 
1561  _transform = new double[3, 3];
1562 
1563  _transform[0, 0] = 1;
1564  _transform[1, 1] = 1;
1565  _transform[2, 2] = 1;
1566 
1567  states = new Stack<double[,]>();
1568 
1569  _textOption = textOption;
1570 
1571  _clippingPath = null;
1572  clippingPaths = new Stack<Geometry>();
1573  clippingPaths.Push(_clippingPath);
1574 
1575  _filterOption = filterOption;
1576  }
1577 
1578  public List<RenderAction> RenderActions { get; set; }
1579 
1580  public double Width { get; private set; }
1581  public double Height { get; private set; }
1582 
1583  public void Translate(double x, double y)
1584  {
1585  _transform = MatrixUtils.Translate(_transform, x, y);
1586 
1587  currentPath = new PathGeometry();
1588  currentFigure = new PathFigure() { IsClosed = false };
1589  figureInitialised = false;
1590  }
1591 
1592  public TextBaselines TextBaseline { get; set; }
1593 
1594  private void PathText(string text, double x, double y)
1595  {
1596  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1597 
1598  GraphicsPath textPath = new GraphicsPath().AddText(x, y, text, Font, TextBaseline);
1599 
1600  for (int j = 0; j < textPath.Segments.Count; j++)
1601  {
1602  switch (textPath.Segments[j].Type)
1603  {
1604  case VectSharp.SegmentType.Move:
1605  this.MoveTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
1606  break;
1607  case VectSharp.SegmentType.Line:
1608  this.LineTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
1609  break;
1610  case VectSharp.SegmentType.CubicBezier:
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);
1612  break;
1613  case VectSharp.SegmentType.Close:
1614  this.Close();
1615  break;
1616  }
1617  }
1618  }
1619 
1620  public void StrokeText(string text, double x, double y)
1621  {
1622  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1623 
1624  PathText(text, x, y);
1625  Stroke();
1626  }
1627 
1628  public void FillText(string text, double x, double y)
1629  {
1630  if (!Font.EnableKerning)
1631  {
1632  FillSimpleText(text, x, y);
1633  }
1634  else
1635  {
1636  List<(string, Point)> tSpans = new List<(string, Point)>();
1637 
1638  System.Text.StringBuilder currentRun = new System.Text.StringBuilder();
1639  Point currentKerning = new Point();
1640 
1641  Point currentGlyphPlacementDelta = new Point();
1642  Point currentGlyphAdvanceDelta = new Point();
1643  Point nextGlyphPlacementDelta = new Point();
1644  Point nextGlyphAdvanceDelta = new Point();
1645 
1646  for (int i = 0; i < text.Length; i++)
1647  {
1648  if (i < text.Length - 1)
1649  {
1650  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
1651  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
1652  nextGlyphAdvanceDelta = new Point();
1653  nextGlyphPlacementDelta = new Point();
1654 
1655  TrueTypeFile.PairKerning kerning = Font.FontFamily.TrueTypeFile.Get1000EmKerning(text[i], text[i + 1]);
1656 
1657  if (kerning != null)
1658  {
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);
1661 
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);
1664  }
1665  }
1666 
1667  if (currentGlyphPlacementDelta.X != 0 || currentGlyphPlacementDelta.Y != 0 || currentGlyphAdvanceDelta.X != 0 || currentGlyphAdvanceDelta.Y != 0)
1668  {
1669  if (currentRun.Length > 0)
1670  {
1671  tSpans.Add((currentRun.ToString(), currentKerning));
1672 
1673  tSpans.Add((text[i].ToString(), new Point(currentGlyphPlacementDelta.X * Font.FontSize / 1000, currentGlyphPlacementDelta.Y * Font.FontSize / 1000)));
1674 
1675  currentRun.Clear();
1676  currentKerning = new Point((currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X) * Font.FontSize / 1000, (currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y) * Font.FontSize / 1000);
1677  }
1678  else
1679  {
1680  tSpans.Add((text[i].ToString(), new Point(currentGlyphPlacementDelta.X * Font.FontSize / 1000 + currentKerning.X, currentGlyphPlacementDelta.Y * Font.FontSize / 1000 + currentKerning.Y)));
1681 
1682  currentRun.Clear();
1683  currentKerning = new Point((currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X) * Font.FontSize / 1000, (currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y) * Font.FontSize / 1000);
1684  }
1685  }
1686  else
1687  {
1688  currentRun.Append(text[i]);
1689  }
1690  }
1691 
1692  if (currentRun.Length > 0)
1693  {
1694  tSpans.Add((currentRun.ToString(), currentKerning));
1695  }
1696 
1697  double currX = x;
1698  double currY = y;
1699 
1700  Font.DetailedFontMetrics fullMetrics = Font.MeasureTextAdvanced(text);
1701 
1702  if (TextBaseline == TextBaselines.Top)
1703  {
1704  if (Font.FontFamily.TrueTypeFile != null)
1705  {
1706  currY += fullMetrics.Top;
1707  }
1708  }
1709  else if (TextBaseline == TextBaselines.Middle)
1710  {
1711  if (Font.FontFamily.TrueTypeFile != null)
1712  {
1713  currY += (fullMetrics.Top + fullMetrics.Bottom) * 0.5;
1714  }
1715  }
1716  else if (TextBaseline == TextBaselines.Bottom)
1717  {
1718  if (Font.FontFamily.TrueTypeFile != null)
1719  {
1720  currY += fullMetrics.Bottom;
1721  }
1722  }
1723 
1724  TextBaseline = TextBaselines.Baseline;
1725 
1726  for (int i = 0; i < tSpans.Count; i++)
1727  {
1728  Font.DetailedFontMetrics metrics = Font.MeasureTextAdvanced(tSpans[i].Item1);
1729 
1730  if (i == 0)
1731  {
1732  FillSimpleText(tSpans[i].Item1, currX + tSpans[i].Item2.X, currY + tSpans[i].Item2.Y);
1733  }
1734  else
1735  {
1736  FillSimpleText(tSpans[i].Item1, currX + metrics.LeftSideBearing - fullMetrics.LeftSideBearing + tSpans[i].Item2.X, currY + tSpans[i].Item2.Y);
1737  }
1738 
1739 
1740  currX += metrics.AdvanceWidth + tSpans[i].Item2.X;
1741  currY += tSpans[i].Item2.Y;
1742  }
1743  }
1744  }
1745 
1746  public void FillSimpleText(string text, double x, double y)
1747  {
1748  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1749 
1750  if (_textOption == AvaloniaContextInterpreter.TextOptions.NeverConvert || (_textOption == AvaloniaContextInterpreter.TextOptions.ConvertIfNecessary && Font.FontFamily.IsStandardFamily && Font.FontFamily.FileName != "ZapfDingbats" && Font.FontFamily.FileName != "Symbol"))
1751  {
1752  Avalonia.Media.FormattedText txt = new Avalonia.Media.FormattedText()
1753  {
1754  Text = text,
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
1757  };
1758 
1759 
1760 
1761  double top = y;
1762  double left = x;
1763 
1764  double[,] currTransform = null;
1765  double[,] deltaTransform = MatrixUtils.Identity;
1766 
1767  if (Font.FontFamily.TrueTypeFile != null)
1768  {
1769  currTransform = MatrixUtils.Translate(_transform, x, y);
1770  }
1771 
1772  Font.DetailedFontMetrics metrics = Font.MeasureTextAdvanced(text);
1773 
1774  if (TextBaseline == TextBaselines.Top)
1775  {
1776  if (Font.FontFamily.TrueTypeFile != null)
1777  {
1778  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1779  {
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);
1782  }
1783  else
1784  {
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);
1787  }
1788 
1789  }
1790  }
1791  else if (TextBaseline == TextBaselines.Middle)
1792  {
1793  if (Font.FontFamily.TrueTypeFile != null)
1794  {
1795  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1796  {
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);
1799  }
1800  else
1801  {
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);
1804  }
1805  }
1806  }
1807  else if (TextBaseline == TextBaselines.Baseline)
1808  {
1809  double lsb = Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(text[0]).LeftSideBearing * Font.FontSize / 1000;
1810 
1811  if (Font.FontFamily.TrueTypeFile != null)
1812  {
1813  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1814  {
1815  currTransform = MatrixUtils.Translate(_transform, left - lsb, top - Font.WinAscent);
1816  deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top - Font.YMax);
1817  }
1818  else
1819  {
1820  currTransform = MatrixUtils.Translate(_transform, left - lsb, top - Font.Ascent);
1821  deltaTransform = MatrixUtils.Translate(deltaTransform, left - lsb, top - Font.YMax);
1822  }
1823  }
1824  }
1825  else if (TextBaseline == TextBaselines.Bottom)
1826  {
1827  if (Font.FontFamily.TrueTypeFile != null)
1828  {
1829  if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
1830  {
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);
1833  }
1834  else
1835  {
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);
1838  }
1839  }
1840  }
1841 
1842  Avalonia.Media.Brush fill = null;
1843 
1844  if (this.FillStyle is SolidColourBrush solid)
1845  {
1846  fill = new SolidColorBrush(Color.FromArgb(FillAlpha, (byte)(solid.R * 255), (byte)(solid.G * 255), (byte)(solid.B * 255)));
1847  }
1848  else if (this.FillStyle is LinearGradientBrush linearGradient)
1849  {
1850  fill = linearGradient.ToLinearGradientBrush(deltaTransform);
1851  }
1852  else if (this.FillStyle is RadialGradientBrush radialGradient)
1853  {
1854  fill = radialGradient.ToRadialGradientBrush(metrics.Width + metrics.LeftSideBearing + metrics.RightSideBearing, deltaTransform);
1855  }
1856 
1857  RenderAction act = RenderAction.TextAction(txt, fill, currTransform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
1858 
1859  if (!string.IsNullOrEmpty(Tag))
1860  {
1861  if (TaggedActions.ContainsKey(Tag))
1862  {
1863  IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
1864 
1865  foreach (RenderAction action in actions)
1866  {
1867  RenderActions.Add(action);
1868  }
1869 
1870  if (removeTaggedActions)
1871  {
1872  TaggedActions.Remove(Tag);
1873  }
1874  }
1875  else
1876  {
1877  RenderActions.Add(act);
1878  }
1879  }
1880  else if (TaggedActions.ContainsKey(""))
1881  {
1882  IEnumerable<RenderAction> actions = TaggedActions[""](act);
1883 
1884  foreach (RenderAction action in actions)
1885  {
1886  RenderActions.Add(action);
1887  }
1888  }
1889  else
1890  {
1891  RenderActions.Add(act);
1892  }
1893  }
1894  else
1895  {
1896  PathText(text, x, y);
1897  Fill();
1898  }
1899  }
1900 
1901  public Brush StrokeStyle { get; private set; } = Colour.FromRgb(0, 0, 0);
1902  private byte StrokeAlpha = 255;
1903 
1904  public Brush FillStyle { get; private set; } = Colour.FromRgb(0, 0, 0);
1905  private byte FillAlpha = 255;
1906 
1907  public void SetFillStyle((int r, int g, int b, double a) style)
1908  {
1909  FillStyle = Colour.FromRgba(style.r, style.g, style.b, (int)(style.a * 255));
1910  FillAlpha = (byte)(style.a * 255);
1911  }
1912 
1913  public void SetFillStyle(Brush style)
1914  {
1915  FillStyle = style;
1916 
1917  if (style is SolidColourBrush solid)
1918  {
1919  FillAlpha = (byte)(solid.A * 255);
1920  }
1921  else
1922  {
1923  FillAlpha = 255;
1924  }
1925  }
1926 
1927  public void SetStrokeStyle((int r, int g, int b, double a) style)
1928  {
1929  StrokeStyle = Colour.FromRgba(style.r, style.g, style.b, (int)(style.a * 255));
1930  StrokeAlpha = (byte)(style.a * 255);
1931  }
1932 
1933  public void SetStrokeStyle(Brush style)
1934  {
1935  StrokeStyle = style;
1936 
1937  if (style is SolidColourBrush solid)
1938  {
1939  StrokeAlpha = (byte)(solid.A * 255);
1940  }
1941  else
1942  {
1943  StrokeAlpha = 255;
1944  }
1945  }
1946 
1947  private double[] LineDash;
1948 
1949  public void SetLineDash(LineDash dash)
1950  {
1951  LineDash = new double[] { dash.UnitsOn, dash.UnitsOff, dash.Phase };
1952  }
1953 
1954  public void Rotate(double angle)
1955  {
1956  Utils.CoerceNaNAndInfinityToZero(ref angle);
1957 
1958  _transform = MatrixUtils.Rotate(_transform, angle);
1959 
1960  currentPath = new PathGeometry();
1961  currentFigure = new PathFigure() { IsClosed = false };
1962  figureInitialised = false;
1963  }
1964 
1965  public void Transform(double a, double b, double c, double d, double e, double f)
1966  {
1967  Utils.CoerceNaNAndInfinityToZero(ref a, ref b, ref c, ref d, ref e, ref f);
1968 
1969  double[,] transfMatrix = new double[3, 3] { { a, c, e }, { b, d, f }, { 0, 0, 1 } };
1970  _transform = MatrixUtils.Multiply(_transform, transfMatrix);
1971 
1972  currentPath = new PathGeometry();
1973  currentFigure = new PathFigure() { IsClosed = false };
1974  figureInitialised = false;
1975  }
1976 
1977  public void Scale(double x, double y)
1978  {
1979  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
1980 
1981  _transform = MatrixUtils.Scale(_transform, x, y);
1982 
1983  currentPath = new PathGeometry();
1984  currentFigure = new PathFigure() { IsClosed = false };
1985  figureInitialised = false;
1986  }
1987 
1988  private double[,] _transform;
1989 
1990  private readonly Stack<double[,]> states;
1991 
1992  public void Save()
1993  {
1994  states.Push((double[,])_transform.Clone());
1995  clippingPaths.Push(_clippingPath?.Clone());
1996  }
1997 
1998  public void Restore()
1999  {
2000  _transform = states.Pop();
2001  _clippingPath = clippingPaths.Pop();
2002  }
2003 
2004  public double LineWidth { get; set; }
2005  public LineCaps LineCap { get; set; }
2006  public LineJoins LineJoin { get; set; }
2007 
2008  private string FontFamily;
2009  private Font _Font;
2010 
2011  public Font Font
2012  {
2013  get
2014  {
2015  return _Font;
2016  }
2017 
2018  set
2019  {
2020  _Font = value;
2021 
2022  if (!Font.FontFamily.IsStandardFamily)
2023  {
2024  if (Font.FontFamily is ResourceFontFamily fam)
2025  {
2026  FontFamily = fam.ResourceName;
2027  }
2028  else
2029  {
2030  if (Font.FontFamily.TrueTypeFile != null)
2031  {
2032  FontFamily = Font.FontFamily.TrueTypeFile.GetFontFamilyName();
2033  }
2034  else
2035  {
2036  FontFamily = Font.FontFamily.FileName;
2037  }
2038  }
2039  }
2040  else
2041  {
2042  FontFamily = "resm:VectSharp.StandardFonts.?assembly=VectSharp#" + Font.FontFamily.TrueTypeFile.GetFontFamilyName();
2043  }
2044  }
2045  }
2046 
2047  public (double Width, double Height) MeasureText(string text)
2048  {
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);
2051  }
2052 
2053  private PathGeometry currentPath;
2054  private PathFigure currentFigure;
2055 
2056  private bool figureInitialised = false;
2057 
2058  public void MoveTo(double x, double y)
2059  {
2060  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
2061 
2062  if (figureInitialised)
2063  {
2064  currentPath.Figures.Add(currentFigure);
2065  }
2066 
2067  currentFigure = new PathFigure() { StartPoint = new Avalonia.Point(x, y), IsClosed = false };
2068  figureInitialised = true;
2069  }
2070 
2071  public void LineTo(double x, double y)
2072  {
2073  Utils.CoerceNaNAndInfinityToZero(ref x, ref y);
2074 
2075  if (figureInitialised)
2076  {
2077  currentFigure.Segments.Add(new Avalonia.Media.LineSegment() { Point = new Avalonia.Point(x, y) });
2078  }
2079  else
2080  {
2081  currentFigure = new PathFigure() { StartPoint = new Avalonia.Point(x, y), IsClosed = false };
2082  figureInitialised = true;
2083  }
2084  }
2085 
2086  public void Rectangle(double x0, double y0, double width, double height)
2087  {
2088  Utils.CoerceNaNAndInfinityToZero(ref x0, ref y0, ref width, ref height);
2089 
2090  if (currentFigure != null && figureInitialised)
2091  {
2092  currentPath.Figures.Add(currentFigure);
2093  }
2094 
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;
2100 
2101  currentPath.Figures.Add(currentFigure);
2102  figureInitialised = false;
2103  }
2104 
2105  public void CubicBezierTo(double p1X, double p1Y, double p2X, double p2Y, double p3X, double p3Y)
2106  {
2107  Utils.CoerceNaNAndInfinityToZero(ref p1X, ref p1Y, ref p2X, ref p2Y, ref p3X, ref p3Y);
2108 
2109  if (figureInitialised)
2110  {
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) });
2112  }
2113  else
2114  {
2115  currentFigure = new PathFigure() { StartPoint = new Avalonia.Point(p1X, p1Y), IsClosed = false };
2116  figureInitialised = true;
2117  }
2118  }
2119 
2120  public void Close()
2121  {
2122  currentFigure.IsClosed = true;
2123  currentPath.Figures.Add(currentFigure);
2124  figureInitialised = false;
2125  }
2126 
2127  public void Stroke()
2128  {
2129  if (figureInitialised)
2130  {
2131  currentFigure.IsClosed = false;
2132  currentPath.Figures.Add(currentFigure);
2133  }
2134 
2135  Avalonia.Media.Brush stroke = null;
2136 
2137  if (this.StrokeStyle is SolidColourBrush solid)
2138  {
2139  stroke = new SolidColorBrush(Color.FromArgb(StrokeAlpha, (byte)(solid.R * 255), (byte)(solid.G * 255), (byte)(solid.B * 255)));
2140  }
2141  else if (this.StrokeStyle is LinearGradientBrush linearGradient)
2142  {
2143  stroke = linearGradient.ToLinearGradientBrush();
2144  }
2145  else if (this.StrokeStyle is RadialGradientBrush radialGradient)
2146  {
2147  stroke = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
2148  }
2149 
2150  Pen pen = new Pen(stroke,
2151  LineWidth,
2152  new DashStyle(new double[] { (LineDash[0] + (LineCap == LineCaps.Butt ? 0 : LineWidth)) / LineWidth, (LineDash[1] - (LineCap == LineCaps.Butt ? 0 : LineWidth)) / LineWidth }, LineDash[2] / LineWidth));
2153 
2154  switch (LineCap)
2155  {
2156  case LineCaps.Butt:
2157  pen.LineCap = PenLineCap.Flat;
2158  break;
2159  case LineCaps.Round:
2160  pen.LineCap = PenLineCap.Round;
2161  break;
2162  case LineCaps.Square:
2163  pen.LineCap = PenLineCap.Square;
2164  break;
2165  }
2166 
2167  switch (LineJoin)
2168  {
2169  case LineJoins.Bevel:
2170  pen.LineJoin = PenLineJoin.Bevel;
2171  break;
2172  case LineJoins.Round:
2173  pen.LineJoin = PenLineJoin.Round;
2174  break;
2175  case LineJoins.Miter:
2176  pen.LineJoin = PenLineJoin.Miter;
2177  break;
2178  }
2179 
2180  RenderAction act = RenderAction.PathAction(currentPath, pen, null, _transform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
2181 
2182  if (!string.IsNullOrEmpty(Tag))
2183  {
2184  if (TaggedActions.ContainsKey(Tag))
2185  {
2186  IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
2187 
2188  foreach (RenderAction action in actions)
2189  {
2190  RenderActions.Add(action);
2191  }
2192 
2193  if (removeTaggedActions)
2194  {
2195  TaggedActions.Remove(Tag);
2196  }
2197  }
2198  else
2199  {
2200  RenderActions.Add(act);
2201  }
2202  }
2203  else if (TaggedActions.ContainsKey(""))
2204  {
2205  IEnumerable<RenderAction> actions = TaggedActions[""](act);
2206 
2207  foreach (RenderAction action in actions)
2208  {
2209  RenderActions.Add(action);
2210  }
2211  }
2212  else
2213  {
2214  RenderActions.Add(act);
2215  }
2216 
2217  currentPath = new PathGeometry();
2218  currentFigure = new PathFigure() { IsClosed = false };
2219  figureInitialised = false;
2220  }
2221 
2222  public void Fill()
2223  {
2224  if (figureInitialised)
2225  {
2226  currentPath.Figures.Add(currentFigure);
2227  }
2228 
2229  Avalonia.Media.Brush fill = null;
2230 
2231  if (this.FillStyle is SolidColourBrush solid)
2232  {
2233  fill = new SolidColorBrush(Color.FromArgb(FillAlpha, (byte)(solid.R * 255), (byte)(solid.G * 255), (byte)(solid.B * 255)));
2234  }
2235  else if (this.FillStyle is LinearGradientBrush linearGradient)
2236  {
2237  fill = linearGradient.ToLinearGradientBrush();
2238  }
2239  else if (this.FillStyle is RadialGradientBrush radialGradient)
2240  {
2241  fill = radialGradient.ToRadialGradientBrush(currentPath.Bounds.Width);
2242  }
2243 
2244  RenderAction act = RenderAction.PathAction(currentPath, null, fill, _transform.ToAvaloniaMatrix(), _clippingPath?.Clone(), Tag);
2245 
2246  if (!string.IsNullOrEmpty(Tag))
2247  {
2248  if (TaggedActions.ContainsKey(Tag))
2249  {
2250  IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
2251 
2252  foreach (RenderAction action in actions)
2253  {
2254  RenderActions.Add(action);
2255  }
2256 
2257  if (removeTaggedActions)
2258  {
2259  TaggedActions.Remove(Tag);
2260  }
2261  }
2262  else
2263  {
2264  RenderActions.Add(act);
2265  }
2266  }
2267  else if (TaggedActions.ContainsKey(""))
2268  {
2269  IEnumerable<RenderAction> actions = TaggedActions[""](act);
2270 
2271  foreach (RenderAction action in actions)
2272  {
2273  RenderActions.Add(action);
2274  }
2275  }
2276  else
2277  {
2278  RenderActions.Add(act);
2279  }
2280 
2281  currentPath = new PathGeometry();
2282  currentFigure = new PathFigure() { IsClosed = false };
2283  figureInitialised = false;
2284  }
2285 
2286  private static void TransformGeometry(PathGeometry geo, double[,] transf)
2287  {
2288  for (int i = 0; i < geo.Figures.Count; i++)
2289  {
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]);
2292 
2293  for (int j = 0; j < geo.Figures[i].Segments.Count; j++)
2294  {
2295  if (geo.Figures[i].Segments[j] is LineSegment lS)
2296  {
2297  tP = MatrixUtils.Multiply(transf, new double[] { lS.Point.X, lS.Point.Y });
2298  lS.Point = new Avalonia.Point(tP[0], tP[1]);
2299  }
2300  else if (geo.Figures[i].Segments[j] is BezierSegment bS)
2301  {
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 });
2305 
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]);
2309  }
2310  }
2311  }
2312  }
2313 
2314  public void SetClippingPath()
2315  {
2316  if (figureInitialised)
2317  {
2318  currentPath.Figures.Add(currentFigure);
2319  }
2320 
2321  TransformGeometry(currentPath, _transform);
2322 
2323  if (_clippingPath == null)
2324  {
2325  _clippingPath = currentPath;
2326  }
2327  else
2328  {
2329  //Can't find a better way of transforming an IStreamGeometryImpl into a Geometry...
2330  _clippingPath = (StreamGeometry)Activator.CreateInstance(typeof(StreamGeometry), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new object[] { _clippingPath.PlatformImpl.Intersect(currentPath.PlatformImpl) }, null);
2331  }
2332 
2333  currentPath = new PathGeometry();
2334  currentFigure = new PathFigure() { IsClosed = false };
2335  figureInitialised = false;
2336  }
2337 
2338  public void DrawRasterImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, RasterImage image)
2339  {
2340  Utils.CoerceNaNAndInfinityToZero(ref destinationX, ref destinationY, ref destinationWidth, ref destinationHeight);
2341 
2342  if (!this.Images.ContainsKey(image.Id))
2343  {
2344  Bitmap bmp = new Bitmap(image.PNGStream);
2345  this.Images.Add(image.Id, (bmp, image.Interpolate));
2346  }
2347 
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);
2349 
2350  if (!string.IsNullOrEmpty(Tag))
2351  {
2352  if (TaggedActions.ContainsKey(Tag))
2353  {
2354  IEnumerable<RenderAction> actions = TaggedActions[Tag](act);
2355 
2356  foreach (RenderAction action in actions)
2357  {
2358  RenderActions.Add(action);
2359  }
2360 
2361  if (removeTaggedActions)
2362  {
2363  TaggedActions.Remove(Tag);
2364  }
2365  }
2366  else
2367  {
2368  RenderActions.Add(act);
2369  }
2370  }
2371  else if (TaggedActions.ContainsKey(""))
2372  {
2373  IEnumerable<RenderAction> actions = TaggedActions[""](act);
2374 
2375  foreach (RenderAction action in actions)
2376  {
2377  RenderActions.Add(action);
2378  }
2379  }
2380  else
2381  {
2382  RenderActions.Add(act);
2383  }
2384  }
2385 
2386  public void DrawFilteredGraphics(Graphics graphics, IFilter filter)
2387  {
2388  if (this._filterOption.Operation == FilterOption.FilterOperations.RasteriseAllWithSkia)
2389  {
2390  double scale = this._filterOption.RasterisationResolution;
2391 
2392  Rectangle bounds = graphics.GetBounds();
2393 
2394  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
2395 
2396  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
2397  {
2398  if (!this._filterOption.RasterisationResolutionRelative)
2399  {
2400  scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
2401  }
2402 
2403  RasterImage rasterised = SKRenderContextInterpreter.Rasterise(graphics, bounds, scale, true);
2404  RasterImage filtered = null;
2405 
2406  if (filter is IFilterWithRasterisableParameter filterWithRastParam)
2407  {
2408  filterWithRastParam.RasteriseParameter(SKRenderContextInterpreter.Rasterise, scale);
2409  }
2410 
2411  if (filter is ILocationInvariantFilter locInvFilter)
2412  {
2413  filtered = locInvFilter.Filter(rasterised, scale);
2414  }
2415  else if (filter is IFilterWithLocation filterWithLoc)
2416  {
2417  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
2418  }
2419 
2420  if (filtered != null)
2421  {
2422  rasterised.Dispose();
2423 
2424  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
2425  }
2426  }
2427  }
2428  else if (this._filterOption.Operation == FilterOption.FilterOperations.RasteriseAllWithVectSharp)
2429  {
2430  double scale = this._filterOption.RasterisationResolution;
2431 
2432  Rectangle bounds = graphics.GetBounds();
2433 
2434  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
2435 
2436  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
2437  {
2438  if (!this._filterOption.RasterisationResolutionRelative)
2439  {
2440  scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
2441  }
2442 
2443  if (graphics.TryRasterise(bounds, scale, true, out RasterImage rasterised))
2444  {
2445  RasterImage filtered = null;
2446 
2447  if (filter is IFilterWithRasterisableParameter filterWithRastParam)
2448  {
2449  filterWithRastParam.RasteriseParameter(SKRenderContextInterpreter.Rasterise, scale);
2450  }
2451 
2452  if (filter is ILocationInvariantFilter locInvFilter)
2453  {
2454  filtered = locInvFilter.Filter(rasterised, scale);
2455  }
2456  else if (filter is IFilterWithLocation filterWithLoc)
2457  {
2458  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
2459  }
2460 
2461  if (filtered != null)
2462  {
2463  rasterised.Dispose();
2464 
2465  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
2466  }
2467  }
2468  else
2469  {
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"".");
2474  }
2475  }
2476  }
2477  else if (this._filterOption.Operation == FilterOption.FilterOperations.IgnoreAll)
2478  {
2479  graphics.CopyToIGraphicsContext(this);
2480  }
2481  else
2482  {
2483 
2484  }
2485  }
2486  }
2487 
2488 
2489  /// <summary>
2490  /// Contains methods to render a <see cref="Page"/> to an <see cref="Avalonia.Controls.Canvas"/>.
2491  /// </summary>
2492  public static class AvaloniaContextInterpreter
2493  {
2494  /// <summary>
2495  /// Defines whether text items should be converted into paths when drawing.
2496  /// </summary>
2497  public enum TextOptions
2498  {
2499  /// <summary>
2500  /// Converts all text items into paths.
2501  /// </summary>
2502  AlwaysConvert,
2503 
2504  /// <summary>
2505  /// Converts all text items into paths, with the exception of those that use a standard font.
2506  /// </summary>
2507  ConvertIfNecessary,
2508 
2509  /// <summary>
2510  /// Does not convert any text items into paths.
2511  /// </summary>
2512  NeverConvert
2513  }
2514 
2515  /// <summary>
2516  /// Render a <see cref="Page"/> to an <see cref="Avalonia.Controls.Canvas"/>.
2517  /// </summary>
2518  /// <param name="page">The <see cref="Page"/> to render.</param>
2519  /// <param name="textOption">Defines whether text items should be converted into paths when drawing.</param>
2520  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
2521  /// <returns>An <see cref="Avalonia.Controls.Canvas"/> containing the rendered graphics objects.</returns>
2522  public static Avalonia.Controls.Canvas PaintToCanvas(this Page page, TextOptions textOption = TextOptions.ConvertIfNecessary, FilterOption filterOption = default)
2523  {
2524  if (filterOption == null)
2525  {
2526  filterOption = FilterOption.Default;
2527  }
2528 
2529  AvaloniaContext ctx = new AvaloniaContext(page.Width, page.Height, true, textOption, filterOption);
2530  page.Graphics.CopyToIGraphicsContext(ctx);
2531  ctx.ControlItem.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)));
2532  return ctx.ControlItem;
2533  }
2534 
2535  /// <summary>
2536  /// Render a <see cref="Page"/> to an <see cref="Avalonia.Controls.Canvas"/>.
2537  /// </summary>
2538  /// <param name="page">The <see cref="Page"/> to render.</param>
2539  /// <param name="graphicsAsControls">If this is true, each graphics object (e.g. paths, text...) is rendered as a separate <see cref="Avalonia.Controls.Control"/>. Otherwise, they are directly rendered onto the drawing context (which is faster, but does not allow interactivity).</param>
2540  /// <param name="textOption">Defines whether text items should be converted into paths when drawing.</param>
2541  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
2542  /// <returns>An <see cref="Avalonia.Controls.Canvas"/> containing the rendered graphics objects.</returns>
2543  public static Avalonia.Controls.Canvas PaintToCanvas(this Page page, bool graphicsAsControls, TextOptions textOption = TextOptions.ConvertIfNecessary, FilterOption filterOption = default)
2544  {
2545  if (graphicsAsControls)
2546  {
2547  Avalonia.Controls.Canvas tbr = page.PaintToCanvas(textOption, filterOption);
2548  tbr.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)));
2549  return tbr;
2550  }
2551  else
2552  {
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))) };
2554  }
2555  }
2556 
2557  /// <summary>
2558  /// Render a <see cref="Page"/> to an <see cref="Avalonia.Controls.Canvas"/>.
2559  /// </summary>
2560  /// <param name="page">The <see cref="Page"/> to render.</param>
2561  /// <param name="graphicsAsControls">If this is true, each graphics object (e.g. paths, text...) is rendered as a separate <see cref="Avalonia.Controls.Control"/>. Otherwise, they are directly rendered onto the drawing context (which is faster, but does not allow interactivity).</param>
2562  /// <param name="taggedActions">A <see cref="Dictionary{String, Delegate}"/> containing the <see cref="Action"/>s that will be performed on items with the corresponding tag.
2563  /// If <paramref name="graphicsAsControls"/> is true, the delegates should be voids that accept one parameter of type <see cref="TextBlock"/> or <see cref="Path"/> (depending on the tagged item), otherwise, they should accept one parameter of type <see cref="RenderAction"/> and return an <see cref="IEnumerable{RenderAction}"/> of the actions that will actually be performed.</param>
2564  /// <param name="removeTaggedActionsAfterExecution">Whether the <see cref="Action"/>s should be removed from <paramref name="taggedActions"/> after their execution. Set to false if the same <see cref="Action"/> should be performed on multiple items with the same tag.</param>
2565  /// <param name="textOption">Defines whether text items should be converted into paths when drawing.</param>
2566  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
2567  /// <returns>An <see cref="Avalonia.Controls.Canvas"/> containing the rendered graphics objects.</returns>
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)
2569  {
2570  if (graphicsAsControls)
2571  {
2572  Avalonia.Controls.Canvas tbr = page.PaintToCanvas(taggedActions, removeTaggedActionsAfterExecution, textOption, filterOption);
2573  tbr.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)));
2574  return tbr;
2575  }
2576  else
2577  {
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))) }; ;
2579  }
2580  }
2581 
2582  /// <summary>
2583  /// Render a <see cref="Page"/> to an <see cref="Avalonia.Controls.Canvas"/>.
2584  /// </summary>
2585  /// <param name="page">The <see cref="Page"/> to render.</param>
2586  /// <param name="taggedActions">A <see cref="Dictionary{String, Delegate}"/> containing the <see cref="Action"/>s that will be performed on items with the corresponding tag.
2587  /// The delegates should accept one parameter of type <see cref="TextBlock"/> or <see cref="Path"/> (depending on the tagged item).</param>
2588  /// <param name="removeTaggedActionsAfterExecution">Whether the <see cref="Action"/>s should be removed from <paramref name="taggedActions"/> after their execution. Set to false if the same <see cref="Action"/> should be performed on multiple items with the same tag.</param>
2589  /// <param name="textOption">Defines whether text items should be converted into paths when drawing.</param>
2590  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
2591  /// <returns>An <see cref="Avalonia.Controls.Canvas"/> containing the rendered graphics objects.</returns>
2592  public static Avalonia.Controls.Canvas PaintToCanvas(this Page page, Dictionary<string, Delegate> taggedActions, bool removeTaggedActionsAfterExecution = true, TextOptions textOption = TextOptions.ConvertIfNecessary, FilterOption filterOption = default)
2593  {
2594  if (filterOption == null)
2595  {
2596  filterOption = FilterOption.Default;
2597  }
2598 
2599  AvaloniaContext ctx = new AvaloniaContext(page.Width, page.Height, removeTaggedActionsAfterExecution, textOption, filterOption)
2600  {
2601  TaggedActions = taggedActions
2602  };
2603  page.Graphics.CopyToIGraphicsContext(ctx);
2604  ctx.ControlItem.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)));
2605  return ctx.ControlItem;
2606  }
2607 
2608  internal static Avalonia.Media.LinearGradientBrush ToLinearGradientBrush(this LinearGradientBrush brush, double[,] transformMatrix = null)
2609  {
2610  Point start = brush.StartPoint;
2611  Point end = brush.EndPoint;
2612 
2613  if (transformMatrix != null)
2614  {
2615  double[,] inverse = MatrixUtils.Invert(transformMatrix);
2616 
2617  double[] startVec = MatrixUtils.Multiply(inverse, new double[] { start.X, start.Y });
2618  double[] endVec = MatrixUtils.Multiply(inverse, new double[] { end.X, end.Y });
2619 
2620  start = new Point(startVec[0], startVec[1]);
2621  end = new Point(endVec[0], endVec[1]);
2622  }
2623 
2624  Avalonia.Media.LinearGradientBrush tbr = new Avalonia.Media.LinearGradientBrush()
2625  {
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)
2629  };
2630 
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));
2633 
2634  tbr.GradientStops = stops;
2635 
2636  return tbr;
2637  }
2638 
2639  internal static Avalonia.Media.RadialGradientBrush ToRadialGradientBrush(this RadialGradientBrush brush, double objectWidth, double[,] transformMatrix = null)
2640  {
2641  Point focus = brush.FocalPoint;
2642  Point centre = brush.Centre;
2643 
2644  if (transformMatrix != null)
2645  {
2646  double[,] inverse = MatrixUtils.Invert(transformMatrix);
2647 
2648  double[] focusVec = MatrixUtils.Multiply(inverse, new double[] { focus.X, focus.Y });
2649  double[] centreVec = MatrixUtils.Multiply(inverse, new double[] { centre.X, centre.Y });
2650 
2651  focus = new Point(focusVec[0], focusVec[1]);
2652  centre = new Point(centreVec[0], centreVec[1]);
2653  }
2654 
2655  Avalonia.Media.RadialGradientBrush tbr = new Avalonia.Media.RadialGradientBrush()
2656  {
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
2661  };
2662 
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;
2666 
2667  return tbr;
2668  }
2669  }
2670 }
VectSharp.Canvas.SKRenderContextInterpreter.Rasterise
static RasterImage Rasterise(this Graphics graphics, Rectangle region, double scale, bool interpolate)
Rasterise a region of a Graphics object.
Definition: SKRenderContext.cs:1712
VectSharp.Rectangle
Represents a rectangle.
Definition: Point.cs:173
VectSharp.Canvas.RenderAction.ClippingPath
Geometry ClippingPath
The current clipping path.
Definition: AvaloniaContext.cs:1356
VectSharp.Canvas.FilterOption.RasterisationResolutionRelative
bool RasterisationResolutionRelative
Determines whether the value of RasterisationResolution is absolute (i.e. a size in pixel),...
Definition: SKRenderContext.cs:1510
VectSharp.Canvas
Definition: AvaloniaContext.cs:29
VectSharp.RasterImage.PNGStream
MemoryStream PNGStream
Contains a representation of the image in PNG format. Generated at the first access and cached until ...
Definition: RasterImage.cs:141
VectSharp.Canvas.FilterOption.FilterOperations
FilterOperations
Defines whether image filters should be rasterised or not.
Definition: SKRenderContext.cs:1475
VectSharp.Canvas.RenderAction.PathAction
static RenderAction PathAction(Geometry geometry, Pen stroke, IBrush fill, Avalonia.Matrix transform, Geometry clippingPath, string tag=null)
Creates a new RenderAction representing a path.
Definition: AvaloniaContext.cs:1452
VectSharp.Filters.IFilter.TopLeftMargin
Point TopLeftMargin
Determines how much the area of the filter's subject should be expanded on the top-left to accommodat...
Definition: Filters.cs:30
VectSharp.Canvas.RenderAction.ImageDestination
Avalonia.? Rect ImageDestination
The destination rectangle of the image.
Definition: AvaloniaContext.cs:1351
VectSharp.Canvas.AvaloniaContextInterpreter
Contains methods to render a Page to an Avalonia.Controls.Canvas.
Definition: AvaloniaContext.cs:2493
VectSharp.Canvas.RenderAction.PointerReleased
EventHandler< Avalonia.Input.PointerReleasedEventArgs > PointerReleased
Raised when the pointer is released after a PointerPressed event.
Definition: AvaloniaContext.cs:1414
VectSharp.Canvas.RenderAction.Geometry
Geometry Geometry
Geometry that needs to be rendered (null if the action type is ActionTypes.Text). If you change this,...
Definition: AvaloniaContext.cs:1321
VectSharp.Colour
Represents an RGB colour.
Definition: Colour.cs:26
VectSharp.Canvas.FilterOption.Operation
FilterOperations Operation
Defines whether image filters should be rasterised or not.
Definition: SKRenderContext.cs:1500
VectSharp.LineDash.UnitsOff
double UnitsOff
Length of the "off" (not painted) segment.
Definition: Enums.cs:127
VectSharp.Canvas.RenderAction.Transform
Avalonia.Matrix Transform
Rendering transformation matrix. If you change this, you need to invalidate the Parent's visual.
Definition: AvaloniaContext.cs:1369
VectSharp.Canvas.RenderAction.TextAction
static RenderAction TextAction(Avalonia.Media.FormattedText text, IBrush fill, Avalonia.Matrix transform, Geometry clippingPath, string tag=null)
Creates a new RenderAction representing text.
Definition: AvaloniaContext.cs:1475
VectSharp.Page.Height
double Height
Height of the page.
Definition: Document.cs:57
VectSharp.RasterImage
Represents a raster image, created from raw pixel data. Consider using the derived classes included i...
Definition: RasterImage.cs:99
VectSharp.GraphicsPath
Represents a graphics path that can be filled or stroked.
Definition: GraphicsPath.cs:29
VectSharp.Colour.R
double R
Red component of the colour. Range: [0, 1].
Definition: Colour.cs:30
VectSharp.Canvas.FilterOption.Default
static FilterOption Default
The default options for image filter rasterisation.
Definition: SKRenderContext.cs:1515
VectSharp.Canvas.FilterOption
Determines how and whether image filters are rasterised.
Definition: SKRenderContext.cs:1470
VectSharp.Canvas.RenderAction
Represents a light-weight rendering action.
Definition: AvaloniaContext.cs:1291
VectSharp.Page.Background
Colour Background
Background colour of the page.
Definition: Document.cs:67
VectSharp.Canvas.RenderAction.PointerPressed
EventHandler< Avalonia.Input.PointerPressedEventArgs > PointerPressed
Raised when the pointer is pressed while over the area covered by the RenderAction.
Definition: AvaloniaContext.cs:1409
VectSharp
Definition: Brush.cs:26
VectSharp.Filters.IFilter
Represents a filter. Do not implement this interface directly; instead, implement ILocationInvariantF...
Definition: Filters.cs:26
VectSharp.FontFamily.IsItalic
bool IsItalic
Whether this font is italic or oblique or not. This is set based on the information included in the O...
Definition: Font.cs:596
VectSharp.Graphics.CopyToIGraphicsContext
void CopyToIGraphicsContext(IGraphicsContext destinationContext)
Copy the current graphics to an instance of a class implementing IGraphicsContext.
Definition: Graphics.cs:599
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.Font.WinAscent
double WinAscent
Height above the baseline for a clipping region (Windows ascent). Always >= 0.
Definition: Font.cs:223
VectSharp.Canvas.RenderAction.PointerLeave
EventHandler< Avalonia.Input.PointerEventArgs > PointerLeave
Raised when the pointer leaves the area covered by the RenderAction.
Definition: AvaloniaContext.cs:1404
VectSharp.Page.Width
double Width
Width of the page.
Definition: Document.cs:52
VectSharp.Page
Represents a Graphics object with a width and height.
Definition: Document.cs:48
VectSharp.Canvas.AvaloniaContextInterpreter.PaintToCanvas
static Avalonia.Controls.Canvas PaintToCanvas(this Page page, TextOptions textOption=TextOptions.ConvertIfNecessary, FilterOption filterOption=default)
Render a Page to an Avalonia.Controls.Canvas.
Definition: AvaloniaContext.cs:2522
VectSharp.RasterImage.Width
int Width
The width in pixels of the image.
Definition: RasterImage.cs:123
VectSharp.Font.DetailedFontMetrics.Top
double Top
Height of the tallest glyph in the string over the baseline. Always >= 0.
Definition: Font.cs:126
VectSharp.Brush
Represents a brush used to fill or stroke graphics elements. This could be a solid colour,...
Definition: Brush.cs:31
VectSharp.RadialGradientBrush
Represents a brush painting with a radial gradient.
Definition: Brush.cs:368
VectSharp.GradientBrush.GradientStops
GradientStops GradientStops
The colour stops in the gradient.
Definition: Brush.cs:236
VectSharp.Colour.A
double A
Alpha component of the colour. Range: [0, 1].
Definition: Colour.cs:45
VectSharp.Size.Height
double Height
Height of the object.
Definition: Point.cs:155
VectSharp.SegmentType.Close
@ Close
The segment represents the closing segment of a figure.
VectSharp.LineCaps
LineCaps
Represents line caps.
Definition: Enums.cs:71
VectSharp.FontFamily.IsStandardFamily
bool IsStandardFamily
Whether this is one of the 14 standard font families or not.
Definition: Font.cs:494
VectSharp.Font.YMax
double YMax
Absolute maximum height over the baseline of the glyphs in the font. Always >= 0.
Definition: Font.cs:259
VectSharp.Canvas.RenderAction.ImageSource
Avalonia.? Rect ImageSource
The source rectangle of the image.
Definition: AvaloniaContext.cs:1346
VectSharp.Font
Represents a typeface with a specific size.
Definition: Font.cs:29
VectSharp.TextBaselines
TextBaselines
Represent text baselines.
Definition: Enums.cs:24
VectSharp.RasterImage.Interpolate
bool Interpolate
Determines whether the image should be interpolated when it is resized.
Definition: RasterImage.cs:133
VectSharp.Graphics
Represents an abstract drawing surface.
Definition: Graphics.cs:262
VectSharp.Canvas.AvaloniaContextInterpreter.PaintToCanvas
static Avalonia.Controls.Canvas PaintToCanvas(this Page page, bool graphicsAsControls, Dictionary< string, Delegate > taggedActions, bool removeTaggedActionsAfterExecution=true, TextOptions textOption=TextOptions.ConvertIfNecessary, FilterOption filterOption=default)
Render a Page to an Avalonia.Controls.Canvas.
Definition: AvaloniaContext.cs:2568
VectSharp.Rectangle.Size
Size Size
The size of the rectangle.
Definition: Point.cs:187
VectSharp.Canvas.AvaloniaContextInterpreter.PaintToCanvas
static Avalonia.Controls.Canvas PaintToCanvas(this Page page, Dictionary< string, Delegate > taggedActions, bool removeTaggedActionsAfterExecution=true, TextOptions textOption=TextOptions.ConvertIfNecessary, FilterOption filterOption=default)
Render a Page to an Avalonia.Controls.Canvas.
Definition: AvaloniaContext.cs:2592
VectSharp.Canvas.RenderAction.PointerEnter
EventHandler< Avalonia.Input.PointerEventArgs > PointerEnter
Raised when the pointer enters the area covered by the RenderAction.
Definition: AvaloniaContext.cs:1399
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.LineJoins
LineJoins
Represents line joining options.
Definition: Enums.cs:92
VectSharp.LineDash.UnitsOn
double UnitsOn
Length of the "on" (painted) segment.
Definition: Enums.cs:122
VectSharp.Canvas.SKRenderContextInterpreter
Contains methods to render a Page to an Avalonia.Controls.Canvas using the SkiaSharp renderer.
Definition: SKRenderContext.cs:1535
VectSharp.FontFamily
Represents a typeface.
Definition: Font.cs:421
VectSharp.Graphics.TryRasterise
bool TryRasterise(Rectangle region, double scale, bool interpolate, out RasterImage output)
Tries to rasterise specified region of this Graphics object using the default rasterisation method.
Definition: Graphics.cs:1404
VectSharp.LinearGradientBrush.EndPoint
Point EndPoint
The end point of the gradient. Note that this is relative to the current coordinate system when the g...
Definition: Brush.cs:254
VectSharp.TextAnchors.Center
@ Center
The current coordinate will determine the position of the center of the text string.
VectSharp.Canvas.AvaloniaContextInterpreter.PaintToCanvas
static Avalonia.Controls.Canvas PaintToCanvas(this Page page, bool graphicsAsControls, TextOptions textOption=TextOptions.ConvertIfNecessary, FilterOption filterOption=default)
Render a Page to an Avalonia.Controls.Canvas.
Definition: AvaloniaContext.cs:2543
VectSharp.Canvas.RenderAction.ImageId
string ImageId
Univocal identifier of the image that needs to be drawn.
Definition: AvaloniaContext.cs:1341
VectSharp.Page.Graphics
Graphics Graphics
Graphics surface of the page.
Definition: Document.cs:62
VectSharp.FontFamily.IsOblique
bool IsOblique
Whether this font is oblique or not. This is set based on the information included in the OS/2 table ...
Definition: Font.cs:601
VectSharp.Canvas.RenderAction.ActionTypes
ActionTypes
Types of rendering actions.
Definition: AvaloniaContext.cs:1296
VectSharp.Font.DetailedFontMetrics
Represents detailed information about the metrics of a text string when drawn with a certain font.
Definition: Font.cs:102
VectSharp.Filters.IFilterWithRasterisableParameter
Represents a filter with a parameter that needs to be rasterised at the same resolution as the subjec...
Definition: Filters.cs:72
VectSharp.FontFamily.IsBold
bool IsBold
Whether this font is bold or not. This is set based on the information included in the OS/2 table of ...
Definition: Font.cs:591
VectSharp.LinearGradientBrush.StartPoint
Point StartPoint
The starting point of the gradient. Note that this is relative to the current coordinate system when ...
Definition: Brush.cs:249
VectSharp.TrueTypeFile.Bearings.LeftSideBearing
int LeftSideBearing
The left-side bearing of the glyph.
Definition: TrueType.cs:2190
VectSharp.Colour.B
double B
Blue component of the colour. Range: [0, 1].
Definition: Colour.cs:40
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.RasterImage.Height
int Height
The height in pixels of the image.
Definition: RasterImage.cs:128
VectSharp.Font.Ascent
double Ascent
Maximum height over the baseline of the usual glyphs in the font (there may be glyphs taller than thi...
Definition: Font.cs:205
VectSharp.ResourceFontFamily
Represents a FontFamily created from a resource stream.
Definition: Font.cs:695
VectSharp.Canvas.RenderAction.Stroke
Pen Stroke
Rendering stroke (null if the action type is ActionTypes.Text or if the rendered action only has a Fi...
Definition: AvaloniaContext.cs:1331
VectSharp.Canvas.RenderAction.Tag
string Tag
A tag to access the RenderAction.
Definition: AvaloniaContext.cs:1381
VectSharp.Font.FontFamily
FontFamily FontFamily
Font typeface.
Definition: Font.cs:158
VectSharp.LinearGradientBrush
Represents a brush painting with a linear gradient.
Definition: Brush.cs:245
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Canvas.AvaloniaContextInterpreter.TextOptions
TextOptions
Defines whether text items should be converted into paths when drawing.
Definition: AvaloniaContext.cs:2498
VectSharp.Canvas.RenderAction.BringToFront
void BringToFront()
Brings the render action to the front of the rendering queue. This method can only be invoked after t...
Definition: AvaloniaContext.cs:1516
VectSharp.LineDash
Represents instructions on how to paint a dashed line.
Definition: Enums.cs:113
VectSharp.Rectangle.Location
Point Location
The top-left corner of the rectangle.
Definition: Point.cs:182
VectSharp.Canvas.RenderAction.Fill
IBrush Fill
Rendering fill (null if the rendered action only has a Stroke). If you change this,...
Definition: AvaloniaContext.cs:1336
VectSharp.Canvas.RenderAction.InverseTransform
Avalonia.Matrix InverseTransform
Inverse transformation matrix.
Definition: AvaloniaContext.cs:1363
VectSharp.Size.Width
double Width
Width of the object.
Definition: Point.cs:150
VectSharp.Filters.IFilter.BottomRightMargin
Point BottomRightMargin
Determines how much the area of the filter's subject should be expanded on the bottom-right to accomm...
Definition: Filters.cs:35
VectSharp.Canvas.RenderAction.Text
Avalonia.Media.FormattedText Text
Text that needs to be rendered (null if the action type is ActionTypes.Path). If you change this,...
Definition: AvaloniaContext.cs:1326
VectSharp.Filters.IFilterWithLocation
Represents a filter whose results depend on the position of the subject image on the graphics surface...
Definition: Filters.cs:56
VectSharp.Filters.ILocationInvariantFilter
Represents a filter that can be applied to an image regardless of its location on the graphics surfac...
Definition: Filters.cs:42
VectSharp.FontFamily.FontFamily
FontFamily(string fileName)
Create a new FontFamily.
Definition: Font.cs:608
VectSharp.SolidColourBrush
Represents a brush painting with a single solid colour.
Definition: Brush.cs:55
VectSharp.IGraphicsContext
This interface should be implemented by classes intended to provide graphics output capability to a G...
Definition: Graphics.cs:36
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.Graphics.GetBounds
Rectangle GetBounds()
Computes the rectangular bounds of the region affected by the drawing operations performed on the Gra...
Definition: Graphics.cs:1313
VectSharp.Canvas.RenderAction.SendToBack
void SendToBack()
Brings the render action to the back of the rendering queue. This method can only be invoked after th...
Definition: AvaloniaContext.cs:1524
VectSharp.Canvas.FilterOption.RasterisationResolution
double RasterisationResolution
The resolution that will be used to rasterise image filters. Depending on the value of RasterisationR...
Definition: SKRenderContext.cs:1505
VectSharp.Canvas.RenderAction.Parent
Avalonia.Controls.Canvas Parent
The container of this RenderAction.
Definition: AvaloniaContext.cs:1389
VectSharp.LineDash.Phase
double Phase
Position in the dash pattern at which the line starts.
Definition: Enums.cs:132
VectSharp.Canvas.RenderAction.ImageAction
static RenderAction ImageAction(string imageId, Avalonia.Rect sourceRect, Avalonia.Rect destinationRect, Avalonia.Matrix transform, Geometry clippingPath, string tag=null)
Creates a new RenderAction representing an image.
Definition: AvaloniaContext.cs:1499
VectSharp.Colour.FromRgb
static Colour FromRgb(double r, double g, double b)
Create a new colour from RGB (red, green and blue) values.
Definition: Colour.cs:62
VectSharp.Colour.G
double G
Green component of the colour. Range: [0, 1].
Definition: Colour.cs:35
VectSharp.TrueTypeFile.GetFontFamilyName
string GetFontFamilyName()
Obtains the font family name from the TrueType file.
Definition: TrueType.cs:1846
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.FontFamily.FileName
string FileName
Full path to the TrueType font file for this font family (or, if this is a standard font family,...
Definition: Font.cs:575
VectSharp.GraphicsPath.Segments
List< Segment > Segments
The segments that make up the path.
Definition: GraphicsPath.cs:33
VectSharp.Canvas.RenderAction.ActionType
ActionTypes ActionType
Type of the rendering action.
Definition: AvaloniaContext.cs:1316
VectSharp.Colour.FromRgba
static Colour FromRgba(double r, double g, double b, double a)
Create a new colour from RGBA (red, green, blue and alpha) values.
Definition: Colour.cs:99