XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using XFESystem.Browser;
using XFESystem.Desktop;

namespace XFESystem.Browser.Tests;

public sealed class WebsiteCssParityTests
{
    private static IDocument Page(string body, string css) => new HtmlParser().ParseDocument("<style>body{margin:0}" + css + "</style>" + body);
    private static BrowserDisplayList Layout(IDocument page) => new BrowserLayoutEngine().Layout(page, new(800, 600));
    [Fact] public void ColorMixUsesPremultipliedSrgbAndTransparentAlpha()
    {
        var page = Page("<div id=A></div>", "#A{height:10px;color:color-mix(in srgb,#512db4 40%,white);background:color-mix(in srgb,#512db4 15%,transparent);border:1px solid color-mix(in srgb,#512db4 24%,#454545)}");
        var list = Layout(page); var style = list.GetComputedStyle(page.GetElementById("A")!)!;
        Assert.Equal(0xB9ABE1u, style.Color.Rgb); Assert.Equal(0x512DB4u,style.Background.Rgb); Assert.Equal(.15,style.Background.Alpha,6);
        Assert.Equal(0x483F60u,Assert.Single(list.Commands,c=>c.Kind==BrowserDrawKind.Border && c.Bounds.Y==0).Color);
    }
    [Theory][InlineData("min(50%,18rem)",288)][InlineData("max(50%,18rem)",400)][InlineData("clamp(200px,50%,300px)",300)]
    public void CssMathFunctionsUseContainingBlockDimensions(string expression, double expected)
    {
        var page = Page("<div id=A></div>", "#A{height:10px;width:" + expression + "}");
        Assert.Equal(expected, Layout(page).GetBoundingClientRect(page.GetElementById("A")!).Width);
    }
    [Fact] public void FontInheritKeepsComputedSizeAndResolvedLengthLineHeight()
    {
        var page = Page("<div id=A><code id=B>Value<span id=C>Token</span></code></div>", "#A{font:italic bold 20px/1.6em Consolas}#B{font:inherit}#C{font-size:.5em}");
        var list = Layout(page); var code = list.GetComputedStyle(page.GetElementById("B")!)!; var token = list.GetComputedStyle(page.GetElementById("C")!)!;
        Assert.Equal(20, code.Font.Size); Assert.Equal(700,code.Font.Weight); Assert.True(code.Font.Italic);
        Assert.Equal(32,code.LineHeight); Assert.Equal(32,token.LineHeight); Assert.Equal(10,token.Font.Size);
    }
    [Fact] public void UnitlessLineHeightIsInheritedAsNumber()
    {
        var page = Page("<div><span id=A>Small</span></div>","div{font-size:20px;line-height:1.8}span{font-size:.5em}");
        Assert.Equal(18, Layout(page).GetComputedStyle(page.GetElementById("A")!)!.LineHeight);
    }
    [Fact] public void HeadingDefaultsHaveDistinctFontSizesWithoutAuthorOverride()
    {
        var page = Page("<h1 id=A>Title</h1><h2 id=B>Section</h2>", ""); var list = Layout(page);
        Assert.Equal(32,list.GetComputedStyle(page.GetElementById("A")!)!.Font.Size); Assert.Equal(24,list.GetComputedStyle(page.GetElementById("B")!)!.Font.Size);
    }
    [Fact] public void InsetShorthandPositionsExpandedElement()
    {
        var page = Page("<div id=A></div>","#A{position:fixed;inset:1rem 2rem 3rem 4rem}"); var rect = Layout(page).GetBoundingClientRect(page.GetElementById("A")!);
        Assert.Equal(new BrowserRect(64,16,704,536),rect);
    }
    [Fact] public void RealListMarkersFollowNumberingAndAuthorPseudoStyle()
    {
        var page = Page("<ol start=3><li>Alpha</li><li value=8>Beta</li><li>Gamma</li></ol>", "li{margin-top:.4em}li::marker{color:#512db4;font-weight:bold;font-size:1.05em}"); var list=Layout(page);
        foreach (string text in new[]{"3.","8.","9."}) { var marker=Assert.Single(list.Commands,c=>c.Text==text); Assert.Equal(0x512DB4u,marker.Color); Assert.Equal(700,marker.Font.Weight); Assert.Equal(16.8,marker.Font.Size,4); }
    }
    [Fact] public void NestedListMarkersInheritListTypeAndMayBeRemoved()
    {
        var page=Page("<ul><li>One<ul><li>Two</li></ul></li><li class=task>Task</li></ul>","ul ul{list-style-type:circle}.task{list-style-type:none}"); var list=Layout(page);
        Assert.Single(list.Commands,c=>c.Text=="•"); Assert.Single(list.Commands,c=>c.Text=="◦");
    }
    [Fact] public void TableCellsShareColumnsAndRowsHaveActualTwoDimensionalBounds()
    {
        var page=Page("<table><thead><tr><th id=A>Key</th><th id=B>Description</th></tr></thead><tbody><tr><td id=C>C#</td><td id=D>Code</td></tr></tbody></table>","table{display:block;width:max-content;max-width:100%;border-collapse:collapse}td,th{padding:6px 13px;border:1px solid gray}"); var list=Layout(page);
        var a=list.GetBoundingClientRect(page.GetElementById("A")!); var b=list.GetBoundingClientRect(page.GetElementById("B")!); var c=list.GetBoundingClientRect(page.GetElementById("C")!); var d=list.GetBoundingClientRect(page.GetElementById("D")!);
        Assert.Equal(a.X,c.X); Assert.Equal(b.X,d.X); Assert.Equal(a.Width,c.Width); Assert.Equal(b.Width,d.Width); Assert.Equal(a.Right-1,b.Left); Assert.Equal(a.Bottom-1,c.Top); Assert.Equal(c.Top,d.Top);
    }
    [Fact] public void RadiusCommandsContainNormalizedCornersAndShadowBounds()
    {
        var page=Page("<div id=A></div>","#A{width:100px;height:40px;border:1px solid #454545;border-radius:12px 8px 4px 0;background:#2b2b2b;box-shadow:0 6px 18px rgba(0,0,0,.18)}"); var list=Layout(page); var command=Assert.Single(list.Commands,c=>c.Decoration is not null); var box=command.Decoration!;
        Assert.Equal(new BrowserEdges(12,8,4,0),box.Radius); Assert.Equal(0x2B2B2Bu,box.Background.Rgb); Assert.Equal(18,Assert.Single(box.Shadows).Blur); Assert.True(command.Bounds.Left<box.Bounds.Left); Assert.True(command.Bounds.Bottom>box.Bounds.Bottom);
    }
    [Fact] public void CoverageLeavesRoundedCornerTransparentAndSmoothsBoundary()
    {
        var rect=new BrowserRect(0,0,40,40); var radius=new BrowserEdges(12,12,12,12);
        Assert.Equal(0,BrowserBoxRasterPainter.Coverage(rect,radius,.5,.5)); Assert.Equal(1,BrowserBoxRasterPainter.Coverage(rect,radius,20,20));
        Assert.InRange(BrowserBoxRasterPainter.Coverage(rect,radius,3.5,3.5),.1,.95);
    }
    [Fact] public void LinearGradientUsesCssDirectionAndPremultipliedStops()
    {
        var page=Page("<div id=A></div>","#A{width:100px;height:20px;background:linear-gradient(90deg,red,blue)}");
        var decoration=Assert.Single(Layout(page).Commands,c=>c.Decoration is not null).Decoration!; var gradient=Assert.Single(decoration.Gradients);
        Assert.Equal(0xFF0000u,gradient.Sample(0,10,100,20).Rgb); Assert.Equal(0x0000FFu,gradient.Sample(100,10,100,20).Rgb); Assert.Equal(0x800080u,gradient.Sample(50,10,100,20).Rgb);
    }
    [Fact] public void WebsiteLayeredRadialAndLinearGradientsUseAuthoredStops()
    {
        var page=Page("<div id=A></div>","#A{width:100px;height:100px;background:radial-gradient(circle at 8% 8%,color-mix(in srgb,#512db4 10%,transparent),transparent 28%),linear-gradient(145deg,#ffffff,#eeeeee)}");
        var decoration=Assert.Single(Layout(page).Commands,c=>c.Decoration is not null).Decoration!;
        Assert.Equal(2,decoration.Gradients.Count); var radial=decoration.Gradients[0]; Assert.True(radial.Radial); Assert.Equal(.08,radial.CenterX); Assert.Equal(.1,radial.Sample(8,8,100,100).Alpha,6); Assert.Equal(0,radial.Sample(100,100,100,100).Alpha);
    }
    [Fact] public void DeepColorAndLengthFunctionsFailWithinBoundedDepth()
    {
        string color=string.Concat(Enumerable.Repeat("color-mix(in srgb,red,",100))+"blue"+new string(')',100);
        string length=string.Concat(Enumerable.Repeat("min(1px,",100))+"2px"+new string(')',100);
        var page=Page("<div id=A></div>","#A{color:"+color+";width:"+length+";height:10px}");
        Assert.Equal(800,Layout(page).GetBoundingClientRect(page.GetElementById("A")!).Width);
    }
    [Fact] public void SiblingBlockMarginsCollapseToLargestPositiveAndMostNegative()
    {
        var page=Page("<p id=A></p><h2 id=B></h2><p id=C></p>","#A{height:10px;margin-bottom:20px}#B{height:10px;margin-top:30px;margin-bottom:-5px}#C{height:10px;margin-top:8px}");var list=Layout(page);
        Assert.Equal(40,list.GetBoundingClientRect(page.GetElementById("B")!).Top);Assert.Equal(53,list.GetBoundingClientRect(page.GetElementById("C")!).Top);
    }
    [Fact] public void AnywhereAndCjkTextWrapAtMeasuredGlyphBoundaries()
    {
        var page=Page("<p id=A>ABCDEFGHIJ</p><p id=B>你好世界你好</p>","p{width:32px;font-size:10px;line-height:1;overflow-wrap:anywhere}");var list=Layout(page);
        Assert.Equal(20,list.GetBoundingClientRect(page.GetElementById("A")!).Height);Assert.Equal(20,list.GetBoundingClientRect(page.GetElementById("B")!).Height);
        Assert.Equal("ABCDEFGHIJ",string.Concat(list.Commands.Where(c=>c.Node?.ParentElement?.Id=="A").Select(c=>c.Text)));
    }
    [Fact] public void RoundedOverflowClipsDescendantHitTestingAndPaintsActualCoverage()
    {
        var page=Page("<div id=A><div id=B></div></div>","#A{width:40px;height:40px;border-radius:12px;overflow:hidden;background:white}#B{width:40px;height:40px;background:red}");var list=Layout(page);
        Assert.NotSame(page.GetElementById("B"),list.HitTest(1,1)?.Node);Assert.Same(page.GetElementById("B"),list.HitTest(20,20)?.Node);
        Assert.Single(list.Commands,c=>c.Kind==BrowserDrawKind.PushClip&&c.RoundedClip is not null);
        var pixels=new Pixels();BrowserRasterPainter.Paint(pixels,new(0,0,80,80),list);
        Assert.Equal(0xFFFFFFu,pixels.At(0,0));Assert.Equal(0xFF0000u,pixels.At(20,20));Assert.NotEqual(0xFF0000u,pixels.At(3,3));
    }
    [Fact] public void ColumnFlexIntrinsicHeightMeasuresInlineSyntaxRunsInActualSharedLines()
    {
        var page=Page("<pre id=A><div class=toolbar>csharp</div><div class=viewport><code><span>using</span> <span>System</span>;\n\n<span>public</span> <span>class</span> <span>Example</span> {}</code></div></pre>","#A{display:flex;flex-direction:column;width:500px;font-size:14px;line-height:20px}.toolbar{height:40px;flex:0 0 auto}.viewport{display:flex}code{display:block;white-space:pre}");
        var list=Layout(page);Assert.Equal(100,list.GetBoundingClientRect(page.GetElementById("A")!).Height);
        Assert.Single(list.Commands,c=>c.Text=="using");Assert.Single(list.Commands,c=>c.Text=="Example");
    }
    [Fact] public void FlexItemPercentageMaxWidthUsesContainingBlockInsteadOfItsOwnBasis()
    {
        var page=Page("<div id=A><span id=B>Label text</span><span>Tools</span></div>","#A{display:flex;width:600px}#B{max-width:50%;flex-shrink:0}");var list=Layout(page);
        Assert.Equal(96,list.GetBoundingClientRect(page.GetElementById("B")!).Width,4);
    }
    [Fact] public void VariableOnlyBackgroundShorthandResolvesTheOriginalAuthorColor()
    {
        var page=Page("<div id=A></div>",":root{--surface:#323232}#A{height:10px;background:var(--surface,#ffffff)}");
        Assert.Equal(0x323232u,Layout(page).GetComputedStyle(page.GetElementById("A")!)!.Background.Rgb);
    }
    [Fact] public void WebsiteArticleRadialLengthStopStaysTransparentAndZeroCenterIsTheTopEdge()
    {
        var page=Page("<article id=A></article>",":root{--main-color:#512db4;--bg-color:white}article{width:80px;height:80px;background:radial-gradient(circle at 100% 0,color-mix(in srgb,var(--main-color) 10%,transparent),transparent 28rem),var(--bg-color)}");
        var list=Layout(page);var decoration=Assert.Single(list.Commands,c=>c.Decoration is not null).Decoration!;var gradient=Assert.Single(decoration.Gradients);
        Assert.Equal(1,gradient.CenterX);Assert.Equal(0,gradient.CenterY);
        Assert.Equal(0,gradient.Stops[^1].Color.Alpha);Assert.Equal(448/Math.Sqrt(80*80+80*80),gradient.Stops[^1].Position,6);
        Assert.Equal(.1,gradient.Sample(80,0,80,80).Alpha,6);Assert.InRange(gradient.Sample(0,0,80,80).Alpha,.07,.09);
        var pixels=new Pixels();BrowserRasterPainter.Paint(pixels,new(0,0,80,80),list);
        foreach(var point in new[]{(1,1),(40,20),(78,1),(20,70)})
        {uint rgb=pixels.At(point.Item1,point.Item2);Assert.InRange(rgb>>16&255,230u,255u);Assert.InRange(rgb>>8&255,230u,255u);Assert.InRange(rgb&255,240u,255u);}
    }
    [Theory][InlineData("transparent 20px",20)][InlineData("transparent 2rem",32)][InlineData("transparent 2em",32)]
    public void GradientLengthStopsUseTheirActualCssCoordinateSpace(string stop,double pixels)
    {
        var page=Page("<div id=A></div>","#A{width:80px;height:80px;font-size:16px;background:linear-gradient(90deg,red,"+stop+")}");var gradient=Assert.Single(Assert.Single(Layout(page).Commands,c=>c.Decoration is not null).Decoration!.Gradients);
        Assert.Equal(pixels/80,gradient.Stops[^1].Position,6);Assert.Equal(0,gradient.Sample(pixels+1,1,80,80).Alpha);
    }
    private sealed class Pixels:IDesktopCoverageCanvas
    {
        private readonly uint[] values=new uint[80*80];public int Width=>80;public int Height=>80;
        public uint At(int x,int y)=>values[y*80+x];
        public void Fill(uint rgb,int x,int y,int width,int height)
        {for(int py=Math.Max(0,y);py<Math.Min(80,y+height);py++)for(int px=Math.Max(0,x);px<Math.Min(80,x+width);px++)values[py*80+px]=rgb;}
        public void BlendCoverage(uint rgb,int x,int y,int width,int height,ReadOnlySpan<byte> mask)
        {
            for(int py=0;py<height;py++)for(int px=0;px<width;px++)
            {int xx=x+px,yy=y+py;if(xx<0||xx>=80||yy<0||yy>=80)continue;uint old=values[yy*80+xx];int alpha=mask[py*width+px];uint Channel(int shift)=>(uint)((((rgb>>shift)&255)*alpha+((old>>shift)&255)*(255-alpha)+127)/255);values[yy*80+xx]=Channel(16)<<16|Channel(8)<<8|Channel(0);}
        }
        public void Line(uint rgb,int x1,int y1,int x2,int y2){}public void Text(string text,uint rgb,int x,int y){}public void Present(){}
    }
}