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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using AngleSharp.Dom;

namespace XFESystem.Browser;

public readonly record struct BrowserRect(double X, double Y, double Width, double Height)
{
    public double Left => X;
    public double Top => Y;
    public double Right => X + Width;
    public double Bottom => Y + Height;
    public bool Contains(double x, double y) => Width > 0 && Height > 0 && x >= X && y >= Y && x < Right && y < Bottom;
    public BrowserRect Translate(double x, double y) => new(X + x, Y + y, Width, Height);
    public BrowserRect Intersect(BrowserRect other)
    {
        double x = Math.Max(X, other.X), y = Math.Max(Y, other.Y);
        return new(x, y, Math.Max(0, Math.Min(Right, other.Right) - x), Math.Max(0, Math.Min(Bottom, other.Bottom) - y));
    }
    public BrowserRect Union(BrowserRect other)
    {
        if (Width <= 0 || Height <= 0) return other;
        if (other.Width <= 0 || other.Height <= 0) return this;
        double x = Math.Min(X, other.X), y = Math.Min(Y, other.Y);
        return new(x, y, Math.Max(Right, other.Right) - x, Math.Max(Bottom, other.Bottom) - y);
    }
}

public readonly record struct BrowserViewport(double Width, double Height, double ScrollX = 0, double ScrollY = 0);
/// <summary>Native input state, kept outside DOM attributes. Hover/active include their ancestor chain.</summary>
public sealed record BrowserInteractionState(INode? Hovered = null, INode? Active = null, INode? Focus = null);
public readonly record struct BrowserEdges(double Top, double Right, double Bottom, double Left)
{
    public double Horizontal => Left + Right;
    public double Vertical => Top + Bottom;
}
public readonly record struct BrowserFont(double Size = 16, string Family = "monospace", int Weight = 400, bool Italic = false);
public interface IBrowserTextMetrics { double Measure(string text, BrowserFont font); }

/// <summary>Host fallback only; native callers provide the actual renderer's font metrics.</summary>
public sealed class BrowserFallbackTextMetrics : IBrowserTextMetrics
{
    public double Measure(string text, BrowserFont font)
    {
        double units = 0;
        foreach (char character in text) if (!char.IsLowSurrogate(character)) units += character > 255 ? 1 : .6;
        return units * font.Size;
    }
}

public sealed record BrowserStyleSheet(string CssText, IElement? Owner = null, string? Source = null);
public enum BrowserDrawKind { Fill, Text, Border, PushClip, PopClip, Canvas }
public sealed record BrowserRoundedClip(BrowserRect Bounds, BrowserEdges Radius);

/// <summary>Bounds are CSS pixels in document coordinates. Border commands fill their exact side rectangle.</summary>
public sealed record BrowserDrawCommand(BrowserDrawKind Kind, BrowserRect Bounds, int NodeId, INode? Node,
    uint Color = 0, double Opacity = 1, string Text = "", BrowserFont Font = default, BrowserBoxDecoration? Decoration = null, BrowserRoundedClip? RoundedClip = null,
    bool ViewportPinned = false);

public sealed class BrowserLayoutBox
{
    public required INode Node { get; init; }
    public required int NodeId { get; init; }
    public required CssComputedStyle Style { get; init; }
    public BrowserRect ContentBox { get; internal set; }
    public BrowserRect PaddingBox { get; internal set; }
    public BrowserRect BorderBox { get; internal set; }
    public BrowserRect MarginBox { get; internal set; }
    public BrowserRect? Clip { get; internal set; }
    public bool Visible => Style.Visibility != "hidden" && Style.Display != "none" && EffectiveOpacity > 0;
    public IElement? Element => Node as IElement ?? Node.ParentElement;
    public IReadOnlyList<BrowserRect> Fragments => fragments;
    internal readonly List<BrowserRect> fragments = [];
    internal readonly List<BrowserLayoutBox> children = [];
    internal readonly List<BrowserDrawCommand> textCommands = [];
    internal readonly List<BrowserTextSourceRun> sourceRuns = [];
    internal bool IsLaidOut;
    internal BrowserEdges Padding, Border, Margin;
    internal CssComputedStyle? MarkerStyle;
    public string? GeneratedPseudo {get;internal set;}
    internal string? GeneratedText;
    internal IReadOnlyList<BrowserRoundedClip>? roundedClips;
    internal BrowserLayoutBox? Parent;
    internal double EffectiveOpacity = 1;
}
internal sealed record BrowserTextSourceRun(int CommandIndex, int[] Offsets);

