Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Surrogate Unicode sequences #139

Merged
merged 39 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f087bf5
Add support for Surragate sequences
tomlm Nov 6, 2024
9446c9b
fix release build warning.
tomlm Nov 6, 2024
637747c
* fix unit tests
tomlm Nov 6, 2024
559fe7b
Automated JetBrains cleanup
github-actions[bot] Nov 6, 2024
ef299e5
use Text...everything works except for double char emojiis
tomlm Nov 7, 2024
ebc751d
formatting merge
tomlm Nov 7, 2024
e0a2a52
use TextShaper.Text for render text instead of trying to render from …
tomlm Nov 7, 2024
352bb67
FINALLY I have emoticons rendering correctly
tomlm Nov 8, 2024
adeb5ea
added scroll bars to textlbock gallery page
tomlm Nov 8, 2024
c174f9b
update sample to show that CJK wide chars work
tomlm Nov 8, 2024
479460b
removed unused using statements
tomlm Nov 8, 2024
cc417f8
update unit tests for textblock
tomlm Nov 8, 2024
7e36247
lint feedback, adopt WcWidth for accurate calculation of char width
tomlm Nov 8, 2024
88ebb08
properly calculate GlyphInfos in textshaper
tomlm Nov 8, 2024
5891381
fix bugs found by coderabbit. Nice job code rabbit!
tomlm Nov 8, 2024
e4e247b
remove using
tomlm Nov 8, 2024
532ac52
add detection of complex emojii
tomlm Nov 8, 2024
b7c1e35
fix calc of glyph
tomlm Nov 8, 2024
8cab697
update unitestconsole
tomlm Nov 8, 2024
78710e5
add comment
tomlm Nov 8, 2024
2be4221
fix
tomlm Nov 8, 2024
265eb64
ah, finally figured out the using issue. Visual studio didn't conside…
tomlm Nov 8, 2024
8c76411
argh. Lint is killing me!
tomlm Nov 8, 2024
0b478fc
Automated JetBrains cleanup
github-actions[bot] Nov 8, 2024
9791ab4
Merge branch 'main' into tomlm/rune
tomlm Nov 9, 2024
6c008eb
Merge branch 'main' into tomlm/rune
tomlm Nov 9, 2024
272b00f
merge
tomlm Nov 9, 2024
d4fc7f1
make list readonly
tomlm Nov 9, 2024
019a80f
Automated JetBrains cleanup
github-actions[bot] Nov 9, 2024
a823630
cleanup
tomlm Nov 9, 2024
3a5efde
add comments
tomlm Nov 9, 2024
61b9697
Merge branch 'tomlm/rune' of https://github.com/jinek/Consolonia into…
tomlm Nov 9, 2024
40dedcc
removed accidental comment of /// instead of //
tomlm Nov 9, 2024
379870e
Automated JetBrains cleanup
github-actions[bot] Nov 9, 2024
a829699
fix caret drawing bug in Pixel.Blend() I was merging IsCaret when mer…
tomlm Nov 10, 2024
51b30fe
huh, I thought I removed this.
tomlm Nov 10, 2024
7560a6c
Add PlaceholderGlyph
tomlm Nov 10, 2024
b97e7bc
sigh. Linter
tomlm Nov 10, 2024
c356590
Automated JetBrains cleanup
github-actions[bot] Nov 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Consolonia.Core/Consolonia.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
<PackageReference Include="Avalonia.FreeDesktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Skia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageReference Include="NullLib.ConsoleEx" Version="1.0.4.4" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />
<PackageReference Include="Unicode.net" Version="2.0.0" />
<PackageReference Include="Wcwidth" Version="2.0.0" />
</ItemGroup>

</Project>
Expand Down
2 changes: 2 additions & 0 deletions src/Consolonia.Core/Drawing/ConsoleBrush.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Diagnostics;
using Avalonia;
using Avalonia.Media;
using Consolonia.Core.Drawing.PixelBufferImplementation;
using Consolonia.Core.Infrastructure;

