/// <summary> /// Draw multiline text in Skia coordinates /// </summary> /// <param name="canvas">Canvas where text is drawn</param> /// <param name="paint">Paint for drawn</param> /// <param name="x">Start x coordinate</param> /// <param name="y">Start y coordinate</param> /// <param name="maxWidth">Max width</param> /// <param name="lineHeight">Pre measured line height</param> /// <param name="isMultiline">Is text wrapped to multiple lines</param> /// <param name="text">Actual text</param> public static void DrawTextArea(SKCanvas canvas, SKPaint paint, float x, float y, float maxWidth, float lineHeight, bool isMultiline, string text) { if (isMultiline) { var spaceWidth = paint.MeasureText(" "); var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.None); lines = lines.SelectMany(l => SplitLine(paint, maxWidth, l, spaceWidth)).ToArray(); for (int i = 0; i < lines.Length; i++) { var line = lines[i]; canvas.DrawText(line, x, y, paint); y += lineHeight; } } else { long maxCharacters = paint.BreakText(text, maxWidth); string actualString = text; if (maxCharacters + 1 < text.Count()) // +1 quick and dirty for UWP { float dotsWidth = paint.MeasureText("..."); maxCharacters = paint.BreakText(text, maxWidth - dotsWidth); actualString = text.Substring(0, (int)maxCharacters); actualString = actualString.Trim(); actualString += "..."; } canvas.DrawText(actualString, x, y, paint); } }
public void BreakTextSucceedsForNullPointerZeroLength() { var paint = new SKPaint(); paint.TextEncoding = SKTextEncoding.Utf8; Assert.Equal(0, paint.BreakText(IntPtr.Zero, IntPtr.Zero, 50.0f)); Assert.Equal(0, paint.BreakText(IntPtr.Zero, 0, 50.0f)); }
public void BreakTextThrowsForNullPointer() { var paint = new SKPaint(); paint.TextEncoding = SKTextEncoding.Utf8; Assert.Throws <ArgumentNullException>(() => paint.BreakText(IntPtr.Zero, (IntPtr)123, 50.0f)); Assert.Throws <ArgumentNullException>(() => paint.BreakText(IntPtr.Zero, 123, 50.0f)); }
public static IntDimension MeasureText(this Font font, string title, float lineWidth) { using var paint = new SKPaint() { Typeface = font.ToSkia(), TextSize = font.Size, IsAntialias = true, Style = SKPaintStyle.Fill }; var bounds = new SKRect(); paint.MeasureText(title, ref bounds); var width = 0f; var height = 0f; while (true) { var offset = paint.BreakText(title, lineWidth, out var measuredWidth, out _); width = Math.Max(width, measuredWidth); height += bounds.Height; if (offset == 0) { break; } title = title.Substring((int)offset); } return(new IntDimension((int)Math.Ceiling(width), (int)Math.Ceiling(height))); }
private string[] BreakText(string text, float width) { var lines = new List <string>(); var remainder = text; while (remainder.Length > 0) { var lineMax = (int)_skFillPaint.BreakText(remainder, width); var lineMin = lineMax * 3 / 4; var lineBreak = lineMax; if (lineBreak < remainder.Length) { lineBreak = remainder.LastIndexOfAny(BREAK_CHARS, lineMax - 1); if (lineBreak < lineMin) { lineBreak = lineMax; } else { lineBreak++; } } var line = remainder.Substring(0, lineBreak).Trim(); remainder = remainder.Substring(lineBreak).Trim(); lines.Add(line); } return(lines.ToArray()); }
string breakText(string input, SKPaint paint, Brush style) { var restOfText = input; var brokenText = ""; do { var lineLength = paint.BreakText(restOfText, (float)(style.Paint.TextMaxWidth * style.Paint.TextSize)); if (lineLength == restOfText.Length) { // its the end brokenText += restOfText.Trim(); break; } var lastSpaceIndex = restOfText.LastIndexOf(' ', (int)(lineLength - 1)); if (lastSpaceIndex == -1 || lastSpaceIndex == 0) { // no more spaces, probably ;) brokenText += restOfText.Trim(); break; } brokenText += restOfText.Substring(0, (int)lastSpaceIndex).Trim() + "\n"; restOfText = restOfText.Substring((int)lastSpaceIndex, restOfText.Length - (int)lastSpaceIndex); } while (restOfText.Length > 0); return(brokenText.Trim()); }
public int GetTextPosition(string[] textLines, float x, float y, SKPaint paint) { int characterPostion = 0; string line = string.Empty; for (int i = 0; i < textLines.Length; i++) { line = textLines[i]; float yOffset = paint.TextSize * (i + 1); if (y > yOffset) { characterPostion += line.Length; } else { characterPostion += (int)paint.BreakText(line, x); break; } characterPostion++; } return(characterPostion); }
public static void DrawTextLines(this SKCanvas g, string title, Font font, Color brush, IntPoint textOffset, int lineWidth) { using var paint = new SKPaint() { Typeface = font.ToSkia(), TextSize = font.Size, Color = new SKColor(brush.ToUint32()), IsAntialias = true, Style = SKPaintStyle.Fill }; while (true) { var offset = paint.BreakText(title, lineWidth, out _, out var measuredText); g.DrawText(measuredText, new SKPoint(textOffset.X, textOffset.Y + paint.TextSize), paint); textOffset = new IntPoint(textOffset.X, textOffset.Y + (int)paint.TextSize); if (offset == 0) { break; } title = title.Substring((int)offset); } }
public void BreakTextReturnsTheCorrectNumberOfBytes(SKTextEncoding encoding, string text, int extectedRead) { var paint = new SKPaint(); paint.TextEncoding = encoding; // get bytes var bytes = encoding == SKTextEncoding.GlyphId ? GetGlyphBytes(text) : StringUtilities.GetEncodedText(text, encoding); var read = paint.BreakText(bytes, 50.0f, out var measured); Assert.Equal(extectedRead, read); Assert.True(measured > 0); byte[] GetGlyphBytes(string text) { var glyphs = paint.GetGlyphs(text); var bytes = new byte[Buffer.ByteLength(glyphs)]; Buffer.BlockCopy(glyphs, 0, bytes, 0, bytes.Length); return(bytes); } }
public void BreakTextSucceedsForEmtptyString() { var paint = new SKPaint(); paint.TextEncoding = SKTextEncoding.Utf8; Assert.Equal(0, paint.BreakText("", 50.0f)); }
public void BreakTextHandlesLongText(int textSize) { var font = new SKPaint(); if (textSize >= 0) { font.TextSize = textSize; } var text = string.Concat(Enumerable.Repeat('a', 1024)); var width = font.MeasureText(text); var length = font.BreakText(text, width, out var mm); Assert.Equal(1024, length); Assert.Equal(width, mm); }
private List <TextLine> CreateLines(float y, float bottom, float width) { var lines = new List <TextLine>(); var index = 0; var length = _value.Length; while (index < length) { y += _lineHeight; if (_textFlow == TextFlow.ClipBounds && _textAttributes.VerticalAlignment == VerticalAlignment.Top && y > bottom) { return(lines); } var count = (int)_paint.BreakText(_value.Substring(index), width, out var textWidth); var found = false; if (WordWrap && index + count < length) { for (var i = index + count - 1; i >= index && !found; i--) { if (char.IsWhiteSpace(_value[i])) { count = i - index + 1; found = true; } } } var line = _value.Substring(index, count); if (found) { textWidth = _paint.MeasureText(line); } lines.Add(new TextLine(line, textWidth)); index += count; } return(lines); }
private static int LineBreak(string text, SKPaint paint, double width) { int idx = 0, last = 0; var lengthBreak = (int)paint.BreakText(text, (float)width); while (idx < text.Length) { var next = text.IndexOfAny(new char[] { ' ', '\n' }, idx); if (next == -1) { if (idx == 0) { // Word is too long, we will have to break it return(lengthBreak); } else { // Ellipsize if it's the last line if (lengthBreak == text.Length // || text.IndexOfAny (new char [] { ' ', '\n' }, lengthBreak + 1) == -1 ) { return(lengthBreak); } // Split at the last word; return(last); } } if (text[idx] == '\n') { return(idx); } if (next > lengthBreak) { return(idx); } last = next; idx = next + 1; } return(last); }
public void BreatTextHasCorrectLogic(int textSize) { var font = new SKPaint(); if (textSize >= 0) { font.TextSize = textSize; } var text = "sdfkljAKLDFJKEWkldfjlk#$%&sdfs.dsj"; var length = text.Length; var width = font.MeasureText(text); var mm = 0f; var nn = 0L; var step = Math.Max(width / 10f, 1f); for (float w = 0; w <= width; w += step) { var n = font.BreakText(text, w, out var m); Assert.True(n <= length); Assert.True(m <= width); if (n == 0) { Assert.Equal(0, m); } else if (n == nn) { Assert.Equal(mm, m); } else { Assert.True(n > nn); Assert.True(m > mm); } nn = n; mm = m; } }
public void BreakTextWidthIsEqualToMeasureTextWidth(int textSize) { var font = new SKPaint(); if (textSize >= 0) { font.TextSize = textSize; } var text = "The ultimate measure of a man is not where he stands in moments of comfort " + "and convenience, but where he stands at times of challenge and controversy."; var length = text.Length; var width = font.MeasureText(text); var length2 = font.BreakText(text, width, out var mm); Assert.Equal(length, length2); Assert.Equal(width, mm); }
public void BreakTextReturnsTheCorrectNumberOfCharacters() { var paint = new SKPaint(); paint.TextEncoding = SKTextEncoding.Utf8; Assert.Equal(1, paint.BreakText("ä", 50.0f)); Assert.Equal(1, paint.BreakText("a", 50.0f)); paint.TextEncoding = SKTextEncoding.Utf16; Assert.Equal(1, paint.BreakText("ä", 50.0f)); Assert.Equal(1, paint.BreakText("a", 50.0f)); paint.TextEncoding = SKTextEncoding.Utf32; Assert.Equal(1, paint.BreakText("ä", 50.0f)); Assert.Equal(1, paint.BreakText("a", 50.0f)); }
public void BreakTextReturnsTheCorrectNumberOfBytes() { var paint = new SKPaint(); paint.TextEncoding = SKTextEncoding.Utf8; Assert.Equal(2, paint.BreakText(StringUtilities.GetEncodedText("ä", paint.TextEncoding), 50.0f)); Assert.Equal(1, paint.BreakText(StringUtilities.GetEncodedText("a", paint.TextEncoding), 50.0f)); paint.TextEncoding = SKTextEncoding.Utf16; Assert.Equal(2, paint.BreakText(StringUtilities.GetEncodedText("ä", paint.TextEncoding), 50.0f)); Assert.Equal(2, paint.BreakText(StringUtilities.GetEncodedText("a", paint.TextEncoding), 50.0f)); paint.TextEncoding = SKTextEncoding.Utf32; Assert.Equal(4, paint.BreakText(StringUtilities.GetEncodedText("ä", paint.TextEncoding), 50.0f)); Assert.Equal(4, paint.BreakText(StringUtilities.GetEncodedText("a", paint.TextEncoding), 50.0f)); }
public List <TextLine> SplitLines(SKPaint paint) { var result = new List <TextLine>(); var lines = Text.Split(NewLines, StringSplitOptions.None); foreach (var line in lines) { if (_size.X <= 0) { var width = (int)paint.MeasureText(line); result.Add(new TextLine(width, line)); continue; } int restLineLength = line.Length; var restLineText = line; while (restLineLength > 0) { var measuredLength = (int)paint.BreakText(restLineText, _size.X, out float measuredWidth, out string measuredText); restLineLength -= measuredLength; restLineText = restLineText.Substring(measuredLength, restLineLength); if (measuredWidth > RealWidth) { RealWidth = (int)measuredWidth; } result.Add(new TextLine((int)measuredWidth, measuredText)); } } RealHeight = (int)((result.Count * paint.FontSpacing) - paint.FontMetrics.Leading); return(result); }
internal unsafe void Run() { if (!needRun) { return; } lines.Clear(); //设置paint参数 var paint = new SKPaint(); paint.TextEncoding = SKTextEncoding.Utf16; font.ApplyToSKPaint(paint, GraphicsUnit.Pixel, dpi); //注意目标类型为像素 //测量FontMetric SKFontMetrics metrics = font.FontMetrics; //根据文字排版方向确认限宽及限高 float maxWidth = Math.Min(32767, IsVertical ? height : width); //TODO: float maxHeight = IsVertical ? width : height; //TODO:计算高度不满足一行的情况 fixed(char *ptr = Text) { byte *textPtr = (byte *)ptr; byte *startTextPtr = (byte *)ptr; int totalBytes = text.Length * 2; int leftBytes = totalBytes; float y = 0; while (true) { //根据是否允许换行,调用不同的breakText方法 var measuredWidth = 0f; var lineBytes = (int)paint.BreakText(new IntPtr(startTextPtr), new IntPtr(leftBytes), maxWidth, out measuredWidth); //var lineBytes = SkiaApi.sk_paint_break_text_icu(paint, new IntPtr(startTextPtr), leftBytes, maxWidth, null); //TODO:***** fix maxWidth var newLine = new TextLayoutLine(this); newLine.startByteIndex = (int)(startTextPtr - textPtr); newLine.byteLength = lineBytes; newLine.widths = paint.GetGlyphWidths(new IntPtr(startTextPtr), lineBytes); //newLine.startCharIndex = 0; //TODO: //根据对齐方式计算Line的offsetX值 if (stringFormat != null && stringFormat.Alignment != StringAlignment.Near) { //计算当前行Ink总宽度 float lineInkWidth = 0f; for (int i = 0; i < newLine.widths.Length; i++) { lineInkWidth += newLine.widths[i]; } float lineSpace = width - lineInkWidth; if (lineSpace > 0f) { if (stringFormat.Alignment == StringAlignment.Center) { newLine.offsetX = lineSpace / 2f; } else if (stringFormat.Alignment == StringAlignment.Far) { newLine.offsetX = lineSpace; } } } //添加新行 lines.Add(newLine); //判断是否允许换行,不允许直接退出循环 if (IsNoWrap) { break; } //计算下一行高度是否超出限高 y = y - metrics.Ascent + metrics.Descent + metrics.Leading; if ((y - metrics.Ascent + metrics.Descent) > maxHeight) { break; } //偏移剩余部分,并判断是否全部计算完毕 startTextPtr += lineBytes; if ((int)(startTextPtr - textPtr) >= totalBytes) { break; } leftBytes -= lineBytes; } } //根据对齐方式计算offsetY的值 CalcOffsetY(); needRun = false; }
void Rebuild() { var length = _text.Length; _lines.Clear(); _skiaRects = new SKRect[length]; _skiaLines = new List <AvaloniaFormattedTextLine>(); int curOff = 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; // Rendering is relative to baseline LineOffset = -metrics.Top; string subString; byte[] bytes; GCHandle pinnedArray; IntPtr pointer; for (int c = 0; curOff < length; c++) { float lineWidth = -1; int measured; int extraSkip = 0; if (WidthConstraint <= 0) { measured = length; } else { float constraint = WidthConstraint; if (constraint > MAX_LINE_WIDTH) { constraint = MAX_LINE_WIDTH; } subString = _text.Substring(curOff); // TODO: This method is not linking into SkiaSharp so we must use the RAW buffer version for now //measured = (int)Paint.BreakText(subString, constraint, out lineWidth) / 2; bytes = Encoding.UTF8.GetBytes(subString); pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned); pointer = pinnedArray.AddrOfPinnedObject(); // for some reason I have to pass nBytes * 2. I assume under the hood it expects Unicode/WChar?? measured = (int)Paint.BreakText(pointer, (IntPtr)(bytes.Length * 2), constraint, out lineWidth) / 2; pinnedArray.Free(); if (measured == 0) { measured = 1; lineWidth = -1; } char nextChar = ' '; if (curOff + measured < length) { nextChar = _text[curOff + measured]; } if (nextChar != ' ') { // Perform scan for the last space and end the line there for (int si = curOff + measured - 1; si > curOff; si--) { if (_text[si] == ' ') { measured = si - curOff; extraSkip = 1; break; } } } } AvaloniaFormattedTextLine line = new AvaloniaFormattedTextLine(); line.Start = curOff; line.Length = measured; line.Width = lineWidth; line.Height = lineHeight; line.Top = curY; if (line.Width < 0) { line.Width = _skiaRects[line.Start + line.Length - 1].Right; } // Build character rects for (int i = line.Start; i < line.Start + line.Length; i++) { float prevRight = 0; if (i != line.Start) { prevRight = _skiaRects[i - 1].Right; } subString = _text.Substring(line.Start, i - line.Start + 1); float w = Paint.MeasureText(subString); SKRect rc; rc.Left = prevRight; rc.Right = w; rc.Top = line.Top; rc.Bottom = line.Top + line.Height; _skiaRects[i] = rc; } subString = _text.Substring(line.Start, line.Length); line.Width = Paint.MeasureText(subString); _skiaLines.Add(line); curY += lineHeight; // TODO: We may want to consider adding Leading to the vertical line spacing but for now // it appears to make no difference. Revisit as part of FormattedText improvements. // //curY += mLeading; curOff += measured + extraSkip; } // Now convert to Avalonia data formats _lines.Clear(); _rects.Clear(); float maxX = 0; for (var c = 0; c < _skiaLines.Count; c++) { var w = _skiaLines[c].Width; if (maxX < w) { maxX = w; } _lines.Add(new FormattedTextLine(_skiaLines[c].Length, _skiaLines[c].Height)); } for (var c = 0; c < _text.Length; c++) { _rects.Add(_skiaRects[c].ToAvaloniaRect()); } if (_skiaLines.Count == 0) { _size = new Size(); } else { var lastLine = _skiaLines[_skiaLines.Count - 1]; _size = new Size(maxX, lastLine.Top + lastLine.Height); } }
private SkiaHelper CreateSkiaHelper() { return(new SkiaHelper() { Size = new Components.RawSize { Width = this.Width, Height = this.Height }, PostChain = new SkiaHelper.CanvasDelegate((SKCanvas canvas) => { using (var rectPaint = new SKPaint { StrokeWidth = 0, StrokeMiter = 0, StrokeJoin = SKStrokeJoin.Round, StrokeCap = SKStrokeCap.Round, Style = SKPaintStyle.Stroke, Color = SKColor.Parse("666"), TextSize = 32, IsAntialias = true }) using (var bgPaint = new SKPaint { StrokeWidth = 0, StrokeMiter = 0, StrokeJoin = SKStrokeJoin.Round, StrokeCap = SKStrokeCap.Round, Style = SKPaintStyle.Fill, Color = SKColor.Parse("33fff9a3"), TextSize = 32, IsAntialias = true }) using (var textPaint = new SKPaint { Typeface = SKFontManager.Default.MatchCharacter('가'), StrokeWidth = 0, StrokeMiter = 0, StrokeJoin = SKStrokeJoin.Round, StrokeCap = SKStrokeCap.Round, Style = SKPaintStyle.StrokeAndFill, Color = SKColor.Parse("FFFFFFFF"), TextSize = 32, IsAntialias = true }) using (var outlinePaint = new SKPaint { Style = textPaint.Style, Typeface = textPaint.Typeface, TextSize = textPaint.TextSize, StrokeCap = textPaint.StrokeCap, StrokeJoin = textPaint.StrokeJoin, StrokeMiter = textPaint.StrokeMiter, IsAntialias = textPaint.IsAntialias, StrokeWidth = 3, Color = SKColor.Parse("FF4374D9"), }) using (var outline2Paint = new SKPaint { Style = textPaint.Style, Typeface = textPaint.Typeface, TextSize = textPaint.TextSize, StrokeCap = textPaint.StrokeCap, StrokeJoin = textPaint.StrokeJoin, StrokeMiter = textPaint.StrokeMiter, IsAntialias = textPaint.IsAntialias, StrokeWidth = 6, Color = SKColor.Parse("FF5CD1E5") }) using (SKPaint preventPaint = new SKPaint { IsAntialias = true, StrokeWidth = 10, Style = SKPaintStyle.StrokeAndFill, Color = SKColors.Black }) using (SKPath frameTextPath = new SKPath()) { SKHelper.Skia_Canvas.Clear(SKColors.Transparent); float x = 10.0f; float y = 5.0f; float measuredWidth = 0; string str = SKHelper.ContentText; while (str != null && str.Length != 0) { SKRect bounds = new SKRect(); int textLength = (int)textPaint.BreakText(str, Size.Width - 24, out measuredWidth); outline2Paint.MeasureText(str, ref bounds); var b = Encoding.UTF8.GetBytes(str); var s = Encoding.UTF8.GetString(b, 0, textLength); if (bounds.Width > this.Width) { textLength -= Encoding.UTF8.GetByteCount($"{s[s.Length - 1]}"); } s = Encoding.UTF8.GetString(b, textLength, b.Length - textLength); // 띄어쓰기가 짤리면 줄 위로 올린다 for (int i = 0; i < s.Length; i++) { if (s[i] == ' ') { textLength++; } else { break; } } y += (int)bounds.Height + 2; using (SKPath textPath = textPaint.GetTextPath(Encoding.UTF8.GetString(b, 0, textLength), x, y)) using (SKPath outlinePath = new SKPath()) { using (SKPaint tempPaint = new SKPaint { IsAntialias = true, Color = SKColors.Black }) { tempPaint.GetFillPath(textPath, outlinePath); frameTextPath.AddPath(textPath, SKPathAddMode.Append); } } str = Encoding.UTF8.GetString(b, (int)textLength, b.Length - (int)textLength); } SKHelper.Skia_Canvas.DrawRect(new SKRect(0, 0, Size.Width, Size.Height), bgPaint); SKHelper.Skia_Canvas.DrawRect(new SKRect(0, 0, Size.Width, Size.Height), rectPaint); SKHelper.Skia_Canvas.DrawPath(frameTextPath, outline2Paint); SKHelper.Skia_Canvas.DrawPath(frameTextPath, outlinePaint); SKHelper.Skia_Canvas.DrawPath(frameTextPath, textPaint); } }) }); }
private static int LineBreak(string textInput, int textIndex, int stop, SKPaint paint, float maxWidth, out int trailingCount) { int lengthBreak; if (maxWidth == -1) { lengthBreak = stop - textIndex; } else { float measuredWidth; string subText = textInput.Substring(textIndex, stop - textIndex); lengthBreak = (int)paint.BreakText(subText, maxWidth, out measuredWidth); } //Check for white space or line breakers before the lengthBreak int startIndex = textIndex; int index = textIndex; int word_start = textIndex; bool prevBreak = true; trailingCount = 0; while (index < stop) { int prevText = index; char currChar = textInput[index++]; bool currBreak = IsBreakChar(currChar); if (!currBreak && prevBreak) { word_start = prevText; } prevBreak = currBreak; if (index > startIndex + lengthBreak) { if (currBreak) { // eat the rest of the whitespace while (index < stop && IsBreakChar(textInput[index])) { index++; } trailingCount = index - prevText; } else { // backup until a whitespace (or 1 char) if (word_start == startIndex) { if (prevText > startIndex) { index = prevText; } } else { index = word_start; } } break; } if ('\n' == currChar) { int ret = index - startIndex; int lineBreakSize = 1; if (index < stop) { currChar = textInput[index++]; if ('\r' == currChar) { ret = index - startIndex; ++lineBreakSize; } } trailingCount = lineBreakSize; return(ret); } if ('\r' == currChar) { int ret = index - startIndex; int lineBreakSize = 1; if (index < stop) { currChar = textInput[index++]; if ('\n' == currChar) { ret = index - startIndex; ++lineBreakSize; } } trailingCount = lineBreakSize; return(ret); } } return(index - startIndex); }