using AngleSharp.Dom;
using AngleSharp.Html.Parser;
namespace XFESystem.Browser.Tests;
public sealed class BrowserCanvasTests
{
[Fact]
public void CanvasFillClearAndImageDataReadRealPixels()
{
var page = new CanvasPage();
page.Run("canvas.width=4;canvas.height=3;ctx.fillStyle='#1256AB';ctx.fillRect(1,1,2,1);var data=ctx.getImageData(0,0,4,3);if(!(data.data instanceof Uint8ClampedArray))throw Error('type');");
Assert.Equal("18,86,171,255", page.Value("Array.from(data.data.slice(20,24)).join(',')"));
Assert.Equal(0xFF1256ABu, page.Surface.Pixels.Span[5]);
page.Run("ctx.clearRect(1,1,1,1)"); Assert.Equal(0u, page.Surface.Pixels.Span[5]); Assert.Equal(0xFF1256ABu, page.Surface.Pixels.Span[6]);
}
[Fact]
public void CanvasCompositePutImageDataAndTransformsAreNotNoOps()
{
var page = new CanvasPage();
page.Run("canvas.width=4;canvas.height=4;ctx.fillStyle='red';ctx.fillRect(0,0,4,4);ctx.globalAlpha=.5;ctx.fillStyle='blue';ctx.fillRect(0,0,1,1);ctx.save();ctx.translate(1,1);ctx.scale(2,2);ctx.globalAlpha=1;ctx.fillStyle='white';ctx.fillRect(0,0,1,1);ctx.restore();var data=ctx.createImageData(1,1);data.data.set([1,2,3,4]);ctx.putImageData(data,3,0);");
Assert.Equal(0xFF7F0080u, page.Surface.Pixels.Span[0]); Assert.Equal(0xFFFFFFFFu, page.Surface.Pixels.Span[5]); Assert.Equal(0x04010203u, page.Surface.Pixels.Span[3]);
}
[Fact]
public void CanvasTextUsesHostRasterizedMaskAndMeasuredMetrics()
{
var page = new CanvasPage(new() { MeasureText = (_, _) => 2, RasterizeText = (_, _) => (2, 2, [255, 0, 128, 255]) });
page.Run("canvas.width=4;canvas.height=4;ctx.fillStyle='#123456';ctx.textBaseline='top';ctx.fillText('X',1,1);var width=ctx.measureText('X').width;");
Assert.Equal("2", page.Value("width")); Assert.Equal(0xFF123456u, page.Surface.Pixels.Span[5]); Assert.Equal(0u, page.Surface.Pixels.Span[6]); Assert.Equal(0x80123456u, page.Surface.Pixels.Span[9]);
Assert.Equal("128", page.Value("ctx.getImageData(1,2,1,1).data[3]"));
}
[Fact]
public void MissingTextRasterizerFailsExplicitlyAndResizeResetsBackingPixels()
{
var page = new CanvasPage(); Assert.False(page.Runtime.Evaluate("ctx.fillText('X',0,0)").Success);
page.Run("ctx.fillStyle='red';ctx.fillRect(0,0,2,2);canvas.setAttribute('width','2');canvas.height=2;");
Assert.Equal(2, page.Surface.Width); Assert.Equal(2, page.Surface.Height); Assert.All(page.Surface.Pixels.ToArray(), pixel => Assert.Equal(0u, pixel));
Assert.Equal("#000000", page.Value("ctx.fillStyle"));
Assert.False(page.Runtime.Evaluate("canvas.width=4096;canvas.height=4096").Success);
Assert.True((long)page.Surface.Width * page.Surface.Height <= 1_048_576);
}
private sealed class CanvasPage
{
private readonly IDocument document;
private readonly DomBridge dom;
public CanvasPage(BrowserDomHost? host = null)
{
var limits = new BrowserLimits(); Runtime = new(limits); document = new HtmlParser().ParseDocument("");
dom = new(Runtime, document, new("https://example.test/"), limits, host);
Run("var canvas=document.getElementById('Canvas'),ctx=canvas.getContext('2d');if(canvas.getContext('2d')!==ctx||canvas.getContext('webgl')!==null)throw Error('identity');");
}
public JavaScriptRuntime Runtime { get; }
public BrowserCanvasSurface Surface => dom.GetCanvasSurface(document.GetElementById("Canvas")!)!;
public void Run(string source) { var result = Runtime.Evaluate(source); Assert.True(result.Success, result.Error); }
public string Value(string source) { var result = Runtime.Evaluate(source); Assert.True(result.Success, result.Error); return result.Value; }
}
}