namespace Consolonia.Core.Drawing
{
[DebuggerDisplay("{Color} [{Mode}]")]
public class ConsoleBrush : AvaloniaObject, IImmutableBrush
{
public static readonly StyledProperty<Color> ColorProperty =
Expand Down
89 changes: 44 additions & 45 deletions src/Consolonia.Core/Drawing/DrawingContextImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Consolonia.Core.Drawing.PixelBufferImplementation;
using Consolonia.Core.Helpers;
using Consolonia.Core.Infrastructure;
using Consolonia.Core.InternalHelpers;
using Consolonia.Core.Text;
Expand All @@ -24,7 +26,8 @@ internal class DrawingContextImpl : IDrawingContextImpl
private const byte HorizontalEndPattern = 0b0001;

public const int UnderlineThickness = 10;
public const int StrikthroughThickness = 11;
public const int StrikethroughThickness = 11;

private readonly Stack<Rect> _clipStack = new(100);
private readonly ConsoleWindow _consoleWindow;
private readonly PixelBuffer _pixelBuffer;
Expand Down Expand Up @@ -91,7 +94,8 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect
Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel);
Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel);

var imagePixel = new Pixel(new PixelForeground(new SimpleSymbol(quadPixel), color: foreground),
var imagePixel = new Pixel(
new PixelForeground(new SimpleSymbol(quadPixel), color: foreground),
new PixelBackground(background));
CurrentClip.ExecuteWithClipping(new Point(px, py),
() =>
Expand Down Expand Up @@ -206,9 +210,9 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun)
return;
}

string charactersDoDraw =
string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray());
DrawStringInternal(foreground, charactersDoDraw, glyphRun.GlyphTypeface);
var shapedBuffer = (ShapedBuffer)glyphRunImpl.GlyphInfos;
string text = shapedBuffer.Text.ToString();
DrawStringInternal(foreground, text, glyphRun.GlyphTypeface);
}

public IDrawingContextLayerImpl CreateLayer(Size size)
Expand Down Expand Up @@ -302,9 +306,12 @@ private void DrawLineInternal(IPen pen, Line line)
line = TransformLineInternal(line);

Point head = line.PStart;

if (IfMoveConsoleCaretMove(pen, head))
if (pen.Brush is MoveConsoleCaretToPositionBrush)
{
CurrentClip.ExecuteWithClipping(head,
() => { _pixelBuffer.Set((PixelBufferCoordinate)head, pixel => pixel.Blend(new Pixel(true))); });
return;
}

if (line.Vertical == false && pen.Thickness > 1)
{
Expand All @@ -328,7 +335,7 @@ private void ApplyTextDecorationLineInternal(ref Point head, IPen pen, Line line
TextDecorationCollection textDecoration = pen.Thickness switch
{
UnderlineThickness => TextDecorations.Underline,
StrikthroughThickness => TextDecorations.Strikethrough,
StrikethroughThickness => TextDecorations.Strikethrough,
_ => throw new ArgumentOutOfRangeException($"Unsupported thickness {pen.Thickness}")
};

Expand Down Expand Up @@ -366,8 +373,12 @@ private void DrawRectangleLineInternal(IPen pen, Line line)

Point head = line.PStart;

if (IfMoveConsoleCaretMove(pen, head))
if (pen.Brush is MoveConsoleCaretToPositionBrush)
{
CurrentClip.ExecuteWithClipping(head,
() => { _pixelBuffer.Set((PixelBufferCoordinate)head, pixel => pixel.Blend(new Pixel(true))); });
return;
}

var extractColorCheckPlatformSupported = ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle);
if (extractColorCheckPlatformSupported == null)
Expand Down Expand Up @@ -398,22 +409,6 @@ private Line TransformLineInternal(Line line)
return line;
}

/// <summary>
/// If the pen brush is a MoveConsoleCaretToPositionBrush, move the caret
/// </summary>
/// <param name="pen"></param>
/// <param name="head"></param>
/// <returns></returns>
private bool IfMoveConsoleCaretMove(IPen pen, Point head)
{
if (pen.Brush is not MoveConsoleCaretToPositionBrush)
return false;

CurrentClip.ExecuteWithClipping(head,
() => { _pixelBuffer.Set((PixelBufferCoordinate)head, pixel => pixel.Blend(new Pixel(true))); });
return true;
}

/// <summary>
/// Extract color from pen brush
/// </summary>
Expand Down Expand Up @@ -486,7 +481,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl
}
}

