"""
SVG 路径平滑脚本
- 读取复合 path，拆分成子路径
- 对每个子路径应用 RDP (Ramer-Douglas-Peucker) 简化，去掉锯齿
- 用 Catmull-Rom 样条转三次贝塞尔曲线，使边缘平滑
- 输出新的 SVG 文件
"""
import re
import math
import xml.etree.ElementTree as ET
import numpy as np

INPUT_PATH = r"C:\Users\jwj_5\Downloads\cat_mandala_safe_v2.svg\cat_mandala_safe_v2.svg"
OUTPUT_PATH = r"C:\Users\jwj_5\Downloads\cat_mandala_safe_v2.svg\cat_mandala_smoothed.svg"

NS = {'svg': 'http://www.w3.org/2000/svg'}

# ===================== 参数 =====================
RDP_EPSILON = 1.5      # RDP 简化容差（SVG 单位），越大越简化
SMOOTH_TENSION = 0.5  # Catmull-Rom 张力 0~1，越小越平滑
MIN_POINTS = 3         # 子路径最少保留点数
# ================================================


def split_subpaths(d):
    """按 M/m 拆分子路径"""
    parts = re.split(r'(?=[Mm])', d)
    return [p.strip() for p in parts if p.strip()]


def parse_subpath(sub):
    """解析子路径为点列表"""
    pts = []
    cx, cy = 0.0, 0.0
    cmd = None
    for tok in re.split(r'([MmLlHhVvCcSsQqTtAaZz])', sub):
        tok = tok.strip()
        if not tok:
            continue
        if tok in 'MmLlHhVvCcSsQqTtAaZz':
            cmd = tok
            continue
        vals = [float(v) for v in re.findall(r'[-+]?\d*\.?\d+', tok)]
        if not vals:
            continue
        if cmd in 'Mm':
            for k in range(0, len(vals) - 1, 2):
                if cmd == 'm' and pts:
                    cx += vals[k]; cy += vals[k + 1]
                else:
                    cx, cy = vals[k], vals[k + 1]
                pts.append((cx, cy))
        elif cmd in 'Ll':
            for k in range(0, len(vals) - 1, 2):
                if cmd == 'l':
                    cx += vals[k]; cy += vals[k + 1]
                else:
                    cx, cy = vals[k], vals[k + 1]
                pts.append((cx, cy))
        elif cmd in 'Hh':
            for v in vals:
                cx = cx + v if cmd == 'h' else v
                pts.append((cx, cy))
        elif cmd in 'Vv':
            for v in vals:
                cy = cy + v if cmd == 'v' else v
                pts.append((cx, cy))
        elif cmd in 'Cc':
            for k in range(0, len(vals) - 5, 6):
                if cmd == 'c':
                    cx += vals[k + 4]; cy += vals[k + 5]
                else:
                    cx, cy = vals[k + 4], vals[k + 5]
                pts.append((cx, cy))
        elif cmd in 'Ss':
            for k in range(0, len(vals) - 3, 4):
                if cmd == 's':
                    cx += vals[k + 2]; cy += vals[k + 3]
                else:
                    cx, cy = vals[k + 2], vals[k + 3]
                pts.append((cx, cy))
        elif cmd in 'Qq':
            for k in range(0, len(vals) - 3, 4):
                if cmd == 'q':
                    cx += vals[k + 2]; cy += vals[k + 3]
                else:
                    cx, cy = vals[k + 2], vals[k + 3]
                pts.append((cx, cy))
        elif cmd in 'Tt':
            for k in range(0, len(vals) - 1, 2):
                if cmd == 't':
                    cx += vals[k]; cy += vals[k + 1]
                else:
                    cx, cy = vals[k], vals[k + 1]
                pts.append((cx, cy))
        elif cmd in 'Aa':
            for k in range(0, len(vals) - 6, 7):
                if cmd == 'a':
                    cx += vals[k + 5]; cy += vals[k + 6]
                else:
                    cx, cy = vals[k + 5], vals[k + 6]
                pts.append((cx, cy))
    return pts


def is_closed(sub):
    s = sub.rstrip()
    return s.endswith('Z') or s.endswith('z')


