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

XFESystem

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

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

/// <summary>CSS 2D column-vector matrix. Multiplication preserves CSS function order.</summary>
public readonly record struct BrowserAffineTransform(double A, double B, double C, double D, double E, double F)
{
    public static BrowserAffineTransform Identity => new(1, 0, 0, 1, 0, 0);
    public bool IsIdentity => this == Identity;
    public bool IsTranslation => A == 1 && B == 0 && C == 0 && D == 1;
    public (double X, double Y) Point(double x, double y) => (A * x + C * y + E, B * x + D * y + F);
    public BrowserRect Bounds(BrowserRect r)
    {
        var p = Point(r.Left, r.Top); var q = Point(r.Right, r.Top);
        var s = Point(r.Left, r.Bottom); var t = Point(r.Right, r.Bottom);
        double left = Math.Min(Math.Min(p.X, q.X), Math.Min(s.X, t.X)), top = Math.Min(Math.Min(p.Y, q.Y), Math.Min(s.Y, t.Y));
        return new(left, top, Math.Max(Math.Max(p.X, q.X), Math.Max(s.X, t.X)) - left, Math.Max(Math.Max(p.Y, q.Y), Math.Max(s.Y, t.Y)) - top);
    }
    public static BrowserAffineTransform operator *(BrowserAffineTransform x, BrowserAffineTransform y) => new(
        x.A * y.A + x.C * y.B, x.B * y.A + x.D * y.B, x.A * y.C + x.C * y.D, x.B * y.C + x.D * y.D,
        x.A * y.E + x.C * y.F + x.E, x.B * y.E + x.D * y.F + x.F);
    public static BrowserAffineTransform Translation(double x, double y) => new(1, 0, 0, 1, x, y);
    public bool TryInverse(out BrowserAffineTransform inverse)
    {
        double determinant = A * D - B * C;
        if (determinant == 0 || !double.IsFinite(determinant)) { inverse = default; return false; }
        inverse = new(D / determinant, -B / determinant, -C / determinant, A / determinant,
            (C * F - D * E) / determinant, (B * E - A * F) / determinant);
        return inverse.Finite;
    }
    internal bool Finite => double.IsFinite(A) && double.IsFinite(B) && double.IsFinite(C) && double.IsFinite(D) && double.IsFinite(E) && double.IsFinite(F);
    internal static bool TryParse(string text, BrowserRect reference, CssComputedStyle style, out BrowserAffineTransform matrix)
    {
        matrix = Identity; text = text.Trim().ToLowerInvariant();
        if (text == "none") return true;
        int count = 0;
        for (int offset = 0; offset < text.Length;)
        {
            if (++count > 64) return false;
            int open = text.IndexOf('(', offset); if (open < 0) return false;
            string name = text[offset..open].Trim(); int depth = 1, close = open + 1;
            while (close < text.Length && depth > 0) { if (text[close] == '(') depth++; else if (text[close] == ')') depth--; close++; }
            if (depth != 0) return false;
            var parts = new List<string>(); string args = text[(open + 1)..(close - 1)]; int start = 0; depth = 0;
            for (int index = 0; index <= args.Length; index++)
            {
                if (index == args.Length || depth == 0 && (args[index] == ',' || char.IsWhiteSpace(args[index])))
                { if (index > start) parts.Add(args[start..index].Trim()); start = index + 1; }
                else if (args[index] == '(') depth++; else if (args[index] == ')') depth--;
            }
            double Number(int index) => index < parts.Count ? CssValue.Number(parts[index], double.NaN) : double.NaN;
            double Length(int index, double basis) => index < parts.Count ? CssValue.Length(parts[index], basis, style.Font.Size, style.Viewport, 16) ?? double.NaN : double.NaN;
            double Angle(int index)
            {
                if (index >= parts.Count) return double.NaN;
                string part = parts[index];
                foreach (var unit in new[] { ("deg", Math.PI / 180), ("grad", Math.PI / 200), ("rad", 1d), ("turn", Math.PI * 2) })
                    if (part.EndsWith(unit.Item1, StringComparison.Ordinal)) return CssValue.Number(part[..^unit.Item1.Length], double.NaN) * unit.Item2;
                return part == "0" ? 0 : double.NaN;
            }
            BrowserAffineTransform next;
            switch (name)
            {
                case "translate" when parts.Count is 1 or 2: next = Translation(Length(0, reference.Width), parts.Count == 1 ? 0 : Length(1, reference.Height)); break;
                case "translatex" when parts.Count == 1: next = Translation(Length(0, reference.Width), 0); break;
                case "translatey" when parts.Count == 1: next = Translation(0, Length(0, reference.Height)); break;
                case "translate3d" when parts.Count == 3 && Length(2, 0) == 0: next = Translation(Length(0, reference.Width), Length(1, reference.Height)); break;
                case "scale" when parts.Count is 1 or 2: next = new(Number(0), 0, 0, parts.Count == 1 ? Number(0) : Number(1), 0, 0); break;
                case "scalex" when parts.Count == 1: next = new(Number(0), 0, 0, 1, 0, 0); break;
                case "scaley" when parts.Count == 1: next = new(1, 0, 0, Number(0), 0, 0); break;
                case "rotate" when parts.Count == 1:
                    double angle = Angle(0); next = new(Math.Cos(angle), Math.Sin(angle), -Math.Sin(angle), Math.Cos(angle), 0, 0); break;
                case "skew" when parts.Count is 1 or 2: next = new(1, parts.Count == 1 ? 0 : Math.Tan(Angle(1)), Math.Tan(Angle(0)), 1, 0, 0); break;
                case "skewx" when parts.Count == 1: next = new(1, 0, Math.Tan(Angle(0)), 1, 0, 0); break;
                case "skewy" when parts.Count == 1: next = new(1, Math.Tan(Angle(0)), 0, 1, 0, 0); break;
                case "matrix" when parts.Count == 6: next = new(Number(0), Number(1), Number(2), Number(3), Number(4), Number(5)); break;
                default: return false;
            }
            matrix *= next;
            if (!matrix.Finite || Math.Abs(matrix.A) > 1000000 || Math.Abs(matrix.B) > 1000000 || Math.Abs(matrix.C) > 1000000 || Math.Abs(matrix.D) > 1000000 || Math.Abs(matrix.E) > 1000000 || Math.Abs(matrix.F) > 1000000) return false;
            offset = close; while (offset < text.Length && char.IsWhiteSpace(text[offset])) offset++;
        }
        return count > 0;
    }
    internal static (double X, double Y) Origin(CssComputedStyle style, BrowserRect box)
    {
        var parts = style.Get("transform-origin", "50% 50%").Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
        if (parts.Length is < 1 or > 3) return (box.X + box.Width / 2, box.Y + box.Height / 2);
        string x = parts[0], y = parts.Length > 1 ? parts[1] : "center";
        if (x is "top" or "bottom" || y is "left" or "right") (x, y) = (y, x);
        double Resolve(string value, double basis) => value switch { "center" => basis / 2, "left" or "top" => 0, "right" or "bottom" => basis,
            _ => CssValue.Length(value, basis, style.Font.Size, style.Viewport, 16) ?? basis / 2 };
        return (box.X + Resolve(x, box.Width), box.Y + Resolve(y, box.Height));
    }
}