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.Desktop;

namespace XFESystem.Browser.Tests;

public sealed class CssAffineLayoutTests
{
    private static IDocument Page(string html, string css) => new HtmlParser().ParseDocument("<style>body{margin:0}" + css + "</style>" + html);
    private static BrowserDisplayList Layout(IDocument page) => new BrowserLayoutEngine().Layout(page, new(128, 96));
    [Fact]
    public void NestedRowFlexIntrinsicWidthIncludesBothBlockItemsAndTheGap()
    {
        var page = Page("<div id=outer><a id=link><div id=icon></div><div><div>Site</div><div class=meta><cite id=url>domain.test / download</cite></div></div></a></div><h2 id=title>Title</h2>",
            "#outer,#link,.meta{display:flex}#outer{width:400px}#link{column-gap:8px}#icon{width:28px;height:28px}cite{font:14px monospace;line-height:20px}.meta{height:20px}h2{margin:0}");
        var list = Layout(page); var link = list.GetBoundingClientRect(page.GetElementById("link")!);
        var icon = list.GetBoundingClientRect(page.GetElementById("icon")!); var url = list.GetBoundingClientRect(page.GetElementById("url")!);
        Assert.Equal(28, icon.Width, 5);
        Assert.Equal(icon.Right + 8, url.X, 5);
        Assert.Equal("domain.test / download".Length * 14 * .6, url.Width, 5);
        Assert.Equal(url.Right, link.Right, 5);
        var runs = list.Commands.Where(c => c.Kind == BrowserDrawKind.Text && (c.Node?.ParentElement?.LocalName == "cite")).ToArray();
        Assert.Single(runs.Select(c => c.Bounds.Y).Distinct());
        Assert.True(runs.Max(c => c.Bounds.Bottom) <= list.GetBoundingClientRect(page.GetElementById("title")!).Top);
    }
    [Fact]
    public void IntrinsicCitationUsesTheSameSeparateGlyphRunsAsActualPaint()
    {
        var page = Page("<div id=outer><div id=meta><cite id=url>domain.<strong>test</strong> / download</cite></div></div><h2 id=title>Title</h2>",
            "#outer,#meta{display:flex}#outer{width:400px}cite,strong{font:14px monospace}#meta{height:20px}h2{margin:0}");
        var list = new BrowserLayoutEngine().Layout(page, new(500, 200), textMetrics: new ShapedMetrics());
        var runs = list.Commands.Where(c => c.Kind == BrowserDrawKind.Text && c.Node?.ParentElement?.Closest("cite") is not null).ToArray();
        Assert.Single(runs.Select(c => c.Bounds.Y).Distinct());
        Assert.True(runs.Max(c => c.Bounds.Bottom) <= list.GetBoundingClientRect(page.GetElementById("title")!).Top);
    }
    private sealed class ShapedMetrics : IBrowserTextMetrics
    {
        public double Measure(string text, BrowserFont font) => text.Length * 8 + (text == " " ? 1 : 0);
    }
    [Fact]
    public void PercentageUnderlineDoesNotInflateAutoInlineTabsButExplicitPercentageStillResolves()
    {
        var page = Page("<nav><ul><li id=first><a>Web</a></li><li id=second><a>Images</a></li></ul></nav><div id=explicit>Half</div>",
            "nav{height:40px;overflow:hidden}ul{margin:0;padding:0}li{display:inline-block;line-height:30px;margin-right:12px}li::after{content:'';display:block;width:100%;height:3px;background:red}#explicit{display:inline-block;width:50%}");
        var list = new BrowserLayoutEngine().Layout(page, new(500, 200));
        var first = list.GetBoundingClientRect(page.GetElementById("first")!); var second = list.GetBoundingClientRect(page.GetElementById("second")!);
        Assert.Equal(first.Y, second.Y, 5); Assert.InRange(first.Width, 20, 40); Assert.InRange(second.Width, 40, 65);
        Assert.Equal(250, list.GetBoundingClientRect(page.GetElementById("explicit")!).Width, 5);
        var underline = Assert.Single(list.Commands, c => c.Kind == BrowserDrawKind.Fill && c.Color == 0xFF0000 && c.Node == page.GetElementById("first"));
        Assert.Equal(first.Width, underline.Bounds.Width, 5);
    }
    [Theory]
    [InlineData("rotate(90deg)", 30, 0, 20, 40)]
    [InlineData("matrix(0,1,-1,0,0,0)", 30, 0, 20, 40)]
    [InlineData("scale(.5) translateX(50%)", 40, 15, 20, 10)]
    public void TransformOrderAndDefaultBorderBoxOriginMatchCss(string transform, double x, double y, double width, double height)
    {
        var page = Page("<div id=A></div><div id=B></div>", "#A{position:relative;margin:10px 20px;width:40px;height:20px;background:red;transform:" + transform + "}#B{height:5px}");
        var list = Layout(page); var r = list.GetBoundingClientRect(page.GetElementById("A")!);
        Assert.Equal(x, r.X, 5); Assert.Equal(y, r.Y, 5); Assert.Equal(width, r.Width, 5); Assert.Equal(height, r.Height, 5);
        Assert.Equal(40, list.GetBoundingClientRect(page.GetElementById("B")!).Top, 5); // Flow remains untransformed.
    }
    [Fact]
    public void NestedTransformsApplyOnceToTextRangesAndInverseHitTesting()
    {
        var page = Page("<div id=A><div id=B>abc</div></div>", "#A{position:absolute;left:40px;top:20px;width:40px;height:40px;transform-origin:0 0;transform:rotate(90deg)}#B{width:20px;height:10px;transform:translateX(10px)}");
        var list = Layout(page); var child = page.GetElementById("B")!; var r = list.GetBoundingClientRect(child);
        Assert.Equal(30, r.X, 5); Assert.Equal(30, r.Y, 5); Assert.Equal(10, r.Width, 5); Assert.Equal(20, r.Height, 5);
        Assert.Same(child, list.HitTest(35, 35)!.Element);
        var text = child.FirstChild!; var range = Assert.Single(list.GetRangeRects(text, 0, text, 3));
        Assert.True(range.Height > range.Width);
        Assert.Contains(list.Commands, c => c.Kind == BrowserDrawKind.Text && c.Transform is not null);
    }
    [Theory]
    [InlineData("scale(0)")]
    [InlineData("matrix(1,2,2,4,0,0)")]
    public void SingularTransformsHaveNoPixelsOrHitTarget(string transform)
    {
        var page = Page("<div id=A>Hidden</div>", "#A{width:50px;height:50px;transform:" + transform + ";background:red}");
        var list = Layout(page);
        Assert.DoesNotContain(list.Commands, c => c.Node?.ParentElement?.Id == "A" || (c.Node as IElement)?.Id == "A");
        Assert.NotEqual("A", list.HitTest(10, 10)?.Element?.Id);
    }
    [Fact]
    public void RotatedBorderChevronPaintsAndFreezesAtItsTransformedLocation()
    {
        var page = Page("<div id=A></div>", "#A{position:absolute;left:40px;top:20px;width:40px;height:40px;box-sizing:border-box;border-left:6px solid red;border-bottom:6px solid red;transform:scale(.25) translateX(25%) rotate(45deg)}");
        var list = Layout(page); var original = new Pixels(); var frozen = new Pixels();
        BrowserRasterPainter.Paint(original, new(0, 0, 128, 96), list);
        BrowserPaintSnapshot.Capture(list).Paint(frozen, new(0, 0, 128, 96));
        Assert.Equal(original.Data, frozen.Data);
        var ink = original.Data.Select((p, i) => (p, i)).Where(p => ((p.p >> 8) & 255) < 220).ToArray();
        Assert.InRange(ink.Length, 15, 80);
        Assert.All(ink, p => { Assert.InRange(p.i % 128, 54, 70); Assert.InRange(p.i / 128, 32, 48); });
        Assert.Equal(0xFFFFFFu, original.Data[20 * 128 + 40]); // Old untransformed L is gone.
    }
    [Fact]
    public void RotatedOverflowClipMasksEscapingStackedDescendants()
    {
        var page = Page("<div id=A><div id=B></div></div>", "#A{position:absolute;left:40px;top:20px;width:20px;height:20px;overflow:hidden;transform:rotate(45deg)}#B{position:relative;left:-10px;top:-10px;width:40px;height:40px;background:red;z-index:3}");
        var list = Layout(page); var pixels = new Pixels(); BrowserRasterPainter.Paint(pixels, new(0, 0, 128, 96), list);
        Assert.Equal(0xFF0000u, pixels.Data[30 * 128 + 50]);
        Assert.Equal(0xFFFFFFu, pixels.Data[17 * 128 + 37]); // Inside bounding rectangle, outside rotated diamond.
        Assert.NotEqual("B", list.HitTest(37, 17)?.Element?.Id);
        Assert.Equal("B", list.HitTest(50, 30)?.Element?.Id);
    }
    private sealed class Pixels : IDesktopCoverageCanvas
    {
        public uint[] Data = new uint[128 * 96];
        public int Width => 128;
        public int Height => 96;
        public void Fill(uint rgb, int x, int y, int width, int height)
        { for (int yy = Math.Max(0, y); yy < Math.Min(Height, y + height); yy++) for (int xx = Math.Max(0, x); xx < Math.Min(Width, x + width); xx++) Data[yy * Width + xx] = rgb; }
        public void BlendCoverage(uint rgb, int x, int y, int width, int height, ReadOnlySpan<byte> coverage)
        {
            for (int yy = 0; yy < height; yy++) for (int xx = 0; xx < width; xx++)
            {
                if (x + xx < 0 || x + xx >= Width || y + yy < 0 || y + yy >= Height) continue;
                uint old = Data[(y + yy) * Width + x + xx], a = coverage[yy * width + xx], result = 0;
                for (int shift = 0; shift < 24; shift += 8) result |= ((((rgb >> shift) & 255) * a + ((old >> shift) & 255) * (255 - a) + 127) / 255) << shift;
                Data[(y + yy) * Width + x + xx] = result;
            }
        }
        public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new NotSupportedException();
        public void Text(string text, uint rgb, int x, int y) => throw new NotSupportedException();
        public void Present() { }
    }
}