# ---------- RDP 简化 ----------
def rdp(points, epsilon):
    """Ramer-Douglas-Peucker 简化（numpy 版）"""
    if len(points) < 3:
        return points
    pts = np.array(points, dtype=np.float64)

    def _rdp_recursive(pts, epsilon):
        if len(pts) < 3:
            return pts
        start = pts[0]
        end = pts[-1]
        # 点到线段距离
        if np.allclose(start, end):
            dists = np.linalg.norm(pts - start, axis=1)
        else:
            line = end - start
            line_len = np.linalg.norm(line)
            line_unit = line / line_len
            vecs = pts - start
            proj = np.dot(vecs, line_unit)
            proj = np.clip(proj, 0, line_len)
            proj_pts = start + np.outer(proj, line_unit)
            dists = np.linalg.norm(pts - proj_pts, axis=1)
        max_idx = np.argmax(dists)
        max_dist = dists[max_idx]
        if max_dist < epsilon:
            return np.array([start, end])
        left = _rdp_recursive(pts[:max_idx + 1], epsilon)
        right = _rdp_recursive(pts[max_idx:], epsilon)
        return np.vstack([left[:-1], right])

    result = _rdp_recursive(pts, epsilon)
    return [tuple(p) for p in result]


# ---------- Catmull-Rom 转贝塞尔 ----------
def catmull_rom_to_bezier(points, closed, tension=0.5):
    """
    将点序列转换为平滑的三次贝塞尔曲线段
    返回 [(p0, c1, c2, p1), ...] 的列表
    """
    n = len(points)
    if n < 2:
        return []
    if n == 2:
        return [(points[0], points[0], points[1], points[1])]

    # 为闭合/开放路径准备扩展点
    if closed:
        ext = [points[-1]] + list(points) + [points[0], points[1]]
        seg_count = n
    else:
        ext = [points[0]] + list(points) + [points[-1]]
        seg_count = n - 1

    segments = []
    for i in range(seg_count):
        p0 = ext[i]
        p1 = ext[i + 1]
        p2 = ext[i + 2]
        p3 = ext[i + 3]

        # Catmull-Rom 控制点
        c1 = (p1[0] + (p2[0] - p0[0]) * tension / 6,
              p1[1] + (p2[1] - p0[1]) * tension / 6)
        c2 = (p2[0] - (p3[0] - p1[0]) * tension / 6,
              p2[1] - (p3[1] - p1[1]) * tension / 6)

        segments.append((p1, c1, c2, p2))
    return segments


def segments_to_d(segments, closed):
    """将贝塞尔段转为 SVG path d 字符串"""
    if not segments:
        return ""
    parts = []
    first_p = segments[0][0]
    parts.append(f"M{first_p[0]:.2f},{first_p[1]:.2f}")
    for p0, c1, c2, p1 in segments:
        parts.append(f"C{c1[0]:.2f},{c1[1]:.2f} {c2[0]:.2f},{c2[1]:.2f} {p1[0]:.2f},{p1[1]:.2f}")
    if closed:
        parts.append("Z")
    return "".join(parts)


def main():
    print(f"读取: {INPUT_PATH}")
    tree = ET.parse(INPUT_PATH)
    root = tree.getroot()

    paths = root.findall('.//svg:path', NS)
    if not paths:
        print("未找到 path 元素")
        return

    total_in = 0
    total_out = 0
    sub_count = 0

    # 处理每条 path
    for path_el in paths:
        d = path_el.get('d', '')
        subs = split_subpaths(d)
        new_d_parts = []

        for sub in subs:
            pts = parse_subpath(sub)
            if len(pts) < 2:
                continue
            closed = is_closed(sub)
            total_in += len(pts)
            sub_count += 1

            # RDP 简化
            simplified = rdp(pts, RDP_EPSILON)
            if len(simplified) < MIN_POINTS:
                # 太小的子路径保留原样（避免碎点丢失后出错）
                simplified = pts

            # 转为平滑贝塞尔
            segments = catmull_rom_to_bezier(simplified, closed, SMOOTH_TENSION)
            sub_d = segments_to_d(segments, closed)
            total_out += len(simplified)
            new_d_parts.append(sub_d)

        path_el.set('d', "".join(new_d_parts))

    # 写出
    ET.register_namespace('', 'http://www.w3.org/2000/svg')
    tree.write(OUTPUT_PATH, encoding='utf-8', xml_declaration=True)

    print(f"处理完成")
    print(f"  子路径数: {sub_count}")
    print(f"  原始总节点: {total_in}")
    print(f"  简化后节点: {total_out}")
    print(f"  节点减少: {total_in - total_out} ({(1-total_out/total_in)*100:.1f}%)")
    print(f"  RDP epsilon: {RDP_EPSILON}")
    print(f"  平滑张力: {SMOOTH_TENSION}")
    print(f"输出: {OUTPUT_PATH}")


if __name__ == '__main__':
    main()