public sealed class BrowserDisplayList
{
    internal BrowserDisplayList(BrowserViewport viewport, IReadOnlyList<BrowserDrawCommand> commands,
        IReadOnlyList<BrowserLayoutBox> boxes, IReadOnlyList<string> diagnostics, double documentWidth, double documentHeight,
        IReadOnlyList<BrowserLayoutBox>? allBoxes = null, IBrowserTextMetrics? textMetrics = null)
    {
        Viewport = viewport; Commands = commands; Boxes = boxes; Diagnostics = diagnostics;
        DocumentWidth = documentWidth; DocumentHeight = documentHeight;
        metrics = textMetrics ?? new BrowserFallbackTextMetrics();
        foreach (var box in allBoxes ?? boxes) if(box.GeneratedPseudo is null)byNode[box.Node] = box;
    }
    private readonly Dictionary<INode, BrowserLayoutBox> byNode = new(ReferenceEqualityComparer.Instance);
    private readonly IBrowserTextMetrics metrics;
    internal bool CanReuseForScroll(BrowserViewport target) => !Boxes.Any(box => box.Style.Position == "fixed" ||
        box.Style.Position == "sticky" &&
        (target.ScrollY != Viewport.ScrollY && (box.Style.Get("top", "auto") != "auto" || box.Style.Get("bottom", "auto") != "auto") ||
         target.ScrollX != Viewport.ScrollX && (box.Style.Get("left", "auto") != "auto" || box.Style.Get("right", "auto") != "auto")));
    internal BrowserDisplayList WithScroll(BrowserViewport viewport) => new(this, viewport);
    private BrowserDisplayList(BrowserDisplayList source, BrowserViewport viewport)
    {
        if (source.Viewport.Width != viewport.Width || source.Viewport.Height != viewport.Height ||
            !double.IsFinite(viewport.ScrollX) || !double.IsFinite(viewport.ScrollY) || !source.CanReuseForScroll(viewport))
            throw new ArgumentException("Only viewport scrolling of ordinary flow can reuse this layout.");
        Viewport = viewport; Boxes = source.Boxes; Diagnostics = source.Diagnostics;
        DocumentWidth = source.DocumentWidth; DocumentHeight = source.DocumentHeight;
        metrics = source.metrics; byNode = source.byNode; RangePositions = source.RangePositions;
        Commands = source.Commands.Select(command => command.ViewportPinned
            ? command with { Bounds = new(viewport.ScrollX, viewport.ScrollY, viewport.Width, viewport.Height) }
            : command).ToArray();
    }
    internal Dictionary<INode, (long Start, long End)>? RangePositions;
    public BrowserViewport Viewport { get; }
    public IReadOnlyList<BrowserDrawCommand> Commands { get; }
    public IReadOnlyList<BrowserLayoutBox> Boxes { get; }
    public IReadOnlyList<string> Diagnostics { get; }
    public double DocumentWidth { get; }
    public double DocumentHeight { get; }
    public BrowserRect GetBoundingClientRect(INode node) => byNode.TryGetValue(node, out var box)
        ? box.BorderBox.Translate(-Viewport.ScrollX, -Viewport.ScrollY) : default;
    public IReadOnlyList<BrowserRect> GetClientRects(INode node) => byNode.TryGetValue(node, out var box)
        ? box.fragments.Select(rect => rect.Translate(-Viewport.ScrollX, -Viewport.ScrollY)).ToArray() : [];
    public CssComputedStyle? GetComputedStyle(INode node) => byNode.TryGetValue(node, out var box) ? box.Style : null;
    public IReadOnlyList<BrowserRect> GetRangeRects(INode start, int startOffset, INode end, int endOffset) =>
        BrowserRangeGeometry.Measure(this, start, startOffset, end, endOffset, metrics);
    public IReadOnlyList<BrowserRect> GetRangeRects(INode start, int startOffset, INode end, int endOffset, IBrowserTextMetrics textMetrics) =>
        BrowserRangeGeometry.Measure(this, start, startOffset, end, endOffset, textMetrics);
    public IReadOnlyList<BrowserRect> GetRangeClientRects(INode start, int startOffset, INode end, int endOffset) => GetRangeRects(start, startOffset, end, endOffset);
    public BrowserLayoutBox? HitTest(double viewportX, double viewportY)
    {
        double x = viewportX + Viewport.ScrollX, y = viewportY + Viewport.ScrollY;
        for (int index = Boxes.Count - 1; index >= 0; index--)
        {
            var box = Boxes[index];
            if (box.Visible && box.Style.PointerEvents != "none" && box.BorderBox.Contains(x, y) &&
                (box.Clip is not { } clip || clip.Contains(x, y)) &&
                (box.roundedClips is null || box.roundedClips.All(shape=>BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,x,y)>.5))) return box;
        }
        return null;
    }
}

public sealed record BrowserCssLimits
{
    public int MaximumNodes { get; init; } = 20000;
    public int MaximumDepth { get; init; } = 256;
    public int MaximumCssCharacters { get; init; } = 2097152;
    // A complete stylesheet set may include thousands of generated-content
    // selectors in addition to its normal rules. Keep a finite default large
    // enough to parse both; selector work still has its independent budget.
    public int MaximumRules { get; init; } = 8192;
    public int MaximumDeclarations { get; init; } = 65536;
    public int MaximumSelectorEvaluations { get; init; } = 2000000;
    public int MaximumDisplayCommands { get; init; } = 100000;
}