diff --git a/src/Consolonia.Core/Drawing/ConsoloniaRenderInterface.cs b/src/Consolonia.Core/Drawing/ConsoloniaRenderInterface.cs index 4a7f7c38..f77ce63b 100644 --- a/src/Consolonia.Core/Drawing/ConsoloniaRenderInterface.cs +++ b/src/Consolonia.Core/Drawing/ConsoloniaRenderInterface.cs @@ -50,34 +50,35 @@ public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, IGe if (g1 is not StreamGeometryImpl stream1 || g2 is not StreamGeometryImpl stream2) throw new ArgumentException("Only StreamGeometryImpl is supported"); - var newGeometry = CreateStreamGeometry(); - using (var ctx = newGeometry.Open()) + IStreamGeometryImpl newGeometry = CreateStreamGeometry(); + using (IStreamGeometryContextImpl ctx = newGeometry.Open()) { // Resharper disable UnusedVariable - var hasLeftStroke = stream2.Bounds.X.IsNearlyEqual(1); - var hasTopStroke = stream2.Bounds.Y.IsNearlyEqual(1); - var hasRightStroke = (stream1.Bounds.Width - stream2.Bounds.Width).IsNearlyEqual(stream2.Bounds.X + 1); - var hasBottomStroke = (stream1.Bounds.Height - stream2.Bounds.Height).IsNearlyEqual(stream2.Bounds.Y + 1); - var topLeft = stream1.Bounds.TopLeft; - var topRight = stream1.Bounds.TopRight; - var bottomLeft = stream1.Bounds.BottomLeft; - var bottomRight = stream1.Bounds.BottomRight; - var topStroke = stream1.Strokes[0]; - var rightStroke = stream1.Strokes[1]; - var bottomStroke = stream1.Strokes[2]; - var leftStroke = stream1.Strokes[3]; + bool hasLeftStroke = stream2.Bounds.X.IsNearlyEqual(1); + bool hasTopStroke = stream2.Bounds.Y.IsNearlyEqual(1); + bool hasRightStroke = (stream1.Bounds.Width - stream2.Bounds.Width).IsNearlyEqual(stream2.Bounds.X + 1); + bool hasBottomStroke = + (stream1.Bounds.Height - stream2.Bounds.Height).IsNearlyEqual(stream2.Bounds.Y + 1); + Point topLeft = stream1.Bounds.TopLeft; + Point topRight = stream1.Bounds.TopRight; + Point bottomLeft = stream1.Bounds.BottomLeft; + Point bottomRight = stream1.Bounds.BottomRight; + Line topStroke = stream1.Strokes[0]; + Line rightStroke = stream1.Strokes[1]; + Line bottomStroke = stream1.Strokes[2]; + Line leftStroke = stream1.Strokes[3]; // Resharper enable UnusedVariable // add "null" strokes to establish boundries of box even when there is a single real stroke. AddStroke(ctx, topLeft, topLeft); AddStroke(ctx, bottomRight, bottomRight); - + if (hasTopStroke) AddStroke(ctx, topStroke.PStart, topStroke.PEnd + new Vector(-1, 0)); if (hasRightStroke) AddStroke(ctx, rightStroke.PStart + new Vector(-1, 0), rightStroke.PEnd + new Vector(-1, -1)); if (hasBottomStroke) - AddStroke(ctx, bottomStroke.PStart + new Vector(0,-1), bottomStroke.PEnd + new Vector(-1, -1)); + AddStroke(ctx, bottomStroke.PStart + new Vector(0, -1), bottomStroke.PEnd + new Vector(-1, -1)); if (hasLeftStroke) AddStroke(ctx, leftStroke.PStart, leftStroke.PEnd + new Vector(0, -1)); } diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 7ecf6c3f..735bae47 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -28,6 +28,29 @@ internal class DrawingContextImpl : IDrawingContextImpl public const int UnderlineThickness = 10; public const int StrikethroughThickness = 11; + private const int TopLeft = 0; + private const int TopRight = 1; + private const int BottomRight = 2; + private const int BottomLeft = 3; + + private static readonly char[][] EdgeChars = + [ + // LineStyle=Edge + ['▕', '▁', '▏', '▔'], + // LineStyle=EdgeWide + ['▐', '▄', '▌', '▀'] + ]; + + + // top left, top right, bottom right, bottom left, + private static readonly char[][] CornerChars = + { + // LineStyle=Edge we don't draw chars for edge corners + [' ', ' ', ' ', ' '], + // LineStyle=EdgeWide + ['▗', '▖', '▘', '▝'] + }; + private readonly Stack _clipStack = new(100); private readonly ConsoleWindow _consoleWindow; private readonly PixelBuffer _pixelBuffer; @@ -73,37 +96,37 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect new SKPaint { FilterQuality = SKFilterQuality.Medium }); for (int y = 0; y < bitmap.Info.Height; y += 2) - for (int x = 0; x < bitmap.Info.Width; x += 2) - { - // NOTE: we divide by 2 because we are working with quad pixels, - // // the bitmap has twice the horizontal and twice the vertical of the target rect. - int px = (int)targetRect.TopLeft.X + x / 2; - int py = (int)targetRect.TopLeft.Y + y / 2; + for (int x = 0; x < bitmap.Info.Width; x += 2) + { + // NOTE: we divide by 2 because we are working with quad pixels, + // // the bitmap has twice the horizontal and twice the vertical of the target rect. + int px = (int)targetRect.TopLeft.X + x / 2; + int py = (int)targetRect.TopLeft.Y + y / 2; - // get the quad pixel the bitmap - var quadColors = new[] - { + // get the quad pixel the bitmap + var quadColors = new[] + { bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) }; - // map it to a single char to represet the 4 pixels - char quadPixel = GetQuadPixelCharacter(quadColors); + // map it to a single char to represet the 4 pixels + char quadPixel = GetQuadPixelCharacter(quadColors); - // get the combined colors for the quad pixel - Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); - Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); + // get the combined colors for the quad pixel + Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); + Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); - var imagePixel = new Pixel( - new PixelForeground(new SimpleSymbol(quadPixel), foreground), - new PixelBackground(background)); - CurrentClip.ExecuteWithClipping(new Point(px, py), - () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - existingPixel => existingPixel.Blend(imagePixel)); - }); - } + var imagePixel = new Pixel( + new PixelForeground(new SimpleSymbol(quadPixel), foreground), + new PixelBackground(background)); + CurrentClip.ExecuteWithClipping(new Point(px, py), + () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + existingPixel => existingPixel.Blend(imagePixel)); + }); + } } public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) @@ -127,130 +150,89 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) DrawLineInternal(pen, myLine); break; case StreamGeometryImpl streamGeometry: - { - pen = pen ?? new Pen(brush); + { + pen = pen ?? new Pen(brush); - var extractColorCheckPlatformSupported = ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle); - if (extractColorCheckPlatformSupported == null) - return; + var extractColorCheckPlatformSupported = + ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle); + if (extractColorCheckPlatformSupported == null) + return; - var color = (Color)extractColorCheckPlatformSupported; + var color = (Color)extractColorCheckPlatformSupported; - if (lineStyle == null) - lineStyle = LineStyle.SingleLine; + if (lineStyle == null) + lineStyle = LineStyle.SingleLine; - var strokePostions = InferStrokePositions(streamGeometry); + var strokePostions = InferStrokePositions(streamGeometry); - bool hasTop = strokePostions.Contains(RectangleLinePosition.Top); - bool hasRight = strokePostions.Contains(RectangleLinePosition.Right); - bool hasBottom = strokePostions.Contains(RectangleLinePosition.Bottom); - bool hasLeft = strokePostions.Contains(RectangleLinePosition.Left); + bool hasTop = strokePostions.Contains(RectangleLinePosition.Top); + bool hasRight = strokePostions.Contains(RectangleLinePosition.Right); + bool hasBottom = strokePostions.Contains(RectangleLinePosition.Bottom); + bool hasLeft = strokePostions.Contains(RectangleLinePosition.Left); - if (lineStyle == LineStyle.Edge || lineStyle == LineStyle.EdgeWide) + if (lineStyle == LineStyle.Edge || lineStyle == LineStyle.EdgeWide) + { + for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++) { - for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++) - { - var stroke = TransformLineInternal(streamGeometry.Strokes[iStroke]); + Line stroke = TransformLineInternal(streamGeometry.Strokes[iStroke]); - if (stroke.Bounds.Width > 0 || stroke.Bounds.Height > 0) - { - if (stroke.Vertical) - DrawEdgeLine(stroke, strokePostions[iStroke], lineStyle.Value, color, hasTop, hasBottom); - else - DrawEdgeLine(stroke, strokePostions[iStroke], lineStyle.Value, color, hasLeft, hasRight); - } + if (stroke.Bounds.Width > 0 || stroke.Bounds.Height > 0) + { + if (stroke.Vertical) + DrawEdgeLine(stroke, strokePostions[iStroke], lineStyle.Value, color, hasTop, + hasBottom); + else + DrawEdgeLine(stroke, strokePostions[iStroke], lineStyle.Value, color, hasLeft, + hasRight); } } - else + } + else + { + Line strokeTop = null; + Line strokeLeft = null; + Line strokeRight = null; + Line strokeBottom = null; + for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++) { - Line strokeTop = null; - Line strokeLeft = null; - Line strokeRight = null; - Line strokeBottom = null; - for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++) - { - var stroke = streamGeometry.Strokes[iStroke]; - var strokePosition = strokePostions[iStroke]; - if (strokePosition == RectangleLinePosition.Left) - strokeLeft = stroke; - else if (strokePosition == RectangleLinePosition.Right) - strokeRight = stroke; - else if (strokePosition == RectangleLinePosition.Top) - strokeTop = stroke; - else if (strokePosition == RectangleLinePosition.Bottom) - strokeBottom = stroke; - } - if (strokeLeft != null) - { - //if (strokeBottom != null) - // strokeLeft = new Line(strokeLeft.PStart, strokeBottom.PStart, strokeLeft.SourceGeometry, strokeLeft.Transform); - DrawBoxLineInternal(pen, strokeLeft, RectangleLinePosition.Left); - } - - if (strokeTop != null) - { - //if (strokeRight != null) - // strokeTop = new Line(strokeTop.PStart, strokeRight.PStart, strokeTop.SourceGeometry, strokeTop.Transform); - DrawBoxLineInternal(pen, strokeTop, RectangleLinePosition.Top); - } - - if (strokeRight != null) - { - //if (strokeBottom != null) - // strokeRight = new Line(strokeRight.PStart, strokeBottom.PEnd, strokeRight.SourceGeometry, strokeRight.Transform); - DrawBoxLineInternal(pen, strokeRight, RectangleLinePosition.Right); - } - - if (strokeBottom != null) - { - //if (strokeLeft != null) - // strokeBottom = new Line(strokeLeft.PEnd, strokeBottom.PEnd, strokeBottom.SourceGeometry, strokeBottom.Transform); - DrawBoxLineInternal(pen, strokeBottom, RectangleLinePosition.Bottom); - } + Line stroke = streamGeometry.Strokes[iStroke]; + RectangleLinePosition strokePosition = strokePostions[iStroke]; + if (strokePosition == RectangleLinePosition.Left) + strokeLeft = stroke; + else if (strokePosition == RectangleLinePosition.Right) + strokeRight = stroke; + else if (strokePosition == RectangleLinePosition.Top) + strokeTop = stroke; + else if (strokePosition == RectangleLinePosition.Bottom) + strokeBottom = stroke; } + + if (strokeLeft != null) + //if (strokeBottom != null) + // strokeLeft = new Line(strokeLeft.PStart, strokeBottom.PStart, strokeLeft.SourceGeometry, strokeLeft.Transform); + DrawBoxLineInternal(pen, strokeLeft, RectangleLinePosition.Left); + + if (strokeTop != null) + //if (strokeRight != null) + // strokeTop = new Line(strokeTop.PStart, strokeRight.PStart, strokeTop.SourceGeometry, strokeTop.Transform); + DrawBoxLineInternal(pen, strokeTop, RectangleLinePosition.Top); + + if (strokeRight != null) + //if (strokeBottom != null) + // strokeRight = new Line(strokeRight.PStart, strokeBottom.PEnd, strokeRight.SourceGeometry, strokeRight.Transform); + DrawBoxLineInternal(pen, strokeRight, RectangleLinePosition.Right); + + if (strokeBottom != null) + //if (strokeLeft != null) + // strokeBottom = new Line(strokeLeft.PEnd, strokeBottom.PEnd, strokeBottom.SourceGeometry, strokeBottom.Transform); + DrawBoxLineInternal(pen, strokeBottom, RectangleLinePosition.Bottom); } + } break; default: ConsoloniaPlatform.RaiseNotSupported(5); break; } - - } - - private static RectangleLinePosition[] InferStrokePositions(StreamGeometryImpl streamGeometry) - { - // infer rectangle hints by using focolpoint - var focalPointX = streamGeometry.Strokes.Average(stroke => stroke.PStart.X + Math.Abs(stroke.PStart.X - stroke.PEnd.X) / 2); - var focalPointY = streamGeometry.Strokes.Average(stroke => stroke.PStart.Y + Math.Abs(stroke.PStart.Y - stroke.PEnd.Y) / 2); - var focalPoint = new Point(focalPointX, focalPointY); - var strokePositions = new RectangleLinePosition[streamGeometry.Strokes.Count]; - - for (int i = 0; i < streamGeometry.Strokes.Count; i++) - { - var stroke = streamGeometry.Strokes[i]; - if (stroke.Bounds.Width == 0 && stroke.Bounds.Height == 0) - { - // ignore zero length strokes - strokePositions[i] = RectangleLinePosition.Unknown; - continue; - } - - if (stroke.Vertical) - { - if (stroke.PStart.X <= focalPoint.X) - strokePositions[i] = RectangleLinePosition.Left; - else if (stroke.PStart.X >= focalPoint.X) - strokePositions[i] = RectangleLinePosition.Right; - } - else - { - if (stroke.PStart.Y <= focalPoint.Y) - strokePositions[i] = RectangleLinePosition.Top; - else if (stroke.PStart.Y >= focalPoint.Y) - strokePositions[i] = RectangleLinePosition.Bottom; - } - } - return strokePositions; } public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = new()) @@ -275,11 +257,11 @@ private static RectangleLinePosition[] InferStrokePositions(StreamGeometryImpl s case VisualBrush: throw new NotImplementedException(); case ISceneBrush sceneBrush: - { - ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); - if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); - return; - } + { + ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); + if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); + return; + } } FillRectangleWithBrush(brush, pen, r); @@ -416,6 +398,45 @@ public void PopLayer() throw new NotImplementedException(); } + private static RectangleLinePosition[] InferStrokePositions(StreamGeometryImpl streamGeometry) + { + // infer rectangle hints by using focolpoint + double focalPointX = streamGeometry.Strokes.Average(stroke => + stroke.PStart.X + Math.Abs(stroke.PStart.X - stroke.PEnd.X) / 2); + double focalPointY = streamGeometry.Strokes.Average(stroke => + stroke.PStart.Y + Math.Abs(stroke.PStart.Y - stroke.PEnd.Y) / 2); + var focalPoint = new Point(focalPointX, focalPointY); + var strokePositions = new RectangleLinePosition[streamGeometry.Strokes.Count]; + + for (int i = 0; i < streamGeometry.Strokes.Count; i++) + { + Line stroke = streamGeometry.Strokes[i]; + if (stroke.Bounds.Width == 0 && stroke.Bounds.Height == 0) + { + // ignore zero length strokes + strokePositions[i] = RectangleLinePosition.Unknown; + continue; + } + + if (stroke.Vertical) + { + if (stroke.PStart.X <= focalPoint.X) + strokePositions[i] = RectangleLinePosition.Left; + else if (stroke.PStart.X >= focalPoint.X) + strokePositions[i] = RectangleLinePosition.Right; + } + else + { + if (stroke.PStart.Y <= focalPoint.Y) + strokePositions[i] = RectangleLinePosition.Top; + else if (stroke.PStart.Y >= focalPoint.Y) + strokePositions[i] = RectangleLinePosition.Bottom; + } + } + + return strokePositions; + } + /// /// Draw a straight horizontal line or vertical line /// @@ -481,44 +502,21 @@ private void FillRectangleWithBrush(IBrush brush, IPen pen, Rect r) double width = r2.Width + (pen?.Thickness ?? 0); double height = r2.Height + (pen?.Thickness ?? 0); for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - { - int px = (int)(r2.TopLeft.X + x); - int py = (int)(r2.TopLeft.Y + y); + for (int y = 0; y < height; y++) + { + int px = (int)(r2.TopLeft.X + x); + int py = (int)(r2.TopLeft.Y + y); - ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); - CurrentClip.ExecuteWithClipping(new Point(px, py), () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - pixel => pixel.Blend(new Pixel(new PixelBackground(backgroundBrush.Mode, - backgroundBrush.Color)))); - }); - } + ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); + CurrentClip.ExecuteWithClipping(new Point(px, py), () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + pixel => pixel.Blend(new Pixel(new PixelBackground(backgroundBrush.Mode, + backgroundBrush.Color)))); + }); + } } - private static char[][] EdgeChars = - [ - // LineStyle=Edge - ['▕', '▁', '▏', '▔'], - // LineStyle=EdgeWide - ['▐', '▄', '▌', '▀'], - ]; - - - // top left, top right, bottom right, bottom left, - private static char[][] CornerChars = - { - // LineStyle=Edge we don't draw chars for edge corners - [' ', ' ', ' ', ' ' ], - // LineStyle=EdgeWide - ['▗', '▖', '▘', '▝'], - }; - - private const int TopLeft = 0; - private const int TopRight = 1; - private const int BottomRight = 2; - private const int BottomLeft = 3; - /// /// Draw a rectangle line with corners /// @@ -551,7 +549,7 @@ private void DrawBoxLineInternal(IPen pen, Line line, RectangleLinePosition line if (lineStyle == LineStyle.Edge || lineStyle == LineStyle.EdgeWide) { - DrawEdgeLine(line, linePosition, lineStyle.Value, color, includeStartSymbol: true, includeEndSymbol: true); + DrawEdgeLine(line, linePosition, lineStyle.Value, color, true, true); } else { @@ -566,14 +564,15 @@ private void DrawBoxLineInternal(IPen pen, Line line, RectangleLinePosition line } } - private void DrawEdgeLine(Line line, RectangleLinePosition linePosition, LineStyle lineStyle, Color color, bool includeStartSymbol, bool includeEndSymbol) + private void DrawEdgeLine(Line line, RectangleLinePosition linePosition, LineStyle lineStyle, Color color, + bool includeStartSymbol, bool includeEndSymbol) { if (line.Length == 0) return; ISymbol startSymbol; ISymbol middleSymbol; ISymbol endSymbol; - var iStyle = (int)lineStyle - (int)LineStyle.Edge; + int iStyle = (int)lineStyle - (int)LineStyle.Edge; switch (linePosition) { @@ -603,7 +602,7 @@ private void DrawEdgeLine(Line line, RectangleLinePosition linePosition, LineSty Point head = line.PStart; - var length = line.Length; + int length = line.Length; if (includeStartSymbol) DrawLineSymbolAndMoveHead(ref head, line.Vertical, startSymbol, color, 1); else @@ -705,10 +704,11 @@ private void DrawLineSymbolAndMoveHead(ref Point head, bool isVertical, ISymbol for (int i = 0; i < count; i++) { Point h = head; - CurrentClip.ExecuteWithClipping(h, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)h, (pixel) => pixel.Blend(new Pixel(symbol, color))); - }); + CurrentClip.ExecuteWithClipping(h, + () => + { + _pixelBuffer.Set((PixelBufferCoordinate)h, pixel => pixel.Blend(new Pixel(symbol, color))); + }); head = isVertical ? head.WithY(head.Y + 1) : head.WithX(head.X + 1); @@ -742,21 +742,21 @@ private void DrawLineSymbolAndMoveHead(ref Point head, bool isVertical, ISymbol switch (glyph) { case "\t": + { + const int tabSize = 8; + var consolePixel = new Pixel(new SimpleSymbol(' '), foregroundColor); + for (int j = 0; j < tabSize; j++) { - const int tabSize = 8; - var consolePixel = new Pixel(new SimpleSymbol(' '), foregroundColor); - for (int j = 0; j < tabSize; j++) + Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); + CurrentClip.ExecuteWithClipping(newCharacterPoint, () => { - Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); - CurrentClip.ExecuteWithClipping(newCharacterPoint, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, - oldPixel => oldPixel.Blend(consolePixel)); - }); - } - - currentXPosition += tabSize - 1; + _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, + oldPixel => oldPixel.Blend(consolePixel)); + }); } + + currentXPosition += tabSize - 1; + } break; case "\r": case "\f": @@ -765,66 +765,66 @@ private void DrawLineSymbolAndMoveHead(ref Point head, bool isVertical, ISymbol currentYPosition++; break; default: + { + var symbol = new SimpleSymbol(glyph); + // if we are attempting to draw a wide glyph we need to make sure that the clipping point + // is for the last physical char. Aka a double char should be clipped if it's second rendered + // char would break the boundary of the clip. + // var clippingPoint = new Point(characterPoint.X + symbol.Width - 1, characterPoint.Y); + var newPixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight); + CurrentClip.ExecuteWithClipping(characterPoint, () => { - var symbol = new SimpleSymbol(glyph); - // if we are attempting to draw a wide glyph we need to make sure that the clipping point - // is for the last physical char. Aka a double char should be clipped if it's second rendered - // char would break the boundary of the clip. - // var clippingPoint = new Point(characterPoint.X + symbol.Width - 1, characterPoint.Y); - var newPixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight); - CurrentClip.ExecuteWithClipping(characterPoint, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - oldPixel => + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + oldPixel => + { + if (oldPixel.Width == 0) { - if (oldPixel.Width == 0) + // if the oldPixel was empty, we need to set the previous pixel to space + double targetX = characterPoint.X - 1; + if (targetX >= 0) + _pixelBuffer.Set( + (PixelBufferCoordinate)new Point(targetX, characterPoint.Y), + oldPixel2 => + new Pixel( + new PixelForeground(new SimpleSymbol(' '), Colors.Transparent), + oldPixel2.Background)); + } + else if (oldPixel.Width > 1) + { + // if oldPixel was wide we need to reset overlapped symbols from empty to space + for (ushort i = 1; i < oldPixel.Width; i++) { - // if the oldPixel was empty, we need to set the previous pixel to space - double targetX = characterPoint.X - 1; - if (targetX >= 0) + double targetX = characterPoint.X + i; + if (targetX < _pixelBuffer.Size.Width) _pixelBuffer.Set( (PixelBufferCoordinate)new Point(targetX, characterPoint.Y), oldPixel2 => new Pixel( - new PixelForeground(new SimpleSymbol(' '), Colors.Transparent), - oldPixel2.Background)); + new PixelForeground(new SimpleSymbol(' '), + Colors.Transparent), oldPixel2.Background)); } - else if (oldPixel.Width > 1) + } + + // if the pixel was a wide character, we need to set the overlapped pixels to empty pixels. + if (newPixel.Width > 1) + for (int i = 1; i < symbol.Width; i++) { - // if oldPixel was wide we need to reset overlapped symbols from empty to space - for (ushort i = 1; i < oldPixel.Width; i++) - { - double targetX = characterPoint.X + i; - if (targetX < _pixelBuffer.Size.Width) - _pixelBuffer.Set( - (PixelBufferCoordinate)new Point(targetX, characterPoint.Y), - oldPixel2 => - new Pixel( - new PixelForeground(new SimpleSymbol(' '), - Colors.Transparent), oldPixel2.Background)); - } + double targetX = characterPoint.X + i; + if (targetX < _pixelBuffer.Size.Width) + _pixelBuffer.Set( + (PixelBufferCoordinate)new Point(targetX, characterPoint.Y), + oldPixel2 => + new Pixel( + new PixelForeground(new SimpleSymbol(), Colors.Transparent), + oldPixel2.Background)); } - // if the pixel was a wide character, we need to set the overlapped pixels to empty pixels. - if (newPixel.Width > 1) - for (int i = 1; i < symbol.Width; i++) - { - double targetX = characterPoint.X + i; - if (targetX < _pixelBuffer.Size.Width) - _pixelBuffer.Set( - (PixelBufferCoordinate)new Point(targetX, characterPoint.Y), - oldPixel2 => - new Pixel( - new PixelForeground(new SimpleSymbol(), Colors.Transparent), - oldPixel2.Background)); - } - - return oldPixel.Blend(newPixel); - }); - }); + return oldPixel.Blend(newPixel); + }); + }); - currentXPosition += symbol.Width; - } + currentXPosition += symbol.Width; + } break; } } diff --git a/src/Consolonia.Core/Drawing/RectangleLinePosition.cs b/src/Consolonia.Core/Drawing/RectangleLinePosition.cs index 38ceaa50..3194bde0 100644 --- a/src/Consolonia.Core/Drawing/RectangleLinePosition.cs +++ b/src/Consolonia.Core/Drawing/RectangleLinePosition.cs @@ -6,6 +6,6 @@ internal enum RectangleLinePosition Top, Right, Bottom, - Unknown, + Unknown } -} +} \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/StreamGeometryImpl.cs b/src/Consolonia.Core/Drawing/StreamGeometryImpl.cs index 27335eb4..1ea540a6 100644 --- a/src/Consolonia.Core/Drawing/StreamGeometryImpl.cs +++ b/src/Consolonia.Core/Drawing/StreamGeometryImpl.cs @@ -10,18 +10,9 @@ namespace Consolonia.Core.Drawing { internal class StreamGeometryImpl : IStreamGeometryImpl { - private List _strokes; - private List _fills; + private readonly List _fills; + private readonly List _strokes; private Rect _bounds; - // private SKPath _path; - - public Rect Bounds => _bounds; - - public double ContourLength => _strokes.Sum(l => l.ContourLength); - - public IReadOnlyList Strokes => _strokes; - - public IReadOnlyList Fills => _fills; public StreamGeometryImpl() { @@ -29,18 +20,21 @@ public StreamGeometryImpl() _fills = new List(); } + public IReadOnlyList Strokes => _strokes; + + public IReadOnlyList Fills => _fills; + // private SKPath _path; + + public Rect Bounds => _bounds; + + public double ContourLength => _strokes.Sum(l => l.ContourLength); + public IStreamGeometryImpl Clone() { var clone = new StreamGeometryImpl(); - foreach(var line in _strokes) - { - clone._strokes.Add(line); - } - foreach (var rect in _fills) - { - clone._fills.Add(rect); - } + foreach (Line line in _strokes) clone._strokes.Add(line); + foreach (Rectangle rect in _fills) clone._fills.Add(rect); clone._bounds = _bounds; return clone; } @@ -52,7 +46,7 @@ public bool FillContains(Point point) public Rect GetRenderBounds(IPen pen) { - var strokeBounds = _strokes.Aggregate(new Rect(), (rect, line) => rect.Union(line.GetRenderBounds(pen))); + Rect strokeBounds = _strokes.Aggregate(new Rect(), (rect, line) => rect.Union(line.GetRenderBounds(pen))); return _fills.Aggregate(strokeBounds, (rect, r) => rect.Union(r.GetRenderBounds(pen))); } @@ -87,7 +81,8 @@ public bool TryGetPointAtDistance(double distance, out Point point) throw new NotImplementedException(); } - public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, [NotNullWhen(true)] out IGeometryImpl segmentGeometry) + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, + [NotNullWhen(true)] out IGeometryImpl segmentGeometry) { throw new NotImplementedException(); } @@ -98,7 +93,7 @@ public ITransformedGeometryImpl WithTransform(Matrix transform) } /// - /// A Conolonia implementation of a . + /// A Conolonia implementation of a . /// private class StreamGeometryContextImpl : IStreamGeometryContextImpl, IGeometryContext2 { @@ -107,8 +102,8 @@ private class StreamGeometryContextImpl : IStreamGeometryContextImpl, IGeometryC private Point _lastPoint; /// - /// Initializes a new instance of the StreamGeometryContextImpl class. - /// Geometry to operate on. + /// Initializes a new instance of the StreamGeometryContextImpl class. + /// Geometry to operate on. /// public StreamGeometryContextImpl(StreamGeometryImpl geometryImpl) { @@ -116,7 +111,33 @@ public StreamGeometryContextImpl(StreamGeometryImpl geometryImpl) } /// - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + public void LineTo(Point point, bool isStroked) + { + LineTo(point); + } + + /// + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, + SweepDirection sweepDirection, bool isStroked) + { + _lastPoint = point; + } + + /// + public void CubicBezierTo(Point point1, Point point2, Point point3, bool isStroked) + { + throw new NotSupportedException(); + } + + /// + public void QuadraticBezierTo(Point point1, Point point2, bool isStroked) + { + throw new NotSupportedException(); + } + + /// + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, + SweepDirection sweepDirection) { // ignore arc instructions. It's attempt to draw rounded corners, we don't do that. //_lastPoint = point; @@ -155,7 +176,7 @@ public void LineTo(Point point) /// public void EndFigure(bool isClosed) { - var bound = _geometryImpl._strokes.Aggregate(new Rect(), (rect, line) => rect.Union(line.Bounds)); + Rect bound = _geometryImpl._strokes.Aggregate(new Rect(), (rect, line) => rect.Union(line.Bounds)); _geometryImpl._bounds = bound; if (_isFilled) { @@ -168,35 +189,9 @@ public void SetFillRule(FillRule fillRule) { } - /// - public void LineTo(Point point, bool isStroked) - { - this.LineTo(point); - } - - /// - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked) - { - _lastPoint = point; - } - - /// - public void CubicBezierTo(Point point1, Point point2, Point point3, bool isStroked) - { - throw new NotSupportedException(); - - } - - /// - public void QuadraticBezierTo(Point point1, Point point2, bool isStroked) - { - throw new NotSupportedException(); - } - public void Dispose() { } } - } -} +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Gallery.Tests/BorderTests.cs b/src/Tests/Consolonia.Gallery.Tests/BorderTests.cs index 99004c06..753d17c2 100644 --- a/src/Tests/Consolonia.Gallery.Tests/BorderTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/BorderTests.cs @@ -14,11 +14,11 @@ public async Task PerformSingleTest() { await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("┌───────┐┌────────────────────┐╔════════════════════╗", - "│Default││LineStyle=SingleLine│║LineStyle=DoubleLine║", - "└───────┘└────────────────────┘╚════════════════════╝", - " ▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▗▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▖████████████████ ", - "▕LineStyle=Edge▏▐LineStyle=EdgeWide▌█LineStyle=Bold█ ", - " ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘████████████████ "); + "│Default││LineStyle=SingleLine│║LineStyle=DoubleLine║", + "└───────┘└────────────────────┘╚════════════════════╝", + " ▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▗▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▖████████████████ ", + "▕LineStyle=Edge▏▐LineStyle=EdgeWide▌█LineStyle=Bold█ ", + " ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘████████████████ "); } } } \ No newline at end of file