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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Browser;

public sealed record BrowserGradientStop(CssColor Color, double Position);
/// <summary>Finite, non-repeating CSS linear and circular radial gradient layers.</summary>
public sealed record BrowserCssGradient(bool Radial, double Angle, double CenterX, double CenterY, IReadOnlyList<BrowserGradientStop> Stops)
{
    internal static IReadOnlyList<BrowserCssGradient> Parse(string text, CssComputedStyle style, BrowserRect? box = null)
    {
        if (!text.Contains("gradient(",StringComparison.OrdinalIgnoreCase)) return [];
        var result = new List<BrowserCssGradient>();
        foreach (string raw in CssValue.Split(text, ','))
        {
            string layer = raw.Trim(); bool radial = layer.StartsWith("radial-gradient(", StringComparison.OrdinalIgnoreCase);
            int open = layer.IndexOf('(');
            if ((!radial && !layer.StartsWith("linear-gradient(", StringComparison.OrdinalIgnoreCase)) || !layer.EndsWith(')') || open < 0 || result.Count >= 8) continue;
            string[] parts = CssValue.Split(layer[(open + 1)..^1], ',').Select(part => part.Trim()).ToArray();
            if (parts.Length < 2 || parts.Length > 64) continue;
            double angle = 180, centerX = .5, centerY = .5; int start = 0;
            double width=box?.Width??style.Viewport.Width,height=box?.Height??style.Viewport.Height;
            if (!radial && parts[0].EndsWith("deg", StringComparison.OrdinalIgnoreCase)) { angle = CssValue.Number(parts[0][..^3], 180); start = 1; }
            else if (!radial && parts[0].StartsWith("to ", StringComparison.OrdinalIgnoreCase))
            {
                string direction = parts[0][3..]; bool top = direction.Contains("top"), right = direction.Contains("right"), bottom = direction.Contains("bottom"), left = direction.Contains("left");
                angle = top ? right ? 45 : left ? 315 : 0 : bottom ? right ? 135 : left ? 225 : 180 : right ? 90 : left ? 270 : 180; start = 1;
            }
            else if (radial && (parts[0].StartsWith("circle", StringComparison.OrdinalIgnoreCase) || parts[0].StartsWith("at ", StringComparison.OrdinalIgnoreCase)))
            {
                string shape = parts[0]; int at = shape.IndexOf("at ", StringComparison.OrdinalIgnoreCase);
                if (at >= 0)
                {
                    string[] position = shape[(at + 3)..].Split(' ', StringSplitOptions.RemoveEmptyEntries);
                    double Position(string value, double fallback,double basis) => value.EndsWith('%') ? CssValue.Number(value[..^1],fallback * 100) / 100 : value switch { "left" or "top" => 0, "right" or "bottom" => 1, "center" => .5, _ => CssValue.Length(value,basis,style.Font.Size,style.Viewport,16) is { } length&&basis>0?length/basis:fallback };
                    if (position.Length > 0) centerX = Position(position[0],.5,width);
                    if (position.Length > 1) centerY = Position(position[1],.5,height);
                }
                start = 1;
            }
            double cx=centerX*width,cy=centerY*height;
            double rx=Math.Max(Math.Abs(cx),Math.Abs(width-cx)),ry=Math.Max(Math.Abs(cy),Math.Abs(height-cy));
            double radians=angle*Math.PI/180;
            double gradientLength=radial?Math.Sqrt(rx*rx+ry*ry):Math.Abs(width*Math.Sin(radians))+Math.Abs(height*Math.Cos(radians));
            var colors = new List<CssColor>(); var positions = new List<double?>();
            for (int i = start; i < parts.Length; i++)
            {
                string[] words = CssValue.Split(parts[i], ' ').ToArray(); if (words.Length == 0) continue;
                double? position = words.Length > 1 ? words[^1].EndsWith('%') ? CssValue.Number(words[^1][..^1], double.NaN) / 100 :
                    CssValue.Length(words[^1],gradientLength,style.Font.Size,style.Viewport,16) is { } length&&gradientLength>0?length/gradientLength:null : null;
                if (position is { } number && !double.IsFinite(number)) position = null;
                string color = position is null ? parts[i] : string.Join(' ', words.Take(words.Length - 1));
                colors.Add(CssValue.Color(color,style.Color)); positions.Add(position);
            }
            if (colors.Count < 2) continue;
            positions[0] ??= 0; positions[^1] ??= 1;
            double previous = positions[0]!.Value;
            for (int i = 1; i < positions.Count; i++) if (positions[i] is { } specified) { positions[i] = Math.Max(previous, specified); previous = positions[i]!.Value; }
            int anchor = 0;
            for (int i = 1; i < positions.Count; i++)
                if (positions[i] is { } end)
                { double begin = positions[anchor]!.Value; for (int gap = anchor + 1; gap < i; gap++) positions[gap] = begin + (end - begin) * (gap - anchor) / (i - anchor); anchor = i; }
            result.Add(new(radial,angle,centerX,centerY, colors.Select((color,index)=>new BrowserGradientStop(color,positions[index]!.Value)).ToArray()));
        }
        return result.ToArray();
    }
    public CssColor Sample(double x, double y, double width, double height)
        => Prepare(width, height).Sample(x, y);

    // Box geometry is constant for every pixel of a gradient layer. Prepare it
    // once per paint instead of repeating trigonometry and radius calculations
    // for each device pixel. Keep the original arithmetic order for sampling.
    internal BrowserGradientSampler Prepare(double width, double height) => new(this, width, height);
}

internal readonly struct BrowserGradientSampler
{
    private readonly BrowserCssGradient gradient;
    private readonly double halfWidth, halfHeight, centerX, centerY, dx, dy, length;
    public BrowserGradientSampler(BrowserCssGradient gradient, double width, double height)
    {
        this.gradient = gradient;
        halfWidth = width / 2; halfHeight = height / 2;
        centerX = gradient.CenterX * width; centerY = gradient.CenterY * height;
        if (gradient.Radial)
        {
            double rx = Math.Max(Math.Abs(centerX), Math.Abs(width - centerX));
            double ry = Math.Max(Math.Abs(centerY), Math.Abs(height - centerY));
            length = Math.Sqrt(rx * rx + ry * ry); dx = dy = 0;
        }
        else
        {
            double radians = gradient.Angle * Math.PI / 180;
            dx = Math.Sin(radians); dy = -Math.Cos(radians);
            length = Math.Abs(width * dx) + Math.Abs(height * dy);
        }
    }
    public CssColor Sample(double x, double y)
    {
        double position;
        if (gradient.Radial) position = length <= 0 ? 1 : Math.Sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)) / length;
        else position = length <= 0 ? 1 : .5 + ((x - halfWidth) * dx + (y - halfHeight) * dy) / length;
        var Stops = gradient.Stops;
        if (position<=Stops[0].Position) return Stops[0].Color;
        for(int i=1;i<Stops.Count;i++)
            if(position<=Stops[i].Position)
            { double length=Stops[i].Position-Stops[i-1].Position; return CssValue.Mix(Stops[i-1].Color,Stops[i].Color,length<=0?0:1-(position-Stops[i-1].Position)/length); }
        return Stops[^1].Color;
    }
}