From cb49e500e3aeb93aae94582be8a41acce05de757 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 7 Nov 2015 00:07:42 +0300 Subject: [PATCH] Initial skia support code import from https://github.com/kekekeks/perspex-skia-temp --- Perspex.sln | 15 ++ .../Perspex.Skia.Desktop.csproj | 92 ++++++++ .../Properties/AssemblyInfo.cs | 36 +++ src/Skia/Perspex.Skia/BitmapImpl.cs | 62 +++++ src/Skia/Perspex.Skia/DrawingContextImpl.cs | 210 +++++++++++++++++ src/Skia/Perspex.Skia/FormattedTextImpl.cs | 135 +++++++++++ src/Skia/Perspex.Skia/MethodTable.cs | 216 ++++++++++++++++++ src/Skia/Perspex.Skia/NativeBrush.cs | 113 +++++++++ .../NativeDrawingContextSettings.cs | 10 + src/Skia/Perspex.Skia/NativeFormattedText.cs | 23 ++ src/Skia/Perspex.Skia/Perspex.Skia.projitems | 28 +++ src/Skia/Perspex.Skia/Perspex.Skia.shproj | 13 ++ src/Skia/Perspex.Skia/PerspexHandleHolder.cs | 47 ++++ .../Perspex.Skia/PlatformRenderInterface.cs | 69 ++++++ src/Skia/Perspex.Skia/RenderTarget.cs | 30 +++ src/Skia/Perspex.Skia/SkRect.cs | 26 +++ src/Skia/Perspex.Skia/SkiaPlatform.cs | 13 ++ src/Skia/Perspex.Skia/SkiaPoint.cs | 29 +++ src/Skia/Perspex.Skia/StreamGeometryImpl.cs | 135 +++++++++++ src/Skia/Perspex.Skia/TypefaceCache.cs | 52 +++++ 20 files changed, 1354 insertions(+) create mode 100644 src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj create mode 100644 src/Skia/Perspex.Skia.Desktop/Properties/AssemblyInfo.cs create mode 100644 src/Skia/Perspex.Skia/BitmapImpl.cs create mode 100644 src/Skia/Perspex.Skia/DrawingContextImpl.cs create mode 100644 src/Skia/Perspex.Skia/FormattedTextImpl.cs create mode 100644 src/Skia/Perspex.Skia/MethodTable.cs create mode 100644 src/Skia/Perspex.Skia/NativeBrush.cs create mode 100644 src/Skia/Perspex.Skia/NativeDrawingContextSettings.cs create mode 100644 src/Skia/Perspex.Skia/NativeFormattedText.cs create mode 100644 src/Skia/Perspex.Skia/Perspex.Skia.projitems create mode 100644 src/Skia/Perspex.Skia/Perspex.Skia.shproj create mode 100644 src/Skia/Perspex.Skia/PerspexHandleHolder.cs create mode 100644 src/Skia/Perspex.Skia/PlatformRenderInterface.cs create mode 100644 src/Skia/Perspex.Skia/RenderTarget.cs create mode 100644 src/Skia/Perspex.Skia/SkRect.cs create mode 100644 src/Skia/Perspex.Skia/SkiaPlatform.cs create mode 100644 src/Skia/Perspex.Skia/SkiaPoint.cs create mode 100644 src/Skia/Perspex.Skia/StreamGeometryImpl.cs create mode 100644 src/Skia/Perspex.Skia/TypefaceCache.cs 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 entry; + if (!Cache.TryGetValue(name, out entry)) + Cache[name] = entry = new Dictionary(); + IntPtr rv; + if (!entry.TryGetValue(style, out rv)) + { + var bytes = Encoding.ASCII.GetBytes(name); + var buffer = new byte[bytes.Length + 1]; + bytes.CopyTo(buffer, 0); + fixed (void* pname = buffer) + { + entry[style] = rv = MethodTable.Instance.CreateTypeface(pname, (int)style); + } + } + return rv; + } + + [Flags] + enum Style + { + Normal = 0, + Bold = 0x01, + Italic = 0x02, + BoldItalic = 0x03 + }; + + public static IntPtr GetTypeface(string name, FontStyle style, FontWeight weight) + { + Style sstyle = Style.Normal; + if (style != FontStyle.Normal) + sstyle |= Style.Italic; + if(weight>FontWeight.Normal) + sstyle |= Style.Bold; + return GetTypeface(name, sstyle); + } + + } +} \ No newline at end of file