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; } } }