From 2acc80df3349b0842e6357a6c1758daacdfa5d7d Mon Sep 17 00:00:00 2001 From: Jason Jarvis Date: Tue, 12 Apr 2016 22:28:16 -0700 Subject: [PATCH] Fixed issues with SkiaSharp text layout and rendering. --- src/Skia/Perspex.Skia/FormattedTextImpl.cs | 101 +++++++----------- src/Skia/Perspex.Skia/SkiaSharpExtensions.cs | 14 ++- src/iOS/Perspex.iOS/PerspexView.cs | 10 +- .../Perspex.iOSTestApplication/AppDelegate.cs | 25 +++-- .../Perspex.iOSTestApplication.csproj | 2 +- 5 files changed, 81 insertions(+), 71 deletions(-) diff --git a/src/Skia/Perspex.Skia/FormattedTextImpl.cs b/src/Skia/Perspex.Skia/FormattedTextImpl.cs index 87ab9307e9..08a688b7ea 100644 --- a/src/Skia/Perspex.Skia/FormattedTextImpl.cs +++ b/src/Skia/Perspex.Skia/FormattedTextImpl.cs @@ -12,29 +12,18 @@ namespace Perspex.Skia { public SKPaint Paint { get; private set; } - //private readonly NativeFormattedText* _shared; - private readonly string _text; - public FormattedTextImpl(string text) { _text = text; Paint = new SKPaint(); - //Length = length; Paint.TextEncoding = SKTextEncoding.Utf16; Paint.IsStroke = false; Paint.IsAntialias = true; LineOffset = 0; - //Shared.WidthConstraint = -1.0; - //Data = new pchar[length + 1]; - //memcpy(Data, data, length * 2); - //Data[length] = 0; - //Replace 0 characters with zero-width spaces (200B) _text = _text.Replace((char)0, (char) 0x200B); - - //Rebuild(); } public static FormattedTextImpl Create(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, @@ -45,23 +34,13 @@ namespace Perspex.Skia FormattedTextImpl instance = new FormattedTextImpl(text); instance.Paint.Typeface = typeface; instance.Paint.TextSize = (float) fontSize; - - //rv->Paint.setTextAlign(align); //TODO: Manually align + instance.Paint.TextAlign = textAlignment.ToSKTextAlign(); instance.Rebuild(); - - //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); - //} - return instance; } + private readonly string _text; + readonly List _lines = new List(); readonly List _rects = new List(); @@ -129,8 +108,16 @@ namespace Perspex.Skia public void SetForegroundBrush(IBrush brush, int startIndex, int length) { + // Is this method even relevant now??? + // + //var scb = brush as SolidColorBrush; + //if (scb != null) + //{ + // Layout.Attributes.Insert(brushAttr); + //} } + void Rebuild() { var length = _text.Length; @@ -141,12 +128,18 @@ namespace Perspex.Skia _skiaLines = new List(); int curOff = 0; - int curY = 0; + float curY = 0; + + var metrics = Paint.FontMetrics; + var mTop = metrics.Top; // The greatest distance above the baseline for any glyph (will be <= 0). + var mBottom = metrics.Bottom; // The greatest distance below the baseline for any glyph (will be >= 0). + var mLeading = metrics.Leading; // The recommended distance to add between lines of text (will be >= 0). + + // This seems like the best measure of full vertical extent + float lineHeight = mBottom - mTop; - // TODO: cannot find Font Metrics in SkiaSharp! - //SKPaint.FontMetrics metrics; - float lineHeight = Paint.TextSize; // Paint.getFontMetrics(&metrics); - LineOffset = 0; // -metrics.fTop; + // Rendering is relative to baseline + LineOffset = -metrics.Top; string subString; @@ -171,18 +164,19 @@ namespace Perspex.Skia subString = _text.Substring(curOff); - // This method is not linking into SkiaSharp so we must use the RAW buffer version + // TODO: This method is not linking into SkiaSharp so we must use the RAW buffer version //measured = (int)Paint.BreakText(subString, constraint, out lineWidth) / 2; - bytes = Encoding.ASCII.GetBytes(subString); + bytes = Encoding.UTF8.GetBytes(subString); pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned); pointer = pinnedArray.AddrOfPinnedObject(); - measured = (int)Paint.BreakText(pointer, (IntPtr)bytes.Length, constraint, out lineWidth); + // for some reason I have to pass nBytes * 2 + measured = (int)Paint.BreakText(pointer, (IntPtr)(bytes.Length * 2), constraint, out lineWidth) / 2; // some weird unicode byte issue again - if(subString.Length % 2 == 1) - { - measured -= 1; - } + //if(subString.Length % 2 == 1) + //{ + // measured -= 1; + //} pinnedArray.Free(); @@ -232,15 +226,7 @@ namespace Perspex.Skia prevRight = _skiaRects[i - 1].Right; subString = _text.Substring(line.Start, i - line.Start + 1); - - // Unfortunately this version appears to be incorrect and skipping - // even characters. Some issue with Unicode? - //float w = Paint.MeasureText(subString); - bytes = Encoding.ASCII.GetBytes(subString); - pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned); - pointer = pinnedArray.AddrOfPinnedObject(); - float w = Paint.MeasureText(pointer, (IntPtr)bytes.Length); - pinnedArray.Free(); + float w = Paint.MeasureText(subString); SKRect rc; rc.Left = prevRight; @@ -251,25 +237,14 @@ namespace Perspex.Skia } subString = _text.Substring(line.Start, line.Length); - - // Unfortunately this version appears to be incorrect and skipping - // even characters. Some issue with Unicode? - //line.Width = Paint.MeasureText(subString); - bytes = Encoding.ASCII.GetBytes(subString); - pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned); - pointer = pinnedArray.AddrOfPinnedObject(); - line.Width = Paint.MeasureText(pointer, (IntPtr)bytes.Length); - pinnedArray.Free(); + line.Width = Paint.MeasureText(subString); _skiaLines.Add(line); - curY += (int)lineHeight; + curY += lineHeight; // + mLeading; curOff += measured + extraSkip; } - //Shared.Lines = Lines.data(); - //Shared.CharRects = Rects.data(); - // Now convert to Perspex data formats _lines.Clear(); _rects.Clear(); @@ -296,12 +271,11 @@ namespace Perspex.Skia } } - internal void Draw(SKCanvas canvas, /*PerspexBrush* foreground, */ SKPoint origin) + internal void Draw(SKCanvas canvas, SKPoint origin) { SKPaint paint = Paint; - //ConfigurePaint(paint, ctx, foreground); - /* + /* This originated from Native code, it might be useful //Debugging code for character positions SkPaint dpaint; ctx->Canvas->save(); @@ -325,11 +299,14 @@ namespace Perspex.Skia ctx->Canvas->restore(); */ + // These seems to vertically align the text properly + var yOffset = (LineOffset); // + 1) / 2; + for (int c = 0; c < _skiaLines.Count; c++) { PerspexFormattedTextLine line = _skiaLines[c]; var subString = _text.Substring(line.Start, line.Length); - canvas.DrawText(subString, origin.X, origin.Y + line.Top + LineOffset, paint); + canvas.DrawText(subString, origin.X, origin.Y + line.Top + yOffset, paint); } } diff --git a/src/Skia/Perspex.Skia/SkiaSharpExtensions.cs b/src/Skia/Perspex.Skia/SkiaSharpExtensions.cs index 35084be5c7..2f20eb3012 100644 --- a/src/Skia/Perspex.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Perspex.Skia/SkiaSharpExtensions.cs @@ -1,4 +1,5 @@ -using SkiaSharp; +using Perspex.Media; +using SkiaSharp; namespace Perspex.Skia @@ -53,5 +54,16 @@ namespace Perspex.Skia case Media.GradientSpreadMethod.Repeat: return SKShaderTileMode.Repeat; } } + + public static SKTextAlign ToSKTextAlign(this TextAlignment a) + { + switch (a) + { + default: + case TextAlignment.Left: return SKTextAlign.Left; + case TextAlignment.Center: return SKTextAlign.Center; + case TextAlignment.Right: return SKTextAlign.Right; + } + } } } diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index ef54a0ebc5..38314e0f82 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -75,7 +75,15 @@ namespace Perspex.iOS public IPlatformHandle Handle => PerspexPlatformHandle; - public double Scaling => 1; + public double Scaling + { + get + { + // This does not appear to make any difference, but on iOS we + // have Retina (x2) and we probably want this eventually + return 1; //UIScreen.MainScreen.Scale; + } + } public WindowState WindowState { diff --git a/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs b/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs index 1148796f3a..0a1b353fb1 100644 --- a/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs +++ b/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs @@ -33,7 +33,8 @@ namespace Perspex.iOSTestApplication MainWindow.RootNamespace = "Perspex.iOSTestApplication"; var window = MainWindow.Create(); - window.Show(); + //var window = Create(); + window.Show(); app.Run(window); return true; @@ -48,18 +49,30 @@ namespace Perspex.iOSTestApplication //Width = 900, //Height = 480, Background = Brushes.Red, - Content = new Grid + Content = new StackPanel { - Margin = new Thickness(100), + Margin = new Thickness(30), Background = Brushes.Yellow, Children = new Controls.Controls { + new TextBlock + { + Text = "TEXT BLOCK", + Width = 300, + Height = 40, + Background = Brushes.White, + Foreground = Brushes.Black + }, + new Button { - Content = "Hello World!", - Width = 200, - Height = 200 + Content = "BUTTON", + Width = 150, + Height = 40, + Background = Brushes.LightGreen, + Foreground = Brushes.Black } + } } }; diff --git a/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj b/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj index e7535d2212..412712bc28 100644 --- a/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj +++ b/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj @@ -47,7 +47,7 @@ iPhone Developer True 9.1 - SdkOnly + None False False False