diff --git a/Perspex.sln b/Perspex.sln
index e3fa298f74..63e3f5e398 100644
--- a/Perspex.sln
+++ b/Perspex.sln
@@ -106,11 +106,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlTestApplicationPcl", "s
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skia", "Skia", "{3743B0F2-CC41-4F14-A8C8-267F579BF91E}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Perspex.Skia", "src\Skia\Perspex.Skia\Perspex.Skia.shproj", "{2F59F3D0-748D-4652-B01E-E0D954756308}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Skia.Desktop", "src\Skia\Perspex.Skia.Desktop\Perspex.Skia.Desktop.csproj", "{925DD807-B651-475F-9F7C-CBEB974CE43D}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
+ src\Skia\Perspex.Skia\Perspex.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4
+ src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
+ src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
@@ -265,6 +274,10 @@ Global
{EA113F1A-D8D7-4142-9948-353270E7EBAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA113F1A-D8D7-4142-9948-353270E7EBAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA113F1A-D8D7-4142-9948-353270E7EBAE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {925DD807-B651-475F-9F7C-CBEB974CE43D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {925DD807-B651-475F-9F7C-CBEB974CE43D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {925DD807-B651-475F-9F7C-CBEB974CE43D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {925DD807-B651-475F-9F7C-CBEB974CE43D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -295,5 +308,7 @@ Global
{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{EA113F1A-D8D7-4142-9948-353270E7EBAE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{3C4C0CB4-0C0F-4450-A37B-148C84FF905F} = {A689DEF5-D50F-4975-8B72-124C9EB54066}
+ {2F59F3D0-748D-4652-B01E-E0D954756308} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
+ {925DD807-B651-475F-9F7C-CBEB974CE43D} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
EndGlobalSection
EndGlobal
diff --git a/src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj b/src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj
new file mode 100644
index 0000000000..1d614bafde
--- /dev/null
+++ b/src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj
@@ -0,0 +1,92 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {925DD807-B651-475F-9F7C-CBEB974CE43D}
+ Library
+ Properties
+ Perspex.Skia.Desktop
+ Perspex.Skia.Desktop
+ v4.5
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {D211E587-D8BC-45B9-95A4-F297C8FA5200}
+ Perspex.Animation
+
+
+ {b09b78d8-9b26-48b0-9149-d64a2f120f3f}
+ Perspex.Base
+
+
+ {d2221c82-4a25-4583-9b43-d791e3f6820c}
+ Perspex.Controls
+
+
+ {62024b2d-53eb-4638-b26b-85eeaa54866e}
+ Perspex.Input
+
+
+ {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}
+ Perspex.Interactivity
+
+
+ {42472427-4774-4c81-8aff-9f27b8e31721}
+ Perspex.Layout
+
+
+ {eb582467-6abb-43a1-b052-e981ba910e3a}
+ Perspex.SceneGraph
+
+
+ {f1baa01a-f176-4c6a-b39d-5b40bb1b148f}
+ Perspex.Styling
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia.Desktop/Properties/AssemblyInfo.cs b/src/Skia/Perspex.Skia.Desktop/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..6076f7be25
--- /dev/null
+++ b/src/Skia/Perspex.Skia.Desktop/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Perspex.Skia.Desktop")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Perspex.Skia.Desktop")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("925dd807-b651-475f-9f7c-cbeb974ce43d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Skia/Perspex.Skia/BitmapImpl.cs b/src/Skia/Perspex.Skia/BitmapImpl.cs
new file mode 100644
index 0000000000..19c6b2c67f
--- /dev/null
+++ b/src/Skia/Perspex.Skia/BitmapImpl.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Perspex.Media;
+using Perspex.Platform;
+
+namespace Perspex.Skia
+{
+ class BitmapImpl : PerspexHandleHolder, IRenderTargetBitmapImpl
+ {
+ private int width;
+ private int height;
+
+ public void Save(string fileName)
+ {
+ var ext = Path.GetExtension(fileName)?.ToLower();
+ var type = MethodTable.SkiaImageType.Png;
+ if(ext=="gif")
+ type = MethodTable.SkiaImageType.Gif;
+ if(ext=="jpeg" || ext =="jpg")
+ type = MethodTable.SkiaImageType.Jpeg;
+ var skdata = MethodTable.Instance.SaveImage(Handle, type, 100);
+ var size = MethodTable.Instance.GetSkDataSize(skdata);
+ var buffer = new byte[size];
+ MethodTable.Instance.ReadSkData(skdata, buffer, size);
+ File.WriteAllBytes(fileName, buffer);
+ }
+
+ public int PixelWidth { get; private set; }
+ public int PixelHeight { get; private set; }
+
+ protected override void Delete(IntPtr handle)
+ {
+ MethodTable.Instance.DisposeImage(handle);
+ }
+
+ public DrawingContext CreateDrawingContext()
+ {
+ return
+ new DrawingContext(
+ new DrawingContextImpl(MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
+ }
+
+ public void Resize(int width, int height)
+ {
+ MethodTable.Instance.ResizeBitmap(Handle, PixelWidth = Math.Max(1, width), PixelHeight = Math.Max(1, height));
+ }
+
+ public BitmapImpl(IntPtr handle, int width, int height) : base(handle)
+ {
+ PixelHeight = height;
+ PixelWidth = width;
+ }
+
+ public BitmapImpl(int width, int height)
+ : this(MethodTable.Instance.CreateRenderTargetBitmap(width, height), width, height)
+ {
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/DrawingContextImpl.cs b/src/Skia/Perspex.Skia/DrawingContextImpl.cs
new file mode 100644
index 0000000000..c8352be734
--- /dev/null
+++ b/src/Skia/Perspex.Skia/DrawingContextImpl.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using Perspex.Media;
+using Perspex.Media.Imaging;
+using Perspex.RenderHelpers;
+
+namespace Perspex.Skia
+{
+ unsafe class DrawingContextImpl : PerspexHandleHolder, IDrawingContextImpl
+ {
+ private readonly NativeDrawingContextSettings* _settings;
+ public DrawingContextImpl(IntPtr handle) : base(handle)
+ {
+ _settings = MethodTable.Instance.GetDrawingContextSettingsPtr(handle);
+ _settings->Opacity = 1;
+ }
+
+ protected override void Delete(IntPtr handle) => MethodTable.Instance.DisposeRenderingContext(handle);
+
+ public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
+ {
+ var impl = (BitmapImpl) source.PlatformImpl;
+ var s = SkRect.FromRect(sourceRect);
+ var d = SkRect.FromRect(destRect);
+ MethodTable.Instance.DrawImage(Handle, impl.Handle, (float) opacity, ref s, ref d);
+ }
+
+ public void DrawLine(Pen pen, Point p1, Point p2)
+ {
+
+ using (var brush = CreateBrush(pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
+ MethodTable.Instance.DrawLine(Handle, brush.Brush,
+ (float) p1.X, (float) p1.Y, (float) p2.X, (float) p2.Y);
+ }
+
+ static readonly NativeBrushContainer _dummy = new NativeBrushContainer(null);
+ public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
+ {
+ var impl = ((StreamGeometryImpl) geometry.PlatformImpl);
+ var oldTransform = Transform;
+ if (!impl.Transform.IsIdentity)
+ Transform = impl.Transform*Transform;
+
+ var size = geometry.Bounds.Size;
+ using(var fill = brush!=null?CreateBrush(brush, size):null)
+ using (var stroke = pen?.Brush != null ? CreateBrush(pen, size) : null)
+ {
+ MethodTable.Instance.DrawGeometry(Handle, impl.Path.Handle, fill != null ? fill.Brush : null,
+ stroke != null ? stroke.Brush : null);
+ }
+ Transform = oldTransform;
+ }
+
+ unsafe NativeBrushContainer CreateBrush(Brush brush, Size targetSize)
+ {
+ var rv = NativeBrushPool.Instance.Get();
+ rv.Brush->Opacity = brush.Opacity;
+
+
+ var solid = brush as SolidColorBrush;
+ if (solid != null)
+ {
+ rv.Brush->Type = NativeBrushType.Solid;
+ rv.Brush->Color = solid.Color.ToUint32();
+ return rv;
+ }
+ var gradient = brush as GradientBrush;
+ if (gradient != null)
+ {
+ if (gradient.GradientStops.Count > NativeBrush.MaxGradientStops)
+ throw new NotSupportedException("Maximum supported gradient stop count is " +
+ NativeBrush.MaxGradientStops);
+ rv.Brush->GradientSpreadMethod = gradient.SpreadMethod;
+ rv.Brush->GradientStopCount = gradient.GradientStops.Count;
+
+ for (var c = 0; c < gradient.GradientStops.Count; c++)
+ {
+ var st = gradient.GradientStops[c];
+ rv.Brush->GradientStops[c] = (float) st.Offset;
+ rv.Brush->GradientStopColors[c] = st.Color.ToUint32();
+ }
+
+ }
+
+ var linearGradient = brush as LinearGradientBrush;
+ if (linearGradient != null)
+ {
+ rv.Brush->Type = NativeBrushType.LinearGradient;
+ rv.Brush->GradientStartPoint = linearGradient.StartPoint.ToPixels(targetSize);
+ rv.Brush->GradientEndPoint = linearGradient.EndPoint.ToPixels(targetSize);
+ }
+ var radialGradient = brush as RadialGradientBrush;
+ if (radialGradient != null)
+ {
+ rv.Brush->Type = NativeBrushType.RadialGradient;
+ rv.Brush->GradientStartPoint = radialGradient.Center.ToPixels(targetSize);
+ rv.Brush->GradientRadius = (float)radialGradient.Radius;
+ }
+ var tileBrush = brush as TileBrush;
+ if (tileBrush != null)
+ {
+ rv.Brush->Type = NativeBrushType.Image;
+ var helper = new TileBrushImplHelper(tileBrush, targetSize);
+ var bitmap = new BitmapImpl((int) helper.IntermediateSize.Width, (int) helper.IntermediateSize.Height);
+ rv.AddDisposable(bitmap);
+ using (var ctx = bitmap.CreateDrawingContext())
+ helper.DrawIntermediate(ctx);
+ rv.Brush->Bitmap = bitmap.Handle;
+ rv.Brush->BitmapTileMode = tileBrush.TileMode;
+ rv.Brush->BitmapTranslation = new SkiaPoint(-helper.DestinationRect.X, -helper.DestinationRect.Y);
+
+
+ }
+
+ return rv;
+ }
+
+ NativeBrushContainer CreateBrush(Pen pen, Size targetSize)
+ {
+ var brush = CreateBrush(pen.Brush, targetSize);
+ brush.Brush->Stroke = true;
+ brush.Brush->StrokeThickness = (float)pen.Thickness;
+ brush.Brush->StrokeLineCap = pen.StartLineCap;
+ brush.Brush->StrokeMiterLimit = (float)pen.MiterLimit;
+
+ if (pen.DashStyle != null)
+ {
+ var dashes = pen.DashStyle.Dashes;
+ if (dashes.Count > NativeBrush.MaxDashCount)
+ throw new NotSupportedException("Maximum supported dash count is " + NativeBrush.MaxDashCount);
+ brush.Brush->StrokeDashCount = dashes.Count;
+ for (int c = 0; c < dashes.Count; c++)
+ brush.Brush->StrokeDashes[c] = (float) dashes[c];
+ brush.Brush->StrokeDashOffset = (float)pen.DashStyle.Offset;
+
+ }
+
+
+ return brush;
+ }
+
+ public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
+ {
+ using (var brush = CreateBrush(pen, rect.Size))
+ {
+ var rc = SkRect.FromRect(rect);
+ MethodTable.Instance.DrawRectangle(Handle, brush.Brush, ref rc, cornerRadius);
+ }
+ }
+
+ public void FillRectangle(Brush pbrush, Rect rect, float cornerRadius = 0)
+ {
+ using (var brush = CreateBrush(pbrush, rect.Size))
+ {
+ var rc = SkRect.FromRect(rect);
+ MethodTable.Instance.DrawRectangle(Handle, brush.Brush, ref rc, cornerRadius);
+ }
+ }
+
+ public void DrawText(Brush foreground, Point origin, FormattedText text)
+ {
+ using (var br = CreateBrush(foreground, text.Measure()))
+ MethodTable.Instance.DrawFormattedText(Handle, br.Brush, ((FormattedTextImpl) text.PlatformImpl).Handle,
+ (float) origin.X, (float) origin.Y);
+ }
+
+
+ public void PushClip(Rect clip)
+ {
+ var rc = SkRect.FromRect(clip);
+ //MethodTable.Instance.PushClip(Handle, ref rc);
+ }
+
+ public void PopClip()
+ {
+ //MethodTable.Instance.PopClip(Handle);
+ }
+
+ private readonly Stack _opacityStack = new Stack();
+
+ public void PushOpacity(double opacity)
+ {
+ _opacityStack.Push(_settings->Opacity);
+ _settings->Opacity *= opacity;
+ }
+
+ public void PopOpacity() => _settings->Opacity = _opacityStack.Pop();
+
+ private Matrix _currentTransform = Matrix.Identity;
+ private float[] _fmatrix = new float[6];
+ public Matrix Transform
+ {
+ get { return _currentTransform; }
+ set
+ {
+ if(_currentTransform == value)
+ return;
+ _currentTransform = value;
+ _fmatrix[0] = (float)value.M11;
+ _fmatrix[1] = (float)value.M21;
+ _fmatrix[2] = (float)value.M31;
+
+ _fmatrix[3] = (float)value.M12;
+ _fmatrix[4] = (float)value.M22;
+ _fmatrix[5] = (float)value.M32;
+ MethodTable.Instance.SetTransform(Handle, _fmatrix);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/FormattedTextImpl.cs b/src/Skia/Perspex.Skia/FormattedTextImpl.cs
new file mode 100644
index 0000000000..b9fd05c17d
--- /dev/null
+++ b/src/Skia/Perspex.Skia/FormattedTextImpl.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using Perspex.Media;
+using Perspex.Platform;
+
+namespace Perspex.Skia
+{
+ unsafe class FormattedTextImpl : PerspexHandleHolder, IFormattedTextImpl
+ {
+ private readonly NativeFormattedText* _shared;
+ private readonly string _text;
+
+ public static FormattedTextImpl Create(string text, string fontFamilyName, double fontSize, FontStyle fontStyle,
+ TextAlignment textAlignment, FontWeight fontWeight)
+ {
+ NativeFormattedText* pShared;
+ fixed (void* ptext = text)
+ {
+ IntPtr handle = MethodTable.Instance.CreateFormattedText(ptext, text.Length,
+ TypefaceCache.GetTypeface(fontFamilyName, fontStyle, fontWeight),
+ (float) fontSize, textAlignment, &pShared);
+ return new FormattedTextImpl(handle, pShared, text);
+ }
+ }
+
+ List _lines = new List();
+ List _rects = new List();
+ Size _size;
+
+ public IEnumerable GetLines()
+ {
+ return _lines;
+ }
+
+ public TextHitTestResult HitTestPoint(Point point)
+ {
+ for (int c = 0; c < _rects.Count; c++)
+ {
+ //TODO: Detect line first
+ var rc = _rects[c];
+ if (rc.Contains(point))
+ {
+ return new TextHitTestResult
+ {
+ IsInside = true,
+ TextPosition = c,
+ IsTrailing = (point.X - rc.X) > rc.Width/2
+ };
+
+ }
+ }
+ return new TextHitTestResult();
+ }
+
+ public Rect HitTestTextPosition(int index)
+ {
+ return _rects[index];
+ }
+
+ public IEnumerable HitTestTextRange(int index, int length)
+ {
+ for (var c = 0; c < length; c++)
+ yield return _rects[c + index];
+ }
+
+ public Size Measure()
+ {
+ return _size;
+ }
+
+ public void SetForegroundBrush(Brush brush, int startIndex, int length)
+ {
+ }
+
+
+ void Reload()
+ {
+ _lines.Clear();
+ _rects.Clear();
+ float maxX = 0;
+ for (var c = 0; c < _shared->LineCount; c++)
+ {
+ var w = _shared->Lines[c].Width;
+ if (maxX < w)
+ maxX = w;
+ _lines.Add(new FormattedTextLine(_shared->Lines[c].Length, _shared->Lines[c].Height));
+ }
+ for (var c = 0; c < _text.Length; c++)
+ _rects.Add(_shared->Bounds[c].ToRect());
+ var lastLine = _shared->Lines[_shared->LineCount - 1];
+ _size = new Size(maxX, lastLine.Top + lastLine.Height);
+ }
+
+ void Rebuild()
+ {
+ MethodTable.Instance.RebuildFormattedText(Handle);
+ Reload();
+ }
+
+ Size _constraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
+
+ public Size Constraint
+ {
+ get { return _constraint; }
+ set
+ {
+ _constraint = value;
+
+ _shared->WidthConstraint = (_constraint.Width != double.PositiveInfinity)
+ ? (float) _constraint.Width
+ : -1;
+ Rebuild();
+ }
+ }
+
+ protected override void Delete(IntPtr handle)
+ {
+ MethodTable.Instance.DestroyFormattedText(handle);
+ }
+
+ public FormattedTextImpl(IntPtr handle, NativeFormattedText* shared, string text) : base(handle)
+ {
+ _shared = shared;
+ _text = text;
+ Reload();
+ }
+
+ public override string ToString()
+ {
+ return _text;
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/MethodTable.cs b/src/Skia/Perspex.Skia/MethodTable.cs
new file mode 100644
index 0000000000..4cbb5c050c
--- /dev/null
+++ b/src/Skia/Perspex.Skia/MethodTable.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Perspex.Media;
+
+// ReSharper disable InconsistentNaming
+
+namespace Perspex.Skia
+{
+ internal unsafe abstract class MethodTable
+ {
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _CreateRenderTarget(IntPtr nativeHandle, int width, int height);
+
+ public _CreateRenderTarget CreateRenderTarget;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _RenderTargetCreateRenderingContext(IntPtr target);
+
+ public _RenderTargetCreateRenderingContext RenderTargetCreateRenderingContext;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _RenderTargetResize(IntPtr target, int width, int height);
+
+ public _RenderTargetResize RenderTargetResize;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DisposeRenderTarget(IntPtr target);
+
+ public _DisposeRenderTarget DisposeRenderTarget;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DisposeRenderingContext(IntPtr ctx);
+
+ public _DisposeRenderingContext DisposeRenderingContext;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DrawRectangle(IntPtr ctx, void* brush, ref SkRect rect, float borderRadius);
+
+ public _DrawRectangle DrawRectangle;
+
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _PushClip(IntPtr ctx, ref SkRect rect);
+
+ public _PushClip PushClip;
+
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _PopClip(IntPtr ctx);
+
+ public _PopClip PopClip;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _SetTransform(IntPtr ctx, float[] matrix6);
+
+ public _SetTransform SetTransform;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DrawLine(IntPtr ctx, void* brush, float x1, float y1, float x2, float y2);
+
+ public _DrawLine DrawLine;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _CreatePath(SkiaGeometryElement[] elements, int count, out SkRect bounds);
+
+ public _CreatePath CreatePath;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DisposePath(IntPtr handle);
+
+ public _DisposePath DisposePath;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DrawGeometry(IntPtr ctx, IntPtr path, void* fill, void* stroke);
+
+ public _DrawGeometry DrawGeometry;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DestroySkData(IntPtr handle);
+
+ public _DestroySkData DestroySkData;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate bool _LoadImage(byte[] data, int len, out IntPtr image, out int width, out int height);
+
+ public _LoadImage LoadImage;
+
+ public enum SkiaImageType
+ {
+ Png,Gif,Jpeg
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _SaveImage(IntPtr image, SkiaImageType type, int quality);
+
+ public _SaveImage SaveImage;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DrawImage(IntPtr ctx, IntPtr image, float opacity, ref SkRect srcRect, ref SkRect destRect);
+
+ public _DrawImage DrawImage;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _CreateRenderTargetBitmap(int width, int height);
+
+ public _CreateRenderTargetBitmap CreateRenderTargetBitmap;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _ResizeBitmap(IntPtr image, int width, int height);
+
+ public _ResizeBitmap ResizeBitmap;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DisposeImage(IntPtr image);
+
+ public _DisposeImage DisposeImage;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate int _GetSkDataSize(IntPtr data);
+
+ public _GetSkDataSize GetSkDataSize;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _ReadSkData(IntPtr data, byte[] buffer, int count);
+
+ public _ReadSkData ReadSkData;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate NativeDrawingContextSettings* _GetDrawingContextSettingsPtr(IntPtr ctx);
+
+ public _GetDrawingContextSettingsPtr GetDrawingContextSettingsPtr;
+
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _CreateTypeface(void* name, int style);
+
+ public _CreateTypeface CreateTypeface;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _CreateFormattedText(void*utf16, int len, IntPtr typeface, float fontSize, TextAlignment align, NativeFormattedText** shared);
+
+ public _CreateFormattedText CreateFormattedText;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _RebuildFormattedText(IntPtr handle);
+
+ public _RebuildFormattedText RebuildFormattedText;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DestroyFormattedText(IntPtr handle);
+
+ public _DestroyFormattedText DestroyFormattedText;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void _DrawFormattedText(IntPtr ctx, void* brush, IntPtr text, float x, float y);
+
+ public _DrawFormattedText DrawFormattedText;
+
+ private static readonly Type[] TableOrder = new Type[]
+ {
+ typeof (_CreateRenderTarget),
+ typeof (_DisposeRenderTarget),
+ typeof (_RenderTargetResize),
+ typeof (_RenderTargetCreateRenderingContext),
+ typeof (_DisposeRenderingContext),
+ typeof (_DrawRectangle),
+ typeof (_PushClip),
+ typeof (_PopClip),
+ typeof (_SetTransform),
+ typeof (_DrawLine),
+ typeof (_CreatePath),
+ typeof (_DisposePath),
+ typeof (_DrawGeometry),
+ typeof (_GetSkDataSize),
+ typeof (_ReadSkData),
+ typeof (_DestroySkData),
+ typeof (_LoadImage),
+ typeof (_SaveImage),
+ typeof (_DrawImage),
+ typeof (_CreateRenderTargetBitmap),
+ typeof (_ResizeBitmap),
+ typeof (_DisposeImage),
+ typeof (_GetDrawingContextSettingsPtr),
+ typeof (_CreateTypeface),
+ typeof (_CreateFormattedText),
+ typeof (_RebuildFormattedText),
+ typeof (_DestroyFormattedText),
+ typeof (_DrawFormattedText)
+ };
+
+
+
+ protected MethodTable(IntPtr methodTable)
+ {
+ var dic = typeof (MethodTable).GetFields().ToDictionary(f => f.FieldType, f => f);
+
+ for (var c = 0; c < TableOrder.Length; c++)
+ {
+ IntPtr pMethod = Marshal.ReadIntPtr(methodTable, IntPtr.Size*c);
+ var t = TableOrder[c];
+ dic[t].SetValue(this, Marshal.GetDelegateForFunctionPointer(pMethod, t));
+ }
+ }
+
+ public static readonly MethodTable Instance = new Win32MethodTable();
+ }
+
+ class Win32MethodTable : MethodTable
+ {
+ [DllImport(@"libperspesk.dll")]
+ private static extern IntPtr GetPerspexMethodTable();
+
+ public Win32MethodTable() : base(GetPerspexMethodTable()) { }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/NativeBrush.cs b/src/Skia/Perspex.Skia/NativeBrush.cs
new file mode 100644
index 0000000000..885425d768
--- /dev/null
+++ b/src/Skia/Perspex.Skia/NativeBrush.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using Perspex.Media;
+
+namespace Perspex.Skia
+{
+ internal enum NativeBrushType
+ {
+ Solid,
+ LinearGradient,
+ RadialGradient,
+ Image
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct NativeBrush
+ {
+ public const int MaxGradientStops = 1024;
+ public const int MaxDashCount = 1024;
+ public NativeBrushType Type;
+ public double Opacity;
+ public uint Color;
+
+ //Strokes
+ public bool Stroke;
+ public float StrokeThickness;
+ public PenLineJoin StrokeLineJoin;
+ public float StrokeMiterLimit;
+ public int StrokeDashCount;
+ public float StrokeDashOffset;
+ public PenLineCap StrokeLineCap;
+
+
+ //Gradients
+ public int GradientStopCount;
+ public GradientSpreadMethod GradientSpreadMethod;
+ public SkiaPoint GradientStartPoint, GradientEndPoint;
+ public float GradientRadius;
+
+ //Image Brush
+ public IntPtr Bitmap;
+ public TileMode BitmapTileMode;
+ public SkiaPoint BitmapTranslation;
+
+ //Blobs
+ public fixed uint GradientStopColors [MaxGradientStops];
+ public fixed float GradientStops [MaxGradientStops];
+ public fixed float StrokeDashes [MaxDashCount];
+
+ public void Reset()
+ {
+ Type = NativeBrushType.Solid;
+ Opacity = 1f;
+ Color = 0;
+ Stroke = false;
+ StrokeThickness = 1;
+ GradientStopCount = 0;
+ StrokeDashCount = 0;
+ StrokeLineCap = PenLineCap.Flat;
+ }
+
+ }
+
+
+ unsafe class NativeBrushContainer : IDisposable
+ {
+ private readonly NativeBrushPool _pool;
+ public NativeBrush* Brush;
+
+ List _disposables = new List();
+
+ public NativeBrushContainer(NativeBrushPool pool)
+ {
+ _pool = pool;
+ Brush = (NativeBrush*) Marshal.AllocHGlobal(Marshal.SizeOf(typeof (NativeBrush))).ToPointer();
+ Brush->Reset();
+ }
+
+ public void AddDisposable(IDisposable disp)
+ {
+ _disposables.Add(disp);
+ }
+
+ public void Dispose()
+ {
+ foreach (var disp in _disposables)
+ disp.Dispose();
+ _disposables.Clear();
+ Brush->Reset();
+ _pool?.Return(this);
+ }
+ }
+
+ class NativeBrushPool
+ {
+ public static NativeBrushPool Instance { get; } = new NativeBrushPool();
+ Stack _pool = new Stack();
+
+ public void Return(NativeBrushContainer c)
+ {
+ _pool.Push(c);
+ }
+
+ public NativeBrushContainer Get()
+ {
+ if (_pool.Count == 0)
+ return new NativeBrushContainer(this);
+ return _pool.Pop();
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/NativeDrawingContextSettings.cs b/src/Skia/Perspex.Skia/NativeDrawingContextSettings.cs
new file mode 100644
index 0000000000..58e212a357
--- /dev/null
+++ b/src/Skia/Perspex.Skia/NativeDrawingContextSettings.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Perspex.Skia
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct NativeDrawingContextSettings
+ {
+ public double Opacity;
+ }
+}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/NativeFormattedText.cs b/src/Skia/Perspex.Skia/NativeFormattedText.cs
new file mode 100644
index 0000000000..8f2fc9788f
--- /dev/null
+++ b/src/Skia/Perspex.Skia/NativeFormattedText.cs
@@ -0,0 +1,23 @@
+using System.Runtime.InteropServices;
+
+namespace Perspex.Skia
+{
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct NativeFormattedText
+ {
+ public float WidthConstraint;
+ public int LineCount;
+ public NativeFormattedTextLine* Lines;
+ public SkRect* Bounds;
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct NativeFormattedTextLine
+ {
+ public float Top;
+ public int Start;
+ public int Length;
+ public float Height;
+ public float Width;
+ };
+}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/Perspex.Skia.projitems b/src/Skia/Perspex.Skia/Perspex.Skia.projitems
new file mode 100644
index 0000000000..a55d17f96c
--- /dev/null
+++ b/src/Skia/Perspex.Skia/Perspex.Skia.projitems
@@ -0,0 +1,28 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 2f59f3d0-748d-4652-b01e-e0d954756308
+
+
+ Perspex.Skia
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/Perspex.Skia.shproj b/src/Skia/Perspex.Skia/Perspex.Skia.shproj
new file mode 100644
index 0000000000..ca43389b76
--- /dev/null
+++ b/src/Skia/Perspex.Skia/Perspex.Skia.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 2f59f3d0-748d-4652-b01e-e0d954756308
+ 14.0
+
+
+
+
+
+
+
+
diff --git a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
new file mode 100644
index 0000000000..20b6ca929f
--- /dev/null
+++ b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
@@ -0,0 +1,47 @@
+using System;
+
+namespace Perspex.Skia
+{
+ abstract class PerspexHandleHolder : IDisposable
+ {
+ private IntPtr _handle;
+
+ public IntPtr Handle
+ {
+ get
+ {
+ CheckDisposed();
+ return _handle;
+ }
+ }
+
+ public bool IsDisposed { get; private set; }
+
+ public void CheckDisposed()
+ {
+ if (IsDisposed)
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+
+ protected PerspexHandleHolder(IntPtr handle)
+ {
+ _handle = handle;
+ }
+
+ protected abstract void Delete(IntPtr handle);
+
+ public void Dispose()
+ {
+ if(IsDisposed)
+ return;
+ IsDisposed = true;
+ Delete(_handle);
+ GC.SuppressFinalize(this);
+ }
+
+ ~PerspexHandleHolder()
+ {
+ Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/PlatformRenderInterface.cs b/src/Skia/Perspex.Skia/PlatformRenderInterface.cs
new file mode 100644
index 0000000000..6e79c1e35c
--- /dev/null
+++ b/src/Skia/Perspex.Skia/PlatformRenderInterface.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Media;
+using Perspex.Platform;
+
+namespace Perspex.Skia
+{
+ public class PlatformRenderInterface : IPlatformRenderInterface
+ {
+ public IBitmapImpl CreateBitmap(int width, int height)
+ {
+ return CreateRenderTargetBitmap(width, height);
+ }
+
+ public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle,
+ TextAlignment textAlignment, FontWeight fontWeight)
+ {
+ return FormattedTextImpl.Create(text, fontFamilyName, fontSize, fontStyle, textAlignment, fontWeight);
+ }
+
+ public IStreamGeometryImpl CreateStreamGeometry()
+ {
+ return new StreamGeometryImpl();
+ }
+
+ IBitmapImpl LoadBitmap(byte[] data)
+ {
+ IntPtr ptr;
+ int width;
+ int height;
+ if (!MethodTable.Instance.LoadImage(data, data.Length, out ptr, out width, out height))
+ throw new ArgumentException("Unable to load bitmap from provided data");
+ return new BitmapImpl(ptr, width, height);
+ }
+
+ public IBitmapImpl LoadBitmap(System.IO.Stream stream)
+ {
+ using (var sr = new BinaryReader(stream))
+ {
+ return LoadBitmap(sr.ReadBytes((int)stream.Length));
+ }
+ }
+
+ public IBitmapImpl LoadBitmap(string fileName)
+ {
+ return LoadBitmap(File.ReadAllBytes(fileName));
+ }
+
+ public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
+ {
+ if (width < 1)
+ throw new ArgumentException("Width can't be less than 1", nameof(width));
+ if (height < 1)
+ throw new ArgumentException("Height can't be less than 1", nameof(height));
+ return new BitmapImpl(width, height);
+ }
+
+ public IRenderTarget CreateRenderer(IPlatformHandle handle, double width, double height)
+ {
+ width = Math.Min(1, width);
+ height = Math.Min(1, height);
+ return new RenderTarget(MethodTable.Instance.CreateRenderTarget(handle.Handle, (int) width, (int) height));
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/RenderTarget.cs b/src/Skia/Perspex.Skia/RenderTarget.cs
new file mode 100644
index 0000000000..6d47bf0405
--- /dev/null
+++ b/src/Skia/Perspex.Skia/RenderTarget.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using Perspex.Media;
+using Perspex.Platform;
+
+namespace Perspex.Skia
+{
+ class RenderTarget : PerspexHandleHolder, IRenderTarget
+ {
+ public RenderTarget(IntPtr handle) : base(handle)
+ {
+ }
+
+ protected override void Delete(IntPtr handle) => MethodTable.Instance.DisposeRenderTarget(handle);
+
+ public DrawingContext CreateDrawingContext()
+ {
+ return
+ new DrawingContext(
+ new DrawingContextImpl(MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
+ }
+
+ public void Resize(int width, int height)
+ {
+ MethodTable.Instance.RenderTargetResize(Handle,Math.Max(width, 1), Math.Max(height, 1));
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/SkRect.cs b/src/Skia/Perspex.Skia/SkRect.cs
new file mode 100644
index 0000000000..0f8c5232b5
--- /dev/null
+++ b/src/Skia/Perspex.Skia/SkRect.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Perspex.Skia
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct SkRect
+ {
+ public float Left, Top, Right, Bottom;
+
+ public static SkRect FromRect(Rect rc)
+ {
+ return new SkRect()
+ {
+ Left = (float) rc.X,
+ Top = (float) rc.Y,
+ Right = (float) rc.Right,
+ Bottom = (float) rc.Bottom
+ };
+ }
+
+ public Rect ToRect() => new Rect(Left, Top, Right - Left, Bottom - Top);
+ }
+}
diff --git a/src/Skia/Perspex.Skia/SkiaPlatform.cs b/src/Skia/Perspex.Skia/SkiaPlatform.cs
new file mode 100644
index 0000000000..08d089e518
--- /dev/null
+++ b/src/Skia/Perspex.Skia/SkiaPlatform.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Perspex.Platform;
+
+namespace Perspex.Skia
+{
+ public static class SkiaPlatform
+ {
+ public static void Initialize()
+ => PerspexLocator.CurrentMutable.Bind().ToConstant(new PlatformRenderInterface());
+ }
+}
diff --git a/src/Skia/Perspex.Skia/SkiaPoint.cs b/src/Skia/Perspex.Skia/SkiaPoint.cs
new file mode 100644
index 0000000000..521dc9ad30
--- /dev/null
+++ b/src/Skia/Perspex.Skia/SkiaPoint.cs
@@ -0,0 +1,29 @@
+using System.Runtime.InteropServices;
+
+namespace Perspex.Skia
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct SkiaPoint
+ {
+ public float X, Y;
+
+ public SkiaPoint(float x, float y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public SkiaPoint(double x, double y)
+ {
+ X = (float)x;
+ Y = (float)y;
+ }
+
+ public SkiaPoint(Point p) : this(p.X, p.Y)
+ {
+
+ }
+
+ public static implicit operator SkiaPoint(Point pt) => new SkiaPoint(pt);
+ }
+}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
new file mode 100644
index 0000000000..cd9ccb0cb8
--- /dev/null
+++ b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using Perspex.Media;
+using Perspex.Platform;
+
+namespace Perspex.Skia
+{
+ enum SkiaGeometryElementType
+ {
+ LineTo,
+ ArcTo,
+ BezierTo,
+ BeginFigure,
+ EndFigure
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct SkiaGeometryElement
+ {
+ public SkiaGeometryElementType Type;
+ public SkiaPoint Point1, Point2, Point3;
+ public bool Flag;
+ }
+
+ class SkPath : PerspexHandleHolder
+ {
+ public SkPath(IntPtr handle) : base(handle)
+ {
+ }
+
+ protected override void Delete(IntPtr handle)
+ {
+ MethodTable.Instance.DisposePath(handle);
+ }
+ }
+
+
+ class StreamGeometryImpl : IStreamGeometryImpl
+ {
+ public SkPath Path;
+
+ public Rect GetRenderBounds(double strokeThickness)
+ {
+ // TODO: Calculate properly.
+ return Bounds.Inflate(strokeThickness);
+ }
+
+ public Rect Bounds { get; private set; }
+
+ public Matrix Transform { get; set; } = Matrix.Identity;
+
+ public IStreamGeometryImpl Clone()
+ {
+ return new StreamGeometryImpl() {Path = Path, Transform = Transform, Bounds = Bounds};
+ }
+
+ public IStreamGeometryContextImpl Open()
+ {
+ return new StreamContext(this);
+ }
+
+ class StreamContext : IStreamGeometryContextImpl
+ {
+ private readonly StreamGeometryImpl _geometryImpl;
+ readonly List _elements = new List();
+
+ public StreamContext(StreamGeometryImpl geometryImpl)
+ {
+ _geometryImpl = geometryImpl;
+ }
+
+ public void Dispose()
+ {
+ var arr = _elements.ToArray();
+ SkRect rc;
+ _geometryImpl.Path = new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc));
+ _geometryImpl.Bounds = rc.ToRect();
+
+ }
+
+ public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
+ {
+ //TODO: Implement me
+
+ }
+
+ public void BeginFigure(Point startPoint, bool isFilled)
+ {
+ _elements.Add(new SkiaGeometryElement
+ {
+ Type = SkiaGeometryElementType.BeginFigure,
+ Point1 = new SkiaPoint(startPoint),
+ Flag = isFilled
+ });
+ }
+
+ public void BezierTo(Point point1, Point point2, Point point3)
+ {
+ _elements.Add(new SkiaGeometryElement
+ {
+ Type = SkiaGeometryElementType.BezierTo,
+ Point1 = new SkiaPoint(point1),
+ Point2 = new SkiaPoint(point2),
+ Point3 = new SkiaPoint(point3)
+ });
+ }
+
+ public void QuadTo(Point control, Point endPoint)
+ {
+ //throw new NotImplementedException();
+ }
+
+ public void LineTo(Point point)
+ {
+ _elements.Add(new SkiaGeometryElement
+ {
+ Type = SkiaGeometryElementType.LineTo,
+ Point1 = new SkiaPoint(point)
+ });
+ }
+
+ public void EndFigure(bool isClosed)
+ {
+ _elements.Add(new SkiaGeometryElement
+ {
+ Type = SkiaGeometryElementType.EndFigure,
+ Flag = isClosed
+ });
+ }
+ }
+ }
+}
diff --git a/src/Skia/Perspex.Skia/TypefaceCache.cs b/src/Skia/Perspex.Skia/TypefaceCache.cs
new file mode 100644
index 0000000000..97cb060758
--- /dev/null
+++ b/src/Skia/Perspex.Skia/TypefaceCache.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Perspex.Media;
+
+namespace Perspex.Skia
+{
+ static class TypefaceCache
+ {
+ static readonly Dictionary>Cache = new Dictionary>();
+ unsafe static IntPtr GetTypeface(string name, Style style)
+ {
+ if (name == null)
+ name = "Arial";
+ Dictionary