private void DrawStringInternal(IBrush foreground, string str, IGlyphTypeface typeface, Point origin = new())
private void DrawStringInternal(IBrush foreground, string text, IGlyphTypeface typeface, Point origin = new())
{
foreground = ConsoleBrush.FromBrush(foreground);
if (foreground is not ConsoleBrush { Mode: PixelBackgroundMode.Colored } consoleBrush)
Expand All @@ -495,23 +490,27 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl
return;
}

//if (!Transform.IsTranslateOnly()) ConsoloniaPlatform.RaiseNotSupported(15);
// if (!Transform.IsTranslateOnly()) ConsoloniaPlatform.RaiseNotSupported(15);

Point whereToDraw = origin.Transform(Transform);
int currentXPosition = 0;
int currentYPosition = 0;

//todo: support surrogates
foreach (char c in str)
// Each glyph maps to a pixel as a starting point.
// Emoji's and Ligatures are complex strings, so they start at a point and then overlap following pixels
// the x and y are adjusted accodingly.
foreach (string glyph in text.GetGlyphs())
{
Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition++, 0));
Point characterPoint =
whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition));
Color foregroundColor = consoleBrush.Color;

switch (c)
switch (glyph)
{
case '\t':
case "\t":
{
const int tabSize = 8;
var consolePixel = new Pixel(' ', foregroundColor);
var consolePixel = new Pixel(new SimpleSymbol(' '), foregroundColor);
for (int j = 0; j < tabSize; j++)
{
Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j);
Expand All @@ -525,26 +524,26 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl
currentXPosition += tabSize - 1;
}
break;
case '\n':
{
/* it's not clear if we need to draw anything. Cursor can be placed at the end of the line
var consolePixel = new Pixel(' ', foregroundColor);

_pixelBuffer.Set((PixelBufferCoordinate)characterPoint,
(oldPixel, cp) => oldPixel.Blend(cp), consolePixel);*/
}
break;
case '\u200B':
currentXPosition--;
case "\r":
case "\f":
case "\n":
currentXPosition = 0;
currentYPosition++;
break;
default:
{
var consolePixel = new Pixel(c, foregroundColor, typeface.Style, typeface.Weight);
var symbol = new SimpleSymbol(glyph);
var consolePixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight);
CurrentClip.ExecuteWithClipping(characterPoint, () =>
{
_pixelBuffer.Set((PixelBufferCoordinate)characterPoint,
(oldPixel, cp) => oldPixel.Blend(cp), consolePixel);
});

if (symbol.Width > 1)
currentXPosition += symbol.Width;
else
currentXPosition++;
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Diagnostics;

namespace Consolonia.Core.Drawing.PixelBufferImplementation
{
/// <summary>
/// https://en.wikipedia.org/wiki/Box-drawing_character
/// </summary>
[DebuggerDisplay("DrawingBox {Text}")]
public struct DrawingBoxSymbol : ISymbol
{
// all 0bXXXX_0000 are special values
Expand All @@ -18,10 +20,14 @@ public DrawingBoxSymbol(byte upRightDownLeft)

private byte _upRightDownLeft;

public string Text => GetBoxSymbol().ToString();

public ushort Width { get; } = 1;

/// <summary>
/// https://en.wikipedia.org/wiki/Code_page_437
/// </summary>
char ISymbol.GetCharacter()
private char GetBoxSymbol()
{
//DOS linedraw characters are not ordered in any programmatic manner, and calculating a particular character shape needs to use a look-up table. from https://en.wikipedia.org/wiki/Box-drawing_character

Expand Down Expand Up @@ -106,22 +112,21 @@ public ISymbol Blend(ref ISymbol symbolAbove)
_upRightDownLeft = BoldSymbol;
else
_upRightDownLeft |= drawingBoxSymbol._upRightDownLeft;

return this;
}

public static byte UpRightDownLeftFromPattern(byte pattern, LineStyle lineStyle)
public static DrawingBoxSymbol UpRightDownLeftFromPattern(byte pattern, LineStyle lineStyle)
{
if (pattern == EmptySymbol) return EmptySymbol;
if (pattern == EmptySymbol) return new DrawingBoxSymbol(EmptySymbol);
switch (lineStyle)
{
case LineStyle.SingleLine:
return pattern;
return new DrawingBoxSymbol(pattern);
case LineStyle.Bold:
return BoldSymbol;
return new DrawingBoxSymbol(BoldSymbol);
case LineStyle.DoubleLine:
byte leftPart = (byte)(pattern << 4);
return (byte)(leftPart | pattern);
return new DrawingBoxSymbol((byte)(leftPart | pattern));
default:
throw new ArgumentOutOfRangeException(nameof(lineStyle), lineStyle, null);
}
Expand Down
17 changes: 16 additions & 1 deletion src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation
{
public interface ISymbol
{
char GetCharacter();
/// <summary>
/// The text for the symbol (This can be single character or unicode encoding for Emoji's and the like)
/// </summary>
string Text { get; }

/// <summary>
/// The number of characters the symbol takes up
/// </summary>
ushort Width { get; }

bool IsWhiteSpace();

/// <summary>
/// Blend 2 symbols together
/// </summary>
/// <param name="symbolAbove"></param>
/// <returns></returns>
tomlm marked this conversation as resolved.
Show resolved Hide resolved
ISymbol Blend(ref ISymbol symbolAbove);
}
}
29 changes: 14 additions & 15 deletions src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Diagnostics;
using Avalonia.Media;

// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global

namespace Consolonia.Core.Drawing.PixelBufferImplementation
{
[DebuggerDisplay("'{Foreground.Symbol.Text}' [{Foreground.Color}, {Background.Color}]")]
public readonly struct Pixel
{
public PixelForeground Foreground { get; }
Expand All @@ -14,22 +16,13 @@ public readonly struct Pixel

public bool IsCaret { get; }

public Pixel(bool isCaret) : this(PixelBackgroundMode.Transparent)
public Pixel(bool isCaret)
{
Foreground = new PixelForeground(new SimpleSymbol());
Background = new PixelBackground(PixelBackgroundMode.Transparent);
IsCaret = isCaret;
}

public Pixel(char character, Color foregroundColor, FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal) :
this(new SimpleSymbol(character), foregroundColor, style, weight)
{
}

public Pixel(byte drawingBoxSymbol, Color foregroundColor) : this(
new DrawingBoxSymbol(drawingBoxSymbol), foregroundColor)
{
}

public Pixel(ISymbol symbol, Color foregroundColor, FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal, TextDecorationCollection textDecorations = null) : this(
new PixelForeground(symbol, weight, style, textDecorations, foregroundColor),
Expand Down Expand Up @@ -68,11 +61,17 @@ public Pixel Blend(Pixel pixelAbove)
case PixelBackgroundMode.Colored:
// merge pixelAbove into this pixel using alpha channel.
Color mergedColors = MergeColors(Background.Color, pixelAbove.Background.Color);
return new Pixel(pixelAbove.Foreground,
new PixelBackground(mergedColors));
newForeground = pixelAbove.Foreground;
newBackground = new PixelBackground(mergedColors);
return new Pixel(newForeground, newBackground);

case PixelBackgroundMode.Transparent:
newForeground = Foreground.Blend(pixelAbove.Foreground);
// if the foreground is transparent, ignore pixelAbove foreground.
newForeground = pixelAbove.Foreground.Color != Colors.Transparent
? Foreground.Blend(pixelAbove.Foreground)
: Foreground;

// background is transparent, ignore pixelAbove background.
newBackground = Background;
break;
case PixelBackgroundMode.Shaded:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Diagnostics;
using Avalonia.Media;

namespace Consolonia.Core.Drawing.PixelBufferImplementation
{
[DebuggerDisplay("[{Color}, {Mode}]")]
public readonly struct PixelBackground
{
public PixelBackground(Color color)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Diagnostics;
using Avalonia.Media;

namespace Consolonia.Core.Drawing.PixelBufferImplementation
{
[DebuggerDisplay("'{Symbol.Text}' [{Color}]")]
public readonly struct PixelForeground
{
public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal,
Expand Down Expand Up @@ -38,8 +40,6 @@ public PixelForeground Blend(PixelForeground pixelAboveForeground)
ISymbol symbolAbove = pixelAboveForeground.Symbol;
ArgumentNullException.ThrowIfNull(symbolAbove);

if (symbolAbove.IsWhiteSpace()) return this;

ISymbol newSymbol = Symbol.Blend(ref symbolAbove);

return new PixelForeground(newSymbol, pixelAboveForeground.Weight, pixelAboveForeground.Style,
Expand Down
Loading
Loading