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;

/// <summary>DOM UTF-16 boundary offsets mapped to actual laid-out text runs and renderer metrics.</summary>
internal static class BrowserRangeGeometry
{
    public static IReadOnlyList<BrowserRect> Measure(BrowserDisplayList list, INode start, int startOffset, INode end, int endOffset, IBrowserTextMetrics metrics)
    {
        ArgumentNullException.ThrowIfNull(start); ArgumentNullException.ThrowIfNull(end); ArgumentNullException.ThrowIfNull(metrics);
        INode root = start; while (root.Parent is not null) root = root.Parent;
        INode endRoot = end; while (endRoot.Parent is not null) endRoot = endRoot.Parent;
        if (!ReferenceEquals(root, endRoot)) return [];
        if (list.Boxes.Count == 0) return [];
        INode layoutRoot = list.Boxes[0].Node; while (layoutRoot.Parent is not null) layoutRoot = layoutRoot.Parent;
        if (!ReferenceEquals(root, layoutRoot)) return [];
        var positions = list.RangePositions ?? new Dictionary<INode, (long Start, long End)>(ReferenceEqualityComparer.Instance); long cursor = 0;
        void Visit(INode node)
        {
            long begin = cursor;
            if (node.NodeType == NodeType.Text) cursor += node.TextContent.Length;
            else foreach (var child in node.ChildNodes) Visit(child);
            positions[node] = (begin, cursor);
        }
        if (list.RangePositions is null) { Visit(root); list.RangePositions = positions; }
        if (!positions.ContainsKey(start) || !positions.ContainsKey(end)) return [];
        long Boundary(INode node, int offset)
        {
            if (offset < 0 || node.NodeType == NodeType.Text && offset > node.TextContent.Length || node.NodeType != NodeType.Text && offset > node.ChildNodes.Length)
                throw new ArgumentOutOfRangeException(nameof(offset), "DOM range boundary is outside its container.");
            var position = positions[node];
            if (node.NodeType == NodeType.Text) return position.Start + offset;
            return offset == 0 ? position.Start : positions[node.ChildNodes[offset - 1]].End;
        }
        long rangeStart = Boundary(start, startOffset), rangeEnd = Boundary(end, endOffset);
        if (rangeStart > rangeEnd) return [];
        var result = new List<BrowserRect>(); bool collapsed = ReferenceEquals(start, end) && startOffset == endOffset;
        BrowserAffineTransform transform = BrowserAffineTransform.Identity;
        BrowserRect Client(BrowserRect rectangle) => transform.Bounds(rectangle).Translate(-list.Viewport.ScrollX, -list.Viewport.ScrollY);
        foreach (var box in list.Boxes.Where(box => box.Node.NodeType == NodeType.Text && box.IsLaidOut).OrderBy(box => positions[box.Node].Start))
        {
            transform = box.Transform;
            var position = positions[box.Node];
            if (position.End < rangeStart || position.Start > rangeEnd) continue;
            int localStart = (int)Math.Clamp(rangeStart - position.Start, 0, box.Node.TextContent.Length);
            int localEnd = (int)Math.Clamp(rangeEnd - position.Start, 0, box.Node.TextContent.Length);
            if (collapsed && !ReferenceEquals(box.Node, start) && start.NodeType == NodeType.Text) continue;
            BrowserRect? lastCaret = null;
            foreach (var source in box.sourceRuns)
            {
                var command = box.textCommands[source.CommandIndex]; var offsets = source.Offsets;
                if (offsets.Length != command.Text.Length + 1 || offsets[0] == offsets[^1]) continue;
                double Width(int count)
                {
                    double value = metrics.Measure(command.Text[..count], command.Font);
                    if (!double.IsFinite(value) || value < 0 || value > 1000000) throw new BrowserLimitException("Invalid text range metrics.");
                    return value;
                }
                int Index(int offset) { int index = 0; while (index < offsets.Length - 1 && offsets[index] < offset) index++; return index; }
                if (collapsed)
                {
                    if (localStart < offsets[0]) { result.Add(Client(command.Bounds with { Width = 0 })); return result; }
                    if (localStart <= offsets[^1])
                    {
                        var caret = new BrowserRect(command.Bounds.X + Width(Index(localStart)), command.Bounds.Y, 0, command.Bounds.Height);
                        if (localStart < offsets[^1]) { result.Add(Client(caret)); return result; }
                        lastCaret = caret;
                    }
                    continue;
                }
                if (localEnd <= offsets[0] || localStart >= offsets[^1]) continue;
                int first = Index(Math.Max(localStart, offsets[0])), last = Index(Math.Min(localEnd, offsets[^1]));
                double left = Width(first), right = Width(last);
                if (last > first) result.Add(Client(new(command.Bounds.X + left, command.Bounds.Y, Math.Max(0, right - left), command.Bounds.Height)));
            }
            if (collapsed)
            {
                if (lastCaret is { } caret) result.Add(Client(caret));
                else if (ReferenceEquals(box.Node, start) && box.sourceRuns.Count == 0) result.Add(Client(box.BorderBox with { Width = 0, Height = box.Style.LineHeight }));
                if (result.Count > 0) return result;
            }
        }
        return result;
    }
}